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

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

このコミットは、Go言語のコンパイラ(gc)において、浮動小数点数に対する剰余演算子(%)が定数式で使用された場合に発生するエラーメッセージを改善することを目的としています。具体的には、以前は不明瞭だったエラーメッセージを、より具体的で理解しやすいものに修正し、関連するテストケースを追加しています。

コミット

commit 83feedf7bf7147021761fd8b5a2a157095fcabc9
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 19 00:12:31 2012 -0500

    gc: fix error for floating-point constant %
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5674108
---
 src/cmd/gc/const.c |  8 ++++++++\n test/const1.go     | 55 +++++++++++++++++++++++++++---------------------------\n 2 files changed, 36 insertions(+), 27 deletions(-)\n

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

https://github.com/golang/go/commit/83feedf7bf7147021761fd8b5a2a157095fcabc9

元コミット内容

gc: fix error for floating-point constant %

R=ken2
CC=golang-dev
https://golang.org/cl/5674108

変更の背景

Go言語では、浮動小数点数に対する剰余演算子(%)は許可されていません。これは、浮動小数点数の特性上、剰余演算の定義が整数型の場合と異なり、結果が一意に定まりにくい、あるいは数学的に意味をなさない場合があるためです。しかし、Goコンパイラ(gc)の定数評価部分において、この不正な操作が定数式として記述された際に、コンパイラが生成するエラーメッセージが「ideal % ideal」のような、抽象的でユーザーにとって理解しにくいものでした。

このコミットの背景には、Go言語のユーザーエクスペリエンスを向上させるという目的があります。コンパイラのエラーメッセージは、開発者がコードの問題を迅速に特定し、修正するために非常に重要です。不明瞭なエラーメッセージは、デバッグ時間を増加させ、開発者のフラストレーションにつながります。したがって、浮動小数点定数に対する不正な剰余演算が検出された際に、より具体的で分かりやすいエラーメッセージを提示することで、開発者がGo言語の仕様を正しく理解し、適切なコードを書く手助けをすることが求められました。

前提知識の解説

Go言語のコンパイラ (gc)

gc は、Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担っています。Go言語のツールチェインの中核をなすコンポーネントの一つです。gc は、構文解析、型チェック、最適化、コード生成など、コンパイルの様々な段階を実行します。

定数式 (Constant Expression)

定数式とは、コンパイル時にその値が完全に決定される式のことです。Go言語では、数値、真偽値、文字列などのリテラル、およびそれらを用いた演算の結果が定数式となり得ます。定数式は、変数の初期化、配列のサイズ指定、case 文のラベルなど、コンパイル時に値が確定している必要がある場所で使用されます。Go言語の定数には「型なし定数(untyped constant)」という概念があり、これは特定の型に束縛されず、必要に応じて型推論される柔軟な定数を指します。

浮動小数点数 (Floating-point numbers)

浮動小数点数は、実数を近似的に表現するためのデータ型です。Go言語では float32float64 があります。これらは、非常に大きな数や非常に小さな数、あるいは小数点以下の値を持つ数を表現するのに適していますが、精度には限界があります。

剰余演算子 (%)

剰余演算子 % は、一方の数値をもう一方の数値で割ったときの余りを計算します。例えば、10 % 31 となります。伝統的に、この演算子は整数型に対して適用されることが一般的です。浮動小数点数に対する剰余演算は、数学的な定義が複数存在したり、結果が直感的でなかったりするため、多くのプログラミング言語ではサポートされていないか、異なる関数(例: math.Mod)として提供されています。Go言語の組み込みの % 演算子は、整数型にのみ適用可能です。

yyerror

yyerror は、コンパイラやパーサーの文脈でよく見られるエラー報告関数です。通常、字句解析器(lexer)や構文解析器(parser)が構文エラーやセマンティックエラーを検出した際に、この関数を呼び出してエラーメッセージを出力します。gc コンパイラ内部でも、不正なコード構造や型エラーなどを検出した際に yyerror を用いてユーザーにエラーを通知します。

src/cmd/gc/const.c

このファイルは、Goコンパイラ (gc) の中で定数式の評価を担当する部分のソースコードです。Go言語のプログラム内で使用される定数式(例: 1 + 23.14 * 2)は、コンパイル時にその値が計算されます。const.c は、これらの定数式の構文木を走査し、その値を計算するロジックを含んでいます。

技術的詳細

このコミットの核心は、src/cmd/gc/const.c ファイル内の evconst 関数に対する変更です。evconst 関数は、Goコンパイラが定数式を評価する際に呼び出される主要な関数です。この関数は、与えられたノード(抽象構文木の一部)が表す定数式の値を計算します。

変更前は、浮動小数点定数に対する剰余演算(例: 1000 % 1e3)が evconst 関数に渡された場合、この特定のケースを処理する明示的なロジックが存在しませんでした。そのため、コンパイラは一般的なエラーハンドリングパスにフォールバックし、結果として「ideal % ideal」のような、オペランドの「理想型」を示す抽象的なエラーメッセージを出力していました。これは、Go言語の仕様として浮動小数点数に対する % 演算が許可されていないにもかかわらず、その理由を明確に示さない不親切なエラーでした。

今回の変更では、evconst 関数内に以下の新しい case ブロックが追加されました。

case TUP(OMOD, CTFLT):
    // The default case above would print 'ideal % ideal',
    // which is not quite an ideal error.
    if(!n->diag) {
        yyerror("illegal constant expression: floating-point %% operation");
        n->diag = 1;
    }
    return;
  • TUP(OMOD, CTFLT): これは、Goコンパイラの内部表現で、演算子とオペランドの型の組み合わせを表すマクロです。

    • OMOD: 剰余演算子 % を表す内部コードです。
    • CTFLT: 浮動小数点定数型を表す内部コードです。 この case は、「剰余演算子 % が浮動小数点定数に適用された場合」という特定の状況を正確に捕捉します。
  • if(!n->diag): n->diag は、特定のノードに対して既にエラーが報告されているかどうかを示すフラグです。このチェックにより、同じ定数式に対して複数の重複したエラーメッセージが出力されるのを防ぎます。エラーがまだ報告されていない場合にのみ、以下のエラーメッセージが出力されます。

  • yyerror("illegal constant expression: floating-point %% operation");: ここで、より具体的で分かりやすいエラーメッセージが yyerror 関数を通じて出力されます。「illegal constant expression: floating-point % operation」(不正な定数式:浮動小数点数に対する % 演算)というメッセージは、問題の原因(浮動小数点数に対する % 演算)とそれが定数式であることの両方を明確に示します。

  • n->diag = 1;: エラーが報告された後、n->diag フラグを 1 に設定し、将来的にこのノードで再度エラーが報告されるのを防ぎます。

  • return;: この case が処理された後、関数から即座にリターンし、これ以上この定数式を評価しようとしないようにします。

この変更により、コンパイラは不正な浮動小数点剰余演算を早期に、かつ明確なメッセージで検出できるようになりました。

また、test/const1.go ファイルには、この新しいエラーハンドリングが正しく機能するかを検証するためのテストケースが追加されました。

c6 = 1000 % 1e3 // ERROR "floating-point % operation"

この行は、1000(整数定数)と 1e3(浮動小数点定数、1000.0 を意味する)の間で剰余演算を行っています。Go言語の定数評価ルールでは、異なる型の定数間の演算は、より表現力の高い型に昇格されるため、この式は浮動小数点数に対する剰余演算として扱われます。// ERROR "floating-point % operation" コメントは、この行がコンパイル時に指定されたエラーメッセージを生成することを期待していることを示しており、コンパイラの修正が意図通りに機能していることを確認します。

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

src/cmd/gc/const.c

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -660,6 +660,14 @@ evconst(Node *n)
 		}
 		mpdivfltflt(v.u.fval, rv.u.fval);
 		break;
+	case TUP(OMOD, CTFLT):
+		// The default case above would print 'ideal % ideal',
+		// which is not quite an ideal error.
+		if(!n->diag) {
+			yyerror("illegal constant expression: floating-point %% operation");
+			n->diag = 1;
+		}
+		return;
 	case TUP(OADD, CTCPLX):
 		mpaddfltflt(&v.u.cval->real, &rv.u.cval->real);
 		mpaddfltflt(&v.u.cval->imag, &rv.u.cval->imag);

test/const1.go

--- a/test/const1.go
+++ b/test/const1.go
@@ -38,32 +38,33 @@ var (
 	a8 = Int8 * Const / 100      // ERROR "overflow"
 	a9 = Int8 * (Const / 100)    // OK
 
-	b1   = Uint8 * Uint8                                 // ERROR "overflow"
-	b2   = Uint8 * -1                                    // ERROR "overflow"
-	b3   = Uint8 - Uint8                                 // OK
-	b4   = Uint8 - Uint8 - Uint8                         // ERROR "overflow"
-	b5   = uint8(^0)                                     // ERROR "overflow"
-	b6   = ^uint8(0)                                     // OK
-	b7   = uint8(Minus1)                                 // ERROR "overflow"
-	b8   = uint8(int8(-1))                               // ERROR "overflow"
-	b8a  = uint8(-1)                                     // ERROR "overflow"
-	b9   byte                    = (1 << 10) >> 8        // OK
-	b10  byte                    = (1 << 10)             // ERROR "overflow"
-	b11  byte                    = (byte(1) << 10) >> 8  // ERROR "overflow"
-	b12  byte                    = 1000                  // ERROR "overflow"
-	b13  byte                    = byte(1000)            // ERROR "overflow"
-	b14  byte                    = byte(100) * byte(100) // ERROR "overflow"
-	b15  byte                    = byte(100) * 100       // ERROR "overflow"
-	b16  byte                    = byte(0) * 1000        // ERROR "overflow"
-	b16a byte                    = 0 * 1000              // OK
-	b17  byte                    = byte(0) * byte(1000)  // ERROR "overflow"
-	b18  byte                    = Uint8 / 0             // ERROR "division by zero"
+	b1        = Uint8 * Uint8         // ERROR "overflow"
+	b2        = Uint8 * -1            // ERROR "overflow"
+	b3        = Uint8 - Uint8         // OK
+	b4        = Uint8 - Uint8 - Uint8 // ERROR "overflow"
+	b5        = uint8(^0)             // ERROR "overflow"
+	b6        = ^uint8(0)             // OK
+	b7        = uint8(Minus1)         // ERROR "overflow"
+	b8        = uint8(int8(-1))       // ERROR "overflow"
+	b8a       = uint8(-1)             // ERROR "overflow"
+	b9   byte = (1 << 10) >> 8        // OK
+	b10  byte = (1 << 10)             // ERROR "overflow"
+	b11  byte = (byte(1) << 10) >> 8  // ERROR "overflow"
+	b12  byte = 1000                  // ERROR "overflow"
+	b13  byte = byte(1000)            // ERROR "overflow"
+	b14  byte = byte(100) * byte(100) // ERROR "overflow"
+	b15  byte = byte(100) * 100       // ERROR "overflow"
+	b16  byte = byte(0) * 1000        // ERROR "overflow"
+	b16a byte = 0 * 1000              // OK
+	b17  byte = byte(0) * byte(1000)  // ERROR "overflow"
+	b18  byte = Uint8 / 0             // ERROR "division by zero"
 
-	c1 float64     = Big
-	c2 float64     = Big * Big          // ERROR "overflow"
-	c3 float64     = float64(Big) * Big // ERROR "overflow"
-	c4 = Big * Big                      // ERROR "overflow"
-	c5 = Big / 0                        // ERROR "division by zero"
+	c1 float64 = Big
+	c2 float64 = Big * Big          // ERROR "overflow"
+	c3 float64 = float64(Big) * Big // ERROR "overflow"
+	c4         = Big * Big          // ERROR "overflow"
+	c5         = Big / 0            // ERROR "division by zero"
+	c6         = 1000 % 1e3         // ERROR "floating-point % operation"
 )
 
 func f(int)

コアとなるコードの解説

src/cmd/gc/const.c の変更

evconst 関数は、Goコンパイラが定数式を評価する際の中心的なロジックを含んでいます。追加された case TUP(OMOD, CTFLT): ブロックは、コンパイラが抽象構文木を走査している際に、剰余演算子 (OMOD) の左辺または右辺、あるいは両方が浮動小数点定数 (CTFLT) である式に遭遇した場合に実行されます。

このブロックの目的は、Go言語の仕様で許可されていない浮動小数点数に対する剰余演算が定数式で行われた場合に、コンパイラがより明確なエラーメッセージを生成することです。

  • // The default case above would print 'ideal % ideal', ...: このコメントは、なぜこの特定の case が必要なのかを説明しています。以前は、この種の不正な操作が検出された際に、evconst 関数の他の一般的なエラーハンドリングにフォールバックし、その結果「ideal % ideal」という不明瞭なエラーメッセージが出力されていました。
  • if(!n->diag): n は現在評価中のノード(定数式)を表します。n->diag は、このノードに対して既にエラーが報告されているかどうかを示すフラグです。この条件により、同じエラーが複数回報告されるのを防ぎ、コンパイラの出力が冗長になるのを避けます。
  • yyerror("illegal constant expression: floating-point %% operation");: この行が、新しい、より具体的なエラーメッセージを生成します。yyerror はコンパイラのエラー報告メカニズムの一部です。メッセージ内の %% は、C言語の書式指定文字列において % 文字自体を出力するためのエスケープシーケンスです。このメッセージは、問題が「不正な定数式」であり、具体的には「浮動小数点数に対する剰余演算」であるということを明確に伝えます。
  • n->diag = 1;: エラーが報告された後、n->diag フラグを 1 に設定し、このノードに対する将来のエラー報告を抑制します。
  • return;: この case が処理された後、evconst 関数から即座にリターンします。これは、不正な定数式であるため、それ以上の評価は不要であり、意味がないためです。

この変更により、Goコンパイラは、浮動小数点数に対する剰余演算が定数式で使用された場合に、開発者にとってより役立つエラーメッセージを提供するようになりました。

test/const1.go の変更

test/const1.go は、Goコンパイラの定数評価に関する様々なテストケースを含むファイルです。このコミットでは、新しいテストケース c6 が追加されました。

  • c6 = 1000 % 1e3 // ERROR "floating-point % operation":
    • 1000: これは型なしの整数定数です。
    • 1e3: これは型なしの浮動小数点定数で、1000.0 を意味します。
    • Go言語の定数評価ルールでは、異なる型の型なし定数間の演算は、より表現力の高い型(この場合は浮動小数点型)に昇格されます。したがって、この式は実質的に浮動小数点数に対する剰余演算となります。
    • // ERROR "floating-point % operation": このコメントは、Goのテストフレームワークが使用する特別なディレクティブです。この行がコンパイルされた際に、コンパイラが正確に「floating-point % operation」という文字列を含むエラーメッセージを出力することを期待していることを示します。もしこのエラーメッセージが出力されなかったり、異なるメッセージが出力されたりした場合、テストは失敗します。

このテストケースの追加により、src/cmd/gc/const.c で行われたコンパイラの変更が、意図通りに機能し、正しいエラーメッセージを生成していることが自動的に検証されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • Go言語のコンパイラに関する技術ブログや解説記事 (一般的な知識として参照)
  • Go言語のIssueトラッカーやChange List (CL) (コミットメッセージに記載のCLリンクから辿れる情報)

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

このコミットは、Go言語のコンパイラ(gc)において、浮動小数点数に対する剰余演算子(%)が定数式で使用された場合に発生するエラーメッセージを改善することを目的としています。具体的には、以前は不明瞭だったエラーメッセージを、より具体的で理解しやすいものに修正し、関連するテストケースを追加しています。

コミット

commit 83feedf7bf7147021761fd8b5a2a157095fcabc9
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 19 00:12:31 2012 -0500

    gc: fix error for floating-point constant %
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5674108

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

https://github.com/golang/go/commit/83feedf7bf7147021761fd8b5a2a157095fcabc9

元コミット内容

gc: fix error for floating-point constant %

R=ken2
CC=golang-dev
https://golang.org/cl/5674108

変更の背景

Go言語では、浮動小数点数に対する剰余演算子(%)は許可されていません。これは、浮動小数点数の特性上、剰余演算の定義が整数型の場合と異なり、結果が一意に定まりにくい、あるいは数学的に意味をなさない場合があるためです。Go言語で浮動小数点数の剰余を計算したい場合は、math パッケージの math.Mod() 関数を使用する必要があります。

しかし、Goコンパイラ(gc)の定数評価部分において、この不正な操作が定数式として記述された際に、コンパイラが生成するエラーメッセージが「ideal % ideal」のような、抽象的でユーザーにとって理解しにくいものでした。この「ideal」という表現は、Go言語の「型なし定数(untyped constant)」の概念に由来しますが、一般的な開発者にとっては直感的ではありませんでした。

このコミットの背景には、Go言語のユーザーエクスペリエンスを向上させるという目的があります。コンパイラのエラーメッセージは、開発者がコードの問題を迅速に特定し、修正するために非常に重要です。不明瞭なエラーメッセージは、デバッグ時間を増加させ、開発者のフラストレーションにつながります。したがって、浮動小数点定数に対する不正な剰余演算が検出された際に、より具体的で分かりやすいエラーメッセージを提示することで、開発者がGo言語の仕様を正しく理解し、適切なコードを書く手助けをすることが求められました。

前提知識の解説

Go言語のコンパイラ (gc)

gc は、Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担っています。Go言語のツールチェインの中核をなすコンポーネントの一つです。gc は、構文解析、型チェック、最適化、コード生成など、コンパイルの様々な段階を実行します。

定数式 (Constant Expression)

定数式とは、コンパイル時にその値が完全に決定される式のことです。Go言語では、数値、真偽値、文字列などのリテラル、およびそれらを用いた演算の結果が定数式となり得ます。定数式は、変数の初期化、配列のサイズ指定、case 文のラベルなど、コンパイル時に値が確定している必要がある場所で使用されます。Go言語の定数には「型なし定数(untyped constant)」という概念があり、これは特定の型に束縛されず、必要に応じて型推論される柔軟な定数を指します。例えば、1000 は型なし整数定数、1e3 は型なし浮動小数点定数です。型なし定数同士の演算では、より表現力の高い型に昇格されることがあります。

浮動小数点数 (Floating-point numbers)

浮動小数点数は、実数を近似的に表現するためのデータ型です。Go言語では float32float64 があります。これらは、非常に大きな数や非常に小さな数、あるいは小数点以下の値を持つ数を表現するのに適していますが、精度には限界があります。浮動小数点数の演算は、整数演算とは異なる特性を持ち、特に剰余演算においてはその定義が複雑になるため、多くのプログラミング言語では専用の関数が提供されています。

剰余演算子 (%)

剰余演算子 % は、一方の数値をもう一方の数値で割ったときの余りを計算します。例えば、10 % 31 となります。Go言語において、組み込みの % 演算子は整数型にのみ適用可能です。浮動小数点数に対する剰余演算は、math.Mod(x, y) 関数を使用します。この関数は、x - y * trunc(x/y) のように計算され、結果の符号は x の符号と同じになります。

yyerror

yyerror は、コンパイラやパーサーの文脈でよく見られるエラー報告関数です。通常、字句解析器(lexer)や構文解析器(parser)が構文エラーやセマンティックエラーを検出した際に、この関数を呼び出してエラーメッセージを出力します。gc コンパイラ内部でも、不正なコード構造や型エラーなどを検出した際に yyerror を用いてユーザーにエラーを通知します。

src/cmd/gc/const.c

このファイルは、Goコンパイラ (gc) の中で定数式の評価を担当する部分のソースコードです。Go言語のプログラム内で使用される定数式(例: 1 + 23.14 * 2)は、コンパイル時にその値が計算されます。const.c は、これらの定数式の構文木を走査し、その値を計算するロジックを含んでいます。

技術的詳細

このコミットの核心は、src/cmd/gc/const.c ファイル内の evconst 関数に対する変更です。evconst 関数は、Goコンパイラが定数式を評価する際に呼び出される主要な関数です。この関数は、与えられたノード(抽象構文木の一部)が表す定数式の値を計算します。

変更前は、浮動小数点定数に対する剰余演算(例: 1000 % 1e3)が evconst 関数に渡された場合、この特定のケースを処理する明示的なロジックが存在しませんでした。そのため、コンパイラは一般的なエラーハンドリングパスにフォールバックし、結果として「ideal % ideal」のような、オペランドの「理想型」を示す抽象的なエラーメッセージを出力していました。これは、Go言語の仕様として浮動小数点数に対する % 演算が許可されていないにもかかわらず、その理由を明確に示さない不親切なエラーでした。

今回の変更では、evconst 関数内に以下の新しい case ブロックが追加されました。

case TUP(OMOD, CTFLT):
    // The default case above would print 'ideal % ideal',
    // which is not quite an ideal error.
    if(!n->diag) {
        yyerror("illegal constant expression: floating-point %% operation");
        n->diag = 1;
    }
    return;
  • TUP(OMOD, CTFLT): これは、Goコンパイラの内部表現で、演算子とオペランドの型の組み合わせを表すマクロです。

    • OMOD: 剰余演算子 % を表す内部コードです。
    • CTFLT: 浮動小数点定数型を表す内部コードです。 この case は、「剰余演算子 % が浮動小数点定数に適用された場合」という特定の状況を正確に捕捉します。Go言語の型なし定数のルールにより、1000 % 1e3 のような式は、両方のオペランドが型なし定数であるため、より表現力の高い浮動小数点型に昇格され、この case にマッチします。
  • if(!n->diag): n->diag は、特定のノードに対して既にエラーが報告されているかどうかを示すフラグです。このチェックにより、同じ定数式に対して複数の重複したエラーメッセージが出力されるのを防ぎます。エラーがまだ報告されていない場合にのみ、以下のエラーメッセージが出力されます。

  • yyerror("illegal constant expression: floating-point %% operation");: ここで、より具体的で分かりやすいエラーメッセージが yyerror 関数を通じて出力されます。「illegal constant expression: floating-point % operation」(不正な定数式:浮動小数点数に対する % 演算)というメッセージは、問題の原因(浮動小数点数に対する % 演算)とそれが定数式であることの両方を明確に示します。%% は、C言語の書式指定文字列において % 文字自体を出力するためのエスケープシーケンスです。

  • n->diag = 1;: エラーが報告された後、n->diag フラグを 1 に設定し、将来的にこのノードで再度エラーが報告されるのを防ぎます。

  • return;: この case が処理された後、関数から即座にリターンし、これ以上この定数式を評価しようとしないようにします。これは、不正な操作であるため、それ以上の評価は無意味だからです。

この変更により、コンパイラは不正な浮動小数点剰余演算を早期に、かつ明確なメッセージで検出できるようになりました。

また、test/const1.go ファイルには、この新しいエラーハンドリングが正しく機能するかを検証するためのテストケースが追加されました。

c6 = 1000 % 1e3 // ERROR "floating-point % operation"

この行は、1000(型なし整数定数)と 1e3(型なし浮動小数点定数、1000.0 を意味する)の間で剰余演算を行っています。Go言語の定数評価ルールでは、異なる型の型なし定数間の演算は、より表現力の高い型に昇格されるため、この式は浮動小数点数に対する剰余演算として扱われます。// ERROR "floating-point % operation" コメントは、この行がコンパイル時に指定されたエラーメッセージを生成することを期待していることを示しており、コンパイラの修正が意図通りに機能していることを確認します。

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

src/cmd/gc/const.c

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -660,6 +660,14 @@ evconst(Node *n)
 		}
 		mpdivfltflt(v.u.fval, rv.u.fval);
 		break;
+	case TUP(OMOD, CTFLT):
+		// The default case above would print 'ideal % ideal',
+		// which is not quite an ideal error.
+		if(!n->diag) {
+			yyerror("illegal constant expression: floating-point %% operation");
+			n->diag = 1;
+		}
+		return;
 	case TUP(OADD, CTCPLX):
 		mpaddfltflt(&v.u.cval->real, &rv.u.cval->real);
 		mpaddfltflt(&v.u.cval->imag, &rv.u.cval->imag);

test/const1.go

--- a/test/const1.go
+++ b/test/const1.go
@@ -38,32 +38,33 @@ var (
 	a8 = Int8 * Const / 100      // ERROR "overflow"
 	a9 = Int8 * (Const / 100)    // OK
 
-	b1   = Uint8 * Uint8                                 // ERROR "overflow"
-	b2   = Uint8 * -1                                    // ERROR "overflow"
-	b3   = Uint8 - Uint8                                 // OK
-	b4   = Uint8 - Uint8 - Uint8                         // ERROR "overflow"
-	b5   = uint8(^0)                                     // ERROR "overflow"
-	b6   = ^uint8(0)                                     // OK
-	b7   = uint8(Minus1)                                 // ERROR "overflow"
-	b8   = uint8(int8(-1))                               // ERROR "overflow"
-	b8a  = uint8(-1)                                     // ERROR \"overflow\"
-	b9   byte                    = (1 << 10) >> 8        // OK
-	b10  byte                    = (1 << 10)             // ERROR "overflow"
-	b11  byte                    = (byte(1) << 10) >> 8  // ERROR "overflow"
-	b12  byte                    = 1000                  // ERROR "overflow"
-	b13  byte                    = byte(1000)            // ERROR "overflow"
-	b14  byte                    = byte(100) * byte(100) // ERROR "overflow"
-	b15  byte                    = byte(100) * 100       // ERROR "overflow"
-	b16  byte                    = byte(0) * 1000        // ERROR "overflow"
-	b16a byte                    = 0 * 1000              // OK
-	b17  byte                    = byte(0) * byte(1000)  // ERROR "overflow"
-	b18  byte                    = Uint8 / 0             // ERROR "division by zero"
+	b1        = Uint8 * Uint8         // ERROR "overflow"
+	b2        = Uint8 * -1            // ERROR "overflow"
+	b3        = Uint8 - Uint8         // OK
+	b4        = Uint8 - Uint8 - Uint8 // ERROR "overflow"
+	b5        = uint8(^0)             // ERROR "overflow"
+	b6        = ^uint8(0)             // OK
+	b7        = uint8(Minus1)         // ERROR "overflow"
+	b8        = uint8(int8(-1))       // ERROR "overflow"
+	b8a       = uint8(-1)             // ERROR "overflow"
+	b9   byte = (1 << 10) >> 8        // OK
+	b10  byte = (1 << 10)             // ERROR "overflow"
+	b11  byte = (byte(1) << 10) >> 8  // ERROR "overflow"
+	b12  byte = 1000                  // ERROR "overflow"
+	b13  byte = byte(1000)            // ERROR "overflow"
+	b14  byte = byte(100) * byte(100) // ERROR "overflow"
+	b15  byte = byte(100) * 100       // ERROR "overflow"
+	b16  byte = byte(0) * 1000        // ERROR "overflow"
+	b16a byte = 0 * 1000              // OK
+	b17  byte = byte(0) * byte(1000)  // ERROR "overflow"
+	b18  byte = Uint8 / 0             // ERROR "division by zero"
 
-	c1 float64     = Big
-	c2 float64     = Big * Big          // ERROR "overflow"
-	c3 float64     = float64(Big) * Big // ERROR "overflow"
-	c4 = Big * Big                      // ERROR "overflow"
-	c5 = Big / 0                        // ERROR "division by zero"
+	c1 float64 = Big
+	c2 float64 = Big * Big          // ERROR "overflow"
+	c3 float64 = float64(Big) * Big // ERROR "overflow"
+	c4         = Big * Big          // ERROR "overflow"
+	c5         = Big / 0            // ERROR "division by zero"
+	c6         = 1000 % 1e3         // ERROR "floating-point % operation"
 )
 
 func f(int)

コアとなるコードの解説

src/cmd/gc/const.c の変更

evconst 関数は、Goコンパイラが定数式を評価する際の中心的なロジックを含んでいます。追加された case TUP(OMOD, CTFLT): ブロックは、コンパイラが抽象構文木を走査している際に、剰余演算子 (OMOD) の左辺または右辺、あるいは両方が浮動小数点定数 (CTFLT) である式に遭遇した場合に実行されます。

このブロックの目的は、Go言語の仕様で許可されていない浮動小数点数に対する剰余演算が定数式で行われた場合に、コンパイラがより明確なエラーメッセージを生成することです。

  • // The default case above would print 'ideal % ideal', ...: このコメントは、なぜこの特定の case が必要なのかを説明しています。以前は、この種の不正な操作が検出された際に、evconst 関数の他の一般的なエラーハンドリングにフォールバックし、その結果「ideal % ideal」という不明瞭なエラーメッセージが出力されていました。
  • if(!n->diag): n は現在評価中のノード(定数式)を表します。n->diag は、このノードに対して既にエラーが報告されているかどうかを示すフラグです。この条件により、同じエラーが複数回報告されるのを防ぎ、コンパイラの出力が冗長になるのを避けます。
  • yyerror("illegal constant expression: floating-point %% operation");: この行が、新しい、より具体的なエラーメッセージを生成します。yyerror はコンパイラのエラー報告メカニズムの一部です。メッセージ内の %% は、C言語の書式指定文字列において % 文字自体を出力するためのエスケープシーケンスです。このメッセージは、問題が「不正な定数式」であり、具体的には「浮動小数点数に対する剰余演算」であるということを明確に伝えます。
  • n->diag = 1;: エラーが報告された後、n->diag フラグを 1 に設定し、このノードに対する将来のエラー報告を抑制します。
  • return;: この case が処理された後、evconst 関数から即座にリターンします。これは、不正な定数式であるため、それ以上の評価は不要であり、意味がないためです。

この変更により、Goコンパイラは、浮動小数点数に対する剰余演算が定数式で使用された場合に、開発者にとってより役立つエラーメッセージを提供するようになりました。

test/const1.go の変更

test/const1.go は、Goコンパイラの定数評価に関する様々なテストケースを含むファイルです。このコミットでは、新しいテストケース c6 が追加されました。

  • c6 = 1000 % 1e3 // ERROR "floating-point % operation":
    • 1000: これは型なしの整数定数です。
    • 1e3: これは型なしの浮動小数点定数で、1000.0 を意味します。
    • Go言語の定数評価ルールでは、異なる型の型なし定数間の演算は、より表現力の高い型(この場合は浮動小数点型)に昇格されます。したがって、この式は実質的に浮動小数点数に対する剰余演算となります。
    • // ERROR "floating-point % operation": このコメントは、Goのテストフレームワークが使用する特別なディレクティブです。この行がコンパイルされた際に、コンパイラが正確に「floating-point % operation」という文字列を含むエラーメッセージを出力することを期待していることを示します。もしこのエラーメッセージが出力されなかったり、異なるメッセージが出力されたりした場合、テストは失敗します。

このテストケースの追加により、src/cmd/gc/const.c で行われたコンパイラの変更が、意図通りに機能し、正しいエラーメッセージを生成していることが自動的に検証されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • Go言語のコンパイラに関する技術ブログや解説記事 (一般的な知識として参照)
  • Go言語のIssueトラッカーやChange List (CL) (コミットメッセージに記載のCLリンクから辿れる情報)
  • Web検索: "Go language floating point modulo operator" (Go言語における浮動小数点数の剰余演算に関する情報収集)