Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1903] ファイルの概要

このコミットは、Go言語の抽象構文木(AST)における複合リテラル(Composite Literal)内のキーと値のペアの表現方法を変更するものです。具体的には、これまで二項演算子(BinaryExpr)として扱われていたコロン(:)区切りのペアを、専用のKeyValueExprノードとして明示的に表現するように修正しています。これにより、ASTの構造がより意味論的に正確になり、コンパイラやツールが複合リテラルを処理する際の明確性が向上します。また、コロンが演算子として扱われなくなるため、演算子の優先順位レベルからも削除されています。

コミット

- have explicit KeyValueExpr node instead of BinaryExpr ':' (as discussed)
- remove ':' token from operator precedence levels

R=rsc
DELTA=25  (13 added, 8 deleted, 4 changed)
OCL=26850
CL=26854

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7cba8e6f72279f8dc9e35ea0e10c7deb3706a744

元コミット内容

- have explicit KeyValueExpr node instead of BinaryExpr ':' (as discussed)
- remove ':' token from operator precedence levels

R=rsc
DELTA=25  (13 added, 8 deleted, 4 changed)
OCL=26850
CL=26854

変更の背景

Go言語の初期開発段階では、言語仕様やコンパイラの実装が活発に進化していました。このコミットが行われた2009年3月時点では、Go言語の複合リテラル(例: map[string]int{"a": 1, "b": 2}struct{X int}{X: 1})におけるキーと値のペア(key: value)の構文解析が、一般的な二項演算子(BinaryExpr)として処理されていました。具体的には、コロン(:)が二項演算子の一つとして扱われ、その左右の式がそれぞれキーと値に対応していました。

しかし、コロンは算術演算子や論理演算子とは異なり、特定の文脈(複合リテラル内)でのみ特別な意味を持つ区切り文字です。これを汎用的な二項演算子として扱うことは、ASTのセマンティクスを曖昧にし、コンパイラのコード生成や静的解析ツールが複合リテラルの構造を正確に理解する上で不都合が生じる可能性がありました。

この問題は、Go言語の設計者間での議論(コミットメッセージの "(as discussed)" が示唆)を経て、コロンを二項演算子として扱うのではなく、複合リテラル内のキーと値のペアを表現するための専用のASTノードを導入するという結論に至りました。これにより、ASTが言語の意図をより正確に反映し、コンパイラのロジックを簡素化し、将来的な拡張性も確保できるようになります。

前提知識の解説

抽象構文木 (Abstract Syntax Tree, AST)

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタは、ソースコードを直接処理するのではなく、まず字句解析(トークン化)と構文解析(パース)を行い、ASTを構築します。ASTは、コードの意味論的な構造を保持しつつ、具体的な構文の詳細(括弧やセミコロンなど)を抽象化するため、後続の処理(型チェック、最適化、コード生成など)が容易になります。

Go言語のコンパイラも、ソースコードをASTに変換して処理を進めます。ASTの各ノードは、プログラムの要素(変数宣言、関数呼び出し、式など)を表します。

Go言語の複合リテラル (Composite Literals)

Go言語の複合リテラルは、構造体(struct)、配列(array)、スライス(slice)、マップ(map)などの複合型を初期化するための構文です。

  • 構造体リテラル: MyStruct{Field1: value1, Field2: value2}
  • マップリテラル: map[KeyType]ValueType{key1: value1, key2: value2}
  • 配列/スライスリテラル: []int{1, 2, 3} (キーと値のペアは通常使用しないが、インデックスを指定する場合は []int{0: 10, 2: 30} のように書ける)

これらのリテラルにおいて、key: value の形式で要素を指定する場合があります。この key: value の部分が、このコミットの変更対象です。

go/ast パッケージ

go/ast パッケージは、Go言語のソースコードのASTを表現するためのデータ構造と関数を提供します。コンパイラやGoツール(go fmt, go vet など)は、このパッケージを利用してGoコードを解析・操作します。

  • ast.Expr: 式を表すインターフェース。
  • ast.BinaryExpr: 二項演算子(例: a + b, x == y)を表すASTノード。X(左オペランド)、Op(演算子)、Y(右オペランド)のフィールドを持ちます。
  • ast.CompositeLit: 複合リテラルを表すASTノード。Type(リテラルの型)、Lbrace(左中括弧の位置)、Elts(要素のリスト)、Rbrace(右中括弧の位置)のフィールドを持ちます。Eltsast.Exprのリストです。

go/token パッケージ

go/token パッケージは、Go言語の字句解析器(lexer)によって生成されるトークン(キーワード、識別子、演算子、区切り文字など)を定義します。各トークンには、その種類とソースコード上の位置情報が関連付けられます。

  • token.Token: トークンの種類を表す型。
  • token.COLON: コロン(:)を表すトークン。
  • token.Precedence(): トークンの演算子としての優先順位を返すメソッド。

技術的詳細

このコミットの主要な変更点は、複合リテラル内の key: value ペアのAST表現を、汎用的な ast.BinaryExpr から専用の ast.KeyValueExpr へと移行したことです。

変更前: 複合リテラル内の key: value は、ast.BinaryExpr として扱われていました。ここで、X はキー、Y は値、Optoken.COLON でした。ast.CompositeLitElts フィールドには、ast.BinaryExpr のインスタンスが格納されていました。

このアプローチの問題点は、コロンが複合リテラル内で特別な意味を持つにもかかわらず、AST上では加算や比較と同じ「二項演算子」として扱われてしまうことでした。これは、ASTのセマンティクスを歪め、コードの意図を正確に表現できていませんでした。例えば、a : b という構文が、複合リテラル内なのか、それとも将来的に導入されるかもしれない別の構文(例えばラベル付きステートメントなど)なのかを区別するのが難しくなる可能性がありました。

変更後: 新しい ast.KeyValueExpr ノードが導入されました。このノードは、Key(キーを表す ast.Expr)、Colon(コロンの位置情報 token.Position)、Value(値を表す ast.Expr)という専用のフィールドを持ちます。

これにより、ast.CompositeLitElts フィールドには、ast.KeyValueExpr のインスタンスが直接格納されるようになります。この変更により、ASTは複合リテラルの構造をより正確かつ明示的に表現できるようになりました。コンパイラは、KeyValueExpr ノードを見るだけで、それが複合リテラル内のキーと値のペアであることを明確に認識できます。

また、コロンが二項演算子として扱われなくなったため、src/lib/go/token.go から token.COLON の演算子優先順位が削除されました。これにより、パーサーがコロンを演算子として誤って解釈する可能性がなくなります。

この変更は、Go言語のAST設計における重要な改善であり、言語のセマンティクスをより厳密にASTに反映させるための初期の取り組みの一つです。

コアとなるコードの変更箇所

src/lib/go/ast.go

  1. KeyValueExpr 構造体の追加: 複合リテラル内のキーと値のペアを表す新しいASTノード KeyValueExpr が定義されました。
    type (
        // ... 既存の型定義 ...
    
        // A KeyValueExpr node represents (key : value) pairs
        // in composite literals.
        //
        KeyValueExpr struct {
            Key Expr;
            Colon token.Position;  // position of ":"
            Value Expr;
        };
    )
    
  2. CompositeLit および BinaryExpr のコメント修正: CompositeLitBinaryExpr のコメントから、コロンを二項演算子として扱うことに関する古い記述(// A pair (x : y) in a CompositeLit is represented by // a binary expression with the Colon operator.)が削除されました。これは、新しい KeyValueExpr の導入により、この表現が不要になったためです。
  3. Pos() メソッドの追加: KeyValueExpr ノードの開始位置を返す Pos() メソッドが追加されました。
    func (x *KeyValueExpr) Pos() token.Position  { return x.Key.Pos(); }
    
  4. ExprVisitor インターフェースの更新: ASTを走査するための ExprVisitor インターフェースに、新しい DoKeyValueExpr メソッドが追加されました。これにより、ASTウォーカーが KeyValueExpr ノードを適切に処理できるようになります。
    type ExprVisitor interface {
        // ... 既存のメソッド ...
        DoKeyValueExpr(x *KeyValueExpr);
    }
    
  5. Visit メソッドの追加: KeyValueExpr ノードの Visit メソッドが追加され、ExprVisitorDoKeyValueExpr を呼び出すように実装されました。
    func (x *KeyValueExpr) Visit(v ExprVisitor) { v.DoKeyValueExpr(x); }
    

src/lib/go/token.go

  1. COLON の優先順位の削除: Token 型の Precedence() メソッドから、token.COLON の優先順位を返す case COLON: return 0; の行が削除されました。これにより、コロンはもはや演算子として扱われなくなりました。
    func (op Token) Precedence() int {
        switch op {
        // case COLON:  // この行が削除された
        //     return 0;
        case LOR:
            return 1;
        // ...
        }
    }
    
  2. LowestPrec の値の変更: LowestPrec の値が -1 から 0 に変更されました。これは、COLON の優先順位が削除されたことと関連している可能性がありますが、このコミットの直接的な影響というよりは、演算子優先順位の定義全体の見直しの一部である可能性もあります。

コアとなるコードの解説

このコミットの核心は、Go言語の構文解析器が複合リテラル内の key: value ペアをどのように表現するかという、ASTのセマンティクスに関する変更です。

src/lib/go/ast.go における KeyValueExpr の導入は、このペアが単なる二項演算ではなく、複合リテラルという特定の文脈における構造的な要素であることを明確に示します。これにより、コンパイラのセマンティック分析フェーズや、Goツール(例えば、コードフォーマッタやリンター)が、複合リテラルの構造をより正確に理解し、処理できるようになります。例えば、go fmt が複合リテラルの整形を行う際に、KeyValueExpr の存在は、キーと値のペアを適切に配置するための明確なヒントとなります。

src/lib/go/token.go から COLON の演算子優先順位が削除されたことは、コロンがもはや汎用的な二項演算子ではないことを意味します。これは、パーサーがコロンを処理する際に、その文脈(複合リテラル内かどうか)に基づいて特別な扱いをする必要があることを示唆しています。これにより、パーサーのロジックがより堅牢になり、将来的にコロンが他の構文で異なる意味を持つようになった場合でも、曖昧さを避けることができます。

この変更は、Go言語のコンパイラがソースコードを内部的にどのように表現し、処理するかという基盤部分に影響を与えるものであり、言語の設計思想である「明確性」と「シンプルさ」をASTレベルで追求した結果と言えます。

関連リンク

  • Go言語のASTに関する公式ドキュメントやチュートリアル(Go言語の進化に伴い、当時の情報を見つけるのは難しい可能性がありますが、go/ast パッケージの現在のドキュメントが参考になります)。
  • Go言語の初期の設計に関する議論(メーリングリストのアーカイブなど)。

参考にした情報源リンク

  • Go言語のソースコード(特に src/go/ast/ast.gosrc/go/token/token.go の現在のバージョンは、このコミットがもたらした変更の最終的な形を理解するのに役立ちます)。
  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のASTパッケージドキュメント: https://pkg.go.dev/go/ast
  • Go言語のTokenパッケージドキュメント: https://pkg.go.dev/go/token
  • Go言語のコミット履歴(GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期のメーリングリストアーカイブ(もし関連する議論が見つかれば)