codegen: type-driven dispatch for + between Int idents

Closes the known limitation from the self-host commit: `fn add(a:Int,
b:Int) { a + b }` now compiles to integer addition, not string concat.
Previously the codegen heuristic guessed string concat whenever both
operands were Idents with no literal anchor.

Mechanism
- parser captures the leading type identifier from `let x: T = ...`
  bindings (new "type" field on Let) and from function parameter
  annotations (new "type" field on each param).
- codegen maintains a per-function int-name set in process state via
  state_set("__int_names", csv). cg_fn seeds it from typed parameters;
  cg_stmt extends it from typed `let` bindings and from `let x = <Int
  literal>` (literal inference).
- BinOp Plus: when both sides are Idents and both names are in the
  int-name set, emit arithmetic; otherwise the existing literal-anchor
  heuristic applies, with string concat as the fallback.

This is the first compiler change made entirely through the self-
hosting workflow — no Python bootstrap. Edit el source, run existing
elc on elc-combined.el, cc the output, test. Closure holds at the
new binary.

Tests
- add(40, 2) → 42
- count_to(10) → 45 (let i: Int / let total: Int rebinding)
- Regression suite (tiny/implret/whiletest/lextest) unchanged.

dist/platform/elc updated; .prev preserved.
This commit is contained in:
Will Anderson
2026-04-30 13:13:38 -05:00
parent 5c05ce9b99
commit 2eddaf1fe6
6 changed files with 249 additions and 13 deletions
+17 -3
View File
@@ -119,8 +119,15 @@ fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let pname = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
// Capture the leading type identifier so codegen can dispatch
// arithmetic vs string-concat on `+` based on declared types.
let ptype = ""
let kt = tok_kind(tokens, p)
if kt == "Ident" {
let ptype = tok_value(tokens, p)
}
let p = skip_type(tokens, p)
let param = { "name": pname }
let param = { "name": pname, "type": ptype }
let params = native_list_append(params, param)
let k2 = tok_kind(tokens, p)
if k2 == "Comma" {
@@ -533,17 +540,24 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = pos + 1
let name = tok_value(tokens, p)
let p = p + 1
let ltype = ""
let k2 = tok_kind(tokens, p)
// optional type annotation: name: Type
// optional type annotation: name: Type capture the leading
// identifier so codegen can dispatch arithmetic vs concat on
// `+` between two typed Idents.
if k2 == "Colon" {
let p = p + 1
let kt = tok_kind(tokens, p)
if kt == "Ident" {
let ltype = tok_value(tokens, p)
}
let p = skip_type(tokens, p)
}
let p = expect(tokens, p, "Eq")
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
return make_result({ "stmt": "Let", "name": name, "value": val, "type": ltype }, p)
}
// return statement