mirror of
https://github.com/golang/go
synced 2024-11-26 03:27:58 -07:00
[dev.regabi] cmd/compile: cleanup assignment typechecking
The assignment type-checking code previously bounced around a lot between the LHS and RHS sides of the assignment. But there's actually a very simple, consistent pattern to how to type check assignments: 1. Check the RHS expression. 2. If the LHS expression is an identifier that was declared in this statement and it doesn't have an explicit type, give it the RHS expression's default type. 3. Check the LHS expression. 4. Try assigning the RHS expression to the LHS expression, adding implicit conversions as needed. This CL implements this algorithm, and refactors tcAssign and tcAssignList to use a common implementation. It also fixes the error messages to consistently say just "1 variable" or "1 value", rather than occasionally "1 variables" or "1 values". Fixes #43348. Passes toolstash -cmp. Change-Id: I749cb8d6ccbc7d22cd7cb0a381f58a39fc2696b5 Reviewed-on: https://go-review.googlesource.com/c/go/+/280112 Trust: Matthew Dempsky <mdempsky@google.com> Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
parent
e24d2f3d05
commit
396b6c2e7c
@ -93,47 +93,16 @@ func tcAssign(n *ir.AssignStmt) {
|
|||||||
defer tracePrint("typecheckas", n)(nil)
|
defer tracePrint("typecheckas", n)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delicate little dance.
|
if n.Y == nil {
|
||||||
// the definition of n may refer to this assignment
|
|
||||||
// as its definition, in which case it will call typecheckas.
|
|
||||||
// in that case, do not call typecheck back, or it will cycle.
|
|
||||||
// if the variable has a type (ntype) then typechecking
|
|
||||||
// will not look at defn, so it is okay (and desirable,
|
|
||||||
// so that the conversion below happens).
|
|
||||||
n.X = Resolve(n.X)
|
|
||||||
|
|
||||||
if !ir.DeclaredBy(n.X, n) || n.X.Name().Ntype != nil {
|
|
||||||
n.X = AssignExpr(n.X)
|
n.X = AssignExpr(n.X)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ctxMultiOK so we can emit an "N variables but M values" error
|
lhs, rhs := []ir.Node{n.X}, []ir.Node{n.Y}
|
||||||
// to be consistent with typecheckas2 (#26616).
|
assign(n, lhs, rhs)
|
||||||
n.Y = typecheck(n.Y, ctxExpr|ctxMultiOK)
|
n.X, n.Y = lhs[0], rhs[0]
|
||||||
checkassign(n, n.X)
|
|
||||||
if n.Y != nil && n.Y.Type() != nil {
|
|
||||||
if n.Y.Type().IsFuncArgStruct() {
|
|
||||||
base.Errorf("assignment mismatch: 1 variable but %v returns %d values", n.Y.(*ir.CallExpr).X, n.Y.Type().NumFields())
|
|
||||||
// Multi-value RHS isn't actually valid for OAS; nil out
|
|
||||||
// to indicate failed typechecking.
|
|
||||||
n.Y.SetType(nil)
|
|
||||||
} else if n.X.Type() != nil {
|
|
||||||
n.Y = AssignConv(n.Y, n.X.Type(), "assignment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ir.DeclaredBy(n.X, n) && n.X.Name().Ntype == nil {
|
// TODO(mdempsky): This seems out of place.
|
||||||
n.Y = DefaultLit(n.Y, nil)
|
|
||||||
n.X.SetType(n.Y.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
// second half of dance.
|
|
||||||
// now that right is done, typecheck the left
|
|
||||||
// just to get it over with. see dance above.
|
|
||||||
n.SetTypecheck(1)
|
|
||||||
|
|
||||||
if n.X.Typecheck() == 0 {
|
|
||||||
n.X = AssignExpr(n.X)
|
|
||||||
}
|
|
||||||
if !ir.IsBlank(n.X) {
|
if !ir.IsBlank(n.X) {
|
||||||
types.CheckSize(n.X.Type()) // ensure width is calculated for backend
|
types.CheckSize(n.X.Type()) // ensure width is calculated for backend
|
||||||
}
|
}
|
||||||
@ -144,132 +113,118 @@ func tcAssignList(n *ir.AssignListStmt) {
|
|||||||
defer tracePrint("typecheckas2", n)(nil)
|
defer tracePrint("typecheckas2", n)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
ls := n.Lhs
|
assign(n, n.Lhs, n.Rhs)
|
||||||
for i1, n1 := range ls {
|
}
|
||||||
// delicate little dance.
|
|
||||||
n1 = Resolve(n1)
|
|
||||||
ls[i1] = n1
|
|
||||||
|
|
||||||
if !ir.DeclaredBy(n1, n) || n1.Name().Ntype != nil {
|
func assign(stmt ir.Node, lhs, rhs []ir.Node) {
|
||||||
ls[i1] = AssignExpr(ls[i1])
|
// delicate little dance.
|
||||||
|
// the definition of lhs may refer to this assignment
|
||||||
|
// as its definition, in which case it will call typecheckas.
|
||||||
|
// in that case, do not call typecheck back, or it will cycle.
|
||||||
|
// if the variable has a type (ntype) then typechecking
|
||||||
|
// will not look at defn, so it is okay (and desirable,
|
||||||
|
// so that the conversion below happens).
|
||||||
|
|
||||||
|
checkLHS := func(i int, typ *types.Type) {
|
||||||
|
lhs[i] = Resolve(lhs[i])
|
||||||
|
if n := lhs[i]; typ != nil && ir.DeclaredBy(n, stmt) && n.Name().Ntype == nil {
|
||||||
|
if typ.Kind() != types.TNIL {
|
||||||
|
n.SetType(defaultType(typ))
|
||||||
|
} else {
|
||||||
|
base.Errorf("use of untyped nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lhs[i].Typecheck() == 0 {
|
||||||
|
lhs[i] = AssignExpr(lhs[i])
|
||||||
|
}
|
||||||
|
checkassign(stmt, lhs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
assignType := func(i int, typ *types.Type) {
|
||||||
|
checkLHS(i, typ)
|
||||||
|
if typ != nil {
|
||||||
|
checkassignto(typ, lhs[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := len(n.Lhs)
|
cr := len(rhs)
|
||||||
cr := len(n.Rhs)
|
if len(rhs) == 1 {
|
||||||
if cl > 1 && cr == 1 {
|
rhs[0] = typecheck(rhs[0], ctxExpr|ctxMultiOK)
|
||||||
n.Rhs[0] = typecheck(n.Rhs[0], ctxExpr|ctxMultiOK)
|
if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() {
|
||||||
|
cr = rtyp.NumFields()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Exprs(n.Rhs)
|
Exprs(rhs)
|
||||||
}
|
|
||||||
checkassignlist(n, n.Lhs)
|
|
||||||
|
|
||||||
var l ir.Node
|
|
||||||
var r ir.Node
|
|
||||||
if cl == cr {
|
|
||||||
// easy
|
|
||||||
ls := n.Lhs
|
|
||||||
rs := n.Rhs
|
|
||||||
for il, nl := range ls {
|
|
||||||
nr := rs[il]
|
|
||||||
if nl.Type() != nil && nr.Type() != nil {
|
|
||||||
rs[il] = AssignConv(nr, nl.Type(), "assignment")
|
|
||||||
}
|
|
||||||
if ir.DeclaredBy(nl, n) && nl.Name().Ntype == nil {
|
|
||||||
rs[il] = DefaultLit(rs[il], nil)
|
|
||||||
nl.SetType(rs[il].Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
|
|
||||||
l = n.Lhs[0]
|
|
||||||
r = n.Rhs[0]
|
|
||||||
|
|
||||||
// x,y,z = f()
|
|
||||||
if cr == 1 {
|
|
||||||
if r.Type() == nil {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
switch r.Op() {
|
|
||||||
case ir.OCALLMETH, ir.OCALLINTER, ir.OCALLFUNC:
|
|
||||||
if !r.Type().IsFuncArgStruct() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cr = r.Type().NumFields()
|
|
||||||
if cr != cl {
|
|
||||||
goto mismatch
|
|
||||||
}
|
|
||||||
r.(*ir.CallExpr).Use = ir.CallUseList
|
|
||||||
n.SetOp(ir.OAS2FUNC)
|
|
||||||
for i, l := range n.Lhs {
|
|
||||||
f := r.Type().Field(i)
|
|
||||||
if f.Type != nil && l.Type() != nil {
|
|
||||||
checkassignto(f.Type, l)
|
|
||||||
}
|
|
||||||
if ir.DeclaredBy(l, n) && l.Name().Ntype == nil {
|
|
||||||
l.SetType(f.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// x, ok = y
|
// x, ok = y
|
||||||
if cl == 2 && cr == 1 {
|
assignOK:
|
||||||
if r.Type() == nil {
|
for len(lhs) == 2 && cr == 1 {
|
||||||
goto out
|
stmt := stmt.(*ir.AssignListStmt)
|
||||||
}
|
r := rhs[0]
|
||||||
|
|
||||||
switch r.Op() {
|
switch r.Op() {
|
||||||
case ir.OINDEXMAP, ir.ORECV, ir.ODOTTYPE:
|
case ir.OINDEXMAP:
|
||||||
switch r.Op() {
|
stmt.SetOp(ir.OAS2MAPR)
|
||||||
case ir.OINDEXMAP:
|
case ir.ORECV:
|
||||||
n.SetOp(ir.OAS2MAPR)
|
stmt.SetOp(ir.OAS2RECV)
|
||||||
case ir.ORECV:
|
case ir.ODOTTYPE:
|
||||||
n.SetOp(ir.OAS2RECV)
|
r := r.(*ir.TypeAssertExpr)
|
||||||
case ir.ODOTTYPE:
|
stmt.SetOp(ir.OAS2DOTTYPE)
|
||||||
r := r.(*ir.TypeAssertExpr)
|
r.SetOp(ir.ODOTTYPE2)
|
||||||
n.SetOp(ir.OAS2DOTTYPE)
|
default:
|
||||||
r.SetOp(ir.ODOTTYPE2)
|
break assignOK
|
||||||
}
|
|
||||||
if l.Type() != nil {
|
|
||||||
checkassignto(r.Type(), l)
|
|
||||||
}
|
|
||||||
if ir.DeclaredBy(l, n) {
|
|
||||||
l.SetType(r.Type())
|
|
||||||
}
|
|
||||||
l := n.Lhs[1]
|
|
||||||
if l.Type() != nil && !l.Type().IsBoolean() {
|
|
||||||
checkassignto(types.Types[types.TBOOL], l)
|
|
||||||
}
|
|
||||||
if ir.DeclaredBy(l, n) && l.Name().Ntype == nil {
|
|
||||||
l.SetType(types.Types[types.TBOOL])
|
|
||||||
}
|
|
||||||
goto out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assignType(0, r.Type())
|
||||||
|
assignType(1, types.UntypedBool)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mismatch:
|
if len(lhs) != cr {
|
||||||
switch r.Op() {
|
if r, ok := rhs[0].(*ir.CallExpr); ok && len(rhs) == 1 {
|
||||||
default:
|
if r.Type() != nil {
|
||||||
base.Errorf("assignment mismatch: %d variables but %d values", cl, cr)
|
base.ErrorfAt(stmt.Pos(), "assignment mismatch: %d variable%s but %v returns %d value%s", len(lhs), plural(len(lhs)), r.X, cr, plural(cr))
|
||||||
case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
|
}
|
||||||
r := r.(*ir.CallExpr)
|
} else {
|
||||||
base.Errorf("assignment mismatch: %d variables but %v returns %d values", cl, r.X, cr)
|
base.ErrorfAt(stmt.Pos(), "assignment mismatch: %d variable%s but %v value%s", len(lhs), plural(len(lhs)), len(rhs), plural(len(rhs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range lhs {
|
||||||
|
checkLHS(i, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// second half of dance
|
// x,y,z = f()
|
||||||
out:
|
if cr > len(rhs) {
|
||||||
n.SetTypecheck(1)
|
stmt := stmt.(*ir.AssignListStmt)
|
||||||
ls = n.Lhs
|
stmt.SetOp(ir.OAS2FUNC)
|
||||||
for i1, n1 := range ls {
|
r := rhs[0].(*ir.CallExpr)
|
||||||
if n1.Typecheck() == 0 {
|
r.Use = ir.CallUseList
|
||||||
ls[i1] = AssignExpr(ls[i1])
|
rtyp := r.Type()
|
||||||
|
|
||||||
|
for i := range lhs {
|
||||||
|
assignType(i, rtyp.Field(i).Type)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, r := range rhs {
|
||||||
|
checkLHS(i, r.Type())
|
||||||
|
if lhs[i].Type() != nil {
|
||||||
|
rhs[i] = AssignConv(r, lhs[i].Type(), "assignment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func plural(n int) string {
|
||||||
|
if n == 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "s"
|
||||||
|
}
|
||||||
|
|
||||||
// tcFor typechecks an OFOR node.
|
// tcFor typechecks an OFOR node.
|
||||||
func tcFor(n *ir.ForStmt) ir.Node {
|
func tcFor(n *ir.ForStmt) ir.Node {
|
||||||
Stmts(n.Init())
|
Stmts(n.Init())
|
||||||
|
@ -1690,6 +1690,11 @@ func checkassignlist(stmt ir.Node, l ir.Nodes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkassignto(src *types.Type, dst ir.Node) {
|
func checkassignto(src *types.Type, dst ir.Node) {
|
||||||
|
// TODO(mdempsky): Handle all untyped types correctly.
|
||||||
|
if src == types.UntypedBool && dst.Type().IsBoolean() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if op, why := assignop(src, dst.Type()); op == ir.OXXX {
|
if op, why := assignop(src, dst.Type()); op == ir.OXXX {
|
||||||
base.Errorf("cannot assign %v to %L in multiple assignment%s", src, dst, why)
|
base.Errorf("cannot assign %v to %L in multiple assignment%s", src, dst, why)
|
||||||
return
|
return
|
||||||
|
@ -8,7 +8,7 @@ package main
|
|||||||
|
|
||||||
var a = twoResults() // ERROR "assignment mismatch: 1 variable but twoResults returns 2 values"
|
var a = twoResults() // ERROR "assignment mismatch: 1 variable but twoResults returns 2 values"
|
||||||
var b, c, d = twoResults() // ERROR "assignment mismatch: 3 variables but twoResults returns 2 values"
|
var b, c, d = twoResults() // ERROR "assignment mismatch: 3 variables but twoResults returns 2 values"
|
||||||
var e, f = oneResult() // ERROR "assignment mismatch: 2 variables but oneResult returns 1 values"
|
var e, f = oneResult() // ERROR "assignment mismatch: 2 variables but oneResult returns 1 value"
|
||||||
|
|
||||||
func twoResults() (int, int) {
|
func twoResults() (int, int) {
|
||||||
return 1, 2
|
return 1, 2
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var a, b = 1 // ERROR "assignment mismatch: 2 variables but 1 values|wrong number of initializations"
|
var a, b = 1 // ERROR "assignment mismatch: 2 variables but 1 value|wrong number of initializations"
|
||||||
_ = 1, 2 // ERROR "assignment mismatch: 1 variables but 2 values|number of variables does not match"
|
_ = 1, 2 // ERROR "assignment mismatch: 1 variable but 2 values|number of variables does not match"
|
||||||
c, d := 1 // ERROR "assignment mismatch: 2 variables but 1 values|wrong number of initializations"
|
c, d := 1 // ERROR "assignment mismatch: 2 variables but 1 value|wrong number of initializations"
|
||||||
e, f := 1, 2, 3 // ERROR "assignment mismatch: 2 variables but 3 values|wrong number of initializations"
|
e, f := 1, 2, 3 // ERROR "assignment mismatch: 2 variables but 3 values|wrong number of initializations"
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ func _() {
|
|||||||
_ = f1() // ok
|
_ = f1() // ok
|
||||||
_, _ = f2() // ok
|
_, _ = f2() // ok
|
||||||
_ = f2() // ERROR "assignment mismatch: 1 variable but f2 returns 2 values"
|
_ = f2() // ERROR "assignment mismatch: 1 variable but f2 returns 2 values"
|
||||||
|
_ = f1(), 0 // ERROR "assignment mismatch: 1 variable but 2 values"
|
||||||
T.M0 // ERROR "T.M0 evaluated but not used"
|
T.M0 // ERROR "T.M0 evaluated but not used"
|
||||||
t.M0 // ERROR "t.M0 evaluated but not used"
|
t.M0 // ERROR "t.M0 evaluated but not used"
|
||||||
cap // ERROR "use of builtin cap not in function call"
|
cap // ERROR "use of builtin cap not in function call"
|
||||||
|
Loading…
Reference in New Issue
Block a user