Generics: check T: type params and List(T)-shaped calls #53

Merged
jacksonopp merged 3 commits from us6/type-params-and-list-t into main 2026-09-10 20:44:16 +00:00
Owner

Closes #6.

Summary

  • T: type params: two new TyKind variants — Type (no payload, resolved from the literal name "type") and TypeParam(Symbol) (nominal, one per type-parameter name, same "carries a name" reasoning as TyKind::Struct/TyKind::Enum). check_fn's per-param loop binds a T: type param's name to TypeParam(T) (not Type itself) so later params/return type (x: T, -> T) can reference it.
  • resolve_type_expr gains a &Scope parameter so bound type params are actually findable, falling back to scope.lookup for any name that isn't a hardcoded primitive.
  • List(T)-shaped calls: ride on check_call (built in #3) for free once a function's own signature resolves correctly — the missing piece was that primitive names (Int, Float, ...) used as ordinary value expressions (e.g. List(Int)'s argument) didn't resolve to anything, since primitive recognition only existed in type-annotation position. check_ident now recognizes them too, always resolving to Ty::type_(db) (the kind) regardless of which specific primitive — nested calls (List(List(Int))) compose with no special-casing.
  • primitive_ty extracted as a shared helper between resolve_type_expr and check_ident — but not reused verbatim, since the two call sites are answering different questions (annotation-position "what type does this name denote" vs. value-position "what is the type of this value"). A first draft conflated the two and returned the wrong Ty from check_ident; caught by the test suite, documented in docs/type-checker-design.md so it doesn't recur.
  • docs/type-checker-design.md gets a new ## Issue #6 section covering all of the above, plus an explicit "Out of scope" list: struct/enum names as generic arguments (List(Vec3) — Vec3's scope binding can't currently answer both "what is Vec3's definition" and "what is the type of the value Vec3," a real design question bigger than this issue), and TypeExpr::Generic (the type-annotation-position form, e.g. let x: List(Int) — structurally separate from the Expr::Call value-position form this issue covers).

Test plan

  • cargo test --workspace
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
Closes #6. ## Summary - **`T: type` params:** two new `TyKind` variants — `Type` (no payload, resolved from the literal name `"type"`) and `TypeParam(Symbol)` (nominal, one per type-parameter name, same "carries a name" reasoning as `TyKind::Struct`/`TyKind::Enum`). `check_fn`'s per-param loop binds a `T: type` param's name to `TypeParam(T)` (not `Type` itself) so later params/return type (`x: T`, `-> T`) can reference it. - `resolve_type_expr` gains a `&Scope` parameter so bound type params are actually findable, falling back to `scope.lookup` for any name that isn't a hardcoded primitive. - **`List(T)`-shaped calls:** ride on `check_call` (built in #3) for free once a function's own signature resolves correctly — the missing piece was that primitive names (`Int`, `Float`, ...) used as ordinary *value* expressions (e.g. `List(Int)`'s argument) didn't resolve to anything, since primitive recognition only existed in type-annotation position. `check_ident` now recognizes them too, always resolving to `Ty::type_(db)` (the kind) regardless of which specific primitive — nested calls (`List(List(Int))`) compose with no special-casing. - `primitive_ty` extracted as a shared helper between `resolve_type_expr` and `check_ident` — but *not* reused verbatim, since the two call sites are answering different questions (annotation-position "what type does this name denote" vs. value-position "what is the type of this value"). A first draft conflated the two and returned the wrong `Ty` from `check_ident`; caught by the test suite, documented in `docs/type-checker-design.md` so it doesn't recur. - `docs/type-checker-design.md` gets a new `## Issue #6` section covering all of the above, plus an explicit "Out of scope" list: struct/enum names as generic arguments (`List(Vec3)` — `Vec3`'s scope binding can't currently answer both "what is Vec3's definition" and "what is the type of the value Vec3," a real design question bigger than this issue), and `TypeExpr::Generic` (the type-annotation-position form, e.g. `let x: List(Int)` — structurally separate from the `Expr::Call` value-position form this issue covers). ## Test plan - [x] `cargo test --workspace` - [x] `cargo fmt --all -- --check` - [x] `cargo clippy --workspace --all-targets --all-features -- -D warnings`
Add nested generic call test for List(List(Int))
Some checks failed
CI / check (pull_request) Has been cancelled
19ad125f8d
Confirms List(T)-shaped calls compose for free through check_call's
existing recursion, with no special-casing needed for nested generic
instantiation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNg6dnsh1WPghP4FuNHThj
jacksonopp force-pushed us6/type-params-and-list-t from 19ad125f8d
Some checks failed
CI / check (pull_request) Has been cancelled
to 93db08bfff
Some checks failed
CI / check (pull_request) Has been cancelled
2026-09-10 20:36:01 +00:00
Compare
jacksonopp force-pushed us6/type-params-and-list-t from 93db08bfff
Some checks failed
CI / check (pull_request) Has been cancelled
to edf59b96fd
All checks were successful
CI / check (pull_request) Successful in 3m42s
2026-09-10 20:37:35 +00:00
Compare
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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
jacksonopp/twill!53
No description provided.