ジェネリクス(消去ベース)— 設計と TODO¶
目標: Scala スタイルの消去(上限境界、ジェネリッククラス/メソッド、オーバーライド時のブリッジ生成)を備えた基本的なジェネリクスを追加する。小さなPRサイズのステップで段階的に導入する。
スコープ¶
- クラス/メソッドの型パラメータ(オプションの
extendsによる上限境界、例:T extends Foo)。今のところ変性はなし。構文は定義と適用の両方で括弧[]を使用:class Box[T extends Foo]、def id[T extends Foo](x: T): T。 - 型/式に対する型適用:
Box[Int]、foo[String](x)(括弧のみ)。 - 消去: 型パラメータを JVM シグネチャでは
Objectまたは境界の消去型にマップする。消去シグネチャの衝突が起きるオーバーライドではブリッジを生成する。 - プリミティブ型引数: 許可される(例:
ArrayList[Int])。消去された JVM 境界でボックス化/非ボックス化される。
今後のスコープ(後回し): 変性、ワイルドカード、下限境界、reified ジェネリクス、実行時型情報を持つジェネリックフィールド、単一上限を超える制約。
段階的計画¶
1) 文法と AST¶
- 文法(JavaCC)を拡張して以下を解析する:
- クラス/メソッドの型パラメータリスト:
[T]/[T extends Bound](適用と統一された括弧)。 - 型と式に対する型適用:
Foo[Bar]。 - AST の変更(パーサーレベルの
AST): TypeParameter(name, upperBound: Option[TypeNode])を追加。TypeApplication(target: TypeNode, args: List[TypeNode])を追加。- 該当する場合、
ClassDeclaration、MethodDeclaration、FunctionDeclaration、ClosureExpressionに型パラメータを組み込む。 - 存在する場合、
NewObject、StaticMethodCall、MethodCall、StaticMemberSelectionに型引数を組み込む。 - 文法更新後にパーサーソース(JavaCC)を再生成する。
チェックポイント: パーサーがビルドされる。class Box[T] { def get(x: T): T = x } や new Box[Int]() のような単純な例で AST ノードが生成される。
2) Typed AST と Typing¶
- Typed model の追加:
TypedAST.TypeParameter(name, upperBound: IRT.Type)とTypeApplicationノード。ClassDefinition、MethodDefinitionに型パラメータを含める。- 環境/束縛:
- 型パラメータのスコープ(クラスレベル + メソッドレベル)と境界を追跡する。
- 使用箇所で型引数のarityを検証する。
- 境界チェック: 型引数を境界に代入し、適合性を確認する(不明な場合は境界に消去)。
- Typing における消去の意味論:
- 型パラメータの消去型を計算:
erasure(T) = erasure(bound)、それ以外はObject。 - コード生成シグネチャのためにジェネリックメソッド/クラス型を消去。
- 型推論: 一般的なジェネリックメソッド呼び出しでは推論をサポートする。明示的な型引数も引き続きサポートされる。
チェックポイント: Typing がジェネリッククラス/メソッドの定義と適用を受け入れ、arity/境界エラーを拒否し、消去情報が付加された TypedAST を生成する。
3) コード生成(ASM)¶
- シグネチャ計算: 記述子には消去型を使用する。
- ブリッジメソッド:
- サブクラスがジェネリック親メソッドをオーバーライドし、消去シグネチャが異なる場合、型付き実装に委譲するブリッジを生成する。
- メソッドテーブルアセンブリ時に衝突を検出する(消去後の名前+記述子を比較)。
- クラス/メソッド名: 消去を超えるマングリングは行わない。ブリッジによりディスパッチの正しさを担保する。
チェックポイント: ジェネリック階層のバイトコードがロード・実行される。ブリッジが必要なオーバーライドシナリオが正しくディスパッチされる。
テスト計画¶
- パーサーのラウンドトリップ: ジェネリッククラス/メソッド定義とインスタンス化。
- Typing: arity/境界エラー。
Box[Int]の正しい受け入れ、Box[Int, String]の拒否。 - 実行時: ジェネリックコレクション/恒等メソッドを使った小さなプログラムの実行。ブリッジを必要とするオーバーライドシナリオ。
- 非ジェネリックの回帰テスト: 既存のテストスイート全体。
注意¶
- リソース文字列(エラー)は一貫性を保つ。arity/境界エラー用の新しいキーを追加する。
- 後方互換性を維持: 既存の非ジェネリックコードは変更なしでコンパイルされるべき。
- ロールアウトを段階的に行う必要がある場合は機能フラグを検討する。それ以外の場合は、チェックポイントを含む単一の機能ブランチとして出荷する。