let a construct declare its own meaning instead of the emitter knowing it
codegen called fn_has_decorator for exactly three names — manager, accessor,
route. Twelve others parsed, attached as {name,args}, and compiled to nothing,
including four that look like protection: @authenticate (6 uses), @authorize
(3), @rate_limit (3), @validate (2). The cause was not that the branches were
untidy. A construct had nothing to BE, so its meaning had nowhere to live
except the emitter, and every construct was therefore a compiler edit.
A name -> injection table would have moved the enumeration twenty lines up
without removing it. So the construct now carries its own meaning:
@decorator("injects_at_entry", "engram_boundary_beat")
fn audited() {}
@audited
fn risky_op() -> Int { ... } // gets the beat, attributed to "audited"
scan_declared_decorators is a token-level pre-pass beside scan_routes, forced
by streaming codegen having no whole-program AST. manager and accessor are
seeded as the compiled-in core — the fixedSelf shape from substrate.go: a
complete fallback exists, declaration is enrichment.
This is the injection half of the seam only. The prohibition half (@manager's
#error on dharma_emit) stays hardcoded, because "which calls may appear inside
this boundary" is a query over program structure and there is nothing yet to
ask.
Verified three ways: emitted C for existing @manager/@accessor code is
byte-identical to the hardcoded path; a construct with a name the compiler has
never heard of injects correctly; the compiler self-hosts byte-identically.
90/90 native compiler tests pass.
This commit is contained in:
@@ -772,3 +772,36 @@ test "decorator-authenticate-compiles-to-nothing" {
|
||||
let bare: String = compile_capture("fn f() -> Int { return 1 }")
|
||||
assert str_eq(out, bare), "KNOWN DEFECT: @authenticate emits identical C to no decorator at all"
|
||||
}
|
||||
|
||||
// ── Declared constructs ──────────────────────────────────────────────────────
|
||||
//
|
||||
// A construct declares its own meaning and codegen reads it. Adding a
|
||||
// construct is a declaration in the program; it does not touch the compiler.
|
||||
|
||||
test "declared-construct-injects-without-compiler-knowledge" {
|
||||
let src: String = "@decorator(\"injects_at_entry\", \"engram_boundary_beat\")\nfn audited() {}\n@audited\nfn risky() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "engram_boundary_beat(EL_STR(\"risky\")"), "a program-declared construct injects"
|
||||
assert str_contains(out, "EL_STR(\"audited\")"), "the beat is attributed to the declared construct"
|
||||
}
|
||||
|
||||
test "declared-construct-name-unknown-to-codegen" {
|
||||
// The name is arbitrary. Nothing in the compiler mentions it.
|
||||
let src: String = "@decorator(\"injects_at_entry\", \"engram_boundary_beat\")\nfn zzq_unlikely_name() {}\n@zzq_unlikely_name\nfn f() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "EL_STR(\"zzq_unlikely_name\")"), "an arbitrary construct name works"
|
||||
}
|
||||
|
||||
test "undeclared-construct-still-injects-nothing" {
|
||||
let src: String = "@nobody_declared_this\nfn f() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert !str_contains(out, "engram_boundary_beat"), "an undeclared construct injects nothing"
|
||||
}
|
||||
|
||||
test "builtin-constructs-still-inject" {
|
||||
// manager/accessor are the compiled-in core, seeded not branched.
|
||||
let src: String = "@manager\nfn m() -> Int { return 1 }\n@accessor\nfn a() -> Int { return 2 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "EL_STR(\"manager\")"), "seeded manager still injects"
|
||||
assert str_contains(out, "EL_STR(\"accessor\")"), "seeded accessor still injects"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user