[インデックス 10677] ファイルの概要
このコミットは、Go言語のコンパイラ、標準ライブラリ、ツールチェインにおける「新しいデフォルト型ルール」の導入に伴う広範な変更を反映しています。特に、rune
型(GoにおけるUnicodeコードポイントを表す型)の扱いがより明確かつ一貫したものになるように、文字リテラルの型推論とスキャナの挙動が更新されています。これにより、コードの可読性と型安全性が向上し、rune
型がより自然に扱えるようになります。
コミット
commit a250f37cbc93a0d625741b0d380154ed3a94ca09
Author: Russ Cox <rsc@golang.org>
Date: Thu Dec 8 22:08:03 2011 -0500
update tree for new default type rule
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5448091
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a250f37cbc93a0d625741b0d380154ed3a94ca09
元コミット内容
update tree for new default type rule
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5448091
変更の背景
Go言語では、rune
型はUnicodeのコードポイントを表すために使用される組み込みのエイリアス型であり、実体はint32
です。しかし、初期のGoでは、文字リテラル(例: 'a'
, '\n'
, '\u1234'
)は文脈によってint
型として扱われたり、明示的にrune()
でキャストする必要があったりするなど、その型推論に一貫性がない部分がありました。
このコミットの背景には、rune
型をより第一級の市民として扱い、文字リテラルがデフォルトでrune
型(または型なしのrune
定数)として解釈されるようにするという言語設計の変更があります。これにより、開発者がrune
を扱う際の冗長な型変換を減らし、コードの意図をより明確にすることが目的です。特に、fmt
パッケージの%c
フォーマット指定子がint
ではなく直接rune
をスキャンできるようになるなど、rune
の利用がより直感的になります。
前提知識の解説
- Go言語の
rune
型: Go言語において、rune
はUnicodeのコードポイントを表すために使用される組み込みの型です。これはint32
のエイリアスであり、単一のUnicode文字を格納できます。Goの文字列はUTF-8でエンコードされたバイト列であり、rune
は文字列内の個々の文字(コードポイント)を扱う際に重要になります。 - 文字リテラル: Goにおける文字リテラルは、単一引用符で囲まれた文字(例:
'a'
,'世'
)、エスケープシーケンス(例:'\n'
,'\t'
)、またはUnicodeコードポイント(例:'\u0041'
,'\U00000041'
)で表現されます。 go/scanner
パッケージ: Goのソースコードを字句解析(トークン化)するためのパッケージです。このパッケージは、ソースコードを識別子、キーワード、リテラルなどのトークンに分割します。scanner.Mode
:go/scanner
パッケージのScanner
構造体にはMode
フィールドがあり、スキャナがどのような種類のトークンを認識するかを設定できます。例えば、scanner.ScanInts
は整数リテラルを、scanner.ScanStrings
は文字列リテラルを認識します。- 型なし定数: Goには「型なし定数」という概念があります。これは、リテラル(数値、文字、文字列など)が特定の型を持たず、使用される文脈によって適切な型に推論されるというものです。例えば、
const x = 10
の場合、x
は型なしの整数定数であり、int
、int32
、float64
など、様々な数値型に割り当てることができます。このコミットでは、文字リテラルが型なしのrune
定数として扱われるようになる変更が含まれています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
go/scanner
の挙動変更:src/pkg/exp/types/gcimporter.go
において、p.scanner.Mode
にscanner.ScanChars
が追加されました。これは、スキャナが文字リテラル(例:'a'
,'\u1234'
)を独立したトークンタイプscanner.Char
として認識するようになることを意味します。以前は、これらのリテラルはscanner.Ints
として扱われるか、あるいは他のモードで処理される可能性がありました。この変更により、文字リテラルがrune
型としてより正確に識別され、型推論の基盤が強化されます。gcimporter.go
のparseConstDecl
関数内で、scanner.Char
トークンを直接処理する新しいケースが追加されました。これにより、文字リテラルが定数宣言内で適切に解析され、rune
型の定数として扱われるようになります。
-
文字リテラルの型推論の変更:
- 多くのファイル(
src/cmd/cgo/gcc.go
,src/cmd/godoc/dirtrees.go
,src/pkg/bytes/bytes.go
,src/pkg/html/escape.go
,src/pkg/html/template/css.go
,src/pkg/regexp/syntax/parse.go
,src/pkg/strings/strings.go
,src/pkg/go/build/dir.go
)で、rune(0)
やrune('A')
のような明示的なrune()
キャストが、'\x00'
や'A'
のような直接的な文字リテラルに置き換えられています。これは、文字リテラルがデフォルトでrune
型(または型なしのrune
定数)として推論されるようになったため、冗長なキャストが不要になったことを示しています。 src/pkg/unicode/letter.go
では、MaxRune
、ReplacementChar
、MaxASCII
、MaxLatin1
といったUnicode関連の定数が、従来の整数リテラル(例:0x10FFFF
)から文字リテラル(例:'\U0010FFFF'
)に変更されています。これは、これらの定数がUnicodeコードポイントを表すため、rune
型の文字リテラルとして定義することがより自然で、新しい型ルールに合致するためです。
- 多くのファイル(
-
fmt
パッケージの%c
フォーマット指定子の挙動変更:src/pkg/fmt/scan_test.go
において、%c
フォーマット指定子を用いたスキャンテストが、intVal
(int
型)からruneVal
(rune
型)へのスキャンに変更されています。これは、fmt.Scanf
などの関数が%c
指定子で文字を読み込む際に、その値を直接rune
型の変数に格納できるようになることを意味します。これにより、rune
の入出力がより直感的になります。
-
型変換の明示化:
src/pkg/math/big/nat.go
とsrc/pkg/strings/strings_test.go
では、MaxBase + 1
やunicode.MaxRune + 1
のような演算結果をint()
で明示的にキャストする変更が見られます。これは、新しい型ルールによって、これらの定数や演算結果のデフォルトの型推論が変更された可能性があり、既存のコードとの互換性を保つため、あるいは意図しない型推論を防ぐために明示的な型変換が必要になったためと考えられます。
これらの変更は、Go言語の型システムにおけるrune
の扱いをより洗練させ、言語全体の一貫性を高めることを目的としています。
コアとなるコードの変更箇所
このコミットは広範なファイルにわたる変更を含んでいますが、特に「新しいデフォルト型ルール」の核心を示す変更は以下のファイルに見られます。
-
src/pkg/exp/types/gcimporter.go
:p.scanner.Mode
の設定にscanner.ScanChars
が追加された箇所。parseConstDecl
関数内でcase scanner.Char:
が追加され、文字リテラルが直接処理されるようになった箇所。- エラーメッセージが
p.errorf("expected literal got %s", scanner.TokenString(p.tok))
に変更され、より詳細な情報を提供するようになった箇所。
-
src/pkg/fmt/scan_test.go
:runeVal rune
変数の追加。scanfTests
内の%c
テストで、&intVal
が&runeVal
に変更された箇所。
-
src/pkg/unicode/letter.go
:MaxRune
,ReplacementChar
,MaxASCII
,MaxLatin1
の定義が整数リテラルから文字リテラルに変更された箇所。
-
複数のファイルにおける
rune(...)
キャストの削除:src/cmd/cgo/gcc.go
,src/cmd/godoc/dirtrees.go
,src/pkg/bytes/bytes.go
,src/pkg/go/build/dir.go
,src/pkg/html/escape.go
,src/pkg/html/template/css.go
,src/pkg/regexp/syntax/parse.go
,src/pkg/strings/strings.go
など、多くのファイルでrune(...)
による明示的なキャストが削除され、直接文字リテラルが使用されている箇所。
コアとなるコードの解説
src/pkg/exp/types/gcimporter.go
--- a/src/pkg/exp/types/gcimporter.go
+++ b/src/pkg/exp/types/gcimporter.go
@@ -81,7 +81,7 @@ type gcParser struct {
func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*ast.Object) {
p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
- p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
+ p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
p.scanner.Whitespace = 1<<'\t' | 1<<' '
p.scanner.Filename = filename // for good error messages
p.next()
@@ -645,6 +645,7 @@ func (p *gcParser) parseNumber() Const {
// Literal = bool_lit | int_lit | float_lit | complex_lit | string_lit .
// bool_lit = "true" | "false" .
// complex_lit = "(" float_lit "+" float_lit ")" .
+// rune_lit = "(" int_lit "+" int_lit ")" .
// string_lit = "`" { unicode_char } "`" .
//
func (p *gcParser) parseConstDecl() {
@@ -674,21 +675,32 @@ func (p *gcParser) parseConstDecl() {
typ = Float64.Underlying
case '(':
- // complex_lit
+ // complex_lit or rune_lit
p.next()
+ if p.tok == scanner.Char {
+ p.next()
+ p.expect('+')
+ p.parseNumber()
+ // TODO: x = ...
+ break
+ }
re := p.parseNumber()
p.expect('+')
im := p.parseNumber()
p.expect(')')
x = Const{cmplx{re.val.(*big.Rat), im.val.(*big.Rat)}}
typ = Complex128.Underlying
+ case scanner.Char:
+ // TODO: x = ...
+ p.next()
case scanner.String:
// string_lit
x = MakeConst(token.STRING, p.lit)
p.next()
typ = String.Underlying
default:
- p.error("expected literal")
+ println(p.tok)
+ p.errorf("expected literal got %s", scanner.TokenString(p.tok))
}
if obj.Type == nil {
obj.Type = typ
この変更は、Goの型チェッカーがインポートされたパッケージの型情報を解析する際に使用するgcimporter
の挙動を更新しています。
p.scanner.Mode
にscanner.ScanChars
が追加されたことで、gcimporter
は文字リテラルをscanner.Char
トークンとして認識し、適切に処理できるようになります。parseConstDecl
関数内のcase '(':
ブロックにif p.tok == scanner.Char
が追加されたのは、(
で始まるリテラルが複素数リテラルだけでなく、新しいrune_lit
(例:('a'+'b')
のような形式、ただしこのコミットではまだ完全には実装されていない概念的なもの)である可能性を考慮するためです。- 新しい
case scanner.Char:
ブロックは、スキャナがscanner.Char
トークンを返した場合に、それをrune
型の定数として処理するためのものです。 - エラーメッセージがより詳細になったことで、デバッグが容易になります。
src/pkg/fmt/scan_test.go
--- a/src/pkg/fmt/scan_test.go
+++ b/src/pkg/fmt/scan_test.go
@@ -56,6 +56,7 @@ var (
stringVal string
stringVal1 string
bytesVal []byte
+\truneVal rune
complex64Val complex64
complex128Val complex128
renamedBoolVal renamedBool
@@ -225,9 +226,9 @@ var scanfTests = []ScanfTest{
{"%v", "0377\n", &intVal, 0377},
{"%v", "0x44\n", &intVal, 0x44},
{"%d", "72\n", &intVal, 72},
-\t{"%c", "a\n", &intVal, 'a'},
-\t{"%c", "\u5072\n", &intVal, 0x5072},
-\t{"%c", "\u1234\n", &intVal, '\u1234'},
+\t{"%c", "a\n", &runeVal, 'a'},
+\t{"%c", "\u5072\n", &runeVal, '\u5072'},
+\t{"%c", "\u1234\n", &runeVal, '\u1234'},
{"%d", "73\n", &int8Val, int8(73)},
{"%d", "+74\n", &int16Val, int16(74)},
{"%d", "75\n", &int32Val, int32(75)},
@@ -322,6 +323,7 @@ var s, t string
var c complex128
var x, y Xs
var z IntString
+\tvar r1, r2, r3 rune
var multiTests = []ScanfMultiTest{
{"", "", []interface{}{}, []interface{}{}, ""},
@@ -333,7 +335,7 @@ var multiTests = []ScanfMultiTest{
{"%3d22%3d", "33322333", args(&i, &j), args(333, 333), ""},
{"%6vX=%3fY", "3+2iX=2.5Y", args(&c, &f), args((3 + 2i), 2.5), ""},
{"%d%s", "123abc", args(&i, &s), args(123, "abc"), ""},
-\t{"%c%c%c", "2\u50c2X", args(&i, &j, &k), args('2', '\u50c2', 'X'), ""},
+\t{"%c%c%c", "2\u50c2X", args(&r1, &r2, &r3), args('2', '\u50c2', 'X'), ""},
// Custom scanners.
{"%e%f", "eefffff", args(&x, &y), args(Xs("ee"), Xs("fffff")), ""},
@@ -347,7 +349,7 @@ var multiTests = []ScanfMultiTest{
{"X%d", "10X", args(&intVal), nil, "input does not match format"},
// Bad UTF-8: should see every byte.
-\t{"%c%c%c", "\xc2X\xc2", args(&i, &j, &k), args(utf8.RuneError, 'X', utf8.RuneError), ""},
+\t{"%c%c%c", "\xc2X\xc2", args(&r1, &r2, &r3), args(utf8.RuneError, 'X', utf8.RuneError), ""},
}
func testScan(name string, t *testing.T, scan func(r io.Reader, a ...interface{}) (int, error)) {
このテストファイルの変更は、fmt
パッケージのScanf
関数が%c
フォーマット指定子をどのように扱うかを示しています。
runeVal rune
の追加は、rune
型がfmt
パッケージで直接サポートされるようになったことを明確に示しています。%c
テストが&intVal
から&runeVal
に変更されたことで、%c
が文字を読み込んでint
に変換するのではなく、直接rune
型の変数に格納するようになったことが確認できます。これは、rune
がより自然な文字表現として扱われるようになったことの証拠です。
src/pkg/unicode/letter.go
--- a/src/pkg/unicode/letter.go
+++ b/src/pkg/unicode/letter.go
@@ -7,10 +7,10 @@
package unicode
const (
-\tMaxRune = 0x10FFFF // Maximum valid Unicode code point.
-\tReplacementChar = 0xFFFD // Represents invalid code points.
-\tMaxASCII = 0x7F // maximum ASCII value.
-\tMaxLatin1 = 0xFF // maximum Latin-1 value.
+\tMaxRune = '\U0010FFFF' // Maximum valid Unicode code point.
+\tReplacementChar = '\uFFFD' // Represents invalid code points.
+\tMaxASCII = '\u007F' // maximum ASCII value.
+\tMaxLatin1 = '\u00FF' // maximum Latin-1 value.
)
// RangeTable defines a set of Unicode code points by listing the ranges of
この変更は、Unicode関連の定数を整数リテラルから文字リテラルに切り替えることで、rune
型の新しいデフォルト型ルールを直接反映しています。
0x10FFFF
が'\U0010FFFF'
に、0xFFFD
が'\uFFFD'
に、といった変更は、これらの値がUnicodeコードポイントを表すため、rune
型の文字リテラルとして表現することがより適切であることを示しています。これにより、コードの意図がより明確になり、rune
型の一貫した利用が促進されます。
関連リンク
- Go言語の
rune
型に関する公式ドキュメントや仕様(コミット当時のもの、または現在のもの) - Go言語の型システムに関するドキュメント
- Go言語の字句解析(スキャナ)に関する情報
参考にした情報源リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go Code Review Comments (特に型に関するセクション): https://github.com/golang/go/wiki/CodeReviewComments
- Go言語の
go/scanner
パッケージのドキュメント (コミット当時のバージョン): https://pkg.go.dev/go/scanner (現在のバージョンですが、当時の挙動を推測するのに役立ちます) - Go言語の
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt