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

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

このコミットは、Go言語の型システムにおけるポインタ型のデリファレンス(間接参照)に関する修正です。具体的には、名前付きポインタ型(type MyPtr *int のような定義)が正しくデリファレンスできるように、型チェックロジックが改善されました。これにより、Goコンパイラの型チェッカーが、名前付きポインタ型の基底型がポインタである場合に、そのデリファレンスを許可するようになります。

コミット

commit 68ff170ebece48b7fbef3c14c1514811a4d6c370
Author: Andrew Wilkins <axwalk@gmail.com>
Date:   Mon Feb 18 19:03:10 2013 -0800

    go/types: Permit dereferencing of named pointer types.
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/7358044

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

https://github.com/golang/go/commit/68ff170ebece48b7fbef3c14c1514811a4d6c370

元コミット内容

diff --git a/src/pkg/go/types/expr.go b/src/pkg/go/types/expr.go
index 696a0cae68..0caa90a1d3 100644
--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -1193,7 +1193,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
 		case typexpr:
 			x.typ = &Pointer{Base: x.typ}
 		default:
-			if typ, ok := x.typ.(*Pointer); ok {
+			if typ, ok := underlying(x.typ).(*Pointer); ok {
 				x.mode = variable
 				x.typ = typ.Base
 			} else {
diff --git a/src/pkg/go/types/testdata/expr0.src b/src/pkg/go/types/testdata/expr0.src
index c3233d36fe..8d057f63c1 100644
--- a/src/pkg/go/types/testdata/expr0.src
+++ b/src/pkg/go/types/testdata/expr0.src
@@ -149,3 +149,13 @@ var (
 	_ = &((((T{1, 2}))))
 	_ = &f /* ERROR "cannot take address" */ ()\n )
+\n+// recursive pointer types
+type P *P
+\n+var (\n+\tp1 P = new(P)\n+\tp2 P = *p1
+\tp3 P = &p2
+)\n+\n

変更の背景

Go言語の型システムでは、既存の型に新しい名前を付けて「名前付き型」を定義できます。例えば、type MyInt inttype MyPtr *int のように定義します。ポインタ型の場合、*T のように直接ポインタ型を定義することもできますが、type MyPtr *int のように名前を付けることも可能です。

このコミット以前のGoコンパイラの型チェッカー(go/typesパッケージ)では、デリファレンス演算子 * を適用する際に、式の型が直接 *Pointer 型であるかどうかのみをチェックしていました。このため、type MyPtr *int のように定義された「名前付きポインタ型」の変数に対してデリファレンスを行おうとすると、その名前付き型自体は *Pointer 型ではないため、型チェックエラーが発生してしまうという問題がありました。

この挙動は、Goの型システムの設計思想、特に「基底型(underlying type)」の概念と矛盾していました。Goでは、名前付き型はその基底型と同じ操作をサポートすべきであるという原則があります。したがって、名前付きポインタ型も、その基底型がポインタ型であるならば、デリファレンス可能であるべきでした。このコミットは、この不整合を解消し、名前付きポインタ型が期待通りに機能するように修正することを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念が重要です。

  1. 型システム (Type System): Goは静的型付け言語であり、すべての変数には型があります。型はコンパイル時に決定され、プログラムの安全性と予測可能性を保証します。
  2. ポインタ型 (Pointer Types): Goにおけるポインタは、メモリ上の特定のアドレスを指し示す変数です。*T の形式で宣言され、T 型の値へのポインタであることを示します。
    • & 演算子: 変数のアドレスを取得します(例: &x は変数 x のアドレス)。
    • * 演算子: ポインタが指すアドレスに格納されている値を取得します(デリファレンス、間接参照)。
  3. 名前付き型 (Named Types): type NewType OldType の形式で、既存の型に新しい名前を付けることができます。NewTypeOldType とは異なる新しい型とみなされますが、OldType を「基底型(underlying type)」として持ちます。
    • 例: type MyInt int の場合、MyInt の基底型は int です。
    • 例: type MyPtr *int の場合、MyPtr の基底型は *int です。
  4. 基底型 (Underlying Type): Goの型システムにおける重要な概念で、名前付き型が最終的にどのような組み込み型や複合型に基づいているかを示します。型変換や操作の互換性を判断する際に用いられます。例えば、type A inttype B int は異なる型ですが、どちらも基底型は int です。
  5. go/types パッケージ: Goコンパイラの一部であり、Goプログラムの静的セマンティック分析(型チェック、スコープ解決など)を行うためのライブラリです。コンパイラがコードの正当性を検証する上で中心的な役割を担います。このパッケージは、AST (Abstract Syntax Tree) を入力として受け取り、各式の型や値、変数のスコープなどを決定します。

技術的詳細

このコミットの核心は、go/types パッケージ内の型チェックロジック、特にデリファレンス演算子 * の処理方法の変更にあります。

Go言語の仕様では、デリファレンス演算子 * は、ポインタ型である式 x に対して適用され、そのポインタが指す変数を示します。ここで重要なのは、「ポインタ型である」という条件の解釈です。

修正前の src/pkg/go/types/expr.gorawExpr 関数(式を評価する部分)では、デリファレンス対象の式の型 x.typ が直接 *Pointer 型(Goの内部表現におけるポインタ型)であるかどうかを x.typ.(*Pointer) という型アサーションで確認していました。

しかし、type MyPtr *int のように定義された名前付きポインタ型の場合、MyPtr 自体は *Pointer 型ではありません。MyPtrMyPtr という独自の型であり、その「基底型」が *int(つまりポインタ型)なのです。したがって、修正前のロジックでは、MyPtr 型の変数 p に対して *p と記述すると、x.typ.(*Pointer) のアサーションが失敗し、デリファレンスが許可されませんでした。

この問題を解決するため、変更後のコードでは underlying(x.typ).(*Pointer) を使用しています。 underlying(x.typ) は、x.typ の基底型を取得する関数(または概念)です。

  • もし x.typ*int のような直接的なポインタ型であれば、underlying(x.typ) はそのまま *int を返します。
  • もし x.typMyPtr のような名前付きポインタ型(基底型が *int)であれば、underlying(x.typ)*int を返します。

このように、まず基底型を取得してからそれがポインタ型であるかをチェックすることで、名前付きポインタ型であっても、その基底型がポインタであれば正しくデリファレンスが許可されるようになりました。これにより、Goの型システムの整合性が保たれ、より直感的なコード記述が可能になります。

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

変更は src/pkg/go/types/expr.go ファイルの1箇所です。

--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -1193,7 +1193,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
 		case typexpr:
 			x.typ = &Pointer{Base: x.typ}
 		default:
-			if typ, ok := x.typ.(*Pointer); ok {
+			if typ, ok := underlying(x.typ).(*Pointer); ok {
 				x.mode = variable
 				x.typ = typ.Base
 			} else {

また、この変更を検証するためのテストケースが src/pkg/go/types/testdata/expr0.src に追加されています。

--- a/src/pkg/go/types/testdata/expr0.src
+++ b/src/pkg/go/types/testdata/expr0.src
@@ -149,3 +149,13 @@ var (\n 	_ = &((((T{1, 2}))))\n 	_ = &f /* ERROR "cannot take address" */ ()\n )
+\n+// recursive pointer types
+type P *P
+\n+var (\n+\tp1 P = new(P)\n+\tp2 P = *p1
+\tp3 P = &p2
+)\n+\n

コアとなるコードの解説

src/pkg/go/types/expr.go の変更は、rawExpr 関数内のデリファレンス処理の一部です。

元のコード: if typ, ok := x.typ.(*Pointer); ok {

変更後のコード: if typ, ok := underlying(x.typ).(*Pointer); ok {

  • x: 現在評価中の式を表す operand 構造体。x.typ はその式の型を示します。
  • (*Pointer): これはGoの型アサーション構文です。x.typ または underlying(x.typ)*Pointer 型(Goの内部でポインタ型を表す構造体)に変換可能であるかをチェックします。変換可能であれば typ にその値が代入され、oktrue になります。
  • underlying(x.typ): この関数呼び出しが変更の肝です。x.typ が名前付き型である場合、この関数はその名前付き型の基底型を返します。例えば、x.typtype P *P で定義された P 型であれば、underlying(x.typ)*P 型を返します。もし x.typ がすでに基底型(例: *int)であれば、underlying はその型自身を返します。

この変更により、型チェッカーはデリファレンスを行う際に、式の直接の型だけでなく、その型の基底型がポインタ型であるかどうかも考慮するようになりました。これにより、type P *P のような再帰的な名前付きポインタ型や、その他の名前付きポインタ型が正しくデリファレンスできるようになります。

追加されたテストケース src/pkg/go/types/testdata/expr0.src は、この修正が意図通りに機能することを確認します。特に、type P *P という再帰的な名前付きポインタ型を定義し、p2 P = *p1 のようにデリファレンスを行うコードが含まれています。この行がエラーなくコンパイルできることが、修正の成功を示します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語のIssueトラッカーやコードレビューシステム(CL: Change List)