fix(check): a Math.* call has the type the emitter gives it
Math.max, Math.sqrt and the rest (and the bare min/max/abs/clamp) are computed inline by the
emitter, and L4 gave each the unknown type, which agrees with everything: Maroon Lake's trail
put Math.max(5, n) into Notify's string field a1 and it failed in LLVM ("%t63 defined with type
i32 but expected ptr"). It was never about two dispatches on a line - one is enough. The checker
now mirrors the emitter: a float/double first argument gives that type (sign an int); otherwise
min/max/abs/clamp keep the first argument's type, sign/floor/ceil/round/posmod/wrap/ping_pong are
ints, the rest fixed. Named arguments or an argument it cannot type leave it unknown.
rejected/math_into_text is the case. Reseeded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e6729b3123
commit
8e113f749a
6 changed files with 13780 additions and 12630 deletions
10
changes/math-result-types.md
Normal file
10
changes/math-result-types.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**A `Math.*` call has a type the checker knows.** `Math.max`, `Math.sqrt` and the rest are computed
|
||||||
|
inline by the emitter, and the checker gave every one of them the unknown type, which agrees with
|
||||||
|
everything - so `dispatch Notify { a1: Math.max(5, n) }` put an int into a string field, passed L4
|
||||||
|
and failed in LLVM (`%t63 defined with type i32 but expected ptr`). The checker now gives each the
|
||||||
|
type the emitter does: a float or double first argument is that type (`sign` an int); otherwise
|
||||||
|
`min`, `max`, `abs` and `clamp` keep the first argument's type, `sign`, `floor`, `ceil`, `round`,
|
||||||
|
`posmod`, `wrap` and `ping_pong` are ints and the rest fixed. The bare `min` / `max` / `abs` /
|
||||||
|
`clamp` builtins the same. Where the arguments cannot be told, the result stays unknown.
|
||||||
16
examples/rejected/math_into_text.ludic
Normal file
16
examples/rejected/math_into_text.ludic
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# L4: Math.max is typed like any call - its int is refused in an action's text field, the second
|
||||||
|
# of two dispatches on one line (it once passed as "unknown" and failed in LLVM)
|
||||||
|
program MathIntoText {
|
||||||
|
numbers float
|
||||||
|
action Note { text: string, a1: string = "", i1: int = 0 }
|
||||||
|
state Log { n: int = 0 }
|
||||||
|
reducer Log on Note(log: mut Log, a: Note) { log.n = log.n + a.i1 }
|
||||||
|
function said(k: int, d: float) -> void {
|
||||||
|
let m = Math.max(5, int(d / 5.0 + 0.5) * 5)
|
||||||
|
if k > 0 { dispatch Note { text: "about {1} m", i1: m } } else { dispatch Note { text: "about {1} m", a1: m } }
|
||||||
|
}
|
||||||
|
entry {
|
||||||
|
said(1, 12.0)
|
||||||
|
drain_actions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -178,6 +178,7 @@ function ck_call(e: Node) -> pointer {
|
||||||
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
||||||
# an alias (L6) is its target, labels and all, so its arguments are checked in full
|
# an alias (L6) is its target, labels and all, so its arguments are checked in full
|
||||||
if (b.s == "Memory") { ck_raw(e, `Memory.{c.s}`) }
|
if (b.s == "Memory") { ck_raw(e, `Memory.{c.s}`) }
|
||||||
|
if (b.s == "Math") and is_math_ns(c.s) { return ck_math(e, c.s) }
|
||||||
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])
|
||||||
|
|
@ -250,9 +251,47 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
||||||
mg_ref(e.a, g) # 0.S2: a call through a var of a function type reads it
|
mg_ref(e.a, g) # 0.S2: a call through a var of a function type reads it
|
||||||
return ck_call_sig(e, name, g.ty)
|
return ck_call_sig(e, name, g.ty)
|
||||||
}
|
}
|
||||||
|
if is_math_builtin(name) { return ck_math(e, name) }
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
|
# Math.max(a, b), bare max(a, b): the emitter computes these inline, and their type is the one it
|
||||||
|
# gives them - a float or double argument first is that type (sign an int), else the fixed-point
|
||||||
|
# path: min, max, abs and clamp keep the first argument's type, the rounding and wrapping ones an
|
||||||
|
# int, the rest a fixed. Left "?", a Math.max(5, n) went into a string field and failed in LLVM.
|
||||||
|
function ck_math(e: Node, meth: pointer) -> pointer {
|
||||||
|
if len(e.kids) == 0 or e.kids[0].kind == E_FINIT {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
let tys = new []pointer
|
||||||
|
var i = 0
|
||||||
|
while i < len(e.kids) {
|
||||||
|
push(tys, ck_expr(e.kids[i]))
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
if ck_unknown(tys[0]) { return "?" }
|
||||||
|
if ck_is_float(tys[0]) {
|
||||||
|
if (meth == "sign") { return "int" }
|
||||||
|
var t = tys[0]
|
||||||
|
i = 1
|
||||||
|
while i < len(tys) {
|
||||||
|
if (tys[i] == "double") { t = "double" }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
# a later float argument moves the emitter onto the float path only when it can see it
|
||||||
|
i = 1
|
||||||
|
while i < len(tys) {
|
||||||
|
if ck_unknown(tys[i]) or ck_is_float(tys[i]) { return "?" }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") { return tys[0] }
|
||||||
|
if (meth == "sign") or (meth == "floor") or (meth == "ceil") or (meth == "round") { return "int" }
|
||||||
|
if (meth == "posmod") or (meth == "wrap") or (meth == "ping_pong") { return "int" }
|
||||||
|
return "fixed"
|
||||||
|
}
|
||||||
# emit E(field: v): each field gets the type the event declares for it
|
# emit E(field: v): each field gets the type the event declares for it
|
||||||
function ck_emit(s: Node) -> void {
|
function ck_emit(s: Node) -> void {
|
||||||
let ev = find_event(s.s)
|
let ev = find_event(s.s)
|
||||||
|
|
|
||||||
13172
selfhost/ludicc.seed.ll
13172
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1071,6 +1071,7 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/record_mixup", "d wants a Door and this is a Key", "one record type is not another")
|
reject_case("rejected/record_mixup", "d wants a Door and this is a Key", "one record type is not another")
|
||||||
reject_case("rejected/record_as_text", "argument 1 of sign wants a string and this is a Trail", "a record is not text")
|
reject_case("rejected/record_as_text", "argument 1 of sign wants a string and this is a Trail", "a record is not text")
|
||||||
reject_case("rejected/text_plus_number", "text joins text only", "text plus a number is refused")
|
reject_case("rejected/text_plus_number", "text joins text only", "text plus a number is refused")
|
||||||
|
reject_case("rejected/math_into_text", "field a1 of Note wants a string and this is an int", "a Math.* result has its type: an int from Math.max is not text")
|
||||||
reject_count("rejected/every_error", 3, "every type error in a program is reported at once, not only the first")
|
reject_count("rejected/every_error", 3, "every type error in a program is reported at once, not only the first")
|
||||||
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
feat_case("lang/generics", "", "7 Crater Lake a 2 west 7 0.75 2.5 1", "generics.ludic (L5: generic records and functions, nested instances, fn-typed parameters and fields, inference from the result's slot)")
|
||||||
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
reject_case("rejected/generic_unbound", "cannot tell what T is in this call to pool_new", "a call whose type arguments nothing decides is refused")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue