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

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

このコミットは、Go言語の実験的な型システムパッケージ exp/types において、型の文字列表現を生成する String() メソッドの実装、既存のテストの改善、およびマップ型作成時のバグ修正を行ったものです。

コミット

commit dcb6f598116563de1f46babd40c62952621784ae
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Jul 31 17:09:12 2012 -0700

    exp/types: implement Type.String methods for testing/debugging
    
    Also:
    - replaced existing test with a more comprehensive test
    - fixed bug in map type creation
    
    R=r
    CC=golang-dev
    https://golang.org/cl/6450072

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

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

元コミット内容

exp/types: implement Type.String methods for testing/debugging

このコミットの主な目的は、Go言語の実験的な型システムパッケージ exp/types 内の様々な型(Type インターフェースを実装する型)に対して String() メソッドを実装することです。これにより、型の情報を文字列として出力できるようになり、テストやデバッグの際に型の構造を視覚的に確認しやすくなります。

また、以下の追加変更も含まれています。

  • 既存のテストをより包括的なテストに置き換えました。
  • マップ型(map)の作成におけるバグを修正しました。

変更の背景

Go言語のコンパイラやツールチェーンの開発において、型システムは非常に重要なコンポーネントです。exp/types パッケージは、Go言語の型チェックと型情報の表現に関する実験的な実装を提供していました。このような低レベルの型システムを開発する際には、内部で表現されている型が意図した通りに構築されているか、あるいは型チェックが正しく行われているかを検証することが不可欠です。

String() メソッドは、Go言語の fmt パッケージと連携して、任意の型の値を人間が読める形式の文字列に変換するために使用されます。型システム内の各型構造体(例: Array, Slice, Struct, Func, Interface, Map, Chan など)が String() メソッドを実装することで、以下のようなメリットが得られます。

  1. デバッグの容易性: 型チェックの過程で生成される中間的な型表現や、エラーメッセージに含まれる型情報を、開発者が直感的に理解できる形で出力できるようになります。これにより、型システム内部の動作を追跡し、問題の原因を特定する作業が大幅に簡素化されます。
  2. テストの強化: 型の等価性や構造の検証を、文字列比較によって簡単に行えるようになります。特に複雑な型(ネストされた構造体、関数シグネチャ、インターフェースなど)の場合、構造体フィールドを一つずつ比較するよりも、String() メソッドで生成された文字列を比較する方が、テストコードの記述が簡潔になり、可読性も向上します。
  3. 可視性の向上: 型システムがどのように型を解釈し、表現しているかを外部から確認するための標準的な手段を提供します。

マップ型作成時のバグ修正は、型システムが正しく動作するための基本的な修正であり、String() メソッドの実装と合わせて、exp/types パッケージの堅牢性を高めることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と、コンパイラ/型システムに関する基本的な知識が必要です。

Go言語の型システム

Go言語は静的型付け言語であり、プログラムのコンパイル時に厳密な型チェックが行われます。Goの型システムは、以下のような組み込み型と複合型をサポートしています。

  • 基本型 (Basic Types): int, float64, string, bool など。
  • 配列型 (Array Types): [N]T の形式で、固定長の要素のシーケンスを表します。
  • スライス型 (Slice Types): []T の形式で、可変長の要素のシーケンスを表します。配列の上に構築されます。
  • 構造体型 (Struct Types): struct { ... } の形式で、名前付きのフィールドのコレクションを表します。
  • ポインタ型 (Pointer Types): *T の形式で、T型の変数のメモリアドレスを指します。
  • 関数型 (Function Types): func(...) (...) の形式で、関数のシグネチャ(パラメータと戻り値の型)を表します。
  • インターフェース型 (Interface Types): interface { ... } の形式で、メソッドのシグネチャの集合を定義します。
  • マップ型 (Map Types): map[K]V の形式で、キーと値のペアのコレクションを表します。
  • チャネル型 (Channel Types): chan T, chan<- T, <-chan T の形式で、ゴルーチン間の通信に使用されます。

go/ast パッケージ

go/ast パッケージは、Go言語のソースコードを抽象構文木(AST: Abstract Syntax Tree)として表現するためのデータ構造を提供します。コンパイラはソースコードをパースしてASTを構築し、そのASTに対して型チェックやコード生成などの処理を行います。

  • ast.Expr: 抽象構文木における式を表すインターフェース。
  • ast.Object: 識別子(変数、関数、型など)が宣言されたオブジェクトを表す構造体。
  • ast.MapType: マップ型のリテラル(例: map[string]int)をASTで表現する構造体。
  • ast.ChanDir: チャネルの方向(送受信、送信のみ、受信のみ)を表す列挙型。

fmt.Stringer インターフェース

Go言語の標準ライブラリには fmt.Stringer というインターフェースが定義されています。

type Stringer interface {
    String() string
}

このインターフェースを実装する型は、fmt.Print(), fmt.Println(), fmt.Sprintf() などの fmt パッケージの関数によって、その型の値を文字列として出力する際に、自動的に String() メソッドが呼び出されます。これにより、カスタム型のデバッグ出力やログ出力が容易になります。

型チェックと型推論

コンパイラの型チェッカーは、プログラム内の各式や宣言の型を決定し、型規則に違反がないかを確認します。型推論は、明示的な型宣言がない場合でも、コンテキストから型を自動的に決定するプロセスです。exp/types パッケージは、このような型チェックと型推論のロジックを実装するための基盤を提供していました。

技術的詳細

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

  1. Type インターフェースへの String() メソッドの追加と実装:

    • types.go ファイルにおいて、Type インターフェースに String() string メソッドが追加されました。これにより、すべての具体的な型(Bad, Basic, Array, Slice, Struct, Pointer, Func, Interface, Map, Chan, Name)がこのメソッドを実装することが義務付けられます。
    • 以前は ImplementsType という構造体が埋め込まれていましたが、これは implementsType にリネームされ、プライベートなフィールドとして扱われるようになりました。これは、外部から直接アクセスされることを意図しない内部的な実装詳細であることを示唆しています。
    • 各型構造体には、その型をGo言語の構文に似た形式で表現する String() メソッドが追加されました。
      • Bad: badType(msg) の形式でエラーメッセージを含む。
      • Basic: basicType (TODO: 実際の型情報を出力するように改善予定)
      • Array: [Len]Elt の形式。
      • Slice: []Elt の形式。
      • Struct: struct{field1 type1; field2 type2 "tag"; ...} の形式。フィールド名、型、タグを含む。
      • Pointer: *Base の形式。
      • Func: func(params) (results) の形式。パラメータ名、型、可変長引数(...)を適切に表現。戻り値が1つで名前がない場合は括弧を省略。
      • Interface: interface{method1(); method2(...); ...} の形式。メソッド名とシグネチャを含む。メソッドはソートされて出力される。
      • Map: map[Key]Elt の形式。
      • Chan: chan Elt, <-chan Elt, chan<- Elt の形式でチャネルの方向を表現。
      • Name: 型が宣言されたオブジェクトの名前を出力。
    • bytes.Buffer を使用して効率的に文字列を構築しています。これは、多数の文字列連結を避けるためのGo言語の一般的なイディオムです。
  2. マップ型作成時のバグ修正:

    • check.go ファイルの makeType メソッド内で、ast.MapType を処理する部分にバグがありました。
    • 修正前: return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Key, true)}
    • 修正後: return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Value, true)}
    • このバグは、マップのキー型と要素型(値型)を両方ともキー型として誤って設定してしまうというものでした。修正により、要素型が正しく t.Value から取得されるようになりました。これは、map[Key]ValueValue 部分が正しく解釈されるようにするための重要な修正です。
  3. テストの改善:

    • types_test.go ファイルにおいて、既存の TestVariadicFunctions テストが削除され、より包括的な TestTypes テストに置き換えられました。
    • TestTypes は、様々なGo言語の型(基本型、配列、スライス、構造体、ポインタ、関数、インターフェース、マップ、チャネル)のソースコード表現と、それらの型が String() メソッドによって生成されるべき期待される文字列表現をペアにしたテーブル駆動テストです。
    • makePkg ヘルパー関数が導入され、与えられたソースコードからパッケージを生成し、型チェックを行う処理をカプセル化しています。
    • この新しいテストは、String() メソッドが正しく実装されていることを広範囲にわたって検証し、将来の変更に対するリグレッションを防ぐための強力な基盤を提供します。特に、構造体フィールドの順序、関数パラメータの表示、インターフェースメソッドのソートなど、String() メソッドの出力形式に関する詳細な仕様がテストによって保証されています。

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

src/pkg/exp/types/check.go

--- a/src/pkg/exp/types/check.go
+++ b/src/pkg/exp/types/check.go
@@ -168,7 +168,7 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
 		return &Interface{Methods: methods}
 
 	case *ast.MapType:
-		return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Key, true)}
+		return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Value, true)}
 
 	case *ast.ChanType:
 		return &Chan{Dir: t.Dir, Elt: c.makeType(t.Value, true)}

src/pkg/exp/types/types.go

--- a/src/pkg/exp/types/types.go
+++ b/src/pkg/exp/types/types.go
@@ -8,6 +8,8 @@
 package types
 
 import (
+	"bytes"
+	"fmt"
 	"go/ast"
 	"sort"
 )
@@ -15,43 +17,61 @@ import (
 // All types implement the Type interface.
 type Type interface {
 	isType()
+	String() string
 }
 
-// All concrete types embed ImplementsType which
+// All concrete types embed implementsType which
 // ensures that all types implement the Type interface.
-type ImplementsType struct{}
+type implementsType struct{}
 
-func (t *ImplementsType) isType() {}
+func (t *implementsType) isType() {}
 
 // A Bad type is a non-nil placeholder type when we don't know a type.
 type Bad struct {
-	ImplementsType
+	implementsType
 	Msg string // for better error reporting/debugging
 }
 
+func (t *Bad) String() string {
+	return fmt.Sprintf("badType(%s)", t.Msg)
+}
+
 // A Basic represents a (unnamed) basic type.
 type Basic struct {
-	ImplementsType
+	implementsType
 	// TODO(gri) need a field specifying the exact basic type
 }
 
+func (t *Basic) String() string {
+	// TODO(gri) print actual type information
+	return "basicType"
+}
+
 // An Array represents an array type [Len]Elt.
 type Array struct {
-	ImplementsType
+	implementsType
 	Len uint64
 	Elt Type
 }
 
+func (t *Array) String() string {
+	return fmt.Sprintf("[%d]%s", t.Len, t.Elt)
+}
+
 // A Slice represents a slice type []Elt.
 type Slice struct {
-	ImplementsType
+	implementsType
 	Elt Type
 }
 
+func (t *Slice) String() string {
+	return "[]" + t.Elt.String()
+}
+
 // A Struct represents a struct type struct{...}.
 // Anonymous fields are represented by objects with empty names.
 type Struct struct {
-	ImplementsType
+	implementsType
 	Fields ObjList  // struct fields; or nil
 	Tags   []string // corresponding tags; or nil
 	// TODO(gri) This type needs some rethinking:
@@ -60,49 +80,148 @@ type Struct struct {
 	// - there is no scope for fast lookup (but the parser creates one)
 }
 
+func (t *Struct) String() string {
+	buf := bytes.NewBufferString("struct{")
+	for i, fld := range t.Fields {
+		if i > 0 {
+			buf.WriteString("; ")
+		}
+		if fld.Name != "" {
+			buf.WriteString(fld.Name)
+			buf.WriteByte(' ')
+		}
+		buf.WriteString(fld.Type.(Type).String())
+		if i < len(t.Tags) && t.Tags[i] != "" {
+			fmt.Fprintf(buf, " %q", t.Tags[i])
+		}
+	}
+	buf.WriteByte('}')
+	return buf.String()
+}
+
 // A Pointer represents a pointer type *Base.
 type Pointer struct {
-	ImplementsType
+	implementsType
 	Base Type
 }
 
+func (t *Pointer) String() string {
+	return "*" + t.Base.String()
+}
+
 // A Func represents a function type func(...) (...).
 // Unnamed parameters are represented by objects with empty names.
 type Func struct {
-	ImplementsType
+	implementsType
 	Recv       *ast.Object // nil if not a method
 	Params     ObjList     // (incoming) parameters from left to right; or nil
 	Results    ObjList     // (outgoing) results from left to right; or nil
 	IsVariadic bool        // true if the last parameter's type is of the form ...T
 }
 
+func writeParams(buf *bytes.Buffer, params ObjList, isVariadic bool) {
+	buf.WriteByte('(')
+	for i, par := range params {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		if par.Name != "" {
+			buf.WriteString(par.Name)
+			buf.WriteByte(' ')
+		}
+		if isVariadic && i == len(params)-1 {
+			buf.WriteString("...")
+		}
+		buf.WriteString(par.Type.(Type).String())
+	}
+	buf.WriteByte(')')
+}
+
+func writeSignature(buf *bytes.Buffer, t *Func) {
+	writeParams(buf, t.Params, t.IsVariadic)
+	if len(t.Results) == 0 {
+		// no result
+		return
+	}
+
+	buf.WriteByte(' ')
+	if len(t.Results) == 1 && t.Results[0].Name == "" {
+		// single unnamed result
+		buf.WriteString(t.Results[0].Type.(Type).String())
+		return
+	}
+
+	// multiple or named result(s)
+	writeParams(buf, t.Results, false)
+}
+
+func (t *Func) String() string {
+	buf := bytes.NewBufferString("func")
+	writeSignature(buf, t)
+	return buf.String()
+}
+
 // An Interface represents an interface type interface{...}.
 type Interface struct {
-	ImplementsType
+	implementsType
 	Methods ObjList // interface methods sorted by name; or nil
 }
 
+func (t *Interface) String() string {
+	buf := bytes.NewBufferString("interface{")
+	for i, m := range t.Methods {
+		if i > 0 {
+			buf.WriteString("; ")
+		}
+		buf.WriteString(m.Name)
+		writeSignature(buf, m.Type.(*Func))
+	}
+	buf.WriteByte('}')
+	return buf.String()
+}
+
 // A Map represents a map type map[Key]Elt.
 type Map struct {
-	ImplementsType
+	implementsType
 	Key, Elt Type
 }
 
+func (t *Map) String() string {
+	return fmt.Sprintf("map[%s]%s", t.Key, t.Elt)
+}
+
 // A Chan represents a channel type chan Elt, <-chan Elt, or chan<-Elt.
 type Chan struct {
-	ImplementsType
+	implementsType
 	Dir ast.ChanDir
 	Elt Type
 }
 
+func (t *Chan) String() string {
+	var s string
+	switch t.Dir {
+	case ast.SEND:
+		s = "chan<- "
+	case ast.RECV:
+		s = "<-chan "
+	default:
+		s = "chan "
+	}
+	return s + t.Elt.String()
+}
+
 // A Name represents a named type as declared in a type declaration.
 type Name struct {
-	ImplementsType
+	implementsType
 	Underlying Type        // nil if not fully declared
 	Obj        *ast.Object // corresponding declared object
 	// TODO(gri) need to remember fields and methods.
 }
 
+func (t *Name) String() string {
+	return t.Obj.Name
+}
+
 // If typ is a pointer type, Deref returns the pointer's base type;
 // otherwise it returns typ.
 func Deref(typ Type) Type {

src/pkg/exp/types/types_test.go

--- a/src/pkg/exp/types/types_test.go
+++ b/src/pkg/exp/types/types_test.go
@@ -13,55 +13,116 @@ import (
 	"testing"
 )
 
-func checkSource(t *testing.T, src string) *ast.Package {
+func makePkg(t *testing.T, src string) (*ast.Package, error) {
 	const filename = "<src>"
 	file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
 	if err != nil {
-\t\tt.Fatal(err)
+		return nil, err
 	}
 	files := map[string]*ast.File{filename: file}
 	pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
 	if err != nil {
-\t\tt.Fatal(err)
+		return nil, err
 	}
-\t_, err = Check(fset, pkg)
-\tif err != nil {\n-\t\tt.Fatal(err)
+\tif _, err := Check(fset, pkg); err != nil {
+		return nil, err
 	}
-\treturn pkg
+	return pkg, nil
 }
 
-func TestVariadicFunctions(t *testing.T) {
-\tpkg := checkSource(t, `
-package p
-func f1(arg ...int)
-func f2(arg1 string, arg2 ...int)
-func f3()
-func f4(arg int)
-\t`)\n-\tf1 := pkg.Scope.Lookup("f1")
-\tf2 := pkg.Scope.Lookup("f2")
-\tfor _, f := range [...](*ast.Object){f1, f2} {
-\t\tftype := f.Type.(*Func)
-\t\tif !ftype.IsVariadic {
-\t\t\tt.Errorf("expected %s to be variadic", f.Name)
-\t\t}\n-\t\tparam := ftype.Params[len(ftype.Params)-1]
-\t\tif param.Type != Int {
-\t\t\tt.Errorf("expected last parameter of %s to have type int, found %T", f.Name, param.Type)
-\t\t}\n-\t}\n+type testEntry struct {
+	src, str string
+}
+
+// dup returns a testEntry where both src and str are the same.
+func dup(s string) testEntry {
+	return testEntry{s, s}
+}
+
+var testTypes = []testEntry{
+	// basic types
+	dup("int"),
+	dup("float32"),
+	dup("string"),
+
+	// arrays
+	{"[10]int", "[0]int"}, // TODO(gri) fix array length, add more array tests
+
+	// slices
+	dup("[]int"),
+	dup("[][]int"),
+
+	// structs
+	dup("struct{}"),
+	dup("struct{x int}"),
+	{`struct {
+		x, y int
+		z float32 "foo"
+	}`, `struct{x int; y int; z float32 "foo"}`},
+	{`struct {
+		string
+		elems []T
+	}`, `struct{string; elems []T}`},
+
+	// pointers
+	dup("*int"),
+	dup("***struct{}"),
+	dup("*struct{a int; b float32}"),
+
+	// functions
+	dup("func()"),
+	dup("func(x int)"),
+	{"func(x, y int)", "func(x int, y int)"},
+	{"func(x, y int, z string)", "func(x int, y int, z string)"},
+	dup("func(int)"),
+	dup("func(int, string, byte)"),
+
+	dup("func() int"),
+	{"func() (string)", "func() string"},
+	dup("func() (u int)"),
+	{"func() (u, v int, w string)", "func() (u int, v int, w string)"},
+
+	dup("func(int) string"),
+	dup("func(x int) string"),
+	dup("func(x int) (u string)"),
+	{"func(x, y int) (u string)", "func(x int, y int) (u string)"},
+
+	dup("func(...int) string"),
+	dup("func(x ...int) string"),
+	dup("func(x ...int) (u string)"),
+	{"func(x, y ...int) (u string)", "func(x int, y ...int) (u string)"},
+
+	// interfaces
+	dup("interface{}"),
+	dup("interface{m()}"),
+	{`interface{
+		m(int) float32
+		String() string
+	}`, `interface{String() string; m(int) float32}`}, // methods are sorted
+	// TODO(gri) add test for interface w/ anonymous field
+
+	// maps
+	dup("map[string]int"),
+	{"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"},
+
+	// channels
+	dup("chan int"),
+	dup("chan<- func()"),
+	dup("<-chan []func() int"),
+}
+
+func TestTypes(t *testing.T) {
+	for _, test := range testTypes {
+		src := "package p; type T " + test.src
+		pkg, err := makePkg(t, src)
+		if err != nil {
+			t.Errorf("%s: %s", src, err)
+			continue
+		}
+		typ := Underlying(pkg.Scope.Lookup("T").Type.(Type))
+		str := typ.String()
+		if str != test.str {
+			t.Errorf("%s: got %s, want %s", test.src, str, test.str)
+		}
+	}
+}

コアとなるコードの解説

src/pkg/exp/types/check.go の変更

このファイルでは、Go言語の型チェッカーが抽象構文木(AST)を走査し、各式の型を決定するロジックが含まれています。変更された箇所は makeType 関数内で ast.MapType を処理する部分です。

case *ast.MapType:
    return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Value, true)}

以前のコードでは、マップのキー型 (t.Key) を使ってキーと要素(値)の両方の型を生成していました。これは map[Key]Key のような誤った型を生成してしまうバグでした。修正後は、マップのキー型には t.Key を、要素型には t.Value を正しく使用するように変更されています。これにより、map[Key]Value のような正しいマップ型が構築されるようになりました。

src/pkg/exp/types/types.go の変更

このファイルは、exp/types パッケージにおけるGo言語の型を表現するデータ構造と、それらの型が満たすべきインターフェースを定義しています。

  1. Type インターフェースの拡張:

    type Type interface {
        isType()
        String() string // 新しく追加されたメソッド
    }
    

    Type インターフェースに String() string メソッドが追加されました。これにより、Type インターフェースを実装するすべての具体的な型(Bad, Basic, Array, Slice, Struct, Pointer, Func, Interface, Map, Chan, Name)は、このメソッドを実装しなければならなくなりました。

  2. implementsType のリネーム:

    -type ImplementsType struct{}
    +type implementsType struct{}
    
    -func (t *ImplementsType) isType() {}
    +func (t *implementsType) isType() {}
    

    ImplementsTypeimplementsType にリネームされました。これは、Go言語の慣習に従い、パッケージ外から直接アクセスされることを意図しない内部的なヘルパー構造体であることを示しています。この構造体は、各型構造体に埋め込まれることで、isType() メソッドを自動的に提供し、すべての型が Type インターフェースを満たすことを保証します。

  3. 各型構造体への String() メソッドの実装: 各型構造体(Bad, Basic, Array, Slice, Struct, Pointer, Func, Interface, Map, Chan, Name)に、それぞれの型をGo言語の構文に似た文字列として表現する String() メソッドが追加されました。

    • Bad: badType(msg) の形式で、デバッグメッセージを含む。
    • Basic: basicType と出力されますが、コメントに TODO(gri) print actual type information とあり、将来的にはより具体的な基本型(例: int, string)を出力するように改善される予定です。
    • Array: fmt.Sprintf("[%d]%s", t.Len, t.Elt) を使用して、[長さ]要素型 の形式で出力します。
    • Slice: [] と要素型の String() 結果を連結して []要素型 の形式で出力します。
    • Struct: bytes.Buffer を使用して struct{フィールド名 型; ...} の形式で文字列を構築します。タグも " で囲んで出力されます。
    • Pointer: * と基底型の String() 結果を連結して *基底型 の形式で出力します。
    • Func: writeParamswriteSignature というヘルパー関数を導入し、関数のパラメータ、戻り値、可変長引数を適切にフォーマットして func(params) (results) の形式で出力します。
    • Interface: bytes.Buffer を使用して interface{メソッド名(シグネチャ); ...} の形式で文字列を構築します。メソッドは名前でソートされて出力されます。
    • Map: fmt.Sprintf("map[%s]%s", t.Key, t.Elt) を使用して、map[キー型]要素型 の形式で出力します。
    • Chan: チャネルの方向(送受信、送信のみ、受信のみ)に応じて chan, chan<-, <-chan を適切に付加し、要素型と連結して出力します。
    • Name: 型が宣言されたオブジェクトの名前 (t.Obj.Name) を出力します。

これらの String() メソッドの実装により、exp/types パッケージで表現されるGo言語の型が、人間が読める形式で出力できるようになり、デバッグやテストの効率が向上しました。

src/pkg/exp/types/types_test.go の変更

このファイルは、exp/types パッケージの型システムが正しく動作するかを検証するためのテストコードを含んでいます。

  1. TestVariadicFunctions の削除と TestTypes の追加: 以前の TestVariadicFunctions は可変長引数関数のテストに特化していましたが、このコミットでは削除され、より広範な型をカバーする TestTypes に置き換えられました。

  2. makePkg ヘルパー関数の導入:

    func makePkg(t *testing.T, src string) (*ast.Package, error) { ... }
    

    この関数は、テスト用のGoソースコード文字列を受け取り、それをパースしてASTパッケージを生成し、型チェックを実行します。エラーが発生した場合は t.Fatal を呼び出す代わりにエラーを返すように変更され、呼び出し元でエラーハンドリングが可能になりました。

  3. テーブル駆動テスト testTypes:

    type testEntry struct {
        src, str string
    }
    
    func dup(s string) testEntry {
        return testEntry{s, s}
    }
    
    var testTypes = []testEntry{
        // ... テストケースの定義 ...
    }
    

    testTypestestEntry 構造体のスライスで、各エントリはテスト対象のGoソースコードの型定義 (src) と、その型が String() メソッドによって生成されるべき期待される文字列 (str) を含んでいます。dup ヘルパー関数は、srcstr が同じ場合に簡潔に記述するために使用されます。

    TestTypes 関数は、この testTypes スライスをイテレートし、各 testEntry に対して以下の処理を行います。

    • package p; type T のプレフィックスと test.src を結合して完全なソースコードを作成します。
    • makePkg を呼び出してパッケージを生成し、型チェックを実行します。
    • 生成されたパッケージから T という名前の型の Underlying 型(エイリアスを解決した基底型)を取得します。
    • 取得した型の String() メソッドを呼び出し、結果の文字列 (str) を test.str(期待される文字列)と比較します。
    • 一致しない場合は t.Errorf を呼び出してエラーを報告します。

この新しいテストスイートは、基本型、配列、スライス、構造体、ポインタ、関数、インターフェース、マップ、チャネルといったGo言語の主要な複合型を網羅しており、String() メソッドの出力がGo言語の構文規則に厳密に従っていることを検証します。特に、構造体フィールドの順序、関数パラメータの表示、インターフェースメソッドのソートなど、String() メソッドの出力形式に関する詳細な仕様がテストによって保証されています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に src/go/types パッケージの現在の実装)
  • Go言語のコンパイラ設計に関する一般的な情報源
  • bytes.Buffer の使用例に関するGo言語の慣習
  • テーブル駆動テストに関するGo言語のテストの慣習