#273 #(...) vector literal EVALUATES its elements — #(a b c) => #(NIL NIL NIL)

open
Opened by atgreen

Split-independent. The reader (reader-read-vector-literal, src/cold/reader.lisp:2092) lowers #(item...) to the CALL FORM (vector item...), so the elements are EVALUATED at runtime. CL requires #(...) to be a self-evaluating literal whose elements are the read objects, NOT evaluated.

(aref #(a b c) 1) => NIL (should be B; a/b/c evaluated as unbound vars -> NIL) (elt #(a b c) 1) => NIL (length #(a b c)) => 3 (vector built, but all slots NIL) (aref #(1 2 3) 1) => 2 (numbers self-evaluate, so OK) (aref #(:a :b :c) 1) => :B (keywords self-evaluate, OK) (aref #("s" "t") 0) => "s" (strings self-evaluate, OK) (aref (quote #(a b c)) 1) => error "The value (VECTOR A B C) is not a VECTOR" -- proving #(...) reads as the cons (VECTOR A B C) (elt (vector 10 20 30) 1) => 20 (runtime-built vectors are fine; only the #(...) literal is broken)

The code comment already flags this: "CL-style literal sharing across uses of #(…) is a follow-up that needs the quote-pool to admit heap-backed vector constants."

FIX OPTIONS: (a) proper — teach the quote-pool to hold heap-backed simple-vector constants so #(...) self-evaluates (correct, bigger). (b) quick — quote each element: (vector (quote item)...). Fixes flat symbol/list literals but REGRESSES nested literals: #(#(1)) currently lowers to (vector (vector 1)) => #(#(1)) which WORKS, but under (b) becomes (vector (quote (vector 1))) => #((VECTOR 1)). So (b) is not clean. DEFERRED rather than risk the reader (cold-path, regression-prone) — option (a) is the right fix.

Comments (2)

atgreen3992736217

BROADER than #(...): the reader lowers MANY literals to CONSTRUCTOR FORMS, not self-evaluating data objects, so read/read-from-string return the form instead of the object: (read-from-string "#\a") => (CODE-CHAR 97) (should be #\a) (read-from-string "3.14") => (%MAKE-SINGLE-FLOAT ..) (should be 3.14) (read-from-string "#(a b)") => (VECTOR (QUOTE... ) ..) (should be #(A B)) [the #(...) case] For COMPILATION these forms evaluate correctly (code-char/%make-single-float/vector self-construct), so char/float/vector literals WORK in code — but read as DATA they leak the form. Root: the reader emits constructor forms for char (reader-read-char-literal), float, and vector literals instead of interning the actual object (chars are immediate; floats/vectors need quote-pool constant support). Fix = have the reader build the real object at read time (chars/floats are constructible in the reader; vectors need heap-backed quote-pool constants). Affects reader/printer roundtrip tests.

Separately: READ (stream-based, e.g. (read stream)) is UNDEFINED — funcl has read-from-string but no read consuming from a stream; needs the reader wired to a string-input-stream cursor.

atgreen3992785764

STILL BROKEN (probe 2026-07-11): (read-from-string "#(a a)") => (VECTOR A A) — the reader expands #(...) into a VECTOR CALL FORM, so elements are evaluated and unbound symbols break. Correct: a literal simple-vector, self-evaluating. Related: ADR-0028 debugging showed live vectors can't ride compiled quotes — a proper vector literal needs the quote materializer to support vector payloads (same machinery the fix here needs).