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

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

このコミットは、Goコンパイラ(cmd/gc)における、[...]int のような配列リテラル外での使用に関する冗長なエラー出力を抑制するための修正です。具体的には、Go言語の配列型宣言において、要素数をコンパイラに推論させるための [...] 構文が、配列リテラル以外の文脈で誤って使用された場合に、複数のエラーメッセージが繰り返し出力される問題を解決します。

コミット

commit f607c479eabab497b3e7d3dead472a19bd27e063
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 1 21:21:27 2013 -0500

    cmd/gc: silence redundant error prints for misuse of [...]int
    
    Fixes #4452.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7241065

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

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

元コミット内容

cmd/gc: silence redundant error prints for misuse of [...]int

このコミットは、Goコンパイラ(cmd/gc)において、[...]int のような配列型が配列リテラル(例: [...]int{1, 2, 3})の文脈以外で誤って使用された場合に発生する、冗長なエラーメッセージの出力を抑制することを目的としています。これにより、コンパイラのエラー出力がより簡潔になり、ユーザーにとって理解しやすくなります。

変更の背景

この変更は、Go issue #4452 に対応するものです。元の問題は、[...]int(4) のように、配列リテラルではない場所で [...] 構文を使用すると、コンパイラが複数のエラーメッセージを生成してしまうというものでした。例えば、_ = [...]int(4) のようなコードは、本来であれば「配列リテラル外での [...] 配列の使用」というエラーを一度だけ報告すべきですが、実際にはこのエラーに加えて「型変換の失敗」など、複数の関連するエラーが報告されていました。これはユーザーにとって混乱を招き、問題の特定を困難にする可能性がありました。

コンパイラは、一度エラーを報告した問題に対して、それ以上関連するエラーを報告しないようにすることで、エラーメッセージの質を向上させる必要があります。このコミットは、t->broken->diag といったフラグを導入・利用することで、既にエラーが報告された型やノードに対しては、重複するエラーメッセージを出力しないように修正しています。

前提知識の解説

Go言語の配列と [...] 構文

Go言語において、配列は固定長の一連の要素を格納するデータ構造です。配列の型は、要素の型と配列の長さ(要素数)によって定義されます。

例: var a [5]int は、5つの整数を格納できる配列 a を宣言します。

Goには「配列リテラル」という構文があり、配列の宣言と同時に初期化を行うことができます。この際、配列の要素数を明示的に指定する代わりに、...(三点リーダー)を使用することができます。この ... は、コンパイラに初期化子の数から配列の長さを推論させることを指示します。

例: a := [...]int{10, 20, 30} この場合、コンパイラは初期化子 {10, 20, 30} が3つの要素を持つことから、a の型を [3]int と推論します。

この [...] 構文は、配列リテラル内でのみ有効です。配列リテラル以外の文脈で [...]int のように使用することは、Go言語の文法上誤りであり、コンパイルエラーとなります。

Goコンパイラ(cmd/gc)の内部構造とエラー報告

Goコンパイラは、ソースコードを解析し、抽象構文木(AST)を構築し、型チェック、最適化、コード生成などの段階を経て実行可能ファイルを生成します。

  • src/cmd/gc/align.c: 型のアライメントとサイズの計算に関連する処理を扱います。配列のサイズが決定される際にも関与します。
  • src/cmd/gc/const.c: 定数式の評価と型変換に関連する処理を扱います。
  • src/cmd/gc/typecheck.c: 型チェックの主要なロジックが含まれています。ASTノードの型を検証し、型の一貫性を保証します。

コンパイラがエラーを検出した場合、yyerror 関数などを使用してエラーメッセージを出力します。しかし、一つの根本的な問題が複数のコンパイラフェーズで異なるエラーとして検出されると、同じ問題に対して複数のエラーメッセージが報告されることがあります。これは、コンパイラの設計において、エラーの伝播と重複報告を避けるためのメカニズムが必要であることを示しています。

このコミットでは、Type 構造体(Goの型を表す内部構造)に broke フィールドを、Node 構造体(ASTのノードを表す内部構造)に diag フィールドを導入(または既存のものを利用)し、これらをエラーが報告されたことを示すフラグとして使用しています。

  • t->broke: その Type が既に不正な状態であり、エラーが報告されたことを示すフラグ。
  • n->diag: その Node が既に診断済み(エラーが報告済み)であることを示すフラグ。

これらのフラグをチェックすることで、コンパイラは同じ根本原因によるエラーメッセージの重複出力を防ぎます。

技術的詳細

このコミットの核心は、Goコンパイラの型チェックおよびアライメント計算フェーズにおいて、不正な [...] 配列の使用や型変換エラーが検出された際に、冗長なエラーメッセージの出力を抑制するメカニズムを導入することです。

具体的には、以下の変更が行われています。

  1. Type 構造体への broke フィールドの追加と利用:

    • src/cmd/gc/align.cdowidth 関数内で、t->bound == -100[...] 配列を示す内部的な値)の場合に「use of [...] array outside of array literal」エラーを報告する際に、t->broke フラグをチェックし、設定するように変更されています。これにより、この型に関するエラーが一度報告されたら、それ以降は同じエラーが報告されないようになります。
    • src/cmd/gc/typecheck.ctypecheck 関数内で、[...] 配列が配列リテラル外で使用された場合に t->broke = 1 を設定し、yyerror を呼び出す前に !n->diag をチェックしています。
    • src/cmd/gc/typecheck.ctypecheck 関数内で、型変換における ... の不正な使用を検出する際にも !l->type->broke をチェックしています。
    • src/cmd/gc/const.cconvlit1 関数内で、型変換エラーを報告する際に !t->broke をチェックしています。
  2. Node 構造体への diag フィールドの利用:

    • src/cmd/gc/typecheck.ctypecheck 関数内で、[...] 配列が配列リテラル外で使用された場合に n->diag = 1 を設定し、yyerror を呼び出す前に !n->diag をチェックしています。
    • src/cmd/gc/typecheck.ctypecheck 関数内で、型変換エラーを報告する際に !n->diag をチェックし、エラー報告後に n->diag = 1 を設定しています。
    • src/cmd/gc/const.cconvlit1 関数内で、型変換エラーを報告する際に !n->diag をチェックし、エラー報告後に n->diag = 1 を設定しています。

これらの変更により、コンパイラは特定の型やASTノードに対して一度エラーを報告すると、その後の処理で同じ根本原因による重複したエラーメッセージを出力しなくなります。これにより、コンパイルエラーの出力がよりクリーンで、問題の特定が容易になります。

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

src/cmd/gc/align.c

--- a/src/cmd/gc/align.c
+++ b/src/cmd/gc/align.c
@@ -253,8 +253,12 @@ dowidth(Type *t)
 			checkwidth(t->type);
 			t->align = widthptr;
 		}
-		else if(t->bound == -100)
-			yyerror("use of [...] array outside of array literal");
+		else if(t->bound == -100) {
+			if(!t->broke) {
+				yyerror("use of [...] array outside of array literal");
+				t->broke = 1;
+			}
+		}
 		else
 			fatal("dowidth %T", t);	// probably [...]T
 		break;

src/cmd/gc/const.c

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -240,7 +240,8 @@ convlit1(Node **np, Type *t, int explicit)
 
 bad:
 	if(!n->diag) {
-		yyerror("cannot convert %N to type %T", n, t);
+		if(!t->broke)
+			yyerror("cannot convert %N to type %T", n, t);
 		n->diag = 1;
 	}
 	if(isideal(n->type)) {

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -377,8 +377,11 @@ reswitch:
 			t->bound = -1;	// slice
 		} else if(l->op == ODDD) {
 			t->bound = -100;	// to be filled in
-			if(!(top&Ecomplit))
-				yyerror("use of [...] array outside of array literal");
+			if(!(top&Ecomplit) && !n->diag) {
+				t->broke = 1;
+				n->diag = 1;
+				yyerror("use of [...] array outside of array literal");
+			}
 		} else {
 			l = typecheck(&n->left, Erv);
 			switch(consttype(l)) {
@@ -1028,8 +1031,11 @@ reswitch:
 		defaultlit(&n->left, T);
 		l = n->left;
 		if(l->op == OTYPE) {
-			if(n->isddd || l->type->bound == -100)
-				yyerror("invalid use of ... in type conversion", l);
+			if(n->isddd || l->type->bound == -100) {
+				if(!l->type->broke)
+					yyerror("invalid use of ... in type conversion", l);
+				n->diag = 1;
+			}
 			// pick off before type-checking arguments
 			ok |= Erv;
 			// turn CALL(type, arg) into CONV(arg) w/ type
@@ -1335,7 +1341,10 @@ reswitch:
 		if((t = n->left->type) == T || n->type == T)
 			goto error;
 		if((n->op = convertop(t, n->type, &why)) == 0) {
-			yyerror("cannot convert %lN to type %T%s", n->left, n->type, why);
+			if(!n->diag && !n->type->broke) {
+				yyerror("cannot convert %lN to type %T%s", n->left, n->type, why);
+				n->diag = 1;
+			}
 			n->op = OCONV;
 		}
 		switch(n->op) {

test/fixedbugs/issue4452.go

--- /dev/null
+++ b/test/fixedbugs/issue4452.go
@@ -0,0 +1,13 @@
+// errorcheck
+
+// Copyright 2013 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.
+
+// Issue 4452. Used to print many errors, now just one.
+
+package main
+
+func main() {
+	_ = [...]int(4) // ERROR "use of \\[\\.\\.\\.\\] array outside of array literal"
+}

コアとなるコードの解説

このコミットの主要な変更は、Goコンパイラの内部で型 (Type 構造体) や抽象構文木ノード (Node 構造体) にエラーが既に報告されたことを示すフラグ (broke および diag) を導入し、それを利用して冗長なエラーメッセージの出力を抑制することです。

  • src/cmd/gc/align.c の変更:

    • dowidth 関数は、型のメモリレイアウト(サイズとアライメント)を計算します。
    • t->bound == -100 は、[...] 構文が使用された配列型を示します。
    • このコードブロックは、[...] 配列が配列リテラル外で使用された場合にエラーを報告します。
    • if(!t->broke) の条件が追加され、t->broke = 1 が設定されています。これにより、この型の不正な使用に関するエラーが一度報告されたら、それ以降は同じ型に対して重複してエラーが報告されるのを防ぎます。
  • src/cmd/gc/const.c の変更:

    • convlit1 関数は、リテラルの型変換を扱います。
    • if(!n->diag) は、このノードに対して既にエラーが報告されていないかをチェックします。
    • if(!t->broke) が追加され、型 t が既に不正な状態(エラーが報告済み)でない場合にのみ yyerror を呼び出すようにしています。
    • エラー報告後には n->diag = 1 を設定し、このノードが診断済みであることをマークします。これにより、型変換の失敗が他の場所で再度エラーとして報告されるのを防ぎます。
  • src/cmd/gc/typecheck.c の変更:

    • typecheck 関数は、Goコンパイラの型チェックの主要な部分です。
    • l->op == ODDD は、... 構文が使用されたノードを示します。
    • if(!(top&Ecomplit) && !n->diag) の条件が追加され、t->broke = 1n->diag = 1 が設定されています。これは、... が配列リテラル外で使用され、かつこのノードに対してまだエラーが報告されていない場合にのみエラーメッセージを出力し、フラグを設定することを意味します。
    • 型変換に関連する箇所でも同様に、if(!l->type->broke)if(!n->diag && !n->type->broke) といった条件が追加され、n->diag = 1 が設定されています。これにより、型変換の不正な使用や、不正な型への変換に関する冗長なエラーメッセージが抑制されます。
  • test/fixedbugs/issue4452.go の追加:

    • このテストファイルは、_ = [...]int(4) というコードスニペットを含んでいます。
    • // ERROR "use of \\[\\.\\.\\.\\] array outside of array literal" というコメントは、この行で期待されるエラーメッセージを示しています。
    • このテストの目的は、この不正なコードに対して、以前のように複数のエラーではなく、この単一のエラーメッセージのみが出力されることを検証することです。

これらの変更により、コンパイラはより洗練されたエラー報告メカニズムを持つようになり、ユーザーはより明確で簡潔なエラーメッセージを受け取ることができます。

関連リンク

参考にした情報源リンク

  • Go issue #4452 の詳細情報
  • Go言語の配列とスライスのドキュメント
  • Goコンパイラのソースコード(src/cmd/gc ディレクトリ)
  • Go言語のコンパイラ設計に関する一般的な情報