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

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

このコミットは、Go言語のドキュメンテーションツールであるgo/docおよびgodocにおいて発生していた、レシーバ変数のシャドーイング(shadowing)問題を修正するものです。具体的には、go/docパッケージ内のMethod構造体において、フィールド名が既存のフィールドやメソッドと衝突しないように変更することで、ドキュメンテーション生成時の不整合を防ぎます。

コミット

commit 9e5f62ac0c7259988fb616d6a91625befa1db62f
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Jan 19 08:52:53 2012 -0800

    go/doc, godoc: don't shadow receiver
    
    Fixes #2737.
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/5553062

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

https://github.com/golang/go/commit/9e5f62ac0c7259988fb616d6a91625befa1db62f

元コミット内容

go/doc, godoc: don't shadow receiver
Fixes #2737.

変更の背景

このコミットは、Go言語のIssue 2737を修正するために行われました。Issue 2737は、go/docパッケージがメソッドのレシーバ情報を処理する際に、内部的な変数名が構造体のフィールド名と衝突(シャドーイング)することで、ドキュメンテーションの生成に問題が生じるというバグを報告していました。

Go言語では、メソッドを定義する際にレシーバ(receiver)を指定します。例えば、func (r MyType) MyMethod() {}というメソッド定義では、rがレシーバ変数です。go/docパッケージは、Goのソースコードを解析してドキュメンテーションを生成する役割を担っており、この過程でメソッドのレシーバに関する情報も抽出します。

問題は、go/docパッケージ内のMethod構造体が、その内部に*Funcというフィールドを持っており、Func構造体自体がRecvというフィールド(レシーバの型を表す)を持っていたことです。さらに、Method構造体自身もRecvというフィールドを定義しようとしていました。これにより、名前の衝突が発生し、意図しない動作やドキュメンテーションの誤りが生じる可能性がありました。

このコミットは、Method構造体内のRecvフィールドの名前をOriginに変更することで、このシャドーイング問題を解決し、go/docおよびgodocが正確なドキュメンテーションを生成できるようにすることを目的としています。

前提知識の解説

1. Go言語のレシーバ (Receiver)

Go言語において、メソッドは特定の型に関連付けられた関数です。この関連付けは「レシーバ」と呼ばれる特別な引数によって行われます。レシーバは、メソッドが操作するインスタンス(値またはポインタ)を指定します。

例:

type MyStruct struct {
    Value int
}

// 値レシーバのメソッド
func (s MyStruct) GetValue() int {
    return s.Value
}

// ポインタレシーバのメソッド
func (s *MyStruct) SetValue(newValue int) {
    s.Value = newValue
}

ここで、s MyStructs *MyStructがレシーバの宣言です。sはレシーバ変数名、MyStruct*MyStructはレシーバの型です。

2. 変数のシャドーイング (Variable Shadowing)

プログラミングにおけるシャドーイングとは、あるスコープで宣言された変数が、その内側のスコープで同じ名前の別の変数によって「隠される」現象を指します。内側のスコープでは、外側のスコープの同名変数にはアクセスできなくなり、内側の変数が優先されます。

Go言語では、異なるスコープ(例えば、構造体のフィールドとメソッドの引数、またはネストされたブロック)で同じ名前を使用するとシャドーイングが発生します。これは意図しないバグの原因となることがあります。

例:

package main

import "fmt"

type Example struct {
    name string
}

func (e Example) Greet(name string) { // メソッドの引数 `name` が構造体のフィールド `name` をシャドーイング
    fmt.Printf("Hello, %s! My name is %s\n", name, e.name) // ここで `name` は引数の `name` を指す
}

func main() {
    ex := Example{name: "Alice"}
    ex.Greet("Bob") // 出力: Hello, Bob! My name is Alice
}

この例では、Greetメソッドの引数nameExample構造体のフィールドnameをシャドーイングしています。メソッド内でnameと書くと引数のnameが参照され、構造体のフィールドにアクセスするにはe.nameのようにレシーバ変数を通じて明示的にアクセスする必要があります。

今回のコミットの背景にある問題は、これと似たような状況で、go/docパッケージが内部的に持つ構造体のフィールド名が、別の埋め込み構造体のフィールド名と衝突していたために発生しました。

3. go/docパッケージとgodocツール

  • go/docパッケージ: Go標準ライブラリの一部であり、Goのソースコードを解析してドキュメンテーションツリーを構築するためのAPIを提供します。このパッケージは、パッケージ、型、関数、メソッド、変数などの情報を抽出し、それらを構造化されたデータとして表現します。godocツールはこのパッケージを利用してドキュメンテーションを生成します。
  • godocツール: Go言語の公式ドキュメンテーションツールです。Goのソースコードから自動的にドキュメンテーションを生成し、Webサーバーとして提供したり、コマンドラインで表示したりできます。godocは、コードコメントや宣言から情報を抽出し、それを整形して表示します。

技術的詳細

go/docパッケージは、Goのソースコードを抽象構文木(AST)として解析し、その情報からドキュメンテーションモデルを構築します。このモデルは、PackageTypeFuncMethodなどの構造体で構成されます。

問題が発生したのは、src/pkg/go/doc/doc.go内のMethod構造体でした。この構造体は、Goのメソッドに関するドキュメンテーション情報を保持します。

元のコードでは、Method構造体は以下のように定義されていました。

type Method struct {
    *Func
    // TODO(gri) The following fields are not set at the moment. 
    Recv  *Type // original receiver base type
    Level int   // embedding level; 0 means Func is not embedded
}

ここで注目すべきは、*Funcという埋め込みフィールドです。Goでは、構造体にポインタまたは非ポインタの型を埋め込むことができます。埋め込まれた型(この場合はFunc構造体)のフィールドやメソッドは、外側の構造体(Method)のフィールドやメソッドであるかのように直接アクセスできます。

Func構造体は、関数やメソッドに関する情報を保持しており、その中にはレシーバの型を表すRecvというフィールドが存在します。

// Func is the documentation for a function or method.
type Func struct {
    // ...
    Recv string // actual receiver name (e.g. "x", "*x", or "")
    // ...
}

したがって、Method構造体は*Funcを埋め込んでいるため、MethodのインスタンスからFuncRecvフィールドにm.Recvとしてアクセスできます。しかし、Method構造体自身もRecv *Typeというフィールドを定義しようとしていました。

この状況は、Method構造体内でRecvという名前が二重に定義されることになり、シャドーイングが発生します。具体的には、Method構造体自身のRecv *Typeフィールドが、埋め込まれた*FuncRecv stringフィールドをシャドーイングしてしまいます。これにより、go/docがメソッドのレシーバ情報を正しく処理できなくなり、ドキュメンテーション生成時に誤った情報が使用されたり、予期せぬ動作を引き起こしたりする可能性がありました。

このコミットは、Method構造体内のRecvフィールドの名前をOriginに変更することで、この名前の衝突を解消し、シャドーイングを防ぎます。これにより、go/docはメソッドのレシーバに関する情報を正確に抽出し、godocは正しいドキュメンテーションを生成できるようになります。

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

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -36,8 +36,8 @@ type Value struct {
 type Method struct {
 	*Func
 	// TODO(gri) The following fields are not set at the moment. 
-	Recv  *Type // original receiver base type
-	Level int   // embedding level; 0 means Func is not embedded
+	Origin *Type // original receiver base type
+	Level  int   // embedding level; 0 means Func is not embedded
 }
 
 // Type is the documentation for type declaration.

コアとなるコードの解説

変更はsrc/pkg/go/doc/doc.goファイル内のMethod構造体の定義にあります。

元のコード:

type Method struct {
    *Func
    // TODO(gri) The following fields are not set at the moment. 
    Recv  *Type // original receiver base type
    Level int   // embedding level; 0 means Func is not embedded
}

変更後:

type Method struct {
    *Func
    // TODO(gri) The following fields are not set at the moment. 
    Origin *Type // original receiver base type
    Level  int   // embedding level; 0 means Func is not embedded
}

この変更の核心は、Method構造体内のRecv *Typeフィールドの名前がOrigin *Typeに変更されたことです。

  • RecvからOriginへの変更:
    • Method構造体は*Funcを埋め込んでいます。
    • Func構造体にはRecv stringというフィールドがあります。
    • 元のコードでは、Method構造体自身もRecv *Typeというフィールドを持っていました。
    • これにより、Method構造体のインスタンスからRecvにアクセスしようとすると、Method自身のRecv *Typeフィールドが、埋め込まれたFuncRecv stringフィールドをシャドーイングしていました。
    • フィールド名をOriginに変更することで、この名前の衝突が解消され、シャドーイングがなくなりました。
    • Originという新しい名前は、「元のレシーバの基底型」という意味合いをより明確に示しており、コードの可読性も向上しています。

このシンプルな名前変更により、go/docパッケージはメソッドのレシーバ情報を正しく区別し、処理できるようになり、godocツールが生成するドキュメンテーションの正確性が保証されます。

関連リンク

  • Go Issue 2737: このコミットが修正したIssueの直接的なリンクは、Goの旧Issueトラッカーに存在した可能性がありますが、現在のGitHubリポジトリでは直接見つけることができませんでした。しかし、コミットメッセージに明記されているため、このIssueが存在し、このコミットによって解決されたことは確かです。
  • Go CL 5553062: https://golang.org/cl/5553062 (GoのコードレビューシステムであるGerritのチェンジリストへのリンク)

参考にした情報源リンク

  • Go言語の公式ドキュメンテーション (特にメソッドと構造体の埋め込みに関するセクション)
  • Go言語における変数のシャドーイングに関する一般的な解説記事
  • go/docパッケージのソースコード (src/pkg/go/doc/doc.go)
  • GitHubのGoリポジトリにおける関連するコミット履歴
  • Web検索: "golang issue 2737", "go receiver shadowing", "go/doc package"