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

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

このコミットは、Go言語のコンパイラ(gc)において、構造体(struct)のフィールド名とメソッド名が同じ場合にコンパイルエラーを診断し、報告するように修正するものです。これにより、開発者が意図しない名前の衝突を早期に発見し、コードの曖昧さや潜在的なバグを防ぐことができます。

コミット

commit 7dd90621f8f8ed25708daf279067e8c0024af20b
Author: Russ Cox <rsc@golang.org>
Date:   Sat Feb 11 01:21:12 2012 -0500

    gc: diagnose field+method of same name
    
    Fixes #2828.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5653065

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

https://github.com/golang/go/commit/7dd90621f8f8ed25708daf279067e8c0024af20b

元コミット内容

gc: diagnose field+method of same name

Fixes #2828.

R=ken2
CC=golang-dev
https://golang.org/cl/5653065

変更の背景

Go言語では、構造体(struct)はデータフィールドをまとめるための型であり、メソッドは特定の型に関連付けられた関数です。このコミットが導入される以前は、Goコンパイラが、構造体のフィールド名と、その構造体に関連付けられたメソッド名が偶然同じであるケースを適切に診断していませんでした。

このような名前の衝突は、コードの可読性を低下させるだけでなく、プログラマが意図しない動作を引き起こす可能性がありました。例えば、t.Xという記述があった場合、それが構造体tのフィールドXを指すのか、それともメソッドX()を呼び出すのかが曖昧になり、コンパイラや実行時の挙動が予測しにくくなることが考えられます。

この問題は、GoのIssueトラッカーでIssue #2828として報告されていたバグであり、このコミットはその問題を修正するために作成されました。コンパイラがこの種の名前の衝突を検出し、明確なエラーメッセージを出すことで、開発者はより堅牢で理解しやすいコードを書くことができるようになります。

前提知識の解説

Go言語の構造体 (Struct)

Go言語における構造体は、異なるデータ型のフィールドをひとつのまとまりとして扱うためのユーザー定義型です。例えば、Personという構造体はName(文字列)とAge(整数)というフィールドを持つことができます。

type Person struct {
    Name string
    Age  int
}

Go言語のメソッド (Method)

メソッドは、特定の型に関連付けられた関数です。Goでは、レシーバ引数(receiver argument)を持つことで、関数を型に「アタッチ」します。これにより、その型の値やポインタに対してメソッドを呼び出すことができます。

type Person struct {
    Name string
    Age  int
}

func (p Person) Greet() string {
    return "Hello, my name is " + p.Name
}

この例では、GreetメソッドはPerson型に関連付けられています。

Goコンパイラ (gc)

gcはGo言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルの過程で、構文解析、型チェック、最適化、コード生成など、様々な段階を経て実行可能ファイルを生成します。このコミットで変更されたsrc/cmd/gc/dcl.cは、Goコンパイラの宣言(declaration)処理の一部を担うC言語のソースファイルです。

名前空間と名前の衝突 (Name Collision)

プログラミング言語において、名前空間は識別子(変数名、関数名、型名など)が定義されるスコープを指します。名前の衝突は、同じ名前が異なる意味で同じ名前空間内で使用される場合に発生します。これは、コードの曖昧さや予期せぬ動作の原因となるため、通常は避けるべきです。Go言語では、構造体のフィールドとメソッドは異なる名前空間に属しているわけではありませんが、同じ名前を持つことで参照の曖昧さが生じる可能性があります。このコミットは、その曖昧さをコンパイル時にエラーとして明確にすることで、開発者が問題を解決できるように促します。

技術的詳細

このコミットの主要な変更は、Goコンパイラの宣言処理を行うsrc/cmd/gc/dcl.cファイル内のaddmethod関数にあります。addmethod関数は、新しいメソッドが型に追加される際に呼び出されます。

変更前は、この関数はメソッドを追加する前に、同じ名前のフィールドが存在するかどうかをチェックしていませんでした。変更後は、以下のロジックが追加されました。

  1. 型が構造体であるかの確認: if(pa->etype == TSTRUCT)という条件で、現在処理している型(pa)が構造体型であるかどうかを確認します。TSTRUCTはGoコンパイラ内部で構造体型を表す定数です。
  2. 既存フィールドの走査: もし型が構造体であれば、for(f=pa->type; f; f=f->down)というループを使って、その構造体のすべてのフィールド(f)を走査します。pa->typeは構造体のフィールドリストの先頭を指し、f->downは次のフィールドへのポインタです。
  3. 名前の衝突チェック: 各フィールドfについて、if(f->sym == sf)という条件で、そのフィールドの名前(f->sym)が、現在追加しようとしているメソッドの名前(sf)と同じであるかどうかをチェックします。sfはメソッドのシンボル(名前)を表します。
  4. エラー報告: もし同じ名前のフィールドが見つかった場合、yyerror("type %T has both field and method named %S", pa, sf);という行が実行されます。yyerrorはGoコンパイラがエラーメッセージを出力するための内部関数です。この関数は、指定されたフォーマット文字列と引数を使って、コンパイルエラーメッセージを生成し、コンパイルプロセスを停止させます。エラーメッセージは「type T has both field and method named X」のような形式で出力されます。
  5. メソッド追加の中止: エラーが報告された後、return;によってaddmethod関数の実行が中断され、同じ名前のメソッドが追加されるのを防ぎます。

この変更により、コンパイラは構造体のフィールドとメソッドの名前が衝突するケースを正確に検出し、開発者にその問題を通知できるようになりました。

また、test/fixedbugs/bug416.goという新しいテストファイルが追加されました。このテストファイルは、意図的にフィールドとメソッドに同じ名前を付けて、コンパイラが期待通りにエラーを報告するかどうかを検証します。テストファイルの先頭にある// errchk $G $D/$F.goは、Goのテストフレームワークに対して、このファイルがコンパイルエラーを発生させることを期待していることを示しています。

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

src/cmd/gc/dcl.c の変更

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1303,6 +1303,14 @@ addmethod(Sym *sf, Type *t, int local)
 	}
 
 	pa = f;
+	if(pa->etype == TSTRUCT) {
+		for(f=pa->type; f; f=f->down) {
+			if(f->sym == sf) {
+				yyerror("type %T has both field and method named %S", pa, sf);
+				return;
+			}
+		}
+	}
 
 	n = nod(ODCLFIELD, newname(sf), N);
 	n->type = t;

test/fixedbugs/bug416.go の追加

--- /dev/null
+++ b/test/fixedbugs/bug416.go
@@ -0,0 +1,13 @@
+// errchk $G $D/$F.go
+
+// Copyright 2012 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.
+
+package p
+
+type T struct {
+	X int
+}
+
+func (t *T) X() {} // ERROR "type T has both field and method named X"

コアとなるコードの解説

src/cmd/gc/dcl.c の変更点

addmethod関数は、Goコンパイラが型にメソッドを追加する際に呼び出される重要な部分です。追加されたコードブロックは、メソッドを追加する直前に実行されます。

	if(pa->etype == TSTRUCT) { // (1)
		for(f=pa->type; f; f=f->down) { // (2)
			if(f->sym == sf) { // (3)
				yyerror("type %T has both field and method named %S", pa, sf); // (4)
				return; // (5)
			}
		}
	}
  1. (1) if(pa->etype == TSTRUCT): paは現在処理中の型を表すポインタです。この条件は、paが構造体型である場合にのみ、以下のチェックを実行することを示しています。構造体以外の型(例えば、プリミティブ型やインターフェース型)にメソッドを追加する際には、フィールドとの名前衝突は発生しないため、このチェックは不要です。
  2. (2) for(f=pa->type; f; f=f->down): これは、構造体paのすべてのフィールドをイテレートするためのループです。pa->typeは構造体の最初のフィールドへのポインタを保持しており、各フィールド構造体にはdownポインタがあり、次のフィールドを指します。このリンクリストを辿ることで、構造体内のすべてのフィールドにアクセスできます。
  3. (3) if(f->sym == sf): この条件は、現在のフィールドfのシンボル(名前)f->symが、追加しようとしているメソッドのシンボルsfと等しいかどうかを比較しています。もし両者が同じであれば、名前の衝突が発生していることを意味します。
  4. (4) yyerror("type %T has both field and method named %S", pa, sf);: 名前が衝突した場合、yyerror関数が呼び出され、コンパイルエラーメッセージが生成されます。%Tは型paの名前を、%Sはシンボルsfの名前をそれぞれ表示するためのフォーマット指定子です。これにより、ユーザーには「型Tはフィールドとメソッドの両方にXという名前を持っています」といった明確なエラーメッセージが表示されます。
  5. (5) return;: エラーが報告された後、addmethod関数は即座に終了します。これにより、不正な名前を持つメソッドがコンパイラの内部データ構造に追加されるのを防ぎ、後続のコンパイルプロセスでのさらなるエラーや予期せぬ動作を防ぎます。

test/fixedbugs/bug416.go の解説

このファイルは、Goコンパイラのバグ修正を検証するための回帰テストケースです。

// errchk $G $D/$F.go

package p

type T struct {
	X int
}

func (t *T) X() {} // ERROR "type T has both field and method named X"
  • // errchk $G $D/$F.go: これはGoのテストシステムに対する指示です。$GはGoコンパイラ、$D/$F.goは現在のテストファイルのパスを指します。この行は、このGoファイルをコンパイルした際に、コンパイラがエラーを発生させることを期待していることを示しています。
  • type T struct { X int }: Tという名前の構造体を定義し、その中にXという名前のint型のフィールドを持たせています。
  • func (t *T) X() {}: T型にXという名前のメソッドを定義しています。このメソッドはレシーバt*T型)を持ち、引数も戻り値もありません。
  • // ERROR "type T has both field and method named X": このコメントは、Goのテストシステムが、この行で指定された正規表現に一致するエラーメッセージがコンパイラから出力されることを期待していることを示しています。この正規表現は、src/cmd/gc/dcl.cの変更によって出力されるエラーメッセージと完全に一致するように設計されています。

このテストケースは、コンパイラがフィールドXとメソッドXの名前衝突を正しく検出し、適切なエラーメッセージを出力することを確認するために不可欠です。

関連リンク

  • GitHub上のコミットページ: https://github.com/golang/go/commit/7dd90621f8f8ed25708daf279067e8c0024af20b
  • Go Issue #2828: コミットメッセージにFixes #2828と記載されていますが、このIssueはGoの初期のIssueトラッカー(Google Codeなど)で管理されていた可能性があり、現在のGitHub Issuesでは直接見つけることができませんでした。GoプロジェクトはIssueトラッカーを移行しているため、古いIssue番号は現在のシステムでは異なる番号になっているか、アーカイブされている可能性があります。
  • Go Change List (CL) 5653065: コミットメッセージにhttps://golang.org/cl/5653065と記載されていますが、このCL番号も現在のGoのコードレビューシステム(Gerrit)では直接見つけることができませんでした。これは、非常に古いCLであるか、内部的なCL番号である可能性があります。

参考にした情報源リンク

  • Go言語公式ドキュメント (Go言語の構造体とメソッドに関する一般的な情報)
  • Goコンパイラのソースコード (特にsrc/cmd/gc/dcl.cの周辺コードの理解)
  • Go言語のテストフレームワークに関する情報 (特に// errchkディレクティブの理解)