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

[インデックス 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は型なしの整数定数であり、intint32float64など、様々な数値型に割り当てることができます。このコミットでは、文字リテラルが型なしのrune定数として扱われるようになる変更が含まれています。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. go/scannerの挙動変更:

    • src/pkg/exp/types/gcimporter.goにおいて、p.scanner.Modescanner.ScanCharsが追加されました。これは、スキャナが文字リテラル(例: 'a', '\u1234')を独立したトークンタイプscanner.Charとして認識するようになることを意味します。以前は、これらのリテラルはscanner.Intsとして扱われるか、あるいは他のモードで処理される可能性がありました。この変更により、文字リテラルがrune型としてより正確に識別され、型推論の基盤が強化されます。
    • gcimporter.goparseConstDecl関数内で、scanner.Charトークンを直接処理する新しいケースが追加されました。これにより、文字リテラルが定数宣言内で適切に解析され、rune型の定数として扱われるようになります。
  2. 文字リテラルの型推論の変更:

    • 多くのファイル(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では、MaxRuneReplacementCharMaxASCIIMaxLatin1といったUnicode関連の定数が、従来の整数リテラル(例: 0x10FFFF)から文字リテラル(例: '\U0010FFFF')に変更されています。これは、これらの定数がUnicodeコードポイントを表すため、rune型の文字リテラルとして定義することがより自然で、新しい型ルールに合致するためです。
  3. fmtパッケージの%cフォーマット指定子の挙動変更:

    • src/pkg/fmt/scan_test.goにおいて、%cフォーマット指定子を用いたスキャンテストが、intValint型)からruneValrune型)へのスキャンに変更されています。これは、fmt.Scanfなどの関数が%c指定子で文字を読み込む際に、その値を直接rune型の変数に格納できるようになることを意味します。これにより、runeの入出力がより直感的になります。
  4. 型変換の明示化:

    • src/pkg/math/big/nat.gosrc/pkg/strings/strings_test.goでは、MaxBase + 1unicode.MaxRune + 1のような演算結果をint()で明示的にキャストする変更が見られます。これは、新しい型ルールによって、これらの定数や演算結果のデフォルトの型推論が変更された可能性があり、既存のコードとの互換性を保つため、あるいは意図しない型推論を防ぐために明示的な型変換が必要になったためと考えられます。

これらの変更は、Go言語の型システムにおけるruneの扱いをより洗練させ、言語全体の一貫性を高めることを目的としています。

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

このコミットは広範なファイルにわたる変更を含んでいますが、特に「新しいデフォルト型ルール」の核心を示す変更は以下のファイルに見られます。

  1. 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))に変更され、より詳細な情報を提供するようになった箇所。
  2. src/pkg/fmt/scan_test.go:

    • runeVal rune変数の追加。
    • scanfTests内の%cテストで、&intVal&runeValに変更された箇所。
  3. src/pkg/unicode/letter.go:

    • MaxRune, ReplacementChar, MaxASCII, MaxLatin1の定義が整数リテラルから文字リテラルに変更された箇所。
  4. 複数のファイルにおける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.Modescanner.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言語の字句解析(スキャナ)に関する情報

参考にした情報源リンク