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

[インデックス 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は、プログラムの構造を木構造で表現したもので、各ノードがプログラムの要素(変数、関数、式、宣言など)に対応します。これにより、コンパイラはコードの意味を理解し、最適化やエラーチェックを行うことができます。また、goimportsgofmtのようなGoのツールもASTを利用してコードの整形や分析を行っています。

go/astパッケージ

go/astパッケージは、Go言語のソースコードのASTを定義する標準ライブラリです。このパッケージには、ASTの各ノードを表す構造体やインターフェースが多数含まれています。

  • ast.File: Goの単一のソースファイル全体を表すASTのルートノードです。ファイル内のすべての宣言(Declsフィールド)を含みます。
  • ast.Decl: 宣言を表すインターフェースです。ast.GenDeclast.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.godocprinter.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.LPARENtoken.RPARENの出力、および内部の宣言リストの整形を行います。これにより、宣言グループの出力ロジックが一元化され、コードの重複が解消されました。
  • DoDeclList関数の削除:
    • 以前存在したDoDeclList関数が削除されました。この関数は、おそらくprettyパッケージ内部で宣言のリストを扱うために使われていたものですが、その機能はDoGenDeclに統合され、ast.GenDeclSpecsフィールドを直接処理する形に置き換えられました。

docprinter.goにおける変更

  • ドキュメント構造体の変更:
    • constDocvarDoc構造体が、以前は*ast.DeclListを保持していましたが、*ast.GenDeclを保持するように変更されました。これは、定数や変数の宣言がast.GenDeclとして表現されるようになったことに対応するためです。
    • typeDoc構造体も、以前は*ast.TypeDeclを保持していましたが、*ast.GenDeclを保持するように変更されました。型宣言もast.GenDeclの一部として扱われるようになったためです。
  • エクスポートされた宣言のチェックロジックの変更:
    • hasExportedDecls関数がhasExportedSpecs関数に名称変更され、引数も[]ast.Declから[]ast.Specに変更されました。これにより、ast.GenDeclSpecsフィールドに含まれる個々の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としてPackageDocconstsvarsリストに追加したり、型宣言を個別にaddType関数で処理したりするようになりました。
    • 特に型宣言の場合、ast.GenDeclからast.TypeSpecを抽出し、そのTypeSpecのために(一時的に)新しいast.GenDeclノードを作成してaddTypeに渡すという処理が行われています。これは、ドキュメント生成の際にGenDeclのドキュメント情報(d.Doc)を失わないようにするための工夫です。
  • printメソッド群の変更:
    • constDocvarDocprintメソッドが、p.DoDeclList(c.decl)の代わりにp.DoGenDecl(c.decl)を呼び出すように変更されました。
    • typeDocprintメソッドも、p.DoTypeDecl(d)の代わりにp.DoGenDecl(d)を呼び出すように変更されました。これにより、ドキュメントの出力も新しいGenDeclベースの処理に統一されました。

これらの変更により、astprinterdocprinterは、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_scopeP.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_scopeP.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.goaddDecl関数も、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.GenDeclSpecsリストから*ast.TypeSpecを抽出し、それがエクスポートされている場合、そのTypeSpecのために一時的なast.GenDeclノードを作成してdoc.addTypeに渡します。これは、元のGenDeclが持つドキュメント情報(d.Doc)を失わずに、個々の型宣言をドキュメントに追加するための巧妙な方法です。

この変更により、ドキュメント生成のロジックもast.GenDeclの構造に完全に適合し、より効率的かつ正確にGoのドキュメントを生成できるようになりました。

関連リンク

参考にした情報源リンク