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

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

このコミットは、Goコンパイラ(cmd/gc)におけるcomplex組み込み関数の型チェックロジックを修正し、引数の型が厳密に一致しない場合にコンパイルエラーを発生させるように変更したものです。具体的には、Go言語の仕様で定められている「complex関数の2つの引数は同じ浮動小数点型でなければならない」という要件をより厳密に強制するために、型比較の方法を改善しています。

コミット

commit 401e0fea3ad120a495f7c8770cfbf1278c00c16e
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Mon Mar 11 22:55:14 2013 +0100

    cmd/gc: reject complex calls with mismatched argument types.
    
    The specification says "the two arguments must be of the same
    floating-point type."
    
    R=rsc, gri
    CC=golang-dev
    https://golang.org/cl/7671045

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

https://github.com/golang/go/commit/401e0fea3ad120a495f7c8770cfbf1278c00c16e

元コミット内容

cmd/gc: reject complex calls with mismatched argument types.cmd/gc: complex呼び出しにおける引数の型不一致を拒否する。)

The specification says "the two arguments must be of the same floating-point type." (仕様では「2つの引数は同じ浮動小数点型でなければならない」と規定されている。)

変更の背景

Go言語には、実部と虚部から複素数を作成するための組み込み関数complexが存在します。この関数は、complex(real, imag)のように使用され、realimagは同じ浮動小数点型(float32またはfloat64)である必要があります。しかし、このコミット以前のGoコンパイラでは、この型チェックが不十分でした。

具体的には、float32とそれから派生したユーザー定義型(例: type MyFloat32 float32)をcomplex関数の引数として渡した場合、コンパイラはこれらを同じ「基本型」(float32)として扱ってしまい、型が厳密に一致しないことを見落とす可能性がありました。Goの型システムでは、基底型が同じであっても、異なる型として宣言されたものは異なる型として扱われます。このコミットは、この仕様の厳密な解釈と実装の乖離を修正し、より堅牢な型安全性を提供することを目的としています。

前提知識の解説

Go言語の型システム

Go言語は静的型付け言語であり、変数は宣言時に型を持ち、その型に合った値のみを格納できます。Goの型システムは、以下のような特徴を持ちます。

  • 基本型 (Basic Types): int, float32, string, boolなど、言語に組み込まれている型です。
  • 複合型 (Composite Types): 配列、スライス、マップ、構造体、インターフェースなど、基本型を組み合わせて作られる型です。
  • ユーザー定義型 (User-defined Types): type NewType UnderlyingType の形式で、既存の型を基にして新しい型を定義できます。例えば、type MyFloat32 float32と定義した場合、MyFloat32float32とは異なる新しい型として扱われます。ただし、基底型が同じであれば、型変換(キャスト)によって相互に変換可能です。

complex組み込み関数

complex関数は、Go言語の組み込み関数の一つで、2つの浮動小数点数から複素数を作成します。

  • シグネチャ: func complex(real, imag FloatType) ComplexType
  • 引数: realimagは同じ浮動小数点型(float32またはfloat64)でなければなりません。
  • 戻り値: 引数がfloat32の場合、complex64を返します。引数がfloat64の場合、complex128を返します。

Go言語の仕様では、complex関数の引数について「The two arguments must be of the same floating-point type.」(2つの引数は同じ浮動小数点型でなければならない。)と明記されています。

Goコンパイラの型チェック

Goコンパイラは、ソースコードを機械語に変換する過程で、プログラムの型がGo言語の仕様に準拠しているかを確認します。この型チェックの段階で、型の不一致や不正な操作が検出されると、コンパイルエラーが発生します。

  • Node構造体: Goコンパイラの内部では、AST(抽象構文木)の各ノードがNode構造体で表現されます。このNode構造体には、そのノードが表す式の型情報を持つtypeフィールドが含まれています。
  • Type構造体: Type構造体は、Go言語の型システムにおける型を表す内部表現です。この構造体には、型の種類(基本型、ポインタ型、構造体型など)を示すetype(element type)フィールドや、型の詳細な情報が含まれます。
  • etype: etypeは、型の「種類」を示す列挙型のようなものです。例えば、float32MyFloat32は異なる型ですが、どちらも基底型がfloat32であるため、etypeは同じ値(例えばTFLOAT32)を持つ可能性があります。
  • eqtype関数: eqtype関数は、2つのType構造体が「等しい型」であるかを厳密に比較する関数です。これは、単にetypeが同じであるかを見るだけでなく、型が持つすべての属性(例えば、ユーザー定義型であるか、ポインタの深さ、構造体のフィールドなど)を考慮して比較します。

技術的詳細

このコミットの核心は、complex組み込み関数の引数に対する型チェックの厳密化です。以前のコンパイラでは、src/cmd/gc/typecheck.c内のcomplex関数の型チェックにおいて、引数の型がl->type->etype != r->type->etypeという条件で比較されていました。これは、引数の「基本型」が異なる場合にエラーを出すものでした。

しかし、このアプローチでは、以下のようなケースで問題が発生します。

type MyFloat32 float32

var f32 float32
var mf32 MyFloat32

// 以前のコンパイラではエラーにならなかった可能性
_ = complex(f32, mf32)

上記の例では、f32mf32は異なる型ですが、どちらも基底型はfloat32です。そのため、etypeは同じ値を持つ可能性があり、l->type->etype != r->type->etypeという比較では、この型不一致を検出できませんでした。Goの仕様では、これらは「同じ浮動小数点型」ではないため、エラーとなるべきです。

このコミットでは、この問題を解決するために、型比較を!eqtype(l->type, r->type)に変更しました。eqtype関数は、2つの型が完全に同一であるかをチェックします。これには、基底型だけでなく、型がユーザー定義型であるかどうかも含め、型のすべての属性が考慮されます。これにより、float32MyFloat32のような、基底型は同じだが異なるユーザー定義型である引数がcomplex関数に渡された場合に、コンパイラが正しく型不一致を検出できるようになります。

エラーメッセージも、より正確に「mismatched types」(型不一致)を示すように変更されました。

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

変更は主に以下の2つのファイルで行われています。

  1. src/cmd/gc/typecheck.c: complex関数の型チェックロジックが変更されました。

    --- a/src/cmd/gc/typecheck.c
    +++ b/src/cmd/gc/typecheck.c
    @@ -1182,9 +1182,9 @@ reswitch:
     		defaultlit2(&l, &r, 0);\n \t\tn->left = l;\n \t\tn->right = r;\n-\t\tif(l->type->etype != r->type->etype) {\n+\t\tif(!eqtype(l->type, r->type)) {\n \t\tbadcmplx:\n-\t\t\tyyerror(\"invalid operation: %N (complex of types %T, %T)\", n, l->type, r->type);\n+\t\t\tyyerror(\"invalid operation: %N (mismatched types %T and %T)\", n, l->type, r->type);\n \t\t\tgoto error;\n \t\t}\n \t\tswitch(l->type->etype) {
    
    • l->type->etype != r->type->etype!eqtype(l->type, r->type) に変更されました。
    • エラーメッセージが "invalid operation: %N (complex of types %T, %T)" から "invalid operation: %N (mismatched types %T and %T)" に変更されました。
  2. test/cmplx.go: 新しいテストケースが追加され、この変更によって正しくエラーが検出されることを検証しています。

    package main
    
    type (
    	Float32    float32
    	Float64    float64
    	Complex64  complex64
    	Complex128 complex128
    )
    
    var (
    	f32 float32
    	f64 float64
    	F32 Float32 // ユーザー定義型
    	F64 Float64 // ユーザー定義型
    
    	c64  complex64
    	c128 complex128
    	C64  Complex64 // ユーザー定義型
    	C128 Complex128 // ユーザー定義型
    )
    
    func main() {
    	_ = complex128(0)     // ok
    	_ = complex(f32, f64) // ERROR "complex" (既存のテスト)
    	_ = complex(f64, f32) // ERROR "complex" (既存のテスト)
    
    	// 新しく追加されたテストケース
    	_ = complex(f32, F32) // ERROR "complex" - float32 と MyFloat32 の不一致
    	_ = complex(F32, f32) // ERROR "complex" - MyFloat32 と float32 の不一致
    	_ = complex(f64, F64) // ERROR "complex" - float64 と MyFloat64 の不一致
    	_ = complex(F64, f64) // ERROR "complex" - MyFloat64 と float64 の不一致
    
    	c128 = complex(f32, f32) // ERROR "cannot use" (既存のテスト)
    	c64 = complex(f64, f64)  // ERROR "cannot use" (既存のテスト)
    
    	c64 = complex(1.0, 2.0) // ok, constant is untyped
    	c128 = complex(1.0, 2.0)
    	C64 = complex(1.0, 2.0)
    	C128 = complex(1.0, 2.0)
    
    	C64 = complex(f32, f32)  // ERROR "cannot use" - ユーザー定義型への代入
    	C128 = complex(f64, f64) // ERROR "cannot use" - ユーザー定義型への代入
    }
    

コアとなるコードの解説

src/cmd/gc/typecheck.cにおける変更は、Goコンパイラの型チェックの精度を大幅に向上させます。

  • l->type->etype != r->type->etype から !eqtype(l->type, r->type) への変更:

    • 旧ロジック (etype比較): etypeは型の「基本カテゴリ」を示すため、float32type MyFloat32 float32のようなユーザー定義型は、どちらも基底型がfloat32であるため、同じetypeを持つ可能性がありました。これにより、コンパイラはこれらを同じ型として誤認し、仕様に反してcomplex(float32, MyFloat32)のような呼び出しを許可してしまう可能性がありました。
    • 新ロジック (eqtype比較): eqtype関数は、2つのType構造体が完全に同一であるかを厳密に比較します。これには、型の基底型だけでなく、それがユーザー定義型であるか、ポインタの深さ、構造体のフィールド構成など、型のすべての属性が考慮されます。したがって、float32MyFloat32eqtype関数によって異なる型であると正しく識別され、complex関数に渡された場合にエラーが報告されるようになります。これは、Go言語の型システムの厳密なルールをコンパイラが正しく適用するために不可欠な変更です。
  • エラーメッセージの変更:

    • 旧エラーメッセージ: "invalid operation: %N (complex of types %T, %T)"
    • 新エラーメッセージ: "invalid operation: %N (mismatched types %T and %T)" この変更は、エラーの原因が単に「complex型」であることではなく、「引数の型が一致しない」ことにあることをより明確にユーザーに伝えるためのものです。これにより、開発者は問題の根本原因をより迅速に特定し、修正することができます。

test/cmplx.goに追加されたテストケースは、この変更が意図通りに機能することを確認するためのものです。特に、complex(f32, F32)のような、基底型は同じだがユーザー定義型と基本型が混在するケースが正しくエラーとして扱われることを検証しています。

このコミットは、Go言語の型安全性を高め、言語仕様の厳密な遵守を保証する上で重要な改善です。

関連リンク

参考にした情報源リンク