fix(check): 0.R4 - a misspelled type in a parameter, a result or a field is refused where it is written
`function kind_of(f: CharFact)` for a CharacterFact was taken on trust and failed in the code writer as "member access on non-aggregate". A capitalised type - plain, in a slice, or a generic's argument - must name a declared property, record, state, event, enum, action or packed value type, or a type parameter of its declaration: `kind_of's parameter f: there is no type CharFact`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c35481f344
commit
2eefae0618
10 changed files with 13423 additions and 10801 deletions
|
|
@ -401,7 +401,10 @@ What it holds apart: `int`, `float`, `fixed` and `bool` (a float or a fixed into
|
|||
text and numbers (`string(n)` or a template); one record type and another; slices of different
|
||||
elements; functions of different types. A call gives exactly as many arguments as there are
|
||||
parameters, a `return` gives the declared result, and `push` gives the slice's own element. A
|
||||
function names each parameter once (`add names two parameters n`).
|
||||
function names each parameter once (`add names two parameters n`). A type written in a parameter, a
|
||||
result or a field names a declared type (or one of the declaration's type parameters): a misspelling
|
||||
is refused there (`kind_of's parameter f: there is no type CharFact`), not later as a member access
|
||||
that makes no sense.
|
||||
|
||||
`pointer` is untyped, as `void *` is in C: it goes wherever a reference is wanted and takes any
|
||||
reference, and a `[]pointer` any slice of references. Restricting what a raw pointer may reach is
|
||||
|
|
|
|||
7
changes/unknown-type-names.md
Normal file
7
changes/unknown-type-names.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**A misspelled type is refused where it is written.** `function kind_of(f: CharFact)` for a
|
||||
`CharacterFact` used to be accepted and fail in the code writer as "member access on non-aggregate",
|
||||
far from the typo. A capitalised type in a parameter, a result or a field (plain, in a slice, or as a
|
||||
generic's argument) must name a declared property, record, state, event, enum or action, or a type
|
||||
parameter of its declaration: `kind_of's parameter f: there is no type CharFact`.
|
||||
6
examples/rejected/unknown_field_type.ludic
Normal file
6
examples/rejected/unknown_field_type.ludic
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# a misspelled type in a field, inside a slice, is refused too
|
||||
program UnknownFieldType {
|
||||
property CharacterFact { kind: int = 0 }
|
||||
property Sheet { facts: []CharFact = null }
|
||||
entry { print("x") }
|
||||
}
|
||||
6
examples/rejected/unknown_type.ludic
Normal file
6
examples/rejected/unknown_type.ludic
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# a misspelled type in a parameter is refused where it is written, not as member access far away
|
||||
program UnknownType {
|
||||
property CharacterFact { kind: int = 0 }
|
||||
function kind_of(f: CharFact) -> int { return f.kind }
|
||||
entry { print(`{kind_of(new CharacterFact)}`) }
|
||||
}
|
||||
|
|
@ -279,6 +279,7 @@ function check_program() -> void {
|
|||
gen_collect()
|
||||
ck_index()
|
||||
ck_runtime_shadows()
|
||||
ck_tynames() # 0.R4: a type written names a type
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
|
|
|
|||
104
selfhost/check/check_tynames.ludic
Normal file
104
selfhost/check/check_tynames.ludic
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# check_tynames.ludic — 0.R4: a type written in a parameter or a field names a type. A misspelled
|
||||
# one (`f: CharFact` for CharacterFact) used to be taken on trust and fail far away, in the emitter,
|
||||
# as "member access on non-aggregate". A capitalised name is a declared type's - a property, a
|
||||
# record, a state, an event, an enum, an action - or a type parameter of the declaration it is in;
|
||||
# anything else is refused where it is written. Lower-case names are the language's own.
|
||||
var ck_ty_k: []pointer = new []pointer
|
||||
var ck_ty_v: []Node = new []Node
|
||||
function ck_ty_is_decl(d: Node) -> bool {
|
||||
let k = d.kind
|
||||
return k == N_COMP or k == N_STRUCT or k == N_EVENT or k == N_ENUM or k == N_ARCH or k == N_PREFAB
|
||||
}
|
||||
function ck_ty_index() -> void {
|
||||
ck_tab_init(ck_ty_k, ck_ty_v)
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.s != null and ck_ty_is_decl(d) { ck_tab_put(ck_ty_k, ck_ty_v, d.s, d) }
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
function ck_ty_upper(c: int) -> bool { return c >= 'A' and c <= 'Z' }
|
||||
function ck_ty_ident(c: int) -> bool { return (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or c == '_' }
|
||||
# the unknown capitalised name in t, or null: t is plain (Name), a slice ([]T) or an instance
|
||||
# (Pool<T, U>); a function type, a pointer or anything else is left to the emitter
|
||||
function ck_ty_unknown(t: pointer, tps: pointer) -> pointer {
|
||||
if t == null { return null }
|
||||
let n = len(t)
|
||||
if n > 1 and t[0] == '[' and t[1] == ']' { return ck_ty_unknown(t[2 .. n], tps) }
|
||||
if n == 0 or not ck_ty_upper(t[0]) { return null }
|
||||
var e = 0
|
||||
while e < n and ck_ty_ident(t[e]) { e += 1 }
|
||||
let name = t[0 .. e]
|
||||
if e < n and t[e] != '<' { return null }
|
||||
if ck_ty_known(name, tps) {
|
||||
if e < n { return ck_ty_args(t[e + 1 .. n], tps) }
|
||||
return null
|
||||
}
|
||||
return name
|
||||
}
|
||||
# the arguments of an instance, `A, []B>`, each checked (nested instances by their own brackets)
|
||||
function ck_ty_args(s: pointer, tps: pointer) -> pointer {
|
||||
var depth = 0
|
||||
var a = 0
|
||||
var i = 0
|
||||
while i < len(s) {
|
||||
let c = s[i]
|
||||
if c == '<' { depth += 1 }
|
||||
if (c == ',' or c == '>') and depth == 0 {
|
||||
var b = a
|
||||
while b < i and s[b] == ' ' { b += 1 }
|
||||
let bad = ck_ty_unknown(s[b .. i], tps)
|
||||
if bad != null { return bad }
|
||||
a = i + 1
|
||||
}
|
||||
if c == '>' and depth > 0 { depth -= 1 }
|
||||
i += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
function ck_ty_known(name: pointer, tps: pointer) -> bool {
|
||||
if ck_tab_get(ck_ty_k, ck_ty_v, name) != null { return true }
|
||||
if (name == "Vector") or (name == "IVec2") or (name == "Rect") { return true } # the packed value types
|
||||
if is_state_ty(name) or ck_record(name) != null or ck_enum(name) != null { return true }
|
||||
if gen_template(g_gen_recs, name) != null { return true }
|
||||
if tps != null {
|
||||
let ps = gen_split(tps)
|
||||
var i = 0
|
||||
while i < len(ps) {
|
||||
if (ps[i] == name) { return true }
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
function ck_ty_say(n: Node, t: pointer, tps: pointer, what: pointer) -> void {
|
||||
let bad = ck_ty_unknown(t, tps)
|
||||
if bad != null { ck_err("type", n, `{what}: there is no type {bad}`) }
|
||||
}
|
||||
# every function's parameters and result, every record's fields
|
||||
function ck_tynames() -> void {
|
||||
ck_ty_index()
|
||||
var i = 0
|
||||
while i < g_prog_user_end and i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_FN and d.file != null and d.pos >= 0 {
|
||||
var k = 0
|
||||
while k < len(d.kids) {
|
||||
let p = d.kids[k]
|
||||
if p.kind == N_PARAM { ck_ty_say(p, p.ty, d.tps, `{d.s}'s parameter {p.s}`) }
|
||||
k += 1
|
||||
}
|
||||
ck_ty_say(d, d.ty, d.tps, `{d.s}'s result`)
|
||||
}
|
||||
if (d.kind == N_COMP or d.kind == N_STRUCT or d.kind == N_EVENT) and d.file != null and d.pos >= 0 {
|
||||
var f = 0
|
||||
while f < len(d.kids) {
|
||||
let x = d.kids[f]
|
||||
if x.kind == N_FIELD { ck_ty_say(x, x.ty, d.tps, `{d.s}.{x.s}`) }
|
||||
f += 1
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
12046
selfhost/ludicc.seed.ll
12046
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -88,6 +88,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/check/check_stmt.ludic")
|
||||
push(f, "selfhost/check/check_gen.ludic")
|
||||
push(f, "selfhost/check/check_gen_call.ludic")
|
||||
push(f, "selfhost/check/check_tynames.ludic")
|
||||
push(f, "selfhost/check/check_unsafe.ludic")
|
||||
push(f, "selfhost/backend/emit_stmt.ludic")
|
||||
push(f, "selfhost/backend/game/emit_ecs.ludic")
|
||||
|
|
|
|||
|
|
@ -1027,6 +1027,8 @@ function cmd_dev_test() -> int {
|
|||
migrate_prune_case()
|
||||
vis_report_case("rejected/uses_missing", "uses: examples/rejected/uses_valley/items/index.ludic:3: items.inv_add used from", "LUDIC_VIS_REPORT=1 lists a uses violation and builds")
|
||||
feat_case("lang/checked", "", "7.5 3 12 hi 2 ok", "checked.ludic (L4: literals take their slot's kind, string(p), []string as []pointer, null, named args)")
|
||||
reject_case("rejected/unknown_type", "kind_of's parameter f: there is no type CharFact", "a misspelled type in a parameter is refused where it is written")
|
||||
reject_case("rejected/unknown_field_type", "Sheet.facts: there is no type CharFact", "a misspelled type in a field is refused where it is written")
|
||||
reject_case("rejected/param_twice", "add names two parameters n", "a parameter named twice is refused")
|
||||
reject_case("rejected/wrong_arity", "this call to area leaves out h, which has no default", "a call with the wrong number of arguments is refused")
|
||||
reject_case("rejected/float_into_int", "metres wants an int and this is a float", "a float computed into an int is refused")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue