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

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

このコミットは、Go言語の型チェッカー(go/typesパッケージ)における組み込み関数printおよびprintlnの挙動を変更し、これらの関数が0個以上の引数を受け入れるように修正するものです。以前は、これらの関数は少なくとも1つの引数を必要としていました。この変更により、引数なしでprint()println()を呼び出すことが可能になり、例えば単に改行を出力したい場合などに便利になります。

コミット

commit 27970af5c9a90e1b50026b4ff003064ff9d04920
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Feb 12 19:40:20 2013 -0800

    go/types: print, println accept 0 or more arguments
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/7304089

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

https://github.com/golang/go/commit/27970af5c9a90e1b50026b4ff003064ff9d04920

元コミット内容

go/types: print, println accept 0 or more arguments

このコミットは、Go言語の型システムにおいて、組み込み関数であるprintprintlnが0個以上の引数を受け入れるように変更します。

変更の背景

Go言語の初期のバージョンでは、printprintlnはデバッグや簡単な出力のために提供される組み込み関数であり、少なくとも1つの引数を必要としていました。しかし、他の多くの言語の類似機能(例えばC言語のprintf("\n")やPythonのprint())と同様に、引数なしで呼び出して単に改行を出力したり、特定の値を伴わないデバッグメッセージを出力したりするユースケースが存在します。

このコミットは、このような一般的な使用パターンに対応し、printprintlnの柔軟性を高めることを目的としています。これにより、Go言語のユーザーはより直感的にこれらの関数を使用できるようになります。また、型チェッカーがこれらの呼び出しを正しく処理できるように、内部的な定義とチェックロジックが更新されています。

前提知識の解説

Go言語の組み込み関数 (builtins)

Go言語には、言語仕様の一部として定義され、特別な意味を持ついくつかの組み込み関数があります。これらはimportすることなく直接使用できます。make, new, len, cap, append, panic, print, printlnなどがその例です。これらの関数はコンパイラによって特別に扱われ、その型チェックやコード生成は通常の関数とは異なるロジックで行われます。

go/typesパッケージ

go/typesパッケージは、Go言語のコンパイラツールチェーンの一部であり、Goプログラムの型チェックを行うためのライブラリです。このパッケージは、ソースコードを解析し、各式の型を決定し、型の一貫性や正当性を検証します。例えば、関数呼び出しの引数の数や型が、その関数の定義と一致しているかなどをチェックします。

ast (Abstract Syntax Tree)

Goコンパイラは、ソースコードを解析する際に、その構造を抽象構文木(AST)として表現します。ast.CallExprは、AST内で関数呼び出しを表すノードです。型チェッカーは、このASTを走査し、各ノードに対応する型情報を付与したり、型エラーを検出したりします。

operand構造体

go/typesパッケージ内部で使用されるoperand構造体は、式の結果を表すために用いられます。これには、式のモード(例: variable, value, novalue, invalidなど)と型情報が含まれます。

  • novalue: 式が値を生成しないことを示します(例: panicprintのような副作用を持つ関数)。
  • invalid: 式が型エラーを含んでいることを示します。

技術的詳細

このコミットは、主に以下の3つのファイルにわたる変更を通じて、printprintlnの引数処理を修正しています。

  1. src/pkg/go/types/universe.go:

    • このファイルは、Go言語の「ユニバースブロック」に存在する、事前に宣言された(組み込みの)関数や型を定義しています。
    • predeclaredFunctionsという配列内で、_Print_Printlnの定義が更新されています。
    • 変更前: {_Print, "print", 1, true, true} および {_Println, "println", 1, true, true}
    • 変更後: {_Print, "print", 0, true, true} および {_Println, "println", 0, true, true}
    • ここで変更された1から0への数値は、その関数が受け入れる最小引数(minArgs)を示しています。これにより、型チェッカーはprintprintlnが0個の引数でも有効であると認識するようになります。
  2. src/pkg/go/types/builtins.go:

    • このファイルには、組み込み関数の呼び出しを型チェックするためのロジックが含まれています。
    • 変更点1: builtinメソッド内のswitch id文において、_Print_Println_Make, _New, _Traceと同じケースに追加されました。これは、これらの関数が引数の数に関してより柔軟な扱いを受けることを示唆しています。
    • 変更点2: _Panic, _Print, _Printlnを処理するcaseブロックが修正されました。
      • _Panicの処理が分離され、x.mode = novalueが直接設定されるようになりました。これは、panicが常に値を返さないことを明示しています。
      • _Print_Printlnの引数処理ループがfor _, arg := range args[1:]からfor _, arg := range argsに変更されました。
        • 元のコードではargs[1:](スライス)を使用していたため、最初の引数(args[0])はスキップされ、関数が少なくとも1つの引数を持つことを前提としていました。
        • 新しいコードではargs全体をイテレートするため、引数が0個の場合でもループが正しく動作し、引数がない呼び出しが有効になります。
      • ループ内にif x.mode == invalid { goto Error }というチェックが追加されました。これは、printprintlnの引数の中に型エラーがある場合、そのエラーを適切に伝播させるためのものです。
  3. src/pkg/go/types/testdata/builtins.src:

    • このファイルは、go/typesパッケージの組み込み関数に対するテストケースを含んでいます。
    • 新しいテストケースが追加され、_print()_println()が引数なしで呼び出されるシナリオが検証されています。
    • また、_panic()が引数なしや複数引数で呼び出された場合にエラーとなることを確認するテストも追加されており、panicの引数制約(常に1つの引数)が維持されていることを示しています。

これらの変更により、Goコンパイラの型チェッカーは、print()println()のような引数なしの呼び出しを有効な構文として認識し、コンパイルエラーを発生させなくなります。

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

src/pkg/go/types/builtins.go

--- a/src/pkg/go/types/builtins.go
+++ b/src/pkg/go/types/builtins.go
@@ -41,7 +41,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
 	if n > 0 {
 		arg0 = args[0]
 		switch id {
-		case _Make, _New, _Trace:
+		case _Make, _New, _Print, _Println, _Trace:
 			// respective cases below do the work
 		default:
 			// argument must be an expression
@@ -301,9 +301,15 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota
 		x.mode = variable
 		x.typ = &Pointer{Base: resultTyp}
 
-	case _Panic, _Print, _Println:
-		for _, arg := range args[1:] {
+	case _Panic:
+		x.mode = novalue
+
+	case _Print, _Println:
+		for _, arg := range args {
 			check.expr(x, arg, nil, -1)
+			if x.mode == invalid {
+				goto Error
+			}
 		}
 		x.mode = novalue
 

src/pkg/go/types/testdata/builtins.src

--- a/src/pkg/go/types/testdata/builtins.src
+++ b/src/pkg/go/types/testdata/builtins.src
@@ -214,6 +214,32 @@ func _new() {
 	new /* ERROR "not used" */ (int)
 }
 
+func _panic() {
+	panic /* ERROR "arguments" */ ()
+	panic /* ERROR "arguments" */ (1, 2)
+	panic(0)
+	panic("foo")
+	panic(false)
+}
+
+func _print() {
+	print()
+	print(1)
+	print(1, 2)
+	print("foo")
+	print(2.718281828)
+	print(false)
+}
+
+func _println() {
+	println()
+	println(1)
+	println(1, 2)
+	println("foo")
+	println(2.718281828)
+	println(false)
+}
+
 func _real() {
 	var f32 float32
 	var f64 float64

src/pkg/go/types/universe.go

--- a/src/pkg/go/types/universe.go
+++ b/src/pkg/go/types/universe.go
@@ -73,8 +73,8 @@ var predeclaredFunctions = [...]*builtin{
 	{_Make, "make", 1, true, false},
 	{_New, "new", 1, false, false},
 	{_Panic, "panic", 1, false, true},
-	{_Print, "print", 1, true, true},
-	{_Println, "println", 1, true, true},
+	{_Print, "print", 0, true, true},
+	{_Println, "println", 0, true, true},
 	{_Real, "real", 1, false, false},
 	{_Recover, "recover", 0, false, true},
 

コアとなるコードの解説

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

  • case _Make, _New, _Print, _Println, _Trace: の追加:

    • これは、printprintlnが、make, new, traceと同様に、引数の数や型に関して特別な処理を必要とする組み込み関数として認識されるようにするための変更です。これにより、これらの関数が可変引数を受け入れる可能性が示唆されます。
  • case _Panic: の分離と case _Print, _Println: の変更:

    • 以前は_Panic, _Print, _Printlnが同じcaseで処理されていましたが、_Panicが分離されました。これは、panicが常に1つの引数を必要とするのに対し、printprintlnは引数の数が可変になるため、処理を分ける必要があったためです。
    • _Print_Printlnのループがfor _, arg := range args[1:]からfor _, arg := range argsに変更された点が最も重要です。
      • args[1:]は、引数リストの2番目以降の要素を処理することを意味していました。つまり、少なくとも1つの引数(args[0])が存在することを前提としていました。
      • argsは、引数リスト全体を処理することを意味します。これにより、argsが空(引数が0個)の場合でもループが正しく終了し、エラーなく処理が続行されるようになります。
    • if x.mode == invalid { goto Error } の追加は、printprintlnに渡された引数自体が型エラーを含んでいる場合に、そのエラーを適切に伝播させるための堅牢性向上です。

src/pkg/go/types/testdata/builtins.src の変更

  • このファイルは、go/typesパッケージのテストデータとして機能します。
  • _print()_println()の引数なしの呼び出しが追加され、これがエラーにならないことを確認しています。
  • _print(1), _print(1, 2), _print("foo")などの様々な引数パターンもテストされており、これらの関数が引き続き単一または複数の引数を受け入れることを確認しています。
  • _panic()のテストケースも更新され、引数なしや複数引数でのpanic呼び出しがコンパイルエラーになることを明示的にテストしています。これにより、panicの引数制約が維持されていることが確認されます。

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

  • このファイルは、Go言語の組み込み関数に関するメタデータを提供します。
  • _Print_Printlnの定義における3番目のフィールド(minArgs)が1から0に変更されました。
  • このminArgsフィールドは、その組み込み関数が受け入れる最小の引数の数を型チェッカーに伝えます。0に設定することで、printprintlnが引数なしで呼び出されても型エラーにならないことが保証されます。

これらの変更が連携することで、Go言語の型チェッカーはprint()println()のような引数なしの呼び出しを正しく処理し、コンパイルエラーを発生させなくなります。

関連リンク

参考にした情報源リンク