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

[インデックス 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言語およびコンパイラの基本的な概念を理解しておく必要があります。

  1. Go言語の型システム: Goは静的型付け言語であり、すべての変数、関数、式には型があります。型チェッカーは、プログラムが型の規則に従っていることを検証します。
  2. 可変引数関数 (Variadic Functions): Goでは、関数の最後のパラメータの型に ... を付けることで、任意の数の引数を受け取ることができます。例えば、func sum(nums ...int) は任意の数の int 型の引数を受け取ります。関数内部では、可変引数はスライスとして扱われます。
  3. 抽象構文木 (Abstract Syntax Tree, AST): コンパイラは、ソースコードを解析(パース)して、プログラムの構造を木構造で表現したASTを生成します。ASTは、プログラムの意味を理解し、型チェックやコード生成などの後続の処理を行うための基盤となります。
    • go/ast パッケージ: Goの標準ライブラリに含まれるパッケージで、GoソースコードのASTを表現するための型と関数を提供します。
    • ast.FuncType: 関数の型(引数と戻り値の型)を表すASTノードです。
    • ast.FuncDecl: 関数宣言(func Name(...) ... { ... })を表すASTノードです。
    • ast.Fun: ast.FuncDeclast.FuncLit (関数リテラル) のような、関数に関連するASTノードを抽象的に指すために使われることがあります。この文脈では、ast.FuncDecl を指している可能性が高いです。
    • ast.Object: Goのプログラムにおける名前付きエンティティ(変数、関数、型など)を表す抽象的なオブジェクトです。ASTノードと型情報を関連付けるために使用されます。
  4. exp/types パッケージ: これはGo言語の標準ライブラリの一部ではありませんが、Goの型チェッカーの実験的な実装、または将来の型チェッカーのプロトタイプとして開発されていたパッケージです。Goのコンパイラ開発において、新しい型システムや型チェックのロジックを試すために使用されます。このパッケージは、Goのソースコードを解析し、ASTを走査して、各要素の型を決定し、型エラーを検出する役割を担います。
  5. checkObj 関数: exp/types パッケージ内の主要な関数の一つで、特定の ast.Object(名前付きエンティティ)の型をチェックし、その型情報を解決する役割を担います。

技術的詳細

このコミットは、src/pkg/exp/types/check.gosrc/pkg/exp/types/types_test.go の2つのファイルにわたる変更を含んでいます。

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

  1. 可変引数関数の 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 フラグが、引数リストに基づいて正しく設定されるようになりました。
  2. 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 の変更点

  1. makeType 関数における *ast.FuncType の処理:

    • makeType 関数は、ASTノードからGoの型システムにおける型オブジェクト(Type インターフェースを実装する構造体)を構築する役割を担っています。
    • *ast.FuncType のケースは、関数型(引数と戻り値の型)を処理します。
    • 変更前は、isVariadic フラグ(関数が可変引数であるかを示す)を results(戻り値)から取得していました。これは論理的に誤りです。可変引数は常に引数リストの最後の要素として定義されます。
    • 変更後、isVariadicparams(引数)から取得されるようになりました。これにより、Func 型オブジェクトが可変引数に関する正しい情報を保持するようになります。
  2. 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 は、特に可変引数関数の型チェックに焦点を当てています。
    • f1f2 は可変引数関数として定義されており、テストではこれらの関数の IsVariadic フラグが true であること、そして最後のパラメータの型が期待通りであることを検証します。
    • f3f4 は通常の関数として定義されており、テストではこれらの関数の IsVariadic フラグが false であることを検証します。
  • このテストの追加により、makeType 関数における IsVariadic フラグの修正が正しく機能していることが自動的に検証されるようになり、将来の回帰を防ぐことができます。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージに記載されているGoのコードレビューシステムへのリンク: https://golang.org/cl/6402046
  • Go言語の可変引数関数に関する公式ブログ記事やドキュメント (一般的な情報源として):
  • GoのコンパイラとASTに関する一般的な情報源 (例: Goのコンパイラ設計に関する論文やブログ記事など)
    • Go compiler internals: https://go.dev/blog/go1.5-compiler (これは後のバージョンに関するものですが、コンパイラの概念を理解するのに役立ちます)
  • Goの型チェッカーに関する情報源 (例: go/types パッケージの設計ドキュメントなど)