Design: how do generic container instantiations resolve to a concrete Ty #69

Closed
opened 2026-09-17 20:11:41 +00:00 by jacksonopp · 1 comment
Owner

Surfaced while implementing #56/#57 (array/map literal checking) — both issues cite #6 as the blocking dependency for their result type ("no TyKind variant exists yet for a generic container instantiation... needs that settled first"), but #6 is closed and never actually covered this. #6 was narrower: treating T: type as an ordinary parameter and List(T)-shaped calls as ordinary function calls type-checking against the abstract T (landed in #53). It didn't resolve how a generic container instantiation resolves to a concrete Ty when used in type-annotation position — resolve_type_expr's TypeExpr::Generic { .. } => None arm (crates/twill-check/src/ty.rs) still leaves this completely unhandled today.

This issue is that actual open question. Two real directions, not just "add an enum variant":

  1. Special-cased container TyKinds — add dedicated variants (e.g. TyKind::List(Box), TyKind::Array(Box, usize), TyKind::Map(Box, Box)) that resolve_type_expr/check_call produce directly whenever the callee is List/Array/Map. Smaller, immediately unblocks #56/#57, but bakes three compiler-known containers into the type system as special cases.
  2. General comptime type-function evaluation — since List/Array/Map are themselves just ordinary functions returning type (per syntax-notes.md's naming-convention section: fn List(T: type) -> type { struct { items: *T, len: Int } }), the principled fix is comptime-evaluating a type-returning function call and deriving a real Ty from its body, structurally. This covers List/Array/Map for free as a side effect, and also generalizes to any user-defined type-returning function -- but needs an actual mini-evaluator, a meaningfully bigger feature.

Currently #56/#57 land with a placeholder result type (e.g. the array literal's own element type, or None for maps) rather than a real List(T)/Array(T, N)/Map(K, V) Ty, specifically because of this open question. #56/#57's own descriptions should get their "Dependency" note repointed here instead of #6.

Surfaced while implementing #56/#57 (array/map literal checking) — both issues cite #6 as the blocking dependency for their result type ("no TyKind variant exists yet for a generic container instantiation... needs that settled first"), but #6 is closed and never actually covered this. #6 was narrower: treating T: type as an ordinary parameter and List(T)-shaped calls as ordinary function calls type-checking against the abstract T (landed in #53). It didn't resolve how a generic container instantiation resolves to a concrete Ty when used in type-annotation position — resolve_type_expr's TypeExpr::Generic { .. } => None arm (crates/twill-check/src/ty.rs) still leaves this completely unhandled today. This issue is that actual open question. Two real directions, not just "add an enum variant": 1. **Special-cased container TyKinds** — add dedicated variants (e.g. TyKind::List(Box<Ty>), TyKind::Array(Box<Ty>, usize), TyKind::Map(Box<Ty>, Box<Ty>)) that resolve_type_expr/check_call produce directly whenever the callee is List/Array/Map. Smaller, immediately unblocks #56/#57, but bakes three compiler-known containers into the type system as special cases. 2. **General comptime type-function evaluation** — since List/Array/Map are themselves just ordinary functions returning type (per syntax-notes.md's naming-convention section: fn List(T: type) -> type { struct { items: *T, len: Int } }), the principled fix is comptime-evaluating a type-returning function call and deriving a real Ty from its body, structurally. This covers List/Array/Map for free as a side effect, and also generalizes to any user-defined type-returning function -- but needs an actual mini-evaluator, a meaningfully bigger feature. Currently #56/#57 land with a placeholder result type (e.g. the array literal's own element type, or None for maps) rather than a real List(T)/Array(T, N)/Map(K, V) Ty, specifically because of this open question. #56/#57's own descriptions should get their "Dependency" note repointed here instead of #6.
jacksonopp added this to the twill project 2026-09-17 21:11:59 +00:00
Author
Owner

Landed with a narrower scope than originally written up:

  • List(T)-shaped generic-call resolution is done end to end: a new fn_decl salsa query looks up a callee's declaration by name within a given SourceFile, and evaluate_generic_call comptime-evaluates the body under substituted T: type bindings to produce a real Ty (reusing check_struct/check_expr rather than adding a dedicated container TyKind — the 'general comptime evaluation' direction from the original writeup, not the special-cased-variants one).
  • Pointer-type resolution (TypeExpr::Pointer -> TyKind::Pointer) landed as a prerequisite, since List's own body (items: *T) needed it.
  • Wired into check_array_literal/check_map_literal's bare/typed cases too, so [1, 2, 3], []Int[1, 2, 3], and both Map literal forms now resolve to real List(T)/Map(K, V) shapes instead of the old placeholder result types.
  • Array's Comptime(Int) size and the general Comptime(_)-value-param machinery got split to #71 -- corrected from the original writeup along the way: Map(K, V)'s params are both plain type-kinded, so it needed no Comptime support and already works via the same mechanism as List once a Map decl exists somewhere to find. Only Array is actually blocked on #71.
  • How List/Array/Map become available without a user declaring them inline in every file (a builtin/prelude, not a general importable std library) got split to #72 -- every test here declares List/Map inline in the checked source, since fn_decl only searches the given file.
Landed with a narrower scope than originally written up: - `List(T)`-shaped generic-call resolution is done end to end: a new `fn_decl` salsa query looks up a callee's declaration by name within a given `SourceFile`, and `evaluate_generic_call` comptime-evaluates the body under substituted `T: type` bindings to produce a real `Ty` (reusing `check_struct`/`check_expr` rather than adding a dedicated container `TyKind` — the 'general comptime evaluation' direction from the original writeup, not the special-cased-variants one). - Pointer-type resolution (`TypeExpr::Pointer` -> `TyKind::Pointer`) landed as a prerequisite, since `List`'s own body (`items: *T`) needed it. - Wired into `check_array_literal`/`check_map_literal`'s bare/typed cases too, so `[1, 2, 3]`, `[]Int[1, 2, 3]`, and both `Map` literal forms now resolve to real `List(T)`/`Map(K, V)` shapes instead of the old placeholder result types. - `Array`'s `Comptime(Int)` size and the general `Comptime(_)`-value-param machinery got split to #71 -- corrected from the original writeup along the way: `Map(K, V)`'s params are both plain `type`-kinded, so it needed no `Comptime` support and already works via the same mechanism as `List` once a `Map` decl exists somewhere to find. Only `Array` is actually blocked on #71. - How `List`/`Array`/`Map` become available without a user declaring them inline in every file (a builtin/prelude, not a general importable std library) got split to #72 -- every test here declares `List`/`Map` inline in the checked source, since `fn_decl` only searches the given file.
jacksonopp removed this from the twill project 2026-10-04 02:28:22 +00:00
Sign in to join this conversation.
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#69
No description provided.