Feat(type-system): land TY1 — Ty IR, union normalization, assignability (#41) #11

Manually merged
dferreira merged 2 commits from feat/011-feat-type-system-land-ty1-ty-ir-union-normaliz into master 2026-01-14 16:44:03 +01:00
Owner

Feat(type-system): land TY1 — Ty IR, union normalization, assignability (#41)

Along the way:

  • feat(type-system): land TY1 — Ty IR, union normalization, assignability (#41)
  • feat(resolve): land N6 — body lowering to owned HIR (#42)

Touches 13 files: docs/impl/resolution/N6-hir-lowering.md, docs/impl/resolution/README.md, docs/impl/slices.md, docs/impl/type-system/README.md, docs/impl/type-system/TY1-type-ir-and-assignability.md, src/rust/crates/ck_hir/src/expr.rs and 7 more.

Feat(type-system): land TY1 — Ty IR, union normalization, assignability (#41) Along the way: - feat(type-system): land TY1 — Ty IR, union normalization, assignability (#41) - feat(resolve): land N6 — body lowering to owned HIR (#42) Touches 13 files: `docs/impl/resolution/N6-hir-lowering.md`, `docs/impl/resolution/README.md`, `docs/impl/slices.md`, `docs/impl/type-system/README.md`, `docs/impl/type-system/TY1-type-ir-and-assignability.md`, `src/rust/crates/ck_hir/src/expr.rs` and 7 more.
dferreira approved these changes 2026-01-14 14:38:26 +01:00
dferreira left a comment

Yes. The commit messages carried me through all 13 files, which is not always true.

Yes. The commit messages carried me through all 13 files, which is not always true.
@ -5,0 +292,4 @@
fn generics_are_invariant() {
// List<Cat> is unrelated to List<Animal> even though Cat ∈ Animal.
let animal = Ty::union([cat(), dog()]);
assert!(!assignable(&list(cat()), &list(animal.clone())));
Owner

assignable is cloned on what looks like a hot path. Is the ownership forced here, or can this borrow?

`assignable` is cloned on what looks like a hot path. Is the ownership forced here, or can this borrow?
Author
Owner

It can borrow. The clone of assignable was left over from before the lifetime was threaded through src/rust/crates/ck_types/src/lib.rs, and it is gone.

It can borrow. The clone of `assignable` was left over from before the lifetime was threaded through `src/rust/crates/ck_types/src/lib.rs`, and it is gone.
Open the type-checker stage in ck_types. TY1 is pure judgment
infrastructure, consumed by every later slice:

- the `Ty` enum: primitives (Int default; widths provisional), the
  String/EmptyString split, nominal records (TypeId + invariant args),
  normalized unions, type variables, Fn types, Nothing, Unit;
- `Ty::union`: the normalizing constructor (flatten, absorb Nothing,
  dedupe, sort, collapse singleton/empty) so two spellings of one union
  compare equal in one step;
- `assignable(from, to)`: the whole of CkLang's "subtyping" — four cases
  (identity / tag-membership-into-union / Nothing / the String-EmptyString
  split), no lattice, no variance, no width or numeric coercion.

TypeVarId is checker-allocated, so it lives here, not in the resolver's
interner. TY1 emits no diagnostic; the E07xx block opens when TY2's
bidirectional checker fires the first argument-type mismatch.

17 unit tests cover the four cases, invariance, the payload-free-variant
rule, and the five normalization laws. Gates: build 0, clippy 0
(deny all+pedantic), workspace 652 tests pass.
Realize the expression-level HIR that N1 named but deferred. "The CST is
what we wrote; the owned HIR is how it will run": the CST + the resolver's
span-keyed side-tables stay the editor's source-faithful view, while the
owned tree is the type checker's and runtime's lowered view. (Reasoning:
what would Erlang do — the BEAM lowers into owned IRs; even Dialyzer types
the lowered form.)

- ck_hir: owned expression HIR in a new `expr` module — `Body` (a flat
  ExprId-indexed arena), `Expr`/`ExprKind`, `Stmt`/`StmtKind`, `Lit`,
  `BodyBuilder`. Every node carries its source Span (the PROJECTION
  INVARIANT — synthesized nodes will inherit the surface span they lower
  from, so errors always blame real source). `Resolution` moved here (its
  variants are all ck_hir handles), re-exported from ck_resolve.
- ck_resolve::lower: `lower_body` projects a resolved body's CST into the
  owned tree — literals / refs / `let` / blocks / calls. Resolution is
  reused, not recomputed (looked up by head-name span). Forms outside this
  frame lower to a spanned `Unsupported`, keeping the tree total.
- ck_syntax: literal/call/paren accessors + `from_node` constructors;
  re-export `SyntaxKind` so ck_resolve matches tree shapes without reaching
  into ck_lex.
- Per-expression results (types, TY2) will live in a side-table keyed by
  ExprId, leaving this tree immutable after lowering.

Docs reconciled: ck_hir's stale "expression HIR lands in N3 as an owned
tree" comment corrected (N3 built side-tables; lowering builds the tree);
N6 spec added; resolution README + slices index updated. ck_hir/lib.rs
split to stay under the R3 LoC cap.

Gates: build 0, clippy 0 (deny all+pedantic), workspace 658 tests pass.
dferreira manually merged commit ca042be1be into master 2026-01-14 16:44:03 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!11
No description provided.