feat(lang): L3 module scope - module, export, friend module
A barrel's 'module NAME' makes its directory a module; a declaration other modules use says 'export'. Private use from another module is an error naming the module and where to mark it; 'friend module' sees everything (a test harness); a file in no module is public and a package keeps its own module. LUDIC_VIS_REPORT=1 lists every violation instead of stopping, so a codebase can be given its exports first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0c73287e35
commit
0677aee93f
20 changed files with 55913 additions and 54132 deletions
|
|
@ -1409,6 +1409,7 @@ function emit_call(e: Node) -> Val {
|
|||
if (fn2 == null) { perr(`unknown function {name}`) }
|
||||
cname = rtname
|
||||
}
|
||||
vis_check(fn2, name)
|
||||
reorder_named(e, param_labels(fn2))
|
||||
# evaluate args first (their IR is emitted before the call instruction), coercing
|
||||
# each to the parameter's declared type so an int passed for a `long` widens.
|
||||
|
|
@ -1471,6 +1472,7 @@ function emit_expr(e: Node) -> Val {
|
|||
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }
|
||||
let g = find_global(e.s)
|
||||
if (g != null) {
|
||||
vis_check(g, e.s)
|
||||
# a const reference IS its initializer expression, carrying that
|
||||
# expression's real type — so `const X: fixed = 10.0` yields a `fixed`, not
|
||||
# the raw Q16.16 bits mislabelled `int`. Every existing const is an int
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ function fn_ty_ret(t: pointer) -> pointer {
|
|||
function emit_fnref(e: Node) -> Val {
|
||||
let d = find_fn(e.s)
|
||||
if d == null { perr(`fn {e.s}: no function called {e.s}`) }
|
||||
vis_check(d, e.s)
|
||||
return val(`@fn_{e.s}`, fn_sig_of(d))
|
||||
}
|
||||
# a call through a value of a function type
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ function rec_field(rec: Node, fname: pointer) -> Node {
|
|||
function emit_new_struct(name: pointer, rec: Node) -> Val {
|
||||
let s = layout_node(name) # a struct or a property — same shape
|
||||
if (s == null) { perr("unknown record type in new") }
|
||||
vis_check(s, name)
|
||||
let lty = layout_ty(name)
|
||||
let sz = emit_sizeof(lty)
|
||||
let obj = emit_bind(`call ptr @malloc(i64 {sz})`)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ function emit_assign(st: Node) -> void {
|
|||
else {
|
||||
let g = find_global(t.s)
|
||||
if (g == null) { perr(`assign to unknown {t.s}`) }
|
||||
vis_check(g, t.s)
|
||||
addr = `@g_{t.s}`; ty = g.ty
|
||||
}
|
||||
} else {
|
||||
|
|
@ -291,6 +292,7 @@ function emit_match(st: Node) -> void {
|
|||
function emit_emit(st: Node) -> Val {
|
||||
let ev = find_event(st.s)
|
||||
if (ev == null) { perr(`emit: unknown event {st.s}`) }
|
||||
vis_check(ev, st.s)
|
||||
# evaluate each payload field in declared order (default for a missing arg)
|
||||
let fcodes = new []pointer
|
||||
let ftys = new []pointer
|
||||
|
|
|
|||
37
selfhost/backend/emit_vis.ludic
Normal file
37
selfhost/backend/emit_vis.ludic
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# emit_vis.ludic — L3: module scope. A file belongs to the module its own `module NAME` line or
|
||||
# its importer's names (frontend/parse.ludic, module_of); a file in no module is public. A
|
||||
# reference from one module to another module's function, global, record or event needs that
|
||||
# declaration `export`ed, unless the referring module is a `friend module` (a test harness).
|
||||
# LUDIC_VIS_REPORT=1 prints every violation as `vis: <file>: <module>.<name>` and goes on, so a
|
||||
# tool can add the exports a codebase needs before the rule is switched on for it.
|
||||
var g_vis_report: int = -1
|
||||
function vis_reporting() -> bool {
|
||||
if g_vis_report < 0 {
|
||||
g_vis_report = 0
|
||||
if getenv("LUDIC_VIS_REPORT") != null { g_vis_report = 1 }
|
||||
}
|
||||
return g_vis_report == 1
|
||||
}
|
||||
function vis_allowed(d: Node, from: pointer) -> bool {
|
||||
if d.vis == 1 { return true }
|
||||
if d.file == null { return true }
|
||||
let to = module_of(d.file)
|
||||
if to == "" { return true }
|
||||
if from == to { return true }
|
||||
return module_is_friend(from)
|
||||
}
|
||||
# `what` is the name as written at the reference
|
||||
function vis_check(d: Node, what: pointer) -> void {
|
||||
if d == null { return }
|
||||
var here = g_err_file
|
||||
if here == null { return }
|
||||
let from = module_of(here)
|
||||
if vis_allowed(d, from) { return }
|
||||
let to = module_of(d.file)
|
||||
if vis_reporting() {
|
||||
let m = `vis: {d.file}:{itoa(d.line)}: {to}.{what} used from {here}\n`
|
||||
file_write(file_stderr(), m, len(m))
|
||||
return
|
||||
}
|
||||
perr(`{what} is private to module {to}; mark it 'export' where it is declared ({d.file})`)
|
||||
}
|
||||
|
|
@ -658,6 +658,8 @@ function emit_event_fns() -> void {
|
|||
g_term = false
|
||||
let next = lbl("evnext")
|
||||
g_ret_label = next
|
||||
g_err_file = g_onlisten[i].file
|
||||
vis_check(ev, en)
|
||||
emit_block(g_onlisten[i].a)
|
||||
g_ret_label = "ret"
|
||||
if not g_term { emit(` br label %{next}\n`) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue