Merge remote-tracking branch 'origin/dev' into wt/soul-runtime-reconcile

This commit is contained in:
bigmerge
2026-08-15 21:55:40 -05:00
3 changed files with 79 additions and 6 deletions
+35 -6
View File
@@ -862,10 +862,23 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// arithmetic BinOp (or vice-versa). Without this check the
// fallthrough to str_eq produces str_eq(int_value, int_value)
// which reads the integer as a char* and segfaults.
// EITHER side provably Int is enough. Requiring BOTH meant a call
// whose return type codegen cannot infer poisoned the operator:
// getint(5) == a -> str_eq(getint(5), a)
// even with `a` declared Int. str_eq then reads an integer as a
// char* and segfaults. Only an integer LITERAL on one side forced
// the numeric form, so the bug was invisible in the common case.
//
// Loosening to OR is strictly safer: when one side is a known Int,
// str_eq is always wrong (it dereferences that int), while numeric
// comparison is at worst a wrong answer on an already ill-typed
// program. When neither side is Int nothing changes, so string
// comparison is untouched.
if is_int_expr(left) {
if is_int_expr(right) {
return "(" + left_c + " == " + right_c + ")"
}
return "(" + left_c + " == " + right_c + ")"
}
if is_int_expr(right) {
return "(" + left_c + " == " + right_c + ")"
}
// Float literal or negative float literal: use plain == (bit-equal
// el_val_t comparison). This handles `r0 == 3.0`, `neg == -3.0`, etc.
@@ -921,10 +934,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
}
// Same mixed Ident/BinOp fix as EqEq: use is_int_expr to detect
// integer-typed operands before falling through to !str_eq.
// Either side Int is enough see the EqEq note above.
if is_int_expr(left) {
if is_int_expr(right) {
return "(" + left_c + " != " + right_c + ")"
}
return "(" + left_c + " != " + right_c + ")"
}
if is_int_expr(right) {
return "(" + left_c + " != " + right_c + ")"
}
// Float-typed operands use plain != (bit-equal comparison).
if is_float_expr(left) {
@@ -1495,6 +1510,11 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
if str_eq(ltype, "Int") {
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") {
add_int_name(name)
}
if str_eq(ltype, "Float") {
add_float_name(name)
}
@@ -3113,6 +3133,15 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
if str_eq(ptype, "Int") {
add_int_name(pname)
}
// Bool is an integer in the value model (type_to_c maps Bool -> "int";
// el_runtime.h: "Bool -> el_val_t (0 = false, nonzero = true)"), but
// Bool names were registered nowhere. So `cond == want` between two
// Bool params fell through to str_eq and dereferenced 0 or 1 as a
// char* an immediate segfault. Track them as int-like, which is what
// they are.
if str_eq(ptype, "Bool") {
add_int_name(pname)
}
if str_eq(ptype, "Float") {
add_float_name(pname)
}