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

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

このコミットは、Go言語のパーサー(go/parserパッケージ)における型解決の挙動を改善するものです。特に、関数パラメータと構造体フィールドの型が、パーシング段階で適切に解決されるように修正が加えられています。これにより、パーサーが生成する抽象構文木(AST)の正確性が向上し、後続の型チェックやセマンティック分析のフェーズでの処理がより堅牢になります。

コミット

commit 1f46cb0ba26b392e19be34d74db51bc32b9b1b92
Author: Robert Griesemer <gri@golang.org>
Date:   Wed May 23 16:12:45 2012 -0700

    go/parser: resolve all parameter types
    
    Fixes #3655.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6213065

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

https://github.com/golang/go/commit/1f46cb0ba26b392b19be34d74db51bc32b9b1b92

元コミット内容

go/parser: resolve all parameter types

このコミットは、Go言語のパーサーがすべてのパラメータ型を解決するように修正します。

Fixes #3655.

これはIssue #3655を修正します。

変更の背景

Go言語のコンパイラは、ソースコードを処理する際に複数のフェーズを経ます。その初期段階の一つが「パーシング(構文解析)」であり、ソースコードを抽象構文木(AST)に変換します。このASTは、その後の型チェック、セマンティック分析、コード生成などのフェーズで利用されます。

このコミットが修正するIssue #3655は、go/parserが特定の状況下で関数パラメータや構造体フィールドの型を適切に解決できていなかった問題に関連しています。具体的には、パーサーがASTを構築する際に、型を表す識別子(例: int, string, カスタム型名)が、それが参照する実際の型定義にリンクされていない、つまり「未解決」のまま残ってしまうケースがあったと考えられます。

このような未解決の型が存在すると、後続のコンパイルフェーズで正確な型情報が得られず、誤った型チェックエラーが発生したり、コンパイラがクラッシュしたりする可能性がありました。このコミットの目的は、パーシング段階で可能な限りすべての型識別子を解決し、ASTの健全性を高めることにあります。これにより、コンパイラの安定性と正確性が向上します。

前提知識の解説

このコミットを理解するためには、以下の概念が重要です。

  • Go言語の構文と型システム:
    • 関数パラメータ: Goの関数は、func f(a int, b string)のようにパラメータを持ちます。
    • 可変長パラメータ (...): func f(args ...int)のように、任意の数の引数を受け取るパラメータです。
    • 構造体フィールド: type MyStruct struct { Field1 int; Field2 string }のように、構造体はフィールドを持ちます。
    • 匿名フィールド: 構造体内に型名のみで宣言されるフィールド(例: type MyStruct struct { io.Reader })。この場合、フィールド名は型名と同じになります。
  • コンパイラの基本概念:
    • 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例: func, f, (, a, int, ,など)。
    • 構文解析 (Parsing): トークンのストリームを文法規則に従って解析し、プログラムの構造を階層的なツリー構造(抽象構文木、AST)として構築するプロセス。
    • 抽象構文木 (AST - Abstract Syntax Tree): ソースコードの構造を表現するツリーデータ構造。各ノードはプログラムの構成要素(式、文、宣言など)を表します。go/astパッケージで定義されています。
    • シンボル解決 (Symbol Resolution): プログラム内の識別子(変数名、関数名、型名など)が、それが参照する宣言(定義)と関連付けられるプロセス。例えば、変数xがどのxの宣言を参照しているかを特定します。パーサーやセマンティックアナライザーの重要な機能です。
    • スコープ (Scope): 識別子が有効なプログラムの領域。Goでは、ブロック、関数、ファイル、パッケージなど、様々なスコープが存在します。
  • Goの標準ライブラリ go/parsergo/ast:
    • go/parserパッケージは、Goのソースコードを解析し、go/astパッケージで定義されたASTを生成します。
    • go/ast.Expr: AST内の式を表すインターフェース。
    • go/ast.Field: 構造体のフィールド、または関数のパラメータや結果変数を表すASTノード。
    • go/ast.BasicLit: 整数リテラル、文字列リテラルなどの基本的なリテラルを表すASTノード。
    • go/ast.StarExpr: ポインタ型(例: *int)を表すASTノード。
    • go/ast.Scope: 識別子のスコープを表すデータ構造。
    • tokenパッケージ: Go言語のトークン定数を定義します(例: token.INT, token.STRING, token.LPARENなど)。
  • parser構造体の内部メソッド:
    • p.resolve(typ ast.Expr): パーサー内部のメソッドで、ASTノードtypが表す型を解決する役割を担います。これは通常、typが識別子である場合に、その識別子がどの型宣言を参照しているかをスコープ内で探し、ASTノードにその解決済み情報を付与する処理を指します。
    • p.declare(field *ast.Field, ...): パーサー内部のメソッドで、新しい識別子(この場合はフィールドやパラメータ)を現在のスコープに宣言する役割を担います。

技術的詳細

このコミットの核心は、go/parserパッケージ内のparser構造体のメソッドにおけるp.resolve(typ)の呼び出しタイミングと場所の調整です。p.resolveは、ASTノードが表す型を、その識別子が参照する実際の型定義にリンクさせるための重要な操作です。

変更前は、特定の構文パターン(特に匿名フィールドや可変長パラメータの型、複数のパラメータがまとめて宣言される場合など)において、型を表すASTノードが生成された直後にp.resolveが呼び出されていませんでした。これにより、ASTが完全に構築された時点で、一部の型情報が未解決のまま残る可能性がありました。

このコミットでは、以下の主要な変更を通じて、この問題を解決しています。

  1. parseFieldDeclにおける型解決の統一:
    • 構造体のフィールド宣言を解析するparseFieldDecl関数において、匿名フィールドの型解決ロジックが変更されました。以前は特定の条件下でのみp.resolve(typ)が呼び出されていましたが、変更後はフィールドが宣言された直後(p.declareの後)に、常にp.resolve(typ)が呼び出されるようになりました。これにより、すべてのフィールド型が確実に解決されるようになります。
  2. tryVarTypeにおける可変長パラメータの型解決:
    • 可変長パラメータ(...Type)の型を解析するtryVarType関数において、型が正常に解析された場合にp.resolve(typ)が明示的に呼び出されるようになりました。以前は、型が存在しない場合にエラーを報告するロジックの中に型解決が含まれておらず、型が存在する場合の解決が漏れる可能性がありました。
  3. parseVarListにおける型解決の委譲:
    • 変数リスト(例: a, b int)を解析するparseVarList関数から、直接的なp.resolve(typ)の呼び出しが削除されました。代わりに、この関数が返す型(typ)の解決は、そのtypを受け取る呼び出し元(例: parseParameterList)に委譲されるようになりました。これは、parseVarListが必ずしも最終的な型解決を行うべき場所ではないという設計思想の変更を示唆しています。コメント// If any of the results are identifiers, they are not resolved.が追加され、この関数の役割が「識別子を解決しない」ことにあると明記されています。
  4. parseParameterListにおけるパラメータ型解決の強化:
    • 関数パラメータリストを解析するparseParameterList関数において、パラメータの型が宣言された直後にp.resolve(typ)が追加されました。これは、a, b intのような複数のパラメータが同じ型を持つ場合や、個別のパラメータ宣言の場合の両方で適用されます。これにより、すべての関数パラメータの型がパーシング段階で確実に解決されるようになります。
    • 匿名パラメータ(例: func(int, string))の型解決も、ループ内でp.resolve(typ)が明示的に呼び出されることで、より堅牢になりました。

これらの変更は、go/parserがASTを構築する際に、型情報をより早期かつ一貫して解決するように促します。これにより、ASTがより「完全な」状態で生成され、後続のコンパイルフェーズでの処理が簡素化され、エラーの可能性が低減されます。

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

変更は主にsrc/pkg/go/parser/parser.goと、その変更を検証するためのテストファイルsrc/pkg/go/parser/parser_test.goに集中しています。

src/pkg/go/parser/parser.go

  1. func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field

    • 変更前:
      // [\"*\"] TypeName (AnonymousField)
      typ = list[0] // we always have at least one element
      p.resolve(typ) // <-- この行が削除された
      if n := len(list); n > 1 || !isTypeName(deref(typ)) {
          pos := typ.Pos()
          p.errorExpected(pos, "anonymous field")
          typ = &ast.BadExpr{From: pos, To: p.pos}
      }
      
    • 変更後:
      // [\"*\"] TypeName (AnonymousField)
      typ = list[0] // we always have at least one element
      if n := len(list); n > 1 || !isTypeName(deref(typ)) {
          pos := typ.Pos()
          p.errorExpected(pos, "anonymous field")
          typ = &ast.BadExpr{From: pos, To: p.pos}
      }
      // ...
      field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment}
      p.declare(field, nil, scope, ast.Var, idents...)
      p.resolve(typ) // <-- ここに移動・追加された
      
    • 変更の意図: 匿名フィールドの型解決が、フィールド宣言全体の解決ロジックと統合され、p.declareの直後に一貫して行われるようになりました。
  2. func (p *parser) tryVarType(isParam bool) ast.Expr

    • 変更前:
      if isParam && p.tok == token.ELLIPSIS {
          pos := p.pos
          p.next()
          typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message
          if typ == nil { // <-- この条件分岐が変更された
              p.error(pos, "'...' parameter is missing type")
              typ = &ast.BadExpr{From: pos, To: p.pos}
          }
          return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
      }
      
    • 変更後:
      if isParam && p.tok == token.ELLIPSIS {
          pos := p.pos
          p.next()
          typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message
          if typ != nil { // <-- typがnilでない場合に解決するようになった
              p.resolve(typ)
          } else { // <-- typがnilの場合の処理
              p.error(pos, "'...' parameter is missing type")
              typ = &ast.BadExpr{From: pos, To: p.pos}
          }
          return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
      }
      
    • 変更の意図: 可変長パラメータの型が正常に解析された場合、その型を明示的に解決するようにしました。
  3. func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr)

    • 変更前:
      // if we had a list of identifiers, it must be followed by a type
      if typ = p.tryVarType(isParam); typ != nil {
          p.resolve(typ) // <-- この行が削除された
      }
      
    • 変更後:
      // if we had a list of identifiers, it must be followed by a type
      // If any of the results are identifiers, they are not resolved. // <-- 新しいコメント
      typ = p.tryVarType(isParam) // <-- p.resolve(typ)が削除された
      
    • 変更の意図: parseVarList自身が型を解決するのではなく、その結果を受け取る呼び出し元に解決を委譲するようになりました。新しいコメントは、この関数の役割を明確にしています。
  4. func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field)

    • 変更前:
      if typ != nil {
          // IdentifierList Type
          idents := p.makeIdentList(list)
          field := &ast.Field{Names: idents, Type: typ}
          p.declare(field, nil, scope, ast.Var, idents...)
          // p.resolve(typ) がなかった
          if p.tok == token.COMMA {
              p.next()
          }
          // ...
          for p.tok != token.RPAREN && p.tok != token.EOF {
              idents := p.parseIdentList()
              typ := p.parseVarType(ellipsisOk)
              field := &ast.Field{Names: idents, Type: typ}
              p.declare(field, nil, scope, ast.Var, idents...)
              // p.resolve(typ) がなかった
              if !p.atComma("parameter list") {
                  break
              }
              p.next()
          }
      } else {
          // Type { "," Type } (anonymous parameters)
          params = make([]*ast.Field, len(list))
          for i, x := range list {
              p.resolve(x) // <-- xをtypにリネーム
              params[i] = &ast.Field{Type: x} // <-- xをtypにリネーム
          }
      }
      
    • 変更後:
      // ParameterDecl // <-- 新しいコメント
      list, typ := p.parseVarList(ellipsisOk)
      
      // analyze case // <-- 新しいコメント
      if typ != nil {
          // IdentifierList Type
          idents := p.makeIdentList(list)
          field := &ast.Field{Names: idents, Type: typ}
          p.declare(field, nil, scope, ast.Var, idents...)
          p.resolve(typ) // <-- 追加された
          if p.tok == token.COMMA {
              p.next()
          }
          // ...
          for p.tok != token.RPAREN && p.tok != token.EOF {
              idents := p.parseIdentList()
              typ := p.parseVarType(ellipsisOk)
              field := &ast.Field{Names: idents, Type: typ}
              p.declare(field, nil, scope, ast.Var, idents...)
              p.resolve(typ) // <-- 追加された
              if !p.atComma("parameter list") {
                  break
              }
              p.next()
          }
      } else {
          // Type { "," Type } (anonymous parameters)
          params = make([]*ast.Field, len(list))
          for i, typ := range list { // <-- xをtypにリネーム
              p.resolve(typ) // <-- xをtypにリネーム
              params[i] = &ast.Field{Type: typ} // <-- xをtypにリネーム
          }
      }
      
    • 変更の意図: 関数パラメータの型が、識別子リストの後に続く場合(例: a, b int)と、個別のパラメータ宣言の場合(例: a int, b string)の両方で、p.declareの直後にp.resolve(typ)が呼び出されるようになりました。これにより、すべてのパラメータ型が確実に解決されます。匿名パラメータのループ変数もxからtypにリネームされ、可読性が向上しています。

src/pkg/go/parser/parser_test.go

  1. func TestUnresolved(t *testing.T)の追加
    • この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーがf.Unresolvedリストに格納する「未解決の識別子」を収集します。
    • want変数には、Goの組み込み型(int, byte, float, complexなど)のように、パーサー自身が明示的に解決する必要がない(コンパイラの型チェッカーが最終的に解決する)識別子のリストが期待値として定義されています。
    • このテストの目的は、パーサーが「解決すべき型」を適切に解決し、「解決する必要がない(または後続フェーズで解決される)型」を未解決リストに正しく残すことを検証することです。このテストの追加は、今回の変更がパーサーの型解決ロジックに与える影響を正確に捉えるために不可欠です。

コアとなるコードの解説

このコミットのコアとなる変更は、p.resolve(typ)メソッドの戦略的な配置にあります。

p.resolve(typ)は、go/parserパッケージの内部で、ASTノードtypが表す型を、その識別子が参照する実際の型定義にリンクさせる役割を担っています。例えば、ソースコードにvar x MyTypeという宣言があった場合、パーサーはMyTypeという識別子を解析し、それをASTノードとして表現します。p.resolve(MyTypeASTNode)が呼び出されると、パーサーは現在のスコープ内でMyTypeという名前の型定義を探し、見つかればその型定義への参照をMyTypeASTNodeに設定します。これにより、ASTは単なる構文構造だけでなく、セマンティックな情報(この識別子がどの定義を参照しているか)も含むようになります。

変更前は、特に以下のようなケースでp.resolve(typ)の呼び出しが漏れていました。

  • 匿名フィールド: 構造体内の匿名フィールド(例: struct { io.Reader })の型。
  • 可変長パラメータ: func f(args ...int)のような可変長パラメータの型。
  • 複数のパラメータ宣言: func f(a, b int)のように、複数のパラメータが同じ型を持つ場合の型。

これらのケースでp.resolve(typ)が適切に呼び出されないと、生成されるASTではこれらの型が「未解決」のままとなり、後続のコンパイルフェーズで問題を引き起こす可能性がありました。

今回の変更では、これらの見落としを修正し、parseFieldDecltryVarTypeparseParameterListといった関数内で、型が特定された直後、またはフィールド/パラメータがスコープに宣言された直後にp.resolve(typ)を呼び出すようにしました。

特にparseVarListからのp.resolve(typ)の削除は重要です。これは、parseVarListが識別子のリストとそれに続く型を解析する汎用的な関数であり、その型が常に即座に解決されるべきではないという設計上の判断を示しています。例えば、var a, b intのような変数宣言の場合、int型はparseVarListで解析されますが、その解決は変数宣言全体のコンテキストで行われるべきです。そのため、parseVarListは型を「見つける」だけで、その解決は呼び出し元に委ねるという役割分担が明確化されました。

新しいTestUnresolvedテストは、この変更が意図通りに機能していることを検証します。このテストは、パーサーがASTを構築した後に残る「未解決の識別子」のリストをチェックします。期待される未解決の識別子は、int, byte, floatなどの組み込み型です。これらの型はGoの言語仕様で定義されており、パーサーが明示的に解決しなくても、コンパイラの型チェッカーが最終的に解決できるため、未解決のままで問題ありません。このテストがパスすることで、パーサーが「解決すべき型」を適切に解決し、「未解決のままで良い型」を正しく識別していることが確認されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のソースコード (github.com/golang/go)
  • Go言語のgo/parserおよびgo/astパッケージのドキュメント
  • コンパイラ設計に関する一般的な情報源(構文解析、AST、シンボル解決など)

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

このコミットは、Go言語のパーサー(go/parserパッケージ)における型解決の挙動を改善するものです。特に、関数パラメータと構造体フィールドの型が、パーシング段階で適切に解決されるように修正が加えられています。これにより、パーサーが生成する抽象構文木(AST)の正確性が向上し、後続の型チェックやセマンティック分析のフェーズでの処理がより堅牢になります。

コミット

commit 1f46cb0ba26b392e19be34d74db51bc32b9b1b92
Author: Robert Griesemer <gri@golang.org>
Date:   Wed May 23 16:12:45 2012 -0700

    go/parser: resolve all parameter types
    
    Fixes #3655.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6213065

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

https://github.com/golang/go/commit/1f46cb0ba26b392e19be34d74db51bc32b9b1b92

元コミット内容

go/parser: resolve all parameter types

このコミットは、Go言語のパーサーがすべてのパラメータ型を解決するように修正します。

Fixes #3655.

これはIssue #3655を修正します。

変更の背景

Go言語のコンパイラは、ソースコードを処理する際に複数のフェーズを経ます。その初期段階の一つが「パーシング(構文解析)」であり、ソースコードを抽象構文木(AST)に変換します。このASTは、その後の型チェック、セマンティック分析、コード生成などのフェーズで利用されます。

このコミットが修正するIssue #3655は、go/parserが特定の状況下で関数パラメータや構造体フィールドの型を適切に解決できていなかった問題に関連しています。具体的には、パーサーがASTを構築する際に、型を表す識別子(例: int, string, カスタム型名)が、それが参照する実際の型定義にリンクされていない、つまり「未解決」のまま残ってしまうケースがあったと考えられます。

このような未解決の型が存在すると、後続のコンパイルフェーズで正確な型情報が得られず、誤った型チェックエラーが発生したり、コンパイラがクラッシュしたりする可能性がありました。このコミットの目的は、パーシング段階で可能な限りすべての型識別子を解決し、ASTの健全性を高めることにあります。これにより、コンパイラの安定性と正確性が向上します。

前提知識の解説

このコミットを理解するためには、以下の概念が重要です。

  • Go言語の構文と型システム:
    • 関数パラメータ: Goの関数は、func f(a int, b string)のようにパラメータを持ちます。
    • 可変長パラメータ (...): func f(args ...int)のように、任意の数の引数を受け取るパラメータです。
    • 構造体フィールド: type MyStruct struct { Field1 int; Field2 string }のように、構造体はフィールドを持ちます。
    • 匿名フィールド: 構造体内に型名のみで宣言されるフィールド(例: type MyStruct struct { io.Reader })。この場合、フィールド名は型名と同じになります。
  • コンパイラの基本概念:
    • 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例: func, f, (, a, int, ,など)。
    • 構文解析 (Parsing): トークンのストリームを文法規則に従って解析し、プログラムの構造を階層的なツリー構造(抽象構文木、AST)として構築するプロセス。
    • 抽象構文木 (AST - Abstract Syntax Tree): ソースコードの構造を表現するツリーデータ構造。各ノードはプログラムの構成要素(式、文、宣言など)を表します。go/astパッケージで定義されています。
    • シンボル解決 (Symbol Resolution): プログラム内の識別子(変数名、関数名、型名など)が、それが参照する宣言(定義)と関連付けられるプロセス。例えば、変数xがどのxの宣言を参照しているかを特定します。パーサーやセマンティックアナライザーの重要な機能です。
    • スコープ (Scope): 識別子が有効なプログラムの領域。Goでは、ブロック、関数、ファイル、パッケージなど、様々なスコープが存在します。
  • Goの標準ライブラリ go/parsergo/ast:
    • go/parserパッケージは、Goのソースコードを解析し、go/astパッケージで定義されたASTを生成します。
    • go/ast.Expr: AST内の式を表すインターフェース。
    • go/ast.Field: 構造体のフィールド、または関数のパラメータや結果変数を表すASTノード。
    • go/ast.BasicLit: 整数リテラル、文字列リテラルなどの基本的なリテラルを表すASTノード。
    • go/ast.StarExpr: ポインタ型(例: *int)を表すASTノード。
    • go/ast.Scope: 識別子のスコープを表すデータ構造。
    • tokenパッケージ: Go言語のトークン定数を定義します(例: token.INT, token.STRING, token.LPARENなど)。
  • parser構造体の内部メソッド:
    • p.resolve(typ ast.Expr): パーサー内部のメソッドで、ASTノードtypが表す型を解決する役割を担います。これは通常、typが識別子である場合に、その識別子がどの型宣言を参照しているかをスコープ内で探し、ASTノードにその解決済み情報を付与する処理を指します。
    • p.declare(field *ast.Field, ...): パーサー内部のメソッドで、新しい識別子(この場合はフィールドやパラメータ)を現在のスコープに宣言する役割を担います。

技術的詳細

このコミットの核心は、go/parserパッケージ内のparser構造体のメソッドにおけるp.resolve(typ)の呼び出しタイミングと場所の調整です。p.resolveは、ASTノードが表す型を、その識別子が参照する実際の型定義にリンクさせるための重要な操作です。

変更前は、特定の構文パターン(特に匿名フィールドや可変長パラメータの型、複数のパラメータがまとめて宣言される場合など)において、型を表すASTノードが生成された直後にp.resolveが呼び出されていませんでした。これにより、ASTが完全に構築された時点で、一部の型情報が未解決のまま残る可能性がありました。

このコミットでは、以下の主要な変更を通じて、この問題を解決しています。

  1. parseFieldDeclにおける型解決の統一:
    • 構造体のフィールド宣言を解析するparseFieldDecl関数において、匿名フィールドの型解決ロジックが変更されました。以前は特定の条件下でのみp.resolve(typ)が呼び出されていましたが、変更後はフィールドが宣言された直後(p.declareの後)に、常にp.resolve(typ)が呼び出されるようになりました。これにより、すべてのフィールド型が確実に解決されるようになります。
  2. tryVarTypeにおける可変長パラメータの型解決:
    • 可変長パラメータ(...Type)の型を解析するtryVarType関数において、型が正常に解析された場合にp.resolve(typ)が明示的に呼び出されるようになりました。以前は、型が存在しない場合にエラーを報告するロジックの中に型解決が含まれておらず、型が存在する場合の解決が漏れる可能性がありました。
  3. parseVarListにおける型解決の委譲:
    • 変数リスト(例: a, b int)を解析するparseVarList関数から、直接的なp.resolve(typ)の呼び出しが削除されました。代わりに、この関数が返す型(typ)の解決は、そのtypを受け取る呼び出し元(例: parseParameterList)に委譲されるようになりました。これは、parseVarListが必ずしも最終的な型解決を行うべき場所ではないという設計思想の変更を示唆しています。コメント// If any of the results are identifiers, they are not resolved.が追加され、この関数の役割が「識別子を解決しない」ことにあると明記されています。
  4. parseParameterListにおけるパラメータ型解決の強化:
    • 関数パラメータリストを解析するparseParameterList関数において、パラメータの型が宣言された直後にp.resolve(typ)が追加されました。これは、a, b intのような複数のパラメータが同じ型を持つ場合や、個別のパラメータ宣言の場合の両方で適用されます。これにより、すべての関数パラメータの型がパーシング段階で確実に解決されるようになります。
    • 匿名パラメータ(例: func(int, string))の型解決も、ループ内でp.resolve(typ)が明示的に呼び出されることで、より堅牢になりました。

これらの変更は、go/parserがASTを構築する際に、型情報をより早期かつ一貫して解決するように促します。これにより、ASTがより「完全な」状態で生成され、後続のコンパイルフェーズでの処理が簡素化され、エラーの可能性が低減します。

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

変更は主にsrc/pkg/go/parser/parser.goと、その変更を検証するためのテストファイルsrc/pkg/go/parser/parser_test.goに集中しています。

src/pkg/go/parser/parser.go

  1. func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field

    • 変更前:
      // [\"*\"] TypeName (AnonymousField)
      typ = list[0] // we always have at least one element
      p.resolve(typ) // <-- この行が削除された
      if n := len(list); n > 1 || !isTypeName(deref(typ)) {
          pos := typ.Pos()
          p.errorExpected(pos, "anonymous field")
          typ = &ast.BadExpr{From: pos, To: p.pos}
      }
      
    • 変更後:
      // [\"*\"] TypeName (AnonymousField)
      typ = list[0] // we always have at least one element
      if n := len(list); n > 1 || !isTypeName(deref(typ)) {
          pos := typ.Pos()
          p.errorExpected(pos, "anonymous field")
          typ = &ast.BadExpr{From: pos, To: p.pos}
      }
      // ...
      field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment}
      p.declare(field, nil, scope, ast.Var, idents...)
      p.resolve(typ) // <-- ここに移動・追加された
      
    • 変更の意図: 匿名フィールドの型解決が、フィールド宣言全体の解決ロジックと統合され、p.declareの直後に一貫して行われるようになりました。
  2. func (p *parser) tryVarType(isParam bool) ast.Expr

    • 変更前:
      if isParam && p.tok == token.ELLIPSIS {
          pos := p.pos
          p.next()
          typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message
          if typ == nil { // <-- この条件分岐が変更された
              p.error(pos, "'...' parameter is missing type")
              typ = &ast.BadExpr{From: pos, To: p.pos}
          }
          return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
      }
      
    • 変更後:
      if isParam && p.tok == token.ELLIPSIS {
          pos := p.pos
          p.next()
          typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message
          if typ != nil { // <-- typがnilでない場合に解決するようになった
              p.resolve(typ)
          } else { // <-- typがnilの場合の処理
              p.error(pos, "'...' parameter is missing type")
              typ = &ast.BadExpr{From: pos, To: p.pos}
          }
          return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
      }
      
    • 変更の意図: 可変長パラメータの型が正常に解析された場合、その型を明示的に解決するようにしました。
  3. func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr)

    • 変更前:
      // if we had a list of identifiers, it must be followed by a type
      if typ = p.tryVarType(isParam); typ != nil {
          p.resolve(typ) // <-- この行が削除された
      }
      
    • 変更後:
      // if we had a list of identifiers, it must be followed by a type
      // If any of the results are identifiers, they are not resolved. // <-- 新しいコメント
      typ = p.tryVarType(isParam) // <-- p.resolve(typ)が削除された
      
    • 変更の意図: parseVarList自身が型を解決するのではなく、その結果を受け取る呼び出し元に解決を委譲するようになりました。新しいコメントは、この関数の役割を明確にしています。
  4. func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field)

    • 変更前:
      if typ != nil {
          // IdentifierList Type
          idents := p.makeIdentList(list)
          field := &ast.Field{Names: idents, Type: typ}
          p.declare(field, nil, scope, ast.Var, idents...)
          // p.resolve(typ) がなかった
          if p.tok == token.COMMA {
              p.next()
          }
          // ...
          for p.tok != token.RPAREN && p.tok != token.EOF {
              idents := p.parseIdentList()
              typ := p.parseVarType(ellipsisOk)
              field := &ast.Field{Names: idents, Type: typ}
              p.declare(field, nil, scope, ast.Var, idents...)
              // p.resolve(typ) がなかった
              if !p.atComma("parameter list") {
                  break
              }
              p.next()
          }
      } else {
          // Type { "," Type } (anonymous parameters)
          params = make([]*ast.Field, len(list))
          for i, x := range list {
              p.resolve(x) // <-- xをtypにリネーム
              params[i] = &ast.Field{Type: x} // <-- xをtypにリネーム
          }
      }
      
    • 変更後:
      // ParameterDecl // <-- 新しいコメント
      list, typ := p.parseVarList(ellipsisOk)
      
      // analyze case // <-- 新しいコメント
      if typ != nil {
          // IdentifierList Type
          idents := p.makeIdentList(list)
          field := &ast.Field{Names: idents, Type: typ}
          p.declare(field, nil, scope, ast.Var, idents...)
          p.resolve(typ) // <-- 追加された
          if p.tok == token.COMMA {
              p.next()
          }
          // ...
          for p.tok != token.RPAREN && p.tok != token.EOF {
              idents := p.parseIdentList()
              typ := p.parseVarType(ellipsisOk)
              field := &ast.Field{Names: idents, Type: typ}
              p.declare(field, nil, scope, ast.Var, idents...)
              p.resolve(typ) // <-- 追加された
              if !p.atComma("parameter list") {
                  break
              }
              p.next()
          }
      } else {
          // Type { "," Type } (anonymous parameters)
          params = make([]*ast.Field, len(list))
          for i, typ := range list { // <-- xをtypにリネーム
              p.resolve(typ) // <-- xをtypにリネーム
              params[i] = &ast.Field{Type: typ} // <-- xをtypにリネーム
          }
      }
      
    • 変更の意図: 関数パラメータの型が、識別子リストの後に続く場合(例: a, b int)と、個別のパラメータ宣言の場合(例: a int, b string)の両方で、p.declareの直後にp.resolve(typ)が呼び出されるようになりました。これにより、すべてのパラメータ型が確実に解決されます。匿名パラメータのループ変数もxからtypにリネームされ、可読性が向上しています。

src/pkg/go/parser/parser_test.go

  1. func TestUnresolved(t *testing.T)の追加
    • この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーがf.Unresolvedリストに格納する「未解決の識別子」を収集します。
    • want変数には、Goの組み込み型(int, byte, float, complexなど)のように、パーサー自身が明示的に解決する必要がない(コンパイラの型チェッカーが最終的に解決する)識別子のリストが期待値として定義されています。
    • このテストの目的は、パーサーが「解決すべき型」を適切に解決し、「解決する必要がない(または後続フェーズで解決される)型」を未解決リストに正しく残すことを検証することです。このテストの追加は、今回の変更がパーサーの型解決ロジックに与える影響を正確に捉えるために不可欠です。

コアとなるコードの解説

このコミットのコアとなる変更は、p.resolve(typ)メソッドの戦略的な配置にあります。

p.resolve(typ)は、go/parserパッケージの内部で、ASTノードtypが表す型を、その識別子が参照する実際の型定義にリンクさせる役割を担っています。例えば、ソースコードにvar x MyTypeという宣言があった場合、パーサーはMyTypeという識別子を解析し、それをASTノードとして表現します。p.resolve(MyTypeASTNode)が呼び出されると、パーサーは現在のスコープ内でMyTypeという名前の型定義を探し、見つかればその型定義への参照をMyTypeASTNodeに設定します。これにより、ASTは単なる構文構造だけでなく、セマンティックな情報(この識別子がどの定義を参照しているか)も含むようになります。

変更前は、特に以下のようなケースでp.resolve(typ)の呼び出しが漏れていました。

  • 匿名フィールド: 構造体内の匿名フィールド(例: struct { io.Reader })の型。
  • 可変長パラメータ: func f(args ...int)のような可変長パラメータの型。
  • 複数のパラメータ宣言: func f(a, b int)のように、複数のパラメータが同じ型を持つ場合の型。

これらのケースでp.resolve(typ)が適切に呼び出されないと、生成されるASTではこれらの型が「未解決」のままとなり、後続のコンパイルフェーズで問題を引き起こす可能性がありました。

今回の変更では、これらの見落としを修正し、parseFieldDecltryVarTypeparseParameterListといった関数内で、型が特定された直後、またはフィールド/パラメータがスコープに宣言された直後にp.resolve(typ)を呼び出すようにしました。

特にparseVarListからのp.resolve(typ)の削除は重要です。これは、parseVarListが識別子のリストとそれに続く型を解析する汎用的な関数であり、その型が常に即座に解決されるべきではないという設計上の判断を示しています。例えば、var a, b intのような変数宣言の場合、int型はparseVarListで解析されますが、その解決は変数宣言全体のコンテキストで行われるべきです。そのため、parseVarListは型を「見つける」だけで、その解決は呼び出し元に委ねるという役割分担が明確化されました。

新しいTestUnresolvedテストは、この変更が意図通りに機能していることを検証します。このテストは、パーサーがASTを構築した後に残る「未解決の識別子」のリストをチェックします。期待される未解決の識別子は、int, byte, floatなどの組み込み型です。これらの型はGoの言語仕様で定義されており、パーサーが明示的に解決しなくても、コンパイラの型チェッカーが最終的に解決できるため、未解決のままで問題ありません。このテストがパスすることで、パーサーが「解決すべき型」を適切に解決し、「未解決のままで良い型」を正しく識別していることが確認されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のソースコード (github.com/golang/go)
  • Go言語のgo/parserおよびgo/astパッケージのドキュメント
  • コンパイラ設計に関する一般的な情報源(構文解析、AST、シンボル解決など)