Skip to content

Commit 4f03401

Browse files
committed
feat(meta): D-316 — computed def-meta evaluates at def time (clj-exact)
Smell-audited: 1: the residual resolved ON the D-563(b) meta_expr channel — the def-meta map now analyzes as a real expression (quotes stay data, computed values evaluate: ^{:k (+ 1 2)} → 3, defn attr-map (* 2 3) → 6, both clj-oracle-matched). The enabling fix is at the GENERATORS: defn/defmacro now QUOTE their synthesized :arglists like clj does, which is what made raw-expression analysis safe (the earlier wholesale-quote workaround existed only to protect those raw lists). Corpus def_meta_eval.txt; 2 e2e cases; D-316 discharged (active -> discharged).
1 parent e44217b commit 4f03401

5 files changed

Lines changed: 44 additions & 30 deletions

File tree

‎.dev/debt.yaml‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -611,18 +611,6 @@ active:
611611
user-ns resolution). The current source-synthesis migrates cleanly: the
612612
synthesized form IS the Clojure body that fn would host.
613613
last_reviewed: "2026-06-07"
614-
- id: "D-316"
615-
status: "NARROWED (2026-06-08) — quoted-datum + literal meta values now evaluate at def time. analyzeDef/analyzeDefmacro lift via unquoteMetaValues: a `(quote X)` meta value lifts to X (clj parity), covering the realistic case `:arglists '([k v])` (integrant defmulti) + `{:k '(a b)}`; literals are unchanged; defn synthesized :arglists unaffected. RESIDUAL: arbitrary COMPUTED meta (`{:k (+ 1 2)}`) still stores the unevaluated Form — full def-time RUNTIME eval needs a meta-expr node threaded through DefNode/op_def/evalDef (depth-2, both backends), deferred as rare (real def-meta is literal or quoted, never computed). 5 e2e (phase14_def_meta_quote_value). PRIOR: now (2026-06-07, integrant probe / defmulti fix) — def-target metadata MAP VALUES are not evaluated. `(defn g {:foo '(a b)} [x] x)` → `(:foo (meta #'g))` is `(quote (a b))` in cljw vs `(a b)` in clj; same for the defmulti attr-map `:arglists '([k v])` (cljw `(quote ([k v]))` vs clj `([k v])`). GENERAL: the attr-map / `^{…}` value FORMS are attached to Var.meta verbatim, not evaluated. Affects defn + defmulti (and any `^meta`). Not defmulti-specific — surfaced while fixing defmulti docstring/attr-map attachment (which itself now matches clj for `:doc`). Low-impact (most metadata values are literals: strings/keywords/bools), but `:arglists`/computed-meta diverge."
616-
category: "scope"
617-
barrier: |-
618-
Def-target metadata map values are attached as unevaluated Forms. clj
619-
evaluates `^{:k expr}` / attr-map values at def time. Fix: evaluate the
620-
metadata map's VALUES (analyzeDef / the meta-lift path) instead of storing
621-
the raw value Forms, so `:arglists '([k v])` → `([k v])`. Touches the meta
622-
side-channel lift shared by defn/defmulti (buildDefnMeta + analyzeDef). Most
623-
metadata is literal so impact is narrow; surfaced by integrant's defmulti
624-
`{:arglists '([k v])}`. Cross-ref D-183 (defn meta), defmulti docstring fix.
625-
last_reviewed: "2026-06-07"
626614
- id: "D-341"
627615
status: "open — security (SE-8, eval-free deploy build). `eval` (+ read-string) is public with no confinement story. read-string is ALREADY eval-free by design (ADR-0122 / AD-026 — `#=` not evaluated; the read-eval RCE footgun is closed). The residual is the `eval`/nREPL surface itself: a deployed serverless edge binary should be able to ship eval-free, author opts IN."
628616
category: "security"
@@ -2384,6 +2372,18 @@ standing:
23842372
barrier: "open after the D-440 arc completes (R4 + R5 must first rewrite the bulk of 'Phase N' citations to gap-area terms). Premature adoption = ~70 dangling citations with no 1:1 redirect. Trigger: D-440 R4/R5 done + a user nod to re-architect the planning axis (it is depth-4, the project's organizing metaphor)."
23852373
last_reviewed: "2026-06-15"
23862374
discharged:
2375+
- id: "D-316"
2376+
status: "DISCHARGED 2026-07-17 — the residual (computed def-meta) resolved on the D-563(b) meta_expr channel: the def-meta map now analyzes as a REAL expression (quotes stay data, literals embed, computed values EVALUATE at def time — clj-exact, oracle [3 (a b) \"s\"] / defn attr-map (* 2 3) → 6). Prerequisite: defn/defmacro now QUOTE their synthesized :arglists at the generator (like clj), which is what made raw-expression analysis safe. unquoteMetaValues stays only for the analyze-time static Var.meta lift (pre-eval approximation; the runtime value wins). Corpus def_meta_eval.txt; e2e computed_meta/defn_attr_computed on phase15_var_source_meta. ORIGINAL NARROWED note (2026-06-08) + original finding (2026-06-07) in git history."
2377+
category: "scope"
2378+
barrier: |-
2379+
Def-target metadata map values are attached as unevaluated Forms. clj
2380+
evaluates `^{:k expr}` / attr-map values at def time. Fix: evaluate the
2381+
metadata map's VALUES (analyzeDef / the meta-lift path) instead of storing
2382+
the raw value Forms, so `:arglists '([k v])` → `([k v])`. Touches the meta
2383+
side-channel lift shared by defn/defmulti (buildDefnMeta + analyzeDef). Most
2384+
metadata is literal so impact is narrow; surfaced by integrant's defmulti
2385+
`{:arglists '([k v])}`. Cross-ref D-183 (defn meta), defmulti docstring fix.
2386+
last_reviewed: "2026-06-07"
23872387
- id: "D-561"
23882388
status: |-
23892389
DISCHARGED 2026-07-17 — Character/getName + codePointOf land on the generated UCD 16.0.0 word-indexed name table (scripts/gen_unicode_names.py → unicode_names.zig, pre-compressed raw-deflate blobs + delta-varint index, decoded once per process in char_name.zig). JVM-oracle-exact semantics: stored names, pinned control aliases (scripts/data/jvm_control_names.txt), assigned-but-unnamed = "<BLOCK> <HEX>" (the JVM does NOT synthesize UCD algorithmic names), unassigned nil; codePointOf = trimmed case-insensitive inverse accepting exactly getName output forms. Corpus char_names.txt 10/10. Size: mac +279KB (7.35MB; ADR-0172 revision — README claim 7.8MB; Linux ~94% of ceiling, next size work measures Linux first).

‎src/eval/analyzer/special_forms.zig‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,11 @@ pub fn analyzeDefmacro(
516516
break :blk2 av;
517517
};
518518
try meta_items.append(arena, .{ .data = .{ .keyword = .{ .name = "arglists" } }, .location = form.location });
519-
try meta_items.append(arena, .{ .data = .{ .list = arglists_inner2 }, .location = form.location });
519+
// Synthesized arglists are DATA — quoted, like clj's defn/defmacro.
520+
const agq = try arena.alloc(Form, 2);
521+
agq[0] = macro_dispatch.makeSymbol("quote", form.location);
522+
agq[1] = .{ .data = .{ .list = arglists_inner2 }, .location = form.location };
523+
try meta_items.append(arena, .{ .data = .{ .list = agq }, .location = form.location });
520524
const kws = [_]struct { name: []const u8, v: i64 }{
521525
.{ .name = "line", .v = @intCast(src_loc.line) },
522526
.{ .name = "column", .v = @intCast(src_loc.column) },
@@ -528,11 +532,9 @@ pub fn analyzeDefmacro(
528532
try meta_items.append(arena, .{ .data = .{ .keyword = .{ .name = "file" } }, .location = form.location });
529533
try meta_items.append(arena, .{ .data = .{ .string = src_loc.file }, .location = form.location });
530534
const meta_map2: Form = .{ .data = .{ .map = try arena.dupe(Form, meta_items.items) }, .location = form.location };
531-
const quote_items = try arena.alloc(Form, 2);
532-
quote_items[0] = macro_dispatch.makeSymbol("quote", form.location);
533-
quote_items[1] = try unquoteMetaValues(arena, meta_map2);
534-
const quoted: Form = .{ .data = .{ .list = quote_items }, .location = form.location };
535-
break :blk try analyzer_mod.analyze(arena, rt, env, scope, quoted, macro_table);
535+
// Real-expression analysis, mirroring analyzeDef (D-316): quoted
536+
// arglists stay data, computed values evaluate.
537+
break :blk try analyzer_mod.analyze(arena, rt, env, scope, meta_map2, macro_table);
536538
};
537539

538540
// Build the synthetic `fn` Form, then analyse it through the regular path
@@ -680,17 +682,13 @@ pub fn analyzeDef(
680682
try meta_items.append(arena, .{ .data = .{ .keyword = .{ .name = "file" } }, .location = form.location });
681683
try meta_items.append(arena, .{ .data = .{ .string = src_loc.file }, .location = form.location });
682684
const meta_map: Form = .{ .data = .{ .map = try arena.dupe(Form, meta_items.items) }, .location = form.location };
683-
// The whole map is wrapped in (quote …): def-meta is STATIC DATA in
684-
// cljw (same semantics as the formToValue lift above — defn's raw
685-
// `:arglists ([s] …)` lists must never evaluate; clj's
686-
// evaluate-meta-exprs nuance is out of scope, AD candidate if ever
687-
// hit). The quoted map analyzes to ONE composite constant, which the
688-
// AOT wire carries (quoted literals round-trip `cljw build` today).
689-
const quote_items = try arena.alloc(Form, 2);
690-
quote_items[0] = macro_dispatch.makeSymbol("quote", form.location);
691-
quote_items[1] = try unquoteMetaValues(arena, meta_map);
692-
const quoted: Form = .{ .data = .{ .list = quote_items }, .location = form.location };
693-
break :blk try analyzer_mod.analyze(arena, rt, env, scope, quoted, macro_table);
685+
// The map analyzes as a REAL EXPRESSION (D-316): quoted values stay
686+
// data (defn's synthesized `:arglists '([x])` — now quoted at the
687+
// generator), literals embed, and a COMPUTED value like
688+
// `^{:k (+ 1 2)}` EVALUATES at def time — clj's def-meta semantics
689+
// exactly. The static formToValue lift above remains the pre-eval
690+
// approximation; the runtime value set by evalDef/op_var_meta wins.
691+
break :blk try analyzer_mod.analyze(arena, rt, env, scope, meta_map, macro_table);
694692
};
695693
// `^:dynamic` / `^:private` on the def target set the Var flags (evalDef /
696694
// op_def copy DefNode flags onto the Var). Without this the metadata was

‎src/lang/macro_transforms.zig‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1854,7 +1854,15 @@ fn buildDefnMeta(
18541854
} else {
18551855
for (body_forms) |af| try arglist_vecs.append(arena, af.data.list[0]);
18561856
}
1857-
const arglists = try list(arena, arglist_vecs.items, loc);
1857+
// The param-vector list is DATA — emit it QUOTED, exactly like clj's
1858+
// defn (`:arglists '([params])`), so the def-meta EXPRESSION path
1859+
// (DefNode.meta_expr, D-316) can evaluate the whole meta map with
1860+
// clj semantics: quotes stay data, computed values evaluate.
1861+
const arglists_data = try list(arena, arglist_vecs.items, loc);
1862+
const quote_items = try arena.alloc(Form, 2);
1863+
quote_items[0] = sym("quote", loc);
1864+
quote_items[1] = arglists_data;
1865+
const arglists: Form = .{ .data = .{ .list = quote_items }, .location = loc };
18581866

18591867
var meta_items: std.ArrayList(Form) = .empty;
18601868
if (existing) |m| try meta_items.appendSlice(arena, m.data.map);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(do (def ^{:k (+ 1 2)} cm 1) (:k (meta #'cm)))
2+
;;=> 3

‎test/e2e/phase15_var_source_meta.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ assert_eq 'doc_and_user_meta' "$(run '(defn g "gdoc" {:author "me"} [x] x)
4747
assert_eq 'compiler_line_wins' "$(run '(def ^{:line 999 :author "me"} h 1)
4848
(println [(:line (meta (var h))) (:author (meta (var h)))])')" '[1 me]'
4949

50+
# --- computed def-meta EVALUATES at def time (D-316; clj-exact) ---
51+
assert_eq 'computed_meta' "$(run '(def ^{:computed (+ 1 2) :quoted (quote (a b)) :lit "s"} mv 1)
52+
(println [(:computed (meta (var mv))) (:quoted (meta (var mv))) (:lit (meta (var mv)))])')" '[3 (a b) s]'
53+
assert_eq 'defn_attr_computed' "$(run '(defn mf "d" {:extra (* 2 3)} [x] x)
54+
(println [(:extra (meta (var mf))) (:arglists (meta (var mf)))])')" '[6 ([x])]'
55+
5056
# --- fixture file: :file is the real path, :line the real line ---
5157
fixdir=$(mktemp -d)
5258
trap 'rm -rf "$fixdir"' EXIT

0 commit comments

Comments
 (0)