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

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

このコミットは、Go言語のコンパイラ内部で使用される抽象構文木(AST)のgo/astパッケージと、型システム関連のexp/typesパッケージにおける変更です。具体的には、inttruefalsenilといったGo言語の「事前宣言済みオブジェクト(predeclared objects)」の内部表現を改善し、これらのオブジェクトが「Universeスコープ」または「Unsafeスコープ」をその宣言元(Declフィールド)として持つように修正しています。これにより、オブジェクトが事前宣言済みであるか否かを容易に判別できるようになります。

コミット

go/ast: predeclared objects have the Universe/Unsafe scope as Decl

Makes it possible to easily detect if an Object was predeclared
(as opposed to unresolved).

R=rsc
CC=golang-dev
https://golang.org/cl/5530072

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

https://github.com/golang/go/commit/090049130e751718ee8100d673c6add9f98289b2

元コミット内容

commit 090049130e751718ee8100d673c6add9f98289b2
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Jan 10 18:30:06 2012 -0800

    go/ast: predeclared objects have the Universe/Unsafe scope as Decl
    
    Makes it possible to easily detect if an Object was predeclared
    (as opposed to unresolved).
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5530072
---
 src/pkg/exp/types/universe.go | 1 +
 src/pkg/go/ast/scope.go       | 4 +++-\n 2 files changed, 4 insertions(+), 1 deletion(-)\n

変更の背景

Go言語のコンパイラや静的解析ツールは、ソースコードを解析する際に、各識別子(変数名、関数名など)が何を指しているのかを解決する必要があります。この解決プロセスにおいて、inttruefalsenilといったGo言語に組み込みで存在する「事前宣言済みオブジェクト」は特別な扱いを受けます。

以前のGoコンパイラの内部実装では、ast.Object構造体のDecl(Declaration、宣言元)フィールドは、そのオブジェクトがソースコード上のどこで宣言されたかを示すASTノードへのポインタを保持していました。しかし、事前宣言済みオブジェクトはソースコード上の特定の宣言箇所を持たないため、そのDeclフィールドはnilになっていました。

この「Declnilである」という状態は、コンパイラが識別子を解決できなかった場合(つまり、未解決の識別子)にも発生しうるため、事前宣言済みオブジェクトと未解決の識別子を区別することが困難でした。この曖昧さは、コンパイラのシンボル解決ロジックや、静的解析ツールが正確な情報に基づいて動作する上で問題となる可能性がありました。

このコミットは、この問題を解決するために、事前宣言済みオブジェクトのDeclフィールドに、そのオブジェクトが属する特別なスコープ(UniverseスコープまたはUnsafeスコープ)を設定するように変更しました。これにより、obj.Decl*ast.Scope型であるかどうかをチェックするだけで、そのオブジェクトが事前宣言済みであるかを明確かつ容易に判別できるようになります。

前提知識の解説

このコミットの理解には、Go言語のコンパイラがどのようにソースコードを処理するか、特に抽象構文木(AST)とスコープの概念に関する基本的な知識が必要です。

  • Go言語のAST (Abstract Syntax Tree): Goコンパイラは、ソースコードを字句解析(トークン化)し、構文解析(パース)することで、プログラムの構造を木構造で表現した「抽象構文木(AST)」を生成します。go/astパッケージは、このASTをプログラム的に操作するための型と関数を提供します。ASTは、コンパイラの意味解析、型チェック、コード生成などの後続フェーズの入力となります。

  • ast.Object: ast.Objectは、AST内で識別子(変数名、関数名、型名など)が参照する実体(オブジェクト)を表す構造体です。この構造体は、オブジェクトの種類(Kind、例: Var, Func, Type)、名前(Name)、宣言元(Decl)、およびその他の関連情報(Data, Type)を保持します。Declフィールドは、通常、そのオブジェクトがソースコード上のどのASTノードで宣言されたかを示します。

  • ast.Scope: スコープは、プログラム内で識別子が有効な範囲を定義する概念です。Go言語では、各ブロック、関数、パッケージ、そして最上位の「Universeスコープ」がそれぞれスコープを持ちます。ast.Scope構造体は、そのスコープ内で宣言されたast.Objectのマップを保持し、識別子の解決(名前解決)に使用されます。

  • Predeclared Objects (事前宣言済みオブジェクト): Go言語には、特別な宣言なしに最初から利用できる組み込みの識別子が存在します。これらを事前宣言済みオブジェクトと呼びます。例としては、以下のものがあります。

    • 組み込み型: bool, byte, int, string, errorなど
    • 組み込み関数: len, cap, make, new, panic, recoverなど
    • 組み込み定数: true, false, nil, iotaなど これらは、Go言語の仕様によって定義されており、特定のソースファイルで宣言されているわけではありません。
  • Universe Scope (ユニバーススコープ): Go言語における最上位のスコープであり、すべてのGoプログラムで常に利用可能な事前宣言済み識別子(上記のような組み込み型、関数、定数など)が含まれます。

  • Unsafe Scope (アンセーフスコープ): unsafeパッケージに関連する識別子(例: unsafe.Pointer)が含まれる特別なスコープです。これも事前宣言済みオブジェクトと同様に扱われます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのシンボル解決メカニズムにおける事前宣言済みオブジェクトの識別方法の改善にあります。

Goコンパイラは、ソースコードを解析する過程で、各識別子(例: x, myFunc, MyType)がどのast.Objectに対応するかを決定します。この解決プロセスでは、現在のスコープから上位のスコープへと順に検索が行われます。事前宣言済みオブジェクトはUniverseスコープに存在するため、最終的に解決されるべきオブジェクトとなります。

しかし、このコミット以前は、事前宣言済みオブジェクトのast.Object.Declフィールドはnilでした。これは、これらのオブジェクトがソースコード上の特定の宣言位置を持たないためです。このnilという状態は、コンパイラが識別子を解決できなかった場合(つまり、その識別子がどこにも宣言されていない場合)にも発生するため、Declnilであるという事実だけでは、そのオブジェクトが「事前宣言済みである」のか「未解決である」のかを区別できませんでした。

この変更により、事前宣言済みオブジェクトがUniverseスコープ(またはUnsafeスコープ)に登録される際に、そのast.Object.Declフィールドに、そのオブジェクトが属するast.Scopeオブジェクト自身が設定されるようになります。これにより、コンパイラや静的解析ツールは、ast.Object.Decl*ast.Scope型であるかどうかをチェックするだけで、そのオブジェクトが事前宣言済みであるかを確実に判別できるようになります。

この明確な識別メカニズムは、以下のような利点をもたらします。

  1. シンボル解決の堅牢性: コンパイラが事前宣言済みオブジェクトと未解決の識別子をより正確に区別できるようになり、シンボル解決のロジックが簡素化され、エラーの可能性が減少します。
  2. 静的解析の精度向上: go vetのような静的解析ツールや、IDEのコード補完機能などが、事前宣言済みオブジェクトに関するより正確な情報に基づいて動作できるようになります。例えば、事前宣言済みオブジェクトに対して誤った操作が行われた場合に、より適切な警告やエラーを生成できるようになります。
  3. コードの可読性と保守性: ast.ObjectDeclフィールドが、事前宣言済みオブジェクトの場合にも意味のある情報(属するスコープ)を持つことで、コンパイラ内部のコードの可読性と保守性が向上します。

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

このコミットは、主に以下の2つのファイルに変更を加えています。

  1. src/pkg/exp/types/universe.go このファイルは、Go言語のUniverseスコープに事前宣言済みオブジェクトを定義するロジックを含んでいます。

    --- a/src/pkg/exp/types/universe.go
    +++ b/src/pkg/exp/types/universe.go
    @@ -20,6 +20,7 @@ func define(kind ast.ObjKind, name string) *ast.Object {
     	if scope.Insert(obj) != nil {
     		panic("types internal error: double declaration")
     	}
    +	obj.Decl = scope
     	return obj
     }
    

    define関数内で、新しく定義されたobj(事前宣言済みオブジェクト)のDeclフィールドに、そのオブジェクトが挿入されたscope(Universeスコープ)自身が代入されるようになりました。

  2. src/pkg/go/ast/scope.go このファイルは、ast.Object構造体とast.Scope構造体の定義を含んでいます。

    --- a/src/pkg/go/ast/scope.go
    +++ b/src/pkg/go/ast/scope.go
    @@ -80,7 +80,7 @@ func (s *Scope) String() string {
     type Object struct {\n \tKind ObjKind\n \tName string      // declared name
    -\tDecl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, or AssignStmt; or nil
    +\tDecl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil
     	Data interface{} // object-specific data; or nil
     	Type interface{} // place holder for type information; may be nil
     }
    @@ -131,6 +131,8 @@ func (obj *Object) Pos() token.Pos {
     			return ident.Pos()
     		}
     	}
    +	case *Scope:
    +		// predeclared object - nothing to do for now
     	}
     	return token.NoPos
     }
    

    ast.Object構造体のDeclフィールドのコメントが更新され、Scope型もDeclとして設定されうることを明示しています。 また、Object.Pos()メソッドに*Scope型のDeclが渡された場合の新しいケースが追加されました。Object.Pos()は、通常、オブジェクトが宣言されたソースコード上の位置を返すために使用されますが、事前宣言済みオブジェクトには物理的な宣言位置がないため、このケースでは何もしない(結果的にtoken.NoPosが返される)という挙動が正しいです。

コアとなるコードの解説

  • src/pkg/exp/types/universe.goの変更: この変更は、事前宣言済みオブジェクトがUniverseスコープに登録される際の初期化ロジックを修正しています。obj.Decl = scopeという行が追加されたことで、define関数によって作成されるast.ObjectインスタンスのDeclフィールドに、そのオブジェクトが属するast.Scope(この場合はUniverseスコープ)への参照が設定されます。これにより、事前宣言済みオブジェクトが「どこで宣言されたか」という情報が、そのオブジェクトが属するスコープとして明確に表現されるようになります。

  • src/pkg/go/ast/scope.goの変更:

    1. ast.Object.Declコメントの更新: DeclフィールドのコメントにScopeが追加されたことで、このフィールドが*ast.Scope型も取りうるという新しいセマンティクスがドキュメント化されました。これは、コンパイラ開発者にとって、Declフィールドの役割をより正確に理解するための重要な情報となります。
    2. Object.Pos()メソッドへの*Scopeケースの追加: Object.Pos()メソッドは、ast.Objectがソースコード上のどこで宣言されたかを示すtoken.Pos(位置情報)を返します。事前宣言済みオブジェクトはソースコード上の特定の宣言位置を持たないため、Decl*ast.Scope型である場合には、token.NoPos(無効な位置)を返すのが適切です。この新しいcase *Scope:ブロックは、この正しい挙動を保証し、コンパイラが事前宣言済みオブジェクトの位置情報を不適切に扱わないようにします。

これらの変更は、Goコンパイラの内部表現をより正確かつ堅牢にし、事前宣言済みオブジェクトの識別を容易にすることで、コンパイラ自体の機能と、その上に構築される静的解析ツールやIDEの機能の信頼性を向上させるものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のASTに関する一般的な解説記事
  • Go言語のスコープと識別子解決に関する技術ブログやフォーラムの議論