Generics (Erasure-Based) – Design & TODO¶
Status: shipped. Every checkpoint below has landed: type parameters on classes and
methods, [T extends Bound] upper bounds, bracket type applications, erasure-based
codegen with bridge methods, and inference for common generic calls. See
docs/reference/specification.md and
docs/guide/classes-and-objects.md for the
syntax and semantics as they exist today, and CLAUDE.md ("Known Limitations") for what
remains deliberately out of scope (erasure, invariance, no reified type info). This page
is kept as a historical record of the original incremental plan.
Goal: Add basic generics with Scala-style erasure (upper bounds, generic classes/methods, bridge emission for overriding). Deliver in small PR-sized steps.
Scope¶
- Type params on classes/methods with optional upper bounds using
extends(e.g.,T extends Foo). No variance for now. Syntax uses brackets[]for both definitions and applications:class Box[T extends Foo],def id[T extends Foo](x: T): T. - Type applications on types/expressions:
Box[Int],foo[String](x)(brackets only). - Erasure: map type params to
Objector bound’s erasure in JVM signatures. Emit bridges when overriding causes erased signature collisions. - Primitive type arguments: allowed (e.g.,
ArrayList[Int]); boxed/unboxed at erased JVM boundaries.
Out of scope (for later): variance, wildcards, lower bounds, reified generics, generic fields with runtime type info, constraints beyond single upper bound.
Incremental Plan¶
1) Grammar & AST¶
- Extend grammar (JavaCC) to parse:
- Type parameter lists on classes/methods:
[T]/[T extends Bound](brackets unified with applications). - Type applications on types and expressions:
Foo[Bar]. - AST changes (parser-level
AST): - Add
TypeParameter(name, upperBound: Option[TypeNode]). - Add
TypeApplication(target: TypeNode, args: List[TypeNode]). - Thread type params into
ClassDeclaration,MethodDeclaration,FunctionDeclaration,ClosureExpressionif applicable. - Thread type args into
NewObject,StaticMethodCall,MethodCall,StaticMemberSelectionwhen present. - Regenerate parser sources (JavaCC) after grammar updates.
Checkpoint: Parser builds; AST nodes produced for simple examples like class Box[T] { def get(x: T): T = x } and new Box[Int]().
2) Typed AST & Typing¶
- Typed model additions:
TypedAST.TypeParameter(name, upperBound: IRT.Type)andTypeApplicationnodes.- Include type params on
ClassDefinition,MethodDefinition. - Environment/bindings:
- Track type param scopes (class-level + method-level) with bounds.
- Validate type argument arity at use sites.
- Bound checking: substitute type args into bounds, ensure conformity (erased to bound when unknown).
- Erasure semantics in typing:
- Compute erased type for type params:
erasure(T) = erasure(bound)elseObject. - Erase generic method/class types for codegen signatures.
- Type inference: supported for common generic method calls; explicit type arguments are still supported.
Checkpoint: Typing accepts generic class/method definitions and applications, rejects arity/bound errors, produces TypedAST with erased info attached.
3) Code Generation (ASM)¶
- Signature computation: use erased types for descriptors.
- Bridge methods:
- When a subclass overrides a generic parent method where the erased signature would differ, emit a bridge that delegates to the typed implementation.
- Detect collisions during method table assembly (compare erased names+descs).
- Class/method naming: no mangling beyond erasure; rely on bridges for dispatch correctness.
Checkpoint: Bytecode for generic hierarchies loads and runs; overridden generic methods dispatch correctly via bridges.
Testing Plan¶
- Parser round-trips: generic class/method definitions and instantiations.
- Typing: arity/bound errors; correct acceptance of
Box[Int], rejection ofBox[Int, String]. - Runtime: execute small programs with generic collections/identity methods; override scenarios that require bridges.
- Non-generic regressions: full existing test suite.
Notes¶
- Keep resource strings (errors) consistent; add new keys for arity/bound errors.
- Maintain backward compatibility: existing non-generic code should compile unchanged.
- Consider feature flags if rollout needs to be staged; otherwise, ship as a single feature branch with checkpoints.