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

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

このコミットは、Go言語の初期のコンパイラである6gにおける特定のバグを修正するものです。具体的には、名前付きポインタ型(type MyPointer *MyStructのような定義)の変数に対してメソッドを呼び出す際に、6gコンパイラがそのメソッドを正しく認識できない問題に対処しています。この修正を検証するために、bug117.goという新しいテストケースが追加され、既存のpowser1.goファイルもコンパイルが通るように変更されています。

コミット

このコミットは、現在の6gコンパイラでコンパイルが通るようにpowser1.goを修正します。6gは、名前付きポインタ型である変数のメソッドを認識しませんでした。このケースをテストするためにbug117が追加されました。

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

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

元コミット内容

Fix powser1.go to compile with the current 6g, which doesn't
recognize methods for a variable whose type is a named type
which is a pointer type. Add bug117 to test this case.

R=r
DELTA=24  (22 added, 0 deleted, 2 changed)
OCL=18547
CL=18554

変更の背景

Go言語の初期開発段階において、コンパイラ(特に6g)はまだ成熟しておらず、様々なエッジケースやバグが存在していました。このコミットで対処されている問題は、Goの型システムにおける特定の組み合わせ、すなわち「名前付き型であり、かつポインタ型である変数」に対するメソッド呼び出しの認識に関するものです。

当時の6gコンパイラは、以下のようなコードパターンを正しく処理できませんでした。

  1. 構造体Sを定義する。
  2. Sのポインタ型*Sに対してメソッドを定義する。
  3. *Sを基底とする新しい名前付き型PSを定義する(例: type PS *S)。
  4. PS型の変数に対して、*Sに定義されたメソッドを呼び出す。

このシナリオで6gはメソッドの解決に失敗し、コンパイルエラーを引き起こしていました。このコミットは、このコンパイラの制限を解消し、Go言語の型システムが意図する通りの挙動を保証することを目的としています。powser1.goは、このバグの影響を受けていた既存のコードベースの一部であり、その修正が必要とされました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念と、当時のコンパイラに関する知識が必要です。

  1. Go言語の型システム:

    • 構造体 (Struct): 複数のフィールドをまとめた複合データ型です。
    • ポインタ (Pointer): 変数のメモリアドレスを保持する型です。Goでは*Tのように記述され、T型の値へのポインタを表します。
    • 名前付き型 (Named Type): type MyType BaseTypeのように、既存の型に新しい名前を付けて定義する型です。新しい名前付き型は、基底型とは異なる独自のメソッドセットを持つことができます。
    • メソッド (Method): 特定の型に関連付けられた関数です。Goでは、関数名の前にレシーバ引数を記述することでメソッドを定義します。
    • レシーバ (Receiver): メソッドがどの型の値に対して動作するかを指定する引数です。レシーバには「値レシーバ」(func (t MyType) MethodName(...))と「ポインタレシーバ」(func (t *MyType) MethodName(...))があります。ポインタレシーバは、メソッド内でレシーバの値を変更できる点が特徴です。
  2. Go言語の初期コンパイラ (6g):

    • 6gは、Go言語の初期に存在したコンパイラの一つで、特にamd64アーキテクチャ(64ビットIntel/AMDプロセッサ)をターゲットとしていました。当時のGoコンパイラは、ターゲットアーキテクチャに応じて6g(amd64)、8g(386)、5g(arm)のように命名されていました。
    • これらのコンパイラは、Go言語の初期のツールチェインの一部であり、Plan 9のコンパイラツールチェインから派生し、C言語で実装されていました。
    • 現代のGo(Go 1.5以降)では、これらのアーキテクチャ固有のコンパイラはgo tool compileという単一のバイナリに統合され、コンパイラ自体もGo言語で再実装されています。したがって、6gは現在では直接使用されることはありませんが、Go言語の歴史と進化を理解する上で重要な存在です。

このコミットは、6gコンパイラが、type PS *Sのように定義されたPS型の変数(これは*Sというポインタ型に新しい名前を付けたもの)に対して、*Sに定義されたメソッド(例: (*S).get())を正しく解決できなかったという、当時のコンパイラの限界を示しています。

技術的詳細

このコミットの技術的な核心は、6gコンパイラの型解決およびメソッド解決ロジックの不備にあります。Go言語では、基底型に定義されたメソッドは、その基底型を埋め込んだ構造体や、その基底型を基にした名前付き型に対しても、特定のルールに基づいてプロモート(昇格)され、呼び出すことができます。

問題は、名前付き型がポインタ型である場合に発生しました。具体的には、type PS *Sという定義があった場合、PS*Sの別名であり、*Sに定義されたメソッド(例: func (p *S) get() int)はPS型の変数からも呼び出せるべきです。しかし、当時の6gコンパイラはこのプロモーションを正しく処理できず、PS型の変数に対してget()メソッドを呼び出そうとすると「未定義のメソッド」としてエラーを報告していました。

この修正は、コンパイラの内部で、名前付きポインタ型がその基底ポインタ型に定義されたメソッドを継承し、正しく解決できるようにするための変更が加えられたことを示唆しています。これにより、Go言語の型システムの一貫性が保たれ、開発者はより柔軟な型定義とメソッドの使用が可能になりました。

test/bugs/bug117.goは、この問題を明確に再現するための最小限のテストケースとして設計されています。このテストケースがコンパイルエラーを発生させることが期待されており、修正後はエラーなくコンパイルが成功するようになることで、バグが解消されたことを検証します。

test/chan/powser1.goの変更は、このコンパイラのバグによって影響を受けていた既存のコードを、修正後のコンパイラで正しく動作するように適応させたものです。具体的な変更内容は、関数の引数型をitemから*ratに変更している点です。これは、itemが名前付きポインタ型として定義されており、その型に対するメソッド呼び出しが6gで問題となっていた可能性を示唆しています。*ratへの変更は、コンパイラが正しく処理できる型に明示的に変更することで、一時的に問題を回避するか、あるいはitemの定義自体がこのバグの影響を受けていたため、より具体的なポインタ型に修正したと考えられます。

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

このコミットにおける主要なコード変更は以下の2つのファイルにあります。

  1. test/bugs/bug117.go (新規追加)

    • このファイルは、6gコンパイラのバグを再現するための新しいテストケースです。
    • type S struct { a int } で構造体 S を定義。
    • type PS *S で、*SSへのポインタ)を基底とする名前付きポインタ型 PS を定義。
    • func (p *S) get() int で、*Sに対するメソッド get() を定義。
    • func fn(p PS) int { return p.get() } で、PS型の引数 p を取り、その p に対して get() メソッドを呼び出す関数 fn を定義。この p.get() の部分が、修正前の6gでコンパイルエラーとなっていた箇所です。
    • // errchk $G $D/$F.go というコメントは、このファイルがコンパイルエラーを発生させることを期待するテストであることを示しています。修正後はこのエラーが解消されるべきです。
  2. test/chan/powser1.go (変更)

    • 既存のget関数とcheck関数のシグネチャが変更されています。
    • func get(in *dch) itemfunc get(in *dch) *rat に変更。
    • func check(U PS, c item, count int, str string)func check(U PS, c *rat, count int, str string) に変更。
    • この変更は、item型が名前付きポインタ型であり、その型に対するメソッド呼び出しが6gで問題となっていたため、より具体的なポインタ型である*ratに置き換えることで、コンパイルエラーを回避または修正後のコンパイラで正しく動作するように適応させたものと推測されます。

コアとなるコードの解説

test/bugs/bug117.go

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

// Copyright 2009 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 main
type S struct { a int }
type PS *S
func (p *S) get() int {
  return p.a
}
func fn(p PS) int {
  return p.get()
}
func main() {
  s := S{1};
  if s.get() != 1 {
    panic()
  }
}

このテストケースは、6gコンパイラのバグをピンポイントで狙っています。

  • type S struct { a int }:単純な構造体Sを定義します。
  • type PS *S:ここで、Sへのポインタ型*SPSという新しい名前を付けています。PSは名前付きポインタ型です。
  • func (p *S) get() int { return p.a }*S型に対するメソッドgetを定義しています。このメソッドはSのフィールドaを返します。
  • func fn(p PS) int { return p.get() }:この関数fnがバグの核心を突いています。引数pPS型(名前付きポインタ型)ですが、p.get()と呼び出しています。Goの言語仕様では、PS*Sの基底型を持つため、*Sに定義されたget()メソッドはPS型の変数からも呼び出せるべきです。しかし、修正前の6gコンパイラは、このp.get()を「PS型にgetというメソッドは定義されていない」と誤って判断し、コンパイルエラーを発生させていました。
  • main関数内のs.get()は、S型の変数sから直接get()を呼び出しており、これは問題なくコンパイルされることを示しています。問題はあくまで「名前付きポインタ型」に対するメソッド呼び出しに限定されていました。

このファイルがerrchkコメントと共に存在するということは、このファイルがコンパイルエラーを出すことを期待するテストであり、このコミットによってそのエラーが解消されることを意味します。

test/chan/powser1.go

--- a/test/chan/powser1.go
+++ b/test/chan/powser1.go
@@ -116,7 +116,7 @@ func put(dat item, out *dch){
  	out.dat <- dat;
 }

-func get(in *dch) item{
+func get(in *dch) *rat {
  	seqno++;
  	in.req <- seqno;
  	return <-in.dat;
@@ -610,7 +610,7 @@ func Init() {
  	Twos = Rep(itor(2));
 }

-func check(U PS, c item, count int, str string) {
+func check(U PS, c *rat, count int, str string) {
  	for i := 0; i < count; i++ {
  	\tr := get(U);\
  	\tif !r.eq(c) {

この差分は、powser1.goファイル内の2つの関数シグネチャの変更を示しています。

  • get関数の戻り値の型がitemから*ratに変更されました。
  • check関数の第2引数cの型がitemから*ratに変更されました。

この変更は、item型がbug117.goで示されたような「名前付きポインタ型」であり、その型が6gコンパイラのバグの影響を受けていたために、コンパイルが通らなかった可能性が高いです。*ratは、おそらくrat構造体へのポインタ型であり、この型に直接変更することで、コンパイラが正しくメソッドを解決できるようになり、powser1.goのコンパイルが成功するようになったと考えられます。これは、コンパイラの修正が、既存のコードベースのコンパイル問題を解決した具体的な例と言えます。

関連リンク

参考にした情報源リンク