mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Focus on "isfoo" funcs that take a *Node, and conver them to isFoo methods instead. This makes for more idiomatic Go code, and also more readable func names. Found candidates with grep, and applied most changes with sed. The funcs chosen were isgoconst, isnil, and isblank. All had the same signature, func(*Node) bool. While at it, camelCase the isliteral and iszero function names. Don't move these to methods, as they are only used in the backend part of gc, which might one day be split into a separate package. Passes toolstash -cmp on std cmd. Change-Id: I4df081b12d36c46c253167c8841c5a841f1c5a16 Reviewed-on: https://go-review.googlesource.com/105555 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
959 lines
26 KiB
Go
959 lines
26 KiB
Go
// 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.
|
|
|
|
package gc
|
|
|
|
import (
|
|
"cmd/compile/internal/types"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
const (
|
|
// expression switch
|
|
switchKindExpr = iota // switch a {...} or switch 5 {...}
|
|
switchKindTrue // switch true {...} or switch {...}
|
|
switchKindFalse // switch false {...}
|
|
)
|
|
|
|
const (
|
|
binarySearchMin = 4 // minimum number of cases for binary search
|
|
integerRangeMin = 2 // minimum size of integer ranges
|
|
)
|
|
|
|
// An exprSwitch walks an expression switch.
|
|
type exprSwitch struct {
|
|
exprname *Node // node for the expression being switched on
|
|
kind int // kind of switch statement (switchKind*)
|
|
}
|
|
|
|
// A typeSwitch walks a type switch.
|
|
type typeSwitch struct {
|
|
hashname *Node // node for the hash of the type of the variable being switched on
|
|
facename *Node // node for the concrete type of the variable being switched on
|
|
okname *Node // boolean node used for comma-ok type assertions
|
|
}
|
|
|
|
// A caseClause is a single case clause in a switch statement.
|
|
type caseClause struct {
|
|
node *Node // points at case statement
|
|
ordinal int // position in switch
|
|
hash uint32 // hash of a type switch
|
|
// isconst indicates whether this case clause is a constant,
|
|
// for the purposes of the switch code generation.
|
|
// For expression switches, that's generally literals (case 5:, not case x:).
|
|
// For type switches, that's concrete types (case time.Time:), not interfaces (case io.Reader:).
|
|
isconst bool
|
|
}
|
|
|
|
// caseClauses are all the case clauses in a switch statement.
|
|
type caseClauses struct {
|
|
list []caseClause // general cases
|
|
defjmp *Node // OGOTO for default case or OBREAK if no default case present
|
|
niljmp *Node // OGOTO for nil type case in a type switch
|
|
}
|
|
|
|
// typecheckswitch typechecks a switch statement.
|
|
func typecheckswitch(n *Node) {
|
|
typecheckslice(n.Ninit.Slice(), Etop)
|
|
|
|
var nilonly string
|
|
var top int
|
|
var t *types.Type
|
|
|
|
if n.Left != nil && n.Left.Op == OTYPESW {
|
|
// type switch
|
|
top = Etype
|
|
n.Left.Right = typecheck(n.Left.Right, Erv)
|
|
t = n.Left.Right.Type
|
|
if t != nil && !t.IsInterface() {
|
|
yyerrorl(n.Pos, "cannot type switch on non-interface value %L", n.Left.Right)
|
|
}
|
|
if v := n.Left.Left; v != nil && !v.isBlank() && n.List.Len() == 0 {
|
|
// We don't actually declare the type switch's guarded
|
|
// declaration itself. So if there are no cases, we
|
|
// won't notice that it went unused.
|
|
yyerrorl(v.Pos, "%v declared and not used", v.Sym)
|
|
}
|
|
} else {
|
|
// expression switch
|
|
top = Erv
|
|
if n.Left != nil {
|
|
n.Left = typecheck(n.Left, Erv)
|
|
n.Left = defaultlit(n.Left, nil)
|
|
t = n.Left.Type
|
|
} else {
|
|
t = types.Types[TBOOL]
|
|
}
|
|
if t != nil {
|
|
switch {
|
|
case !okforeq[t.Etype]:
|
|
yyerrorl(n.Pos, "cannot switch on %L", n.Left)
|
|
case t.IsSlice():
|
|
nilonly = "slice"
|
|
case t.IsArray() && !IsComparable(t):
|
|
yyerrorl(n.Pos, "cannot switch on %L", n.Left)
|
|
case t.IsStruct():
|
|
if f := IncomparableField(t); f != nil {
|
|
yyerrorl(n.Pos, "cannot switch on %L (struct containing %v cannot be compared)", n.Left, f.Type)
|
|
}
|
|
case t.Etype == TFUNC:
|
|
nilonly = "func"
|
|
case t.IsMap():
|
|
nilonly = "map"
|
|
}
|
|
}
|
|
}
|
|
|
|
n.Type = t
|
|
|
|
var def, niltype *Node
|
|
for _, ncase := range n.List.Slice() {
|
|
if ncase.List.Len() == 0 {
|
|
// default
|
|
if def != nil {
|
|
setlineno(ncase)
|
|
yyerrorl(ncase.Pos, "multiple defaults in switch (first at %v)", def.Line())
|
|
} else {
|
|
def = ncase
|
|
}
|
|
} else {
|
|
ls := ncase.List.Slice()
|
|
for i1, n1 := range ls {
|
|
setlineno(n1)
|
|
ls[i1] = typecheck(ls[i1], Erv|Etype)
|
|
n1 = ls[i1]
|
|
if n1.Type == nil || t == nil {
|
|
continue
|
|
}
|
|
|
|
setlineno(ncase)
|
|
switch top {
|
|
// expression switch
|
|
case Erv:
|
|
ls[i1] = defaultlit(ls[i1], t)
|
|
n1 = ls[i1]
|
|
switch {
|
|
case n1.Op == OTYPE:
|
|
yyerrorl(ncase.Pos, "type %v is not an expression", n1.Type)
|
|
case n1.Type != nil && assignop(n1.Type, t, nil) == 0 && assignop(t, n1.Type, nil) == 0:
|
|
if n.Left != nil {
|
|
yyerrorl(ncase.Pos, "invalid case %v in switch on %v (mismatched types %v and %v)", n1, n.Left, n1.Type, t)
|
|
} else {
|
|
yyerrorl(ncase.Pos, "invalid case %v in switch (mismatched types %v and bool)", n1, n1.Type)
|
|
}
|
|
case nilonly != "" && !n1.isNil():
|
|
yyerrorl(ncase.Pos, "invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left)
|
|
case t.IsInterface() && !n1.Type.IsInterface() && !IsComparable(n1.Type):
|
|
yyerrorl(ncase.Pos, "invalid case %L in switch (incomparable type)", n1)
|
|
}
|
|
|
|
// type switch
|
|
case Etype:
|
|
var missing, have *types.Field
|
|
var ptr int
|
|
switch {
|
|
case n1.Op == OLITERAL && n1.Type.IsKind(TNIL):
|
|
// case nil:
|
|
if niltype != nil {
|
|
yyerrorl(ncase.Pos, "multiple nil cases in type switch (first at %v)", niltype.Line())
|
|
} else {
|
|
niltype = ncase
|
|
}
|
|
case n1.Op != OTYPE && n1.Type != nil: // should this be ||?
|
|
yyerrorl(ncase.Pos, "%L is not a type", n1)
|
|
// reset to original type
|
|
n1 = n.Left.Right
|
|
ls[i1] = n1
|
|
case !n1.Type.IsInterface() && t.IsInterface() && !implements(n1.Type, t, &missing, &have, &ptr):
|
|
if have != nil && !missing.Broke() && !have.Broke() {
|
|
yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+
|
|
" (wrong type for %v method)\n\thave %v%S\n\twant %v%S", n.Left.Right, n1.Type, missing.Sym, have.Sym, have.Type, missing.Sym, missing.Type)
|
|
} else if !missing.Broke() {
|
|
if ptr != 0 {
|
|
yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+
|
|
" (%v method has pointer receiver)", n.Left.Right, n1.Type, missing.Sym)
|
|
} else {
|
|
yyerrorl(ncase.Pos, "impossible type switch case: %L cannot have dynamic type %v"+
|
|
" (missing %v method)", n.Left.Right, n1.Type, missing.Sym)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if n.Type == nil || n.Type.IsUntyped() {
|
|
// if the value we're switching on has no type or is untyped,
|
|
// we've already printed an error and don't need to continue
|
|
// typechecking the body
|
|
return
|
|
}
|
|
|
|
if top == Etype {
|
|
ll := ncase.List
|
|
if ncase.Rlist.Len() != 0 {
|
|
nvar := ncase.Rlist.First()
|
|
if ll.Len() == 1 && ll.First().Type != nil && !ll.First().Type.IsKind(TNIL) {
|
|
// single entry type switch
|
|
nvar.Type = ll.First().Type
|
|
} else {
|
|
// multiple entry type switch or default
|
|
nvar.Type = n.Type
|
|
}
|
|
|
|
nvar = typecheck(nvar, Erv|Easgn)
|
|
ncase.Rlist.SetFirst(nvar)
|
|
}
|
|
}
|
|
|
|
typecheckslice(ncase.Nbody.Slice(), Etop)
|
|
}
|
|
switch top {
|
|
// expression switch
|
|
case Erv:
|
|
checkDupExprCases(n.Left, n.List.Slice())
|
|
}
|
|
}
|
|
|
|
// walkswitch walks a switch statement.
|
|
func walkswitch(sw *Node) {
|
|
// convert switch {...} to switch true {...}
|
|
if sw.Left == nil {
|
|
sw.Left = nodbool(true)
|
|
sw.Left = typecheck(sw.Left, Erv)
|
|
sw.Left = defaultlit(sw.Left, nil)
|
|
}
|
|
|
|
if sw.Left.Op == OTYPESW {
|
|
var s typeSwitch
|
|
s.walk(sw)
|
|
} else {
|
|
var s exprSwitch
|
|
s.walk(sw)
|
|
}
|
|
}
|
|
|
|
// walk generates an AST implementing sw.
|
|
// sw is an expression switch.
|
|
// The AST is generally of the form of a linear
|
|
// search using if..goto, although binary search
|
|
// is used with long runs of constants.
|
|
func (s *exprSwitch) walk(sw *Node) {
|
|
casebody(sw, nil)
|
|
|
|
cond := sw.Left
|
|
sw.Left = nil
|
|
|
|
s.kind = switchKindExpr
|
|
if Isconst(cond, CTBOOL) {
|
|
s.kind = switchKindTrue
|
|
if !cond.Val().U.(bool) {
|
|
s.kind = switchKindFalse
|
|
}
|
|
}
|
|
|
|
cond = walkexpr(cond, &sw.Ninit)
|
|
t := sw.Type
|
|
if t == nil {
|
|
return
|
|
}
|
|
|
|
// convert the switch into OIF statements
|
|
var cas []*Node
|
|
if s.kind == switchKindTrue || s.kind == switchKindFalse {
|
|
s.exprname = nodbool(s.kind == switchKindTrue)
|
|
} else if consttype(cond) > 0 {
|
|
// leave constants to enable dead code elimination (issue 9608)
|
|
s.exprname = cond
|
|
} else {
|
|
s.exprname = temp(cond.Type)
|
|
cas = []*Node{nod(OAS, s.exprname, cond)}
|
|
typecheckslice(cas, Etop)
|
|
}
|
|
|
|
// Enumerate the cases and prepare the default case.
|
|
clauses := s.genCaseClauses(sw.List.Slice())
|
|
sw.List.Set(nil)
|
|
cc := clauses.list
|
|
|
|
// handle the cases in order
|
|
for len(cc) > 0 {
|
|
run := 1
|
|
if okforcmp[t.Etype] && cc[0].isconst {
|
|
// do binary search on runs of constants
|
|
for ; run < len(cc) && cc[run].isconst; run++ {
|
|
}
|
|
// sort and compile constants
|
|
sort.Sort(caseClauseByConstVal(cc[:run]))
|
|
}
|
|
|
|
a := s.walkCases(cc[:run])
|
|
cas = append(cas, a)
|
|
cc = cc[run:]
|
|
}
|
|
|
|
// handle default case
|
|
if nerrors == 0 {
|
|
cas = append(cas, clauses.defjmp)
|
|
sw.Nbody.Prepend(cas...)
|
|
walkstmtlist(sw.Nbody.Slice())
|
|
}
|
|
}
|
|
|
|
// walkCases generates an AST implementing the cases in cc.
|
|
func (s *exprSwitch) walkCases(cc []caseClause) *Node {
|
|
if len(cc) < binarySearchMin {
|
|
// linear search
|
|
var cas []*Node
|
|
for _, c := range cc {
|
|
n := c.node
|
|
lno := setlineno(n)
|
|
|
|
a := nod(OIF, nil, nil)
|
|
if rng := n.List.Slice(); rng != nil {
|
|
// Integer range.
|
|
// exprname is a temp or a constant,
|
|
// so it is safe to evaluate twice.
|
|
// In most cases, this conjunction will be
|
|
// rewritten by walkinrange into a single comparison.
|
|
low := nod(OGE, s.exprname, rng[0])
|
|
high := nod(OLE, s.exprname, rng[1])
|
|
a.Left = nod(OANDAND, low, high)
|
|
} else if (s.kind != switchKindTrue && s.kind != switchKindFalse) || assignop(n.Left.Type, s.exprname.Type, nil) == OCONVIFACE || assignop(s.exprname.Type, n.Left.Type, nil) == OCONVIFACE {
|
|
a.Left = nod(OEQ, s.exprname, n.Left) // if name == val
|
|
} else if s.kind == switchKindTrue {
|
|
a.Left = n.Left // if val
|
|
} else {
|
|
// s.kind == switchKindFalse
|
|
a.Left = nod(ONOT, n.Left, nil) // if !val
|
|
}
|
|
a.Left = typecheck(a.Left, Erv)
|
|
a.Left = defaultlit(a.Left, nil)
|
|
a.Nbody.Set1(n.Right) // goto l
|
|
|
|
cas = append(cas, a)
|
|
lineno = lno
|
|
}
|
|
return liststmt(cas)
|
|
}
|
|
|
|
// find the middle and recur
|
|
half := len(cc) / 2
|
|
a := nod(OIF, nil, nil)
|
|
n := cc[half-1].node
|
|
var mid *Node
|
|
if rng := n.List.Slice(); rng != nil {
|
|
mid = rng[1] // high end of range
|
|
} else {
|
|
mid = n.Left
|
|
}
|
|
le := nod(OLE, s.exprname, mid)
|
|
if Isconst(mid, CTSTR) {
|
|
// Search by length and then by value; see caseClauseByConstVal.
|
|
lenlt := nod(OLT, nod(OLEN, s.exprname, nil), nod(OLEN, mid, nil))
|
|
leneq := nod(OEQ, nod(OLEN, s.exprname, nil), nod(OLEN, mid, nil))
|
|
a.Left = nod(OOROR, lenlt, nod(OANDAND, leneq, le))
|
|
} else {
|
|
a.Left = le
|
|
}
|
|
a.Left = typecheck(a.Left, Erv)
|
|
a.Left = defaultlit(a.Left, nil)
|
|
a.Nbody.Set1(s.walkCases(cc[:half]))
|
|
a.Rlist.Set1(s.walkCases(cc[half:]))
|
|
return a
|
|
}
|
|
|
|
// casebody builds separate lists of statements and cases.
|
|
// It makes labels between cases and statements
|
|
// and deals with fallthrough, break, and unreachable statements.
|
|
func casebody(sw *Node, typeswvar *Node) {
|
|
if sw.List.Len() == 0 {
|
|
return
|
|
}
|
|
|
|
lno := setlineno(sw)
|
|
|
|
var cas []*Node // cases
|
|
var stat []*Node // statements
|
|
var def *Node // defaults
|
|
br := nod(OBREAK, nil, nil)
|
|
|
|
for _, n := range sw.List.Slice() {
|
|
setlineno(n)
|
|
if n.Op != OXCASE {
|
|
Fatalf("casebody %v", n.Op)
|
|
}
|
|
n.Op = OCASE
|
|
needvar := n.List.Len() != 1 || n.List.First().Op == OLITERAL
|
|
|
|
jmp := nod(OGOTO, autolabel(".s"), nil)
|
|
switch n.List.Len() {
|
|
case 0:
|
|
// default
|
|
if def != nil {
|
|
yyerrorl(n.Pos, "more than one default case")
|
|
}
|
|
// reuse original default case
|
|
n.Right = jmp
|
|
def = n
|
|
case 1:
|
|
// one case -- reuse OCASE node
|
|
n.Left = n.List.First()
|
|
n.Right = jmp
|
|
n.List.Set(nil)
|
|
cas = append(cas, n)
|
|
default:
|
|
// Expand multi-valued cases and detect ranges of integer cases.
|
|
if typeswvar != nil || sw.Left.Type.IsInterface() || !n.List.First().Type.IsInteger() || n.List.Len() < integerRangeMin {
|
|
// Can't use integer ranges. Expand each case into a separate node.
|
|
for _, n1 := range n.List.Slice() {
|
|
cas = append(cas, nod(OCASE, n1, jmp))
|
|
}
|
|
break
|
|
}
|
|
// Find integer ranges within runs of constants.
|
|
s := n.List.Slice()
|
|
j := 0
|
|
for j < len(s) {
|
|
// Find a run of constants.
|
|
var run int
|
|
for run = j; run < len(s) && Isconst(s[run], CTINT); run++ {
|
|
}
|
|
if run-j >= integerRangeMin {
|
|
// Search for integer ranges in s[j:run].
|
|
// Typechecking is done, so all values are already in an appropriate range.
|
|
search := s[j:run]
|
|
sort.Sort(constIntNodesByVal(search))
|
|
for beg, end := 0, 1; end <= len(search); end++ {
|
|
if end < len(search) && search[end].Int64() == search[end-1].Int64()+1 {
|
|
continue
|
|
}
|
|
if end-beg >= integerRangeMin {
|
|
// Record range in List.
|
|
c := nod(OCASE, nil, jmp)
|
|
c.List.Set2(search[beg], search[end-1])
|
|
cas = append(cas, c)
|
|
} else {
|
|
// Not large enough for range; record separately.
|
|
for _, n := range search[beg:end] {
|
|
cas = append(cas, nod(OCASE, n, jmp))
|
|
}
|
|
}
|
|
beg = end
|
|
}
|
|
j = run
|
|
}
|
|
// Advance to next constant, adding individual non-constant
|
|
// or as-yet-unhandled constant cases as we go.
|
|
for ; j < len(s) && (j < run || !Isconst(s[j], CTINT)); j++ {
|
|
cas = append(cas, nod(OCASE, s[j], jmp))
|
|
}
|
|
}
|
|
}
|
|
|
|
stat = append(stat, nod(OLABEL, jmp.Left, nil))
|
|
if typeswvar != nil && needvar && n.Rlist.Len() != 0 {
|
|
l := []*Node{
|
|
nod(ODCL, n.Rlist.First(), nil),
|
|
nod(OAS, n.Rlist.First(), typeswvar),
|
|
}
|
|
typecheckslice(l, Etop)
|
|
stat = append(stat, l...)
|
|
}
|
|
stat = append(stat, n.Nbody.Slice()...)
|
|
|
|
// Search backwards for the index of the fallthrough
|
|
// statement. Do not assume it'll be in the last
|
|
// position, since in some cases (e.g. when the statement
|
|
// list contains autotmp_ variables), one or more OVARKILL
|
|
// nodes will be at the end of the list.
|
|
fallIndex := len(stat) - 1
|
|
for stat[fallIndex].Op == OVARKILL {
|
|
fallIndex--
|
|
}
|
|
last := stat[fallIndex]
|
|
if last.Op != OFALL {
|
|
stat = append(stat, br)
|
|
}
|
|
}
|
|
|
|
stat = append(stat, br)
|
|
if def != nil {
|
|
cas = append(cas, def)
|
|
}
|
|
|
|
sw.List.Set(cas)
|
|
sw.Nbody.Set(stat)
|
|
lineno = lno
|
|
}
|
|
|
|
// genCaseClauses generates the caseClauses value for clauses.
|
|
func (s *exprSwitch) genCaseClauses(clauses []*Node) caseClauses {
|
|
var cc caseClauses
|
|
for _, n := range clauses {
|
|
if n.Left == nil && n.List.Len() == 0 {
|
|
// default case
|
|
if cc.defjmp != nil {
|
|
Fatalf("duplicate default case not detected during typechecking")
|
|
}
|
|
cc.defjmp = n.Right
|
|
continue
|
|
}
|
|
c := caseClause{node: n, ordinal: len(cc.list)}
|
|
if n.List.Len() > 0 {
|
|
c.isconst = true
|
|
}
|
|
switch consttype(n.Left) {
|
|
case CTFLT, CTINT, CTRUNE, CTSTR:
|
|
c.isconst = true
|
|
}
|
|
cc.list = append(cc.list, c)
|
|
}
|
|
|
|
if cc.defjmp == nil {
|
|
cc.defjmp = nod(OBREAK, nil, nil)
|
|
}
|
|
return cc
|
|
}
|
|
|
|
// genCaseClauses generates the caseClauses value for clauses.
|
|
func (s *typeSwitch) genCaseClauses(clauses []*Node) caseClauses {
|
|
var cc caseClauses
|
|
for _, n := range clauses {
|
|
switch {
|
|
case n.Left == nil:
|
|
// default case
|
|
if cc.defjmp != nil {
|
|
Fatalf("duplicate default case not detected during typechecking")
|
|
}
|
|
cc.defjmp = n.Right
|
|
continue
|
|
case n.Left.Op == OLITERAL:
|
|
// nil case in type switch
|
|
if cc.niljmp != nil {
|
|
Fatalf("duplicate nil case not detected during typechecking")
|
|
}
|
|
cc.niljmp = n.Right
|
|
continue
|
|
}
|
|
|
|
// general case
|
|
c := caseClause{
|
|
node: n,
|
|
ordinal: len(cc.list),
|
|
isconst: !n.Left.Type.IsInterface(),
|
|
hash: typehash(n.Left.Type),
|
|
}
|
|
cc.list = append(cc.list, c)
|
|
}
|
|
|
|
if cc.defjmp == nil {
|
|
cc.defjmp = nod(OBREAK, nil, nil)
|
|
}
|
|
|
|
// diagnose duplicate cases
|
|
s.checkDupCases(cc.list)
|
|
return cc
|
|
}
|
|
|
|
func (s *typeSwitch) checkDupCases(cc []caseClause) {
|
|
if len(cc) < 2 {
|
|
return
|
|
}
|
|
// We store seen types in a map keyed by type hash.
|
|
// It is possible, but very unlikely, for multiple distinct types to have the same hash.
|
|
seen := make(map[uint32][]*Node)
|
|
// To avoid many small allocations of length 1 slices,
|
|
// also set up a single large slice to slice into.
|
|
nn := make([]*Node, 0, len(cc))
|
|
Outer:
|
|
for _, c := range cc {
|
|
prev, ok := seen[c.hash]
|
|
if !ok {
|
|
// First entry for this hash.
|
|
nn = append(nn, c.node)
|
|
seen[c.hash] = nn[len(nn)-1 : len(nn) : len(nn)]
|
|
continue
|
|
}
|
|
for _, n := range prev {
|
|
if eqtype(n.Left.Type, c.node.Left.Type) {
|
|
yyerrorl(c.node.Pos, "duplicate case %v in type switch\n\tprevious case at %v", c.node.Left.Type, n.Line())
|
|
// avoid double-reporting errors
|
|
continue Outer
|
|
}
|
|
}
|
|
seen[c.hash] = append(seen[c.hash], c.node)
|
|
}
|
|
}
|
|
|
|
func checkDupExprCases(exprname *Node, clauses []*Node) {
|
|
// boolean (naked) switch, nothing to do.
|
|
if exprname == nil {
|
|
return
|
|
}
|
|
// The common case is that s's expression is not an interface.
|
|
// In that case, all constant clauses have the same type,
|
|
// so checking for duplicates can be done solely by value.
|
|
if !exprname.Type.IsInterface() {
|
|
seen := make(map[interface{}]*Node)
|
|
for _, ncase := range clauses {
|
|
for _, n := range ncase.List.Slice() {
|
|
// Can't check for duplicates that aren't constants, per the spec. Issue 15896.
|
|
// Don't check for duplicate bools. Although the spec allows it,
|
|
// (1) the compiler hasn't checked it in the past, so compatibility mandates it, and
|
|
// (2) it would disallow useful things like
|
|
// case GOARCH == "arm" && GOARM == "5":
|
|
// case GOARCH == "arm":
|
|
// which would both evaluate to false for non-ARM compiles.
|
|
if ct := consttype(n); ct == 0 || ct == CTBOOL {
|
|
continue
|
|
}
|
|
|
|
val := n.Val().Interface()
|
|
prev, dup := seen[val]
|
|
if !dup {
|
|
seen[val] = n
|
|
continue
|
|
}
|
|
yyerrorl(ncase.Pos, "duplicate case %s in switch\n\tprevious case at %v",
|
|
nodeAndVal(n), prev.Line())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// s's expression is an interface. This is fairly rare, so
|
|
// keep this simple. Case expressions are only duplicates if
|
|
// they have the same value and identical types.
|
|
//
|
|
// In general, we have to use eqtype to test type identity,
|
|
// because == gives false negatives for anonymous types and
|
|
// the byte/uint8 and rune/int32 builtin type aliases.
|
|
// However, this is not a problem here, because constant
|
|
// expressions are always untyped or have a named type, and we
|
|
// explicitly handle the builtin type aliases below.
|
|
//
|
|
// This approach may need to be revisited though if we fix
|
|
// #21866 by treating all type aliases like byte/uint8 and
|
|
// rune/int32.
|
|
type typeVal struct {
|
|
typ *types.Type
|
|
val interface{}
|
|
}
|
|
seen := make(map[typeVal]*Node)
|
|
for _, ncase := range clauses {
|
|
for _, n := range ncase.List.Slice() {
|
|
if ct := consttype(n); ct == 0 || ct == CTBOOL {
|
|
continue
|
|
}
|
|
tv := typeVal{
|
|
typ: n.Type,
|
|
val: n.Val().Interface(),
|
|
}
|
|
switch tv.typ {
|
|
case types.Bytetype:
|
|
tv.typ = types.Types[TUINT8]
|
|
case types.Runetype:
|
|
tv.typ = types.Types[TINT32]
|
|
}
|
|
prev, dup := seen[tv]
|
|
if !dup {
|
|
seen[tv] = n
|
|
continue
|
|
}
|
|
yyerrorl(ncase.Pos, "duplicate case %s in switch\n\tprevious case at %v",
|
|
nodeAndVal(n), prev.Line())
|
|
}
|
|
}
|
|
}
|
|
|
|
func nodeAndVal(n *Node) string {
|
|
show := n.String()
|
|
val := n.Val().Interface()
|
|
if s := fmt.Sprintf("%#v", val); show != s {
|
|
show += " (value " + s + ")"
|
|
}
|
|
return show
|
|
}
|
|
|
|
// walk generates an AST that implements sw,
|
|
// where sw is a type switch.
|
|
// The AST is generally of the form of a linear
|
|
// search using if..goto, although binary search
|
|
// is used with long runs of concrete types.
|
|
func (s *typeSwitch) walk(sw *Node) {
|
|
cond := sw.Left
|
|
sw.Left = nil
|
|
|
|
if cond == nil {
|
|
sw.List.Set(nil)
|
|
return
|
|
}
|
|
if cond.Right == nil {
|
|
yyerrorl(sw.Pos, "type switch must have an assignment")
|
|
return
|
|
}
|
|
|
|
cond.Right = walkexpr(cond.Right, &sw.Ninit)
|
|
if !cond.Right.Type.IsInterface() {
|
|
yyerrorl(sw.Pos, "type switch must be on an interface")
|
|
return
|
|
}
|
|
|
|
var cas []*Node
|
|
|
|
// predeclare temporary variables and the boolean var
|
|
s.facename = temp(cond.Right.Type)
|
|
|
|
a := nod(OAS, s.facename, cond.Right)
|
|
a = typecheck(a, Etop)
|
|
cas = append(cas, a)
|
|
|
|
s.okname = temp(types.Types[TBOOL])
|
|
s.okname = typecheck(s.okname, Erv)
|
|
|
|
s.hashname = temp(types.Types[TUINT32])
|
|
s.hashname = typecheck(s.hashname, Erv)
|
|
|
|
// set up labels and jumps
|
|
casebody(sw, s.facename)
|
|
|
|
clauses := s.genCaseClauses(sw.List.Slice())
|
|
sw.List.Set(nil)
|
|
def := clauses.defjmp
|
|
|
|
// For empty interfaces, do:
|
|
// if e._type == nil {
|
|
// do nil case if it exists, otherwise default
|
|
// }
|
|
// h := e._type.hash
|
|
// Use a similar strategy for non-empty interfaces.
|
|
|
|
// Get interface descriptor word.
|
|
// For empty interfaces this will be the type.
|
|
// For non-empty interfaces this will be the itab.
|
|
itab := nod(OITAB, s.facename, nil)
|
|
|
|
// Check for nil first.
|
|
i := nod(OIF, nil, nil)
|
|
i.Left = nod(OEQ, itab, nodnil())
|
|
if clauses.niljmp != nil {
|
|
// Do explicit nil case right here.
|
|
i.Nbody.Set1(clauses.niljmp)
|
|
} else {
|
|
// Jump to default case.
|
|
lbl := autolabel(".s")
|
|
i.Nbody.Set1(nod(OGOTO, lbl, nil))
|
|
// Wrap default case with label.
|
|
blk := nod(OBLOCK, nil, nil)
|
|
blk.List.Set2(nod(OLABEL, lbl, nil), def)
|
|
def = blk
|
|
}
|
|
i.Left = typecheck(i.Left, Erv)
|
|
i.Left = defaultlit(i.Left, nil)
|
|
cas = append(cas, i)
|
|
|
|
// Load hash from type or itab.
|
|
h := nodSym(ODOTPTR, itab, nil)
|
|
h.Type = types.Types[TUINT32]
|
|
h.SetTypecheck(1)
|
|
if cond.Right.Type.IsEmptyInterface() {
|
|
h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type
|
|
} else {
|
|
h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime.itab
|
|
}
|
|
h.SetBounded(true) // guaranteed not to fault
|
|
a = nod(OAS, s.hashname, h)
|
|
a = typecheck(a, Etop)
|
|
cas = append(cas, a)
|
|
|
|
cc := clauses.list
|
|
|
|
// insert type equality check into each case block
|
|
for _, c := range cc {
|
|
c.node.Right = s.typeone(c.node)
|
|
}
|
|
|
|
// generate list of if statements, binary search for constant sequences
|
|
for len(cc) > 0 {
|
|
if !cc[0].isconst {
|
|
n := cc[0].node
|
|
cas = append(cas, n.Right)
|
|
cc = cc[1:]
|
|
continue
|
|
}
|
|
|
|
// identify run of constants
|
|
var run int
|
|
for run = 1; run < len(cc) && cc[run].isconst; run++ {
|
|
}
|
|
|
|
// sort by hash
|
|
sort.Sort(caseClauseByType(cc[:run]))
|
|
|
|
// for debugging: linear search
|
|
if false {
|
|
for i := 0; i < run; i++ {
|
|
n := cc[i].node
|
|
cas = append(cas, n.Right)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// combine adjacent cases with the same hash
|
|
var batch []caseClause
|
|
for i, j := 0, 0; i < run; i = j {
|
|
hash := []*Node{cc[i].node.Right}
|
|
for j = i + 1; j < run && cc[i].hash == cc[j].hash; j++ {
|
|
hash = append(hash, cc[j].node.Right)
|
|
}
|
|
cc[i].node.Right = liststmt(hash)
|
|
batch = append(batch, cc[i])
|
|
}
|
|
|
|
// binary search among cases to narrow by hash
|
|
cas = append(cas, s.walkCases(batch))
|
|
cc = cc[run:]
|
|
}
|
|
|
|
// handle default case
|
|
if nerrors == 0 {
|
|
cas = append(cas, def)
|
|
sw.Nbody.Prepend(cas...)
|
|
sw.List.Set(nil)
|
|
walkstmtlist(sw.Nbody.Slice())
|
|
}
|
|
}
|
|
|
|
// typeone generates an AST that jumps to the
|
|
// case body if the variable is of type t.
|
|
func (s *typeSwitch) typeone(t *Node) *Node {
|
|
var name *Node
|
|
var init Nodes
|
|
if t.Rlist.Len() == 0 {
|
|
name = nblank
|
|
nblank = typecheck(nblank, Erv|Easgn)
|
|
} else {
|
|
name = t.Rlist.First()
|
|
init.Append(nod(ODCL, name, nil))
|
|
a := nod(OAS, name, nil)
|
|
a = typecheck(a, Etop)
|
|
init.Append(a)
|
|
}
|
|
|
|
a := nod(OAS2, nil, nil)
|
|
a.List.Set2(name, s.okname) // name, ok =
|
|
b := nod(ODOTTYPE, s.facename, nil)
|
|
b.Type = t.Left.Type // interface.(type)
|
|
a.Rlist.Set1(b)
|
|
a = typecheck(a, Etop)
|
|
a = walkexpr(a, &init)
|
|
init.Append(a)
|
|
|
|
c := nod(OIF, nil, nil)
|
|
c.Left = s.okname
|
|
c.Nbody.Set1(t.Right) // if ok { goto l }
|
|
|
|
init.Append(c)
|
|
return init.asblock()
|
|
}
|
|
|
|
// walkCases generates an AST implementing the cases in cc.
|
|
func (s *typeSwitch) walkCases(cc []caseClause) *Node {
|
|
if len(cc) < binarySearchMin {
|
|
var cas []*Node
|
|
for _, c := range cc {
|
|
n := c.node
|
|
if !c.isconst {
|
|
Fatalf("typeSwitch walkCases")
|
|
}
|
|
a := nod(OIF, nil, nil)
|
|
a.Left = nod(OEQ, s.hashname, nodintconst(int64(c.hash)))
|
|
a.Left = typecheck(a.Left, Erv)
|
|
a.Left = defaultlit(a.Left, nil)
|
|
a.Nbody.Set1(n.Right)
|
|
cas = append(cas, a)
|
|
}
|
|
return liststmt(cas)
|
|
}
|
|
|
|
// find the middle and recur
|
|
half := len(cc) / 2
|
|
a := nod(OIF, nil, nil)
|
|
a.Left = nod(OLE, s.hashname, nodintconst(int64(cc[half-1].hash)))
|
|
a.Left = typecheck(a.Left, Erv)
|
|
a.Left = defaultlit(a.Left, nil)
|
|
a.Nbody.Set1(s.walkCases(cc[:half]))
|
|
a.Rlist.Set1(s.walkCases(cc[half:]))
|
|
return a
|
|
}
|
|
|
|
// caseClauseByConstVal sorts clauses by constant value to enable binary search.
|
|
type caseClauseByConstVal []caseClause
|
|
|
|
func (x caseClauseByConstVal) Len() int { return len(x) }
|
|
func (x caseClauseByConstVal) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
func (x caseClauseByConstVal) Less(i, j int) bool {
|
|
// n1 and n2 might be individual constants or integer ranges.
|
|
// We have checked for duplicates already,
|
|
// so ranges can be safely represented by any value in the range.
|
|
n1 := x[i].node
|
|
var v1 interface{}
|
|
if s := n1.List.Slice(); s != nil {
|
|
v1 = s[0].Val().U
|
|
} else {
|
|
v1 = n1.Left.Val().U
|
|
}
|
|
|
|
n2 := x[j].node
|
|
var v2 interface{}
|
|
if s := n2.List.Slice(); s != nil {
|
|
v2 = s[0].Val().U
|
|
} else {
|
|
v2 = n2.Left.Val().U
|
|
}
|
|
|
|
switch v1 := v1.(type) {
|
|
case *Mpflt:
|
|
return v1.Cmp(v2.(*Mpflt)) < 0
|
|
case *Mpint:
|
|
return v1.Cmp(v2.(*Mpint)) < 0
|
|
case string:
|
|
// Sort strings by length and then by value.
|
|
// It is much cheaper to compare lengths than values,
|
|
// and all we need here is consistency.
|
|
// We respect this sorting in exprSwitch.walkCases.
|
|
a := v1
|
|
b := v2.(string)
|
|
if len(a) != len(b) {
|
|
return len(a) < len(b)
|
|
}
|
|
return a < b
|
|
}
|
|
|
|
Fatalf("caseClauseByConstVal passed bad clauses %v < %v", x[i].node.Left, x[j].node.Left)
|
|
return false
|
|
}
|
|
|
|
type caseClauseByType []caseClause
|
|
|
|
func (x caseClauseByType) Len() int { return len(x) }
|
|
func (x caseClauseByType) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
func (x caseClauseByType) Less(i, j int) bool {
|
|
c1, c2 := x[i], x[j]
|
|
// sort by hash code, then ordinal (for the rare case of hash collisions)
|
|
if c1.hash != c2.hash {
|
|
return c1.hash < c2.hash
|
|
}
|
|
return c1.ordinal < c2.ordinal
|
|
}
|
|
|
|
type constIntNodesByVal []*Node
|
|
|
|
func (x constIntNodesByVal) Len() int { return len(x) }
|
|
func (x constIntNodesByVal) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
func (x constIntNodesByVal) Less(i, j int) bool {
|
|
return x[i].Val().U.(*Mpint).Cmp(x[j].Val().U.(*Mpint)) < 0
|
|
}
|