aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/typecheck/stmt.go
diff options
context:
space:
mode:
authorMatthew Dempsky <mdempsky@google.com>2020-12-23 16:14:59 -0800
committerMatthew Dempsky <mdempsky@google.com>2020-12-25 09:18:20 +0000
commit396b6c2e7c5c368c67e71824471d4f2d48f5c128 (patch)
tree222c878df5c18a10dfe33a0538f313fb946592c8 /src/cmd/compile/internal/typecheck/stmt.go
parente24d2f3d0513961441904afdf71cafe7808c0be9 (diff)
downloadgo-396b6c2e7c5c368c67e71824471d4f2d48f5c128.tar.gz
go-396b6c2e7c5c368c67e71824471d4f2d48f5c128.zip
[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>
Diffstat (limited to 'src/cmd/compile/internal/typecheck/stmt.go')
-rw-r--r--src/cmd/compile/internal/typecheck/stmt.go235
1 files changed, 95 insertions, 140 deletions
diff --git a/src/cmd/compile/internal/typecheck/stmt.go b/src/cmd/compile/internal/typecheck/stmt.go
index fe9ef400bb..7e74b730bc 100644
--- a/src/cmd/compile/internal/typecheck/stmt.go
+++ b/src/cmd/compile/internal/typecheck/stmt.go
@@ -93,47 +93,16 @@ func tcAssign(n *ir.AssignStmt) {
defer tracePrint("typecheckas", n)(nil)
}
- // delicate little dance.
- // 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 {
+ if n.Y == nil {
n.X = AssignExpr(n.X)
+ return
}
- // Use ctxMultiOK so we can emit an "N variables but M values" error
- // to be consistent with typecheckas2 (#26616).
- n.Y = typecheck(n.Y, ctxExpr|ctxMultiOK)
- 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 {
- 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)
+ lhs, rhs := []ir.Node{n.X}, []ir.Node{n.Y}
+ assign(n, lhs, rhs)
+ n.X, n.Y = lhs[0], rhs[0]
- if n.X.Typecheck() == 0 {
- n.X = AssignExpr(n.X)
- }
+ // TODO(mdempsky): This seems out of place.
if !ir.IsBlank(n.X) {
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)
}
- ls := n.Lhs
- for i1, n1 := range ls {
- // delicate little dance.
- n1 = Resolve(n1)
- ls[i1] = n1
+ assign(n, n.Lhs, n.Rhs)
+}
+
+func assign(stmt ir.Node, lhs, rhs []ir.Node) {
+ // 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).
- if !ir.DeclaredBy(n1, n) || n1.Name().Ntype != nil {
- ls[i1] = AssignExpr(ls[i1])
+ 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])
}
- cl := len(n.Lhs)
- cr := len(n.Rhs)
- if cl > 1 && cr == 1 {
- n.Rhs[0] = typecheck(n.Rhs[0], ctxExpr|ctxMultiOK)
- } else {
- Exprs(n.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())
- }
+ assignType := func(i int, typ *types.Type) {
+ checkLHS(i, typ)
+ if typ != nil {
+ checkassignto(typ, lhs[i])
}
+ }
- goto out
+ cr := len(rhs)
+ if len(rhs) == 1 {
+ rhs[0] = typecheck(rhs[0], ctxExpr|ctxMultiOK)
+ if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() {
+ cr = rtyp.NumFields()
+ }
+ } else {
+ Exprs(rhs)
}
- l = n.Lhs[0]
- r = n.Rhs[0]
+ // x, ok = y
+assignOK:
+ for len(lhs) == 2 && cr == 1 {
+ stmt := stmt.(*ir.AssignListStmt)
+ r := 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
+ case ir.OINDEXMAP:
+ stmt.SetOp(ir.OAS2MAPR)
+ case ir.ORECV:
+ stmt.SetOp(ir.OAS2RECV)
+ case ir.ODOTTYPE:
+ r := r.(*ir.TypeAssertExpr)
+ stmt.SetOp(ir.OAS2DOTTYPE)
+ r.SetOp(ir.ODOTTYPE2)
+ default:
+ break assignOK
}
+
+ assignType(0, r.Type())
+ assignType(1, types.UntypedBool)
+ return
}
- // x, ok = y
- if cl == 2 && cr == 1 {
- if r.Type() == nil {
- goto out
- }
- switch r.Op() {
- case ir.OINDEXMAP, ir.ORECV, ir.ODOTTYPE:
- switch r.Op() {
- case ir.OINDEXMAP:
- n.SetOp(ir.OAS2MAPR)
- case ir.ORECV:
- n.SetOp(ir.OAS2RECV)
- case ir.ODOTTYPE:
- r := r.(*ir.TypeAssertExpr)
- n.SetOp(ir.OAS2DOTTYPE)
- r.SetOp(ir.ODOTTYPE2)
+ if len(lhs) != cr {
+ if r, ok := rhs[0].(*ir.CallExpr); ok && len(rhs) == 1 {
+ if r.Type() != nil {
+ 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))
}
- 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
+ } else {
+ 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
}
-mismatch:
- switch r.Op() {
- default:
- base.Errorf("assignment mismatch: %d variables but %d values", cl, cr)
- case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
- r := r.(*ir.CallExpr)
- base.Errorf("assignment mismatch: %d variables but %v returns %d values", cl, r.X, cr)
+ // x,y,z = f()
+ if cr > len(rhs) {
+ stmt := stmt.(*ir.AssignListStmt)
+ stmt.SetOp(ir.OAS2FUNC)
+ r := rhs[0].(*ir.CallExpr)
+ r.Use = ir.CallUseList
+ rtyp := r.Type()
+
+ for i := range lhs {
+ assignType(i, rtyp.Field(i).Type)
+ }
+ return
}
- // second half of dance
-out:
- n.SetTypecheck(1)
- ls = n.Lhs
- for i1, n1 := range ls {
- if n1.Typecheck() == 0 {
- ls[i1] = AssignExpr(ls[i1])
+ 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.
func tcFor(n *ir.ForStmt) ir.Node {
Stmts(n.Init())