[インデックス 1778] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)とランタイム(runtime
)における複数の重要な変更を導入しています。主な目的は、Go言語に「型スイッチ(type switches)」構文を導入し、インターフェースの型アサーションをより簡潔かつ安全に行えるようにすることです。これに伴い、typeof
キーワードの削除、およびインターフェースから型への変換(I2T)におけるランタイムのバグ修正も含まれています。
コミット
commit a4a10ed856509b9e1f8f2a53b90696e428a51014
Author: Ken Thompson <ken@golang.org>
Date: Fri Mar 6 17:50:43 2009 -0800
1. type switches
2. fixed fault on bug128
3. got rid of typeof
4. fixed bug in t,ok = I2T
R=r
OCL=25873
CL=25873
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4a10ed856509b9e1f8f2a53b90696e428a51014
元コミット内容
1. type switches
2. fixed fault on bug128
3. got rid of typeof
4. fixed bug in t,ok = I2T
R=r
OCL=25873
CL=25873
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ一般公開される前の初期開発段階にありました。Go言語の設計思想の一つに、シンプルさと安全性があります。インターフェースはGoの強力な機能の一つですが、インターフェース値が実際にどのような具象型を持っているかを検査し、それに基づいて異なる処理を行う必要が頻繁に生じます。
従来の型アサーション(value, ok := interfaceValue.(Type)
)は単一の型チェックには有効ですが、複数の型に対して分岐処理を行う場合には冗長になりがちでした。このような背景から、より簡潔で読みやすい構文として「型スイッチ」が提案され、このコミットでそのコンパイラとランタイムのサポートが実装されました。
また、typeof
キーワードは、おそらく初期の実験的な機能として存在していたものの、型スイッチの導入によりその必要性がなくなり、言語のシンプルさを保つために削除されました。
さらに、インターフェースの内部処理におけるバグ修正も含まれており、これは言語の安定性と正確性を確保するための継続的な改善の一環です。特にt,ok = I2T
に関するバグ修正は、インターフェース値から具象型への変換処理の信頼性を高めるものです。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースで定義されたすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします(暗黙的な実装)。インターフェース値は、内部的に具象型の値と、その具象型の型情報(型ディスクリプタ)の2つの要素を保持しています。これにより、実行時にインターフェース値が保持する具象型を特定することが可能になります。
型アサーション (i.(T)
)
型アサーションは、インターフェース値が特定の具象型であるかどうかを検査し、もしそうであればその具象型の値として取り出すための構文です。
例: value, ok := myInterface.(MyStruct)
ok
は、アサーションが成功したかどうかを示す真偽値です。
型スイッチ (switch v := i.(type)
)
型スイッチは、インターフェース値が取りうる複数の具象型に対して、それぞれ異なる処理を記述するための制御構造です。これは、複数のif-else if
と型アサーションを組み合わせたものよりも、はるかに簡潔で読みやすいコードを提供します。
例:
switch v := myInterface.(type) {
case MyStruct:
// v は MyStruct 型
case MyOtherStruct:
// v は MyOtherStruct 型
default:
// v はその他の型
}
型スイッチのcase
節では、型アサーションのようにv
がその型として扱われます。
Goコンパイラ (gc
) の構造
Goコンパイラ(gc
)は、Go言語のソースコードを機械語に変換する役割を担っています。その主要なコンポーネントは以下の通りです。
- 字句解析 (Lexer):
lex.c
などで実装され、ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。 - 構文解析 (Parser):
go.y
(Yacc/Bisonの文法定義ファイル)で定義され、トークンのストリームを抽象構文木(AST: Abstract Syntax Tree)に変換します。ASTはプログラムの構造を階層的に表現したものです。 - 意味解析 (Semantic Analysis): ASTを走査し、型チェック、名前解決、定数畳み込みなどの意味的な検証を行います。
- コード生成 (Code Generation): 意味解析後のASTから、最終的な機械語コードを生成します。
このコミットでは、主に構文解析(go.y
)と意味解析/コード生成(swt.c
)の層に大きな変更が加えられています。
Goランタイム (runtime
)
Goランタイムは、Goプログラムの実行をサポートする低レベルなコードの集合です。ガベージコレクション、スケジューラ、インターフェースの内部処理、メモリ管理などが含まれます。src/runtime/iface.c
は、インターフェースの内部表現と操作に関するコードを含んでいます。
メモリのアライメント (rnd
関数)
コンピュータのメモリは、特定のバイト境界にデータを配置することで、効率的なアクセスが可能になります。この配置の規則を「アライメント」と呼びます。rnd
関数は、与えられた値を指定された境界にアライメントするために使用されることが多いです。例えば、rnd(value, 8)
はvalue
を8バイト境界にアライメントします。アライメントが正しくないと、パフォーマンスの低下や、場合によってはクラッシュなどのバグにつながることがあります。
技術的詳細
型スイッチの実装
このコミットの最も重要な変更は、型スイッチの導入です。
-
ASTノードの追加:
src/cmd/gc/go.h
に新しいASTノードタイプOTYPESW
が追加されました。これは、型スイッチ構文をAST上で表現するために使用されます。また、typeswvar
というNode*
型のグローバル変数も追加され、型スイッチの対象となる変数を管理するために使われます。 -
文法解析の変更 (
src/cmd/gc/go.y
):pexpr '.' '(' LTYPE ')'
という新しい文法規則が追加され、これがOTYPESW
ノードを生成するように定義されました。これは、interfaceValue.(type)
という構文を解析するためのものです。LCASE type ':'
という規則がcomplex_stmt
に追加され、型スイッチのcase
節の文法が定義されました。if_header
とif_body
の規則が修正され、型スイッチの構文がif
文の内部でどのように扱われるか(特にswitch v := i.(type)
のような初期化文を含む場合)が考慮されています。
-
字句解析の変更 (
src/cmd/gc/lex.c
):typeof
キーワードが字句解析器から完全に削除されました。これにより、Go言語のキーワードセットからtypeof
がなくなりました。
-
オペレーション名の追加 (
src/cmd/gc/subr.c
):OTYPESW
に対応する文字列"TYPESW"
がopnames
配列に追加され、デバッグやエラーメッセージでOTYPESW
ノードが適切に表示されるようになりました。
-
スイッチ処理ロジックの変更 (
src/cmd/gc/swt.c
):- このファイルは、Goコンパイラにおける
switch
文のセマンティック解析とコード生成の大部分を担っています。 enum { Snorm, Strue, Sfalse, Stype }
が導入され、switch
文の種類(通常の式スイッチ、真偽値定数スイッチ、型スイッチ)を区別できるようになりました。sw0
,sw1
,sw2
,sw3
,walkcases
といった既存の関数が、新しいarg
パラメータを受け取るように変更されました。これにより、これらの関数はスイッチの種類に応じて異なる振る舞いをすることができます。typeswitch
関数の導入: この関数が型スイッチの核心的な処理を行います。- 型スイッチの対象がインターフェース型であることを検証します。
- 型スイッチは、内部的には一連の
if
文と型アサーションに変換されます。例えば、switch v := i.(type) { case T1: ... case T2: ... }
は、if _, ok := i.(T1); ok { ... } else if _, ok := i.(T2); ok { ... }
のような構造に変換されます。 ODOTTYPE
ノード(interface.(type)
)とOAS
ノード(代入)を組み合わせて、各case
節に対応する型チェックと変数への代入を表現するASTを生成します。
walkswitch
関数のリファクタリング:walkswitch
関数は、与えられたswitch
ノードが型スイッチであるかどうかをsw->ntest->op == OTYPESW
で判断し、もしそうであれば新しく導入されたtypeswitch
関数に処理を委譲します。そうでなければ、従来の通常のスイッチ処理(prepsw
など)を続行します。prepsw
関数もarg
パラメータを受け取るように変更され、スイッチの種類に応じたコード生成の準備を行うようになりました。
- このファイルは、Goコンパイラにおける
typeof
キーワードの削除
typeof
キーワードは、Go言語の初期段階で型の情報を取得するために検討された可能性のある機能ですが、型スイッチやリフレクションといったより強力でGoらしいメカニズムが導入されたことで、その必要性がなくなりました。このコミットで字句解析器から削除されたことで、Go言語の構文から正式に姿を消しました。
t,ok = I2T
バグ修正
src/runtime/iface.c
のsys·ifaceI2T2
関数におけるバグ修正は、インターフェースから型への変換(Interface to Type)処理に関するものです。具体的には、ok
変数のメモリ配置を計算するrnd
関数の引数がrnd(wid, 8)
からrnd(wid, 1)
に変更されました。
sys·ifaceI2T2
は、インターフェース値i
を特定の型st
に変換しようとするランタイム関数です。ret
は変換後の値が格納されるポインタ、ok
は変換が成功したかどうかを示す真偽値が格納されるポインタです。wid
は変換後の型のサイズ(幅)を示します。ok = (bool*)(ret+rnd(wid, 8))
は、ret
ポインタからwid
バイト進んだ後、さらに8バイト境界にアライメントされた位置にok
変数を配置しようとしていました。- しかし、
bool
型は通常1バイトであり、8バイト境界にアライメントする必要はありません。また、ok
変数は変換後の値の直後に配置されるべきであり、過剰なアライメントはメモリの無駄や、場合によっては誤ったメモリ位置へのアクセスを引き起こす可能性があります。 rnd(wid, 1)
への変更は、ok
変数をwid
バイトの直後に、1バイト境界(つまり、アライメントなし)で配置するように修正したものです。これにより、メモリの効率的な利用と、ok
変数が正しい位置に書き込まれることが保証されます。これは、インターフェースの型アサーションがvalue, ok := interfaceValue.(Type)
の形式で、ok
が正しく設定されるために重要な修正です。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -329,7 +329,7 @@ enum
OKEY, OPARAM,
OCOMPOS,
OCONV,
- ODOTTYPE,
+ ODOTTYPE, OTYPESW,
OBAD,
OEXTEND, // 6g internal
@@ -526,6 +526,7 @@ EXTERN Node* retnil;
EXTERN Node* fskel;
EXTERN Node* addtop;
+EXTERN Node* typeswvar;
EXTERN char* context;
EXTERN char* pkgcontext;
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -18,7 +18,7 @@
%token <sym> LPACKAGE LIMPORT LDEFER
%token <sym> LMAP LCHAN LINTERFACE LFUNC LSTRUCT
%token <sym> LCOLAS LFALL LRETURN LDDD
-%token <sym> LLEN LCAP LTYPEOF LPANIC LPANICN LPRINT LPRINTN
+%token <sym> LLEN LCAP LPANIC LPANICN LPRINT LPRINTN
%token <sym> LVAR LTYPE LCONST LCONVERT LSELECT LMAKE LNEW
%token <sym> LFOR LIF LELSE LSWITCH LCASE LDEFAULT
%token <sym> LBREAK LCONTINUE LGO LGOTO LRANGE
@@ -507,6 +511,18 @@ complex_stmt:
$$ = nod(OXCASE, $$, N);\n\t\taddtotop($$);\n\t}\n+|\tLCASE type ':'\n+\t{\n+\t\tpoptodcl();\n+\t\tif(typeswvar == N || typeswvar->right == N) {\n+\t\t\tyyerror("type case not in a type switch");\n+\t\t\t$$ = N;\n+\t\t} else\n+\t\t\t$$ = old2new(typeswvar->right, $2);\n+\t\t$$ = nod(OTYPESW, $$, N);\n+\t\t$$ = nod(OXCASE, $$, N);\n+\t\taddtotop($$);\n+\t}\n |\tLDEFAULT ':'
{\n \tpoptodcl();
@@ -836,6 +862,10 @@ pexpr:
$$ = nod(ODOTTYPE, $1, N);\n\t\t$$->type = $4;\n }\n+|\tpexpr '.' '(' LTYPE ')'\n+\t{\n+\t\t$$ = nod(OTYPESW, $1, N);\n+\t}\n |\tpexpr '[' expr ']'
{\n \t$$ = nod(OINDEX, $1, $3);\
@@ -858,11 +888,6 @@ pexpr:
{\n \t$$ = nod(OCAP, $3, N);\n }\n-|\tLTYPEOF '(' type ')'\n-\t{\n-\t\t$$ = nod(OTYPEOF, N, N);\n-\t\t$$->type = $3;\n-\t}\n |\tLNEW '(' type ')'
{\n \t$$ = nod(ONEW, N, N);\
@@ -1001,7 +1026,6 @@ sym3:
|\tLNEW
|\tLMAKE
|\tLBASETYPE
-|\tLTYPEOF
/*
* keywords that we can
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1110,7 +1110,6 @@ static struct
"switch", LSWITCH, Txxx,
"true", LTRUE, Txxx,
"type", LTYPE, Txxx,
- "typeof", LTYPEOF, Txxx,
"var", LVAR, Txxx,
"notwithstanding", LIGNORE, Txxx,
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -715,6 +715,7 @@ opnames[] =
[OSELECT] = "SELECT",
[OSWITCH] = "SWITCH",
[OTYPE] = "TYPE",
+ [OTYPESW] = "TYPESW",
[OVAR] = "VAR",
[OIMPORT] = "IMPORT",
[OXOR] = "XOR",
src/cmd/gc/swt.c
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -299,35 +332,141 @@ loop:
}
+/*
+ * convert switch of the form
+ *\tswitch v := i.(type) { case t1: ..; case t2: ..; }
+ * into if statements
+ */
void
-walkswitch(Node *n)
+typeswitch(Node *sw)
{
-\tType *t;\n+\tIter save;\n+\tNode *face, *bool, *cas;\n+\tNode *t, *a, *b;\n \n-\tcasebody(n);\n-\tif(n->ntest == N)\n-\t\tn->ntest = booltrue;\n+//dump("typeswitch", sw);\n+\n+\twalktype(sw->ntest->right, Erv);\n+\tif(!istype(sw->ntest->right->type, TINTER)) {\n+\t\tyyerror("type switch must be on an interface");\n+\t\treturn;\n+\t}\n+\twalkcases(sw, sw0, Stype);\n \n-\twalkstate(n->ninit);\n-\twalktype(n->ntest, Erv);\n-\twalkstate(n->nbody);\n+\t/*\n+\t * predeclare variables for the interface var\n+\t * and the boolean var\n+\t */\n+\tface = nod(OXXX, N, N);\n+\ttempname(face, sw->ntest->right->type);\n+\tcas = nod(OAS, face, sw->ntest->right);\n \n-\t// walktype\n-\twalkcases(n, sw0);\n+\tbool = nod(OXXX, N, N);\n+\ttempname(bool, types[TBOOL]);\n \n-\t// find common type\n-\tt = n->ntest->type;\n-\tif(t == T)\n-\t\tt = walkcases(n, sw1);\n+\tt = listfirst(&save, &sw->nbody->left);\n \n-\t// if that fails pick a type\n-\tif(t == T)\n-\t\tt = walkcases(n, sw2);\n+loop:\n+\tif(t == N) {\n+\t\tsw->nbody->left = rev(cas);\n+\t\twalkstate(sw->nbody);\n+//dump("done", sw->nbody->left);\n+\t\treturn;\n+\t}\n \n-\t// set the type on all literals\n-\tif(t != T) {\n-\t\twalkcases(n, sw3);\n-\t\tconvlit(n->ntest, t);\n-\t\tprepsw(n);\n+\tif(t->left == N) {\n+\t\tcas = list(cas, t->right);\t\t// goto default\n+\t\tt = listnext(&save);\n+\t\tgoto loop;\n \t}\n+\tif(t->left->op != OTYPESW) {\n+\t\tt = listnext(&save);\n+\t\tgoto loop;\n+\t}\n+\n+\ta = t->left->left;\t\t// var\n+\ta = nod(OLIST, a, bool);\t// var,bool\n+\n+\tb = nod(ODOTTYPE, face, N);\n+\tb->type = t->left->left->type;\t// interface.(type)\n+\n+\ta = nod(OAS, a, b);\t\t// var,bool = interface.(type)\n+\tcas = list(cas, a);\n+\n+\ta = nod(OIF, N, N);\n+\ta->ntest = bool;\n+\ta->nbody = t->right;\t\t// if bool { goto l }\n+\tcas = list(cas, a);\n+\n+\tt = listnext(&save);\n+\tgoto loop;\n+}\n+\n+void\n+walkswitch(Node *sw)\n+{\n+\tType *t;\n+\tint arg;\n+\n+//dump("walkswitch", sw);\n+\n+\t/*\n+\t * reorder the body into (OLIST, cases, statements)\n+\t * cases have OGOTO into statements.\n+\t * both have inserted OBREAK statements\n+\t */\n+\twalkstate(sw->ninit);\n+\tif(sw->ntest == N)\n+\t\tsw->ntest = booltrue;\n+\tcasebody(sw);\n+\n+\t/*\n+\t * classify the switch test\n+\t * Strue or Sfalse if the test is a bool constant\n+\t *\tthis allows cases to be map/chan/interface assignments\n+\t *\tas well as (boolean) expressions\n+\t * Stype if the test is v := interface.(type)\n+\t *\tthis forces all cases to be types\n+\t * Snorm otherwise\n+\t *\tall cases are expressions\n+\t */\n+\tif(sw->ntest->op == OTYPESW) {\n+\t\ttypeswitch(sw);\n+\t\treturn;\n+\t}\n+\targ = Snorm;\n+\tif(whatis(sw->ntest) == Wlitbool) {\n+\t\targ = Strue;\n+\t\tif(sw->ntest->val.u.xval == 0)\n+\t\t\targ = Sfalse;\n+\t}\n+\n+\t/*\n+\t * init statement is nothing important\n+\t */\n+\twalktype(sw->ntest, Erv);\n+//print("after walkwalks\\n");\n+\n+\t/*\n+\t * pass 0,1,2,3\n+\t * walk the cases as appropriate for switch type\n+\t */\n+\twalkcases(sw, sw0, arg);\n+\tt = sw->ntest->type;\n+\tif(t == T)\n+\t\tt = walkcases(sw, sw1, arg);\n+\tif(t == T)\n+\t\tt = walkcases(sw, sw2, arg);\n+\tif(t == T)\n+\t\treturn;\n+\twalkcases(sw, sw3, arg);\n+\tconvlit(sw->ntest, t);\n+//print("after walkcases\\n");\n+\n+\t/*\n+\t * convert the switch into OIF statements\n+\t */\n+\tprepsw(sw, arg);\n+\twalkstate(sw->nbody);\n+//print("normal done\\n");\n }\n```
### `src/runtime/iface.c`
```diff
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -315,7 +315,7 @@ sys·ifaceI2T2(Sigt *st, Iface i, ...)
ret = (byte*)(&i+1);
alg = st->hash & 0xFF;
wid = st->offset;
- ok = (bool*)(ret+rnd(wid, 8));
+ ok = (bool*)(ret+rnd(wid, 1));
if(iface_debug) {
prints("I2T2 sigt=");
コアとなるコードの解説
src/cmd/gc/go.h
の変更
ODOTTYPE, OTYPESW,
:enum
にOTYPESW
が追加されました。これは、switch i.(type)
のような型スイッチ構文を抽象構文木(AST)で表現するための新しいノードタイプです。ODOTTYPE
はi.(T)
のような型アサーションを表す既存のノードです。EXTERN Node* typeswvar;
:typeswvar
というNode*
型のグローバル変数が宣言されました。これは、型スイッチの対象となるインターフェース変数を一時的に保持するために使用されます。
src/cmd/gc/go.y
の変更
%token ... LTYPEOF ...
の行からLTYPEOF
が削除されました。これは、typeof
キーワードがGo言語の構文から削除されたことを示します。LCASE type ':'
の新しい規則が追加されました。これは、型スイッチのcase T:
節を解析するためのものです。この規則では、typeswvar
(型スイッチの対象変数)と$2
(case
で指定された型)を使って新しいOTYPESW
ノードを生成し、それをOXCASE
ノードの子として追加しています。pexpr '.' '(' LTYPE ')'
の新しい規則が追加されました。これは、interfaceValue.(type)
という構文を解析し、OTYPESW
ノードを生成するためのものです。このノードは、型スイッチのテスト式として使用されます。
src/cmd/gc/lex.c
の変更
"typeof", LTYPEOF, Txxx,
の行が削除されました。これは、字句解析器がtypeof
という文字列を特別なキーワードとして認識しなくなったことを意味します。
src/cmd/gc/subr.c
の変更
[OTYPESW] = "TYPESW",
の行がopnames
配列に追加されました。これにより、OTYPESW
ノードがデバッグ出力などで"TYPESW"
という文字列として表示されるようになります。
src/cmd/gc/swt.c
の変更
typeswitch(Node *sw)
関数の追加:- この関数は、型スイッチのセマンティック解析とコード生成の主要なロジックを含んでいます。
sw->ntest->right
がインターフェース型であることを確認します。そうでない場合はエラーを報告します。face = nod(OXXX, N, N); tempname(face, sw->ntest->right->type); cas = nod(OAS, face, sw->ntest->right);
- これは、型スイッチの対象となるインターフェース変数を一時変数
face
に代入するコードを生成しています。
- これは、型スイッチの対象となるインターフェース変数を一時変数
bool = nod(OXXX, N, N); tempname(bool, types[TBOOL]);
- 型アサーションの結果(成功したかどうか)を保持するための一時的な真偽値変数
bool
を生成しています。
- 型アサーションの結果(成功したかどうか)を保持するための一時的な真偽値変数
a = t->left->left; // var
a = nod(OLIST, a, bool); // var,bool
b = nod(ODOTTYPE, face, N); b->type = t->left->left->type; // interface.(type)
a = nod(OAS, a, b); // var,bool = interface.(type)
- これらの行は、各
case
節に対応する型アサーションのコードを生成しています。具体的には、var, bool = face.(type)
のような形式の代入をASTで表現しています。ODOTTYPE
は型アサーションを表し、その結果がvar
とbool
に代入されます。
- これらの行は、各
a = nod(OIF, N, N); a->ntest = bool; a->nbody = t->right; // if bool { goto l }
- 型アサーションが成功した場合(
bool
が真の場合)に、対応するcase
節のコードブロックにジャンプするためのif
文を生成しています。
- 型アサーションが成功した場合(
walkswitch(Node *sw)
関数の変更:if(sw->ntest->op == OTYPESW) { typeswitch(sw); return; }
- この部分が、
switch
文のテスト式がOTYPESW
ノード(つまりinterfaceValue.(type)
)である場合に、新しく追加されたtypeswitch
関数を呼び出すように変更されました。これにより、型スイッチの処理が専用の関数に委譲されます。
- この部分が、
- それ以外の場合は、従来の通常のスイッチ処理(
arg
パラメータを使って真偽値スイッチや通常の式スイッチを区別)が続行されます。
src/runtime/iface.c
の変更
ok = (bool*)(ret+rnd(wid, 8));
からok = (bool*)(ret+rnd(wid, 1));
への変更。sys·ifaceI2T2
関数は、インターフェースから具象型への変換を行うランタイム関数です。ret
は変換された具象値へのポインタ、ok
は変換が成功したかを示す真偽値へのポインタです。rnd(wid, 8)
は、wid
(具象型のサイズ)を8バイト境界にアライメントした値を返していました。これは、ok
変数を8バイト境界に配置しようとしていたことを意味します。- しかし、
bool
型は通常1バイトであり、8バイト境界にアライメントする必要はありません。この修正は、ok
変数のメモリ配置をwid
の直後(1バイト境界)に修正することで、メモリの無駄をなくし、ok
変数が正しく書き込まれるようにするためのバグ修正です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語のインターフェースに関するブログ記事 (Go公式ブログ): https://go.dev/blog/interfaces
- Go言語の型スイッチに関するドキュメント: https://go.dev/tour/methods/16 (Go Tour)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語のコンパイラ設計に関する一般的な情報源 (例: Go compiler internals, Go AST)
- Yacc/Bisonの文法定義に関する一般的な情報