Skip to content

ジェネリクス(消去ベース)— 設計と 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]) を追加。
  • 該当する場合、ClassDeclarationMethodDeclarationFunctionDeclarationClosureExpression に型パラメータを組み込む。
  • 存在する場合、NewObjectStaticMethodCallMethodCallStaticMemberSelection に型引数を組み込む。
  • 文法更新後にパーサーソース(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 ノード。
  • ClassDefinitionMethodDefinition に型パラメータを含める。
  • 環境/束縛:
  • 型パラメータのスコープ(クラスレベル + メソッドレベル)と境界を追跡する。
  • 使用箇所で型引数のarityを検証する。
  • 境界チェック: 型引数を境界に代入し、適合性を確認する(不明な場合は境界に消去)。
  • Typing における消去の意味論:
  • 型パラメータの消去型を計算: erasure(T) = erasure(bound)、それ以外は Object
  • コード生成シグネチャのためにジェネリックメソッド/クラス型を消去。
  • 型推論: 一般的なジェネリックメソッド呼び出しでは推論をサポートする。明示的な型引数も引き続きサポートされる。

チェックポイント: Typing がジェネリッククラス/メソッドの定義と適用を受け入れ、arity/境界エラーを拒否し、消去情報が付加された TypedAST を生成する。

3) コード生成(ASM)

  • シグネチャ計算: 記述子には消去型を使用する。
  • ブリッジメソッド:
  • サブクラスがジェネリック親メソッドをオーバーライドし、消去シグネチャが異なる場合、型付き実装に委譲するブリッジを生成する。
  • メソッドテーブルアセンブリ時に衝突を検出する(消去後の名前+記述子を比較)。
  • クラス/メソッド名: 消去を超えるマングリングは行わない。ブリッジによりディスパッチの正しさを担保する。

チェックポイント: ジェネリック階層のバイトコードがロード・実行される。ブリッジが必要なオーバーライドシナリオが正しくディスパッチされる。

テスト計画

  • パーサーのラウンドトリップ: ジェネリッククラス/メソッド定義とインスタンス化。
  • Typing: arity/境界エラー。Box[Int] の正しい受け入れ、Box[Int, String] の拒否。
  • 実行時: ジェネリックコレクション/恒等メソッドを使った小さなプログラムの実行。ブリッジを必要とするオーバーライドシナリオ。
  • 非ジェネリックの回帰テスト: 既存のテストスイート全体。

注意

  • リソース文字列(エラー)は一貫性を保つ。arity/境界エラー用の新しいキーを追加する。
  • 後方互換性を維持: 既存の非ジェネリックコードは変更なしでコンパイルされるべき。
  • ロールアウトを段階的に行う必要がある場合は機能フラグを検討する。それ以外の場合は、チェックポイントを含む単一の機能ブランチとして出荷する。