Compiled DEFUN not equivalent to interpreted wrt missing arguments
(CL:DEFUN FOO (A B) (CONS A B))
(FOO 1) gives an error "Too few arguments to FOO"
(CL:COMPILE 'FOO)
(FOO 1) returns (1), no error
Regarding: signaling errors when functions are called with argument lists that do not match the lambda list. The behavior was not specified in the ANSI Common Lisp standard X3J13 until June 1990. (See: Issue ARGUMENT-MISMATCH-ERROR Writeup). Medley doesn't claim X3J134 conformance. The implementation predates this cleanup of the standard.
That some compiled DEFUN functions do give errors on argument list mismatch seems to be cases where the form of the lambda list requires treating the function as a lambda nospread; e.g. the inclusion of &REST in the lambda list.
I propose this be marked "wontfix".
I'd put this down as a HARD problem to solve in going from Medley today to full ANSI Common Lisp compatibility. Fixing it -- adding error checking in the function call opcodes -- requires changes to maiko. Maybe we need another category of "bugs" which are "full ANSI CL compatibility" rather than "wontfix"?
now, it might be possible to fix CL:COMPILE to issue a warning... so that in the FOO example:
(CL:DEFUN BAR () (FOO 1 2 3 4)) (CL:COMPILE 'BAR)
would add a WARNING ... but I don't know if we pay much attention to warnings.
What do other Common Lisp implementations do?
Actually, this may have already been in the documentation consistent with Medley documentation? that calls to compiled functions in Medley are "unsafe"
We could go the other way, remove this error checking in the interpreted invocations, if that's easier. I agree it would be nice to remove at least this incompatibility with ANSI Common Lisp. But one way or another, I would like to see this incompatibility between compiled and interpreted in Medley go away.
On Oct 2, 2025, at 8:42 AM, Larry Masinter @.***> wrote:
masinter left a comment (Interlisp/medley#2305) https://github.com/Interlisp/medley/issues/2305#issuecomment-3361922707 I'd put this down as a HARD problem to solve in going from Medley today to full ANSI Common Lisp compatibility. Fixing it -- adding error checking in the function call opcodes -- requires changes to maiko. Maybe we need another category of "bugs" which are "full ANSI CL compatibility" rather than "wontfix"?
now, it might be possible to fix CL:COMPILE to issue a warning... so that in the FOO example:
(CL:DEFUN BAR () (FOO 1 2 3 4)) (CL:COMPILE 'BAR)
would add a WARNING ... but I don't know if we pay much attention to warnings.
What do other Common Lisp implementations do?
— Reply to this email directly, view it on GitHub https://github.com/Interlisp/medley/issues/2305#issuecomment-3361922707, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQSTUJIC3O5BO6NENRF3AU33VVBVJAVCNFSM6AAAAACH5PKGVKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTGNRRHEZDENZQG4. You are receiving this because you authored the thread.
SBCL checks the argument count for compiled code as well as interpreted, and also notes when the act of defining a function references another with the wrong number of arguments --
* (defun bar () (foo 1 2 3 4))
; in: DEFUN BAR
; (FOO 1 2 3 4)
;
; caught STYLE-WARNING:
; The function FOO is called with four arguments, but wants exactly two.
;
; compilation unit finished
; caught 1 STYLE-WARNING condition
BAR
SBCL checks the argument count for compiled code as well as interpreted
Just so we understand exactly what it does:
So, when foo is compiled, typing that (foo 1 2 3 4) expression to its "exec" gives an error?
Is too few arguments also caught/reported?
And other argument list mismatches are caught? Like using an unspecified keyword argument, without having &allow-other-keys in the definition.
* (defun foo (a b) (cons a b))
FOO
* (foo)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070070 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 0
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO) [external]
source: (SB-INT:NAMED-LAMBDA FOO
(A B)
(BLOCK FOO (CONS A B)))
0] 0
* (foo 1 2 3 4)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070070 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 4
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO 1 2 3 4) [external]
source: (SB-INT:NAMED-LAMBDA FOO
(A B)
(BLOCK FOO (CONS A B)))
0] 0
* (compile 'foo)
FOO
NIL
NIL
* (foo)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070070 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 0
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO) [external]
source: (SB-INT:NAMED-LAMBDA FOO
(A B)
(BLOCK FOO (CONS A B)))
0] 0
* (foo 1 2 3 4)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070070 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 4
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO 1 2 3 4) [external]
source: (SB-INT:NAMED-LAMBDA FOO
(A B)
(BLOCK FOO (CONS A B)))
0] 0
and then for &optional it catches the wrong number there, too - regardless of whether it is compiled or interpreted.
* (defun foo2 (a &optional b) (list a b))
FOO2
* (foo2 )
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070123 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 0
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO2) [external]
source: (SB-INT:NAMED-LAMBDA FOO2
(A &OPTIONAL B)
(BLOCK FOO2 (LIST A B)))
0] 0
* (foo2 'a)
(A NIL)
* (foo2 'a 'b)
(A B)
* (foo2 'a 'b 'c)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070123 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 3
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO2 A B C) [external]
source: (SB-INT:NAMED-LAMBDA FOO2
(A &OPTIONAL B)
(BLOCK FOO2 (LIST A B)))
0] 0
* (compile 'foo2)
FOO2
NIL
NIL
* (foo2)
debugger invoked on a SB-INT:SIMPLE-PROGRAM-ERROR @59070123 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
invalid number of arguments: 0
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(FOO2) [external]
source: (SB-INT:NAMED-LAMBDA FOO2
(A &OPTIONAL B)
(BLOCK FOO2 (LIST A B)))
0] 0
*
and for &key arguments:
* (defun foo3 (&key foo-key) (list foo-key))
FOO3
* (foo3)
(NIL)
* (foo3 :foo-key 42)
(42)
* (foo3 :bar-key 37)
debugger invoked on a UNKNOWN-KEYWORD-ARGUMENT @59070207 in thread
#<THREAD tid=100195 "main thread" RUNNING {5AA800F1}>:
Unknown &KEY argument: :BAR-KEY
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE] Ignore all unknown keywords
1: [ABORT ] Exit debugger, returning to top level.
(FOO3 :BAR-KEY 37) [more]
source: (SB-INT:NAMED-LAMBDA FOO3
(&KEY FOO-KEY)
(BLOCK FOO3 (LIST FOO-KEY)))
0] 0
(NIL)
Fixing it -- adding error checking in the function call opcodes -- requires changes to maiko.
I don't think that it requires changing maiko.
I think that the CL compiler could prepend argument list checking code to the body of compiled CL:LAMBDA expressions (explicit or implicit).
Forms that have lambda lists with required arguments followed by &rest, or any containing &key currently are compiled as if lambda-nospread, and the pulling apart of the arguments is handled in the function. This could be extended to all CL:LAMBDA expressions. Presumably, this would make the code slightly(?) slower.
In any case, I think that this would be non-trivial.