[インデックス 1420] ファイルの概要
このコミットは、Go言語の初期のパーサーの一部である usr/gri/pretty/parser.go
ファイルに対する変更です。このファイルは、Go言語のソースコードを解析し、抽象構文木(AST)を構築する役割を担っていたと考えられます。特に、関数呼び出しの解析、中でも new
や make
といった組み込み関数の特殊な解析ロジックに関連する部分が修正されています。
コミット
- fix parse heuristic: make(x) must accept a type for x
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e2862606866b5618f6726579db2b1dd923c3469a
元コミット内容
commit e2862606866b5618f6726579db2b1dd923c3469a
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 6 15:30:26 2009 -0800
- fix parse heuristic: make(x) must accept a type for x
R=r
OCL=22171
CL=22171
変更の背景
このコミットは、Go言語のパーサーにおける make
関数の解析ロジックの修正を目的としています。Go言語には new
と make
という2つの組み込み関数があり、それぞれ異なる目的で使用されます。
new(T)
: 型T
のゼロ値が格納された新しい項目を割り当て、その項目へのポインタを返します。make(T, args)
: スライス、マップ、チャネルといった組み込みのデータ構造を初期化し、使用可能な状態にします。これらの型は参照型であり、new
で割り当てられただけではゼロ値(nil)であり、そのままでは使用できません。make
は、これらの型に必要な内部データ構造を割り当て、初期化します。
コミットメッセージにある「fix parse heuristic: make(x) must accept a type for x」という記述から、以前のパーサーのヒューリスティック(経験則に基づいた解析ルール)では、make
関数の引数として型が正しく認識されていなかった、あるいは new
関数と同様の扱いがされていなかったことが示唆されます。
new(T)
の場合、引数 T
は常に型です。しかし、make
の場合、make([]int, 10)
のように最初の引数は型であり、その後に容量などの引数が続くことがあります。このコミット以前は、パーサーが make
の引数を new
と同じように型として適切に解析できていなかったため、make
を使用したコードが正しくコンパイルされない、あるいは意図しない解析結果になる問題があったと考えられます。この修正は、make
関数の引数解析を new
関数と同様に、最初の引数を型として扱うように拡張することで、この問題を解決しています。
前提知識の解説
1. Go言語の new
と make
関数
Go言語のメモリ割り当てと初期化には、new
と make
という2つの組み込み関数が使われます。
-
new(Type)
:- 任意の型
Type
のゼロ値を格納するためのメモリを割り当てます。 - 割り当てられたメモリへのポインタ(
*Type
)を返します。 - 例:
p := new(int)
はint
型のゼロ値(0)を格納するメモリを割り当て、そのアドレスをp
に代入します。p
は*int
型になります。 - スライス、マップ、チャネルなどの参照型に対して
new
を使うと、それらはnil
ポインタとして初期化され、そのままでは使用できません。
- 任意の型
-
make(Type, size, capacity)
:- スライス、マップ、チャネルといった特定の組み込み参照型のために設計されています。
- これらの型に必要な内部データ構造を割り当て、初期化し、使用可能な状態にします。
- ポインタではなく、初期化された型の値を返します。
- 例:
s := make([]int, 5, 10)
は、長さ5、容量10のint
型のスライスを作成し、s
に代入します。s
は[]int
型になります。 make
は、スライス、マップ、チャネルの内部構造(例えば、スライスの場合は基盤となる配列、長さ、容量)を適切に設定します。
2. プログラミング言語のパーサーと抽象構文木(AST)
- パーサー: プログラミング言語のコンパイラやインタプリタの一部で、ソースコードの文字列を解析し、その文法構造が正しいかどうかを検証する役割を担います。正しい場合、その構造を抽象構文木(AST)として表現します。
- 抽象構文木(AST: Abstract Syntax Tree): ソースコードの抽象的な構文構造を木構造で表現したものです。ASTは、コンパイラの次の段階(意味解析、最適化、コード生成など)で利用されます。例えば、
a + b * c
という式は、+
をルートとする木構造で表現され、その子ノードとしてa
と*
があり、*
の子ノードとしてb
とc
がある、といった形になります。 - ヒューリスティック: プログラミング言語の解析において、特定のパターンや文脈に基づいて、より効率的または正確な解析を行うための経験則や近似的なルールを指します。特に、曖昧な文法や、特定の組み込み関数に特化した解析が必要な場合に用いられます。このコミットでは、
new
やmake
の引数が型であるという「ヒューリスティック」が使われています。
3. Go言語の初期開発におけるパーサーの進化
Go言語は2009年に公開された比較的新しい言語ですが、このコミットは2009年1月のものであり、Go言語がまだ活発に開発されていた初期段階のものです。この時期のパーサーは、言語仕様の策定と並行して進化しており、特定の構文や組み込み関数の挙動が固まっていく中で、解析ロジックも調整されていました。このコミットは、make
関数の引数解析に関する初期の課題を解決する一環として行われたと考えられます。
技術的詳細
このコミットは、usr/gri/pretty/parser.go
ファイル内の ParseCall
関数に焦点を当てています。ParseCall
関数は、関数呼び出しを解析する役割を担っています。
変更前のコードでは、ParseCall
関数内で、呼び出しの対象が new
という識別子である場合にのみ、その引数を型として解析するヒューリスティックが適用されていました。
// 変更前
if x0.tok == Scanner.IDENT && x0.s == "new" {
// heuristic: assume it's a new(*T, ...) call, try to parse a type
t = P.TryType();
}
ここで x0
は関数呼び出しの対象(この場合は new
という識別子)、x0.tok
はそのトークンタイプ(Scanner.IDENT
は識別子)、x0.s
は識別子の文字列値です。P.TryType()
は、現在のパーサーの状態から型を解析しようとする関数です。
このヒューリスティックは、new
関数が常に型を引数として取るという性質に基づいています。しかし、make
関数もまた、最初の引数として型を取るという同様の性質を持っています。変更前のパーサーは make
関数に対してこのヒューリスティックを適用していなかったため、make(Type, ...)
のような呼び出しが正しく解析されない可能性がありました。
このコミットでは、このヒューリスティックを make
関数にも拡張することで、この問題を解決しています。
// 変更後
if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {
// heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type
t = P.TryType();
}
変更後のコードでは、x0.s
が "new"
または "make"
のいずれかである場合に、P.TryType()
を呼び出して引数を型として解析するようになりました。これにより、make
関数の呼び出しにおいても、その最初の引数が型として正しく認識され、パーサーがより正確なASTを構築できるようになります。
この修正は、Go言語のパーサーが、特定の組み込み関数のセマンティクス(意味)を理解し、それに基づいて解析の挙動を調整する「文脈依存の解析」を行っていたことを示しています。このようなヒューリスティックは、言語の構文解析をより堅牢にし、開発者が意図した通りのコードを正しく解釈するために重要です。
コアとなるコードの変更箇所
diff --git a/usr/gri/pretty/parser.go b/usr/gri/pretty/parser.go
index b773d8e233..1e78215058 100644
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -785,8 +785,8 @@ func (P *Parser) ParseCall(x0 *AST.Expr) *AST.Expr {\n if P.tok != Scanner.RPAREN {\n P.expr_lev++;\n var t *AST.Type;\n- if x0.tok == Scanner.IDENT && x0.s == "new" {\n- // heuristic: assume it's a new(*T, ...) call, try to parse a type\n+ if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {\n+ // heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type\n t = P.TryType();\n }\n if t != nil {\n```
## コアとなるコードの解説
変更は `usr/gri/pretty/parser.go` ファイルの `ParseCall` 関数内、具体的には788行目と789行目で行われています。
* **変更前 (788行目)**:
```go
if x0.tok == Scanner.IDENT && x0.s == "new" {
```
この行は、関数呼び出しの対象(`x0`)が識別子であり、かつその識別子の文字列値が `"new"` である場合にのみ、続くコードブロックを実行するという条件を示しています。このブロックでは、`new` 関数の引数を型として解析するヒューリスティックが適用されていました。
* **変更後 (788行目)**:
```go
if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {
```
この行では、条件が拡張され、関数呼び出しの対象が識別子であり、かつその識別子の文字列値が `"new"` **または** `"make"` のいずれかである場合に、続くコードブロックを実行するように変更されました。
* **変更前 (789行目)**:
```go
// heuristic: assume it's a new(*T, ...) call, try to parse a type
```
このコメントは、`new` 関数がポインタ型を引数として取ることを想定したヒューリスティックであることを説明しています。
* **変更後 (789行目)**:
```go
// heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type
```
このコメントは、ヒューリスティックが `new` 関数だけでなく `make` 関数にも適用され、両者が型 `T` を引数として取ることを想定していることを明確にしています。
この変更により、パーサーは `make` 関数の呼び出しにおいても、その最初の引数を型として正しく解析できるようになりました。これは、Go言語の `make` 関数のセマンティクスに合致するものであり、言語の正確な解析とコンパイルを保証するために不可欠な修正です。
## 関連リンク
* Go言語の `new` と `make` 関数に関する公式ドキュメント:
* [The Go Programming Language Specification - Allocation](https://go.dev/ref/spec#Allocation)
* [Effective Go - Allocation](https://go.dev/doc/effective_go#allocation)
* Go言語のパーサーに関する情報(一般的な概念として):
* [Go compiler internals: Parsing](https://go.dev/blog/go-compiler-internals-parsing) (これはこのコミットよりはるかに後の記事ですが、Goのパーシングの概念を理解するのに役立ちます)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (上記「関連リンク」に記載)
* Gitコミットの差分情報
* Go言語の `new` と `make` 関数の一般的な知識
* プログラミング言語のパーサーとASTに関する一般的な知識
* [Google Search: "golang new vs make"](https://www.google.com/search?q=golang+new+vs+make)
* [Google Search: "golang parser heuristic"](https://www.google.com/search?q=golang+parser+heuristic)
* [Google Search: "golang AST"](https://www.google.com/search?q=golang+AST)
* [Google Search: "golang Robert Griesemer"](https://www.google.com/search?q=golang+Robert+Griesemer)
* [Google Search: "golang parser.go"](https://www.google.com/search?q=golang+parser.go)
* [Google Search: "golang commit e2862606866b5618f6726579db2b1dd923c3469a"](https://www.google.com/search?q=golang+commit+e2862606866b5618f6726579db2b1dd923c3469a)# [インデックス 1420] ファイルの概要
このコミットは、Go言語の初期のパーサーの一部である `usr/gri/pretty/parser.go` ファイルに対する変更です。このファイルは、Go言語のソースコードを解析し、抽象構文木(AST)を構築する役割を担っていたと考えられます。特に、関数呼び出しの解析、中でも `new` や `make` といった組み込み関数の特殊な解析ロジックに関連する部分が修正されています。
## コミット
- fix parse heuristic: make(x) must accept a type for x
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/e2862606866b5618f6726579db2b1dd923c3469a](https://github.com/golang/go/commit/e2862606866b5618f6726579db2b1dd923c3469a)
## 元コミット内容
commit e2862606866b5618f6726579db2b1dd923c3469a Author: Robert Griesemer gri@golang.org Date: Tue Jan 6 15:30:26 2009 -0800
- fix parse heuristic: make(x) must accept a type for x
R=r
OCL=22171
CL=22171
## 変更の背景
このコミットは、Go言語のパーサーにおける `make` 関数の解析ロジックの修正を目的としています。Go言語には `new` と `make` という2つの組み込み関数があり、それぞれ異なる目的で使用されます。
* `new(T)`: 型 `T` のゼロ値が格納された新しい項目を割り当て、その項目へのポインタを返します。
* `make(T, args)`: スライス、マップ、チャネルといった組み込みのデータ構造を初期化し、使用可能な状態にします。これらの型は参照型であり、`new` で割り当てられただけではゼロ値(nil)であり、そのままでは使用できません。`make` は、これらの型に必要な内部データ構造を割り当て、初期化します。
コミットメッセージにある「fix parse heuristic: make(x) must accept a type for x」という記述から、以前のパーサーのヒューリスティック(経験則に基づいた解析ルール)では、`make` 関数の引数として型が正しく認識されていなかった、あるいは `new` 関数と同様の扱いがされていなかったことが示唆されます。
`new(T)` の場合、引数 `T` は常に型です。しかし、`make` の場合、`make([]int, 10)` のように最初の引数は型であり、その後に容量などの引数が続くことがあります。このコミット以前は、パーサーが `make` の引数を `new` と同じように型として適切に解析できていなかったため、`make` を使用したコードが正しくコンパイルされない、あるいは意図しない解析結果になる問題があったと考えられます。この修正は、`make` 関数の引数解析を `new` 関数と同様に、最初の引数を型として扱うように拡張することで、この問題を解決しています。
## 前提知識の解説
### 1. Go言語の `new` と `make` 関数
Go言語のメモリ割り当てと初期化には、`new` と `make` という2つの組み込み関数が使われます。
* **`new(Type)`**:
* 任意の型 `Type` のゼロ値を格納するためのメモリを割り当てます。
* 割り当てられたメモリへのポインタ(`*Type`)を返します。
* 例: `p := new(int)` は `int` 型のゼロ値(0)を格納するメモリを割り当て、そのアドレスを `p` に代入します。`p` は `*int` 型になります。
* スライス、マップ、チャネルなどの参照型に対して `new` を使うと、それらは `nil` ポインタとして初期化され、そのままでは使用できません。
* **`make(Type, size, capacity)`**:
* スライス、マップ、チャネルといった特定の組み込み参照型のために設計されています。
* これらの型に必要な内部データ構造を割り当て、初期化し、使用可能な状態にします。
* ポインタではなく、初期化された型の値を返します。
* 例: `s := make([]int, 5, 10)` は、長さ5、容量10の `int` 型のスライスを作成し、`s` に代入します。`s` は `[]int` 型になります。
* `make` は、スライス、マップ、チャネルの内部構造(例えば、スライスの場合は基盤となる配列、長さ、容量)を適切に設定します。
### 2. プログラミング言語のパーサーと抽象構文木(AST)
* **パーサー**: プログラミング言語のコンパイラやインタプリタの一部で、ソースコードの文字列を解析し、その文法構造が正しいかどうかを検証する役割を担います。正しい場合、その構造を抽象構文木(AST)として表現します。
* **抽象構文木(AST: Abstract Syntax Tree)**: ソースコードの抽象的な構文構造を木構造で表現したものです。ASTは、コンパイラの次の段階(意味解析、最適化、コード生成など)で利用されます。例えば、`a + b * c` という式は、`+` をルートとする木構造で表現され、その子ノードとして `a` と `*` があり、`*` の子ノードとして `b` と `c` がある、といった形になります。
* **ヒューリスティック**: プログラミング言語の解析において、特定のパターンや文脈に基づいて、より効率的または正確な解析を行うための経験則や近似的なルールを指します。特に、曖昧な文法や、特定の組み込み関数に特化した解析が必要な場合に用いられます。このコミットでは、`new` や `make` の引数が型であるという「ヒューリスティック」が使われています。
### 3. Go言語の初期開発におけるパーサーの進化
Go言語は2009年に公開された比較的新しい言語ですが、このコミットは2009年1月のものであり、Go言語がまだ活発に開発されていた初期段階のものです。この時期のパーサーは、言語仕様の策定と並行して進化しており、特定の構文や組み込み関数の挙動が固まっていく中で、解析ロジックも調整されていました。このコミットは、`make` 関数の引数解析に関する初期の課題を解決する一環として行われたと考えられます。
## 技術的詳細
このコミットは、`usr/gri/pretty/parser.go` ファイル内の `ParseCall` 関数に焦点を当てています。`ParseCall` 関数は、関数呼び出しを解析する役割を担っています。
変更前のコードでは、`ParseCall` 関数内で、呼び出しの対象が `new` という識別子である場合にのみ、その引数を型として解析するヒューリスティックが適用されていました。
```go
// 変更前
if x0.tok == Scanner.IDENT && x0.s == "new" {
// heuristic: assume it's a new(*T, ...) call, try to parse a type
t = P.TryType();
}
ここで x0
は関数呼び出しの対象(この場合は new
という識別子)、x0.tok
はそのトークンタイプ(Scanner.IDENT
は識別子)、x0.s
は識別子の文字列値です。P.TryType()
は、現在のパーサーの状態から型を解析しようとする関数です。
このヒューリスティックは、new
関数が常に型を引数として取るという性質に基づいています。しかし、make
関数もまた、最初の引数として型を取るという同様の性質を持っています。変更前のパーサーは make
関数に対してこのヒューリスティックを適用していなかったため、make(Type, ...)
のような呼び出しが正しく解析されない可能性がありました。
このコミットでは、このヒューリスティックを make
関数にも拡張することで、この問題を解決しています。
// 変更後
if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {
// heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type
t = P.TryType();
}
変更後のコードでは、x0.s
が "new"
または "make"
のいずれかである場合に、P.TryType()
を呼び出して引数を型として解析するようになりました。これにより、make
関数の呼び出しにおいても、その最初の引数が型として正しく認識され、パーサーがより正確なASTを構築できるようになります。
この修正は、Go言語のパーサーが、特定の組み込み関数のセマンティクス(意味)を理解し、それに基づいて解析の挙動を調整する「文脈依存の解析」を行っていたことを示しています。このようなヒューリスティックは、言語の構文解析をより堅牢にし、開発者が意図した通りのコードを正しく解釈するために重要です。
コアとなるコードの変更箇所
diff --git a/usr/gri/pretty/parser.go b/usr/gri/pretty/parser.go
index b773d8e233..1e78215058 100644
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -785,8 +785,8 @@ func (P *Parser) ParseCall(x0 *AST.Expr) *AST.Expr {\n if P.tok != Scanner.RPAREN {\n P.expr_lev++;\n var t *AST.Type;\n- if x0.tok == Scanner.IDENT && x0.s == "new" {\n- // heuristic: assume it's a new(*T, ...) call, try to parse a type\n+ if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {\n+ // heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type\n t = P.TryType();\n }\n if t != nil {\n```
## コアとなるコードの解説
変更は `usr/gri/pretty/parser.go` ファイルの `ParseCall` 関数内、具体的には788行目と789行目で行われています。
* **変更前 (788行目)**:
```go
if x0.tok == Scanner.IDENT && x0.s == "new" {
```
この行は、関数呼び出しの対象(`x0`)が識別子であり、かつその識別子の文字列値が `"new"` である場合にのみ、続くコードブロックを実行するという条件を示しています。このブロックでは、`new` 関数の引数を型として解析するヒューリスティックが適用されていました。
* **変更後 (788行目)**:
```go
if x0.tok == Scanner.IDENT && (x0.s == "new" || x0.s == "make") {
```
この行では、条件が拡張され、関数呼び出しの対象が識別子であり、かつその識別子の文字列値が `"new"` **または** `"make"` のいずれかである場合に、続くコードブロックを実行するように変更されました。
* **変更前 (789行目)**:
```go
// heuristic: assume it's a new(*T, ...) call, try to parse a type
```
このコメントは、`new` 関数がポインタ型を引数として取ることを想定したヒューリスティックであることを説明しています。
* **変更後 (789行目)**:
```go
// heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type
```
このコメントは、ヒューリスティックが `new` 関数だけでなく `make` 関数にも適用され、両者が型 `T` を引数として取ることを想定していることを明確にしています。
この変更により、パーサーは `make` 関数の呼び出しにおいても、その最初の引数を型として正しく解析できるようになりました。これは、Go言語の `make` 関数のセマンティクスに合致するものであり、言語の正確な解析とコンパイルを保証するために不可欠な修正です。
## 関連リンク
* Go言語の `new` と `make` 関数に関する公式ドキュメント:
* [The Go Programming Language Specification - Allocation](https://go.dev/ref/spec#Allocation)
* [Effective Go - Allocation](https://go.dev/doc/effective_go#allocation)
* Go言語のパーサーに関する情報(一般的な概念として):
* [Go compiler internals: Parsing](https://go.dev/blog/go-compiler-internals-parsing) (これはこのコミットよりはるかに後の記事ですが、Goのパーシングの概念を理解するのに役立ちます)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (上記「関連リンク」に記載)
* Gitコミットの差分情報
* Go言語の `new` と `make` 関数の一般的な知識
* プログラミング言語のパーサーとASTに関する一般的な知識
* [Google Search: "golang new vs make"](https://www.google.com/search?q=golang+new+vs+make)
* [Google Search: "golang parser heuristic"](https://www.google.com/search?q=golang+parser+heuristic)
* [Google Search: "golang AST"](https://www.google.com/search?q=golang+AST)
* [Google Search: "golang Robert Griesemer"](https://www.google.com/search?q=golang+Robert+Griesemer)
* [Google Search: "golang parser.go"](https://www.google.com/search?q=golang+parser.go)
* [Google Search: "golang commit e2862606866b5618f6726579db2b1dd923c3469a"](https://www.google.com/search?q=golang+commit+e2862606866b5618f6726579db2b1dd923c3469a)