feat(lang): module NAME uses A, B - a module reaches only the modules it names

A reference from a module that declares uses into a module it does not
name is refused, exported or not, naming the use and the fix. A module
with no uses clause keeps the old rule; a package's module is always
usable; a friend is not held to it; a cycle in the declared graph is
refused; LUDIC_VIS_REPORT=1 lists the violations as uses: lines. Reseed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 04:06:45 +03:00
parent dd6a449921
commit f4533331e7
19 changed files with 53571 additions and 50826 deletions

View file

@ -0,0 +1,12 @@
# uses.ludic — L3: `module fishing uses base` names the only modules fishing may reach; the program
# itself is in no module, so it may call anything exported. A module with no `uses` line keeps the
# old rule, which is how the rule comes into a codebase one module at a time.
#
# Running it prints: 8 3
import "valley/base"
import "valley/fishing"
program Uses {
entry {
print(`{cast(3)} {bait()}`)
}
}

View file

@ -0,0 +1,5 @@
# base/index.ludic - a module with no uses line keeps the old rule: its exports are anyone's
module base
export function bait() -> int {
return 3
}

View file

@ -0,0 +1,4 @@
# fishing/catch.ludic - inherits `module fishing uses base` from its barrel
export function cast(times: int) -> int {
return times * bait() - 1
}

View file

@ -0,0 +1,3 @@
# fishing/index.ludic - fishing names the one module it may reach
module fishing uses base
import "catch.ludic"

View file

@ -0,0 +1,8 @@
# L3: two modules that each say they use the other are refused, whatever they call
import "uses_cycle/a"
import "uses_cycle/b"
program UsesCycle {
entry {
print(two())
}
}

View file

@ -0,0 +1,5 @@
# a/index.ludic - a uses b
module a uses b
export function one() -> int {
return 1
}

View file

@ -0,0 +1,5 @@
# b/index.ludic - and b uses a: the declared graph goes round
module b uses a
export function two() -> int {
return one() + 1
}

View file

@ -0,0 +1,8 @@
# L3: fishing says `uses base` and calls items.inv_add - exported, but not a module fishing names
import "uses_valley/items"
import "uses_valley/fishing"
program UsesMissing {
entry {
print(land())
}
}

View file

@ -0,0 +1,5 @@
# fishing/index.ludic - says it uses base, and then reaches into items
module fishing uses base
export function land() -> int {
return inv_add(1)
}

View file

@ -0,0 +1,5 @@
# items/index.ludic - exports inv_add, but that is not enough for a module that says `uses`
module items
export function inv_add(n: int) -> int {
return n + 1
}