From e7e0995cba1f57622f593ebd27d4d1a651666c4b Mon Sep 17 00:00:00 2001 From: Dan Scales Date: Tue, 23 Mar 2021 10:19:11 -0700 Subject: [PATCH] cmd/compile: create/use noder2 transform functions for more node types Pull out the transformation part of the typechecking functions for: - assignment statements - return statements - send statements - select statements - type conversions - normal function/method calls - index operations The transform functions are like the original typechecking functions, but with all code removed related to: - Detecting compile-time errors (already done by types2) - Setting the actual type of existing nodes (already done based on info from types2) - Dealing with untyped constants Moved all the transformation functions to a separate file, transform.go. Continuing with the same pattern, we delay transforming a node if it has any type params in its args, marking it with a typecheck flag of 3, and do the actual transformation during stenciling. Assignment statements are tricky, since their transformation must be delayed if any of the left or right-hands-sides are delayed. Still to do are: - selector expressions (OXDOT) - composite literal expressions (OCOMPLIT) - builtin function calls Change-Id: Ie608cadbbc69b40db0067a5536cf707dd974aacc Reviewed-on: https://go-review.googlesource.com/c/go/+/304049 Run-TryBot: Dan Scales TryBot-Result: Go Bot Trust: Dan Scales Trust: Robert Griesemer Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/noder/helpers.go | 129 +---- src/cmd/compile/internal/noder/stencil.go | 62 ++- src/cmd/compile/internal/noder/stmt.go | 77 ++- src/cmd/compile/internal/noder/transform.go | 523 ++++++++++++++++++++ src/cmd/compile/internal/noder/validate.go | 12 +- src/cmd/compile/internal/typecheck/expr.go | 2 +- src/cmd/compile/internal/typecheck/subr.go | 2 +- 7 files changed, 650 insertions(+), 157 deletions(-) create mode 100644 src/cmd/compile/internal/noder/transform.go diff --git a/src/cmd/compile/internal/noder/helpers.go b/src/cmd/compile/internal/noder/helpers.go index e4a1a54fe86..82428daa4ae 100644 --- a/src/cmd/compile/internal/noder/helpers.go +++ b/src/cmd/compile/internal/noder/helpers.go @@ -67,31 +67,6 @@ func Assert(pos src.XPos, x ir.Node, typ *types.Type) ir.Node { return typed(typ, ir.NewTypeAssertExpr(pos, x, nil)) } -// transformAdd transforms an addition operation (currently just addition of -// strings). Equivalent to the "binary operators" case in typecheck.typecheck1. -func transformAdd(n *ir.BinaryExpr) ir.Node { - l := n.X - if l.Type().IsString() { - 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}) - } - r := n.Y - if r.Op() == ir.OADDSTR { - r := r.(*ir.AddStringExpr) - add.List.Append(r.List.Take()...) - } else { - add.List.Append(r) - } - add.SetType(l.Type()) - return add - } - return n -} - func Binary(pos src.XPos, op ir.Op, typ *types.Type, x, y ir.Node) ir.Node { switch op { case ir.OANDAND, ir.OOROR: @@ -124,7 +99,9 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) // the type. return typed(typ, n) } - return typecheck.Expr(n) + n1 := transformConvCall(n) + n1.SetTypecheck(1) + return n1 } if fun, ok := fun.(*ir.Name); ok && fun.BuiltinOp != 0 { @@ -181,6 +158,11 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) } } + n.Use = ir.CallUseExpr + if fun.Type().NumResults() == 0 { + n.Use = ir.CallUseStmt + } + if fun.Op() == ir.OXDOT { if !fun.(*ir.SelectorExpr).X.Type().HasTParam() { base.FatalfAt(pos, "Expecting type param receiver in %v", fun) @@ -192,63 +174,18 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) return n } if fun.Op() != ir.OFUNCINST { - // If no type params, do normal typechecking, since we're - // still missing some things done by tcCall (mainly - // typecheckaste/assignconvfn - implementing assignability of args - // to params). This will convert OCALL to OCALLFUNC. - typecheck.Call(n) + // If no type params, do the normal call transformations. This + // will convert OCALL to OCALLFUNC. + transformCall(n) + typed(typ, n) return n } // Leave the op as OCALL, which indicates the call still needs typechecking. - n.Use = ir.CallUseExpr - if fun.Type().NumResults() == 0 { - n.Use = ir.CallUseStmt - } typed(typ, n) return n } -// transformCompare transforms a compare operation (currently just equals/not -// equals). Equivalent to the "comparison operators" case in -// typecheck.typecheck1, including tcArith. -func transformCompare(n *ir.BinaryExpr) { - if (n.Op() == ir.OEQ || n.Op() == ir.ONE) && !types.Identical(n.X.Type(), n.Y.Type()) { - // Comparison is okay as long as one side is assignable to the - // other. The only allowed case where the conversion is not CONVNOP is - // "concrete == interface". In that case, check comparability of - // the concrete type. The conversion allocates, so only do it if - // the concrete type is huge. - l, r := n.X, n.Y - lt, rt := l.Type(), r.Type() - converted := false - if rt.Kind() != types.TBLANK { - aop, _ := typecheck.Assignop(lt, rt) - if aop != ir.OXXX { - types.CalcSize(lt) - if rt.IsInterface() == lt.IsInterface() || lt.Width >= 1<<16 { - l = ir.NewConvExpr(base.Pos, aop, rt, l) - l.SetTypecheck(1) - } - - converted = true - } - } - - if !converted && lt.Kind() != types.TBLANK { - aop, _ := typecheck.Assignop(rt, lt) - if aop != ir.OXXX { - types.CalcSize(rt) - if rt.IsInterface() == lt.IsInterface() || rt.Width >= 1<<16 { - r = ir.NewConvExpr(base.Pos, aop, lt, r) - r.SetTypecheck(1) - } - } - } - n.X, n.Y = l, r - } -} - func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) ir.Node { n := ir.NewBinaryExpr(pos, op, x, y) if x.Type().HasTParam() || y.Type().HasTParam() { @@ -330,38 +267,16 @@ func method(typ *types.Type, index int) *types.Field { func Index(pos src.XPos, typ *types.Type, x, index ir.Node) ir.Node { n := ir.NewIndexExpr(pos, x, index) - // TODO(danscales): Temporary fix. Need to separate out the - // transformations done by the old typechecker (in tcIndex()), to be - // called here or after stenciling. - if x.Type().HasTParam() && x.Type().Kind() != types.TMAP && - x.Type().Kind() != types.TSLICE && x.Type().Kind() != types.TARRAY { - // Old typechecker will complain if arg is not obviously a slice/array/map. - typed(typ, n) + if x.Type().HasTParam() { + // transformIndex needs to know exact type + n.SetType(typ) + n.SetTypecheck(3) return n } - return typecheck.Expr(n) -} - -// transformSlice transforms a slice operation. Equivalent to typecheck.tcSlice. -func transformSlice(n *ir.SliceExpr) { - l := n.X - if l.Type().IsArray() { - addr := typecheck.NodAddr(n.X) - addr.SetImplicit(true) - typed(types.NewPtr(n.X.Type()), addr) - n.X = addr - l = addr - } - t := l.Type() - if t.IsString() { - n.SetOp(ir.OSLICESTR) - } else if t.IsPtr() && t.Elem().IsArray() { - if n.Op().IsSlice3() { - n.SetOp(ir.OSLICE3ARR) - } else { - n.SetOp(ir.OSLICEARR) - } - } + typed(typ, n) + // transformIndex will modify n.Type() for OINDEXMAP. + transformIndex(n) + return n } func Slice(pos src.XPos, typ *types.Type, x, low, high, max ir.Node) ir.Node { @@ -399,7 +314,7 @@ func Unary(pos src.XPos, op ir.Op, x ir.Node) ir.Node { var one = constant.MakeInt64(1) -func IncDec(pos src.XPos, op ir.Op, x ir.Node) ir.Node { - x = typecheck.AssignExpr(x) +func IncDec(pos src.XPos, op ir.Op, x ir.Node) *ir.AssignOpStmt { + assert(x.Type() != nil) return ir.NewAssignOpStmt(pos, op, x, typecheck.DefaultLit(ir.NewBasicLit(pos, one), x.Type())) } diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 1b76bb27c55..ba01f0424b9 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -95,10 +95,9 @@ func (g *irgen) stencil() { copy(withRecv[1:], call.Args) call.Args = withRecv } - // Do the typechecking of the Call now, which changes OCALL + // Transform the Call now, which changes OCALL // to OCALLFUNC and does typecheckaste/assignconvfn. - call.SetTypecheck(0) - typecheck.Call(call) + transformCall(call) modified = true }) @@ -372,16 +371,36 @@ func (subst *subster) node(n ir.Node) ir.Node { // their instantiated type was known. if typecheck.IsCmp(x.Op()) { transformCompare(m.(*ir.BinaryExpr)) - m.SetTypecheck(1) - } else if x.Op() == ir.OSLICE || x.Op() == ir.OSLICE3 { - transformSlice(m.(*ir.SliceExpr)) - m.SetTypecheck(1) - } else if x.Op() == ir.OADD { - m = transformAdd(m.(*ir.BinaryExpr)) - m.SetTypecheck(1) } else { - base.Fatalf("Unexpected node with Typecheck() == 3") + switch x.Op() { + case ir.OSLICE: + case ir.OSLICE3: + transformSlice(m.(*ir.SliceExpr)) + + case ir.OADD: + m = transformAdd(m.(*ir.BinaryExpr)) + + case ir.OINDEX: + transformIndex(m.(*ir.IndexExpr)) + + case ir.OAS2: + as2 := m.(*ir.AssignListStmt) + transformAssign(as2, as2.Lhs, as2.Rhs) + + case ir.OAS: + as := m.(*ir.AssignStmt) + lhs, rhs := []ir.Node{as.X}, []ir.Node{as.Y} + transformAssign(as, lhs, rhs) + + case ir.OASOP: + as := m.(*ir.AssignOpStmt) + transformCheckAssign(as, as.X) + + default: + base.Fatalf("Unexpected node with Typecheck() == 3") + } } + m.SetTypecheck(1) } switch x.Op() { @@ -415,26 +434,25 @@ func (subst *subster) node(n ir.Node) ir.Node { case ir.OCALL: call := m.(*ir.CallExpr) if call.X.Op() == ir.OTYPE { - // Do typechecking on a conversion, now that we - // know the type argument. - m.SetTypecheck(0) - m = typecheck.Expr(m) + // Transform the conversion, now that we know the + // type argument. + m = transformConvCall(m.(*ir.CallExpr)) + m.SetTypecheck(1) } else if call.X.Op() == ir.OCALLPART { - // Redo the typechecking, now that we know the method - // value is being called. + // Redo the typechecking of OXDOT, now that we + // know the method value is being called. Then + // transform the call. call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT) call.X.SetTypecheck(0) call.X.SetType(nil) typecheck.Callee(call.X) - call.SetTypecheck(0) - typecheck.Call(call) + transformCall(call) } else if call.X.Op() == ir.ODOT || call.X.Op() == ir.ODOTPTR { // An OXDOT for a generic receiver was resolved to // an access to a field which has a function - // value. Typecheck the call to that function, now + // value. Transform the call to that function, now // that the OXDOT was resolved. - call.SetTypecheck(0) - typecheck.Call(call) + transformCall(call) } else if name := call.X.Name(); name != nil { switch name.BuiltinOp { case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND: diff --git a/src/cmd/compile/internal/noder/stmt.go b/src/cmd/compile/internal/noder/stmt.go index 31c6bfe5c8a..f85496be40b 100644 --- a/src/cmd/compile/internal/noder/stmt.go +++ b/src/cmd/compile/internal/noder/stmt.go @@ -27,15 +27,6 @@ func (g *irgen) stmts(stmts []syntax.Stmt) []ir.Node { } func (g *irgen) stmt(stmt syntax.Stmt) ir.Node { - // TODO(mdempsky): Remove dependency on typecheck. - n := g.stmt0(stmt) - if n != nil { - n.SetTypecheck(1) - } - return n -} - -func (g *irgen) stmt0(stmt syntax.Stmt) ir.Node { switch stmt := stmt.(type) { case nil, *syntax.EmptyStmt: return nil @@ -51,35 +42,75 @@ func (g *irgen) stmt0(stmt syntax.Stmt) ir.Node { return x case *syntax.SendStmt: n := ir.NewSendStmt(g.pos(stmt), g.expr(stmt.Chan), g.expr(stmt.Value)) - // Need to do the AssignConv() in tcSend(). - return typecheck.Stmt(n) + transformSend(n) + n.SetTypecheck(1) + return n case *syntax.DeclStmt: return ir.NewBlockStmt(g.pos(stmt), g.decls(stmt.DeclList)) case *syntax.AssignStmt: if stmt.Op != 0 && stmt.Op != syntax.Def { op := g.op(stmt.Op, binOps[:]) - // May need to insert ConvExpr nodes on the args in tcArith + var n *ir.AssignOpStmt if stmt.Rhs == nil { - return typecheck.Stmt(IncDec(g.pos(stmt), op, g.expr(stmt.Lhs))) + n = IncDec(g.pos(stmt), op, g.expr(stmt.Lhs)) + } else { + n = ir.NewAssignOpStmt(g.pos(stmt), op, g.expr(stmt.Lhs), g.expr(stmt.Rhs)) } - return typecheck.Stmt(ir.NewAssignOpStmt(g.pos(stmt), op, g.expr(stmt.Lhs), g.expr(stmt.Rhs))) + if n.X.Typecheck() == 3 { + n.SetTypecheck(3) + return n + } + transformAsOp(n) + n.SetTypecheck(1) + return n } names, lhs := g.assignList(stmt.Lhs, stmt.Op == syntax.Def) rhs := g.exprList(stmt.Rhs) + // We must delay transforming the assign statement if any of the + // lhs or rhs nodes are also delayed, since transformAssign needs + // to know the types of the left and right sides in various cases. + delay := false + for _, e := range lhs { + if e.Typecheck() == 3 { + delay = true + break + } + } + for _, e := range rhs { + if e.Typecheck() == 3 { + delay = true + break + } + } + if len(lhs) == 1 && len(rhs) == 1 { n := ir.NewAssignStmt(g.pos(stmt), lhs[0], rhs[0]) n.Def = initDefn(n, names) - // Need to set Assigned in checkassign for maps - return typecheck.Stmt(n) + + if delay { + n.SetTypecheck(3) + return n + } + + lhs, rhs := []ir.Node{n.X}, []ir.Node{n.Y} + transformAssign(n, lhs, rhs) + n.X, n.Y = lhs[0], rhs[0] + n.SetTypecheck(1) + return n } n := ir.NewAssignListStmt(g.pos(stmt), ir.OAS2, lhs, rhs) n.Def = initDefn(n, names) - // Need to do tcAssignList(). - return typecheck.Stmt(n) + if delay { + n.SetTypecheck(3) + return n + } + transformAssign(n, n.Lhs, n.Rhs) + n.SetTypecheck(1) + return n case *syntax.BranchStmt: return ir.NewBranchStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), branchOps[:]), g.name(stmt.Label)) @@ -87,16 +118,18 @@ func (g *irgen) stmt0(stmt syntax.Stmt) ir.Node { return ir.NewGoDeferStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), callOps[:]), g.expr(stmt.Call)) case *syntax.ReturnStmt: n := ir.NewReturnStmt(g.pos(stmt), g.exprList(stmt.Results)) - // Need to do typecheckaste() for multiple return values - return typecheck.Stmt(n) + transformReturn(n) + n.SetTypecheck(1) + return n case *syntax.IfStmt: return g.ifStmt(stmt) case *syntax.ForStmt: return g.forStmt(stmt) case *syntax.SelectStmt: n := g.selectStmt(stmt) - // Need to convert assignments to OSELRECV2 in tcSelect() - return typecheck.Stmt(n) + transformSelect(n.(*ir.SelectStmt)) + n.SetTypecheck(1) + return n case *syntax.SwitchStmt: return g.switchStmt(stmt) diff --git a/src/cmd/compile/internal/noder/transform.go b/src/cmd/compile/internal/noder/transform.go new file mode 100644 index 00000000000..e90d374d0fc --- /dev/null +++ b/src/cmd/compile/internal/noder/transform.go @@ -0,0 +1,523 @@ +// Copyright 2021 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. + +// This file contains transformation functions on nodes, which are the +// transformations that the typecheck package does that are distinct from the +// typechecking functionality. These transform functions are pared-down copies of +// the original typechecking functions, with all code removed that is related to: +// +// - Detecting compile-time errors (already done by types2) +// - Setting the actual type of existing nodes (already done based on +// type info from types2) +// - Dealing with untyped constants (which types2 has already resolved) + +package noder + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "go/constant" +) + +// Transformation functions for expressions + +// transformAdd transforms an addition operation (currently just addition of +// strings). Corresponds to the "binary operators" case in typecheck.typecheck1. +func transformAdd(n *ir.BinaryExpr) ir.Node { + l := n.X + if l.Type().IsString() { + 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}) + } + r := n.Y + if r.Op() == ir.OADDSTR { + r := r.(*ir.AddStringExpr) + add.List.Append(r.List.Take()...) + } else { + add.List.Append(r) + } + add.SetType(l.Type()) + return add + } + return n +} + +// Corresponds to typecheck.stringtoruneslit. +func stringtoruneslit(n *ir.ConvExpr) ir.Node { + if n.X.Op() != ir.OLITERAL || n.X.Val().Kind() != constant.String { + base.Fatalf("stringtoarraylit %v", n) + } + + var l []ir.Node + i := 0 + for _, r := range ir.StringVal(n.X) { + l = append(l, ir.NewKeyExpr(base.Pos, ir.NewInt(int64(i)), ir.NewInt(int64(r)))) + i++ + } + + nn := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(n.Type()), nil) + nn.List = l + // Need to transform the OCOMPLIT. + // TODO(danscales): update this when we have written transformCompLit() + return typecheck.Expr(nn) +} + +// transformConv transforms an OCONV node as needed, based on the types involved, +// etc. Corresponds to typecheck.tcConv. +func transformConv(n *ir.ConvExpr) ir.Node { + t := n.X.Type() + op, _ := typecheck.Convertop(n.X.Op() == ir.OLITERAL, t, n.Type()) + assert(op != ir.OXXX) + n.SetOp(op) + switch n.Op() { + case ir.OCONVNOP: + if t.Kind() == n.Type().Kind() { + switch t.Kind() { + case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128: + // Floating point casts imply rounding and + // so the conversion must be kept. + n.SetOp(ir.OCONV) + } + } + + // Do not convert to []byte literal. See CL 125796. + // Generated code and compiler memory footprint is better without it. + case ir.OSTR2BYTES: + // ok + + case ir.OSTR2RUNES: + if n.X.Op() == ir.OLITERAL { + return stringtoruneslit(n) + } + } + return n +} + +// transformConvCall transforms a conversion call. Corresponds to the OTYPE part of +// typecheck.tcCall. +func transformConvCall(n *ir.CallExpr) ir.Node { + arg := n.Args[0] + n1 := ir.NewConvExpr(n.Pos(), ir.OCONV, nil, arg) + n1.SetType(n.X.Type()) + return transformConv(n1) +} + +// transformCall transforms a normal function/method call. Corresponds to last half +// (non-conversion, non-builtin part) of typecheck.tcCall. +func transformCall(n *ir.CallExpr) { + transformArgs(n) + l := n.X + t := l.Type() + + switch l.Op() { + case ir.ODOTINTER: + n.SetOp(ir.OCALLINTER) + + case ir.ODOTMETH: + l := l.(*ir.SelectorExpr) + n.SetOp(ir.OCALLMETH) + + tp := t.Recv().Type + + if l.X == nil || !types.Identical(l.X.Type(), tp) { + base.Fatalf("method receiver") + } + + default: + n.SetOp(ir.OCALLFUNC) + } + + typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args) + if t.NumResults() == 0 { + return + } + if t.NumResults() == 1 { + n.SetType(l.Type().Results().Field(0).Type) + + if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.ONAME { + if sym := n.X.(*ir.Name).Sym(); types.IsRuntimePkg(sym.Pkg) && sym.Name == "getg" { + // Emit code for runtime.getg() directly instead of calling function. + // Most such rewrites (for example the similar one for math.Sqrt) should be done in walk, + // so that the ordering pass can make sure to preserve the semantics of the original code + // (in particular, the exact time of the function call) by introducing temporaries. + // In this case, we know getg() always returns the same result within a given function + // and we want to avoid the temporaries, so we do the rewrite earlier than is typical. + n.SetOp(ir.OGETG) + } + } + return + } +} + +// transformCompare transforms a compare operation (currently just equals/not +// equals). Corresponds to the "comparison operators" case in +// typecheck.typecheck1, including tcArith. +func transformCompare(n *ir.BinaryExpr) { + if (n.Op() == ir.OEQ || n.Op() == ir.ONE) && !types.Identical(n.X.Type(), n.Y.Type()) { + // Comparison is okay as long as one side is assignable to the + // other. The only allowed case where the conversion is not CONVNOP is + // "concrete == interface". In that case, check comparability of + // the concrete type. The conversion allocates, so only do it if + // the concrete type is huge. + l, r := n.X, n.Y + lt, rt := l.Type(), r.Type() + converted := false + if rt.Kind() != types.TBLANK { + aop, _ := typecheck.Assignop(lt, rt) + if aop != ir.OXXX { + types.CalcSize(lt) + if rt.IsInterface() == lt.IsInterface() || lt.Width >= 1<<16 { + l = ir.NewConvExpr(base.Pos, aop, rt, l) + l.SetTypecheck(1) + } + + converted = true + } + } + + if !converted && lt.Kind() != types.TBLANK { + aop, _ := typecheck.Assignop(rt, lt) + if aop != ir.OXXX { + types.CalcSize(rt) + if rt.IsInterface() == lt.IsInterface() || rt.Width >= 1<<16 { + r = ir.NewConvExpr(base.Pos, aop, lt, r) + r.SetTypecheck(1) + } + } + } + n.X, n.Y = l, r + } +} + +// Corresponds to typecheck.implicitstar. +func implicitstar(n ir.Node) ir.Node { + // insert implicit * if needed for fixed array + t := n.Type() + if !t.IsPtr() { + return n + } + t = t.Elem() + if !t.IsArray() { + return n + } + star := ir.NewStarExpr(base.Pos, n) + star.SetImplicit(true) + return typed(t, star) +} + +// transformIndex transforms an index operation. Corresponds to typecheck.tcIndex. +func transformIndex(n *ir.IndexExpr) { + n.X = implicitstar(n.X) + l := n.X + t := l.Type() + if t.Kind() == types.TMAP { + n.Index = typecheck.AssignConv(n.Index, t.Key(), "map index") + n.SetOp(ir.OINDEXMAP) + // Set type to just the map value, not (value, bool). This is + // different from types2, but fits the later stages of the + // compiler better. + n.SetType(t.Elem()) + n.Assigned = false + } +} + +// transformSlice transforms a slice operation. Corresponds to typecheck.tcSlice. +func transformSlice(n *ir.SliceExpr) { + l := n.X + if l.Type().IsArray() { + addr := typecheck.NodAddr(n.X) + addr.SetImplicit(true) + typed(types.NewPtr(n.X.Type()), addr) + n.X = addr + l = addr + } + t := l.Type() + if t.IsString() { + n.SetOp(ir.OSLICESTR) + } else if t.IsPtr() && t.Elem().IsArray() { + if n.Op().IsSlice3() { + n.SetOp(ir.OSLICE3ARR) + } else { + n.SetOp(ir.OSLICEARR) + } + } +} + +// Transformation functions for statements + +// Corresponds to typecheck.checkassign. +func transformCheckAssign(stmt ir.Node, n ir.Node) { + if n.Op() == ir.OINDEXMAP { + n := n.(*ir.IndexExpr) + n.Assigned = true + return + } +} + +// Corresponds to typecheck.assign. +func transformAssign(stmt ir.Node, lhs, rhs []ir.Node) { + checkLHS := func(i int, typ *types.Type) { + transformCheckAssign(stmt, lhs[i]) + } + + cr := len(rhs) + if len(rhs) == 1 { + if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() { + cr = rtyp.NumFields() + } + } + + // x, ok = y +assignOK: + for len(lhs) == 2 && cr == 1 { + stmt := stmt.(*ir.AssignListStmt) + r := rhs[0] + + switch r.Op() { + 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 + } + checkLHS(0, r.Type()) + checkLHS(1, types.UntypedBool) + return + } + + if len(lhs) != cr { + for i := range lhs { + checkLHS(i, nil) + } + return + } + + // 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 { + checkLHS(i, rtyp.Field(i).Type) + } + return + } + + for i, r := range rhs { + checkLHS(i, r.Type()) + if lhs[i].Type() != nil { + rhs[i] = assignconvfn(r, lhs[i].Type()) + } + } +} + +// Corresponds to typecheck.typecheckargs. +func transformArgs(n ir.InitNode) { + var list []ir.Node + switch n := n.(type) { + default: + base.Fatalf("typecheckargs %+v", n.Op()) + case *ir.CallExpr: + list = n.Args + if n.IsDDD { + return + } + case *ir.ReturnStmt: + list = n.Results + } + if len(list) != 1 { + return + } + + t := list[0].Type() + if t == nil || !t.IsFuncArgStruct() { + return + } + + // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...). + + // Save n as n.Orig for fmt.go. + if ir.Orig(n) == n { + n.(ir.OrigNode).SetOrig(ir.SepCopy(n)) + } + + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) + as.Rhs.Append(list...) + + // If we're outside of function context, then this call will + // be executed during the generated init function. However, + // init.go hasn't yet created it. Instead, associate the + // temporary variables with InitTodoFunc for now, and init.go + // will reassociate them later when it's appropriate. + static := ir.CurFunc == nil + if static { + ir.CurFunc = typecheck.InitTodoFunc + } + list = nil + for _, f := range t.FieldSlice() { + t := typecheck.Temp(f.Type) + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t)) + as.Lhs.Append(t) + list = append(list, t) + } + if static { + ir.CurFunc = nil + } + + switch n := n.(type) { + case *ir.CallExpr: + n.Args = list + case *ir.ReturnStmt: + n.Results = list + } + + transformAssign(as, as.Lhs, as.Rhs) + as.SetTypecheck(1) + n.PtrInit().Append(as) +} + +// assignconvfn converts node n for assignment to type t. Corresponds to +// typecheck.assignconvfn. +func assignconvfn(n ir.Node, t *types.Type) ir.Node { + if t.Kind() == types.TBLANK { + return n + } + + if types.Identical(n.Type(), t) { + return n + } + + op, _ := typecheck.Assignop(n.Type(), t) + + r := ir.NewConvExpr(base.Pos, op, t, n) + r.SetTypecheck(1) + r.SetImplicit(true) + return r +} + +// Corresponds to typecheck.typecheckaste. +func typecheckaste(op ir.Op, call ir.Node, isddd bool, tstruct *types.Type, nl ir.Nodes) { + var t *types.Type + var i int + + lno := base.Pos + defer func() { base.Pos = lno }() + + var n ir.Node + if len(nl) == 1 { + n = nl[0] + } + + i = 0 + for _, tl := range tstruct.Fields().Slice() { + t = tl.Type + if tl.IsDDD() { + if isddd { + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t) + } + return + } + + // TODO(mdempsky): Make into ... call with implicit slice. + for ; i < len(nl); i++ { + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t.Elem()) + } + } + return + } + + n = nl[i] + ir.SetPos(n) + if n.Type() != nil { + nl[i] = assignconvfn(n, t) + } + i++ + } +} + +// transformSend transforms a send statement, converting the value to appropriate +// type for the channel, as needed. Corresponds of typecheck.tcSend. +func transformSend(n *ir.SendStmt) { + n.Value = assignconvfn(n.Value, n.Chan.Type().Elem()) +} + +// transformReturn transforms a return node, by doing the needed assignments and +// any necessary conversions. Corresponds to typecheck.tcReturn() +func transformReturn(rs *ir.ReturnStmt) { + transformArgs(rs) + nl := rs.Results + if ir.HasNamedResults(ir.CurFunc) && len(nl) == 0 { + return + } + + typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), nl) +} + +// transformSelect transforms a select node, creating an assignment list as needed +// for each case. Corresponds to typecheck.tcSelect(). +func transformSelect(sel *ir.SelectStmt) { + for _, ncase := range sel.Cases { + if ncase.Comm != nil { + n := ncase.Comm + oselrecv2 := func(dst, recv ir.Node, def bool) { + n := ir.NewAssignListStmt(n.Pos(), ir.OSELRECV2, []ir.Node{dst, ir.BlankNode}, []ir.Node{recv}) + n.Def = def + n.SetTypecheck(1) + ncase.Comm = n + } + switch n.Op() { + case ir.OAS: + // convert x = <-c into x, _ = <-c + // remove implicit conversions; the eventual assignment + // will reintroduce them. + n := n.(*ir.AssignStmt) + if r := n.Y; r.Op() == ir.OCONVNOP || r.Op() == ir.OCONVIFACE { + r := r.(*ir.ConvExpr) + if r.Implicit() { + n.Y = r.X + } + } + oselrecv2(n.X, n.Y, n.Def) + + case ir.OAS2RECV: + n := n.(*ir.AssignListStmt) + n.SetOp(ir.OSELRECV2) + + case ir.ORECV: + // convert <-c into _, _ = <-c + n := n.(*ir.UnaryExpr) + oselrecv2(ir.BlankNode, n, false) + + case ir.OSEND: + break + } + } + } +} + +// transformAsOp transforms an AssignOp statement. Corresponds to OASOP case in +// typecheck1. +func transformAsOp(n *ir.AssignOpStmt) { + transformCheckAssign(n, n.X) +} diff --git a/src/cmd/compile/internal/noder/validate.go b/src/cmd/compile/internal/noder/validate.go index 3341de8e04f..b926222c89c 100644 --- a/src/cmd/compile/internal/noder/validate.go +++ b/src/cmd/compile/internal/noder/validate.go @@ -23,10 +23,14 @@ func (g *irgen) match(t1 *types.Type, t2 types2.Type, hasOK bool) bool { } if hasOK { - // For has-ok values, types2 represents the expression's type as - // a 2-element tuple, whereas ir just uses the first type and - // infers that the second type is boolean. - return tuple.Len() == 2 && types.Identical(t1, g.typ(tuple.At(0).Type())) + // For has-ok values, types2 represents the expression's type as a + // 2-element tuple, whereas ir just uses the first type and infers + // that the second type is boolean. Must match either, since we + // sometimes delay the transformation to the ir form. + if tuple.Len() == 2 && types.Identical(t1, g.typ(tuple.At(0).Type())) { + return true + } + return types.Identical(t1, g.typ(t2)) } if t1 == nil || tuple == nil { diff --git a/src/cmd/compile/internal/typecheck/expr.go b/src/cmd/compile/internal/typecheck/expr.go index fb39709686b..7ab1670a451 100644 --- a/src/cmd/compile/internal/typecheck/expr.go +++ b/src/cmd/compile/internal/typecheck/expr.go @@ -419,7 +419,7 @@ func tcConv(n *ir.ConvExpr) ir.Node { n.SetType(nil) return n } - op, why := convertop(n.X.Op() == ir.OLITERAL, t, n.Type()) + op, why := Convertop(n.X.Op() == ir.OLITERAL, t, n.Type()) if op == ir.OXXX { if !n.Diag() && !n.Type().Broke() && !n.X.Diag() { base.Errorf("cannot convert %L to type %v%s", n.X, n.Type(), why) diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index e58ef9fb050..daf5cd72a27 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -460,7 +460,7 @@ func Assignop(src, dst *types.Type) (ir.Op, string) { // If not, return OXXX. In this case, the string return parameter may // hold a reason why. In all other cases, it'll be the empty string. // srcConstant indicates whether the value of type src is a constant. -func convertop(srcConstant bool, src, dst *types.Type) (ir.Op, string) { +func Convertop(srcConstant bool, src, dst *types.Type) (ir.Op, string) { if src == dst { return ir.OCONVNOP, "" }