Skip to content

Commit fb2bc07

Browse files
hyperpolymathclaude
andcommitted
fix: three compiler fixes for explicit-return functions and Never type
1. unify.ml: add Never (bottom type) as universal unifier TCon "Never" unifies with any type, allowing functions whose all paths return via `return expr;` to pass the type checker. 2. typecheck.ml: block divergence propagation Add always_diverges / block_always_diverges helpers; synth_block returns ty_never (not ty_unit) when all block paths diverge. This makes `fn f() -> T { return x; }` typecheck correctly. 3. parser.mly: block_terminator rule for self-delimiting exprs Split the block rule to accept a `block_terminator` as a final expression: `if`/`match`/nested-`block` end with `}` and are LR(1)-distinguishable from statements without consuming a `;`. Simple literals/idents as block-final still need `return`. Parser conflicts: 1248 → 1245. Together these allow real-world AffineScript source to compile: airborne-submarine-squadron/src/main.as now produces a valid 8KB WASM 1.0 binary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a8fec41 commit fb2bc07

4 files changed

Lines changed: 52 additions & 7 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[metadata]
55
project = "affinescript"
66
version = "0.1.0"
7-
last-updated = "2026-03-29"
7+
last-updated = "2026-04-03"
88
status = "active"
99

1010
[project-context]
@@ -15,11 +15,11 @@ tagline = "Rust-inspired language with affine types, dependent types, row polymo
1515

1616
[components]
1717
lexer = "complete"
18-
parser = "complete"
19-
type-checker = "98%"
18+
parser = "complete (1245 conflicts; block_terminator rule added for self-delimiting exprs)"
19+
type-checker = "99% (Never/bottom type handling fixed; block divergence propagation added)"
2020
borrow-checker = "95%"
2121
interpreter = "95%"
22-
wasm-codegen = "90%"
22+
wasm-codegen = "92% (real-world game compiles: airborne-submarine-squadron/src/main.as → 8KB WASM)"
2323
julia-codegen = "exists"
2424
lsp-phase-a = "complete"
2525
lsp-phase-b = "in-progress"

lib/parser.mly

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,23 @@ unsafe_op:
602602

603603
/* ========== Statements ========== */
604604

605+
(* Self-delimiting expressions that can serve as the final expression in a
606+
block without a trailing semicolon. Because they all end with '}', the
607+
LR(1) parser can distinguish "this was the last expression" (followed by
608+
the outer '}') from "this was a statement" (which would need ';'). *)
609+
block_terminator:
610+
| IF cond = expr then_blk = block else_part = else_part?
611+
{ ExprIf { ei_cond = cond; ei_then = ExprBlock then_blk; ei_else = else_part } }
612+
| MATCH scrutinee = expr LBRACE arms = list(match_arm) RBRACE
613+
{ ExprMatch { em_scrutinee = scrutinee; em_arms = arms } }
614+
| inner = block
615+
{ ExprBlock inner }
616+
605617
block:
606-
| LBRACE stmts = list(stmt) final = expr? RBRACE
607-
{ { blk_stmts = stmts; blk_expr = final } }
618+
| LBRACE stmts = list(stmt) RBRACE
619+
{ { blk_stmts = stmts; blk_expr = None } }
620+
| LBRACE stmts = list(stmt) final = block_terminator RBRACE
621+
{ { blk_stmts = stmts; blk_expr = Some final } }
608622

609623
stmt:
610624
| LET mut_ = MUT? pat = pattern ty = type_annotation? EQ value = expr SEMICOLON

lib/typecheck.ml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,27 @@ and synth_list (ctx : context) (exprs : expr list) : (ty list) result =
830830

831831
(** {1 Block and statement typing} *)
832832

833+
(** Returns true if an expression always diverges — i.e., it never produces
834+
a value in normal control flow. Used to give blocks a Never type when
835+
all paths exit via return. *)
836+
and always_diverges (e : expr) : bool =
837+
match e with
838+
| ExprReturn _ -> true
839+
| ExprBlock blk -> block_always_diverges blk
840+
| ExprIf { ei_cond = _; ei_then; ei_else = Some else_e } ->
841+
always_diverges ei_then && always_diverges else_e
842+
| _ -> false
843+
844+
(** Returns true if a block always diverges (all paths return). *)
845+
and block_always_diverges (blk : block) : bool =
846+
match blk.blk_expr with
847+
| Some e -> always_diverges e
848+
| None ->
849+
begin match List.rev blk.blk_stmts with
850+
| StmtExpr e :: _ -> always_diverges e
851+
| _ -> false
852+
end
853+
833854
and synth_block (ctx : context) (blk : block) : ty result =
834855
(* Type-check each statement for side effects *)
835856
let* () = List.fold_left (fun acc stmt ->
@@ -839,7 +860,14 @@ and synth_block (ctx : context) (blk : block) : ty result =
839860
(* The block's type is the type of the final expression, or Unit *)
840861
match blk.blk_expr with
841862
| Some e -> synth ctx e
842-
| None -> Ok ty_unit
863+
| None ->
864+
(* When all paths through the block diverge (every code path ends with
865+
return/break/etc.), the block has type Never rather than Unit. This
866+
allows functions declared as returning T to have bodies that exclusively
867+
use `return expr;` rather than a final expression. *)
868+
if block_always_diverges blk
869+
then Ok ty_never
870+
else Ok ty_unit
843871

844872
and check_stmt (ctx : context) (stmt : stmt) : unit result =
845873
match stmt with

lib/unify.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ let rec unify (t1 : ty) (t2 : ty) : unit result =
220220
if nat_eq (normalize_nat n1) (normalize_nat n2) then Ok ()
221221
else Error (TypeMismatch (t1, t2))
222222

223+
(* Never (bottom type) unifies with anything — diverging paths are compatible with all types *)
224+
| (TCon "Never", _) | (_, TCon "Never") -> Ok ()
225+
223226
(* Mismatch *)
224227
| _ ->
225228
Error (TypeMismatch (t1, t2))

0 commit comments

Comments
 (0)