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

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

このコミットは、Go言語の実験的な型チェッカーAPIの初期セットを導入するものです。具体的には、src/pkg/exp/types/stagingディレクトリ以下に5つの新しいファイルが追加され、Goプログラムの型情報を解析・表現するための基盤が構築されています。これらのファイルは、Goの抽象構文木(AST)を走査し、各要素の型を決定し、型の一致性や互換性を検証するためのデータ構造と関数を提供します。

コミット

commit b29d641b3a379b2fb0f88ceed066f043acab7c33
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Sep 10 14:54:52 2012 -0700

    exp/types/staging: typechecker API
    
    First set of type checker files for review.
    The primary concern here is the typechecker
    API (types.go).
    
    R=rsc, adonovan, r, rogpeppe
    CC=golang-dev
    https://golang.org/cl/6490089

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

https://github.com/golang/go/commit/b29d641b3a379b2fb0f88ceed066f043acab7c33

元コミット内容

exp/types/staging: typechecker API

レビューのための型チェッカーファイルの最初のセットです。ここでの主な関心事は、型チェッカーAPI (types.go) です。

変更の背景

このコミットは、Go言語の公式ツールセットに型チェッカー機能を導入するための初期段階として行われました。Go言語のコンパイラは内部的に型チェックを行いますが、外部ツール(IDE、リンター、静的解析ツールなど)がGoコードの型情報をプログラム的に利用するための標準化されたAPIは存在しませんでした。

このexp/types/stagingパッケージの導入は、Go言語の進化において重要なマイルストーンとなります。これにより、以下のようなメリットが期待されます。

  • より高度な開発ツールの実現: 型情報にアクセスできることで、より正確なコード補完、リファクタリング、エラー検出などが可能になります。
  • 静的解析の強化: コードの潜在的なバグや非効率な部分を、実行前に型レベルで検出する能力が向上します。
  • コンパイラの改善: 型チェッカーAPIが独立した形で提供されることで、コンパイラ内部の型チェックロジックの再利用や、よりモジュール化された開発が可能になります。
  • 言語仕様の厳密な実装: 型チェッカーは、Go言語の型システムに関する仕様を厳密に実装し、その振る舞いを検証するためのリファレンスとしても機能します。

特に、コミットメッセージで「types.goが主な関心事」と述べられているように、型チェッカーの核となるデータ構造とインターフェースの設計がこの段階での最重要課題であったことが伺えます。

前提知識の解説

このコミットの理解には、以下のGo言語およびコンパイラ関連の概念に関する知識が役立ちます。

1. Go言語の型システム

Go言語は静的型付け言語であり、すべての変数、関数、式には型があります。Goの型システムは、以下のような特徴を持ちます。

  • 基本型: int, string, bool, float64などの組み込み型。
  • 複合型:
    • 配列 ([N]T): 固定長で同じ型の要素を格納。
    • スライス ([]T): 可変長で同じ型の要素を格納。
    • 構造体 (struct{...}): 異なる型のフィールドをまとめたもの。
    • ポインタ (*T): 変数のメモリアドレスを指す。
    • 関数型 (func(...) (...)): 関数のシグネチャ(引数と戻り値の型)を定義。
    • インターフェース型 (interface{...}): メソッドのセットを定義し、そのメソッドを実装する任意の型を受け入れる。
    • マップ (map[K]V): キーと値のペアを格納。
    • チャネル (chan T): ゴルーチン間の通信に使用。
  • 名前付き型: type MyInt intのように、既存の型に新しい名前を付けることができる。これにより、基底型が同じでも異なる型として扱われる。
  • 型アサーションと型スイッチ: インターフェース値の基底の具体的な型を動的にチェックするメカニズム。

2. 抽象構文木 (AST: Abstract Syntax Tree)

コンパイラやインタプリタは、ソースコードを直接処理するのではなく、まずその構造を抽象的なツリー形式で表現します。これが抽象構文木(AST)です。Go言語では、標準ライブラリのgo/astパッケージがGoソースコードのASTを表現するためのデータ構造を提供します。

  • ast.Expr: 式を表すインターフェース。
  • ast.Stmt: 文を表すインターフェース。
  • ast.Decl: 宣言を表すインターフェース。
  • ast.File: 単一のGoソースファイル全体のASTを表す。
  • ast.Package: 複数のast.Fileからなるパッケージ全体のASTを表す。

型チェッカーは、このASTを走査し、各ノード(式、変数、関数呼び出しなど)に適切な型を割り当て、Go言語の型規則に違反がないかを確認します。

3. go/tokenパッケージ

go/tokenパッケージは、Goソースコード内の位置(ファイル、行、列)を表現するための型と、キーワードや演算子などのトークンを定義します。型チェッカーがエラーメッセージを生成する際など、ソースコードの正確な位置を特定するために利用されます。

4. 型チェッカーの役割

型チェッカーは、コンパイラのフロントエンドの一部であり、以下の主要な役割を担います。

  • 型推論: 変数や式の型を自動的に決定します。
  • 型検証: 演算子や関数呼び出しの引数など、型が期待される文脈で正しい型が使用されているかを確認します。
  • 型変換のチェック: 暗黙的または明示的な型変換が許可されているかを確認します。
  • 名前解決: 識別子(変数名、関数名など)がどの宣言に対応するかを解決し、その宣言の型情報を取得します。

このコミットで導入されるexp/types/stagingパッケージは、これらの型チェック機能の基盤となるAPIとデータ構造を提供することを目的としています。

技術的詳細

このコミットでは、Go言語の型チェッカーのコアコンポーネントがsrc/pkg/exp/types/stagingパッケージとして導入されています。主要なファイルとその役割は以下の通りです。

types.go

このファイルは、型チェッカーAPIの心臓部であり、Go言語の様々な型を表現するためのデータ構造と、型チェックのエントリーポイントとなるCheck関数(ただし、このコミット時点ではコメントアウトされている)を定義しています。

  • Typeインターフェース: すべてのGoの型が実装する基底インターフェース。
  • BasicKind: int, string, boolなどの基本型の種類を列挙する型。
  • BasicInfo: 基本型の特性(数値型、文字列型、符号なし整数など)を示すフラグのセット。
  • Basic構造体: 基本型を表現。Kind, Info, Nameを持つ。
  • 複合型を表す構造体:
    • Array: 配列型 ([Len]Elt)
    • Slice: スライス型 ([]Elt)
    • StructField: 構造体のフィールド
    • Struct: 構造体型 (struct{...})
    • Pointer: ポインタ型 (*Base)
    • Signature: 関数型 (func(...) (...))
    • Interface: インターフェース型 (interface{...})
    • Map: マップ型 (map[Key]Elt)
    • Chan: チャネル型 (chan Elt, <-chan Elt, chan<- Elt)
  • NamedType構造体: type MyType BaseTypeのように宣言された名前付き型を表現。基底型 (Underlying) と関連するメソッド (Methods) を持つ。
  • ObjList: *ast.Objectのリストで、パラメータやメソッドのリストを表現するために使用される。sort.Interfaceを実装し、名前でソート可能。
  • implementsType: すべての具体的な型がTypeインターフェースを実装するための埋め込みフィールド。

predicates.go

このファイルは、型の比較や特定の特性を持つ型を識別するためのユーティリティ関数(述語関数)を実装しています。

  • isNamed(typ Type) bool: 型が名前付き型であるか(基本型またはNamedType)を判定。
  • isBoolean(typ Type), isInteger(typ Type), isFloat(typ Type), isComplex(typ Type), isNumeric(typ Type), isString(typ Type), isUntyped(typ Type), isOrdered(typ Type): 型が特定の基本型の特性を持つかを判定。BasicInfoフラグを利用。
  • isUnsigned(typ Type): 型が符号なし整数型かを判定。
  • isComparable(typ Type) bool: 型が比較可能であるかを判定。Go言語では、ブール値、数値、文字列、ポインタ、チャネル、インターフェース、構造体(すべてのフィールドが比較可能な場合)、配列(要素が比較可能な場合)が比較可能です。関数、マップ、スライスは比較できません。
  • underlying(typ Type) Type: 名前付き型の基底型を返す。例えば、type MyInt intの場合、MyIntの基底型であるintを返します。
  • deref(typ Type) Type: ポインタ型の基底型を返す。ポインタ型でなければ元の型を返す。
  • isIdentical(x, y Type) bool: 2つの型が同一であるかを再帰的に判定する最も重要な関数。Go言語の型同一性ルールに従って、配列の長さ、スライス・マップ・チャネルの要素型、構造体のフィールド、関数のシグネチャ、インターフェースのメソッドなどを比較します。
  • identicalTypes(a, b ObjList) bool: ObjList内のオブジェクトの型が同一であるかを判定するヘルパー関数。

exprstring.go

このファイルは、go/astパッケージのast.Expr(式)ノードを簡略化された文字列形式で表現するためのユーティリティ関数を提供します。デバッグやログ出力に役立ちます。

  • exprString(expr ast.Expr) string: ast.Exprを文字列に変換するメイン関数。
  • writeExpr(buf *bytes.Buffer, expr ast.Expr): ast.Exprの各種類(識別子、リテラル、関数リテラル、複合リテラル、括弧式、セレクタ式、インデックス式、スライス式、型アサーション、関数呼び出し、ポインタ参照、単項演算、二項演算)に応じて、bytes.Bufferに文字列を書き込む。

typestring.go

このファイルは、typesパッケージで定義されたTypeインターフェースを実装する型を、人間が読める文字列形式で表現するためのユーティリティ関数を提供します。

  • typeString(typ Type) string: Typeを文字列に変換するメイン関数。
  • writeParams(buf *bytes.Buffer, params ObjList, isVariadic bool): 関数のパラメータリストを文字列に変換。
  • writeSignature(buf *bytes.Buffer, sig *Signature): 関数のシグネチャを文字列に変換。
  • writeType(buf *bytes.Buffer, typ Type): Typeの各種類(基本型、配列、スライス、構造体、ポインタ、タプル、シグネチャ、組み込み関数、インターフェース、マップ、チャネル、名前付き型)に応じて、bytes.Bufferに文字列を書き込む。

universe.go

このファイルは、Go言語の「ユニバーススコープ」(組み込みの識別子や型が定義されているグローバルスコープ)とunsafeパッケージのスコープを初期化します。

  • Universe: 組み込みの型、定数、関数(len, cap, make, new, panicなど)が定義される*ast.Scope
  • unsafe: unsafeパッケージのスコープ。
  • Typ: BasicKindに対応する組み込みの*Basic型の配列。
  • aliases: byteruneのエイリアス定義。
  • predeclaredConstants: true, false, iota, nilなどの組み込み定数。
  • predeclaredFunctions: append, len, makeなどの組み込み関数。
  • init()関数: パッケージがロードされる際に、Universeスコープとunsafeスコープを初期化し、組み込みの型、定数、関数を登録します。

これらのファイル群は、Go言語の型システムをプログラム的に表現し、解析するための包括的なフレームワークの初期バージョンを構成しています。

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

このコミットでは、以下の5つの新しいファイルが追加されています。

  • src/pkg/exp/types/staging/exprstring.go
  • src/pkg/exp/types/staging/predicates.go
  • src/pkg/exp/types/staging/types.go
  • src/pkg/exp/types/staging/typestring.go
  • src/pkg/exp/types/staging/universe.go

これらのファイルはすべて新規追加であり、既存のコードの変更はありません。これは、Goの型チェッカーAPIが完全に新しいモジュールとして導入されたことを意味します。

コアとなるコードの解説

このコミットのコアは、types.goで定義される型を表すデータ構造と、predicates.goで定義される型に関する述語関数、特にisIdentical関数です。

types.goにおける型表現

types.goでは、Go言語のあらゆる型を表現するための構造体が定義されています。これらは、GoのASTノード(ast.Exprなど)がソースコードの構文構造を表すのに対し、typesパッケージの構造体は、その構文構造が持つ「意味論的な型」を表します。

例えば、intという基本型はBasic構造体で表現され、[]stringというスライス型はSlice構造体と、その要素型であるstringを表すBasic構造体の組み合わせで表現されます。

// All types implement the Type interface.
type Type interface {
	aType()
}

// A Basic represents a basic type.
type Basic struct {
	implementsType
	Kind BasicKind
	Info BasicInfo
	Name string
}

// A Slice represents a slice type []Elt.
type Slice struct {
	implementsType
	Elt Type // 要素の型
}

これらの構造体は、型チェッカーがASTを走査する際に、各式の型を決定し、その型情報を保持するために使用されます。

predicates.goにおける型同一性判定 (isIdentical)

isIdentical関数は、Go言語の型システムにおいて非常に重要な役割を果たします。Goでは、2つの型が「同一である」と見なされるための厳密なルールがあります。この関数は、そのルールをコードで実装したものです。

func isIdentical(x, y Type) bool {
	if x == y {
		return true
	}

	switch x := x.(type) {
	case *Basic:
		// Basic types are singletons except for the rune and byte
		// aliases, thus we cannot solely rely on the x == y check
		// above.
		if y, ok := y.(*Basic); ok {
			return x.Kind == y.Kind
		}

	case *Array:
		// Two array types are identical if they have identical element types
		// and the same array length.
		if y, ok := y.(*Array); ok {
			return x.Len == y.Len && isIdentical(x.Elt, y.Elt)
		}

	// ... 他の型に関する同一性チェック ...

	case *NamedType:
		// Two named types are identical if their type names originate
		// in the same type declaration.
		if y, ok := y.(*NamedType); ok {
			return x.Obj == y.Obj // 同じ ast.Object から宣言された型であるか
		}
	}

	return false
}

この関数は、再帰的に型の内部構造を比較し、Goの言語仕様で定められた型同一性の規則(例:配列は要素型と長さが同じ、スライスは要素型が同じ、構造体はフィールドの名前・型・タグ・匿名性が同じ、関数はパラメータ・結果の型とvariadic性が同じなど)に基づいてtrueまたはfalseを返します。特にNamedTypeの同一性チェックでは、その型が宣言された元の*ast.Objectが同じであるかを確認することで、異なるパッケージで同じ名前の型が宣言されていても、それらが異なる型として扱われることを保証します。

このisIdentical関数は、型チェッカーが型の一致性や代入可能性を検証する際の基盤となります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go言語の型システム、go/astパッケージ、go/tokenパッケージに関する一般的な情報)
  • コンパイラの理論に関する一般的な知識 (AST、型チェッカーの概念)