1
0
mirror of https://github.com/golang/go synced 2024-11-17 08:04:46 -07:00

[dev.regabi] cmd/compile: refactoring typecheck arith

Currently, the tcArith logic is complicated and involes many
un-necessary checks for some ir.Op. This CL refactors how it works:

 - Add a new tcShiftOp function, which only does necessary works for
   typechecking OLSH/ORSH. That ends up moving OLSH/ORSH to a separated
   case in typecheck1.

 - Move OASOP to separated case, so its logic is detached from tcArith.

 - Move OANDAND/OOROR to separated case, which does some validation
   dedicated to logical operators only.

Passes toolstash -cmp.

Change-Id: I0db7b7c7a3e52d6f9e9d87eee6967871f1c32200
Reviewed-on: https://go-review.googlesource.com/c/go/+/279442
Trust: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Cuong Manh Le 2020-12-29 12:09:51 +07:00
parent a5ec920160
commit e34c44a7c4
2 changed files with 137 additions and 167 deletions

View File

@ -55,103 +55,50 @@ func tcAddr(n *ir.AddrExpr) ir.Node {
return n
}
// tcArith typechecks a binary arithmetic expression.
func tcArith(n ir.Node) ir.Node {
var l, r ir.Node
var setLR func()
switch n := n.(type) {
case *ir.AssignOpStmt:
l, r = n.X, n.Y
setLR = func() { n.X = l; n.Y = r }
case *ir.BinaryExpr:
l, r = n.X, n.Y
setLR = func() { n.X = l; n.Y = r }
case *ir.LogicalExpr:
l, r = n.X, n.Y
setLR = func() { n.X = l; n.Y = r }
}
l = Expr(l)
r = Expr(r)
setLR()
if l.Type() == nil || r.Type() == nil {
n.SetType(nil)
return n
}
op := n.Op()
if n.Op() == ir.OASOP {
n := n.(*ir.AssignOpStmt)
checkassign(n, l)
if n.IncDec && !okforarith[l.Type().Kind()] {
base.Errorf("invalid operation: %v (non-numeric type %v)", n, l.Type())
n.SetType(nil)
return n
}
// TODO(marvin): Fix Node.EType type union.
op = n.AsOp
}
if op == ir.OLSH || op == ir.ORSH {
r = DefaultLit(r, types.Types[types.TUINT])
setLR()
t := r.Type()
if !t.IsInteger() {
base.Errorf("invalid operation: %v (shift count type %v, must be integer)", n, r.Type())
n.SetType(nil)
return n
}
if t.IsSigned() && !types.AllowsGoVersion(curpkg(), 1, 13) {
base.ErrorfVers("go1.13", "invalid operation: %v (signed shift count type %v)", n, r.Type())
n.SetType(nil)
return n
}
t = l.Type()
if t != nil && t.Kind() != types.TIDEAL && !t.IsInteger() {
base.Errorf("invalid operation: %v (shift of type %v)", n, t)
n.SetType(nil)
return n
}
// no defaultlit for left
// the outer context gives the type
n.SetType(l.Type())
if (l.Type() == types.UntypedFloat || l.Type() == types.UntypedComplex) && r.Op() == ir.OLITERAL {
n.SetType(types.UntypedInt)
}
return n
func tcShift(n, l, r ir.Node) (ir.Node, ir.Node, *types.Type) {
if l.Type() == nil || l.Type() == nil {
return l, r, nil
}
// For "x == x && len(s)", it's better to report that "len(s)" (type int)
// can't be used with "&&" than to report that "x == x" (type untyped bool)
// can't be converted to int (see issue #41500).
if n.Op() == ir.OANDAND || n.Op() == ir.OOROR {
n := n.(*ir.LogicalExpr)
if !n.X.Type().IsBoolean() {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.X.Type()))
n.SetType(nil)
return n
}
if !n.Y.Type().IsBoolean() {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.Y.Type()))
n.SetType(nil)
return n
}
r = DefaultLit(r, types.Types[types.TUINT])
t := r.Type()
if !t.IsInteger() {
base.Errorf("invalid operation: %v (shift count type %v, must be integer)", n, r.Type())
return l, r, nil
}
if t.IsSigned() && !types.AllowsGoVersion(curpkg(), 1, 13) {
base.ErrorfVers("go1.13", "invalid operation: %v (signed shift count type %v)", n, r.Type())
return l, r, nil
}
t = l.Type()
if t != nil && t.Kind() != types.TIDEAL && !t.IsInteger() {
base.Errorf("invalid operation: %v (shift of type %v)", n, t)
return l, r, nil
}
// ideal mixed with non-ideal
// no defaultlit for left
// the outer context gives the type
t = l.Type()
if (l.Type() == types.UntypedFloat || l.Type() == types.UntypedComplex) && r.Op() == ir.OLITERAL {
t = types.UntypedInt
}
return l, r, t
}
// tcArith typechecks operands of a binary arithmetic expression.
// The result of tcArith MUST be assigned back to original operands,
// t is the type of the expression, and should be set by the caller. e.g:
// n.X, n.Y, t = tcArith(n, op, n.X, n.Y)
// n.SetType(t)
func tcArith(n ir.Node, op ir.Op, l, r ir.Node) (ir.Node, ir.Node, *types.Type) {
l, r = defaultlit2(l, r, false)
setLR()
if l.Type() == nil || r.Type() == nil {
n.SetType(nil)
return n
return l, r, nil
}
t := l.Type()
if t.Kind() == types.TIDEAL {
t = r.Type()
}
et := t.Kind()
if et == types.TIDEAL {
et = types.TINT
}
aop := ir.OXXX
if iscmp[n.Op()] && t.Kind() != types.TIDEAL && !types.Identical(l.Type(), r.Type()) {
// comparison is okay as long as one side is
@ -167,15 +114,13 @@ func tcArith(n ir.Node) ir.Node {
if aop != ir.OXXX {
if r.Type().IsInterface() && !l.Type().IsInterface() && !types.IsComparable(l.Type()) {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(l.Type()))
n.SetType(nil)
return n
return l, r, nil
}
types.CalcSize(l.Type())
if r.Type().IsInterface() == l.Type().IsInterface() || l.Type().Width >= 1<<16 {
l = ir.NewConvExpr(base.Pos, aop, r.Type(), l)
l.SetTypecheck(1)
setLR()
}
t = r.Type()
@ -188,34 +133,28 @@ func tcArith(n ir.Node) ir.Node {
if aop != ir.OXXX {
if l.Type().IsInterface() && !r.Type().IsInterface() && !types.IsComparable(r.Type()) {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(r.Type()))
n.SetType(nil)
return n
return l, r, nil
}
types.CalcSize(r.Type())
if r.Type().IsInterface() == l.Type().IsInterface() || r.Type().Width >= 1<<16 {
r = ir.NewConvExpr(base.Pos, aop, l.Type(), r)
r.SetTypecheck(1)
setLR()
}
t = l.Type()
}
}
et = t.Kind()
}
if t.Kind() != types.TIDEAL && !types.Identical(l.Type(), r.Type()) {
l, r = defaultlit2(l, r, true)
if l.Type() == nil || r.Type() == nil {
n.SetType(nil)
return n
return l, r, nil
}
if l.Type().IsInterface() == r.Type().IsInterface() || aop == 0 {
base.Errorf("invalid operation: %v (mismatched types %v and %v)", n, l.Type(), r.Type())
n.SetType(nil)
return n
return l, r, nil
}
}
@ -224,85 +163,46 @@ func tcArith(n ir.Node) ir.Node {
}
if dt := defaultType(t); !okfor[op][dt.Kind()] {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, op, typekind(t))
n.SetType(nil)
return n
return l, r, nil
}
// okfor allows any array == array, map == map, func == func.
// restrict to slice/map/func == nil and nil == slice/map/func.
if l.Type().IsArray() && !types.IsComparable(l.Type()) {
base.Errorf("invalid operation: %v (%v cannot be compared)", n, l.Type())
n.SetType(nil)
return n
return l, r, nil
}
if l.Type().IsSlice() && !ir.IsNil(l) && !ir.IsNil(r) {
base.Errorf("invalid operation: %v (slice can only be compared to nil)", n)
n.SetType(nil)
return n
return l, r, nil
}
if l.Type().IsMap() && !ir.IsNil(l) && !ir.IsNil(r) {
base.Errorf("invalid operation: %v (map can only be compared to nil)", n)
n.SetType(nil)
return n
return l, r, nil
}
if l.Type().Kind() == types.TFUNC && !ir.IsNil(l) && !ir.IsNil(r) {
base.Errorf("invalid operation: %v (func can only be compared to nil)", n)
n.SetType(nil)
return n
return l, r, nil
}
if l.Type().IsStruct() {
if f := types.IncomparableField(l.Type()); f != nil {
base.Errorf("invalid operation: %v (struct containing %v cannot be compared)", n, f.Type)
n.SetType(nil)
return n
return l, r, nil
}
}
if iscmp[n.Op()] {
t = types.UntypedBool
n.SetType(t)
if con := EvalConst(n); con.Op() == ir.OLITERAL {
return con
}
l, r = defaultlit2(l, r, true)
setLR()
return n
}
if et == types.TSTRING && n.Op() == ir.OADD {
// create or update OADDSTR node with list of strings in x + y + z + (w + v) + ...
n := n.(*ir.BinaryExpr)
var add *ir.AddStringExpr
if l.Op() == ir.OADDSTR {
add = l.(*ir.AddStringExpr)
add.SetPos(n.Pos())
} else {
add = ir.NewAddStringExpr(n.Pos(), []ir.Node{l})
}
if r.Op() == ir.OADDSTR {
r := r.(*ir.AddStringExpr)
add.List.Append(r.List.Take()...)
} else {
add.List.Append(r)
}
add.SetType(t)
return add
}
if (op == ir.ODIV || op == ir.OMOD) && ir.IsConst(r, constant.Int) {
if constant.Sign(r.Val()) == 0 {
base.Errorf("division by zero")
n.SetType(nil)
return n
return l, r, nil
}
}
n.SetType(t)
return n
return l, r, t
}
// The result of tcCompLit MUST be assigned back to n, e.g.

View File

@ -672,28 +672,98 @@ func typecheck1(n ir.Node, top int) ir.Node {
case ir.ODEREF:
n := n.(*ir.StarExpr)
return tcStar(n, top)
// arithmetic exprs
case ir.OASOP,
ir.OADD,
ir.OAND,
ir.OANDAND,
ir.OANDNOT,
ir.ODIV,
ir.OEQ,
ir.OGE,
ir.OGT,
ir.OLE,
ir.OLT,
ir.OLSH,
ir.ORSH,
ir.OMOD,
ir.OMUL,
ir.ONE,
ir.OOR,
ir.OOROR,
ir.OSUB,
ir.OXOR:
return tcArith(n)
// x op= y
case ir.OASOP:
n := n.(*ir.AssignOpStmt)
n.X, n.Y = Expr(n.X), Expr(n.Y)
checkassign(n, n.X)
if n.IncDec && !okforarith[n.X.Type().Kind()] {
base.Errorf("invalid operation: %v (non-numeric type %v)", n, n.X.Type())
return n
}
switch n.AsOp {
case ir.OLSH, ir.ORSH:
n.X, n.Y, _ = tcShift(n, n.X, n.Y)
case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD, ir.OMUL, ir.OOR, ir.OSUB, ir.OXOR:
n.X, n.Y, _ = tcArith(n, n.AsOp, n.X, n.Y)
default:
base.Fatalf("invalid assign op: %v", n.AsOp)
}
return n
// logical operators
case ir.OANDAND, ir.OOROR:
n := n.(*ir.LogicalExpr)
n.X, n.Y = Expr(n.X), Expr(n.Y)
// For "x == x && len(s)", it's better to report that "len(s)" (type int)
// can't be used with "&&" than to report that "x == x" (type untyped bool)
// can't be converted to int (see issue #41500).
if !n.X.Type().IsBoolean() {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.X.Type()))
n.SetType(nil)
return n
}
if !n.Y.Type().IsBoolean() {
base.Errorf("invalid operation: %v (operator %v not defined on %s)", n, n.Op(), typekind(n.Y.Type()))
n.SetType(nil)
return n
}
l, r, t := tcArith(n, n.Op(), n.X, n.Y)
n.X, n.Y = l, r
n.SetType(t)
return n
// shift operators
case ir.OLSH, ir.ORSH:
n := n.(*ir.BinaryExpr)
n.X, n.Y = Expr(n.X), Expr(n.Y)
l, r, t := tcShift(n, n.X, n.Y)
n.X, n.Y = l, r
n.SetType(t)
return n
// comparison operators
case ir.OEQ, ir.OGE, ir.OGT, ir.OLE, ir.OLT, ir.ONE:
n := n.(*ir.BinaryExpr)
n.X, n.Y = Expr(n.X), Expr(n.Y)
l, r, t := tcArith(n, n.Op(), n.X, n.Y)
if t != nil {
n.X, n.Y = l, r
n.SetType(types.UntypedBool)
if con := EvalConst(n); con.Op() == ir.OLITERAL {
return con
}
n.X, n.Y = defaultlit2(l, r, true)
}
return n
// binary operators
case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD, ir.OMUL, ir.OOR, ir.OSUB, ir.OXOR:
n := n.(*ir.BinaryExpr)
n.X, n.Y = Expr(n.X), Expr(n.Y)
l, r, t := tcArith(n, n.Op(), n.X, n.Y)
if t != nil && t.Kind() == types.TSTRING && n.Op() == ir.OADD {
// create or update OADDSTR node with list of strings in x + y + z + (w + v) + ...
var add *ir.AddStringExpr
if l.Op() == ir.OADDSTR {
add = l.(*ir.AddStringExpr)
add.SetPos(n.Pos())
} else {
add = ir.NewAddStringExpr(n.Pos(), []ir.Node{l})
}
if r.Op() == ir.OADDSTR {
r := r.(*ir.AddStringExpr)
add.List.Append(r.List.Take()...)
} else {
add.List.Append(r)
}
add.SetType(t)
return add
}
n.X, n.Y = l, r
n.SetType(t)
return n
case ir.OBITNOT, ir.ONEG, ir.ONOT, ir.OPLUS:
n := n.(*ir.UnaryExpr)