#253 with-deprecation eval chain: version-deprecation returns NIL (even direct) + parse-version returns NIL via eval — 5 "NIL is not NUMBER" in asdf load

closed
Opened by atgreen

Successor to cave#218 (whose labels/apply macro-execution mechanism is now fixed). With the with-deprecation / with-asdf-deprecation expanders finally EXECUTING (0d55b1d), they run their level form (eval (version-deprecation *asdf-version* :style-warning "3.2" :warning "3.4")) at expansion time — and that yields NIL, which flows into asdf arithmetic → 5 census errors: 3x "NIL is not of type NUMBER | in: WITH-ASDF-DEPRECATION", 1x WITH-UPGRADABILITY, 1x EVAL-WHEN (cave#205 asdf-load, still 8 total).

TWO distinct funcl bugs in the chain, both probed with asdf loaded:

  1. version-deprecation returns NIL even called DIRECTLY. (uiop/version:version-deprecation "3.3.7" :style-warning "3.2" :warning "3.4") => NIL. Should be :STYLE-WARNING (3.3.7 >= 3.2). Yet every sub-piece works in isolation:

    • (parse-version "3.2" nil) => (3 2)
    • (funcall (quote <) 2 3) => T, (funcall (quote <) 3 2) => NIL
    • inline lexicographic< of (3 2) vs (3 3 7) => T
    • version< / version<= trace by hand => :STYLE-WARNING Suspect the &key DEFAULT CHAIN (warning defaults to (next-version style-warning), error to (next-version warning), delete to (next-version error)) as compiled during asdf block-compile, or the with-upgradability notinline binding — something makes the compiled version-deprecation body compute NIL where the pieces individually dont.
  2. parse-version returns NIL when called via eval. (eval (list (quote uiop/version::parse-version) "3.2" nil)) => NIL, while the DIRECT (parse-version "3.2" nil) => (3 2). funcl user-level eval (stdlib tree-walker) mis-calls parse-version. A simpler (eval (list (quote f) "3.2" nil)) for a trivial &optional f works, so its specific to parse-versions body (block/return + loop :always/:finally + mapcar #(quote)parse-integer + split-string).

Impact: DEPRECATED asdf surface only (backward-compat defuns); core find-system/defsystem unaffected. Low priority. But two real funcl correctness bugs (compiled &key-default-chain, eval of a complex-body fn). Repro files: /tmp/wd-probe.lisp, /tmp/eval-fn.lisp patterns (load asdf, then eval/direct compare).

Comments (1)

atgreen3992494085

FIXED 04f94b6. The stated diagnosis was wrong — both "bugs" were red herrings:

  • The &key default-chain works fine generically: (defun f (&key (a 10) (b (+ a 1))) ...)(f :a 5) = (5 6 …).
  • parse-version-via-eval already returns (3 2) (fixed by the #218 labels/apply mechanism).

Real root cause: (format nil ...) returned its raw make-byte-vector scratch buffer (type-of SIMPLE-VECTOR), not a SIMPLE-STRING. It printed correctly (write-string/printer are widetag-blind), but stringp = NIL and length/char/subseq errored on it.

Chain: asdf next-version = (unparse-version …) = (format nil "~{~D~^.~}" list) → non-string. The second next-version in the default chain ((next-version (next-version style-warning))) feeds that non-string into parse-version, whose (unless (stringp version-string) … (return)) fires → returns NIL → (incf (car (last NIL)))"NIL is not NUMBER". That's why version-deprecation with all four keywords explicit worked but the default-chain form errored — only the chained form calls next-version on a formatted string.

Fix: format's null-stream branch now returns (%string-subseq bv 0 (byte-vector-length bv)) — copies the written prefix into a fresh %alloc-string-bv (properly-tagged SIMPLE-STRING, trimmed to length). General correctness win: any code that formats then typechecks now works.

Verified on build/funcl with real asdf loaded:

  • (next-version (next-version "3.2"))"3.4" (was: error)
  • (version-deprecation "3.3.7" :style-warning "3.2"):WARNING (was: error)
  • (stringp (format nil "~D" 3)) → T; (type-of (format nil "~{~D~^.~}" '(3 3))) → SIMPLE-STRING

asdf.lisp load census 8 → 3. All 8 bootstrap shards green.

Residual census-3 (next load-clean targets for #205):

  • undefined function | in: REGISTER-IMAGE-RESTORE-HOOK
  • There is no symbol ~S in package ~S | in: LET