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

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

このコミットは、Go言語のパーサー(go/parserパッケージ)におけるスライス式(a[i:j:k]のような形式)の処理方法に関する変更です。具体的には、スライス式のインデックス(i, j, k)の存在チェックをパーサーから削除し、そのエラーチェックを後続の型チェッカーに委ねるように修正しています。これにより、パーサーの役割がより純粋な構文解析に限定され、意味解析の責任が型チェッカーに明確に分離されます。

コミット

commit dd1fe82cecd530de2ddee961d5d3cb1d6f95f851
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jul 3 10:43:24 2013 -0700

    go/parser: accept optional indices for all parts of an index expression
    
    Instead, leave the error testing to the type checker, eventually.
    
    Fixes #5827.
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/10917043

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

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

元コミット内容

このコミットの主な内容は、「go/parserがインデックス式の全ての部分に対してオプションのインデックスを受け入れるようにする」というものです。これは、インデックスの存在に関するエラーチェックをパーサーではなく、最終的に型チェッカーに任せることを意味します。

変更の背景

この変更は、Go言語のIssue #5827「go/parser: a[i:j:k]においてjkが省略可能であるべき」を修正するために行われました。

元のGo言語の仕様では、フルスライス式 a[i:j:k] において、i は省略可能でしたが、jk は省略できないとされていました。しかし、Go 1.2のリリースに向けて、スライス式のセマンティクスが拡張され、a[i:j:k] 形式において jk も省略可能となるように変更されました。例えば、a[::k]a[i::] のような記述が有効になるように仕様が変更されたのです。

この仕様変更に伴い、パーサーが以前行っていた「jkがnil(省略)の場合にエラーを出す」というチェックが不要、かつ不適切になりました。パーサーは構文的に有効なコードをAST(抽象構文木)に変換する役割を担っており、意味的な正当性(例えば、インデックスが有効な範囲にあるか、型が適切かなど)のチェックは型チェッカーの役割です。

したがって、このコミットは、パーサーが新しいスライス式の構文を正しく受け入れ、意味的なエラーチェックの責任を型チェッカーに適切に委譲するために行われました。

前提知識の解説

Go言語のパーサー (go/parserパッケージ)

Go言語のコンパイラツールチェーンにおいて、go/parserパッケージはソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成する役割を担います。パーサーは、Go言語の文法規則に従ってトークン列を解析し、プログラムの構造を表現するツリー構造を構築します。この段階では、コードが文法的に正しいかどうかが主にチェックされ、意味的な正当性(例えば、変数の型が正しいか、関数呼び出しの引数が適切かなど)は通常チェックされません。

抽象構文木 (AST)

ASTは、ソースコードの構造をツリー形式で表現したものです。各ノードは、式、文、宣言などのプログラムの構成要素に対応します。go/astパッケージは、Go言語のASTを表現するためのデータ構造を提供します。パーサーはソースコードを読み込み、このASTを構築します。

スライス式 (a[i:j:k])

Go言語のスライス式は、配列やスライスから部分的なスライスを作成するために使用されます。一般的な形式は以下の通りです。

  • a[low:high]: lowからhigh-1までの要素を含む新しいスライスを作成します。lowが省略された場合は0、highが省略された場合は元のスライスの長さがデフォルト値となります。
  • a[low:high:capacity]: 上記に加えて、新しいスライスの容量(capacity)を指定します。capacitylowから始まる元のスライスの最大インデックスまでです。この形式は「フルスライス式」と呼ばれます。

Go 1.2以降、フルスライス式 a[i:j:k] において、i, j, k のいずれも省略可能になりました。

  • i: 開始インデックス。省略された場合は0。
  • j: 終了インデックス。省略された場合は元のスライスの長さ。
  • k: 容量の終了インデックス。省略された場合は元のスライスの容量。

型チェッカー

Go言語のコンパイラにおける型チェッカーは、パーサーによって生成されたASTを受け取り、プログラムの意味的な正当性を検証する役割を担います。これには、変数の型が正しく使用されているか、関数呼び出しの引数が期待される型と一致するか、インデックスが有効な範囲にあるか、といったチェックが含まれます。型チェッカーは、プログラムがGo言語の型システムとセマンティクスに準拠していることを保証します。

技術的詳細

このコミットの技術的な核心は、Go言語のコンパイラフロントエンドにおける「責任の分離」の原則を強化することにあります。

以前のgo/parserの実装では、フルスライス式 a[i:j:k] を解析する際に、ncolons == 2(つまり a[::] のような形式)の場合に、index[1]jに対応)または index[2]kに対応)がnil(省略されている)であればエラーを発生させていました。これは、当時のGo言語の仕様では jk が省略不可能であったため、パーサーが構文解析の段階でこの意味的な制約をチェックしていたことを意味します。

しかし、Go 1.2でスライス式の仕様が変更され、jk も省略可能になったことで、このパーサー内のチェックはもはや正しくなくなりました。パーサーの主な役割は、ソースコードをASTに変換することであり、文法的に有効な構造であれば、それが意味的に正しいかどうかに関わらずASTを構築すべきです。意味的なチェック(例えば、kjより小さい場合にエラーを出すなど)は、ASTが構築された後に行われるべきであり、これは型チェッカーの責任です。

このコミットは、パーサーからこの意味的なチェックを取り除くことで、以下の利点をもたらします。

  1. 役割の明確化: パーサーは純粋な構文解析に集中し、型チェッカーは意味解析に集中するという役割分担がより明確になります。これにより、コンパイラの各ステージの設計がシンプルになり、保守性が向上します。
  2. 仕様変更への対応: 文法的な変更ではなく、意味的な変更(この場合はスライス式のセマンティクス拡張)があった場合でも、パーサーのコードを変更する必要がなくなります。パーサーは新しい構文を受け入れるだけでよく、意味的な制約は型チェッカーが処理します。
  3. エラー報告の改善: 意味的なエラーは、型チェッカーの段階でより豊富なコンテキスト情報(型情報など)を利用して報告されるため、より正確で分かりやすいエラーメッセージを提供できる可能性があります。

この変更により、パーサーは a[::]a[i::] のような形式をエラーとせず、有効なASTとして受け入れるようになります。その後、型チェッカーがこのASTを処理する際に、例えば kj より小さいといった意味的なエラーを検出する責任を負うことになります。

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

このコミットでは、主に2つのファイルが変更されています。

  1. src/pkg/go/parser/parser.go:

    • func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr 関数内の以下の4行が削除されました。

      --- a/src/pkg/go/parser/parser.go
      +++ b/src/pkg/go/parser/parser.go
      @@ -1187,10 +1187,6 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr {
       
       	if ncolons > 0 {
       		// slice expression
      -		if ncolons == 2 && (index[1] == nil || index[2] == nil) {
      -			// only i is optional in a[i:j:k]
      -			p.error(rbrack, "2nd and 3rd index must be present full slice expression")
      -		}
       		return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Rbrack: rbrack}
       	}
       
      
  2. src/pkg/go/parser/short_test.go:

    • invalids 変数から、削除されたパーサーのチェックに関連する以下の6つのテストケースが削除されました。これらのテストケースは、a[::]a[i::] のような形式がパーサーによってエラーと判断されることを期待していましたが、変更後はこれらが有効な構文として扱われるため、不要になりました。

      --- a/src/pkg/go/parser/short_test.go
      +++ b/src/pkg/go/parser/short_test.go
      @@ -78,12 +78,6 @@ var invalids = []string{
        	`package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`,
        	`package p; func f() { var s []int; _ = s[::: /* ERROR "expected ']'" */ ] };`,
        	`package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`,
      -	`package p; func f() { var s []int; g(s[::] /* ERROR "index must be present" */) };`,
      -	`package p; func f() { var s []int; g(s[i::] /* ERROR "index must be present" */) };`,
      -	`package p; func f() { var s []int; g(s[i:j:] /* ERROR "index must be present" */) };`,
      -	`package p; func f() { var s []int; g(s[::k] /* ERROR "index must be present" */) };`,
      -	`package p; func f() { var s []int; g(s[:j:] /* ERROR "index must be present" */) };`,
      -	`package p; func f() { var s []int; g(s[i::k] /* ERROR "index must be present" */) };`,
        }
        
        func TestInvalid(t *testing.T) {
      

コアとなるコードの解説

src/pkg/go/parser/parser.go の変更

削除されたコードブロックは以下の通りです。

if ncolons == 2 && (index[1] == nil || index[2] == nil) {
	// only i is optional in a[i:j:k]
	p.error(rbrack, "2nd and 3rd index must be present full slice expression")
}
  • ncolons == 2: これは、スライス式が a[::] のように2つのコロンを含む、つまりフルスライス式 a[i:j:k] の形式であることを示します。
  • index[1] == nil || index[2] == nil: index スライスは、i, j, k に対応するASTノードを格納します。index[1]j に、index[2]k に対応します。この条件は、j または k のいずれかが省略されている(nilである)場合に真となります。
  • p.error(...): 上記の条件が真の場合、パーサーはエラーを発生させ、"2nd and 3rd index must be present full slice expression"(2番目と3番目のインデックスはフルスライス式に存在しなければならない)というメッセージを出力していました。

このコードブロックが削除されたことにより、パーサーは a[::]a[i::]a[:j:]a[::k]a[i::k] のような形式を文法的に有効なスライス式として受け入れるようになります。これらの式が意味的に正しいかどうか(例えば、kjより小さいなど)のチェックは、パーサーの後の段階、具体的には型チェッカーに委ねられます。

src/pkg/go/parser/short_test.go の変更

削除されたテストケースは、以前のパーサーの挙動(jkの省略をエラーとする)を検証するためのものでした。例えば、g(s[::] /* ERROR "index must be present" */) というテストケースは、s[::] がパーサーによって「インデックスが存在しなければならない」というエラーを出すことを期待していました。

パーサーの変更により、これらの形式がもはやパーサーレベルでエラーとならないため、これらのテストケースは削除されました。これにより、テストスイートが新しい仕様と実装に合致するよう更新されました。

関連リンク

参考にした情報源リンク

  • Go言語のIssue #5827の議論内容 (Google Codeアーカイブ)
  • Go言語の公式ドキュメント(スライスに関するセクション)
  • Go言語のコンパイラ設計に関する一般的な知識
  • Go言語のgo/parserおよびgo/astパッケージに関するドキュメント