let a construct declare what may not cross it

The other half of a boundary: not what runs when something crosses, but what
may not cross at all. It was two string literals in vbd_is_restricted_name and
one #error in cg_fn — one prohibition, uneditable without a compiler release.

    @decorator("prohibits_outside", "raw_sql")
    fn repository() {}

    fn sneaky() -> Int { raw_sql("DROP") }
    // #error "boundary violation: raw_sql may only be called from an
    //          @repository fn, but 'sneaky' is not one"

The recursive matcher is parameterised through a state key rather than by
threading an argument through every branch of the walk — the mechanism codegen
already uses for __match_counter and __if_expr_counter. Each prohibition is
checked in its own turn, so the owning construct is known by construction and
the diagnostic names it instead of hardcoding one rule's wording.

PREDICTIONS AND RESULTS
  1 the 3 duplicated uniqueness rules are textually identical    TRUE
  2 a declared prohibition reproduces @manager's #error          TRUE
  3 existing output byte-identical                               TRUE
  4 a program can declare its own prohibition                    TRUE
  5 fixpoint holds                                               TRUE

I misread result 2 on first pass: a @manager fn calling dharma_emit still
emitted one #error, which looked like a failure. It is the CAPABILITY-tier rule
at codegen.el:2578, a separate prohibition system, and it fires identically on
the pre-change compiler.

MEASURED DEFECTS STILL OPEN
  - two independent prohibition systems (VBD constructs, capability tiers);
    only the first is declarable
  - 3 uniqueness rules written 6 times, once per codegen path, kept in sync by
    hand and identical today

102/102 native compiler tests pass, compiler self-hosts byte-identically.
This commit is contained in:
bigmerge
2026-08-17 08:15:27 -05:00
parent 7d01608a9d
commit 1b324a071f
2 changed files with 101 additions and 5 deletions
+28
View File
@@ -875,3 +875,31 @@ test "constructs-compose-guard-entry-exit" {
assert b < x, "entry injection before exit injection"
assert x >= 0, "three independent constructs compose on one fn"
}
// Declared constructs: wraps and prohibitions
test "declared-wrap-emits-closure-and-convention" {
let src: String = "@decorator(\"wraps_body\", \"with_timeout\")\nfn timed() {}\n@timed\nfn slow(k: Int) -> Int { return 9 }"
let out: String = compile_capture(src)
assert str_contains(out, "struct __env_slow"), "captured environment is emitted"
assert str_contains(out, "__thunk_slow(void* __v)"), "a thunk taking void* is emitted"
assert str_contains(out, "extern el_val_t with_timeout(el_val_t, el_val_t, el_val_t(*)(void*), void*);"), "codegen emits the calling convention — El's single type cannot describe a callable"
}
test "declared-prohibition-fires-outside-the-boundary" {
let src: String = "@decorator(\"prohibits_outside\", \"raw_sql\")\nfn repository() {}\nfn sneaky() -> Int { raw_sql(\"DROP\") return 1 }"
let out: String = compile_capture(src)
assert str_contains(out, "raw_sql may only be called from an @repository fn"), "a program-declared prohibition is enforced"
}
test "declared-prohibition-permits-inside-the-boundary" {
let src: String = "@decorator(\"prohibits_outside\", \"raw_sql\")\nfn repository() {}\n@repository\nfn allowed() -> Int { raw_sql(\"SELECT\") return 1 }"
let out: String = compile_capture(src)
assert !str_contains(out, "raw_sql may only be called"), "the owning construct permits the call"
}
test "seeded-vbd-prohibition-still-enforced" {
let src: String = "fn leaky() -> Int { dharma_emit(\"x\", \"y\") return 1 }"
let out: String = compile_capture(src)
assert str_contains(out, "may only be called from an @manager fn"), "the compiled-in core prohibition survives being declared rather than branched"
}