Skip to content

Commit 952eecd

Browse files
committed
fix(meta): def-meta :tag resolves uniformly (clj parity), retiring the bare-symbol workaround
Smell-audited: 1: no code behaviour change — D-316's uniform def-meta eval (already in HEAD) is the finished form. A def-meta map analyzes as a real expression, so a `:tag` symbol resolves like any value: `^String` yields the Class (`(= String (:tag ...))` is true in cljw and clj), `^Foo` is a name error. The pre-D-316 bare-symbol :tag was a pre-Class-resolution workaround, retired now that class symbols resolve as values (v1.5.0). Only phase14_var_metadata's meta_tag still asserted the workaround (`^Foo` => "Foo"), which is why it failed four consecutive nightlies (2026-07-18..21). Flip it to `(= String ...)` => true and add meta_tag_unresolvable (`^Foo` errors, the correct clj-parity behaviour). special_forms.zig is comment-only, documenting the uniform-eval intent so no :tag special case is re-added. No new AD: structurally clj-equal, only the pre-existing AD-003 Class-print simple-name divergence remains (applied to all Class values, not tag-specific).
1 parent bdb65ad commit 952eecd

2 files changed

Lines changed: 21 additions & 3 deletions

File tree

src/eval/analyzer/special_forms.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,8 @@ pub fn analyzeDefmacro(
533533
try meta_items.append(arena, .{ .data = .{ .string = src_loc.file }, .location = form.location });
534534
const meta_map2: Form = .{ .data = .{ .map = try arena.dupe(Form, meta_items.items) }, .location = form.location };
535535
// Real-expression analysis, mirroring analyzeDef (D-316): quoted
536-
// arglists stay data, computed values evaluate.
536+
// arglists stay data, computed values evaluate, `:tag` symbols resolve
537+
// uniformly (a Class value or a name error — clj parity).
537538
break :blk try analyzer_mod.analyze(arena, rt, env, scope, meta_map2, macro_table);
538539
};
539540

@@ -688,6 +689,9 @@ pub fn analyzeDef(
688689
// `^{:k (+ 1 2)}` EVALUATES at def time — clj's def-meta semantics
689690
// exactly. The static formToValue lift above remains the pre-eval
690691
// approximation; the runtime value set by evalDef/op_var_meta wins.
692+
// A `:tag` symbol is resolved uniformly like any other value — clj
693+
// parity: `^String` yields the Class value, `^Foo` (unresolvable)
694+
// is a name error, matching clj exactly (no bare-symbol special case).
691695
break :blk try analyzer_mod.analyze(arena, rt, env, scope, meta_map, macro_table);
692696
};
693697
// `^:dynamic` / `^:private` on the def target set the Var flags (evalDef /

test/e2e/phase14_var_metadata.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,28 @@ assert_last() {
3636
[[ "$got" == "$want" ]] || fail "$name: got '$got', want '$want'"
3737
echo "PASS $name -> $want"
3838
}
39+
# An expression that must be REJECTED (non-zero exit) — e.g. an unresolvable
40+
# type-hint tag, which is a name error in cljw exactly as in clj.
41+
assert_error() {
42+
local name="$1"; local expr="$2"
43+
if "$BIN" -e "$expr" >/dev/null 2>&1; then
44+
fail "$name: expected an error, but it succeeded"
45+
fi
46+
echo "PASS $name -> (errored as expected)"
47+
}
3948

4049
# --- ^{map} on a def target → Var meta, read via (meta #'x) ---
4150
assert_last 'meta_doc' '(def ^{:doc "hi"} x 5) (:doc (meta #'"'"'x))' '"hi"'
4251
assert_last 'meta_map_a' '(def ^{:a 1 :b 2} z 5) (:a (meta #'"'"'z))' '1'
4352
# --- ^:kw shorthand → {:kw true} ---
4453
assert_last 'meta_private' '(def ^:private y 5) (:private (meta #'"'"'y))' 'true'
45-
# --- ^Sym shorthand → {:tag Sym} (cljw keeps the bare symbol tag) ---
46-
assert_last 'meta_tag' '(def ^Foo s 1) (str (:tag (meta #'"'"'s)))' '"Foo"'
54+
# --- ^Sym type-hint shorthand → {:tag <Class>}: a def-meta map analyzes as a
55+
# real expression (D-316), so a resolvable class symbol resolves to the Class
56+
# VALUE — clj parity, `(= String (:tag …))` is true in both. An unresolvable
57+
# tag is a name error, same as clj. (The old bare-symbol tag was a
58+
# pre-Class-resolution workaround, retired once class symbols became values.) ---
59+
assert_last 'meta_tag' '(def ^String s 1) (= String (:tag (meta #'"'"'s)))' 'true'
60+
assert_error 'meta_tag_unresolvable' '(def ^Foo s 1)'
4761
# --- stacked metas merge, outer wins on dup keys ---
4862
assert_last 'meta_stack' '(def ^:a ^:b w 5) [(:a (meta #'"'"'w)) (:b (meta #'"'"'w))]' '[true true]'
4963

0 commit comments

Comments
 (0)