land int signatures, and repair a silent miscompilation they exposed

This commit is contained in:
bigmerge
2026-08-17 09:32:13 -05:00
3 changed files with 71 additions and 40 deletions
+12 -40
View File
@@ -1517,6 +1517,16 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
if str_eq(ltype, "Int") {
add_int_name(name)
}
// UNANNOTATED let: take the type from what the initialiser RETURNS.
// Without this, `let a = str_len(s)` loses the Int and a later `a + b`
// lowers to el_str_concat on two integers -- silently, with no error,
// producing a program that prints nothing where it should print 7.
// Verified present in the pre-change compiler too, so this is a repair
// rather than a regression. The return types were already needed for
// dispatch; they were just never consulted here.
if str_eq(ltype, "") {
if is_int_expr(val) { add_int_name(name) }
}
// Same as params: Bool is an int in the value model. Without this a
// `let ok: Bool = ...` compared to another Bool lowered to str_eq.
if str_eq(ltype, "Bool") {
@@ -1943,48 +1953,10 @@ fn is_duration_name(name: String) -> Bool {
// string-concat on `+` when one side is a Call. New builtins must be added
// here when they return Int and may participate in arithmetic.
fn is_int_call(call_expr: Map<String, Any>) -> Bool {
let func = call_expr["func"]
let fk: String = func["expr"]
if !str_eq(fk, "Ident") { return false }
let name: String = func["name"]
if str_eq(name, "str_len") { return true }
if str_eq(name, "str_index_of") { return true }
if str_eq(name, "str_to_int") { return true }
if str_eq(name, "str_char_code") { return true }
if str_eq(name, "str_count") { return true }
if str_eq(name, "str_count_chars") { return true }
if str_eq(name, "str_count_bytes") { return true }
if str_eq(name, "str_count_lines") { return true }
if str_eq(name, "str_count_words") { return true }
if str_eq(name, "str_count_letters") { return true }
if str_eq(name, "str_count_digits") { return true }
if str_eq(name, "str_last_index_of") { return true }
if str_eq(name, "str_find_chars") { return true }
if str_eq(name, "native_list_len") { return true }
if str_eq(name, "el_list_len") { return true }
if str_eq(name, "len") { return true }
if str_eq(name, "json_get_int") { return true }
if str_eq(name, "json_array_len") { return true }
if str_eq(name, "engram_node_count") { return true }
if str_eq(name, "engram_edge_count") { return true }
if str_eq(name, "time_now") { return true }
if str_eq(name, "time_now_utc") { return true }
if str_eq(name, "time_diff") { return true }
if str_eq(name, "time_add") { return true }
if str_eq(name, "time_from_parts") { return true }
if str_eq(name, "el_abs") { return true }
if str_eq(name, "el_max") { return true }
if str_eq(name, "el_min") { return true }
if str_eq(name, "float_to_int") { return true }
if str_eq(name, "unix_timestamp") { return true }
if str_eq(name, "instant_to_unix_seconds") { return true }
if str_eq(name, "instant_to_unix_millis") { return true }
if str_eq(name, "duration_to_seconds") { return true }
if str_eq(name, "duration_to_millis") { return true }
if str_eq(name, "duration_to_nanos") { return true }
return false
call_returns(call_expr, "Int")
}
// Known runtime builtins that return Float. Parallel to is_int_call lets a
// Call participate in float arithmetic (and get inferred into __float_names on
// an unannotated `let`). New Float-returning builtins must be added here.
+19
View File
@@ -924,3 +924,22 @@ test "compiler-no-longer-emits-prohibition-errors" {
let out: String = compile_capture(src)
assert !str_contains(out, "boundary violation"), "the emitter does not adjudicate"
}
// Int return types drive + dispatch
//
// El has one type, so `a + b` must be dispatched from what the operands ARE.
// The 35 Int-returning builtins moved to signatures.rel; the dispatch stayed,
// because choosing between arithmetic and concatenation is emission.
test "int-returning-builtin-drives-arithmetic-dispatch" {
let src: String = "fn main() { let a = str_len(\"hello\") let b = str_len(\"hi\") let c = a + b println(int_to_str(c)) }"
let out: String = compile_capture(src)
assert str_contains(out, "(a + b)"), "Int + Int is arithmetic"
assert !str_contains(out, "el_str_concat(a, b)"), "and NOT concatenation"
}
test "string-plus-string-still-concatenates" {
let src: String = "fn main() { let s = \"a\" + \"b\" println(s) }"
let out: String = compile_capture(src)
assert str_contains(out, "el_str_concat"), "String + String still concatenates"
}
+40
View File
@@ -31,3 +31,43 @@ el_duration_sub returns Duration
el_duration_scale returns Duration
el_duration_div returns Duration
ttl_cache_age returns Duration
# Int-returning builtins. Previously 35 hardcoded names in is_int_call().
# These decide whether `a + b` is arithmetic or concatenation, so the
# compiler reads them at emission time -- dispatch, not adjudication.
str_len returns Int
str_index_of returns Int
str_to_int returns Int
str_char_code returns Int
str_count returns Int
str_count_chars returns Int
str_count_bytes returns Int
str_count_lines returns Int
str_count_words returns Int
str_count_letters returns Int
str_count_digits returns Int
str_last_index_of returns Int
str_find_chars returns Int
native_list_len returns Int
el_list_len returns Int
len returns Int
json_get_int returns Int
json_array_len returns Int
engram_node_count returns Int
engram_edge_count returns Int
time_now returns Int
time_now_utc returns Int
time_diff returns Int
time_add returns Int
time_from_parts returns Int
el_abs returns Int
el_max returns Int
el_min returns Int
float_to_int returns Int
unix_timestamp returns Int
instant_to_unix_seconds returns Int
instant_to_unix_millis returns Int
duration_to_seconds returns Int
duration_to_millis returns Int
duration_to_nanos returns Int