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

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

このコミットは、Go言語の型チェッカーの一部である src/pkg/go/types/conversions.go ファイルに対する変更を記述しています。このファイルは、Goプログラムにおける型変換(コンバージョン)のルールとロジックを定義および実装する役割を担っています。具体的には、ある型から別の型への値の変換がGo言語の仕様に則って正しく行われるかを検証するための機能を提供します。

コミット

commit 2e15f4b8c49b2e240d4852d39610956a3473da3c
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jan 23 10:57:18 2013 -0800

    go/types: typechecking conversions, part 1 (non-constants)
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/7103058

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

https://github.com/golang/go/commit/2e15f4b8c49b2e240d4852d39610956a3473da3c

元コミット内容

go/types: typechecking conversions, part 1 (non-constants)

このコミットは、Go言語の型チェッカーにおける型変換のチェック機能、特に定数ではない値の型変換(非定数変換)に関する部分を実装するものです。

変更の背景

Go言語は静的型付け言語であり、プログラムの安全性を保証するために厳格な型システムを持っています。型変換は、ある型の値を別の型の値として扱うための重要なメカニズムですが、これが誤って行われると、予期せぬランタイムエラーやセキュリティ上の問題を引き起こす可能性があります。

このコミットが行われた2013年1月時点では、Go言語の型チェッカーはまだ発展途上にあり、特に型変換に関する詳細なルールが完全に実装されていなかったと考えられます。コミットメッセージにある「TODO(gri) fix this - implement all checks and constant evaluation」というコメントからも、型変換のチェックが不完全であったことが伺えます。

この変更の背景には、Go言語の型システムをより堅牢にし、コンパイル時に可能な限り多くの型関連のエラーを捕捉することで、開発者がより信頼性の高いコードを書けるようにするという目的があります。特に、非定数(変数など)の型変換は実行時にその値が変化する可能性があるため、より厳密なチェックが必要とされます。このコミットは、そのための基盤となる isConvertible 関数を導入し、Go言語の型変換ルールをコードとして明示的に定義することで、型チェッカーの精度と網羅性を向上させることを目指しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の型システムに関する基本的な概念を理解しておく必要があります。

  • 型 (Type): Go言語におけるデータの種類を定義します。例えば、intstringboolstructinterface などがあります。
  • 基底型 (Underlying Type): 型の定義において、その型が最終的にどのような組み込み型に基づいているかを示します。例えば、type MyInt int と定義された MyInt 型の基底型は int です。型変換のルールでは、基底型が同一であるかどうかが重要な判断基準となります。underlying(T) 関数は、与えられた型 T の基底型を返します。
  • 基本型 (Basic Type): int, float64, string, bool などのGo言語に組み込みで用意されているプリミティブな型です。
  • ポインタ型 (Pointer Type): 別の変数のメモリアドレスを保持する型です。*T の形式で表されます。
  • スライス型 (Slice Type): 同じ型の要素の可変長シーケンスを表す型です。[]T の形式で表されます。
  • unsafe.Pointer: Go言語の型システムを迂回して、任意の型のポインタを保持できる特殊なポインタ型です。低レベルな操作やC言語との連携などで使用されますが、その名の通り「unsafe(安全でない)」であり、誤用するとプログラムのクラッシュや未定義動作を引き起こす可能性があります。
  • 型変換 (Conversion): ある型の値を別の型の値に明示的に変換する操作です。Go言語では T(x) の形式で記述されます。型変換には厳格なルールがあり、すべての型間で自由に変換できるわけではありません。
  • 定数 (Constant): コンパイル時に値が確定しているリテラルや式です。定数間の型変換には特別なルールが適用されることがあります。
  • 非定数 (Non-constant): 変数など、実行時に値が決定されるものです。

技術的詳細

このコミットの主要な目的は、Go言語の型チェッカーが非定数(変数など)の型変換を正しく検証するためのロジックを実装することです。Go言語の仕様では、型変換が許可される条件が明確に定義されており、このコミットはその仕様をコードに落とし込んでいます。

具体的には、checker.conversion メソッド内で、変換対象のオペランド x が定数でない場合に、新たに導入された x.isConvertible(typ) メソッドを呼び出して、変換が可能かどうかを判断します。もし変換が不可能であれば、check.invalidOp を呼び出して型エラーを報告します。

isConvertible 関数は、Go言語の型変換ルールを網羅的にチェックします。これらのルールは、Go言語の仕様書「Conversions」セクションに記載されているものと密接に対応しています。以下に、isConvertible がチェックする主なルールを挙げます。

  1. 代入可能性 (Assignability): xT に代入可能であれば、変換も可能です。これは最も基本的なルールであり、例えば int 型の変数を int 型の変数に変換(実質的には何もしない)する場合などが該当します。
  2. 基底型の同一性: x の型と T の基底型が同一であれば、変換可能です。例えば、type MyInt inttype YourInt int があった場合、MyInt の変数と YourInt の変数は直接代入できませんが、両者の基底型が int であるため、互いに変換可能です。
  3. 無名ポインタ型の基底型の同一性: x の型と T が両方とも無名ポインタ型(例: *int)であり、かつそれらのポインタの基底型(例: int)の基底型が同一であれば、変換可能です。これは、ポインタの指す先の型が同じであれば、ポインタ型自体も変換可能であることを意味します。
  4. 整数型または浮動小数点型間の変換: x の型と T が両方とも整数型、または両方とも浮動小数点型であれば、変換可能です。例えば、int から int32float32 から float64 などです。
  5. 複素数型間の変換: x の型と T が両方とも複素数型であれば、変換可能です。例えば、complex64 から complex128 などです。
  6. 整数型、バイト/ルーンスライスから文字列型への変換: x が整数型、またはバイトスライス ([]byte) もしくはルーンスライス ([]rune) であり、T が文字列型であれば、変換可能です。これは、数値から文字列への変換や、バイト/ルーンシーケンスから文字列への変換を許可します。
  7. 文字列型からバイト/ルーンスライスへの変換: x が文字列型であり、T がバイトスライス ([]byte) もしくはルーンスライス ([]rune) であれば、変換可能です。これは、文字列からバイト/ルーンシーケンスへの変換を許可します。
  8. unsafe.Pointer との相互変換:
    • 任意のポインタ型または uintptr 型の値を unsafe.Pointer 型に変換できます。
    • unsafe.Pointer 型の値を任意のポインタ型または uintptr 型に変換できます。 これらのルールは、Go言語の型システムにおける「安全でない」操作を可能にする unsafe.Pointer の特殊な性質を反映しています。

これらのルールは、Go言語の型変換の厳密さと柔軟性のバランスを取るために設計されています。このコミットは、これらのルールを isConvertible 関数として具体的に実装することで、型チェッカーの正確性を大幅に向上させています。

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

変更は src/pkg/go/types/conversions.go ファイルに集中しています。

--- a/src/pkg/go/types/conversions.go
+++ b/src/pkg/go/types/conversions.go
@@ -28,10 +28,18 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
 		goto Error
 	}
 
-	// TODO(gri) fix this - implement all checks and constant evaluation
-	if x.mode != constant || !isConstType(typ) {
+	if x.mode == constant && isConstType(typ) {
+		// constant conversion
+		// TODO(gri) implement this
+	} else {
+		// non-constant conversion
+		if !x.isConvertible(typ) {
+			check.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ)
+			goto Error
+		}
 		x.mode = value
 	}
+
 	x.expr = conv
 	x.typ = typ
 	return
@@ -39,3 +47,82 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
 Error:
 	x.mode = invalid
 }
+
+func (x *operand) isConvertible(T Type) bool {
+	// "x is assignable to T"
+	if x.isAssignable(T) {
+		return true
+	}
+
+	// "x's type and T have identical underlying types"
+	V := x.typ
+	Vu := underlying(V)
+	Tu := underlying(T)
+	if isIdentical(Vu, Tu) {
+		return true
+	}
+
+	// "x's type and T are unnamed pointer types and their pointer base types have identical underlying types"
+	if V, ok := V.(*Pointer); ok {
+		if T, ok := T.(*Pointer); ok {
+			if isIdentical(underlying(V.Base), underlying(T.Base)) {
+				return true
+			}
+		}
+	}
+
+	// "x's type and T are both integer or floating point types"
+	if (isInteger(V) || isFloat(V)) && (isInteger(T) || isFloat(T)) {
+		return true
+	}
+
+	// "x's type and T are both complex types"
+	if isComplex(V) && isComplex(T) {
+		return true
+	}
+
+	// "x is an integer or a slice of bytes or runes and T is a string type"
+	if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) {
+		return true
+	}
+
+	// "x is a string and T is a slice of bytes or runes"
+	if isString(V) && isBytesOrRunes(Tu) {
+		return true
+	}
+
+	// package unsafe:
+	// "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer"
+	if (isPointer(Vu) || isUintptr(Vu)) && isUnsafePointer(T) {
+		return true
+	}
+	// "and vice versa"
+	if isUnsafePointer(V) && (isPointer(Tu) || isUintptr(Tu)) {
+		return true
+	}
+
+	return false
+}
+
+func isUintptr(typ Type) bool {
+	t, ok := typ.(*Basic)
+	return ok && t.Kind == Uintptr
+}
+
+func isUnsafePointer(typ Type) bool {
+	t, ok := typ.(*Basic)
+	return ok && t.Kind == UnsafePointer
+}
+
+func isPointer(typ Type) bool {
+	_, ok := typ.(*Pointer)
+	return ok
+}
+
+func isBytesOrRunes(typ Type) bool {
+	if s, ok := typ.(*Slice); ok {
+		t, ok := underlying(s.Elt).(*Basic)
+		return ok && (t.Kind == Byte || t.Kind == Rune)
+	}
+	return false
+}

主な変更点は以下の通りです。

  • checker.conversion 関数内の型変換ロジックが更新され、定数と非定数の変換パスが明確に分離されました。
  • 非定数変換のために、x.isConvertible(typ) という新しいメソッドが導入されました。
  • isConvertible メソッドをサポートするために、以下のヘルパー関数が追加されました。
    • isUintptr(typ Type) bool
    • isUnsafePointer(typ Type) bool
    • isPointer(typ Type) bool
    • isBytesOrRunes(typ Type) bool

コアとなるコードの解説

func (x *operand) isConvertible(T Type) bool

この関数は、オペランド x の型がターゲット型 T に変換可能であるかどうかを判断するGo言語の型変換ルールの中心的な実装です。前述の「技術的詳細」セクションで説明したGo言語の仕様に基づく変換ルールが、この関数内で順にチェックされます。

  • x.isAssignable(T): まず、代入可能性をチェックします。代入可能であれば、変換も可能です。
  • isIdentical(Vu, Tu): x の型 V とターゲット型 T の基底型が同一であるかをチェックします。
  • ポインタ型の基底型の同一性チェック: VT が両方ともポインタ型である場合に、そのポインタが指す先の型の基底型が同一であるかをチェックします。
  • 数値型(整数、浮動小数点、複素数)間の変換チェック: それぞれの数値型グループ内で変換が可能であるかをチェックします。
  • 文字列とバイト/ルーンスライス間の変換チェック: 文字列とバイト/ルーンスライス間の相互変換が可能であるかをチェックします。
  • unsafe.Pointer との相互変換チェック: unsafe.Pointer と他のポインタ型または uintptr 型との間の特殊な変換ルールをチェックします。

これらのチェックは、Go言語の型変換の厳密なセマンティクスを反映しており、いずれかの条件が満たされれば true を返し、変換が可能であることを示します。すべての条件が満たされなければ false を返し、変換が不可能であることを示します。

ヘルパー関数

isConvertible 関数は、コードの可読性と再利用性を高めるために、いくつかのヘルパー関数を使用しています。

  • func isUintptr(typ Type) bool: 与えられた型 typuintptr 型であるかどうかをチェックします。uintptr は、ポインタを整数として表現できる特殊な整数型です。
  • func isUnsafePointer(typ Type) bool: 与えられた型 typunsafe.Pointer 型であるかどうかをチェックします。
  • func isPointer(typ Type) bool: 与えられた型 typ が任意のポインタ型であるかどうかをチェックします。
  • func isBytesOrRunes(typ Type) bool: 与えられた型 typ がバイトスライス ([]byte) またはルーンスライス ([]rune) であるかどうかをチェックします。これは、文字列との相互変換ルールで使用されます。

これらのヘルパー関数は、型チェッカーが特定の型の特性を効率的に識別できるようにすることで、isConvertible 関数のロジックを簡潔に保っています。

関連リンク

参考にした情報源リンク