[インデックス 1942] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)を整形して出力するastprinter.go
と、Goのソースコードからドキュメントを生成するdocprinter.go
の2つのファイルに対する変更を含んでいます。これらのファイルは、Goコンパイラやツールチェーンの一部として、ソースコードの構造を解析し、人間が読める形式やドキュメントとして出力するために重要な役割を担っています。
具体的には、astprinter.go
はASTノードを巡回し、Goのコードとして整形して出力するロジックを含んでいます。一方、docprinter.go
は、ASTからパッケージ、定数、変数、型、関数のドキュメント情報を抽出し、HTML形式などで整形して出力する役割を担っています。
コミット
このコミットは、Go言語のAST(抽象構文木)の内部構造変更に関連する調整です。特に、宣言(import
, const
, type
, var
)の表現方法が変更されたことに伴い、ASTを処理するastprinter
およびdocprinter
パッケージがその新しい構造に適合するようにリファクタリングされています。これにより、より汎用的な宣言の処理が可能になり、コードの重複が削減され、保守性が向上しています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2d543d0c14e03e6a1cbd1ed74152f4018ce505de
元コミット内容
Adjustements related to AST changes.
R=r
OCL=27026
CL=27028
変更の背景
この変更の背景には、Go言語の抽象構文木(AST)における宣言の表現方法の進化があります。初期のGo言語のASTでは、import
, const
, type
, var
といった異なる種類の宣言がそれぞれ個別のASTノード型(例: ast.ImportDecl
, ast.ConstDecl
など)として扱われていた可能性があります。しかし、これらの宣言は多くの場合、共通の構造(例えば、複数の宣言を括弧でグループ化する構文 import (...)
や const (...)
)を持つため、より汎用的なast.GenDecl
(General Declaration)という単一のASTノード型で表現する設計へと移行しました。
このコミットは、astprinter
およびdocprinter
パッケージが、この新しいast.GenDecl
を中心としたAST構造に適切に対応するためのリファクタリングです。以前は個別の宣言型に対応する処理関数(DoImportDecl
, DoConstDecl
など)が存在しましたが、これらをast.GenDecl
を処理する単一の汎用関数に統合し、その内部で具体的な宣言の種類に応じて処理を分岐させることで、コードの簡潔化と将来的な拡張性の向上を図っています。特に、ast.DeclList
という概念(おそらくpretty
パッケージ内部で宣言のリストを扱うために使われていたもの、あるいは古いAST構造の名残)から、go/ast
パッケージの標準的なast.GenDecl
の利用へと移行していることが伺えます。
前提知識の解説
Go言語のAST (Abstract Syntax Tree)
Go言語のコンパイラは、ソースコードを直接機械語に変換するのではなく、まずソースコードを解析してその構造を抽象的に表現した「抽象構文木(AST)」を生成します。ASTは、プログラムの構造を木構造で表現したもので、各ノードがプログラムの要素(変数、関数、式、宣言など)に対応します。これにより、コンパイラはコードの意味を理解し、最適化やエラーチェックを行うことができます。また、goimports
やgofmt
のようなGoのツールもASTを利用してコードの整形や分析を行っています。
go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードのASTを定義する標準ライブラリです。このパッケージには、ASTの各ノードを表す構造体やインターフェースが多数含まれています。
ast.File
: Goの単一のソースファイル全体を表すASTのルートノードです。ファイル内のすべての宣言(Decls
フィールド)を含みます。ast.Decl
: 宣言を表すインターフェースです。ast.GenDecl
とast.FuncDecl
(関数宣言)がこのインターフェースを実装します。ast.GenDecl
: 汎用的な宣言を表す構造体です。import
,const
,type
,var
といった宣言をグループ化して表現します。Tok
フィールドで宣言の種類(token.IMPORT
,token.CONST
など)を示し、Specs
フィールドで具体的な宣言の内容(ast.ImportSpec
,ast.ValueSpec
,ast.TypeSpec
など)のリストを持ちます。ast.ImportSpec
: 単一のimport
宣言の内容を表します。ast.ValueSpec
: 単一のconst
またはvar
宣言の内容を表します。ast.TypeSpec
: 単一のtype
宣言の内容を表します。
token
パッケージ
token
パッケージは、Go言語の字句解析器(lexer)によって識別されるトークン(キーワード、識別子、演算子など)を定義する標準ライブラリです。このコミットでは、token.IMPORT
, token.CONST
, token.TYPE
, token.VAR
といった宣言の種類を示すトークンや、token.LPAREN
(左括弧)、token.RPAREN
(右括弧)などが使用されています。
pretty
パッケージ (astprinter
/docprinter
の役割)
このコミットで変更されているastprinter.go
とdocprinter.go
は、Goのツールチェーンの一部として、ASTを人間が読める形式に変換したり、ドキュメントを生成したりする役割を担っています。
astprinter
: ASTをGoのソースコードとして整形して出力する「プリティプリンター」の機能を提供します。ASTノードを巡回し、適切なインデントや改行を加えて、読みやすいコードを生成します。docprinter
: ASTからGoのパッケージ、定数、変数、型、関数のドキュメントコメントやシグネチャを抽出し、HTMLなどの形式で整形して出力する機能を提供します。これはgo doc
コマンドやGoの公式ドキュメントサイトで利用されるドキュメント生成の基盤となります。
技術的詳細
このコミットの主要な技術的変更点は、GoのASTにおける宣言の処理を、個別の宣言型からast.GenDecl
という汎用的な型に集約したことです。
astprinter.go
における変更
- 個別の宣言処理関数の削除と汎用関数への置き換え:
DoImportDecl
,DoConstDecl
,DoTypeDecl
,DoVarDecl
といった、特定の宣言型(*ast.ImportDecl
,*ast.ConstDecl
など)を直接受け取って処理する関数が削除されました。- 代わりに、
importSpec
,valueSpec
,typeSpec
という、より粒度の小さいast.Spec
インターフェースを実装する型(*ast.ImportSpec
,*ast.ValueSpec
,*ast.TypeSpec
)を処理する汎用的な関数が導入されました。これらの関数は、個々の宣言の内容(名前、型、値など)を整形して出力します。
DoGenDecl
関数の導入と宣言処理の集約:- 新たに
DoGenDecl(d *ast.GenDecl)
関数が導入されました。この関数は、ast.GenDecl
ノードを受け取り、そのTok
フィールド(token.IMPORT
,token.CONST
,token.TYPE
,token.VAR
)に基づいて、どのような種類の宣言であるかを判断します。 DoGenDecl
の内部では、d.Specs
フィールドに格納されているast.Spec
のリストをループ処理し、各Spec
の具体的な型(*ast.ImportSpec
,*ast.ValueSpec
,*ast.TypeSpec
)に応じて、前述のimportSpec
,valueSpec
,typeSpec
関数を呼び出します。- 特に重要なのは、括弧で囲まれた宣言グループ(例:
import (\n "fmt"\n "os"\n)
やconst (\n A = 1\n B = 2\n)
)の処理がDoGenDecl
に集約された点です。d.Lparen.Line > 0
という条件で、グループ化された宣言であるかを判断し、token.LPAREN
とtoken.RPAREN
の出力、および内部の宣言リストの整形を行います。これにより、宣言グループの出力ロジックが一元化され、コードの重複が解消されました。
- 新たに
DoDeclList
関数の削除:- 以前存在した
DoDeclList
関数が削除されました。この関数は、おそらくpretty
パッケージ内部で宣言のリストを扱うために使われていたものですが、その機能はDoGenDecl
に統合され、ast.GenDecl
のSpecs
フィールドを直接処理する形に置き換えられました。
- 以前存在した
docprinter.go
における変更
- ドキュメント構造体の変更:
constDoc
とvarDoc
構造体が、以前は*ast.DeclList
を保持していましたが、*ast.GenDecl
を保持するように変更されました。これは、定数や変数の宣言がast.GenDecl
として表現されるようになったことに対応するためです。typeDoc
構造体も、以前は*ast.TypeDecl
を保持していましたが、*ast.GenDecl
を保持するように変更されました。型宣言もast.GenDecl
の一部として扱われるようになったためです。
- エクスポートされた宣言のチェックロジックの変更:
hasExportedDecls
関数がhasExportedSpecs
関数に名称変更され、引数も[]ast.Decl
から[]ast.Spec
に変更されました。これにより、ast.GenDecl
のSpecs
フィールドに含まれる個々のast.Spec
がエクスポートされているかをチェックするようになりました。
addDecl
関数のロジック変更:addDecl(decl ast.Decl)
関数が大幅にリファクタリングされました。以前は個別の宣言型(*ast.ConstDecl
,*ast.TypeDecl
,*ast.VarDecl
,*ast.DeclList
)に対してswitch
文で処理を分岐していましたが、新しい実装では*ast.GenDecl
型に集約して処理します。ast.GenDecl
の場合、そのTok
フィールド(token.IMPORT
,token.CONST
,token.TYPE
,token.VAR
)に基づいて、インポート宣言を無視したり、定数や変数をvalueDoc
としてPackageDoc
のconsts
やvars
リストに追加したり、型宣言を個別にaddType
関数で処理したりするようになりました。- 特に型宣言の場合、
ast.GenDecl
からast.TypeSpec
を抽出し、そのTypeSpec
のために(一時的に)新しいast.GenDecl
ノードを作成してaddType
に渡すという処理が行われています。これは、ドキュメント生成の際にGenDecl
のドキュメント情報(d.Doc
)を失わないようにするための工夫です。
print
メソッド群の変更:constDoc
とvarDoc
のprint
メソッドが、p.DoDeclList(c.decl)
の代わりにp.DoGenDecl(c.decl)
を呼び出すように変更されました。typeDoc
のprint
メソッドも、p.DoTypeDecl(d)
の代わりにp.DoGenDecl(d)
を呼び出すように変更されました。これにより、ドキュメントの出力も新しいGenDecl
ベースの処理に統一されました。
これらの変更により、astprinter
とdocprinter
は、Go言語のASTにおける宣言の表現がより汎用的なast.GenDecl
に統一されたことに対応し、コードの構造がよりクリーンで効率的になりました。
コアとなるコードの変更箇所
usr/gri/pretty/astprinter.go
--- a/usr/gri/pretty/astprinter.go
+++ b/usr/gri/pretty/astprinter.go
@@ -1112,11 +1112,7 @@ func (P *Printer) DoBadDecl(d *ast.BadDecl) {\n }\n \n \n-func (P *Printer) DoImportDecl(d *ast.ImportDecl) {\n-\tif d.Pos().Offset > 0 {\n-\t\tP.Token(d.Pos(), token.IMPORT);\n-\t\tP.separator = blank;\n-\t}\n+func (P *Printer) importSpec(d *ast.ImportSpec) {\n \tif d.Name != nil {\n \t\tP.Expr(d.Name);\n \t} else {\
@@ -1124,19 +1120,12 @@ func (P *Printer) DoImportDecl(d *ast.ImportDecl) {\n \t}\n \tP.separator = tab;\n \t// TODO fix for longer package names\n-\tif len(d.Path) > 1 {\n-\t\tpanic();\n-\t}\n \tP.HtmlPackageName(d.Path[0].Pos(), string(d.Path[0].Lit));\n \tP.newlines = 2;\n }\n \n \n-func (P *Printer) DoConstDecl(d *ast.ConstDecl) {\n-\tif d.Pos().Offset > 0 {\n-\t\tP.Token(d.Pos(), token.CONST);\n-\t\tP.separator = blank;\n-\t}\n+func (P *Printer) valueSpec(d *ast.ValueSpec) {\n \tP.Idents(d.Names, P.full);\n \tif d.Type != nil {\n \t\tP.separator = blank; // TODO switch to tab? (indentation problem with structs)\
@@ -1152,11 +1141,7 @@ func (P *Printer) DoConstDecl(d *ast.ConstDecl) {\n }\n \n \n-func (P *Printer) DoTypeDecl(d *ast.TypeDecl) {\n-\tif d.Pos().Offset > 0 {\n-\t\tP.Token(d.Pos(), token.TYPE);\n-\t\tP.separator = blank;\n-\t}\n+func (P *Printer) typeSpec(d *ast.TypeSpec) {\n \tP.Expr(d.Name);\n \tP.separator = blank; // TODO switch to tab? (but indentation problem with structs)\n \tP.Expr(d.Type);\
@@ -1164,24 +1149,43 @@ func (P *Printer) DoTypeDecl(d *ast.TypeDecl) {\n }\n \n \n-func (P *Printer) DoVarDecl(d *ast.VarDecl) {\n-\tif d.Pos().Offset > 0 {\n-\t\tP.Token(d.Pos(), token.VAR);\n-\t\tP.separator = blank;\n-\t}\n-\tP.Idents(d.Names, P.full);\n-\tif d.Type != nil {\n-\t\tP.separator = blank; // TODO switch to tab? (indentation problem with structs)\n-\t\tP.Expr(d.Type);\n-\t\t//P.separator = P.Type(d.Type);\n+func (P *Printer) spec(d ast.Spec) {\n+\tswitch s := d.(type) {\n+\tcase *ast.ImportSpec: P.importSpec(s);\n+\tcase *ast.ValueSpec: P.valueSpec(s);\n+\tcase *ast.TypeSpec: P.typeSpec(s);\n+\tdefault: panic(\"unreachable\");\n \t}\n-\tif d.Values != nil {\n-\t\tP.separator = tab;\n-\t\tP.Token(noPos, token.ASSIGN);\n-\t\tP.separator = blank;\n-\t\tP.Exprs(d.Values);\n+}\n+\n+\n+func (P *Printer) DoGenDecl(d *ast.GenDecl) {\n+\tP.Token(d.Pos(), d.Tok);\n+\tP.separator = blank;\n+\n+\tif d.Lparen.Line > 0 {\n+\t\t// group of parenthesized declarations\n+\t\tP.state = opening_scope;\n+\t\tP.Token(d.Lparen, token.LPAREN);\n+\t\tif len(d.Specs) > 0 {\n+\t\t\tP.newlines = 1;\n+\t\t\tfor i := 0; i < len(d.Specs); i++ {\n+\t\t\t\tif i > 0 {\n+\t\t\t\t\tP.separator = semicolon;\n+\t\t\t\t}\n+\t\t\t\tP.spec(d.Specs[i]);\n+\t\t\t\tP.newlines = 1;\n+\t\t\t}\n+\t\t}\n+\t\tP.state = closing_scope;\n+\t\tP.Token(d.Rparen, token.RPAREN);\n+\t\tP.opt_semi = true;\n+\t\tP.newlines = 2;\n+\n+\t} else {\n+\t\t// single declaration\n+\t\tP.spec(d.Specs[0]);\n \t}\n-\tP.newlines = 2;\n }\n \n \n@@ -1209,30 +1213,6 @@ func (P *Printer) DoFuncDecl(d *ast.FuncDecl) {\n }\n \n \n-func (P *Printer) DoDeclList(d *ast.DeclList) {\n-\tP.Token(d.Pos(), d.Tok);\n-\tP.separator = blank;\n-\n-\t// group of parenthesized declarations\n-\tP.state = opening_scope;\n-\tP.Token(noPos, token.LPAREN);\n-\tif len(d.List) > 0 {\n-\t\tP.newlines = 1;\n-\t\tfor i := 0; i < len(d.List); i++ {\n-\t\t\tif i > 0 {\n-\t\t\t\tP.separator = semicolon;\n-\t\t\t}\n-\t\t\tP.Decl(d.List[i]);\n-\t\t\tP.newlines = 1;\n-\t\t}\n-\t}\n-\tP.state = closing_scope;\n-\tP.Token(d.Rparen, token.RPAREN);\n-\tP.opt_semi = true;\n-\tP.newlines = 2;\n-}\n-\n-\n func (P *Printer) Decl(d ast.Decl) {\n \td.Visit(P);\n }\n```
### `usr/gri/pretty/docprinter.go`
```diff
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -38,12 +38,10 @@ func hasExportedNames(names []*ast.Ident) bool {\n }\n \n \n-func hasExportedDecls(decl []ast.Decl) bool {\n-\tfor i, d := range decl {\n-\t\tswitch t := d.(type) {\n-\t\tcase *ast.ConstDecl:\n-\t\t\treturn hasExportedNames(t.Names);\n-\t\t}\n+func hasExportedSpecs(specs []ast.Spec) bool {\n+\tfor i, s := range specs {\n+\t\t// only called for []astSpec lists of *ast.ValueSpec\n+\t\treturn hasExportedNames(s.(*ast.ValueSpec).Names);\n \t}\n \treturn false;\n }\
@@ -51,13 +49,8 @@ func hasExportedDecls(decl []ast.Decl) bool {\n \n // ----------------------------------------------------------------------------\n \n-type constDoc struct {\n-\tdecl *ast.DeclList;\n-}\n-\n-\n-type varDoc struct {\n-\tdecl *ast.DeclList;\n+type valueDoc struct {\n+\tdecl *ast.GenDecl; // len(decl.Specs) >= 1, and the element type is *ast.ValueSpec\n }\n \n \n@@ -67,7 +60,7 @@ type funcDoc struct {\n \n \n type typeDoc struct {\n-\tdecl *ast.TypeDecl;\n+\tdecl *ast.GenDecl; // len(decl.Specs) == 1, and the element type is *ast.TypeSpec\n \tfactories map[string] *funcDoc;\n \tmethods map[string] *funcDoc;\n }\
@@ -76,9 +69,9 @@ type PackageDoc struct {\n \tname string; // package name\n \tdoc ast.Comments; // package documentation, if any\n-\tconsts *vector.Vector; // list of *ast.DeclList with Tok == token.CONST\n-\tvars *vector.Vector; // list of *ast.DeclList with Tok == token.CONST\n+\tconsts *vector.Vector; // list of *valueDoc\n \ttypes map[string] *typeDoc;\n+\tvars *vector.Vector; // list of *valueDoc\n \tfuncs map[string] *funcDoc;\n }\
@@ -160,41 +154,40 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {\n \n func (doc *PackageDoc) addDecl(decl ast.Decl) {\n \tswitch d := decl.(type) {\n-\tcase *ast.ConstDecl:\n-\t\tif hasExportedNames(d.Names) {\n-\t\t\t// TODO\n-\t\t}\n-\n-\tcase *ast.TypeDecl:\n-\t\tif isExported(d.Name) {\n-\t\t\tdoc.addType(d);\n-\t\t}\n-\n-\tcase *ast.VarDecl:\n-\t\tif hasExportedNames(d.Names) {\n-\t\t\t// TODO\n+\tcase *ast.GenDecl:\n+\t\tif len(d.Specs) > 0 {\n+\t\t\tswitch d.Tok {\n+\t\t\tcase token.IMPORT:\n+\t\t\t\t// ignore\n+\t\t\tcase token.CONST:\n+\t\t\t\t// constants are always handled as a group\n+\t\t\t\tif hasExportedSpecs(d.Specs) {\n+\t\t\t\t\tdoc.consts.Push(&valueDoc{d});\n+\t\t\t\t}\n+\t\t\tcase token.TYPE:\n+\t\t\t\t// types are handled individually\n+\t\t\t\tfor i, spec := range d.Specs {\n+\t\t\t\t\ts := spec.(*ast.TypeSpec);\n+\t\t\t\t\tif isExported(s.Name) {\n+\t\t\t\t\t\t// make a (fake) GenDecl node for this TypeSpec\n+\t\t\t\t\t\t// (we need to do this here - as opposed to just\n+\t\t\t\t\t\t// for printing - so we don\'t loose the GenDecl\n+\t\t\t\t\t\t// documentation)\n+\t\t\t\t\t\tvar noPos token.Position;\n+\t\t\t\t\t\tdoc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, noPos, []ast.Spec{s}, noPos});\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\tcase token.VAR:\n+\t\t\t\t// variables are always handled as a group\n+\t\t\t\tif hasExportedSpecs(d.Specs) {\n+\t\t\t\t\tdoc.vars.Push(&valueDoc{d});\n+\t\t\t\t}\n+\t\t\t}\n \t\t}\n-\n \tcase *ast.FuncDecl:\n \t\tif isExported(d.Name) {\n \t\t\tdoc.addFunc(d);\n \t\t}\n-\n-\tcase *ast.DeclList:\n-\t\tswitch d.Tok {\n-\t\tcase token.IMPORT, token.TYPE:\n-\t\t\tfor i, decl := range d.List {\n-\t\t\t\tdoc.addDecl(decl);\n-\t\t\t}\n-\t\tcase token.CONST:\n-\t\t\tif hasExportedDecls(d.List) {\n-\t\t\t\tdoc.consts.Push(&constDoc{d});\n-\t\t\t}\n-\t\tcase token.VAR:\n-\t\t\tif hasExportedDecls(d.List) {\n-\t\t\t\tdoc.consts.Push(&varDoc{d});\n-\t\t\t}\n-\t\t}\n \t}\n }\n \n@@ -214,7 +207,7 @@ func (doc *PackageDoc) AddProgram(prog *ast.Program) {\n \t\tdoc.doc = prog.Doc\n \t}\n \n-\t// add all declarations\n+\t// add all exported declarations\n \tfor i, decl := range prog.Decls {\n \t\tdoc.addDecl(decl);\n \t}\
@@ -381,18 +374,10 @@ func printComments(p *astPrinter.Printer, comment ast.Comments) {\n }\n \n \n-func (c *constDoc) print(p *astPrinter.Printer) {\n+func (c *valueDoc) print(p *astPrinter.Printer) {\n \tprintComments(p, c.decl.Doc);\n \tp.Printf(\"<pre>\");\n-\tp.DoDeclList(c.decl);\n-\tp.Printf(\"</pre>\\n\");\n-}\n-\n-\n-func (c *varDoc) print(p *astPrinter.Printer) {\n-\tprintComments(p, c.decl.Doc);\n-\tp.Printf(\"<pre>\");\n-\tp.DoDeclList(c.decl);\n+\tp.DoGenDecl(c.decl);\n \tp.Printf(\"</pre>\\n\");\n }\n \n@@ -415,11 +400,12 @@ func (f *funcDoc) print(p *astPrinter.Printer, hsize int) {\n \n func (t *typeDoc) print(p *astPrinter.Printer) {\n \td := t.decl;\n-\tp.Printf(\"<h2>type %s</h2>\\n\", string(d.Name.Lit));\n+\ts := d.Specs[0].(*ast.TypeSpec);\n+\tp.Printf(\"<h2>type %s</h2>\\n\", string(s.Name.Lit));\n \tp.Printf(\"<p><pre>\");\n-\tp.DoTypeDecl(d);\n+\tp.DoGenDecl(d);\n \tp.Printf(\"</pre></p>\\n\");\n-\tprintComments(p, d.Doc);\n+\tprintComments(p, s.Doc);\n \t\n \t// print associated methods, if any\n \tfor name, m := range t.factories {\
@@ -458,7 +444,7 @@ func (doc *PackageDoc) Print(writer io.Write) {\n \t\t\t\t\tfmt.Fprintln(writer, \"<hr />\");\n \t\t\t\t\tfmt.Fprintln(writer, \"<h2>Constants</h2>\");\n \t\t\t\t\tfor i := 0; i < doc.consts.Len(); i++ {\n-\t\t\t\t\t\tdoc.consts.At(i).(*constDoc).print(&p);\n+\t\t\t\t\t\tdoc.consts.At(i).(*valueDoc).print(&p);\n \t\t\t\t\t}\n \t\t\t\t}\n \t\t\t},\
@@ -477,7 +463,7 @@ func (doc *PackageDoc) Print(writer io.Write) {\n \t\t\t\t\tfmt.Fprintln(writer, \"<hr />\");\n \t\t\t\t\tfmt.Fprintln(writer, \"<h2>Variables</h2>\");\n \t\t\t\t\tfor i := 0; i < doc.vars.Len(); i++ {\n-\t\t\t\t\t\tdoc.vars.At(i).(*varDoc).print(&p);\n+\t\t\t\t\t\tdoc.vars.At(i).(*valueDoc).print(&p);\n \t\t\t\t\t}\n \t\t\t\t}\n \t\t\t},\
コアとなるコードの解説
astprinter.go
におけるDoGenDecl
の導入
このコミットの最も重要な変更は、astprinter.go
におけるDoGenDecl
関数の導入です。
func (P *Printer) DoGenDecl(d *ast.GenDecl) {
P.Token(d.Pos(), d.Tok);
P.separator = blank;
if d.Lparen.Line > 0 {
// group of parenthesized declarations
P.state = opening_scope;
P.Token(d.Lparen, token.LPAREN);
if len(d.Specs) > 0 {
P.newlines = 1;
for i := 0; i < len(d.Specs); i++ {
if i > 0 {
P.separator = semicolon;
}
P.spec(d.Specs[i]);
P.newlines = 1;
}
}
P.state = closing_scope;
P.Token(d.Rparen, token.RPAREN);
P.opt_semi = true;
P.newlines = 2;
} else {
// single declaration
P.spec(d.Specs[0]);
}
}
P.Token(d.Pos(), d.Tok)
:ast.GenDecl
の開始位置とトークン(import
,const
,type
,var
のいずれか)を出力します。if d.Lparen.Line > 0
: これは、宣言が括弧でグループ化されているかどうかを判断する重要な条件です。例えば、import (\n "fmt"\n "os"\n)
のような形式の場合、Lparen
(左括弧)の位置情報が存在します。- グループ化された宣言の場合:
P.state = opening_scope
とP.Token(d.Lparen, token.LPAREN)
で左括弧を出力し、スコープが開始されたことをプリンターに伝えます。for i := 0; i < len(d.Specs); i++
ループで、ast.GenDecl
に含まれる個々の宣言(ast.Spec
)を処理します。if i > 0 { P.separator = semicolon; }
は、複数の宣言がある場合にセミコロンで区切るためのロジックです。Goでは通常、改行がセミコロンとして扱われますが、明示的にセミコロンを挿入する必要がある場合に対応します。P.spec(d.Specs[i])
が、個々のast.Spec
を処理する汎用関数を呼び出します。このspec
関数は、ast.ImportSpec
,ast.ValueSpec
,ast.TypeSpec
のいずれかであるかをswitch
文で判断し、それぞれの専用の整形関数(importSpec
,valueSpec
,typeSpec
)を呼び出します。P.newlines = 1
は、各宣言の後に改行を挿入します。- ループ終了後、
P.state = closing_scope
とP.Token(d.Rparen, token.RPAREN)
で右括弧を出力し、スコープが終了したことを伝えます。 P.opt_semi = true
は、必要に応じてセミコロンを挿入するオプションを有効にします。
- 単一の宣言の場合:
P.spec(d.Specs[0])
を直接呼び出し、単一の宣言を処理します。
- グループ化された宣言の場合:
このDoGenDecl
の導入により、import
, const
, type
, var
といった異なる種類の宣言の整形ロジックが、ast.GenDecl
という単一の汎用的なASTノード型を通じて一元的に処理されるようになりました。これにより、コードの重複が大幅に削減され、将来的なAST構造の変更にも柔軟に対応できるようになります。
docprinter.go
におけるaddDecl
の変更
docprinter.go
のaddDecl
関数も、ast.GenDecl
の導入に合わせて大きく変更されました。
func (doc *PackageDoc) addDecl(decl ast.Decl) {
switch d := decl.(type) {
case *ast.GenDecl:
if len(d.Specs) > 0 {
switch d.Tok {
case token.IMPORT:
// ignore
case token.CONST:
// constants are always handled as a group
if hasExportedSpecs(d.Specs) {
doc.consts.Push(&valueDoc{d});
}
case token.TYPE:
// types are handled individually
for i, spec := range d.Specs {
s := spec.(*ast.TypeSpec);
if isExported(s.Name) {
// make a (fake) GenDecl node for this TypeSpec
// (we need to do this here - as opposed to just
// for printing - so we don't loose the GenDecl
// documentation)
var noPos token.Position;
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, noPos, []ast.Spec{s}, noPos});
}
}
case token.VAR:
// variables are always handled as a group
if hasExportedSpecs(d.Specs) {
doc.vars.Push(&valueDoc{d});
}
}
}
case *ast.FuncDecl:
if isExported(d.Name) {
doc.addFunc(d);
}
}
}
- 以前は
*ast.ConstDecl
,*ast.TypeDecl
,*ast.VarDecl
,*ast.DeclList
といった個別の型をswitch
で処理していましたが、新しいコードでは*ast.GenDecl
と*ast.FuncDecl
の2つのケースに集約されています。 *ast.GenDecl
の場合、そのTok
フィールド(token.IMPORT
,token.CONST
,token.TYPE
,token.VAR
)に基づいてさらに処理を分岐します。token.IMPORT
: インポート宣言はドキュメントには通常表示されないため無視されます。token.CONST
/token.VAR
: 定数や変数はグループとして扱われ、エクスポートされている場合にvalueDoc
としてPackageDoc
の対応するリストに追加されます。token.TYPE
: 型宣言は個別に処理されます。ast.GenDecl
のSpecs
リストから*ast.TypeSpec
を抽出し、それがエクスポートされている場合、そのTypeSpec
のために一時的なast.GenDecl
ノードを作成してdoc.addType
に渡します。これは、元のGenDecl
が持つドキュメント情報(d.Doc
)を失わずに、個々の型宣言をドキュメントに追加するための巧妙な方法です。
この変更により、ドキュメント生成のロジックもast.GenDecl
の構造に完全に適合し、より効率的かつ正確にGoのドキュメントを生成できるようになりました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
go/ast
パッケージのドキュメント: https://pkg.go.dev/go/astgo/token
パッケージのドキュメント: https://pkg.go.dev/go/token
参考にした情報源リンク
- Web search results for "Go AST changes 2009 ast.DeclList ast.GenDecl" (Google Search)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQIrs0AP2iv-qw9rhHD5eqL2IqProtSTDPr_gwFDbJ_o0X6kq0ZwTJkspEXq_Xia4IvHvy5K7XVwwfDtwKVpbv36J27WlyS2uJouWwU1wSa3_ZVqeqPA3a8MPzMpKcmhuCPYQ3VQd_w1F9mXHpvA9y_eBXeqE=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHbudtZcCajPHIlrjz9v0W_yH6ps6TFd4qa02SQ_TGbWKZ3rGddQB7DsEY39TTs-ioNgG_9i07SOQOG_hQH7_veSvC9OqC96N4N8MfQkvgacLOyGQM=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFoH93rZez8RPBVJT-L7gMV1Esqnxuo5LUmRXjYvQab3OmjznLhk-9NrII9qIgL_q_FqXhxjvNtNkO1ceT5935vVT-SAiPpPR5ouB9amvsfIGAyADTu-XRr8ZjwBOCtclqjdWByInav7ixgLJ_sAnUzSMad0sYS9Xxuq76FwPrzatVDHH2aYGJ1rvUdLkfzKXnvX3dAf4Cgl-yBTdslFG-7csiadX3cQ==