diff --git a/host/chez/host-contract.ss b/host/chez/host-contract.ss index 27ce5be..755c761 100644 --- a/host/chez/host-contract.ss +++ b/host/chez/host-contract.ss @@ -184,12 +184,31 @@ ;; of the list), and the analyzer re-analyzes the returned form. (define (hc-macro? ctx sym) (macro-var? (hc-resolve-cell ctx sym))) +;; Clojure parity: a macro expansion inherits the call form's source position, so +;; errors/traces in macro-generated code point at the macro call site. Carry it +;; onto the top of a LIST expansion (code) that has none of its own — merged under +;; any meta the macro set, leaving collection literals (runtime data) alone. The +;; recursion through analyze re-expands inner macros, so each level's top form +;; picks up the position the same way (as the reference compiler does). +(define (hc-propagate-pos src dst) + (if (and (cseq? dst) (cseq-list? dst)) + (let ((sp (hc-form-position src)) + (dm (jolt-meta dst))) + (if (and (pmap? sp) + (or (jolt-nil? dm) (jolt-nil? (jolt-get dm hc-kw-line)))) + (jolt-with-meta dst + (if (pmap? dm) + (pmap-fold sp (lambda (k v acc) (jolt-assoc1 acc k v)) dm) + sp)) + dst)) + dst)) + (define (hc-expand-1 ctx form) (let* ((items (seq->list form)) (head (car items)) (args (cdr items)) (expander (var-cell-root (hc-resolve-cell ctx head)))) - (apply jolt-invoke expander args))) + (hc-propagate-pos form (apply jolt-invoke expander args)))) ;; Classify a global (non-local) symbol reference against the var registry: ;; {:kind :var :ns NS :name NAME} — a defined var (compile ns / clojure.core) diff --git a/test/chez/build-app/src/app/util.clj b/test/chez/build-app/src/app/util.clj index 2e80fd4..ff524cb 100644 --- a/test/chez/build-app/src/app/util.clj +++ b/test/chez/build-app/src/app/util.clj @@ -5,9 +5,13 @@ (str/upper-case (str s "!"))) ;; A two-deep non-tail call chain that throws — exercises native stack traces in a -;; direct-link build (build-smoke runs -main with a --boom sentinel arg). -(defn deep-boom [x] - (assert (number? x) "needs a number") +;; direct-link build (build-smoke runs -main with a --boom sentinel arg). deep-boom +;; is defined through a USER macro: its source registration only gets a real line +;; if the reader position survives macroexpansion (so the trace frame maps). +(defmacro defguarded [name args & body] + `(defn ~name ~args (assert (number? ~(first args)) "needs a number") ~@body)) + +(defguarded deep-boom [x] (* x 2)) (defn mid-boom [x] diff --git a/test/chez/unit.edn b/test/chez/unit.edn index 004a0fe..2a08a09 100644 --- a/test/chez/unit.edn +++ b/test/chez/unit.edn @@ -339,6 +339,9 @@ {:suite "reader" :expr "(nil? (meta (read-string \"()\")))" :expected "true"} {:suite "reader" :expr "(:foo (meta (read-string \"^:foo (x y)\")))" :expected "true"} {:suite "reader" :expr "(:line (meta (read-string \"^:foo (x y)\")))" :expected "1"} + {:suite "reader" :expr "(:line (meta (macroexpand-1 (read-string \"(when x y)\"))))" :expected "1"} + {:suite "reader" :expr "(:line (meta (macroexpand-1 (read-string \"\\n\\n(when x y)\"))))" :expected "3"} + {:suite "reader" :expr "(:column (meta (macroexpand-1 (read-string \"(when x y)\"))))" :expected "1"} {:suite "reader" :expr "(= 42 (with-in-str \"42\" (read)))" :expected "true"} {:suite "reader" :expr "(= (quote (+ 1 2)) (with-in-str \"(+ 1 2)\" (read)))" :expected "true"} {:suite "reader" :expr "(with-in-str \"1 2\" [(read) (read)])" :expected "[1 2]"}