[dev.typeparams] cmd/compile: call transformArgs before early typecheckaste in noder

In the cases where we do an early call to typecheckaste() in noder to
expose CONVIFACE nodes, we need a preceding call to transformArgs().
This is needed to allow typecheckaste() to run correctly, in the case of
f(g()), where g has multiple return values.

I also cleaned up the code a bit and commented the code in Call(), and
we do the call to typecheckaste() in several more cases.

In stencil.go:stencil(), I moved the transformCall earlier for the
OCALLMETH/ODOTMETH case, just as I did in my previous CL for
OCALL/OFUNCINST. By doing this, transformArgs no longer needs to deal
with the extra dictionary args. Therefore, I was able to simply
transformArgs() to look like typecheckargs() again, and make use of
RewriteMultiValue directly.

Updates #47514

Change-Id: I49eb82ac05707e50c2e2fb03e39458a70491d406
Reviewed-on: https://go-review.googlesource.com/c/go/+/340531
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Dan Scales 2021-08-06 13:24:14 -07:00
parent ca3c6985cd
commit 9f4d6a8359
5 changed files with 55 additions and 91 deletions

View file

@ -171,39 +171,34 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool)
} }
} }
if fun.Type().HasTParam() { if fun.Type().HasTParam() || fun.Op() == ir.OXDOT || fun.Op() == ir.OFUNCINST {
// If the fun arg is or has a type param, we can't do all the // If the fun arg is or has a type param, we can't do all the
// transformations, since we may not have needed properties yet. // transformations, since we may not have needed properties yet
// (e.g. number of return values, etc). However, if we do have the // (e.g. number of return values, etc). The same applies if a fun
// function type (even though it is parameterized), then can add in // which is an XDOT could not be transformed yet because of a generic
// any needed CONVIFACE nodes. We can't do anything if fun is a type // type in the X of the selector expression.
// param (which is probably described by a structural constraint) //
// A function instantiation (even if fully concrete) shouldn't be
// transformed yet, because we need to add the dictionary during the
// transformation.
//
// However, if we have a function type (even though it is
// parameterized), then we can add in any needed CONVIFACE nodes via
// typecheckaste(). We need to call transformArgs() to deal first
// with the f(g(()) case where g returns multiple return values. We
// can't do anything if fun is a type param (which is probably
// described by a structural constraint)
if fun.Type().Kind() == types.TFUNC { if fun.Type().Kind() == types.TFUNC {
transformArgs(n)
typecheckaste(ir.OCALL, fun, n.IsDDD, fun.Type().Params(), n.Args, true) typecheckaste(ir.OCALL, fun, n.IsDDD, fun.Type().Params(), n.Args, true)
} }
return typed(typ, n) return typed(typ, n)
} }
if fun.Op() == ir.OXDOT { // If no type params, do the normal call transformations. This
if !fun.(*ir.SelectorExpr).X.Type().HasTParam() { // will convert OCALL to OCALLFUNC.
base.FatalfAt(pos, "Expecting type param receiver in %v", fun)
}
// For methods called in a generic function, don't do any extra
// transformations. We will do those later when we create the
// instantiated function and have the correct receiver type.
typed(typ, n)
return n
}
if fun.Op() != ir.OFUNCINST {
// If no type params, do the normal call transformations. This
// will convert OCALL to OCALLFUNC.
typed(typ, n)
transformCall(n)
return n
}
// Leave the op as OCALL, which indicates the call still needs typechecking.
typed(typ, n) typed(typ, n)
transformCall(n)
return n return n
} }

View file

@ -161,18 +161,21 @@ func (g *irgen) stencil() {
} }
} }
// Transform the Call now, which changes OCALL
// to OCALLFUNC and does typecheckaste/assignconvfn.
transformCall(call)
st := g.getInstantiation(gf, targs, true) st := g.getInstantiation(gf, targs, true)
dictValue, usingSubdict := g.getDictOrSubdict(declInfo, n, gf, targs, true) dictValue, usingSubdict := g.getDictOrSubdict(declInfo, n, gf, targs, true)
// We have to be using a subdictionary, since this is // We have to be using a subdictionary, since this is
// a generic method call. // a generic method call.
assert(usingSubdict) assert(usingSubdict)
call.SetOp(ir.OCALL) // Transform to a function call, by appending the
// dictionary and the receiver to the args.
call.SetOp(ir.OCALLFUNC)
call.X = st.Nname call.X = st.Nname
call.Args.Prepend(dictValue, meth.X) call.Args.Prepend(dictValue, meth.X)
// Transform the Call now, which changes OCALL
// to OCALLFUNC and does typecheckaste/assignconvfn.
transformCall(call)
modified = true modified = true
} }
}) })

View file

@ -129,6 +129,7 @@ func (g *irgen) stmt(stmt syntax.Stmt) ir.Node {
// Delay transforming the return statement if any of the // Delay transforming the return statement if any of the
// return values have a type param. // return values have a type param.
if !ir.HasNamedResults(ir.CurFunc) { if !ir.HasNamedResults(ir.CurFunc) {
transformArgs(n)
// But add CONVIFACE nodes where needed if // But add CONVIFACE nodes where needed if
// any of the return values have interface type. // any of the return values have interface type.
typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), n.Results, true) typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), n.Results, true)

View file

@ -365,7 +365,7 @@ assignOK:
} }
} }
// Corresponds to, but slightly more general than, typecheck.typecheckargs. // Corresponds to typecheck.typecheckargs. Really just deals with multi-value calls.
func transformArgs(n ir.InitNode) { func transformArgs(n ir.InitNode) {
var list []ir.Node var list []ir.Node
switch n := n.(type) { switch n := n.(type) {
@ -379,76 +379,22 @@ func transformArgs(n ir.InitNode) {
case *ir.ReturnStmt: case *ir.ReturnStmt:
list = n.Results list = n.Results
} }
if len(list) != 1 {
// Look to see if we have any multi-return functions as arguments.
extra := 0
for _, arg := range list {
t := arg.Type()
if t.IsFuncArgStruct() {
num := t.Fields().Len()
if num <= 1 {
base.Fatalf("multi-return type with only %d parts", num)
}
extra += num - 1
}
}
// If not, nothing to do.
if extra == 0 {
return return
} }
// Rewrite f(..., g(), ...) into t1, ..., tN = g(); f(..., t1, ..., tN, ...). t := list[0].Type()
if t == nil || !t.IsFuncArgStruct() {
return
}
// Save n as n.Orig for fmt.go. // Save n as n.Orig for fmt.go.
if ir.Orig(n) == n { if ir.Orig(n) == n {
n.(ir.OrigNode).SetOrig(ir.SepCopy(n)) n.(ir.OrigNode).SetOrig(ir.SepCopy(n))
} }
// If we're outside of function context, then this call will // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...).
// be executed during the generated init function. However, typecheck.RewriteMultiValueCall(n, list[0])
// 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
}
// Expand multi-return function calls.
// The spec only allows a multi-return function as an argument
// if it is the only argument. This code must handle calls to
// stenciled generic functions which have extra arguments
// (like the dictionary) so it must handle a slightly more general
// cases, like f(n, g()) where g is multi-return.
newList := make([]ir.Node, 0, len(list)+extra)
for _, arg := range list {
t := arg.Type()
if t.IsFuncArgStruct() {
as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, []ir.Node{arg})
for _, f := range t.FieldSlice() {
t := typecheck.Temp(f.Type)
as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t))
as.Lhs.Append(t)
newList = append(newList, t)
}
transformAssign(as, as.Lhs, as.Rhs)
as.SetTypecheck(1)
n.PtrInit().Append(as)
} else {
newList = append(newList, arg)
}
}
if static {
ir.CurFunc = nil
}
switch n := n.(type) {
case *ir.CallExpr:
n.Args = newList
case *ir.ReturnStmt:
n.Results = newList
}
} }
// assignconvfn converts node n for assignment to type t. Corresponds to // assignconvfn converts node n for assignment to type t. Corresponds to

View file

@ -0,0 +1,19 @@
// run -gcflags=-G=3
// 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.
package main
func Do[T any](do func() (T, string)) {
_ = func() (T, string) {
return do()
}
}
func main() {
Do[int](func() (int, string) {
return 3, "3"
})
}