[インデックス 13503] ファイルの概要
このコミットは、Go言語の実験的な型チェッカーパッケージ exp/types
における2つの重要な改善を導入しています。一つは可変引数関数の型構築の修正であり、もう一つは抽象構文木(AST)の ast.Fun
オブジェクトの処理に関するものです。これにより、型チェッカーがより正確にGoの関数定義を解析し、特に可変引数関数の特性を正しく識別できるようになります。
コミット
commit d399b681a4d307fa5a09dd15c8cf96adeccb6db4
Author: Andrew Wilkins <axwalk@gmail.com>
Date: Thu Jul 26 11:47:46 2012 -0700
exp/types: process ast.Fun in checkObj; fix variadic function building
Fixed creation of Func's, taking IsVariadic from parameter list rather
than results.
Updated checkObj to process ast.Fun objects.
R=gri
CC=golang-dev
https://golang.org/cl/6402046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d399b681a4d307fa5a09dd15c8cf96adeccb6db4
元コミット内容
exp/types: process ast.Fun in checkObj; fix variadic function building
Fixed creation of Func's, taking IsVariadic from parameter list rather
than results.
Updated checkObj to process ast.Fun objects.
R=gri
CC=golang-dev
https://golang.org/cl/6402046
変更の背景
この変更は、Go言語の型システムが進化する中で、特に可変引数関数(variadic functions)の正確な型表現と、関数宣言の抽象構文木(AST)表現である ast.Fun
オブジェクトの適切な処理を保証するために行われました。
Go言語では、関数が可変引数を持つかどうかは、その引数リストの最後のパラメータが ...Type
の形式であるかによって決まります。しかし、以前の実装では、この IsVariadic
の情報が関数の結果(戻り値)リストから誤って取得されていました。これは、可変引数関数の型を正しく構築する上で根本的な誤りであり、コンパイル時や実行時の予期せぬ動作につながる可能性がありました。
また、Goのコンパイラやツールは、ソースコードを解析して抽象構文木(AST)を構築します。ast.Fun
は、関数リテラル(無名関数)や関数宣言を表すASTノードの一種です。型チェッカーは、これらのASTノードを走査し、それぞれの要素に適切な型情報を付与する必要があります。以前の checkObj
関数は、ast.Fun
オブジェクトを適切に処理していなかったため、関数リテラルや特定の関数宣言の型チェックが不完全であった可能性があります。
これらの問題に対処し、exp/types
パッケージの堅牢性と正確性を向上させることが、このコミットの背景にあります。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。
- Go言語の型システム: Goは静的型付け言語であり、すべての変数、関数、式には型があります。型チェッカーは、プログラムが型の規則に従っていることを検証します。
- 可変引数関数 (Variadic Functions): Goでは、関数の最後のパラメータの型に
...
を付けることで、任意の数の引数を受け取ることができます。例えば、func sum(nums ...int)
は任意の数のint
型の引数を受け取ります。関数内部では、可変引数はスライスとして扱われます。 - 抽象構文木 (Abstract Syntax Tree, AST): コンパイラは、ソースコードを解析(パース)して、プログラムの構造を木構造で表現したASTを生成します。ASTは、プログラムの意味を理解し、型チェックやコード生成などの後続の処理を行うための基盤となります。
go/ast
パッケージ: Goの標準ライブラリに含まれるパッケージで、GoソースコードのASTを表現するための型と関数を提供します。ast.FuncType
: 関数の型(引数と戻り値の型)を表すASTノードです。ast.FuncDecl
: 関数宣言(func Name(...) ... { ... }
)を表すASTノードです。ast.Fun
:ast.FuncDecl
やast.FuncLit
(関数リテラル) のような、関数に関連するASTノードを抽象的に指すために使われることがあります。この文脈では、ast.FuncDecl
を指している可能性が高いです。ast.Object
: Goのプログラムにおける名前付きエンティティ(変数、関数、型など)を表す抽象的なオブジェクトです。ASTノードと型情報を関連付けるために使用されます。
exp/types
パッケージ: これはGo言語の標準ライブラリの一部ではありませんが、Goの型チェッカーの実験的な実装、または将来の型チェッカーのプロトタイプとして開発されていたパッケージです。Goのコンパイラ開発において、新しい型システムや型チェックのロジックを試すために使用されます。このパッケージは、Goのソースコードを解析し、ASTを走査して、各要素の型を決定し、型エラーを検出する役割を担います。checkObj
関数:exp/types
パッケージ内の主要な関数の一つで、特定のast.Object
(名前付きエンティティ)の型をチェックし、その型情報を解決する役割を担います。
技術的詳細
このコミットは、src/pkg/exp/types/check.go
と src/pkg/exp/types/types_test.go
の2つのファイルにわたる変更を含んでいます。
src/pkg/exp/types/check.go
の変更点
-
可変引数関数の
IsVariadic
フラグの修正:func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type)
メソッド内の*ast.FuncType
のケースが修正されました。- 以前は、
c.collectFields(token.FUNC, t.Results, true)
の戻り値からisVariadic
を取得していました。これは誤りです。 - 修正後は、
c.collectFields(token.FUNC, t.Params, true)
の戻り値からisVariadic
を取得するように変更されました。これにより、可変引数関数のIsVariadic
フラグが、引数リストに基づいて正しく設定されるようになりました。
-
checkObj
関数におけるast.Fun
の処理の追加:func (c *checker) checkObj(obj *ast.Object, ref bool)
メソッドに、case ast.Fun:
の新しい処理ブロックが追加されました。- このブロックでは、
obj.Decl
が*ast.FuncDecl
型であることを前提とし、関数宣言のASTノードを処理します。 c.makeType(fdecl.Type, ref).(*Func)
を呼び出して関数の型(*Func
オブジェクト)を構築し、それをobj.Type
に割り当てます。- レシーバ(メソッドの定義)が存在する場合 (
fdecl.Recv != nil
)、レシーバのフィールドを処理し、ftyp.Recv
にレシーバのast.Object
を設定します。レシーバ名がない場合は、_
という名前のダミーのast.Object
を作成します。 - これにより、
checkObj
が関数宣言の型情報を正しく抽出し、関連する型オブジェクトに設定できるようになりました。
src/pkg/exp/types/types_test.go
の追加
- このコミットでは、
exp/types
パッケージの型チェックロジックを検証するための新しいテストファイルtypes_test.go
が追加されました。 - 特に、
TestVariadicFunctions
というテスト関数が追加され、可変引数関数の型が正しく識別されることを確認しています。 - このテストでは、
func f1(arg ...int)
やfunc f2(arg1 string, arg2 ...int)
のような可変引数関数と、func f3()
やfunc f4(arg int)
のような通常の関数を定義したソースコードをcheckSource
ヘルパー関数で型チェックします。 - その後、
pkg.Scope.Lookup
を使用して各関数のast.Object
を取得し、そのType
が*Func
型であり、IsVariadic
フラグが期待通りに設定されているか、そして最後のパラメータの型が正しいか(可変引数の場合は基底型)を検証します。 - このテストの追加により、可変引数関数の型構築の修正が正しく機能していることが保証されます。
コアとなるコードの変更箇所
src/pkg/exp/types/check.go
--- a/src/pkg/exp/types/check.go
+++ b/src/pkg/exp/types/check.go
@@ -158,8 +158,8 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
return &Struct{Fields: fields, Tags: tags}
case *ast.FuncType:
- params, _, _ := c.collectFields(token.FUNC, t.Params, true)
- results, _, isVariadic := c.collectFields(token.FUNC, t.Results, true)
+ params, _, isVariadic := c.collectFields(token.FUNC, t.Params, true)
+ results, _, _ := c.collectFields(token.FUNC, t.Results, true)
return &Func{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic}
case *ast.InterfaceType:
@@ -200,7 +200,21 @@ func (c *checker) checkObj(obj *ast.Object, ref bool) {
// TODO(gri) complete this
case ast.Fun:
- // TODO(gri) complete this
+ fdecl := obj.Decl.(*ast.FuncDecl)
+ ftyp := c.makeType(fdecl.Type, ref).(*Func)
+ obj.Type = ftyp
+ if fdecl.Recv != nil {
+ recvField := fdecl.Recv.List[0]
+ if len(recvField.Names) > 0 {
+ ftyp.Recv = recvField.Names[0].Obj
+ } else {
+ ftyp.Recv = ast.NewObj(ast.Var, "_")
+ ftyp.Recv.Decl = recvField
+ }
+ c.checkObj(ftyp.Recv, ref)
+ // TODO(axw) add method to a list in the receiver type.
+ }
+ // TODO(axw) check function body, if non-nil.
default:
panic("unreachable")
src/pkg/exp/types/types_test.go
(新規ファイル)
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests verifying the types associated with an AST after
// type checking.
package types
import (
"go/ast"
"go/parser"
"testing"
)
func checkSource(t *testing.T, src string) *ast.Package {
const filename = "<src>"
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
}
files := map[string]*ast.File{filename: file}
pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
if err != nil {
t.Fatal(err)
}
_, err = Check(fset, pkg)
if err != nil {
t.Fatal(err)
}
return pkg
}
func TestVariadicFunctions(t *testing.T) {
pkg := checkSource(t, `
package p
func f1(arg ...int)
func f2(arg1 string, arg2 ...int)
func f3()
func f4(arg int)
`)
f1 := pkg.Scope.Lookup("f1")
f2 := pkg.Scope.Lookup("f2")
for _, f := range [...](*ast.Object){f1, f2} {
ftype := f.Type.(*Func)
if !ftype.IsVariadic {
t.Errorf("expected %s to be variadic", f.Name)
}
param := ftype.Params[len(ftype.Params)-1]
if param.Type != Int {
t.Errorf("expected last parameter of %s to have type int, found %T", f.Name, param.Type)
}
}
f3 := pkg.Scope.Lookup("f3")
f4 := pkg.Scope.Lookup("f4")
for _, f := range [...](*ast.Object){f3, f4} {
ftype := f.Type.(*Func)
if ftype.IsVariadic {
t.Fatalf("expected %s to not be variadic", f.Name)
}
}
// TODO(axw) replace this function's innards with table driven tests.
// We should have a helper function that prints a type signature. Then
// we can have a table of function declarations and expected type
// signatures which can be easily expanded.
}
コアとなるコードの解説
src/pkg/exp/types/check.go
の変更点
-
makeType
関数における*ast.FuncType
の処理:makeType
関数は、ASTノードからGoの型システムにおける型オブジェクト(Type
インターフェースを実装する構造体)を構築する役割を担っています。*ast.FuncType
のケースは、関数型(引数と戻り値の型)を処理します。- 変更前は、
isVariadic
フラグ(関数が可変引数であるかを示す)をresults
(戻り値)から取得していました。これは論理的に誤りです。可変引数は常に引数リストの最後の要素として定義されます。 - 変更後、
isVariadic
はparams
(引数)から取得されるようになりました。これにより、Func
型オブジェクトが可変引数に関する正しい情報を保持するようになります。
-
checkObj
関数におけるast.Fun
の処理:checkObj
関数は、ast.Object
(名前付きエンティティ、例えば関数、変数、型など)の型をチェックし、その型情報を解決します。ast.Fun
は、関数宣言(ast.FuncDecl
)や関数リテラル(ast.FuncLit
)のような関数関連のASTノードを抽象的に表すために使用されます。このコミットでは、ast.FuncDecl
の処理に焦点を当てています。- 新しい
case ast.Fun:
ブロックでは、まずobj.Decl
を*ast.FuncDecl
に型アサートし、関数宣言のASTノードを取得します。 - 次に、
c.makeType(fdecl.Type, ref)
を呼び出して、この関数宣言の型(*Func
オブジェクト)を構築します。この*Func
オブジェクトには、引数、戻り値、そして可変引数であるかどうかの情報が含まれます。 - 構築された
*Func
オブジェクトは、obj.Type
に割り当てられ、これによりast.Object
にその関数の型情報が関連付けられます。 - さらに、関数がメソッドである場合(レシーバを持つ場合)、レシーバの情報を抽出し、
ftyp.Recv
に設定します。これにより、メソッドの型チェックも適切に行えるようになります。レシーバ名が明示されていない場合は、_
という名前のダミーのast.Object
が作成されます。 - これらの変更により、型チェッカーはGoの関数宣言をより完全に解析し、その型情報を正確に表現できるようになりました。
src/pkg/exp/types/types_test.go
の追加
- この新しいテストファイルは、
exp/types
パッケージの型チェックの正確性を保証するために不可欠です。 checkSource
ヘルパー関数は、与えられたGoのソースコード文字列をパースし、exp/types
パッケージのCheck
関数で型チェックを実行します。これにより、テスト対象のコードが型チェックプロセスを通過し、ASTに正しい型情報が付与されていることを確認できます。TestVariadicFunctions
は、特に可変引数関数の型チェックに焦点を当てています。f1
とf2
は可変引数関数として定義されており、テストではこれらの関数のIsVariadic
フラグがtrue
であること、そして最後のパラメータの型が期待通りであることを検証します。f3
とf4
は通常の関数として定義されており、テストではこれらの関数のIsVariadic
フラグがfalse
であることを検証します。
- このテストの追加により、
makeType
関数におけるIsVariadic
フラグの修正が正しく機能していることが自動的に検証されるようになり、将来の回帰を防ぐことができます。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のASTパッケージ (
go/ast
): https://pkg.go.dev/go/ast - Go言語のパーサーパッケージ (
go/parser
): https://pkg.go.dev/go/parser - Go言語の型パッケージ (
go/types
): https://pkg.go.dev/go/types (このコミットのexp/types
は実験的なものであり、最終的にはgo/types
に統合またはその基盤となった可能性があります。) - Go Code Review Comments (Goのコードレビューガイドライン): https://github.com/golang/go/wiki/CodeReviewComments
参考にした情報源リンク
- Goのコミットメッセージに記載されているGoのコードレビューシステムへのリンク: https://golang.org/cl/6402046
- Go言語の可変引数関数に関する公式ブログ記事やドキュメント (一般的な情報源として):
- The Go Programming Language Specification - Function types: https://go.dev/ref/spec#Function_types
- Effective Go - Variadic functions: https://go.dev/doc/effective_go#variadic
- GoのコンパイラとASTに関する一般的な情報源 (例: Goのコンパイラ設計に関する論文やブログ記事など)
- Go compiler internals: https://go.dev/blog/go1.5-compiler (これは後のバージョンに関するものですが、コンパイラの概念を理解するのに役立ちます)
- Goの型チェッカーに関する情報源 (例:
go/types
パッケージの設計ドキュメントなど)go/types
package documentation: https://pkg.go.dev/go/types