EXPERIMENT: Int return types as data — and the bug that fell out
PREDICTIONS AND RESULTS
P1 is_int_call's 35 hardcoded names move to data TRUE
P2 is_int_name stays -- it is annotation propagation TRUE
P3 the dispatch stays -- it is emission TRUE
P4 codegen shrinks ~40 lines TRUE 4507 -> 4469
P5 the design doc's characterisation is WRONG TRUE
P6 the moved data also fixes the bug it exposed TRUE
P5 CORRECTS THE RECORD. el-language-design.md and geometry-vs-code.md both cite
"== lowering to str_eq unless both operand names are in a hardcoded int-name
set -- a literal list of variable names treated as integers" as the paradigm
defect. It is not one. __int_names is populated from TYPE ANNOTATIONS
(param["type"] == "Int"), which is primitive but legitimate type propagation.
The actual defect was is_int_call: 35 hardcoded builtin return types, the same
shape as the temporal 19.
P6 IS A LIVE CORRECTNESS BUG, PRE-EXISTING, NOW FIXED
let a = str_len("hello") // no annotation
let b = str_len("hi")
let c = a + b // -> el_str_concat(a, b) on two integers
Verified identical on the pre-change compiler, so not a regression. It compiled
clean, ran, and printed NOTHING where it should print 7. No error at any layer.
The repair is three lines: an unannotated let takes its type from what the
initialiser returns. The return types were already required for dispatch and
were simply never consulted at the binding site. Moving them into data is what
made the gap visible -- reading the code for eight hours did not.
98/98 native + 2 new, 31/31 integration, fixpoint ok.
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user