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

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

このコミットは、Go言語のコンパイラにおける定数変換のバグ修正に関連しています。具体的には、負の整数定数を符号なし整数型(uint)に変換しようとした際に発生するコンパイルエラーを修正するためのテストケースを追加しています。この問題は、定数と変数の変換挙動の違いに起因していました。

コミット

commit b2e91a9a29bfb0aa34f9a24cce3682c3e0923346
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Mar 10 16:39:23 2009 -0700

    constant conversion of int (non-ideal) constant doesn't work
    
    R=rsc
    DELTA=20  (20 added, 0 deleted, 0 changed)
    OCL=26054
    CL=26062

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

https://github.com/golang/go/commit/b2e91a9a29bfb0aa34f9a24cce3682c3e0923346

元コミット内容

    constant conversion of int (non-ideal) constant doesn't work
    
    R=rsc
    DELTA=20  (20 added, 0 deleted, 0 changed)
    OCL=26054
    CL=26062

変更の背景

Go言語の初期開発段階において、コンパイラは定数と変数の型変換に関して一貫性のない挙動を示していました。特に、負の整数定数を符号なし整数型(uint)に変換しようとすると、コンパイルエラーが発生するというバグが存在しました。一方で、同じ負の整数値を保持する変数であれば、同様の変換が問題なく行えました。

このコミットは、この特定のバグを再現し、将来的な回帰を防ぐためのテストケース test/bugs/bug138.go を追加することを目的としています。コミットメッセージの「non-ideal constant」という表現は、負の定数を符号なし型に変換するという、通常はオーバーフローを引き起こす可能性のある操作を指していると考えられます。しかし、Go言語の型システムでは、このような変換がどのように扱われるべきか、特にコンパイル時定数と実行時変数の間で挙動が異なるべきではないという原則に基づき、この不整合が問題視されました。

このバグは、Go言語の型変換規則、特に定数と非定数の扱いにおける初期の設計上の課題を浮き彫りにしています。コンパイラは、定数に対してはより厳密なチェックを行う傾向がありますが、それが実行時の挙動と乖離する場合には、開発者にとって混乱の原因となります。このコミットは、そのような不整合を特定し、修正するための第一歩として、再現可能なテストケースを提供しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念と、一般的なプログラミング言語における型変換の知識が必要です。

  1. Go言語の型システム:

    • 整数型: Go言語には、int (符号付き整数) や uint (符号なし整数) など、様々なサイズの整数型があります。int はプラットフォームに依存するサイズ(32ビットまたは64ビット)を持ち、負の値を表現できます。uint は常に非負の値のみを表現できます。
    • 定数 (Constants): Go言語の定数は、コンパイル時に値が決定される不変のエンティティです。定数は型を持つこともできますが、型なし定数(untyped constant)として扱われることも多く、文脈に応じて適切な型に「デフォルトで型付け」されます。
    • 型変換 (Type Conversion): Go言語では、異なる型間の変換は明示的に行う必要があります(例: uint(c))。暗黙的な型変換はほとんど行われません。
  2. 符号付き整数と符号なし整数:

    • 符号付き整数: 最上位ビットを符号ビットとして使用し、正の値と負の値を表現します。例えば、8ビットの符号付き整数では、-128から127までの値を表現できます。
    • 符号なし整数: 全てのビットを値の表現に使用し、非負の値のみを表現します。例えば、8ビットの符号なし整数では、0から255までの値を表現できます。
    • 負の数の符号なし整数への変換: 負の符号付き整数を符号なし整数に変換する場合、通常は2の補数表現がそのまま解釈されるため、非常に大きな正の値になります(オーバーフロー)。例えば、8ビットシステムで -1uint8 に変換すると 255 になります。これは技術的にはオーバーフローですが、多くの言語ではこの挙動が定義されています。
  3. コンパイル時と実行時:

    • コンパイル時: ソースコードが機械語に変換される段階です。この段階で、型チェック、構文解析、最適化などが行われます。定数の値はコンパイル時に確定します。
    • 実行時: コンパイルされたプログラムが実際に動作する段階です。変数の値は実行時に決定され、変更される可能性があります。

このコミットで問題となっているのは、コンパイル時に行われる定数 -1uint への変換がエラーになる一方で、実行時に行われる変数 -1uint への変換が成功するという、コンパイル時と実行時の挙動の不整合です。Go言語の設計思想では、可能な限りコンパイル時にエラーを検出することが推奨されますが、このケースではその厳密さが、実行時の挙動との一貫性を損ねていました。

技術的詳細

このコミットが対処している問題は、Goコンパイラが定数と変数の型変換を異なる方法で処理していたことにあります。

Go言語の仕様では、定数式はコンパイル時に評価されます。負の整数定数(例: -1)を符号なし整数型(uint)に変換する場合、数学的にはオーバーフローが発生します。しかし、Go言語の定数変換のルールでは、このような変換が許可される場合があります。特に、定数が型なし定数である場合、その値がターゲット型で表現可能であれば変換が成功します。しかし、このバグのケースでは、const c int = -1 のように明示的に型付けされた定数 cuint(c) と変換しようとした際に、コンパイラがオーバーフローを検出してエラーとしていました。

一方で、var i int = -1; var xi uint = uint(i); のように変数 i を介して同じ変換を行う場合、これは実行時に評価されるため、Goのランタイムは通常、2の補数表現に基づいて負の値を符号なし型に変換します。例えば、int 型の -1 は、そのビットパターンが uint 型として解釈されると、その型の最大値(例: uint32 なら 0xFFFFFFFF)になります。この挙動は、C言語などの他の言語でも一般的であり、Go言語でも変数の場合はこの「ラップアラウンド」挙動が期待されていました。

このコミットが追加した bug138.go は、この不整合を明確に示しています。

func main() {
	const c int = -1;
	var i int = -1;
	var xc uint = uint(c);  // this does not work (コンパイルエラー)
	var xi uint = uint(i);  // this works (成功)
}

コンパイラは uint(c) の行で「overflow converting constant to uint」および「illegal combination of literals CONV 7」といったエラーを出力していました。これは、コンパイラが定数 c の値を -1 と認識し、それを uint に変換しようとした際に、uint が負の値を表現できないため、オーバーフローと判断したためです。しかし、変数の場合は、値がメモリ上に存在し、そのビットパターンがそのまま解釈されるため、エラーにはなりません。

このバグの修正は、コンパイラが定数変換を行う際のロジックを調整し、変数の変換挙動との一貫性を持たせることを含んでいたと考えられます。具体的には、型付き定数であっても、その値がターゲットの符号なし型で表現可能(ビットパターンとして解釈可能)であれば、エラーとせずに変換を許可するように変更されたと推測されます。これにより、開発者は定数と変数の変換挙動の違いに悩まされることなく、より予測可能なコードを書けるようになりました。

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

このコミットは、既存のコードの変更ではなく、新しいテストファイル test/bugs/bug138.go の追加と、そのテストの期待される出力 test/golden.out の更新が主な変更点です。

test/bugs/bug138.go の追加

--- /dev/null
+++ b/test/bugs/bug138.go
@@ -0,0 +1,19 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// $G $D/$F.go || echo BUG should compile
+
+package main
+
+func main() {
+	const c int = -1;
+	var i int = -1;
+	var xc uint = uint(c);  // this does not work
+	var xi uint = uint(i);  // this works
+}
+
+/*
+bug138.go:8: overflow converting constant to uint
+bug138.go:8: illegal combination of literals CONV 7
+*/

test/golden.out の更新

--- a/test/golden.out
+++ b/test/golden.out
@@ -154,6 +154,11 @@ bugs/bug137.go:9: break label is not defined: L2
 bugs/bug137.go:15: break label is not defined: L4
 BUG should compile
 
+=========== bugs/bug138.go
+bugs/bug138.go:8: overflow converting constant to uint
+bugs/bug138.go:8: illegal combination of literals CONV 7
+BUG should compile
+
 =========== fixedbugs/bug016.go
 fixedbugs/bug016.go:7: overflow converting constant to uint
 

コアとなるコードの解説

test/bugs/bug138.go

このファイルは、Goコンパイラのバグを再現するための最小限のコードを含んでいます。

  • const c int = -1;: 符号付き整数型 int の定数 c-1 で宣言しています。ここで int と明示的に型付けされている点が重要です。
  • var i int = -1;: 符号付き整数型 int の変数 i-1 で宣言しています。
  • var xc uint = uint(c);: 定数 cuint 型に変換しようとしています。この行が、バグの存在下ではコンパイルエラーを引き起こします。
  • var xi uint = uint(i);: 変数 iuint 型に変換しようとしています。この行は、バグの存在下でも問題なくコンパイルされます。

ファイル末尾のコメントブロック /* ... */ は、このテストケースが期待するコンパイラのエラーメッセージを示しています。これは、テストが実行された際に、これらのエラーメッセージが実際に出力されることを検証するためのものです。BUG should compile というコメントは、このテストが本来はコンパイルが成功すべきであるという意図を示しており、このコミットがバグ修正のテストであることを強調しています。つまり、このテストは、バグが修正されるまではコンパイルエラーを出すが、修正後はコンパイルが成功するようになることを期待しています。

test/golden.out

test/golden.out は、Go言語のテストスイートにおいて、特定のテストケースが生成する標準出力(コンパイルエラーメッセージなど)の「ゴールデン」リファレンスとして機能するファイルです。コンパイラやツールの挙動が変更された場合、このファイルも更新され、新しい期待される出力が反映されます。

このコミットでは、bug138.go の追加に伴い、そのコンパイルエラーメッセージが golden.out に追加されています。これにより、将来的にコンパイラの挙動が変更され、このバグが再発した場合に、テストスイートが失敗することでそれを検出できるようになります。

このコミット自体はバグを修正するコード変更を含んでいませんが、バグを再現するテストケースを追加することで、その後の修正作業をガイドし、修正が正しく行われたことを検証するための基盤を築いています。実際のバグ修正は、このコミットの後に続く別のコミットで行われたと考えられます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコードリポジトリ (GitHub):
  • Go言語の初期のバグトラッカーやメーリングリストのアーカイブ (当時の議論を追うことで、より詳細な背景がわかる可能性がありますが、一般公開されていない場合もあります)。
  • Go言語の仕様書 (Go Language Specification):
  • 2の補数表現に関する一般的なコンピュータサイエンスの資料。