Skip to content

Commit e47af49

Browse files
author
Andre Ferreira
committed
chore(tasks): create 5 follow-up tasks from phase12c invariant audit
From docs/specs/typechecker-invariants.md Appendix B (11 latent bugs) and Appendix C (6 audit gaps): - phase0j_enum-maybe-generic-inference — B-03 (enum type args) + B-04 (Maybe.map wrong return) - phase0k_types-compatible-hardening — B-05 (ImplBehaviorType bypass, high risk) - phase0l_typechecker-small-bugs — B-06 (I32 fallback) + B-08 (@derive skip) + B-11 (let-else divergence) - phase0m_typechecker-refactor-dupes — B-09 (ParsedModuleFile dup) + B-10 (duplicate resolve call) - phase12e_typechecker-gaps-audit — close 6 Appendix C gaps (HIR/THIR/borrow/codegen consumer contracts, when arms, @derive signatures) B-01 already tracked in phase0i_behavior-fqn-keying. B-02 already tracked in phase0h_closure-type-preservation.
1 parent f1190a6 commit e47af49

15 files changed

Lines changed: 397 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"status": "pending",
3+
"createdAt": "2026-04-07T17:23:29.603Z",
4+
"updatedAt": "2026-04-07T17:23:29.603Z"
5+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Proposal: Preserve Type Arguments in Enum Constructors and Maybe Combinators
2+
3+
## Why
4+
5+
Two type-checker soundness bugs documented in `docs/specs/typechecker-invariants.md` Appendix B (B-03 and B-04) cause generic type information to be silently discarded during body checking. This forces the HIR builder to re-infer type arguments downstream, causing cascading bugs across codegen (e.g. RC7 `Maybe__T` vs `Maybe__I32` mangling, iterator `.map()` chains producing wrong types).
6+
7+
**B-03 — Enum constructors lose type arguments** (`checker/expr_call.cpp:391-393`): `Just(42)` is type-checked as `NamedType{"Maybe", "", {}}` with an empty `type_args` vector. The `I32` from the argument is discarded. The HIR builder must re-infer it from the payload every time.
8+
9+
**B-04 — Maybe[T].map(f) returns Maybe[T] instead of Maybe[U]** (`checker/expr_call_method_types.cpp:67-69`): `map`, `and_then`, `or_else`, `filter` on `Maybe[T]` always return `Maybe[T]` regardless of the closure's return type. Chained `.map()` calls silently discard transformation types. HIR builder must infer the actual result type from the closure's signature.
10+
11+
Both bugs are load-bearing (the HIR builder already compensates), but they corrupt the self-hosting contract in `docs/specs/typechecker-invariants.md` Section 6 because a TML-written type checker that fixes these will produce a different TypeEnv than the C++ checker — breaking parity.
12+
13+
## What Changes
14+
15+
1. **`checker/expr_call.cpp`** — when type-checking an enum constructor expression, infer the concrete type arguments from the payload argument types and store them in `NamedType::type_args`. Use `extract_type_params` or a similar mechanism to unify payload types against variant signature.
16+
17+
2. **`checker/expr_call_method_types.cpp`** — for `Maybe[T]::map`, `and_then`, `or_else`, `filter` (and equivalent on `Outcome[T,E]`), read the closure argument's actual return type and substitute it into the method's return type. The return type becomes `Maybe[closure_return_type]`, not `Maybe[T]`.
18+
19+
3. **HIR builder** — remove the compensating re-inference code for enum constructors and Maybe combinators once the type checker produces correct types. Guard with a temporary compatibility flag if needed for incremental rollout.
20+
21+
4. **Self-hosting contract** — update `docs/specs/typechecker-invariants.md` Section 6 items IN-03, IN-05 (or equivalent) to require concrete type-argument preservation. Move B-03 and B-04 from Appendix B (latent bugs) to a "Fixed" subsection with the commit hash.
22+
23+
## Impact
24+
25+
- **Affected specs**: `docs/specs/typechecker-invariants.md` (Section 6 contract, Appendix B), `docs/specs/28-CHECKER.md` (if it claims current behavior)
26+
- **Affected code**: `compiler/src/types/checker/expr_call.cpp`, `compiler/src/types/checker/expr_call_method_types.cpp`, `compiler/src/hir/hir_builder_expr.cpp` (remove compensation), possibly `compiler/src/thir/thir_lower.cpp`
27+
- **Breaking change**: NO — visible behavior does not change; the HIR output should remain identical. Only the intermediate TypeEnv gets more information.
28+
- **User benefit**: Cleaner self-hosting contract. Removes two silent type-information losses. Simpler HIR builder. Indirect improvement to error messages that currently show `Maybe` without the type argument.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Tasks: Preserve Type Arguments in Enum Constructors and Maybe Combinators
2+
3+
**Status**: Planned (0/12)
4+
**Depends on**: None (can start immediately)
5+
**Blocks**: Self-hosting type checker parity (phase12 Era 1)
6+
**Duration**: 2–3 days
7+
**Risk**: Medium — touches body checking for enum constructors and core combinators
8+
**Related bugs**: B-03, B-04 in `docs/specs/typechecker-invariants.md` Appendix B
9+
10+
---
11+
12+
## Investigation
13+
14+
- [ ] I.1 Read `compiler/src/types/checker/expr_call.cpp:391-393` — confirm enum constructor path that drops type args.
15+
- [ ] I.2 Read `compiler/src/types/checker/expr_call_method_types.cpp:67-69` — confirm Maybe combinator return type handling.
16+
- [ ] I.3 Grep HIR builder for compensating code (`hir_builder_expr.cpp`). Identify which sites re-infer enum/maybe type args and would need removal.
17+
18+
## Fix — Enum Constructors (B-03)
19+
20+
- [ ] E.1 In `checker/expr_call.cpp` at the enum constructor branch, match payload argument types against the variant's declared payload types using `extract_type_params` (or equivalent in `expr_call_method_types.cpp`).
21+
- [ ] E.2 Populate `NamedType::type_args` on the returned type with the inferred concrete arguments (in the enum's declared order).
22+
- [ ] E.3 Handle partial inference: if not all type params can be inferred from payload, leave them as `TypeVar` (unification target) or emit T056-style error if the constructor is used in a context that requires concrete types.
23+
- [ ] E.4 Add checker regression test: `compiler/tests/types/enum_constructor_infers_args.test.tml` asserting that `Just(42)` has type `Maybe[I32]`, not `Maybe`.
24+
25+
## Fix — Maybe/Outcome Combinators (B-04)
26+
27+
- [ ] M.1 In `expr_call_method_types.cpp`, for `Maybe::map`, read the closure parameter's type and extract its return type. Return `Maybe[closure_return]`, not `Maybe[T]`.
28+
- [ ] M.2 Same for `Maybe::and_then`, but the closure's return type must itself be `Maybe[U]`; unwrap one level.
29+
- [ ] M.3 Same for `Maybe::or_else`, `Maybe::filter` (filter keeps `T`), `Outcome::map`, `Outcome::map_err`, `Outcome::and_then`, `Outcome::or_else`.
30+
- [ ] M.4 Add checker regression tests under `compiler/tests/types/maybe_combinator_types.test.tml` asserting that `Just(42).map(|x| x.to_string())` has type `Maybe[Text]`.
31+
32+
## HIR Builder Cleanup
33+
34+
- [ ] H.1 Remove the compensating enum-type-arg re-inference in `hir_builder_expr.cpp`. Compile and run affected tests.
35+
- [ ] H.2 Remove the compensating Maybe combinator type re-derivation. Compile and run affected tests.
36+
- [ ] H.3 If any downstream stage breaks, guard the removal behind a compile-time flag and file a follow-up task.
37+
38+
## Verification
39+
40+
- [ ] V.1 Build via `scripts\build.bat`.
41+
- [ ] V.2 Run `mcp__tml__test` on the new regression tests — must pass.
42+
- [ ] V.3 Run full test suite via `mcp__tml__test` with `structured=true`. Confirm no regressions vs the current ~1845/1874 baseline. Target: ≥ baseline.
43+
- [ ] V.4 Spot-check `core/iter`, `core/option`, `std/promise` suites — these exercise Maybe/Outcome combinators heavily.
44+
45+
## Documentation
46+
47+
- [ ] D.1 Update `docs/specs/typechecker-invariants.md` Appendix B: move B-03 and B-04 to a "Fixed" subsection with commit hashes.
48+
- [ ] D.2 Update Section 6 contract items IN-03/IN-05 (or equivalent): add "must preserve type arguments on enum constructors" and "Maybe combinators must return the closure's result type".
49+
- [ ] D.3 Commit with conventional message: `fix(types): preserve type args on enum constructors and Maybe combinators (phase0j)`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"status": "pending",
3+
"createdAt": "2026-04-07T17:23:29.657Z",
4+
"updatedAt": "2026-04-07T17:23:29.657Z"
5+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Proposal: Harden types_compatible to Verify Behavior Conformance
2+
3+
## Why
4+
5+
`types_compatible` in `compiler/src/types/checker/helpers.cpp:91-228` accepts ANY `NamedType` as implementing ANY `ImplBehaviorType` without checking whether the type actually has the behavior registered in `behavior_impls_`. This is documented as bug **B-05** in `docs/specs/typechecker-invariants.md` Appendix B and cross-cutting invariant **CC-16** in Section 5.
6+
7+
The bug is load-bearing: real conformance verification happens during Phase 3 (impl processing) and the body checker relies on the permissive `types_compatible` to avoid ordering cycles. Adding verification naively will reject programs that currently compile because Phase 3 hasn't finished when body checking asks the question.
8+
9+
A correct fix must coordinate type checker phases or defer the check to a point where `behavior_impls_` is fully populated. This is non-trivial and high-risk — the last time someone hardened conformance checks, it took two weeks to unwind the regressions.
10+
11+
The fix is mandatory for self-hosting because a TML-written type checker that verifies conformance will reject real bugs that currently slip through, so the C++ checker and TML checker will diverge on the same input.
12+
13+
## What Changes
14+
15+
1. **Deferred verification pass**: add a new pass after body checking that walks all `ImplBehaviorType` bounds encountered during checking and verifies each type actually implements the claimed behavior via `behavior_impls_` or the superbehavior chain. Emit T-series errors for violations.
16+
17+
2. **Alternative — on-demand strict check at call boundaries**: keep `types_compatible` permissive for local type matching, but at every function call site where an argument is declared as `impl Behavior`, run the strict check against the concrete argument type. Lower risk, less complete.
18+
19+
3. **Regression corpus**: before changing anything, run the full test suite with debug instrumentation that logs every time `types_compatible` returns `true` due to the permissive bypass. Save the log. After fixing, ensure every log entry corresponds to a genuinely correct program or is now an intentional error.
20+
21+
4. **Migration strategy**: land the fix behind a feature flag (`--strict-behavior-check`) for a sprint, fix each flagged test, then flip the default.
22+
23+
## Impact
24+
25+
- **Affected specs**: `docs/specs/typechecker-invariants.md` (CC-16, B-05, Section 6 contract), `docs/specs/28-CHECKER.md`
26+
- **Affected code**: `compiler/src/types/checker/helpers.cpp`, `compiler/src/types/checker/core.cpp` (new pass), possibly `compiler/src/types/env_lookups.cpp`
27+
- **Breaking change**: YES — programs that rely on the permissive check will get new compile errors. Needs the regression-corpus strategy.
28+
- **User benefit**: real type safety for behavior bounds; prevents downstream GEP_UNSIZED errors that RC6/phase0i chased after the fact.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Tasks: Harden types_compatible to Verify Behavior Conformance
2+
3+
**Status**: Planned (0/14)
4+
**Depends on**: None (but recommend landing phase0j first to stabilize type args)
5+
**Blocks**: Self-hosting type checker parity (phase12 Era 1)
6+
**Duration**: 1–2 weeks (high risk, needs migration corpus)
7+
**Risk**: HIGH — breaking change; must coordinate with regression corpus
8+
**Related bug**: B-05, CC-16 in `docs/specs/typechecker-invariants.md`
9+
10+
---
11+
12+
## Investigation
13+
14+
- [ ] I.1 Read `compiler/src/types/checker/helpers.cpp:91-228` — understand every branch that returns `true` for `ImplBehaviorType`.
15+
- [ ] I.2 Identify every call site of `types_compatible` in the type checker. Categorize: (a) local type matching inside bodies, (b) function argument matching, (c) return type coercion, (d) pattern binding.
16+
- [ ] I.3 Read `compiler/src/types/env_lookups.cpp::type_implements` — the strict version. Confirm it checks `behavior_impls_` + superbehavior chain.
17+
18+
## Instrumentation Phase
19+
20+
- [ ] N.1 Add debug logging to `types_compatible` that fires when it returns `true` via the permissive `ImplBehaviorType` branch. Log: (calling function, concrete type, claimed behavior).
21+
- [ ] N.2 Run full test suite with instrumentation enabled. Save log to `.sandbox/types_compatible_bypass.log`.
22+
- [ ] N.3 Categorize log entries: genuine bypass needed (ordering), should-fail-but-slips-through, already-covered-by-another-check.
23+
24+
## Fix Implementation
25+
26+
- [ ] F.1 Add `--strict-behavior-check` flag (default off) to the compiler CLI.
27+
- [ ] F.2 When flag is on, replace the `ImplBehaviorType` permissive branch in `types_compatible` with a call to `type_implements`.
28+
- [ ] F.3 Add a deferred verification pass in `check_module` that runs after Phase 4 (body checking). Walk all `ImplBehaviorType` bounds recorded during body checking and verify each concrete type has the claimed impl.
29+
- [ ] F.4 Record `ImplBehaviorType` verification points during body checking in a side table so the deferred pass knows what to verify.
30+
31+
## Test Corpus Regression
32+
33+
- [ ] R.1 Run full test suite with `--strict-behavior-check` enabled. Expect failures.
34+
- [ ] R.2 For each failure: classify as (a) real bug the test should expose, (b) genuine program the checker now wrongly rejects, (c) missing impl that should be added.
35+
- [ ] R.3 Fix category (c) by adding the missing impls. Fix category (b) by refining the strict check (likely narrow the error case). File individual bugs for category (a).
36+
- [ ] R.4 Re-run with flag enabled until all failures are resolved.
37+
38+
## Flip Default
39+
40+
- [ ] D.1 Change the flag default from off to on.
41+
- [ ] D.2 Remove the permissive branch entirely.
42+
- [ ] D.3 Remove the flag.
43+
44+
## Verification
45+
46+
- [ ] V.1 Build via `scripts\build.bat`.
47+
- [ ] V.2 Full test suite via `mcp__tml__test` `structured=true`. Confirm no regressions.
48+
- [ ] V.3 Manually construct a test that should fail strict conformance and verify it errors.
49+
50+
## Documentation
51+
52+
- [ ] DC.1 Update `docs/specs/typechecker-invariants.md`: remove B-05 and CC-16 from Appendix B / Section 5 "surprising findings", update Section 6 contract to require strict behavior verification.
53+
- [ ] DC.2 Commit with conventional message: `fix(types): verify behavior conformance in types_compatible (phase0k)`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"status": "pending",
3+
"createdAt": "2026-04-07T17:23:29.710Z",
4+
"updatedAt": "2026-04-07T17:23:29.710Z"
5+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Proposal: Fix Small Type Checker Bugs (B-06, B-08, B-11)
2+
3+
## Why
4+
5+
Three small but real bugs were surfaced by the `phase12c` audit and documented in `docs/specs/typechecker-invariants.md` Appendix B. Individually they are each a 1-day fix; bundling them avoids task proliferation.
6+
7+
**B-06 — I32 silent fallback for unresolved types** (`env_module_load_decls.cpp:344-349`): `resolve_simple_type` returns `I32` for any type name it cannot resolve, rather than erroring. The debug log message that should fire is unreachable dead code. Any typo or forward reference in a method signature silently becomes `I32`, corrupting the function's signature in `Module::functions`.
8+
9+
**B-08 — @derive silently skips all generic types** (`decl_struct.cpp:173`): `@derive(Display, Hash, Eq)` on a generic struct registers nothing and emits no warning. Developers writing `@derive` on generics get no feedback that their derive is a no-op. At minimum this should emit a compiler warning (W-series code) with a clear message.
10+
11+
**B-11 — let-else else block not verified to diverge** (`checker/stmt.cpp:137-177`): `let Just(x) = e else { println("ok") }` is accepted by the type checker with no error. The language contract requires the `else` block to have type `Never` (must diverge via `return`, `continue`, `break`, `panic`, or `exit`). HIR lowering is expected to enforce this but the checker should catch it first.
12+
13+
## What Changes
14+
15+
1. **B-06 fix**: Replace the `return make_primitive(PrimitiveKind::I32)` fallback in `resolve_simple_type` with a hard error or a `TypeVar` sentinel that makes the downstream failure visible. Add a T-series error code for unresolved types in method signature positions.
16+
17+
2. **B-08 fix**: In `decl_struct.cpp:173`, when `@derive` is encountered on a generic type, emit a new W-series warning (e.g., `W-DERIVE-ON-GENERIC`) explaining that derives for generic types are not supported in the current compiler and the developer should write explicit impls. Keep the current silent-skip behavior so nothing breaks, just surface it.
18+
19+
3. **B-11 fix**: In `check_let_else` (around `stmt.cpp:137-177`), after checking the `else` block's type, verify it is `Never` or call `check_diverges`. Emit a T-series error if not.
20+
21+
4. Add regression tests for all three.
22+
23+
## Impact
24+
25+
- **Affected specs**: `docs/specs/typechecker-invariants.md` (remove B-06, B-08, B-11 from Appendix B)
26+
- **Affected code**: `compiler/src/types/env_module_load_decls.cpp`, `compiler/src/types/checker/decl_struct.cpp`, `compiler/src/types/checker/stmt.cpp`
27+
- **Breaking change**:
28+
- B-06: YES — programs with previously silent I32 fallbacks will now error. Should be rare but needs audit.
29+
- B-08: NO — only adds warnings.
30+
- B-11: YES — programs with non-diverging let-else else blocks will now error. Should be rare.
31+
- **User benefit**: clearer error messages, fewer silent-corruption classes, better developer feedback on @derive misuse.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Tasks: Fix Small Type Checker Bugs (B-06, B-08, B-11)
2+
3+
**Status**: Planned (0/15)
4+
**Depends on**: None
5+
**Blocks**: Self-hosting type checker parity (minor contributions)
6+
**Duration**: 2–3 days
7+
**Risk**: Low — three small, isolated fixes
8+
**Related bugs**: B-06, B-08, B-11 in `docs/specs/typechecker-invariants.md` Appendix B
9+
10+
---
11+
12+
## B-06 — I32 Silent Fallback
13+
14+
- [ ] B6.1 Read `compiler/src/types/env_module_load_decls.cpp:344-349` to confirm the fallback path.
15+
- [ ] B6.2 Audit all callers of `resolve_simple_type` — what do they do with the return value? Is there a call site that depends on the I32 fallback?
16+
- [ ] B6.3 Replace the silent fallback with a real error. Add new error code `T081-UNRESOLVED-TYPE-IN-METHOD-SIG` (or reuse T038 if appropriate).
17+
- [ ] B6.4 Enable the previously-unreachable debug log message as an actual diagnostic.
18+
- [ ] B6.5 Add regression test asserting that a method with an unresolved type in its signature emits T081.
19+
20+
## B-08 — @derive Silent Skip on Generics
21+
22+
- [ ] B8.1 Read `compiler/src/types/checker/decl_struct.cpp:173` to confirm the skip path.
23+
- [ ] B8.2 Add a new warning code `W-DERIVE-ON-GENERIC` with message: "@derive(...) on generic type '%name' is not supported — derives only apply to non-generic types. Write explicit impl blocks instead."
24+
- [ ] B8.3 Emit the warning at the skip site when `struct.generics` is non-empty and `@derive` is present.
25+
- [ ] B8.4 Add regression test asserting the warning fires for `@derive(Display) struct Foo[T] { x: T }`.
26+
27+
## B-11 — let-else Else Block Must Diverge
28+
29+
- [ ] B11.1 Read `compiler/src/types/checker/stmt.cpp:137-177` and `check_diverges` helper if it exists.
30+
- [ ] B11.2 After type-checking the `else` block in `check_let_else`, call `check_diverges` or verify the block's type is `Never`.
31+
- [ ] B11.3 Emit `T-LET-ELSE-NOT-DIVERGING` error if the else block can fall through.
32+
- [ ] B11.4 Add regression test: `let Just(x) = e else { println("ok") }` must error.
33+
- [ ] B11.5 Add positive regression test: `let Just(x) = e else { return }` must compile.
34+
35+
## Verification
36+
37+
- [ ] V.1 Build via `scripts\build.bat`.
38+
- [ ] V.2 Run `mcp__tml__test` on each new regression test.
39+
- [ ] V.3 Full suite via `mcp__tml__test` `structured=true`. B-06 and B-11 may expose real bugs in the existing test corpus — fix those in the library code, not by reverting the type checker fix. B-08 should have zero breakage.
40+
41+
## Documentation
42+
43+
- [ ] D.1 Update `docs/specs/typechecker-invariants.md` Appendix B: remove B-06, B-08, B-11 after their commits land. Add new error codes to `docs/specs/12-ERRORS.md`.
44+
- [ ] D.2 Commit with conventional message: `fix(types): resolve 3 small typechecker bugs B-06/B-08/B-11 (phase0l)`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"status": "pending",
3+
"createdAt": "2026-04-07T17:23:29.765Z",
4+
"updatedAt": "2026-04-07T17:23:29.765Z"
5+
}

0 commit comments

Comments
 (0)