[インデックス 11751] ファイルの概要
このコミットは、Go言語のAPI抽出ツールである cmd/api
の挙動を改善し、特に定数参照の追跡能力を向上させるものです。これにより、gccgo
のような代替コンパイラとの互換性が高まり、また、定数型解決における多くの特殊ケースが削除され、コードの簡素化と堅牢化が図られています。
コミット
commit c15a42ed76370afd87aebee0be131dba713bc4f4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 10 10:05:26 2012 +1100
cmd/api: follow constant references
For gccgo. Also removes bunch of special cases.
Fixes #2906
R=golang-dev, remyoudompheng
CC=golang-dev
https://golang.org/cl/5644050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c15a42ed76370afd87aebee0be131dba713bc4f4
元コミット内容
cmd/api: follow constant references
このコミットは、cmd/api
ツールが定数参照を追跡できるようにするものです。これは gccgo
のために行われ、また、多くの特殊ケースを削除します。
Fixes #2906
変更の背景
このコミットの主な背景には、以下の点が挙げられます。
gccgo
との互換性:gccgo
はGo言語の代替コンパイラであり、Goの標準コンパイラ(gc
)とは異なる内部表現や挙動を持つことがあります。cmd/api
ツールはGoの公開APIを抽出するために使用されますが、gccgo
が定数をどのように扱うかという点で、標準ツールとの間に不一致が生じる可能性がありました。特に、定数が他の定数を参照している場合に、その参照を正しく解決できないことが問題となっていました。- 定数型解決の堅牢化と簡素化: 以前の
cmd/api
ツールでは、特定のパッケージ(例:compress/gzip
,os
,path/filepath
,unicode/utf8
,text/scanner
)の定数に対して、その型をハードコードで推測する「特殊ケース」が多数存在していました。これは、GoのAST(抽象構文木)が型情報を持たないという当時の制約に起因するものでした。しかし、このようなハードコードされたロジックは、新しい定数やパッケージが追加されるたびに更新が必要となり、保守が困難でエラーの原因にもなりやすかったのです。 - Issue #2906 の解決: このコミットは、GoのIssueトラッカーで報告されていた Issue 2906 を修正します。このIssueは、
cmd/api
が定数の型を正しく解決できない、特にiota
を使用した定数や、他の定数を参照する定数において問題があることを指摘していました。
これらの背景から、cmd/api
ツールがより汎用的かつ正確に定数型を解決できるよう、定数参照を追跡するメカニズムを導入し、同時に不要な特殊ケースを排除する必要がありました。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
Go言語の定数 (Constants):
- Go言語では、
const
キーワードを使用して定数を宣言します。定数はコンパイル時に値が決定され、実行時には変更できません。 - 定数には型が明示的に指定されることもありますが、多くの場合、型が指定されずに宣言されます(例:
const A = 1
)。このような定数は「型なし定数 (untyped constant)」と呼ばれ、その値は「理想型 (ideal type)」を持ちます(例:ideal-int
,ideal-float
,ideal-string
,ideal-bool
)。 - 型なし定数は、使用される文脈によって適切な型に変換されます。
- 定数は他の定数を参照することができます(例:
const B = A + 1
)。 iota
は、const
宣言内で使用される特別な識別子で、連続する定数に自動的にインクリメントされる値を割り当てるために使用されます。
- Go言語では、
-
Go言語のAST (Abstract Syntax Tree):
- Goコンパイラやツールは、Goのソースコードを解析してASTを構築します。ASTはプログラムの構造を木構造で表現したものです。
go/ast
パッケージは、GoのASTを操作するためのAPIを提供します。go/doc
パッケージは、ASTからドキュメントやAPI情報を抽出するために使用されます。
-
cmd/api
ツール:cmd/api
は、Goの標準ライブラリやパッケージの公開API(エクスポートされた型、関数、変数、定数など)を抽出するための内部ツールです。- このツールは、Goの互換性を保証するために重要です。Goのバージョンアップ時にAPIの破壊的変更がないかを確認するために使用されます。
- コミットメッセージにある
BUG(bradfitz): Note that this tool is only currently suitable for use on the Go standard library, not arbitrary packages.
というコメントは、当時のcmd/api
がまだ汎用的なAPI抽出ツールとしては未熟であり、標準ライブラリに特化していたことを示唆しています。これは、ASTが型情報を持たないという制約と関連しています。
-
gccgo
:gccgo
は、GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。Goの標準コンパイラgc
とは異なる実装であり、Goプログラムをコンパパイルする別の手段を提供します。- 異なるコンパイラ実装が存在する場合、それぞれのコンパイラがGo言語の仕様をどのように解釈し、実装するかに微妙な違いが生じることがあります。特に、コンパイル時の定数評価や型推論の挙動は、互換性の問題を引き起こす可能性があります。
-
pkgSymbol
構造体:- このコミットで導入された
pkgSymbol
は、パッケージ名とシンボル名(識別子名)の組み合わせを表す構造体です。これにより、異なるパッケージに存在する同じ名前のシンボルを区別できるようになります。これは、定数参照を解決する際に、どのパッケージの定数を参照しているのかを正確に特定するために重要です。
- このコミットで導入された
技術的詳細
このコミットの技術的詳細は、cmd/api
ツールが定数型を解決するメカニズムを根本的に変更した点にあります。
変更前のアプローチの問題点
変更前の cmd/api
は、定数の型を決定する際に、主に以下の問題に直面していました。
- ハードコードされた特殊ケース:
compress/gzip
,os
,path/filepath
,unicode/utf8
,text/scanner
などの特定のパッケージに対して、定数名に基づいてその型を推測するhardCodedConstantType
メソッドが存在しました。これは、ASTが型情報を持たないための一時的な回避策でしたが、Goの進化とともに保守が困難になっていました。 - 定数参照の不完全な解決: 定数が他の定数を参照している場合(例:
const B = A
)、cmd/api
はその参照を適切に追跡し、参照先の定数の型を継承することができませんでした。特に、参照先の定数がまだ処理されていない場合や、異なるパッケージの定数を参照している場合に問題が生じました。 iota
の特殊処理:iota
はGoの定数宣言において特殊な意味を持つため、その型をideal-int
として特別に扱う必要がありました。
変更後のアプローチ
このコミットでは、これらの問題を解決するために、以下の主要な変更が導入されました。
-
定数依存関係の追跡 (
constDep
とresolveConstantDeps
):Walker
構造体にconstDep map[string]string
という新しいフィールドが追加されました。これは、ある定数識別子(キー)が、まだ型が解決されていない別の定数識別子(値)に依存していることを記録するためのマップです。constDepPrefix
というマジックプレフィックス ("const-dependency:"
) が導入されました。constValueType
メソッドが定数の型を解決できない場合、例えばconst-dependency:SomeOtherConst
のように、このプレフィックスと依存先の定数名を組み合わせた文字列を一時的な型として返します。resolveConstantDeps
メソッドが追加されました。このメソッドは、パッケージ内のすべてのファイルがウォークされた後に呼び出されます。constDep
マップを走査し、依存関係を解決して最終的な定数型を決定します。これは、依存関係が循環している場合や、複数のレベルでネストしている場合でも、再帰的に型を解決しようとします。- このメカニズムにより、定数の宣言順序に依存せず、また、他の定数を参照する定数の型を正確に解決できるようになりました。
-
pkgSymbol
の導入とprevConstType
の変更:prevConstType
マップのキーがstring
からpkgSymbol
に変更されました。pkgSymbol
は{パッケージ名, シンボル名}
のペアを表す構造体です。- これにより、
prevConstType
は、現在のパッケージだけでなく、インポートされた他のパッケージの定数型も正確に記録できるようになりました。これは、ast.SelectorExpr
(例:flate.BestSpeed
) のように、他のパッケージの定数を参照する場合の型解決に不可欠です。
-
hardCodedConstantType
の大幅な削減:- 新しい定数依存解決メカニズムが導入されたことで、多くのハードコードされた特殊ケースが不要になりました。コミットの差分を見ると、
hardCodedConstantType
メソッドからcompress/gzip
,compress/zlib
,os
,path/filepath
,unicode/utf8
,text/scanner
に関するロジックが削除され、syscall
パッケージのdarwinAMD64
のみが残されています。これは、ツールがより汎用的でデータ駆動型になったことを示しています。
- 新しい定数依存解決メカニズムが導入されたことで、多くのハードコードされた特殊ケースが不要になりました。コミットの差分を見ると、
-
ast.SelectorExpr
の改善:constValueType
メソッド内のast.SelectorExpr
の処理が改善されました。以前はerrTODO
を返していましたが、新しいロジックではselectorFullPkg
を使用して参照先のパッケージを特定し、prevConstType
からその定数の型を取得しようとします。これにより、flate.BestSpeed
のようなクロスパッケージ定数参照も解決できるようになりました。
-
エクスポートされた定数のみの出力:
walkConst
メソッド内で、ast.IsExported(ident.Name)
のチェックが追加され、エクスポートされた定数のみがemitFeature
で出力されるようになりました。これにより、APIツールが公開APIのみを対象とするという本来の目的に合致するようになりました。
これらの変更により、cmd/api
は定数の型をより正確に、かつ汎用的に解決できるようになり、gccgo
との互換性も向上し、コードベースの保守性も高まりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に src/cmd/api/goapi.go
ファイルに集中しています。
-
Walker
構造体の変更:prevConstType
の型がmap[string]string
からmap[pkgSymbol]string
に変更されました。constDep map[string]string
という新しいフィールドが追加されました。
type Walker struct { // ... prevConstType map[pkgSymbol]string // identifier -> "ideal-int" constDep map[string]string // key's const identifier has type of future value const identifier // ... }
-
NewWalker
関数での初期化:NewWalker
関数内でprevConstType
がmake(map[pkgSymbol]string)
で初期化されるようになりました。
func NewWalker() *Walker { return &Walker{ // ... prevConstType: make(map[pkgSymbol]string), } }
-
hardCodedConstantType
メソッドの簡素化:- 多くのパッケージ(
compress/gzip
,os
,path/filepath
,unicode/utf8
,text/scanner
)に関するハードコードされたロジックが削除されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -199,34 +206,10 @@ const ( // the cases we can't handle yet. func (w *Walker) hardCodedConstantType(name string) (typ string, ok bool) { switch w.scope[0] { - case "pkg compress/gzip", "pkg compress/zlib": - switch name { - case "NoCompression", "BestSpeed", "BestCompression", "DefaultCompression": - return "ideal-int", true - } - case "pkg os": - switch name { - case "WNOHANG", "WSTOPPED", "WUNTRACED": - return "ideal-int", true - } - case "pkg path/filepath": - switch name { - case "Separator", "ListSeparator": - return "char", true - } - case "pkg unicode/utf8": - switch name { - case "RuneError": - return "char", true - } - case "pkg text/scanner": - // TODO: currently this tool only resolves const types - // that reference other constant types if they appear - // in the right order. the scanner package has - // ScanIdents and such coming before the Ident/Int/etc - // tokens, hence this hack. - if strings.HasPrefix(name, "Scan") || name == "SkipComments" { - return "ideal-int", true + case "pkg syscall": + switch name { + case "darwinAMD64": + return "ideal-bool", true } } return "", false
- 多くのパッケージ(
-
WalkPackage
メソッドの変更:w.prevConstType = map[string]string{}
の行が削除され、w.constDep = map[string]string{}
が追加されました。w.resolveConstantDeps()
の呼び出しが追加されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -306,7 +289,7 @@ func (w *Walker) WalkPackage(name string) { w.curPackageName = name w.curPackage = apkg - w.prevConstType = map[string]string{} + w.constDep = map[string]string{} for _, afile := range apkg.Files { w.recordTypes(afile) @@ -316,6 +299,8 @@ func (w *Walker) WalkPackage(name string) { w.walkFile(afile) } + w.resolveConstantDeps() + // Now that we're done walking types, vars and consts // in the *ast.Package, use go/doc to do the rest // (functions and methods). This is done here because
-
constValueType
メソッドの変更:ast.SelectorExpr
の処理が改善され、他のパッケージの定数参照を解決できるようになりました。ast.Ident
の処理で、prevConstType
のキーがpkgSymbol
に変更され、未解決の定数依存をconstDepPrefix
を付けて返すようになりました。ast.BinaryExpr
の処理で、定数依存関係の型不一致を処理するロジックが追加されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -447,8 +432,16 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { case *ast.UnaryExpr: return w.constValueType(v.X) case *ast.SelectorExpr: - // e.g. compress/gzip's BestSpeed == flate.BestSpeed - return "", errTODO + lhs := w.nodeString(v.X) + rhs := w.nodeString(v.Sel) + pkg, ok := w.selectorFullPkg[lhs] + if !ok { + return "", fmt.Errorf("unknown constant reference; unknown package in expression %s.%s", lhs, rhs) + } + if t, ok := w.prevConstType[pkgSymbol{pkg, rhs}]; ok { + return t, nil + } + return "", fmt.Errorf("unknown constant reference to %s.%s", lhs, rhs) case *ast.Ident: if v.Name == "iota" { return "ideal-int", nil // hack. @@ -460,10 +453,10 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { // Hack. return "ideal-int", nil } - if t, ok := w.prevConstType[v.Name]; ok { + if t, ok := w.prevConstType[pkgSymbol{w.curPackageName, v.Name}]; ok { return t, nil } - return "", fmt.Errorf("can't resolve existing constant %q", v.Name) + return constDepPrefix + v.Name, nil case *ast.BinaryExpr: left, err := w.constValueType(v.X) if err != nil { @@ -474,6 +467,8 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { if err != nil { return "", err } if left != right { + // TODO(bradfitz): encode the real rules here, + // rather than this mess. if left == "ideal-int" && right == "ideal-float" { return "ideal-float", nil // math.Log2E } @@ -487,6 +482,17 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { // Hack, for package time. return "Duration", nil } + if left == "ideal-int" && !strings.HasPrefix(right, "ideal-") { + return right, nil + } + if right == "ideal-int" && !strings.HasPrefix(left, "ideal-") { + return left, nil + } + if strings.HasPrefix(left, constDepPrefix) && strings.HasPrefix(right, constDepPrefix) { + // Just pick one. + // e.g. text/scanner GoTokens const-dependency:ScanIdents, const-dependency:ScanFloats + return left, nil + } return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right) } return left, nil
-
constDepPrefix
定数の追加:const constDepPrefix = "const-dependency:"
-
walkConst
メソッドの変更:- エクスポートされていない定数をスキップするロジックが削除されました(後で
resolveConstantDeps
で処理されるため)。 constDepPrefix
を持つ型をconstDep
に記録するロジックが追加されました。prevConstType
への書き込みでpkgSymbol
を使用するようになりました。- エクスポートされた定数のみ
emitFeature
で出力するようになりました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -601,11 +607,13 @@ func (w *Walker) resolveName(name string) (v interface{}, t interface{}, ok bool return nil, nil, false } +// constDepPrefix is a magic prefix that is used by constValueType +// and walkConst to signal that a type isn't known yet. These are +// resolved at the end of walking of a package's files. +const constDepPrefix = "const-dependency:" + func (w *Walker) walkConst(vs *ast.ValueSpec) { for _, ident := range vs.Names { - if !ast.IsExported(ident.Name) { - continue - } litType := "" if vs.Type != nil { litType = w.nodeString(vs.Type) @@ -627,13 +635,44 @@ func (w *Walker) walkConst(vs *ast.ValueSpec) { } } } + if strings.HasPrefix(litType, constDepPrefix) { + dep := litType[len(constDepPrefix):] + w.constDep[ident.Name] = dep + continue + } if litType == "" { log.Fatalf("unknown kind in const %q", ident.Name) } w.lastConstType = litType - w.emitFeature(fmt.Sprintf("const %s %s", ident, litType)) - w.prevConstType[ident.Name] = litType + w.prevConstType[pkgSymbol{w.curPackageName, ident.Name}] = litType + + if ast.IsExported(ident.Name) { + w.emitFeature(fmt.Sprintf("const %s %s", ident, litType)) + } + } +} + +func (w *Walker) resolveConstantDeps() { + var findConstType func(string) string + findConstType = func(ident string) string { + if dep, ok := w.constDep[ident]; ok { + return findConstType(dep) + } + if t, ok := w.prevConstType[pkgSymbol{w.curPackageName, ident}]; ok { + return t + } + return "" + } + for ident := range w.constDep { + if !ast.IsExported(ident) { + continue + } + t := findConstType(ident) + if t == "" { + log.Fatalf("failed to resolve constant %q", ident) + } + w.emitFeature(fmt.Sprintf("const %s %s", ident, t)) } }
- エクスポートされていない定数をスキップするロジックが削除されました(後で
-
resolveConstantDeps
メソッドの追加:- 定数依存関係を解決するための新しいメソッドが追加されました。
-
テストデータの変更:
src/cmd/api/testdata/src/pkg/p1/golden.txt
に新しい定数AIsLowerA
とConstChase2
の出力が追加されました。src/cmd/api/testdata/src/pkg/p1/p1.go
に、定数参照のテストケース (ConstChase2
,constChase
,AIsLowerA
) が追加されました。
// src/cmd/api/testdata/src/pkg/p1/p1.go const ( ConstChase2 = constChase // forward declaration to unexported ident constChase = AIsLowerA // forward declaration to exported ident A = 1 a = 11 A64 int64 = 1 AIsLowerA = a // previously declared )
これらの変更は、cmd/api
が定数の型を決定するロジックを、ハードコードされた推測から、より動的で依存関係を解決するアプローチへと移行させたことを示しています。
コアとなるコードの解説
このコミットのコアとなるコードは、src/cmd/api/goapi.go
内の Walker
構造体とその関連メソッド、特に constValueType
, walkConst
, そして新しく追加された resolveConstantDeps
です。
Walker
構造体と pkgSymbol
prevConstType map[pkgSymbol]string
:- このマップは、既に処理された定数の型を記録します。キーが
pkgSymbol
({パッケージ名, シンボル名}
) になったことで、異なるパッケージの同じ名前の定数を区別できるようになりました。これにより、flate.BestSpeed
のような、他のパッケージの定数を参照するケースを正確に処理できます。
- このマップは、既に処理された定数の型を記録します。キーが
constDep map[string]string
:- このマップは、まだ型が解決されていない定数間の依存関係を記録します。例えば、
const B = A
のように、B
の型がA
の型に依存している場合、constDep["B"] = "A"
のように記録されます。これは、定数が宣言された順序に関わらず、依存関係を後から解決するための重要なメカニズムです。
- このマップは、まだ型が解決されていない定数間の依存関係を記録します。例えば、
constValueType
メソッド
このメソッドは、定数の値からその型を推測する中心的なロジックを含んでいます。
ast.SelectorExpr
の処理:v.X
(セレクタの左側、通常はパッケージ名) とv.Sel
(セレクタの右側、通常は定数名) を抽出し、selectorFullPkg
マップを使って完全なパッケージ名を解決します。- 解決されたパッケージ名と定数名を使って
pkgSymbol
を作成し、prevConstType
からその定数の型を取得しようとします。これにより、flate.BestSpeed
のようなクロスパッケージ参照が正しく解決されます。
ast.Ident
の処理:- 識別子(定数名)が
iota
であればideal-int
を返します。 - 識別子が
prevConstType
に存在すれば、その型を返します。 - 重要な変更点: 識別子が
prevConstType
に存在しない場合、以前はエラーを返していましたが、このコミットではconstDepPrefix + v.Name
という形式の文字列を返します。これは、この定数の型がまだ不明であり、v.Name
という別の定数に依存していることを示す「プレースホルダー」です。このプレースホルダーは後でresolveConstantDeps
によって解決されます。
- 識別子(定数名)が
ast.BinaryExpr
の処理:- 二項演算子(例:
+
,-
,*
)を含む定数式の場合、左右のオペランドの型を再帰的にconstValueType
で解決します。 - 左右のオペランドの型が異なる場合の型推論ロジックが改善されました。特に、
ideal-int
と他の具体的な型(int
,float64
など)の組み合わせや、両方がconstDepPrefix
を持つ場合の処理が追加されました。これにより、より複雑な定数式も正しく型推論できるようになりました。
- 二項演算子(例:
walkConst
メソッド
このメソッドは、const
宣言内の各定数をウォークし、その型を決定します。
- エクスポートされていない定数の処理:
- 以前は
!ast.IsExported(ident.Name)
のチェックでエクスポートされていない定数をスキップしていましたが、このコミットではそのチェックが削除されました。これは、すべての定数(エクスポートされているか否かに関わらず)の依存関係をconstDep
に記録し、後でresolveConstantDeps
でまとめて解決するためです。最終的にemitFeature
で出力されるのはエクスポートされた定数のみです。
- 以前は
constDepPrefix
を持つ型の処理:constValueType
から返されたlitType
がconstDepPrefix
で始まる場合、その依存関係をw.constDep
マップに記録します。これにより、定数間の依存関係が明示的に追跡されます。
prevConstType
への記録:- 定数の型が決定されると、
w.prevConstType[pkgSymbol{w.curPackageName, ident.Name}] = litType
のように、pkgSymbol
をキーとしてprevConstType
に記録されます。
- 定数の型が決定されると、
emitFeature
の呼び出し:ast.IsExported(ident.Name)
のチェックがここで行われ、エクスポートされた定数のみがAPIとして出力されます。
resolveConstantDeps
メソッド (新設)
このメソッドは、パッケージ内のすべてのファイルがウォークされた後に呼び出され、未解決の定数依存関係を解決します。
findConstType
クロージャ:- この内部関数は、与えられた識別子
ident
の最終的な型を再帰的に検索します。 - まず
w.constDep
をチェックし、依存関係があればその依存先の定数の型を再帰的にfindConstType
で解決しようとします。 - 次に
w.prevConstType
をチェックし、型が直接記録されていればそれを返します。 - これにより、
A -> B -> C
のような多段階の依存関係も解決できます。
- この内部関数は、与えられた識別子
- 依存関係の解決と出力:
w.constDep
マップを走査し、各依存定数についてfindConstType
を呼び出して最終的な型を決定します。- エクスポートされた定数のみが
emitFeature
で出力されます。 - 型が解決できない場合は
log.Fatalf
でエラーを発生させます。
これらの変更により、cmd/api
は定数の型解決において、宣言順序や複雑な依存関係に左右されない、より堅牢で正確なメカニズムを獲得しました。
関連リンク
- Go Issue 2906: https://github.com/golang/go/issues/2906 - このコミットが修正したGoのIssue。定数型解決の問題について議論されています。
- Go CL 5644050: https://golang.org/cl/5644050 - このコミットに対応するGerritの変更リスト。詳細なレビューコメントや変更履歴を確認できます。
cmd/api
のソースコード (現在のバージョン): https://github.com/golang/go/tree/master/src/cmd/api - 現在のcmd/api
ツールの実装を確認できます。このコミット以降も進化を続けています。go/ast
パッケージ: https://pkg.go.dev/go/ast - Goの抽象構文木を扱うためのパッケージ。go/doc
パッケージ: https://pkg.go.dev/go/doc - Goのパッケージドキュメントを生成するためのパッケージ。
参考にした情報源リンク
- Go Issue 2906: 上記の関連リンクに記載。
- Go CL 5644050: 上記の関連リンクに記載。
- Go言語の公式ドキュメント: Go言語の定数、型システム、ASTに関する一般的な情報源。
gccgo
のドキュメント:gccgo
の特性とGo標準コンパイラとの違いに関する情報源。- Goのソースコード:
src/cmd/api/goapi.go
および関連するテストファイル。 - Goのコミット履歴:
git log
コマンドやGitHubの履歴機能を用いて、このコミット前後の変更を調査。 - Goのメーリングリストやフォーラム: 過去の議論や設計決定に関する情報源。
[インデックス 11751] ファイルの概要
このコミットは、Go言語のAPI抽出ツールである cmd/api
の挙動を改善し、特に定数参照の追跡能力を向上させるものです。これにより、gccgo
のような代替コンパイラとの互換性が高まり、また、定数型解決における多くの特殊ケースが削除され、コードの簡素化と堅牢化が図られています。
コミット
commit c15a42ed76370afd87aebee0be131dba713bc4f4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 10 10:05:26 2012 +1100
cmd/api: follow constant references
For gccgo. Also removes bunch of special cases.
Fixes #2906
R=golang-dev, remyoudompheng
CC=golang-dev
https://golang.org/cl/5644050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c15a42ed76370afd87aebee0be131dba713bc4f4
元コミット内容
cmd/api: follow constant references
このコミットは、cmd/api
ツールが定数参照を追跡できるようにするものです。これは gccgo
のために行われ、また、多くの特殊ケースを削除します。
Fixes #2906
変更の背景
このコミットの主な背景には、以下の点が挙げられます。
gccgo
との互換性:gccgo
はGo言語の代替コンパイラであり、Goの標準コンパイラ(gc
)とは異なる内部表現や挙動を持つことがあります。cmd/api
ツールはGoの公開APIを抽出するために使用されますが、gccgo
が定数をどのように扱うかという点で、標準ツールとの間に不一致が生じる可能性がありました。特に、定数が他の定数を参照している場合に、その参照を正しく解決できないことが問題となっていました。- 定数型解決の堅牢化と簡素化: 以前の
cmd/api
ツールでは、特定のパッケージ(例:compress/gzip
,os
,path/filepath
,unicode/utf8
,text/scanner
)の定数に対して、その型をハードコードで推測する「特殊ケース」が多数存在していました。これは、GoのAST(抽象構文木)が型情報を持たないという当時の制約に起因するものでした。しかし、このようなハードコードされたロジックは、新しい定数やパッケージが追加されるたびに更新が必要となり、保守が困難でエラーの原因にもなりやすかったのです。 - Issue #2906 の解決: このコミットは、GoのIssueトラッカーで報告されていた Issue 2906 を修正します。このIssueは、
cmd/api
が定数の型を正しく解決できない、特にiota
を使用した定数や、他の定数を参照する定数において問題があることを指摘していました。
これらの背景から、cmd/api
ツールがより汎用的かつ正確に定数型を解決できるよう、定数参照を追跡するメカニズムを導入し、同時に不要な特殊ケースを排除する必要がありました。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
Go言語の定数 (Constants):
- Go言語では、
const
キーワードを使用して定数を宣言します。定数はコンパイル時に値が決定され、実行時には変更できません。 - 定数には型が明示的に指定されることもありますが、多くの場合、型が指定されずに宣言されます(例:
const A = 1
)。このような定数は「型なし定数 (untyped constant)」と呼ばれ、その値は「理想型 (ideal type)」を持ちます(例:ideal-int
,ideal-float
,ideal-string
,ideal-bool
)。 - 型なし定数は、使用される文脈によって適切な型に変換されます。
- 定数は他の定数を参照することができます(例:
const B = A + 1
)。 iota
は、const
宣言内で使用される特別な識別子で、連続する定数に自動的にインクリメントされる値を割り当てるために使用されます。
- Go言語では、
-
Go言語のAST (Abstract Syntax Tree):
- Goコンパイラやツールは、Goのソースコードを解析してASTを構築します。ASTはプログラムの構造を木構造で表現したものです。
go/ast
パッケージは、GoのASTを操作するためのAPIを提供します。go/doc
パッケージは、ASTからドキュメントやAPI情報を抽出するために使用されます。
-
cmd/api
ツール:cmd/api
は、Goの標準ライブラリやパッケージの公開API(エクスポートされた型、関数、変数、定数など)を抽出するための内部ツールです。- このツールは、Goの互換性を保証するために重要です。Goのバージョンアップ時にAPIの破壊的変更がないかを確認するために使用されます。
- コミットメッセージにある
BUG(bradfitz): Note that this tool is only currently suitable for use on the Go standard library, not arbitrary packages.
というコメントは、当時のcmd/api
がまだ汎用的なAPI抽出ツールとしては未熟であり、標準ライブラリに特化していたことを示唆しています。これは、ASTが型情報を持たないという制約と関連しています。 - Goプロジェクトにおける
cmd/api
ディレクトリは、APIサーバーとして機能するメインアプリケーションコードを整理するための一般的な慣例です。cmd
ディレクトリはビルド可能なアプリケーションや実行可能ファイルを保持し、cmd/api
は特にAPIサーバーのmain
パッケージとmain()
関数を含みます。
-
gccgo
:gccgo
は、GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。Goの標準コンパイラgc
とは異なる実装であり、Goプログラムをコンパイルする別の手段を提供します。- 異なるコンパイラ実装が存在する場合、それぞれのコンパイラがGo言語の仕様をどのように解釈し、実装するかに微妙な違いが生じることがあります。特に、コンパイル時の定数評価や型推論の挙動は、互換性の問題を引き起こす可能性があります。
-
pkgSymbol
構造体:- このコミットで導入された
pkgSymbol
は、パッケージ名とシンボル名(識別子名)の組み合わせを表す構造体です。これにより、異なるパッケージに存在する同じ名前のシンボルを区別できるようになります。これは、定数参照を解決する際に、どのパッケージの定数を参照しているのかを正確に特定するために重要です。
- このコミットで導入された
技術的詳細
このコミットの技術的詳細は、cmd/api
ツールが定数型を解決するメカニズムを根本的に変更した点にあります。
変更前のアプローチの問題点
変更前の cmd/api
は、定数の型を決定する際に、主に以下の問題に直面していました。
- ハードコードされた特殊ケース:
compress/gzip
,os
,path/filepath
,unicode/utf8
,text/scanner
などの特定のパッケージに対して、定数名に基づいてその型を推測するhardCodedConstantType
メソッドが存在しました。これは、ASTが型情報を持たないための一時的な回避策でしたが、Goの進化とともに保守が困難になっていました。 - 定数参照の不完全な解決: 定数が他の定数を参照している場合(例:
const B = A
)、cmd/api
はその参照を適切に追跡し、参照先の定数の型を継承することができませんでした。特に、参照先の定数がまだ処理されていない場合や、異なるパッケージの定数を参照している場合に問題が生じました。 iota
の特殊処理:iota
はGoの定数宣言において特殊な意味を持つため、その型をideal-int
として特別に扱う必要がありました。
変更後のアプローチ
このコミットでは、これらの問題を解決するために、以下の主要な変更が導入されました。
-
定数依存関係の追跡 (
constDep
とresolveConstantDeps
):Walker
構造体にconstDep map[string]string
という新しいフィールドが追加されました。これは、ある定数識別子(キー)が、まだ型が解決されていない別の定数識別子(値)に依存していることを記録するためのマップです。constDepPrefix
というマジックプレフィックス ("const-dependency:"
) が導入されました。constValueType
メソッドが定数の型を解決できない場合、例えばconst-dependency:SomeOtherConst
のように、このプレフィックスと依存先の定数名を組み合わせた文字列を一時的な型として返します。resolveConstantDeps
メソッドが追加されました。このメソッドは、パッケージ内のすべてのファイルがウォークされた後に呼び出されます。constDep
マップを走査し、依存関係を解決して最終的な定数型を決定します。これは、依存関係が循環している場合や、複数のレベルでネストしている場合でも、再帰的に型を解決しようとします。- このメカニズムにより、定数の宣言順序に依存せず、また、他の定数を参照する定数の型を正確に解決できるようになりました。
-
pkgSymbol
の導入とprevConstType
の変更:prevConstType
マップのキーがstring
からpkgSymbol
に変更されました。pkgSymbol
は{パッケージ名, シンボル名}
のペアを表す構造体です。- これにより、
prevConstType
は、現在のパッケージだけでなく、インポートされた他のパッケージの定数型も正確に記録できるようになりました。これは、ast.SelectorExpr
(例:flate.BestSpeed
) のように、他のパッケージの定数を参照する場合の型解決に不可欠です。
-
hardCodedConstantType
の大幅な削減:- 新しい定数依存解決メカニズムが導入されたことで、多くのハードコードされた特殊ケースが不要になりました。コミットの差分を見ると、
hardCodedConstantType
メソッドからcompress/gzip
,compress/zlib
,os
,path/filepath
,unicode/utf8
,text/scanner
に関するロジックが削除され、syscall
パッケージのdarwinAMD64
のみが残されています。これは、ツールがより汎用的でデータ駆動型になったことを示しています。
- 新しい定数依存解決メカニズムが導入されたことで、多くのハードコードされた特殊ケースが不要になりました。コミットの差分を見ると、
-
ast.SelectorExpr
の改善:constValueType
メソッド内のast.SelectorExpr
の処理が改善されました。以前はerrTODO
を返していましたが、新しいロジックではselectorFullPkg
を使用して参照先のパッケージを特定し、prevConstType
からその定数の型を取得しようとします。これにより、flate.BestSpeed
のようなクロスパッケージ定数参照も解決できるようになりました。
-
エクスポートされた定数のみの出力:
walkConst
メソッド内で、ast.IsExported(ident.Name)
のチェックが追加され、エクスポートされた定数のみがemitFeature
で出力されるようになりました。これにより、APIツールが公開APIのみを対象とするという本来の目的に合致するようになりました。
これらの変更により、cmd/api
は定数の型をより正確に、かつ汎用的に解決できるようになり、gccgo
との互換性も向上し、コードベースの保守性も高まりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に src/cmd/api/goapi.go
ファイルに集中しています。
-
Walker
構造体の変更:prevConstType
の型がmap[string]string
からmap[pkgSymbol]string
に変更されました。constDep map[string]string
という新しいフィールドが追加されました。
type Walker struct { // ... prevConstType map[pkgSymbol]string // identifier -> "ideal-int" constDep map[string]string // key's const identifier has type of future value const identifier // ... }
-
NewWalker
関数での初期化:NewWalker
関数内でprevConstType
がmake(map[pkgSymbol]string)
で初期化されるようになりました。
func NewWalker() *Walker { return &Walker{ // ... prevConstType: make(map[pkgSymbol]string), } }
-
hardCodedConstantType
メソッドの簡素化:- 多くのパッケージ(
compress/gzip
,compress/zlib
,os
,path/filepath
,unicode/utf8
,text/scanner
)に関するハードコードされたロジックが削除されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -199,34 +206,10 @@ const ( // the cases we can't handle yet. func (w *Walker) hardCodedConstantType(name string) (typ string, ok bool) { switch w.scope[0] { - case "pkg compress/gzip", "pkg compress/zlib": - switch name { - case "NoCompression", "BestSpeed", "BestCompression", "DefaultCompression": - return "ideal-int", true - } - case "pkg os": - switch name { - case "WNOHANG", "WSTOPPED", "WUNTRACED": - return "ideal-int", true - } - case "pkg path/filepath": - switch name { - case "Separator", "ListSeparator": - return "char", true - } - case "pkg unicode/utf8": - switch name { - case "RuneError": - return "char", true - } - case "pkg text/scanner": - // TODO: currently this tool only resolves const types - // that reference other constant types if they appear - // in the right order. the scanner package has - // ScanIdents and such coming before the Ident/Int/etc - // tokens, hence this hack. - if strings.HasPrefix(name, "Scan") || name == "SkipComments" { - return "ideal-int", true + case "pkg syscall": + switch name { + case "darwinAMD64": + return "ideal-bool", true } } return "", false
- 多くのパッケージ(
-
WalkPackage
メソッドの変更:w.prevConstType = map[string]string{}
の行が削除され、w.constDep = map[string]string{}
が追加されました。w.resolveConstantDeps()
の呼び出しが追加されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -306,7 +289,7 @@ func (w *Walker) WalkPackage(name string) { w.curPackageName = name w.curPackage = apkg - w.prevConstType = map[string]string{} + w.constDep = map[string]string{} for _, afile := range apkg.Files { w.recordTypes(afile) @@ -316,6 +299,8 @@ func (w *Walker) WalkPackage(name string) { w.walkFile(afile) } + w.resolveConstantDeps() + // Now that we're done walking types, vars and consts // in the *ast.Package, use go/doc to do the rest // (functions and methods). This is done here because
-
constValueType
メソッドの変更:ast.SelectorExpr
の処理が改善され、他のパッケージの定数参照を解決できるようになりました。ast.Ident
の処理で、prevConstType
のキーがpkgSymbol
に変更され、未解決の定数依存をconstDepPrefix
を付けて返すようになりました。ast.BinaryExpr
の処理で、定数依存関係の型不一致を処理するロジックが追加されました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -447,8 +432,16 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { case *ast.UnaryExpr: return w.constValueType(v.X) case *ast.SelectorExpr: - // e.g. compress/gzip's BestSpeed == flate.BestSpeed - return "", errTODO + lhs := w.nodeString(v.X) + rhs := w.nodeString(v.Sel) + pkg, ok := w.selectorFullPkg[lhs] + if !ok { + return "", fmt.Errorf("unknown constant reference; unknown package in expression %s.%s", lhs, rhs) + } + if t, ok := w.prevConstType[pkgSymbol{pkg, rhs}]; ok { + return t, nil + } + return "", fmt.Errorf("unknown constant reference to %s.%s", lhs, rhs) case *ast.Ident: if v.Name == "iota" { return "ideal-int", nil // hack. @@ -460,10 +453,10 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { // Hack. return "ideal-int", nil } - if t, ok := w.prevConstType[v.Name]; ok { + if t, ok := w.prevConstType[pkgSymbol{w.curPackageName, v.Name}]; ok { return t, nil } - return "", fmt.Errorf("can't resolve existing constant %q", v.Name) + return constDepPrefix + v.Name, nil case *ast.BinaryExpr: left, err := w.constValueType(v.X) if err != nil { @@ -474,6 +467,8 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { if err != nil { return "", err } if left != right { + // TODO(bradfitz): encode the real rules here, + // rather than this mess. if left == "ideal-int" && right == "ideal-float" { return "ideal-float", nil // math.Log2E } @@ -487,6 +482,17 @@ func (w *Walker) constValueType(vi interface{}) (string, error) { // Hack, for package time. return "Duration", nil } + if left == "ideal-int" && !strings.HasPrefix(right, "ideal-") { + return right, nil + } + if right == "ideal-int" && !strings.HasPrefix(left, "ideal-") { + return left, nil + } + if strings.HasPrefix(left, constDepPrefix) && strings.HasPrefix(right, constDepPrefix) { + // Just pick one. + // e.g. text/scanner GoTokens const-dependency:ScanIdents, const-dependency:ScanFloats + return left, nil + } return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right) } return left, nil
-
constDepPrefix
定数の追加:const constDepPrefix = "const-dependency:"
-
walkConst
メソッドの変更:- エクスポートされていない定数をスキップするロジックが削除されました(後で
resolveConstantDeps
で処理されるため)。 constDepPrefix
を持つ型をconstDep
に記録するロジックが追加されました。prevConstType
への書き込みでpkgSymbol
を使用するようになりました。- エクスポートされた定数のみ
emitFeature
で出力するようになりました。
--- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -601,11 +607,13 @@ func (w *Walker) resolveName(name string) (v interface{}, t interface{}, ok bool return nil, nil, false } +// constDepPrefix is a magic prefix that is used by constValueType +// and walkConst to signal that a type isn't known yet. These are +// resolved at the end of walking of a package's files. +const constDepPrefix = "const-dependency:" + func (w *Walker) walkConst(vs *ast.ValueSpec) { for _, ident := range vs.Names { - if !ast.IsExported(ident.Name) { - continue - } litType := "" if vs.Type != nil { litType = w.nodeString(vs.Type) @@ -627,13 +635,44 @@ func (w *Walker) walkConst(vs *ast.ValueSpec) { } } } + if strings.HasPrefix(litType, constDepPrefix) { + dep := litType[len(constDepPrefix):] + w.constDep[ident.Name] = dep + continue + } if litType == "" { log.Fatalf("unknown kind in const %q", ident.Name) } w.lastConstType = litType - w.emitFeature(fmt.Sprintf("const %s %s", ident, litType)) - w.prevConstType[ident.Name] = litType + w.prevConstType[pkgSymbol{w.curPackageName, ident.Name}] = litType + + if ast.IsExported(ident.Name) { + w.emitFeature(fmt.Sprintf("const %s %s", ident, litType)) + } + } +} + +func (w *Walker) resolveConstantDeps() { + var findConstType func(string) string + findConstType = func(ident string) string { + if dep, ok := w.constDep[ident]; ok { + return findConstType(dep) + } + if t, ok := w.prevConstType[pkgSymbol{w.curPackageName, ident}]; ok { + return t + } + return "" + } + for ident := range w.constDep { + if !ast.IsExported(ident) { + continue + } + t := findConstType(ident) + if t == "" { + log.Fatalf("failed to resolve constant %q", ident) + } + w.emitFeature(fmt.Sprintf("const %s %s", ident, t)) } }
- エクスポートされていない定数をスキップするロジックが削除されました(後で
-
resolveConstantDeps
メソッドの追加:- 定数依存関係を解決するための新しいメソッドが追加されました。
-
テストデータの変更:
src/cmd/api/testdata/src/pkg/p1/golden.txt
に新しい定数AIsLowerA
とConstChase2
の出力が追加されました。src/cmd/api/testdata/src/pkg/p1/p1.go
に、定数参照のテストケース (ConstChase2
,constChase
,AIsLowerA
) が追加されました。
// src/cmd/api/testdata/src/pkg/p1/p1.go const ( ConstChase2 = constChase // forward declaration to unexported ident constChase = AIsLowerA // forward declaration to exported ident A = 1 a = 11 A64 int64 = 1 AIsLowerA = a // previously declared )
これらの変更は、cmd/api
が定数の型を決定するロジックを、ハードコードされた推測から、より動的で依存関係を解決するアプローチへと移行させたことを示しています。
コアとなるコードの解説
このコミットのコアとなるコードは、src/cmd/api/goapi.go
内の Walker
構造体とその関連メソッド、特に constValueType
, walkConst
, そして新しく追加された resolveConstantDeps
です。
Walker
構造体と pkgSymbol
prevConstType map[pkgSymbol]string
:- このマップは、既に処理された定数の型を記録します。キーが
pkgSymbol
({パッケージ名, シンボル名}
) になったことで、異なるパッケージの同じ名前の定数を区別できるようになりました。これにより、flate.BestSpeed
のような、他のパッケージの定数を参照するケースを正確に処理できます。
- このマップは、既に処理された定数の型を記録します。キーが
constDep map[string]string
:- このマップは、まだ型が解決されていない定数間の依存関係を記録します。例えば、
const B = A
のように、B
の型がA
の型に依存している場合、constDep["B"] = "A"
のように記録されます。これは、定数が宣言された順序に関わらず、依存関係を後から解決するための重要なメカニズムです。
- このマップは、まだ型が解決されていない定数間の依存関係を記録します。例えば、
constValueType
メソッド
このメソッドは、定数の値からその型を推測する中心的なロジックを含んでいます。
ast.SelectorExpr
の処理:v.X
(セレクタの左側、通常はパッケージ名) とv.Sel
(セレクタの右側、通常は定数名) を抽出し、selectorFullPkg
マップを使って完全なパッケージ名を解決します。- 解決されたパッケージ名と定数名を使って
pkgSymbol
を作成し、prevConstType
からその定数の型を取得しようとします。これにより、flate.BestSpeed
のようなクロスパッケージ参照が正しく解決されます。
ast.Ident
の処理:- 識別子(定数名)が
iota
であればideal-int
を返します。 - 識別子が
prevConstType
に存在すれば、その型を返します。 - 重要な変更点: 識別子が
prevConstType
に存在しない場合、以前はエラーを返していましたが、このコミットではconstDepPrefix + v.Name
という形式の文字列を返します。これは、この定数の型がまだ不明であり、v.Name
という別の定数に依存していることを示す「プレースホルダー」です。このプレースホルダーは後でresolveConstantDeps
によって解決されます。
- 識別子(定数名)が
ast.BinaryExpr
の処理:- 二項演算子(例:
+
,-
,*
)を含む定数式の場合、左右のオペランドの型を再帰的にconstValueType
で解決します。 - 左右のオペランドの型が異なる場合の型推論ロジックが改善されました。特に、
ideal-int
と他の具体的な型(int
,float64
など)の組み合わせや、両方がconstDepPrefix
を持つ場合の処理が追加されました。これにより、より複雑な定数式も正しく型推論できるようになりました。
- 二項演算子(例:
walkConst
メソッド
このメソッドは、const
宣言内の各定数をウォークし、その型を決定します。
- エクスポートされていない定数の処理:
- 以前は
!ast.IsExported(ident.Name)
のチェックでエクスポートされていない定数をスキップしていましたが、このコミットではそのチェックが削除されました。これは、すべての定数(エクスポートされているか否かに関わらず)の依存関係をconstDep
に記録し、後でresolveConstantDeps
でまとめて解決するためです。最終的にemitFeature
で出力されるのはエクスポートされた定数のみです。
- 以前は
constDepPrefix
を持つ型の処理:constValueType
から返されたlitType
がconstDepPrefix
で始まる場合、その依存関係をw.constDep
マップに記録します。これにより、定数間の依存関係が明示的に追跡されます。
prevConstType
への記録:- 定数の型が決定されると、
w.prevConstType[pkgSymbol{w.curPackageName, ident.Name}] = litType
のように、pkgSymbol
をキーとしてprevConstType
に記録されます。
- 定数の型が決定されると、
emitFeature
の呼び出し:ast.IsExported(ident.Name)
のチェックがここで行われ、エクスポートされた定数のみがAPIとして出力されます。
resolveConstantDeps
メソッド (新設)
このメソッドは、パッケージ内のすべてのファイルがウォークされた後に呼び出され、未解決の定数依存関係を解決します。
findConstType
クロージャ:- この内部関数は、与えられた識別子
ident
の最終的な型を再帰的に検索します。 - まず
w.constDep
をチェックし、依存関係があればその依存先の定数の型を再帰的にfindConstType
で解決しようとします。 - 次に
w.prevConstType
をチェックし、型が直接記録されていればそれを返します。 - これにより、
A -> B -> C
のような多段階の依存関係も解決できます。
- この内部関数は、与えられた識別子
- 依存関係の解決と出力:
w.constDep
マップを走査し、各依存定数についてfindConstType
を呼び出して最終的な型を決定します。- エクスポートされた定数のみが
emitFeature
で出力されます。 - 型が解決できない場合は
log.Fatalf
でエラーを発生させます。
これらの変更により、cmd/api
は定数の型解決において、宣言順序や複雑な依存関係に左右されない、より堅牢で正確なメカニズムを獲得しました。
関連リンク
- Go Issue 2906: https://github.com/golang/go/issues/2906 - このコミットが修正したGoのIssue。定数型解決の問題について議論されています。
- Go CL 5644050: https://golang.org/cl/5644050 - このコミットに対応するGerritの変更リスト。詳細なレビューコメントや変更履歴を確認できます。
cmd/api
のソースコード (現在のバージョン): https://github.com/golang/go/tree/master/src/cmd/api - 現在のcmd/api
ツールの実装を確認できます。このコミット以降も進化を続けています。go/ast
パッケージ: https://pkg.go.dev/go/ast - Goの抽象構文木を扱うためのパッケージ。go/doc
パッケージ: https://pkg.go.dev/go/doc - Goのパッケージドキュメントを生成するためのパッケージ。
参考にした情報源リンク
- Go Issue 2906: 上記の関連リンクに記載。
- Go CL 5644050: 上記の関連リンクに記載。
- Go言語の公式ドキュメント: Go言語の定数、型システム、ASTに関する一般的な情報源。
gccgo
のドキュメント:gccgo
の特性とGo標準コンパイラとの違いに関する情報源。- Goのソースコード:
src/cmd/api/goapi.go
および関連するテストファイル。 - Goのコミット履歴:
git log
コマンドやGitHubの履歴機能を用いて、このコミット前後の変更を調査。 - Goのメーリングリストやフォーラム: 過去の議論や設計決定に関する情報源。
- Goプロジェクトの
cmd/api
ディレクトリに関する情報:- https://medium.com/@benjamin.c.wong/go-project-layout-cmd-internal-pkg-and-more-a72122d029e
- https://go.dev/doc/modules/layout
- https://medium.com/@benjamin.c.wong/go-project-layout-cmd-internal-pkg-and-more-a72122d029e
- https://medium.com/@benjamin.c.wong/go-project-layout-cmd-internal-pkg-and-more-a72122d029e
- https://github.com/golang-standards/project-layout
- https://www.alexedwards.net/blog/organising-go-project-structure
- https://go.dev/doc/code
- https://stackoverflow.com/questions/59014090/how-to-run-a-go-program-from-cmd-main-go
- https://go.dev/doc/tutorial/getting-started
- https://www.digitalocean.com/community/tutorials/how-to-build-go-applications-with-go-modules