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

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

このコミットは、Go言語の型チェッカーの実験的なステージングブランチ(exp/types/staging)において、型チェックの核となる要素である「オペランド(operand)」、「定数(constant)」の表現と操作、そして「エラーハンドリング」の仕組みを導入・整理するものです。具体的には、式の型と値を一時的に保持するoperand構造体の導入、様々な型の定数(真偽値、整数、浮動小数点数、複素数、文字列)を扱うためのconst.goの実装、そしてエラー報告を一元化するためのerrors.goの再編が行われています。これにより、型チェッカーの基盤がより堅牢になり、今後の機能拡張に備えるための重要なステップとなります。

コミット

exp/types/staging: operands, constants, and error handling

More pieces of the typechecker code:

- Operands are temporary objects representing an expressions's
type and value (for constants). An operand is the equivalent of
an "attribute" in attribute grammars except that it's not stored
but only passed around during type checking.

- Constant operations are implemented in const.go. Constants are
represented as bool (booleans), int64 and *big.Int (integers),
*big.Rat (floats), complex (complex numbers), and string (strings).

- Error reporting is consolidated in errors.go. Only the first
dozen of lines is new code, the rest of the file contains the
exprString and typeString functions formerly in two separate
files (which have been removed).

This is a replacement CL for 6492101 (which was created without
proper use of hg).

R=rsc, r
CC=golang-dev
https://golang.org/cl/6500114

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

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

元コミット内容

commit f5483fb801044eb50185d6630020575e70f454ba
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Sep 25 17:38:22 2012 -0700

    exp/types/staging: operands, constants, and error handling

    More pieces of the typechecker code:

    - Operands are temporary objects representing an expressions's
    type and value (for constants). An operand is the equivalent of
    an "attribute" in attribute grammars except that it's not stored
    but only passed around during type checking.

    - Constant operations are implemented in const.go. Constants are
    represented as bool (booleans), int64 and *big.Int (integers),
    *big.Rat (floats), complex (complex numbers), and string (strings).

    - Error reporting is consolidated in errors.go. Only the first
    dozen of lines is new code, the rest of the file contains the
    exprString and typeString functions formerly in two separate
    files (which have been removed).

    This is a replacement CL for 6492101 (which was created without
    proper use of hg).

    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/6500114

変更の背景

このコミットは、Go言語の型チェッカーの初期開発段階における重要な進展を示しています。当時のGo言語の型チェッカーはまだ実験的な段階にあり、exp/types/stagingというディレクトリ名がそのことを示唆しています。型チェッカーは、プログラムの構文木(AST)を走査し、各式の型を決定し、型の一貫性を検証する役割を担います。このプロセスにおいて、以下の課題がありました。

  1. 式の中間表現の欠如: 型チェック中に式の型や定数値といった情報を一時的に保持する統一されたメカニズムが不足していました。これにより、型チェックロジックが複雑になり、定数伝播などの最適化が困難になる可能性がありました。
  2. 定数計算の未整備: Go言語では、コンパイル時に評価可能な定数(untyped constants)が存在し、これらは特定の型に束縛されずに柔軟な振る舞いをします。これらの定数に対する算術演算や比較演算を正確かつ効率的に処理する仕組みが必要でした。特に、任意精度整数(big.Int)や任意精度有理数(big.Rat)を扱う必要がありました。
  3. エラー報告の分散: 型チェック中に発生するエラーの報告ロジックが散在しており、一貫性のあるエラーメッセージの生成や、エラー発生時の処理が煩雑になる傾向がありました。

これらの課題に対処するため、本コミットでは以下の主要な変更が導入されました。

  • オペランドの導入: 式の型と値をカプセル化するoperand構造体を導入し、型チェック中の情報伝達を効率化しました。これは、属性文法における「属性」に相当する概念であり、一時的なデータ構造として利用されます。
  • 定数操作の集約: const.goファイルに定数に関するすべての操作(生成、正規化、算術演算、比較演算など)を集約しました。これにより、定数処理のロジックが明確になり、保守性が向上しました。
  • エラーハンドリングの一元化: errors.goファイルにエラー報告関数を集約し、exprStringtypeStringといった補助関数もこのファイルに移動することで、エラーメッセージの生成と報告のプロセスを簡素化しました。

このコミットは、型チェッカーの機能性と堅牢性を向上させるための基盤を固めるものであり、Go言語のコンパイラ開発における重要なマイルストーンの一つと言えます。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1. 型チェッカー (Type Checker)

型チェッカーは、コンパイラのフロントエンドの一部であり、プログラムのソースコードが言語の型システム規則に準拠しているかを検証します。主な役割は以下の通りです。

  • 型推論: 変数や式の型を自動的に決定します。
  • 型検証: 異なる型の値が不適切な方法で組み合わされていないか(例: 整数と文字列の加算)をチェックします。
  • 定数伝播: コンパイル時に値が確定する定数式を評価し、その結果をプログラム中に埋め込みます。これにより、実行時の計算を減らし、最適化の機会を増やします。

Go言語の型チェッカーは、go/astパッケージで表現される抽象構文木(AST)を走査しながら、これらの処理を行います。

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

ASTは、プログラムのソースコードを抽象的な階層構造で表現したものです。Go言語では、go/astパッケージがASTのノードを定義しています。型チェッカーは、このASTをトラバースしてプログラムの意味を解析します。

3. go/tokenパッケージ

go/tokenパッケージは、Go言語のソースコードにおけるトークン(キーワード、識別子、演算子など)と、それらのソースコード上の位置(行番号、列番号など)を扱うための型と関数を提供します。token.Posはソースコード上の位置を表す型です。

4. go/scannerパッケージ

go/scannerパッケージは、Go言語のソースコードをトークンに分割する字句解析器(lexer)を提供します。エラーリスト(scanner.ErrorList)は、字句解析中に発生したエラーを収集するために使用されます。

5. 任意精度演算 (Arbitrary-Precision Arithmetic)

Go言語のmath/bigパッケージは、任意精度の整数(big.Int)、有理数(big.Rat)、浮動小数点数(big.Float)を扱うための型とメソッドを提供します。通常のGoの組み込み数値型(int64, float64など)は固定のビット幅を持つため、表現できる数値の範囲に限りがあります。しかし、コンパイル時の定数計算では、非常に大きな数値や高い精度が要求される場合があります。このような場合にmath/bigパッケージが利用されます。

  • *big.Int: 任意精度の整数を表現します。
  • *big.Rat: 任意精度の有理数(分数)を表現します。浮動小数点数の精度問題を回避し、正確な計算が可能です。

6. 属性文法 (Attribute Grammars)

属性文法は、文脈自由文法に「属性」という概念を追加したものです。属性は、構文木の各ノードに関連付けられた値であり、構文解析中に計算されます。属性は、構文的な情報だけでなく、意味的な情報(型、値など)を伝達するために使用されます。このコミットで導入される「オペランド」は、この属性文法の「属性」に相当すると説明されています。

技術的詳細

このコミットの技術的詳細は、主に以下の3つの領域に分けられます。

1. オペランド (operand) の導入と管理

src/pkg/exp/types/staging/operand.goで定義されるoperand構造体は、型チェック中に式が持つ「型」と「値」(定数の場合)を一時的にカプセル化するための重要なデータ構造です。

type operand struct {
	mode operandMode
	expr ast.Expr
	typ  Type
	val  interface{}
}
  • mode: オペランドの「モード」を示します。invalid(無効)、novalue(値なし)、typexpr(型式)、constant(定数)、variable(変数)、value(計算された値)、valueok(カンマOK式で使用可能)などがあります。これにより、オペランドがどのような種類の値や式を表しているかを明確に区別できます。
  • expr: オペランドに対応する抽象構文木(AST)の式ノードです。エラー報告などで元のソースコードの位置を特定するために使用されます。
  • typ: オペランドの型です。Go言語の型システムにおけるTypeインターフェースを実装する具体的な型(*Basic, *NamedTypeなど)が格納されます。
  • val: オペランドが定数である場合に、その定数値が格納されます。interface{}型であるため、様々な種類の定数(bool, int64, *big.Int, *big.Rat, complex, string, nilType)を保持できます。

operandは、型チェックの各段階で式の評価結果を次の段階に渡すための「一時的なオブジェクト」として機能します。コミットメッセージにあるように、これは属性文法における「属性」に似ていますが、ASTノードに永続的に保存されるのではなく、型チェック中に動的に生成・伝達されます。

setConst関数は、リテラル文字列とトークンからoperandを定数として初期化する役割を担います。例えば、token.INTと数値文字列からUntypedInt型の定数オペランドを生成します。

2. 定数 (constant) の表現と操作

src/pkg/exp/types/staging/const.goは、Go言語の定数(特に型なし定数)の表現と、それらに対する算術演算、ビット演算、比較演算を実装しています。

定数の表現: 定数は、その値に応じて最も「小さい」データ構造で表現されるように正規化されます。

  • 真偽値: bool
  • 整数: int64または*big.Intint64の範囲を超える場合)
  • 浮動小数点数: *big.Rat(任意精度有理数)
  • 複素数: complex構造体(実部と虚部を*big.Ratで持つ)
  • 文字列: string
  • nil: nilTypenil定数を表すための特別な型)

normalizeIntConst, normalizeRatConst, normalizeComplexConstなどの関数は、定数値を正規化し、最小限のメモリで表現できるようにします。例えば、*big.Intで表現された整数がint64の範囲に収まる場合、int64に変換されます。

定数演算: binaryOpConst, shiftConst, compareConstなどの関数は、定数に対する二項演算、シフト演算、比較演算を実装しています。これらの関数は、オペランドの型に応じて適切なmath/bigパッケージの関数を呼び出し、正確な任意精度計算を行います。

  • binaryOpConst(x, y interface{}, op token.Token, intDiv bool): xyの定数値をop演算子で結合します。intDivtrueの場合、整数除算を行います。
  • shiftConst(x interface{}, s uint, op token.Token): 整数定数xsビットだけop(左シフトまたは右シフト)します。
  • compareConst(x, y interface{}, op token.Token): xyの定数値をop演算子で比較します。

これらの関数は、matchConst関数を使用して、演算を行う前に2つの定数オペランドの表現を最も複雑な方に合わせることで、型の一貫性を保ちます。

3. エラーハンドリングの一元化

src/pkg/exp/types/staging/errors.goは、型チェッカー全体でエラーを報告するための共通のメカニズムを提供します。

  • checker.errorf(pos token.Pos, format string, args ...interface{}): 指定されたソースコード位置posに、フォーマットされたエラーメッセージを報告します。内部的にはgo/scanner.ErrorListにエラーを追加します。
  • checker.invalidAST, checker.invalidArg, checker.invalidOp: 特定の種類のエラーを報告するためのヘルパー関数です。
  • exprString(expr ast.Expr) stringtypeString(typ Type) string: これらの関数は、以前はexprstring.gotypestring.goという別々のファイルに存在していましたが、このコミットでerrors.goに統合されました。これにより、エラーメッセージ内で式や型の文字列表現を生成する際に、関連するコードが近くに配置され、保守性が向上しました。

この統合により、型チェック中に発生する様々なエラーが統一された方法で処理され、開発者やユーザーにとって理解しやすいエラーメッセージが生成されるようになります。

4. check.goにおける型チェックロジックの更新

src/pkg/exp/types/staging/check.goは、型チェッカーの主要なロジックを実装するファイルです。このコミットでは、operandと定数処理の導入に伴い、定数宣言の型チェック(checker.obj内のast.Conケース)や、メソッドと型の関連付けロジックが更新されています。

  • checker.decl: 宣言の型チェックを行う関数で、定数宣言の場合にiota値を使用して初期化式を評価するロジックが追加されました。
  • checker.obj: オブジェクト(定数、変数、型、関数など)の型チェックを行う主要な関数です。定数(ast.Con)のケースでは、operandconst.goの機能を利用して定数値を評価するようになりました。
  • check関数: パッケージ全体の型チェックを開始するエントリポイントです。メソッドと型の関連付けロジックが改善され、エラー報告がerrors.goの新しいメカニズムを使用するように変更されました。

5. predicates.gouniverse.goの変更

  • src/pkg/exp/types/staging/predicates.go: underlyingderef関数が移動され、defaultType関数が追加されました。defaultTypeは、型なし定数(UntypedBool, UntypedIntなど)がデフォルトでどの型に推論されるか(Bool, Int, Float64など)を決定します。
  • src/pkg/exp/types/staging/universe.go: 組み込みの基本型定義(Typ配列)に、各型のサイズ情報(Size int64)が追加されました。これは、型システムが型のメモリレイアウトに関する情報を保持し始めることを示唆しています。

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

このコミットで変更された主要なファイルと、それぞれの役割は以下の通りです。

  • src/pkg/exp/types/staging/check.go:
    • 新規ファイル。型チェッカーのメインロジックを実装。
    • checker構造体と、declare, decl, objなどの型チェック関連関数を定義。
    • 定数宣言の型チェックにoperandconst.goの機能を利用するようになった。
    • メソッドと型の関連付けロジックを改善。
  • src/pkg/exp/types/staging/const.go:
    • 新規ファイル。Go言語の定数(特に型なし定数)の表現と、それらに対する算術演算、ビット演算、比較演算を実装。
    • complexnilType構造体を定義。
    • normalizeIntConst, normalizeRatConst, normalizeComplexConstなどの定数正規化関数。
    • makeRuneConst, makeIntConst, makeFloatConst, makeComplexConst, makeStringConstなどのリテラルから定数を生成する関数。
    • binaryOpConst, shiftConst, compareConstなどの定数演算関数。
  • src/pkg/exp/types/staging/errors.go:
    • 新規ファイル。型チェッカーのエラー報告を一元化。
    • checker.errorfなどのエラー報告関数を定義。
    • 以前は別ファイルだったexprStringtypeString関数を統合。
  • src/pkg/exp/types/staging/exprstring.go:
    • 削除されたファイル。exprString関数がerrors.goに移動。
  • src/pkg/exp/types/staging/operand.go:
    • 新規ファイル。型チェック中に式の型と値を一時的に保持するoperand構造体を定義。
    • operandMode列挙型を定義。
    • setConst関数など、operandを操作する関数を定義。
  • src/pkg/exp/types/staging/predicates.go:
    • 変更されたファイル。underlyingderef関数の移動、defaultType関数の追加。
  • src/pkg/exp/types/staging/stubs.go:
    • 新規ファイル。未実装の型チェック関連関数のスタブを定義。
  • src/pkg/exp/types/staging/types.go:
    • 変更されたファイル。Check関数のコメントアウトが解除され、実際の型チェックロジックが有効化。
    • Basic型にSizeフィールドが追加。
  • src/pkg/exp/types/staging/typestring.go:
    • 削除されたファイル。typeString関数がerrors.goに移動。
  • src/pkg/exp/types/staging/universe.go:
    • 変更されたファイル。組み込みの基本型定義(Typ配列)にSizeフィールドの初期値が追加。

コアとなるコードの解説

src/pkg/exp/types/staging/operand.go

// An operand represents an intermediate value during type checking.
// Operands have an (addressing) mode, the expression evaluating to
// the operand, the operand's type, and for constants a constant value.
//
type operand struct {
	mode operandMode
	expr ast.Expr
	typ  Type
	val  interface{}
}

// setConst sets x to the untyped constant for literal lit.
func (x *operand) setConst(tok token.Token, lit string) {
	x.mode = invalid

	var kind BasicKind
	var val interface{}
	switch tok {
	case token.INT:
		kind = UntypedInt
		val = makeIntConst(lit)
	// ... (other cases for FLOAT, IMAG, CHAR, STRING)
	}

	if val != nil {
		x.mode = constant
		x.typ = Typ[kind]
		x.val = val
	}
}

operand構造体は、型チェックの過程で式の評価結果を一時的に保持するための中心的なデータ構造です。modeはオペランドの種類(定数、変数など)を示し、exprは元のASTノード、typはその型、valは定数値(存在する場合)を格納します。setConst関数は、リテラル(例: "123", "true", "3.14")から型なし定数オペランドを生成する際に使用されます。makeIntConstなどの関数を呼び出して、リテラル文字列を適切なGoの定数表現(int64, *big.Intなど)に変換し、operandvalフィールドに設定します。

src/pkg/exp/types/staging/const.go

// Representation of constant values.
//
// bool     ->  bool (true, false)
// numeric  ->  int64, *big.Int, *big.Rat, complex (ordered by increasing data structure "size")
// string   ->  string
// nil      ->  nilType (nilConst)
//
// Numeric constants are normalized after each operation such
// that they are represented by the "smallest" data structure
// required to represent the constant, independent of actual
// type. Non-numeric constants are always normalized.

// normalizeIntConst returns the smallest constant representation
// for the specific value of x; either an int64 or a *big.Int value.
//
func normalizeIntConst(x *big.Int) interface{} {
	if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 {
		return x.Int64()
	}
	return x
}

// binaryOpConst returns the result of the constant evaluation x op y;
// both operands must be of the same "kind" (boolean, numeric, or string).
// If intDiv is true, division (op == token.QUO) is using integer division
// (and the result is guaranteed to be integer) rather than floating-point
// division. Division by zero leads to a run-time panic.
//
func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} {
	x, y = matchConst(x, y) // オペランドの表現を一致させる

	switch x := x.(type) {
	case bool:
		// ...
	case int64:
		// ... (int64に対する加算、減算、乗算、剰余、除算、ビット演算)
		// 例: 加算
		// if is63bit(x) && is63bit(y) { return x + y }
		// return normalizeIntConst(new(big.Int).Add(big.NewInt(x), big.NewInt(y)))
	case *big.Int:
		// ... (big.Intに対する加算、減算、乗算、剰余、除算、ビット演算)
	case *big.Rat:
		// ... (big.Ratに対する加算、減算、乗算、除算)
	case complex:
		// ... (complexに対する加算、減算、乗算、除算)
	case string:
		// ... (文字列連結)
	}
	// ...
}

const.goは、Goの定数計算の核心部分です。定数は、その値の範囲と精度に応じてint64*big.Int*big.Ratcomplexなどの型で表現されます。normalizeIntConstのような関数は、計算結果がより単純な型で表現できる場合に、その型に変換することでメモリ効率と処理速度を最適化します。binaryOpConstは、Go言語の様々な二項演算子(+, -, *, /, %, &, |, ^, &^)に対応する定数計算ロジックを実装しています。matchConstによってオペランドの型が揃えられた後、それぞれの型に応じた適切な計算(math/bigパッケージの関数を含む)が実行されます。これにより、コンパイル時に正確な定数評価が可能になります。

src/pkg/exp/types/staging/errors.go

// This file implements various error reporters.

package types

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/token"
)

func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) {
	check.convertArgs(args) // 引数内のASTノードや型を文字列に変換
	msg := fmt.Sprintf(format, args...)
	check.errors.Add(check.fset.Position(pos), msg) // エラーリストに追加
}

// exprString returns a (simplified) string representation for an expression.
func exprString(expr ast.Expr) string {
	var buf bytes.Buffer
	writeExpr(&buf, expr)
	return buf.String()
}

// typeString returns a string representation for typ.
func typeString(typ Type) string {
	var buf bytes.Buffer
	writeType(&buf, typ)
	return buf.String()
}

errors.goは、型チェッカーにおけるエラー報告の中心的なファイルです。checker.errorf関数は、エラーメッセージのフォーマットと、go/scanner.ErrorListへの追加を担当します。convertArgs関数は、エラーメッセージの引数として渡されたtoken.Posast.ExprTypeなどの内部表現を、人間が読める文字列形式に変換します。特に、exprStringtypeStringは、エラーメッセージ内で特定の式や型の文字列表現が必要な場合に呼び出され、詳細なエラー情報を提供します。これらの関数がerrors.goに統合されたことで、エラー報告ロジックの凝集性が高まりました。

関連リンク

  • Go言語の型システムに関する公式ドキュメント(当時のものがあれば)
  • go/typesパッケージの進化に関するGoのIssueやデザインドキュメント
  • math/bigパッケージのドキュメント

参考にした情報源リンク