fix(lang): a function named like an engine namespace method's target is refused

Random.range is rng_range, so a package's own rng_range(a, b, c) took
every Random.range call silently. Where the program calls such a method
and the target resolves to a function of its own, the function is
refused, naming the namespace method and the call. Reseed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 05:14:45 +03:00
parent 31d842fca4
commit 73129b59d5
8 changed files with 13668 additions and 13100 deletions

View file

@ -283,6 +283,13 @@ way in its own files. What is still built into the compiler is the namespaces th
`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their `Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their
target by an argument's type (`Audio.play` of a handle or a name). target by an argument's type (`Audio.play` of a handle or a name).
A function of the program's own that is named like the target of one of the engine's namespace
methods would take that method's calls - `Random.range` is `rng_range`, so a package's
`rng_range(a, b, c)` would receive every `Random.range(1, 6)`. Where the program calls that method,
the function is refused (`rng_range is the engine's Random.range, which this program calls (...),
and every such call would reach this function instead; choose another name`), as a function named
like a compiler built-in (`run`, `exit`) or like one of the engine runtime's own functions is.
### Registries (`registry`, `def`) ### Registries (`registry`, `def`)
A table of records that code used to fill with calls in an init function is declared instead: A table of records that code used to fill with calls in an init function is declared instead:

View file

@ -0,0 +1,6 @@
bump: patch
type: fix
**A function named like an engine namespace method's target is refused where that method is
called.** `Random.range` is `rng_range`, so a package's own `rng_range(a, b, c)` silently took
every `Random.range(1, 6)` (and the checker then asked for its third argument). It is now an error
naming the function, the namespace method and the call.

View file

@ -0,0 +1,10 @@
# a function named like the target of one of the engine's namespace methods would take that
# method's calls: Random.range is rng_range, so this rng_range is refused where Random.range is used
program EngineAliasName {
function rng_range(a: int, b: int, c: int) -> int {
return a + b + c
}
entry {
print(Random.range(1, 6))
}
}

View file

@ -148,6 +148,21 @@ function ck_builtin(e: Node, name: pointer) -> pointer {
} }
return null return null
} }
# a program's function named like the target of one of the engine's own namespace methods takes
# that method's calls - a package's rng_range(a, b, c) made every Random.range a call to it, and
# the checker then asked for its third argument. Refused where the method is called.
var ck_shadow_seen: []pointer = new []pointer
function ck_alias_shadow(e: Node, al: int, tf: Node) -> void {
if not has_sub(g_al_file[al], "runtime/native/") { return }
if tf.file == null or has_sub(tf.file, "runtime/native/") { return }
var i = 0
while i < len(ck_shadow_seen) {
if (ck_shadow_seen[i] == tf.s) { return }
i += 1
}
push(ck_shadow_seen, tf.s)
ck_err("shadow", tf, `{tf.s} is the engine's {g_al_ns[al]}.{g_al_meth[al]}, which this program calls ({e.file}:{itoa(e.line)}), and every such call would reach this function instead; choose another name`)
}
function ck_call(e: Node) -> pointer { function ck_call(e: Node) -> pointer {
let c = e.a let c = e.a
if c.kind == E_ID { return ck_call_named(e, c.s) } if c.kind == E_ID { return ck_call_named(e, c.s) }
@ -159,6 +174,7 @@ function ck_call(e: Node) -> pointer {
let al = ns_alias_find(b.s, c.s) let al = ns_alias_find(b.s, c.s)
if al >= 0 { if al >= 0 {
var tf = ck_fn(g_al_target[al]) var tf = ck_fn(g_al_target[al])
if tf != null { ck_alias_shadow(e, al, tf) }
if tf == null { if tf == null {
tf = ck_extern(g_al_target[al]) tf = ck_extern(g_al_target[al])
if tf != null { ck_raw(e, `{b.s}.{c.s}, a C function`) } if tf != null { ck_raw(e, `{b.s}.{c.s}, a C function`) }

View file

@ -9,6 +9,7 @@ var g_al_ns: []pointer = new []pointer
var g_al_meth: []pointer = new []pointer var g_al_meth: []pointer = new []pointer
var g_al_target: []pointer = new []pointer var g_al_target: []pointer = new []pointer
var g_al_labels: []pointer = new []pointer # "a|b|c"; "" for none; null for the target's own var g_al_labels: []pointer = new []pointer # "a|b|c"; "" for none; null for the target's own
var g_al_file: []pointer = new []pointer # where the alias is declared (the engine's: runtime/native)
function ns_parse_alias(nsname: pointer, is_exp: int) -> void { function ns_parse_alias(nsname: pointer, is_exp: int) -> void {
pi += 1 pi += 1
@ -33,6 +34,7 @@ function ns_parse_alias(nsname: pointer, is_exp: int) -> void {
push(g_al_meth, meth) push(g_al_meth, meth)
push(g_al_target, target) push(g_al_target, target)
push(g_al_labels, labels) push(g_al_labels, labels)
push(g_al_file, g_parse_file)
} }
function ns_alias_find(ns: pointer, meth: pointer) -> int { function ns_alias_find(ns: pointer, meth: pointer) -> int {
var i = 0 var i = 0

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -767,6 +767,7 @@ function cmd_dev_test() -> int {
feat_case("events/answer", "", "10 20 0", "answer.ludic (a listener's return ends that listener, not the dispatch)") feat_case("events/answer", "", "10 20 0", "answer.ludic (a listener's return ends that listener, not the dispatch)")
reject_case("rejected/duplicate_const", "'KEY_LEFT' is defined twice", "a const defined twice is refused, naming both places") reject_case("rejected/duplicate_const", "'KEY_LEFT' is defined twice", "a const defined twice is refused, naming both places")
reject_case("rejected/duplicate_property", "'Look' is defined twice", "a property defined twice is refused") reject_case("rejected/duplicate_property", "'Look' is defined twice", "a property defined twice is refused")
reject_case("rejected/engine_alias_name", "rng_range is the engine's Random.range, which this program calls", "a function named like an engine namespace method's target is refused where that method is called")
reject_case("rejected/builtin_name", "run is a built-in of the compiler", "a function named like a compiler built-in (run, exit, free...) is refused") reject_case("rejected/builtin_name", "run is a built-in of the compiler", "a function named like a compiler built-in (run, exit, free...) is refused")
reject_case("rejected/redeclared_local", "'v0' is declared twice in this block", "a local declared twice in one block is refused") reject_case("rejected/redeclared_local", "'v0' is declared twice in this block", "a local declared twice in one block is refused")
reject_case("rejected/missing_return", "can reach its end without returning", "a function that can run off its end without its result is refused") reject_case("rejected/missing_return", "can reach its end without returning", "a function that can run off its end without its result is refused")