[インデックス 10676] ファイルの概要
このコミットは、Goコンパイラ(gc
)における文字定数(rune定数)の型規則の実装に関するものです。具体的には、文字リテラルがコンパイラ内部でどのように扱われるかをより正確に定義し、CTRUNE
という新しい定数型を導入することで、その挙動を改善しています。これにより、文字定数が他の数値定数と組み合わされた際の型推論と演算が、Go言語の仕様に沿ってより厳密に行われるようになります。
コミット
commit be0ffbfd0246646344c2d86a5660564d1a08a5b3
Author: Russ Cox <rsc@golang.org>
Date: Thu Dec 8 22:07:43 2011 -0500
gc: implement character constant type rules
R=ken2
CC=golang-dev
https://golang.org/cl/5444054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/be0ffbfd0246646344c2d86a5660564d1a08a5b3
元コミット内容
src/cmd/5g/cgen.c | 2 +-\n src/cmd/5g/gsubr.c | 3 ++-\n src/cmd/6g/gsubr.c | 3 ++-\n src/cmd/8g/gsubr.c | 1 +\n src/cmd/gc/const.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++---\n src/cmd/gc/fmt.c | 10 ++++++++\n src/cmd/gc/go.h | 3 ++-\n src/cmd/gc/go.y | 6 +++++\n src/cmd/gc/lex.c | 2 +-\n src/cmd/gc/sinit.c | 1 +\n src/cmd/gc/subr.c | 1 +\n src/cmd/gc/swt.c | 2 ++\n src/cmd/gc/typecheck.c | 2 ++\n src/cmd/gc/walk.c | 3 +++\n test/rune.go | 43 +++++++++++++++++++++++++++++++\n 15 files changed, 143 insertions(+), 9 deletions(-)
diff --git a/src/cmd/5g/cgen.c b/src/cmd/5g/cgen.c
index 0616cd3668..b0a6040033 100644
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -1066,7 +1066,7 @@ bgen(Node *n, int true, Prog *to)
}
if(nr->op == OLITERAL) {
- if(nr->val.ctype == CTINT && mpgetfix(nr->val.u.xval) == 0) {
+ if(isconst(nr, CTINT) && mpgetfix(nr->val.u.xval) == 0) {
gencmp0(nl, nl->type, a, to);
break;
}
diff --git a/src/cmd/5g/gsubr.c b/src/cmd/5g/gsubr.c
index f287214533..73ae3304ad 100644
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -1320,6 +1320,7 @@ naddr(Node *n, Addr *a, int canemitcode)
a->dval = mpgetflt(n->val.u.fval);
break;
case CTINT:
+ case CTRUNE:
a->sym = S;
a->type = D_CONST;
a->offset = mpgetfix(n->val.u.xval);
@@ -1777,7 +1778,7 @@ sudoaddable(int as, Node *n, Addr *a, int *w)
switch(n->op) {
case OLITERAL:
- if(n->val.ctype != CTINT)
+ if(!isconst(n, CTINT))
break;
v = mpgetfix(n->val.u.xval);
if(v >= 32000 || v <= -32000)
diff --git a/src/cmd/6g/gsubr.c b/src/cmd/6g/gsubr.c
index c16a3645a8..c43d2ef82f 100644
--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -1175,6 +1175,7 @@ naddr(Node *n, Addr *a, int canemitcode)
a->dval = mpgetflt(n->val.u.fval);
break;
case CTINT:
+ case CTRUNE:
a->sym = S;
a->type = D_CONST;
a->offset = mpgetfix(n->val.u.xval);
@@ -1878,7 +1879,7 @@ sudoaddable(int as, Node *n, Addr *a)
switch(n->op) {
case OLITERAL:
- if(n->val.ctype != CTINT)
+ if(!isconst(n, CTINT))
break;
v = mpgetfix(n->val.u.xval);
if(v >= 32000 || v <= -32000)
diff --git a/src/cmd/8g/gsubr.c b/src/cmd/8g/gsubr.c
index c7c39b4183..dd6ffbc4c6 100644
--- a/src/cmd/8g/gsubr.c
+++ b/src/cmd/8g/gsubr.c
@@ -1885,6 +1885,7 @@ naddr(Node *n, Addr *a, int canemitcode)
a->dval = mpgetflt(n->val.u.fval);
break;
case CTINT:
+ case CTRUNE:
a->sym = S;
a->type = D_CONST;
a->offset = mpgetfix(n->val.u.xval);
diff --git a/src/cmd/gc/const.c b/src/cmd/gc/const.c
index dd4c4433be..01c4f15b3f 100644
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -170,6 +170,7 @@ convlit1(Node **np, Type *t, int explicit)
break;
case CTINT:
+ case CTRUNE:
case CTFLT:
case CTCPLX:
ct = n->val.ctype;
@@ -179,6 +180,7 @@ convlit1(Node **np, Type *t, int explicit)
goto bad;
case CTCPLX:
case CTFLT:
+ case CTRUNE:
n->val = toint(n->val);
// flowthrough
case CTINT:
@@ -192,6 +194,7 @@ convlit1(Node **np, Type *t, int explicit)
goto bad;
case CTCPLX:
case CTINT:
+ case CTRUNE:
n->val = toflt(n->val);
// flowthrough
case CTFLT:
@@ -206,6 +209,7 @@ convlit1(Node **np, Type *t, int explicit)
goto bad;
case CTFLT:
case CTINT:
+ case CTRUNE:
n->val = tocplx(n->val);
break;
case CTCPLX:
@@ -213,7 +217,7 @@ convlit1(Node **np, Type *t, int explicit)
break;
}
} else
- if(et == TSTRING && ct == CTINT && explicit)
+ if(et == TSTRING && (ct == CTINT || ct == CTRUNE) && explicit)
n->val = tostr(n->val);
else
goto bad;
@@ -243,6 +247,7 @@ copyval(Val v)
switch(v.ctype) {
case CTINT:
+ case CTRUNE:
\ti = mal(sizeof(*i));
\tmpmovefixfix(i, v.u.xval);\n \tv.u.xval = i;\n@@ -269,6 +274,7 @@ tocplx(Val v)
switch(v.ctype) {
case CTINT:
+ case CTRUNE:
\tc = mal(sizeof(*c));\n \tmpmovefixflt(&c->real, v.u.xval);\n \tmpmovecflt(&c->imag, 0.0);\n@@ -293,6 +299,7 @@ toflt(Val v)
switch(v.ctype) {
case CTINT:
+ case CTRUNE:
\tf = mal(sizeof(*f));\n \tmpmovefixflt(f, v.u.xval);\n \tv.ctype = CTFLT;\n@@ -316,6 +323,9 @@ toint(Val v)
Mpint *i;
switch(v.ctype) {
+ case CTRUNE:
+ v.ctype = CTINT;
+ break;
case CTFLT:
\ti = mal(sizeof(*i));\n \tif(mpmovefltfix(i, v.u.fval) < 0)\n@@ -345,6 +355,7 @@ overflow(Val v, Type *t)
return;
switch(v.ctype) {
case CTINT:
+ case CTRUNE:
\tif(!isint[t->etype])\n \t\tfatal(\"overflow: %T integer constant\", t);\n \tif(mpcmpfixfix(v.u.xval, minintval[t->etype]) < 0 ||\n@@ -379,6 +390,7 @@ tostr(Val v)
switch(v.ctype) {
case CTINT:
+ case CTRUNE:
\tif(mpcmpfixfix(v.u.xval, minintval[TINT]) < 0 ||\n \t\t mpcmpfixfix(v.u.xval, maxintval[TINT]) > 0)\n \t\t\tyyerror(\"overflow in int -> string\");\n@@ -415,7 +427,12 @@ consttype(Node *n)
int
isconst(Node *n, int ct)
{
- return consttype(n) == ct;
+ int t;
+
+ t = consttype(n);
+ // If the caller is asking for CTINT, allow CTRUNE too.
+ // Makes life easier for back ends.
+ return t == ct || (ct == CTINT && t == CTRUNE);
}
/*
@@ -518,7 +535,8 @@ evconst(Node *n)
n->right = nr;
if(nr->type && (issigned[nr->type->etype] || !isint[nr->type->etype]))
goto illegal;
- nl->val = toint(nl->val);
+ if(nl->val.ctype != CTRUNE)
+ nl->val = toint(nl->val);
nr->val = toint(nr->val);
break;
}
@@ -540,6 +558,17 @@ evconst(Node *n)
v = toflt(v);
rv = toflt(rv);
}
+\n+\t// Rune and int turns into rune.\n+\tif(v.ctype == CTRUNE && rv.ctype == CTINT)\n+\t\trv.ctype = CTRUNE;\n+\tif(v.ctype == CTINT && rv.ctype == CTRUNE) {\n+\t\tif(n->op == OLSH || n->op == ORSH)\n+\t\t\trv.ctype = CTINT;\n+\t\telse\n+\t\t\tv.ctype = CTRUNE;\n+\t}\n+\n \tif(v.ctype != rv.ctype) {
// Use of undefined name as constant?
if((v.ctype == 0 || rv.ctype == 0) && nerrors > 0)
return;
@@ -559,15 +588,19 @@ evconst(Node *n)
case TUP(OADD, CTINT):
+ case TUP(OADD, CTRUNE):
mpaddfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OSUB, CTINT):
+ case TUP(OSUB, CTRUNE):
mpsubfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OMUL, CTINT):
+ case TUP(OMUL, CTRUNE):
mpmulfixfix(v.u.xval, rv.u.xval);
break;
case TUP(ODIV, CTINT):
+ case TUP(ODIV, CTRUNE):
if(mpcmpfixc(rv.u.xval, 0) == 0) {
yyerror(\"division by zero\");
mpmovecfix(v.u.xval, 1);
@@ -576,6 +609,7 @@ evconst(Node *n)
mpdivfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OMOD, CTINT):
+ case TUP(OMOD, CTRUNE):
if(mpcmpfixc(rv.u.xval, 0) == 0) {
yyerror(\"division by zero\");
mpmovecfix(v.u.xval, 1);
@@ -585,21 +619,27 @@ evconst(Node *n)
break;
case TUP(OLSH, CTINT):
+ case TUP(OLSH, CTRUNE):
mplshfixfix(v.u.xval, rv.u.xval);
break;
case TUP(ORSH, CTINT):
+ case TUP(ORSH, CTRUNE):
mprshfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OOR, CTINT):
+ case TUP(OOR, CTRUNE):
mporfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OAND, CTINT):
+ case TUP(OAND, CTRUNE):
mpandfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OANDNOT, CTINT):
+ case TUP(OANDNOT, CTRUNE):
mpandnotfixfix(v.u.xval, rv.u.xval);
break;
case TUP(OXOR, CTINT):
+ case TUP(OXOR, CTRUNE):
mpxorfixfix(v.u.xval, rv.u.xval);
break;
@@ -649,26 +689,32 @@ evconst(Node *n)
goto setfalse;
case TUP(OEQ, CTINT):
+ case TUP(OEQ, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) == 0)
goto settrue;
goto setfalse;
case TUP(ONE, CTINT):
+ case TUP(ONE, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) != 0)
goto settrue;
goto setfalse;
case TUP(OLT, CTINT):
+ case TUP(OLT, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) < 0)
goto settrue;
goto setfalse;
case TUP(OLE, CTINT):
+ case TUP(OLE, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) <= 0)
goto settrue;
goto setfalse;
case TUP(OGE, CTINT):
+ case TUP(OGE, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) >= 0)
goto settrue;
goto setfalse;
case TUP(OGT, CTINT):
+ case TUP(OGT, CTRUNE):
if(mpcmpfixfix(v.u.xval, rv.u.xval) > 0)
goto settrue;
goto setfalse;
@@ -786,17 +832,21 @@ unary:
}
// fall through
case TUP(OCONV, CTINT):
+ case TUP(OCONV, CTRUNE):
case TUP(OCONV, CTFLT):
case TUP(OCONV, CTSTR):
convlit1(&nl, n->type, 1);
break;
case TUP(OPLUS, CTINT):
+ case TUP(OPLUS, CTRUNE):
break;
case TUP(OMINUS, CTINT):
+ case TUP(OMINUS, CTRUNE):
mpnegfix(v.u.xval);
break;
case TUP(OCOM, CTINT):
+ case TUP(OCOM, CTRUNE):
et = Txxx;
if(nl->type != T)
et = nl->type->etype;
@@ -889,6 +939,7 @@ nodlit(Val v)
n->type = idealbool;
break;
case CTINT:
+ case CTRUNE:
case CTFLT:
case CTCPLX:
n->type = types[TIDEAL];
@@ -1008,6 +1059,9 @@ defaultlit(Node **np, Type *t)
case CTINT:
n->type = types[TINT];
goto num;
+ case CTRUNE:
+ n->type = runetype;
+ goto num;
case CTFLT:
n->type = types[TFLOAT64];
goto num;
@@ -1072,6 +1126,13 @@ defaultlit2(Node **lp, Node **rp, int force)
convlit(rp, types[TFLOAT64]);
return;
}
+\n+\tif(isconst(l, CTRUNE) || isconst(r, CTRUNE)) {\n+\t\tconvlit(lp, runetype);\n+\t\tconvlit(rp, runetype);\n+\t\treturn;\n+\t}\n+\n convlit(lp, types[TINT]);
convlit(rp, types[TINT]);
}
@@ -1108,7 +1169,7 @@ cmpslit(Node *l, Node *r)
int
smallintconst(Node *n)
{
-\tif(n->op == OLITERAL && n->val.ctype == CTINT && n->type != T)\n+\tif(n->op == OLITERAL && isconst(n, CTINT) && n->type != T)\n \tswitch(simtype[n->type->etype]) {\n \tcase TINT8:\n \tcase TUINT8:\
@@ -1210,6 +1271,7 @@ convconst(Node *con, Type *t, Val *val)
default:
fatal(\"convconst ctype=%d %lT\", val->ctype, t);
case CTINT:
+ case CTRUNE:
i = mpgetfix(val->u.xval);
break;
case CTBOOL:
diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c
index 86711869d8..35acb5b84b 100644
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -354,12 +354,22 @@ static int
Vconv(Fmt *fp)
{
Val *v;
+\tvlong x;\n \n \tv = va_arg(fp->args, Val*);\n \n \tswitch(v->ctype) {\n \tcase CTINT:\n \t\treturn fmtprint(fp, \"%B\", v->u.xval);\n+\tcase CTRUNE:\n+\t\tx = mpgetfix(v->u.xval);\n+\t\tif(\' \' <= x && x < 0x80)\n+\t\t\treturn fmtprint(fp, \"\'%c\'\", (int)x);\n+\t\tif(0 <= x && x < (1<<16))\n+\t\t\treturn fmtprint(fp, \"\'\\\\u%04ux\'\", (int)x);\n+\t\tif(0 <= x && x <= Runemax)\n+\t\t\treturn fmtprint(fp, \"\'\\\\U%08llux\'\", x);\n+\t\treturn fmtprint(fp, \"(\'\\\\x00\' + %B)\", v->u.xval);\n \tcase CTFLT:\n \t\treturn fmtprint(fp, \"%F\", v->u.fval);\n \tcase CTCPLX: // ? 1234i -> (0p+0+617p+1)\
diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index 82d5039f0c..b8c40fcb9d 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -113,7 +113,7 @@ struct Val
{
short reg; // OREGISTER
short bval; // bool value CTBOOL
- Mpint* xval; // int CTINT
+ Mpint* xval; // int CTINT, rune CTRUNE
Mpflt* fval; // float CTFLT
Mpcplx* cval; // float CTCPLX
Strlit* sval; // string CTSTR
@@ -527,6 +527,7 @@ enum
CTxxx,
CTINT,
+ CTRUNE,
CTFLT,
CTCPLX,
CTSTR,
diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index 075117102b..f71658920a 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1974,6 +1974,7 @@ hidden_literal:
$$ = nodlit($2);
switch($$->val.ctype){
case CTINT:
+ case CTRUNE:
mpnegfix($$->val.u.xval);
break;
case CTFLT:
@@ -1994,6 +1995,11 @@ hidden_constant:
hidden_literal
|\t'(' hidden_literal '+' hidden_literal ')'
{
+ if($2->val.ctype == CTRUNE && $4->val.ctype == CTINT) {
+ $$ = $2;
+ mpaddfixfix($2->val.u.xval, $4->val.u.xval);
+ break;
+ }
$$ = nodcplxlit($2->val, $4->val);
}
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 3dbd6dda1a..1963bfbdaf 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -840,7 +840,7 @@ l0:
}
yylval.val.u.xval = mal(sizeof(*yylval.val.u.xval));
mpmovecfix(yylval.val.u.xval, v);
- yylval.val.ctype = CTINT;
+ yylval.val.ctype = CTRUNE;
DBG(\"lex: codepoint literal\\n\");
strcpy(litbuf, \"string literal\");
return LLITERAL;
diff --git a/src/cmd/gc/sinit.c b/src/cmd/gc/sinit.c
index 8d199e0240..3ef914a60e 100644
--- a/src/cmd/gc/sinit.c
+++ b/src/cmd/gc/sinit.c
@@ -1341,6 +1341,7 @@ iszero(Node *n)
return n->val.u.bval == 0;
case CTINT:
+ case CTRUNE:
return mpcmpfixc(n->val.u.xval, 0) == 0;
case CTFLT:
diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index 5584f78e2f..71e67f1449 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -709,6 +709,7 @@ aindex(Node *b, Type *t)
yyerror(\"array bound must be an integer expression\");
break;
case CTINT:
+ case CTRUNE:
bound = mpgetfix(b->val.u.xval);
if(bound < 0)
yyerror(\"array bound must be non negative\");
diff --git a/src/cmd/gc/swt.c b/src/cmd/gc/swt.c
index fb19129812..786fdf938d 100644
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -132,6 +132,7 @@ exprcmp(Case *c1, Case *c2)
n = mpcmpfltflt(n1->val.u.fval, n2->val.u.fval);
break;
case CTINT:
+ case CTRUNE:
n = mpcmpfixfix(n1->val.u.xval, n2->val.u.xval);
break;
case CTSTR:
@@ -380,6 +381,7 @@ mkcaselist(Node *sw, int arg)
switch(consttype(n->left)) {
case CTFLT:
case CTINT:
+ case CTRUNE:
case CTSTR:
c->type = Texprconst;
}
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 90db76960d..edf32fe2fa 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -257,6 +257,7 @@ reswitch:
l = typecheck(&n->left, Erv);
switch(consttype(l)) {
case CTINT:
+ case CTRUNE:
v = l->val;
break;
case CTFLT:
@@ -1849,6 +1850,7 @@ keydup(Node *n, Node *hash[], ulong nhash)
b = 23;
break;
case CTINT:
+ case CTRUNE:
b = mpgetfix(n->val.u.xval);
break;
case CTFLT:
diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index 075a801a30..93bcd423f4 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1538,6 +1538,9 @@ walkprint(Node *nn, NodeList **init, int defer)
n = l->n;
if(n->op == OLITERAL) {
switch(n->val.ctype) {
+ case CTRUNE:
+ defaultlit(&n, runetype);
+ break;
case CTINT:
defaultlit(&n, types[TINT64]);
break;
diff --git a/test/rune.go b/test/rune.go
new file mode 100644
index 0000000000..b2c73775d4
--- /dev/null
+++ b/test/rune.go
@@ -0,0 +1,43 @@
+// $G $D/$F.go
+\n+// Copyright 2011 The Go Authors. All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+package main\n+\n+var (\n+\tr0 = 'a'\n+\tr1 = 'a'+1\n+\tr2 = 1+'a'\n+\tr3 = 'a'*2\n+\tr4 = 'a'/2\n+\tr5 = 'a'<<1\n+\tr6 = 'b'<<2\n+\n+\tr = []rune{r0, r1, r2, r3, r4, r5, r6}\n+)\n+\n+var (\n+\tf0 = 1.2\n+\tf1 = 1.2/'a'\n+\n+\tf = []float64{f0, f1}\n+)\n+\n+var (\n+\ti0 = 1\n+\ti1 = 1<<'\x01'\n+\t\n+\ti = []int{i0, i1}\n+)\n+\n+const (\n+\tmaxRune = '\U0010FFFF'\n+)\n+\n+var (\n+\tb0 = maxRune < r0\n+\t\n+\tb = []bool{b0}\n+)\n```
## 変更の背景
Go言語では、文字リテラル(例: `'a'`)は`rune`型として扱われます。`rune`は`int32`のエイリアスであり、Unicodeコードポイントを表します。Goの定数システムは「型なし定数(untyped constants)」という概念を持っており、これはコンパイル時に特定の型に束縛されず、文脈に応じて適切な型に推論される特性を指します。
このコミット以前は、Goコンパイラ内部では文字リテラルが単なる整数定数(`CTINT`)として扱われていた可能性があります。しかし、文字定数にはその性質上、通常の整数定数とは異なる特定の型推論や演算規則が適用されるべき場面があります。例えば、文字定数と整数定数の混合演算や、文字定数を文字列に変換する際の挙動などです。
このコミットの目的は、Go言語の仕様に則り、文字定数をコンパイラ内部で`CTRUNE`という独立した定数型として明示的に扱うことで、より正確な型チェック、定数評価、そしてコード生成を実現することにあります。これにより、文字定数に関するコンパイラの挙動がより堅牢になり、予期せぬバグや非標準的な動作を防ぐことができます。特に、`rune`型が`int32`のエイリアスであるという事実と、型なし定数の柔軟性を両立させながら、文字定数特有のセマンティクスを適切に処理することが求められました。
## 前提知識の解説
### Go言語の定数
Go言語の定数には「型付き定数(typed constants)」と「型なし定数(untyped constants)」があります。
* **型付き定数**: `const x int = 10` のように明示的に型が指定された定数です。
* **型なし定数**: `const x = 10` のように型が指定されていない定数です。これらは、使用される文脈(代入先の変数型、演算の相手の型など)に応じて、適切なGoの組み込み型に自動的に推論されます。例えば、`10`は`int`、`10.0`は`float64`、`'a'`は`rune`(`int32`のエイリアス)として推論されます。
型なし定数の利点は、異なる数値型間での柔軟な演算を可能にすることです。例えば、型なしの整数定数`10`は、`int8`、`int16`、`int32`、`int64`、`float32`、`float64`など、様々な数値型の変数に代入したり、それらの型の値と演算したりできます。
### `rune`型
Go言語において、`rune`は組み込み型`int32`のエイリアスです。これはUnicodeコードポイントを表すために使用されます。文字リテラル(例: `'A'`, `'世'`)は、デフォルトで`rune`型の型なし定数として扱われます。
### Goコンパイラの内部構造(`gc`)
Goコンパイラ(`gc`)は、複数のフェーズを経てソースコードを実行可能なバイナリに変換します。
1. **字句解析 (Lexing)**: ソースコードをトークン(キーワード、識別子、リテラルなど)のストリームに変換します。この段階で文字リテラルが認識されます。
2. **構文解析 (Parsing)**: トークンストリームを抽象構文木(AST)に変換します。このASTは、プログラムの構造を階層的に表現します。
3. **型チェック (Type Checking)**: ASTを走査し、Go言語の型規則に従って各ノードの型を決定し、型の一貫性を検証します。型なし定数に対する型推論もここで行われます。
4. **定数評価 (Constant Evaluation)**: コンパイル時に評価可能な定数式を計算し、その結果をASTに埋め込みます。
5. **コード生成 (Code Generation)**: ASTをターゲットアーキテクチャの機械語に変換します。
このコミットは、主に字句解析、型チェック、定数評価の各フェーズにおける文字定数の扱いを改善しています。
## 技術的詳細
このコミットの核心は、Goコンパイラ内部で文字定数を表現するための新しい定数型`CTRUNE`の導入と、それに伴うコンパイラ各部の変更です。
1. **`CTRUNE`の導入**:
* `src/cmd/gc/go.h`において、`Val`構造体(定数の値を保持するユニオン)の`xval`フィールドのコメントが`// int CTINT, rune CTRUNE`と更新され、`enum`に`CTRUNE`が追加されました。これは、`xval`が整数定数だけでなく、rune定数も保持することを示しています。
* これにより、コンパイラは文字リテラルを単なる整数(`CTINT`)としてではなく、`CTRUNE`というより具体的なカテゴリで識別できるようになります。
2. **字句解析器(Lexer)の変更**:
* `src/cmd/gc/lex.c`の`l0`関数(文字リテラルを処理する部分)において、文字リテラルが読み込まれた際に、その`ctype`(定数型)が`CTINT`から`CTRUNE`に設定されるようになりました。
* これは、文字リテラルがソースコードから読み込まれた時点で、その「文字」としての性質がコンパイラ内部で認識されるようになったことを意味します。
3. **定数処理ロジックの変更(`src/cmd/gc/const.c`)**:
* **`isconst`関数の改善**: `isconst(Node *n, int ct)`関数は、与えられたノード`n`が指定された定数型`ct`であるかを判定します。このコミットでは、`ct`が`CTINT`の場合に、`CTRUNE`も許容するように変更されました。これは、バックエンド(コード生成部分)がrune定数を整数として扱えるようにするための互換性レイヤーとして機能します。
* **`convlit1`関数の拡張**: `convlit1`はリテラル定数の型変換を処理する関数です。この関数に`CTRUNE`が追加され、`CTRUNE`から`CTINT`、`CTFLT`、`CTCPLX`、`TSTRING`への変換が適切に処理されるようになりました。特に、`TSTRING`への変換では、`CTINT`だけでなく`CTRUNE`も文字列に変換できるようになりました。
* **`toint`関数の変更**: `CTRUNE`から`CTINT`への変換パスが追加されました。これは、rune定数が整数として扱われる必要がある場合に、明示的に`CTINT`に変換されることを保証します。
* **`evconst`関数の拡張**: `evconst`は定数式を評価する関数です。
* 算術演算子(`OADD`, `OSUB`, `OMUL`, `ODIV`, `OMOD`)、ビット演算子(`OLSH`, `ORSH`, `OOR`, `OAND`, `OANDNOT`, `OXOR`)、比較演算子(`OEQ`, `ONE`, `OLT`, `OLE`, `OGE`, `OGT`)の各ケースに`CTRUNE`が追加されました。これにより、rune定数を含む定数式が正しく評価されるようになります。
* 特に重要なのは、`evconst`内の混合型演算の処理です。`CTRUNE`と`CTINT`が混在する演算において、結果の型が適切に`CTRUNE`または`CTINT`に推論されるロジックが追加されました。例えば、`rune`と`int`の加算は`rune`になるが、シフト演算(`OLSH`, `ORSH`)では`int`になる、といったGoの型推論規則が反映されています。
4. **構文解析器(Parser)の変更(`src/cmd/gc/go.y`)**:
* `hidden_literal`ルールにおいて、文字リテラルの否定演算(`-`)が`CTINT`だけでなく`CTRUNE`に対しても適用されるようになりました。
* `hidden_constant`ルールにおいて、`rune`と`int`の加算(例: `'a' + 1`)が特別に処理され、結果が`rune`型になるように調整されました。
5. **フォーマット出力の改善(`src/cmd/gc/fmt.c`)**:
* `Vconv`関数に`CTRUNE`のケースが追加され、rune定数が人間が読みやすい形式で出力されるようになりました。具体的には、ASCII文字は`'c'`のように、Unicodeエスケープシーケンス(`\uXXXX`や`\UXXXXXXXX`)として、または必要に応じて生の16進数として表示されます。これはデバッグやエラーメッセージの可読性向上に寄与します。
6. **バックエンド(`5g`, `6g`, `8g`)の変更**:
* 各アーキテクチャ固有のコード生成部分(`src/cmd/5g/gsubr.c`, `src/cmd/6g/gsubr.c`, `src/cmd/8g/gsubr.c`など)でも、`CTINT`を期待する箇所で`CTRUNE`も許容するように変更されました。これは、rune定数が最終的には整数値として扱われるため、バックエンドでの処理を簡素化しつつ、フロントエンドでの型情報の精度を保つための調整です。
7. **新しいテストケースの追加(`test/rune.go`)**:
* `test/rune.go`という新しいテストファイルが追加され、文字定数に関する様々な演算(加算、乗算、除算、シフトなど)や型変換の挙動が検証されています。これにより、今回の変更がGo言語の仕様に沿って正しく機能していることが保証されます。
これらの変更により、Goコンパイラは文字定数をより正確に識別し、Go言語の型なし定数と`rune`型のセマンティクスを適切に実装できるようになりました。
## コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
* `src/cmd/gc/go.h`: 新しい定数型`CTRUNE`の定義と、`Val`構造体における`xval`フィールドの役割の拡張。
* `src/cmd/gc/lex.c`: 字句解析器が文字リテラルを`CTRUNE`として識別するよう変更。
* `src/cmd/gc/const.c`: 定数に関する主要なロジック(型変換、定数評価、`isconst`ヘルパー関数)が`CTRUNE`をサポートするように拡張。特に`evconst`関数における`CTRUNE`と`CTINT`の混合演算の型推論ロジック。
* `src/cmd/gc/fmt.c`: `CTRUNE`型の定数を人間が読みやすい形式で出力するためのフォーマットロジックの追加。
* `src/cmd/gc/go.y`: 構文解析器が文字リテラルの否定や混合型演算を正しく処理するためのルール追加。
* `src/cmd/5g/gsubr.c`, `src/cmd/6g/gsubr.c`, `src/cmd/8g/gsubr.c`: 各アーキテクチャのバックエンドで、`CTINT`を期待する箇所で`CTRUNE`も許容するように変更。
* `test/rune.go`: 新しい文字定数に関するテストケース。
## コアとなるコードの解説
### `src/cmd/gc/go.h`
```c
// go.h
enum
{
CTxxx,
CTINT,
CTRUNE, // 新しく追加された定数型
CTFLT,
CTCPLX,
CTSTR,
};
struct Val
{
short reg; // OREGISTER
short bval; // bool value CTBOOL
Mpint* xval; // int CTINT, rune CTRUNE // コメントが更新され、CTRUNEもxvalで表現されることを明示
Mpflt* fval; // float CTFLT
Mpcplx* cval; // float CTCPLX
Strlit* sval; // string CTSTR
};
CTRUNE
という新しい列挙型が追加され、Val
構造体のxval
フィールドがCTINT
とCTRUNE
の両方を保持することを示すコメントが追加されました。これは、コンパイラが文字定数を整数定数とは異なるカテゴリとして内部的に区別するための基盤となります。
src/cmd/gc/lex.c
// lex.c
// ...
yylval.val.u.xval = mal(sizeof(*yylval.val.u.xval));
mpmovecfix(yylval.val.u.xval, v);
yylval.val.ctype = CTRUNE; // 文字リテラルがCTRUNEとして識別されるように変更
DBG("lex: codepoint literal\n");
strcpy(litbuf, "string literal");
return LLITERAL;
// ...
字句解析器が文字リテラル(コードポイントリテラル)を読み込んだ際に、その内部表現のctype
をCTINT
からCTRUNE
に変更しています。これにより、コンパイラの初期段階で文字定数の「文字」としての性質が正確にタグ付けされます。
src/cmd/gc/const.c
// const.c
// ...
int
isconst(Node *n, int ct)
{
int t;
t = consttype(n);
// If the caller is asking for CTINT, allow CTRUNE too.
// Makes life easier for back ends.
return t == ct || (ct == CTINT && t == CTRUNE); // CTINTを要求された場合、CTRUNEも許容
}
// ...
Val
toint(Val v)
{
Mpint *i;
switch(v.ctype) {
case CTRUNE: // CTRUNEからCTINTへの変換パスを追加
v.ctype = CTINT;
break;
case CTFLT:
// ...
}
return v;
}
// ...
Val
evconst(Node *n)
{
// ...
// Rune and int turns into rune.
if(v.ctype == CTRUNE && rv.ctype == CTINT)
rv.ctype = CTRUNE;
if(v.ctype == CTINT && rv.ctype == CTRUNE) {
if(n->op == OLSH || n->op == ORSH) // シフト演算の場合
rv.ctype = CTINT;
else // それ以外の場合
v.ctype = CTRUNE;
}
// ...
// 各演算子(OADD, OSUB, OMUL, ODIV, OMOD, OLSH, ORSH, OOR, OAND, OANDNOT, OXOR, OEQ, ONE, OLT, OLE, OGE, OGT)
// のCTINTケースにCTRUNEケースを追加
case TUP(OADD, CTINT):
case TUP(OADD, CTRUNE): // CTRUNEの加算をサポート
mpaddfixfix(v.u.xval, rv.u.xval);
break;
// ...
}
isconst
関数は、バックエンドがCTINT
を期待する場面でCTRUNE
も受け入れられるように変更されました。toint
関数にはCTRUNE
からCTINT
への明示的な変換ロジックが追加され、rune定数が整数として扱われる際の挙動を保証します。
evconst
関数は、定数式の評価においてCTRUNE
を完全にサポートするようになりました。特に、CTRUNE
とCTINT
が混在する演算における型推論ロジックが追加され、Go言語の仕様(例: 'a' + 1
はrune
、1 << 'a'
はint
)に厳密に従うようになりました。
src/cmd/gc/fmt.c
// fmt.c
// ...
static int
Vconv(Fmt *fp)
{
Val *v;
vlong x;
v = va_arg(fp->args, Val*);
switch(v->ctype) {
case CTINT:
return fmtprint(fp, "%B", v->u.xval);
case CTRUNE: // CTRUNEのフォーマット出力ロジックを追加
x = mpgetfix(v->u.xval);
if(' ' <= x && x < 0x80) // ASCII文字の場合
return fmtprint(fp, "'%c'", (int)x);
if(0 <= x && x < (1<<16)) // U+0000からU+FFFFの範囲の場合
return fmtprint(fp, "'\\u%04ux'", (int)x);
if(0 <= x && x <= Runemax) // Runemaxまでの範囲の場合
return fmtprint(fp, "'\\U%08llux'", x);
return fmtprint(fp, "('\\x00' + %B)", v->u.xval); // それ以外の場合
case CTFLT:
// ...
}
// ...
}
Vconv
関数は、CTRUNE
型の定数を表示する際に、その値に応じて適切な文字リテラル形式(例: 'a'
)、Unicodeエスケープシーケンス(\uXXXX
、\UXXXXXXXX
)、または生の数値表現で出力するように拡張されました。これにより、コンパイラのデバッグ出力やエラーメッセージがより分かりやすくなります。
test/rune.go
// test/rune.go
package main
var (
r0 = 'a'
r1 = 'a'+1 // rune + int
r2 = 1+'a' // int + rune
r3 = 'a'*2
r4 = 'a'/2
r5 = 'a'<<1 // rune << int
r6 = 'b'<<2
)
var (
r = []rune{r0, r1, r2, r3, r4, r5, r6}
)
var (
f0 = 1.2
f1 = 1.2/'a' // float + rune
)
var (
f = []float64{f0, f1}
)
var (
i0 = 1
i1 = 1<<'\x01' // int << rune
)
var (
i = []int{i0, i1}
)
const (
maxRune = '\U0010FFFF'
)
var (
b0 = maxRune < r0
)
var (
b = []bool{b0}
)
この新しいテストファイルは、文字定数と他の定数型(整数、浮動小数点数)との様々な組み合わせでの演算や比較を網羅しています。これにより、今回のコンパイラ変更がGo言語の仕様に沿って正しく機能していることを検証します。特に、'a'+1
のような混合型演算の結果が期待通りrune
型になることや、1<<'\x01'
のようなシフト演算の結果がint
型になることなどがテストされています。
関連リンク
- Go言語仕様 - 定数: https://go.dev/ref/spec#Constants
- Go言語仕様 - Runeリテラル: https://go.dev/ref/spec#Rune_literals
- Go言語仕様 - 型なし定数: https://go.dev/ref/spec#Untyped_constants
- Go言語の
rune
型に関する公式ブログ記事 (Go Blog - The Go Programming Language and Unicode): https://go.dev/blog/strings
参考にした情報源リンク
- 上記のGo言語公式ドキュメントおよびブログ記事
- Goコンパイラのソースコード(特に
src/cmd/gc
ディレクトリ内のファイル) - Go言語の型システムと定数に関する一般的な知識