LABELS local function fails to shadow top-level function under some circumstances
To see this, in a fresh ABCL 1.9.1, do:
(ql:quickload "fset")
(in-package :fset-user)
(defun foo (x)
(labels ((bar (x)
(cond ((null x) nil)
((eq (car x) '&rest) (tail (cadr x)))
(t (cons (car x) (bar (cdr x))))))
(tail (x)
(list 'local-tail x)))
(bar x)))
(compile 'foo)
(foo '(a &rest b))
Expected: (A LOCAL-TAIL B)
Actual:
The value B is not of type LIST.
[Condition of type TYPE-ERROR]
What seems to be going on there is that fset:tail, which is imported, is defined thus:
(defun tail (list)
"Another name for the `cdr' operation on lists."
(cdr list))
(declaim (inline tail))
My guess is that the inline declaration is somehow confusing matters so that the local function tail doesn't get called from bar. But oddly, I have not been able to produce a simple example, without using FSet, that fails. Evidently, there's some other condition required, that I haven't identified, for the bug to bite. It's easy to see that the name matters, though; just rename the local function:
(defun foo (x)
(labels ((bar (x)
(cond ((null x) nil)
((eq (car x) '&rest) (tailx (cadr x)))
(t (cons (car x) (bar (cdr x))))))
(tailx (x)
(list 'local-tail x)))
(bar x)))
This works as expected.
After tracing the precompiler with
(trace)
(JVM:COMPILE-DEFUN JVM::P1-COMPILAND
JVM::CONSTRUCT-FLET/LABELS-FUNCTION
JVM::P1-LABELS)
one can (sorta) see in
; Loading /Users/evenson/work/abcl/t/compiler-inline.lisp ...
; Compiling /Users/evenson/work/abcl/t/eg/inline-labels.lisp ...
; (IN-PACKAGE :FSET-USER)
; (DEFUN FOO ...)
0: (JVM:COMPILE-DEFUN FSET-USER::FOO (LAMBDA (FSET-USER::X) (BLOCK FSET-USER::FOO (LABELS ((FSET-USER::BAR (FSET-USER::X) (NEW-LET:COND ((NULL FSET-USER::X) NIL) ((EQ (CAR FSET-USER::X) (QUOTE &REST)) (FSET:TAIL (CADR FSET-USER::X))) (T (CONS (CAR FSET-USER::X) (FSET-USER::BAR (CDR FSET-USER::X)))))) (FSET:TAIL (FSET-USER::X) (LIST (QUOTE FSET-USER::LOCAL-TAIL) FSET-USER::X))) (FSET-USER::BAR FSET-USER::X)))) NIL #P"/Users/evenson/work/abcl/t/eg/inline_labels_1.cls" #<FILE-STREAM {67E5EF70}> NIL)
1: (JVM::P1-COMPILAND #<JVM::COMPILAND {61C7079D}>)
2: (JVM::P1-LABELS (LABELS ((FSET-USER::BAR (FSET-USER::X) (BLOCK #:G698178 (IF (NULL FSET-USER::X) (RETURN-FROM #:G698178 (PROGN NIL))) (IF (EQ (CAR FSET-USER::X) (QUOTE &REST)) (RETURN-FROM #:G698178 (LET ((LIST (CADR FSET-USER::X))) (BLOCK FSET:TAIL (CDR LIST))))) (RETURN-FROM #:G698178 (CONS (CAR FSET-USER::X) (FSET-USER::BAR (CDR FSET-USER::X)))))) (FSET:TAIL (FSET-USER::X) (LIST (QUOTE FSET-USER::LOCAL-TAIL) FSET-USER::X))) (FSET-USER::BAR FSET-USER::X)))
3: (JVM::CONSTRUCT-FLET/LABELS-FUNCTION (FSET-USER::BAR (FSET-USER::X) (BLOCK #:G698178 (IF (NULL FSET-USER::X) (RETURN-FROM #:G698178 (PROGN NIL))) (IF (EQ (CAR FSET-USER::X) (QUOTE &REST)) (RETURN-FROM #:G698178 (LET ((LIST (CADR FSET-USER::X))) (BLOCK FSET:TAIL (CDR LIST))))) (RETURN-FROM #:G698178 (CONS (CAR FSET-USER::X) (FSET-USER::BAR (CDR FSET-USER::X)))))))
3: CONSTRUCT-FLET/LABELS-FUNCTION returned #<JVM::LOCAL-FUNCTION {6D80881C}>
3: (JVM::CONSTRUCT-FLET/LABELS-FUNCTION (FSET:TAIL (FSET-USER::X) (LIST (QUOTE FSET-USER::LOCAL-TAIL) FSET-USER::X)))
3: CONSTRUCT-FLET/LABELS-FUNCTION returned #<JVM::LOCAL-FUNCTION {2AF1F898}>
3: (JVM::P1-COMPILAND #<JVM::COMPILAND {62A70CD9}>)
3: P1-COMPILAND returned (LAMBDA (FSET-USER::X) #<JVM::BLOCK-NODE {3B0504FF}>)
3: (JVM::P1-COMPILAND #<JVM::COMPILAND {522CBBEB}>)
3: P1-COMPILAND returned (LAMBDA (FSET-USER::X) #<JVM::BLOCK-NODE {77BCBC0E}>)
2: P1-LABELS returned #<JVM::LABELS-NODE {2DA7A7E4}>
1: P1-COMPILAND returned (LAMBDA (FSET-USER::X) #<JVM::BLOCK-NODE {3E167C53}>)
0: COMPILE-DEFUN returned #<JVM::ABCL-CLASS-FILE {4C413149}>
that between the calls to jvm:compile-defun and jvm::p1-compiland, the fset-user:tail symbol has been expanded to its inline declaration. Which is sorta correct as the the function-name of the labels function is the symbol fset-user:tail which has been inherited from fset:tail. I need to think through how the Lisp reader should be processing the symbols in LABELS:
-
Should the labels function-name really be
fset:tail? Or should it be another symbol in an "implicit package" somehow? -
In the LABELS function definitions, how does one distinguish between something referring to a labels function-name and something in the current package?
SBCL and ECL succeed on the test in https://github.com/armedbear/abcl/pull/610.