1
0
mirror of https://github.com/golang/go synced 2024-11-20 10:44:41 -07:00
go/src/cmd/gc/swt.c
Rémy Oudompheng f4f1ba2b1e cmd/gc: accept switches on comparable arrays.
The compiler is incorrectly rejecting switches on arrays of
comparable types. It also doesn't catch incomparable structs
when typechecking the switch, leading to unreadable errors
during typechecking of the generated code.

Fixes #3894.

R=rsc
CC=gobot, golang-dev, r, remy
https://golang.org/cl/6442074
2012-08-03 21:47:26 +02:00

928 lines
18 KiB
C

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <u.h>
#include <libc.h>
#include "go.h"
enum
{
Snorm = 0,
Strue,
Sfalse,
Stype,
Tdefault, // default case
Texprconst, // normal constant case
Texprvar, // normal variable case
Ttypenil, // case nil
Ttypeconst, // type hashes
Ttypevar, // interface type
Ncase = 4, // count needed to split
};
typedef struct Case Case;
struct Case
{
Node* node; // points at case statement
uint32 hash; // hash of a type switch
uint8 type; // type of case
uint8 diag; // suppress multiple diagnostics
uint16 ordinal; // position in switch
Case* link; // linked list to link
};
#define C ((Case*)nil)
void
dumpcase(Case *c0)
{
Case *c;
for(c=c0; c!=C; c=c->link) {
switch(c->type) {
case Tdefault:
print("case-default\n");
print(" ord=%d\n", c->ordinal);
break;
case Texprconst:
print("case-exprconst\n");
print(" ord=%d\n", c->ordinal);
break;
case Texprvar:
print("case-exprvar\n");
print(" ord=%d\n", c->ordinal);
print(" op=%O\n", c->node->left->op);
break;
case Ttypenil:
print("case-typenil\n");
print(" ord=%d\n", c->ordinal);
break;
case Ttypeconst:
print("case-typeconst\n");
print(" ord=%d\n", c->ordinal);
print(" hash=%ux\n", c->hash);
break;
case Ttypevar:
print("case-typevar\n");
print(" ord=%d\n", c->ordinal);
break;
default:
print("case-???\n");
print(" ord=%d\n", c->ordinal);
print(" op=%O\n", c->node->left->op);
print(" hash=%ux\n", c->hash);
break;
}
}
print("\n");
}
static int
ordlcmp(Case *c1, Case *c2)
{
// sort default first
if(c1->type == Tdefault)
return -1;
if(c2->type == Tdefault)
return +1;
// sort nil second
if(c1->type == Ttypenil)
return -1;
if(c2->type == Ttypenil)
return +1;
// sort by ordinal
if(c1->ordinal > c2->ordinal)
return +1;
if(c1->ordinal < c2->ordinal)
return -1;
return 0;
}
static int
exprcmp(Case *c1, Case *c2)
{
int ct, n;
Node *n1, *n2;
// sort non-constants last
if(c1->type != Texprconst)
return +1;
if(c2->type != Texprconst)
return -1;
n1 = c1->node->left;
n2 = c2->node->left;
ct = n1->val.ctype;
if(ct != n2->val.ctype) {
// invalid program, but return a sort
// order so that we can give a better
// error later.
return ct - n2->val.ctype;
}
// sort by constant value
n = 0;
switch(ct) {
case CTFLT:
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:
n = cmpslit(n1, n2);
break;
}
return n;
}
static int
typecmp(Case *c1, Case *c2)
{
// sort non-constants last
if(c1->type != Ttypeconst)
return +1;
if(c2->type != Ttypeconst)
return -1;
// sort by hash code
if(c1->hash > c2->hash)
return +1;
if(c1->hash < c2->hash)
return -1;
// sort by ordinal so duplicate error
// happens on later case.
if(c1->ordinal > c2->ordinal)
return +1;
if(c1->ordinal < c2->ordinal)
return -1;
return 0;
}
static Case*
csort(Case *l, int(*f)(Case*, Case*))
{
Case *l1, *l2, *le;
if(l == C || l->link == C)
return l;
l1 = l;
l2 = l;
for(;;) {
l2 = l2->link;
if(l2 == C)
break;
l2 = l2->link;
if(l2 == C)
break;
l1 = l1->link;
}
l2 = l1->link;
l1->link = C;
l1 = csort(l, f);
l2 = csort(l2, f);
/* set up lead element */
if((*f)(l1, l2) < 0) {
l = l1;
l1 = l1->link;
} else {
l = l2;
l2 = l2->link;
}
le = l;
for(;;) {
if(l1 == C) {
while(l2) {
le->link = l2;
le = l2;
l2 = l2->link;
}
le->link = C;
break;
}
if(l2 == C) {
while(l1) {
le->link = l1;
le = l1;
l1 = l1->link;
}
break;
}
if((*f)(l1, l2) < 0) {
le->link = l1;
le = l1;
l1 = l1->link;
} else {
le->link = l2;
le = l2;
l2 = l2->link;
}
}
le->link = C;
return l;
}
static Node*
newlabel(void)
{
static int label;
label++;
snprint(namebuf, sizeof(namebuf), "%.6d", label);
return newname(lookup(namebuf));
}
/*
* build separate list of statements and cases
* make labels between cases and statements
* deal with fallthrough, break, unreachable statements
*/
static void
casebody(Node *sw, Node *typeswvar)
{
Node *n, *c, *last;
Node *def;
NodeList *cas, *stat, *l, *lc;
Node *go, *br;
int32 lno, needvar;
lno = setlineno(sw);
if(sw->list == nil)
return;
cas = nil; // cases
stat = nil; // statements
def = N; // defaults
br = nod(OBREAK, N, N);
for(l=sw->list; l; l=l->next) {
n = l->n;
lno = setlineno(n);
if(n->op != OXCASE)
fatal("casebody %O", n->op);
n->op = OCASE;
needvar = count(n->list) != 1 || n->list->n->op == OLITERAL;
go = nod(OGOTO, newlabel(), N);
if(n->list == nil) {
if(def != N)
yyerror("more than one default case");
// reuse original default case
n->right = go;
def = n;
}
if(n->list != nil && n->list->next == nil) {
// one case - reuse OCASE node.
c = n->list->n;
n->left = c;
n->right = go;
n->list = nil;
cas = list(cas, n);
} else {
// expand multi-valued cases
for(lc=n->list; lc; lc=lc->next) {
c = lc->n;
cas = list(cas, nod(OCASE, c, go));
}
}
stat = list(stat, nod(OLABEL, go->left, N));
if(typeswvar && needvar && n->nname != N) {
NodeList *l;
l = list1(nod(ODCL, n->nname, N));
l = list(l, nod(OAS, n->nname, typeswvar));
typechecklist(l, Etop);
stat = concat(stat, l);
}
stat = concat(stat, n->nbody);
// botch - shouldnt fall thru declaration
last = stat->end->n;
if(last->op == OXFALL) {
if(typeswvar) {
setlineno(last);
yyerror("cannot fallthrough in type switch");
}
last->op = OFALL;
} else
stat = list(stat, br);
}
stat = list(stat, br);
if(def)
cas = list(cas, def);
sw->list = cas;
sw->nbody = stat;
lineno = lno;
}
static Case*
mkcaselist(Node *sw, int arg)
{
Node *n;
Case *c, *c1, *c2;
NodeList *l;
int ord;
c = C;
ord = 0;
for(l=sw->list; l; l=l->next) {
n = l->n;
c1 = mal(sizeof(*c1));
c1->link = c;
c = c1;
ord++;
c->ordinal = ord;
c->node = n;
if(n->left == N) {
c->type = Tdefault;
continue;
}
switch(arg) {
case Stype:
c->hash = 0;
if(n->left->op == OLITERAL) {
c->type = Ttypenil;
continue;
}
if(istype(n->left->type, TINTER)) {
c->type = Ttypevar;
continue;
}
c->hash = typehash(n->left->type);
c->type = Ttypeconst;
continue;
case Snorm:
case Strue:
case Sfalse:
c->type = Texprvar;
switch(consttype(n->left)) {
case CTFLT:
case CTINT:
case CTRUNE:
case CTSTR:
c->type = Texprconst;
}
continue;
}
}
if(c == C)
return C;
// sort by value and diagnose duplicate cases
switch(arg) {
case Stype:
c = csort(c, typecmp);
for(c1=c; c1!=C; c1=c1->link) {
for(c2=c1->link; c2!=C && c2->hash==c1->hash; c2=c2->link) {
if(c1->type == Ttypenil || c1->type == Tdefault)
break;
if(c2->type == Ttypenil || c2->type == Tdefault)
break;
if(!eqtype(c1->node->left->type, c2->node->left->type))
continue;
yyerrorl(c2->node->lineno, "duplicate case in switch\n\tprevious case at %L", c1->node->lineno);
}
}
break;
case Snorm:
case Strue:
case Sfalse:
c = csort(c, exprcmp);
for(c1=c; c1->link!=C; c1=c1->link) {
if(exprcmp(c1, c1->link) != 0)
continue;
setlineno(c1->link->node);
yyerror("duplicate case in switch\n\tprevious case at %L", c1->node->lineno);
}
break;
}
// put list back in processing order
c = csort(c, ordlcmp);
return c;
}
static Node* exprname;
static Node*
exprbsw(Case *c0, int ncase, int arg)
{
NodeList *cas;
Node *a, *n;
Case *c;
int i, half, lno;
cas = nil;
if(ncase < Ncase) {
for(i=0; i<ncase; i++) {
n = c0->node;
lno = setlineno(n);
switch(arg) {
case Strue:
a = nod(OIF, N, N);
a->ntest = n->left; // if val
a->nbody = list1(n->right); // then goto l
break;
case Sfalse:
a = nod(OIF, N, N);
a->ntest = nod(ONOT, n->left, N); // if !val
typecheck(&a->ntest, Erv);
a->nbody = list1(n->right); // then goto l
break;
default:
a = nod(OIF, N, N);
a->ntest = nod(OEQ, exprname, n->left); // if name == val
typecheck(&a->ntest, Erv);
a->nbody = list1(n->right); // then goto l
break;
}
cas = list(cas, a);
c0 = c0->link;
lineno = lno;
}
return liststmt(cas);
}
// find the middle and recur
c = c0;
half = ncase>>1;
for(i=1; i<half; i++)
c = c->link;
a = nod(OIF, N, N);
a->ntest = nod(OLE, exprname, c->node->left);
typecheck(&a->ntest, Erv);
a->nbody = list1(exprbsw(c0, half, arg));
a->nelse = list1(exprbsw(c->link, ncase-half, arg));
return a;
}
/*
* normal (expression) switch.
* rebulid case statements into if .. goto
*/
static void
exprswitch(Node *sw)
{
Node *def;
NodeList *cas;
Node *a;
Case *c0, *c, *c1;
Type *t;
int arg, ncase;
casebody(sw, N);
arg = Snorm;
if(isconst(sw->ntest, CTBOOL)) {
arg = Strue;
if(sw->ntest->val.u.bval == 0)
arg = Sfalse;
}
walkexpr(&sw->ntest, &sw->ninit);
t = sw->type;
if(t == T)
return;
/*
* convert the switch into OIF statements
*/
exprname = N;
cas = nil;
if(arg != Strue && arg != Sfalse) {
exprname = temp(sw->ntest->type);
cas = list1(nod(OAS, exprname, sw->ntest));
typechecklist(cas, Etop);
}
c0 = mkcaselist(sw, arg);
if(c0 != C && c0->type == Tdefault) {
def = c0->node->right;
c0 = c0->link;
} else {
def = nod(OBREAK, N, N);
}
loop:
if(c0 == C) {
cas = list(cas, def);
sw->nbody = concat(cas, sw->nbody);
sw->list = nil;
walkstmtlist(sw->nbody);
return;
}
// deal with the variables one-at-a-time
if(!okforcmp[t->etype] || c0->type != Texprconst) {
a = exprbsw(c0, 1, arg);
cas = list(cas, a);
c0 = c0->link;
goto loop;
}
// do binary search on run of constants
ncase = 1;
for(c=c0; c->link!=C; c=c->link) {
if(c->link->type != Texprconst)
break;
ncase++;
}
// break the chain at the count
c1 = c->link;
c->link = C;
// sort and compile constants
c0 = csort(c0, exprcmp);
a = exprbsw(c0, ncase, arg);
cas = list(cas, a);
c0 = c1;
goto loop;
}
static Node* hashname;
static Node* facename;
static Node* boolname;
static Node*
typeone(Node *t)
{
NodeList *init;
Node *a, *b, *var;
var = t->nname;
init = nil;
if(var == N) {
typecheck(&nblank, Erv | Easgn);
var = nblank;
} else
init = list1(nod(ODCL, var, N));
a = nod(OAS2, N, N);
a->list = list(list1(var), boolname); // var,bool =
b = nod(ODOTTYPE, facename, N);
b->type = t->left->type; // interface.(type)
a->rlist = list1(b);
typecheck(&a, Etop);
init = list(init, a);
b = nod(OIF, N, N);
b->ntest = boolname;
b->nbody = list1(t->right); // if bool { goto l }
a = liststmt(list(init, b));
return a;
}
static Node*
typebsw(Case *c0, int ncase)
{
NodeList *cas;
Node *a, *n;
Case *c;
int i, half;
cas = nil;
if(ncase < Ncase) {
for(i=0; i<ncase; i++) {
n = c0->node;
if(c0->type != Ttypeconst)
fatal("typebsw");
a = nod(OIF, N, N);
a->ntest = nod(OEQ, hashname, nodintconst(c0->hash));
typecheck(&a->ntest, Erv);
a->nbody = list1(n->right);
cas = list(cas, a);
c0 = c0->link;
}
return liststmt(cas);
}
// find the middle and recur
c = c0;
half = ncase>>1;
for(i=1; i<half; i++)
c = c->link;
a = nod(OIF, N, N);
a->ntest = nod(OLE, hashname, nodintconst(c->hash));
typecheck(&a->ntest, Erv);
a->nbody = list1(typebsw(c0, half));
a->nelse = list1(typebsw(c->link, ncase-half));
return a;
}
/*
* convert switch of the form
* switch v := i.(type) { case t1: ..; case t2: ..; }
* into if statements
*/
static void
typeswitch(Node *sw)
{
Node *def;
NodeList *cas, *hash;
Node *a, *n;
Case *c, *c0, *c1;
int ncase;
Type *t;
Val v;
if(sw->ntest == nil)
return;
if(sw->ntest->right == nil) {
setlineno(sw);
yyerror("type switch must have an assignment");
return;
}
walkexpr(&sw->ntest->right, &sw->ninit);
if(!istype(sw->ntest->right->type, TINTER)) {
yyerror("type switch must be on an interface");
return;
}
cas = nil;
/*
* predeclare temporary variables
* and the boolean var
*/
facename = temp(sw->ntest->right->type);
a = nod(OAS, facename, sw->ntest->right);
typecheck(&a, Etop);
cas = list(cas, a);
casebody(sw, facename);
boolname = temp(types[TBOOL]);
typecheck(&boolname, Erv);
hashname = temp(types[TUINT32]);
typecheck(&hashname, Erv);
t = sw->ntest->right->type;
if(isnilinter(t))
a = syslook("efacethash", 1);
else
a = syslook("ifacethash", 1);
argtype(a, t);
a = nod(OCALL, a, N);
a->list = list1(facename);
a = nod(OAS, hashname, a);
typecheck(&a, Etop);
cas = list(cas, a);
c0 = mkcaselist(sw, Stype);
if(c0 != C && c0->type == Tdefault) {
def = c0->node->right;
c0 = c0->link;
} else {
def = nod(OBREAK, N, N);
}
/*
* insert if statement into each case block
*/
for(c=c0; c!=C; c=c->link) {
n = c->node;
switch(c->type) {
case Ttypenil:
v.ctype = CTNIL;
a = nod(OIF, N, N);
a->ntest = nod(OEQ, facename, nodlit(v));
typecheck(&a->ntest, Erv);
a->nbody = list1(n->right); // if i==nil { goto l }
n->right = a;
break;
case Ttypevar:
case Ttypeconst:
n->right = typeone(n);
break;
}
}
/*
* generate list of if statements, binary search for constant sequences
*/
while(c0 != C) {
if(c0->type != Ttypeconst) {
n = c0->node;
cas = list(cas, n->right);
c0=c0->link;
continue;
}
// identify run of constants
c1 = c = c0;
while(c->link!=C && c->link->type==Ttypeconst)
c = c->link;
c0 = c->link;
c->link = nil;
// sort by hash
c1 = csort(c1, typecmp);
// for debugging: linear search
if(0) {
for(c=c1; c!=C; c=c->link) {
n = c->node;
cas = list(cas, n->right);
}
continue;
}
// combine adjacent cases with the same hash
ncase = 0;
for(c=c1; c!=C; c=c->link) {
ncase++;
hash = list1(c->node->right);
while(c->link != C && c->link->hash == c->hash) {
hash = list(hash, c->link->node->right);
c->link = c->link->link;
}
c->node->right = liststmt(hash);
}
// binary search among cases to narrow by hash
cas = list(cas, typebsw(c1, ncase));
}
if(nerrors == 0) {
cas = list(cas, def);
sw->nbody = concat(cas, sw->nbody);
sw->list = nil;
walkstmtlist(sw->nbody);
}
}
void
walkswitch(Node *sw)
{
/*
* reorder the body into (OLIST, cases, statements)
* cases have OGOTO into statements.
* both have inserted OBREAK statements
*/
if(sw->ntest == N) {
sw->ntest = nodbool(1);
typecheck(&sw->ntest, Erv);
}
if(sw->ntest->op == OTYPESW) {
typeswitch(sw);
//dump("sw", sw);
return;
}
exprswitch(sw);
}
/*
* type check switch statement
*/
void
typecheckswitch(Node *n)
{
int top, lno, ptr;
char *nilonly;
Type *t, *badtype, *missing, *have;
NodeList *l, *ll;
Node *ncase, *nvar;
Node *def;
lno = lineno;
typechecklist(n->ninit, Etop);
nilonly = nil;
if(n->ntest != N && n->ntest->op == OTYPESW) {
// type switch
top = Etype;
typecheck(&n->ntest->right, Erv);
t = n->ntest->right->type;
if(t != T && t->etype != TINTER)
yyerror("cannot type switch on non-interface value %lN", n->ntest->right);
} else {
// value switch
top = Erv;
if(n->ntest) {
typecheck(&n->ntest, Erv);
defaultlit(&n->ntest, T);
t = n->ntest->type;
} else
t = types[TBOOL];
if(t) {
if(!okforeq[t->etype])
yyerror("cannot switch on %lN", n->ntest);
else if(t->etype == TARRAY && !isfixedarray(t))
nilonly = "slice";
else if(t->etype == TARRAY && isfixedarray(t) && algtype1(t, nil) == ANOEQ)
yyerror("cannot switch on %lN", n->ntest);
else if(t->etype == TSTRUCT && algtype1(t, &badtype) == ANOEQ)
yyerror("cannot switch on %lN (struct containing %T cannot be compared)", n->ntest, badtype);
else if(t->etype == TFUNC)
nilonly = "func";
else if(t->etype == TMAP)
nilonly = "map";
}
}
n->type = t;
def = N;
for(l=n->list; l; l=l->next) {
ncase = l->n;
setlineno(n);
if(ncase->list == nil) {
// default
if(def != N)
yyerror("multiple defaults in switch (first at %L)", def->lineno);
else
def = ncase;
} else {
for(ll=ncase->list; ll; ll=ll->next) {
setlineno(ll->n);
typecheck(&ll->n, Erv | Etype);
if(ll->n->type == T || t == T)
continue;
setlineno(ncase);
switch(top) {
case Erv: // expression switch
defaultlit(&ll->n, t);
if(ll->n->op == OTYPE)
yyerror("type %T is not an expression", ll->n->type);
else if(ll->n->type != T && !assignop(ll->n->type, t, nil) && !assignop(t, ll->n->type, nil)) {
if(n->ntest)
yyerror("invalid case %N in switch on %N (mismatched types %T and %T)", ll->n, n->ntest, ll->n->type, t);
else
yyerror("invalid case %N in switch (mismatched types %T and bool)", ll->n, ll->n->type);
} else if(nilonly && !isconst(ll->n, CTNIL)) {
yyerror("invalid case %N in switch (can only compare %s %N to nil)", ll->n, nilonly, n->ntest);
}
break;
case Etype: // type switch
if(ll->n->op == OLITERAL && istype(ll->n->type, TNIL)) {
;
} else if(ll->n->op != OTYPE && ll->n->type != T) { // should this be ||?
yyerror("%lN is not a type", ll->n);
// reset to original type
ll->n = n->ntest->right;
} else if(ll->n->type->etype != TINTER && t->etype == TINTER && !implements(ll->n->type, t, &missing, &have, &ptr)) {
if(have && !missing->broke && !have->broke)
yyerror("impossible type switch case: %lN cannot have dynamic type %T"
" (wrong type for %S method)\n\thave %S%hT\n\twant %S%hT",
n->ntest->right, ll->n->type, missing->sym, have->sym, have->type,
missing->sym, missing->type);
else if(!missing->broke)
yyerror("impossible type switch case: %lN cannot have dynamic type %T"
" (missing %S method)", n->ntest->right, ll->n->type, missing->sym);
}
break;
}
}
}
if(top == Etype && n->type != T) {
ll = ncase->list;
nvar = ncase->nname;
if(nvar != N) {
if(ll && ll->next == nil && ll->n->type != T && !istype(ll->n->type, TNIL)) {
// single entry type switch
nvar->ntype = typenod(ll->n->type);
} else {
// multiple entry type switch or default
nvar->ntype = typenod(n->type);
}
}
}
typechecklist(ncase->nbody, Etop);
}
lineno = lno;
}