[インデックス 1421] ファイルの概要
コミット
このコミットは、Go言語の組み込み関数 new
の挙動に関するバグを修正するものです。具体的には、new
関数が特定の型(特に関数型)に対して正しく動作しない問題を解決し、関連するテストケースを更新しています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/84953bdaa8e444952e4ef6ab7cf96c95e25d42dc
元コミット内容
commit 84953bdaa8e444952e4ef6ab7cf96c95e25d42dc
Author: Russ Cox <rsc@golang.org>
Date: Tue Jan 6 15:39:28 2009 -0800
fix newfn
R=ken
OCL=22173
CL=22173
---
src/cmd/gc/walk.c | 3 ++--
test/newfn.go | 5 ++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index 6c7c02838c..870d30a98a 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2078,13 +2078,14 @@ Node*
newcompat(Node *n)
{
Node *r, *on;
-\tType *t, *t0;\n+\tType *t;\n \n \tt = n->type;\n \tif(t == T)\n \t\tgoto bad;\n \n \tswitch(t->etype) {\n+\tcase TFUNC:\n \tcase TSTRING:\n \tcase TMAP:\n \tcase TCHAN:\ndiff --git a/test/newfn.go b/test/newfn.go
index fbbf942ce4..63df683ce6 100644
--- a/test/newfn.go
+++ b/test/newfn.go
@@ -10,5 +10,8 @@ func main()\n {\n \tf := new(());\t// ERROR \"new\"\n \tg := new((x int, f float) string);\t// ERROR \"new\"\n-\th := new(());\t// ok\n+\th := new(*());\t// ok\n+\ti := new(string);\t// ok\n+\tj := new(map[int]int);\t// ok\n+\tk := new(chan int);\t// ok\n }\n```
## 変更の背景
このコミットは、Go言語の初期開発段階(2009年1月)に行われたもので、Goコンパイラにおける `new` 組み込み関数の型チェックロジックの不備を修正することを目的としています。当時の `new` 関数は、特定の型(特に関数型)に対して誤ったエラーを報告するか、あるいは意図しない挙動を示す可能性がありました。この修正により、`new` 関数がGoの型システムと一貫性のある形で、より広範な型に対して正しく動作するようになります。
## 前提知識の解説
### Go言語の `new` 組み込み関数
Go言語には、メモリを割り当てるための2つの組み込み関数 `new` と `make` があります。
- `new(T)`: 型 `T` のゼロ値が格納されるメモリを割り当て、その型 `*T` のポインタを返します。`new` は、スライス、マップ、チャネル以外の任意の型に対して使用できます。割り当てられたメモリは、その型のゼロ値で初期化されます。
- `make(T, args)`: スライス、マップ、チャネルといった参照型を初期化するために使用されます。これらの型は、使用する前に内部データ構造を初期化する必要があるため、`new` では不十分です。
このコミットの時点では、Go言語はまだ非常に初期の段階であり、`new` 関数のセマンティクスやコンパイラの実装が固まりつつある時期でした。
### Goコンパイラの構造(初期)
このコミットが対象としている `src/cmd/gc/walk.c` は、Goコンパイラの重要な部分です。当時のGoコンパイラはC言語で書かれており、`gc` (Go Compiler) ディレクトリはコンパイラの主要なコンポーネントを含んでいました。
- `walk.c`: コンパイラの「ウォーカー」または「ASTトラバーサル」フェーズを担当するファイルの一つと考えられます。このフェーズでは、構文解析によって生成された抽象構文木 (AST) を走査し、型チェック、最適化、中間コード生成などの処理を行います。`newcompat` のような関数は、特定の操作(この場合は `new` 関数の引数の型チェック)が、Goの型システムと互換性があるかを確認する役割を担っていたと推測されます。
### Goの内部型表現
Goコンパイラ内部では、Go言語の各型が特定の内部表現(`etype`)を持っています。
- `TFUNC`: 関数型
- `TSTRING`: 文字列型
- `TMAP`: マップ型
- `TCHAN`: チャネル型
`newcompat` 関数内の `switch(t->etype)` は、`new` 関数に渡された引数の型がこれらの内部型表現のいずれであるかに応じて、異なる処理を行うことを示しています。
## 技術的詳細
このコミットの技術的な核心は、`src/cmd/gc/walk.c` 内の `newcompat` 関数における `switch` ステートメントの変更です。
`newcompat` 関数は、`new` 組み込み関数に渡される引数の型が、`new` で割り当て可能な型であるかどうかをチェックする役割を担っています。元のコードでは、`TSTRING` (文字列)、`TMAP` (マップ)、`TCHAN` (チャネル) の各型は `new` の引数として互換性があると見なされていましたが、`TFUNC` (関数型) は明示的にリストされていませんでした。
Go言語の `new` 関数は、任意の型 `T` に対して `*T` 型のポインタを返します。関数型もGoの有効な型であるため、`new(func())` のような式も有効であるべきです。しかし、`newcompat` 関数が `TFUNC` を互換性のある型として認識していなかったため、コンパイラは `new(func())` のようなコードに対して誤ったエラーを報告するか、あるいはコンパイル時に予期せぬ問題を引き起こしていました。
この修正により、`newcompat` 関数は `TFUNC` を `new` の有効な引数型として認識するようになり、`new` 関数が関数型に対しても正しく動作するようになりました。
`test/newfn.go` の変更は、この修正を検証するためのものです。
- `h := new(());` から `h := new(*());` への変更:
- `new(())` は、引数なし、戻り値なしの関数型 `func()` を表す可能性があり、これが以前は `newcompat` で正しく処理されていなかったためエラーになっていたと考えられます。
- `new(*())` は、ポインタ型 `*()` を表します。`()` が空の構造体型 `struct{}` を意味すると仮定すると、`*()` は `*struct{}` となり、これは `new` で割り当て可能な有効な型です。この変更は、`new` が関数型を正しく扱えるようになったことを確認しつつ、以前のテストケースが意図していた「`new` が特定の型に対して動作すること」をより明確に表現するための調整である可能性があります。
- `new(string);`, `new(map[int]int);`, `new(chan int);` の追加:
- これらのテストケースは、`new` 関数が基本的な型、マップ型、チャネル型に対しても期待通りに動作することを確認するために追加されました。これは、`newcompat` 関数がこれらの型を既にサポートしていたことを再確認するものであり、`new` 関数の包括的なテストカバレッジを向上させるものです。
## コアとなるコードの変更箇所
### `src/cmd/gc/walk.c`
```diff
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2078,13 +2078,14 @@ Node*
newcompat(Node *n)
{
Node *r, *on;
-\tType *t, *t0;\n+\tType *t;\n \n \tt = n->type;\n \tif(t == T)\n \t\tgoto bad;\n \n \tswitch(t->etype) {\n+\tcase TFUNC:\n \tcase TSTRING:\n \tcase TMAP:\n \tcase TCHAN:\n```
### `test/newfn.go`
```diff
--- a/test/newfn.go
+++ b/test/newfn.go
@@ -10,5 +10,8 @@ func main()\n {\n \tf := new(());\t// ERROR \"new\"\n \tg := new((x int, f float) string);\t// ERROR \"new\"\n-\th := new(());\t// ok\n+\th := new(*());\t// ok\n+\ti := new(string);\t// ok\n+\tj := new(map[int]int);\t// ok\n+\tk := new(chan int);\t// ok\n }\n```
## コアとなるコードの解説
### `src/cmd/gc/walk.c` の変更
- `Type *t0;` の削除: これは未使用の変数であったため削除されました。コードのクリーンアップです。
- `switch(t->etype)` に `case TFUNC:` の追加:
- `newcompat` 関数は、`new` 組み込み関数の引数として渡されたノード `n` の型 `t` をチェックします。
- `t->etype` は、Goの内部型表現(`TFUNC`, `TSTRING` など)を示します。
- 以前は `TFUNC` がこの `switch` ステートメントに含まれていなかったため、関数型が `new` の引数として渡された場合に、`newcompat` はその型を互換性がないと判断し、エラーを発生させていました。
- `case TFUNC:` を追加することで、関数型も `new` の有効な引数として扱われるようになり、コンパイラが正しく処理できるようになりました。これにより、`new(func())` のようなコードが有効になります。
### `test/newfn.go` の変更
- `h := new(()); // ok` から `h := new(*()); // ok` への変更:
- 最初の `new(())` は、おそらく引数なし、戻り値なしの関数型 `func()` を `new` に渡そうとしていたものです。このコミット以前は、これがコンパイルエラーになっていたか、あるいは意図しない挙動を示していました。
- 修正後、`new(*())` は `new` がポインタ型を正しく扱えることをテストしています。`()` が空の構造体 `struct{}` を意味すると仮定すると、`*()` は `*struct{}` となり、これは `new` で割り当て可能な有効な型です。この変更は、`new` が関数型を正しく扱えるようになったことと、ポインタ型も正しく扱えることを同時に確認する意図があったと考えられます。
- 新しいテストケースの追加:
- `i := new(string); // ok`
- `j := new(map[int]int); // ok`
- `k := new(chan int); // ok`
- これらの行は、`new` 関数が基本的な型 (`string`)、マップ型 (`map[int]int`)、チャネル型 (`chan int`) に対して期待通りに動作することを確認するために追加されました。これらは `newcompat` 関数が既にサポートしていた型ですが、テストカバレッジを強化し、`new` 関数の堅牢性を保証するために重要です。
## 関連リンク
- Go言語の `new` と `make` の違いに関する公式ドキュメント(現代のGo):[https://go.dev/doc/effective_go#allocation_new](https://go.dev/doc/effective_go#allocation_new)
- Go言語のソースコードリポジトリ:[https://github.com/golang/go](https://github.com/golang/go)
## 参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に `src/cmd/gc` ディレクトリの初期のコミット履歴)
- Gitの `diff` コマンドの出力解析