mirror of
https://github.com/golang/go
synced 2024-11-26 07:38:00 -07:00
cmd/compile: fix various small bugs related to type lists
Fix various small bugs related to delaying transformations due to type params. Most of these relate to the need to delay a transformation when an argument of an expression or statement has a type parameter that has a structural constraint. The structural constraint implies the operation should work, but the transformation can't happen until the actual value of the type parameter is known. - delay transformations for send statements and return statements if any args/values have type params. - similarly, delay transformation of a call where the function arg has type parameters. This is mainly important for the case where the function arg is a pure type parameter, but has a structural constraint that requires it to be a function. Move the setting of n.Use to transformCall(), since we may not know how many return values there are until then, if the function arg is a type parameter. - set the type of unary expressions from the type2 type (as we do with most other expressions), since that works better with expressions with type params. - deal with these delayed transformations in subster.node() and convert the CALL checks to a switch statement. - make sure ir.CurFunc is set properly during stenciling, including closures (needed for transforming return statements during stenciling). New test file typelist.go with tests for these cases. Change-Id: I1b82f949d8cec47d906429209e846f4ebc8ec85e Reviewed-on: https://go-review.googlesource.com/c/go/+/305729 Trust: Dan Scales <danscales@google.com> Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Dan Scales <danscales@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
a95454b6f3
commit
eeadfa2d38
@ -164,7 +164,7 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
|
|||||||
|
|
||||||
case *syntax.Operation:
|
case *syntax.Operation:
|
||||||
if expr.Y == nil {
|
if expr.Y == nil {
|
||||||
return Unary(pos, g.op(expr.Op, unOps[:]), g.expr(expr.X))
|
return Unary(pos, g.typ(typ), g.op(expr.Op, unOps[:]), g.expr(expr.X))
|
||||||
}
|
}
|
||||||
switch op := g.op(expr.Op, binOps[:]); op {
|
switch op := g.op(expr.Op, binOps[:]); op {
|
||||||
case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE:
|
case ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE:
|
||||||
@ -236,7 +236,7 @@ func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.Selecto
|
|||||||
|
|
||||||
if havePtr != wantPtr {
|
if havePtr != wantPtr {
|
||||||
if havePtr {
|
if havePtr {
|
||||||
x = Implicit(Deref(pos, x))
|
x = Implicit(Deref(pos, x.Type().Elem(), x))
|
||||||
} else {
|
} else {
|
||||||
x = Implicit(Addr(pos, x))
|
x = Implicit(Addr(pos, x))
|
||||||
}
|
}
|
||||||
|
@ -149,9 +149,13 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Use = ir.CallUseExpr
|
if fun.Type().HasTParam() {
|
||||||
if fun.Type().NumResults() == 0 {
|
// If the fun arg is or has a type param, don't do any extra
|
||||||
n.Use = ir.CallUseStmt
|
// transformations, since we may not have needed properties yet
|
||||||
|
// (e.g. number of return values, etc). The type param is probably
|
||||||
|
// described by a structural constraint that requires it to be a
|
||||||
|
// certain function type, etc., but we don't want to analyze that.
|
||||||
|
return typed(typ, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fun.Op() == ir.OXDOT {
|
if fun.Op() == ir.OXDOT {
|
||||||
@ -191,9 +195,9 @@ func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) ir.Node {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func Deref(pos src.XPos, x ir.Node) *ir.StarExpr {
|
func Deref(pos src.XPos, typ *types.Type, x ir.Node) *ir.StarExpr {
|
||||||
n := ir.NewStarExpr(pos, x)
|
n := ir.NewStarExpr(pos, x)
|
||||||
typed(x.Type().Elem(), n)
|
typed(typ, n)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,17 +292,22 @@ func Slice(pos src.XPos, typ *types.Type, x, low, high, max ir.Node) ir.Node {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unary(pos src.XPos, op ir.Op, x ir.Node) ir.Node {
|
func Unary(pos src.XPos, typ *types.Type, op ir.Op, x ir.Node) ir.Node {
|
||||||
switch op {
|
switch op {
|
||||||
case ir.OADDR:
|
case ir.OADDR:
|
||||||
return Addr(pos, x)
|
return Addr(pos, x)
|
||||||
case ir.ODEREF:
|
case ir.ODEREF:
|
||||||
return Deref(pos, x)
|
return Deref(pos, typ, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := x.Type()
|
|
||||||
if op == ir.ORECV {
|
if op == ir.ORECV {
|
||||||
typ = typ.Elem()
|
if typ.IsFuncArgStruct() && typ.NumFields() == 2 {
|
||||||
|
// Remove the second boolean type (if provided by type2),
|
||||||
|
// since that works better with the rest of the compiler
|
||||||
|
// (which will add it back in later).
|
||||||
|
assert(typ.Field(1).Type.Kind() == types.TBOOL)
|
||||||
|
typ = typ.Field(0).Type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return typed(typ, ir.NewUnaryExpr(pos, op, x))
|
return typed(typ, ir.NewUnaryExpr(pos, op, x))
|
||||||
}
|
}
|
||||||
|
@ -270,6 +270,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.No
|
|||||||
newf.Nname.Func = newf
|
newf.Nname.Func = newf
|
||||||
newf.Nname.Defn = newf
|
newf.Nname.Defn = newf
|
||||||
newsym.Def = newf.Nname
|
newsym.Def = newf.Nname
|
||||||
|
ir.CurFunc = newf
|
||||||
|
|
||||||
assert(len(tparams) == len(targs))
|
assert(len(tparams) == len(targs))
|
||||||
|
|
||||||
@ -286,7 +287,6 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.No
|
|||||||
for i, n := range gf.Dcl {
|
for i, n := range gf.Dcl {
|
||||||
newf.Dcl[i] = subst.node(n).(*ir.Name)
|
newf.Dcl[i] = subst.node(n).(*ir.Name)
|
||||||
}
|
}
|
||||||
newf.Body = subst.list(gf.Body)
|
|
||||||
|
|
||||||
// Ugly: we have to insert the Name nodes of the parameters/results into
|
// Ugly: we have to insert the Name nodes of the parameters/results into
|
||||||
// the function type. The current function type has no Nname fields set,
|
// the function type. The current function type has no Nname fields set,
|
||||||
@ -305,6 +305,11 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.No
|
|||||||
newf.Nname.SetTypecheck(1)
|
newf.Nname.SetTypecheck(1)
|
||||||
// TODO(danscales) - remove later, but avoid confusion for now.
|
// TODO(danscales) - remove later, but avoid confusion for now.
|
||||||
newf.Pragma = ir.Noinline
|
newf.Pragma = ir.Noinline
|
||||||
|
|
||||||
|
// Make sure name/type of newf is set before substituting the body.
|
||||||
|
newf.Body = subst.list(gf.Body)
|
||||||
|
ir.CurFunc = nil
|
||||||
|
|
||||||
return newf
|
return newf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,6 +401,12 @@ func (subst *subster) node(n ir.Node) ir.Node {
|
|||||||
as := m.(*ir.AssignOpStmt)
|
as := m.(*ir.AssignOpStmt)
|
||||||
transformCheckAssign(as, as.X)
|
transformCheckAssign(as, as.X)
|
||||||
|
|
||||||
|
case ir.ORETURN:
|
||||||
|
transformReturn(m.(*ir.ReturnStmt))
|
||||||
|
|
||||||
|
case ir.OSEND:
|
||||||
|
transformSend(m.(*ir.SendStmt))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
base.Fatalf("Unexpected node with Typecheck() == 3")
|
base.Fatalf("Unexpected node with Typecheck() == 3")
|
||||||
}
|
}
|
||||||
@ -435,38 +446,55 @@ func (subst *subster) node(n ir.Node) ir.Node {
|
|||||||
|
|
||||||
case ir.OCALL:
|
case ir.OCALL:
|
||||||
call := m.(*ir.CallExpr)
|
call := m.(*ir.CallExpr)
|
||||||
if call.X.Op() == ir.OTYPE {
|
switch call.X.Op() {
|
||||||
|
case ir.OTYPE:
|
||||||
// Transform the conversion, now that we know the
|
// Transform the conversion, now that we know the
|
||||||
// type argument.
|
// type argument.
|
||||||
m = transformConvCall(m.(*ir.CallExpr))
|
m = transformConvCall(m.(*ir.CallExpr))
|
||||||
} else if call.X.Op() == ir.OCALLPART {
|
|
||||||
|
case ir.OCALLPART:
|
||||||
// Redo the transformation of OXDOT, now that we
|
// Redo the transformation of OXDOT, now that we
|
||||||
// know the method value is being called. Then
|
// know the method value is being called. Then
|
||||||
// transform the call.
|
// transform the call.
|
||||||
call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT)
|
call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT)
|
||||||
transformDot(call.X.(*ir.SelectorExpr), true)
|
transformDot(call.X.(*ir.SelectorExpr), true)
|
||||||
transformCall(call)
|
transformCall(call)
|
||||||
} else if call.X.Op() == ir.ODOT || call.X.Op() == ir.ODOTPTR {
|
|
||||||
|
case ir.ODOT, ir.ODOTPTR:
|
||||||
// An OXDOT for a generic receiver was resolved to
|
// An OXDOT for a generic receiver was resolved to
|
||||||
// an access to a field which has a function
|
// an access to a field which has a function
|
||||||
// value. Transform the call to that function, now
|
// value. Transform the call to that function, now
|
||||||
// that the OXDOT was resolved.
|
// that the OXDOT was resolved.
|
||||||
transformCall(call)
|
transformCall(call)
|
||||||
} else if name := call.X.Name(); name != nil {
|
|
||||||
switch name.BuiltinOp {
|
case ir.ONAME:
|
||||||
case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND:
|
name := call.X.Name()
|
||||||
// Transform these builtins now that we
|
if name.BuiltinOp != ir.OXXX {
|
||||||
// know the type of the args.
|
switch name.BuiltinOp {
|
||||||
m = transformBuiltin(call)
|
case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND:
|
||||||
default:
|
// Transform these builtins now that we
|
||||||
base.FatalfAt(call.Pos(), "Unexpected builtin op")
|
// know the type of the args.
|
||||||
|
m = transformBuiltin(call)
|
||||||
|
default:
|
||||||
|
base.FatalfAt(call.Pos(), "Unexpected builtin op")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is the case of a function value that was a
|
||||||
|
// type parameter (implied to be a function via a
|
||||||
|
// structural constraint) which is now resolved.
|
||||||
|
transformCall(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if call.X.Op() != ir.OFUNCINST {
|
case ir.OCLOSURE:
|
||||||
// A call with an OFUNCINST will get typechecked
|
transformCall(call)
|
||||||
|
|
||||||
|
case ir.OFUNCINST:
|
||||||
|
// A call with an OFUNCINST will get transformed
|
||||||
// in stencil() once we have created & attached the
|
// in stencil() once we have created & attached the
|
||||||
// instantiation to be called.
|
// instantiation to be called.
|
||||||
base.FatalfAt(call.Pos(), "Expecting OCALLPART or OTYPE or OFUNCINST or builtin with CALL")
|
|
||||||
|
default:
|
||||||
|
base.FatalfAt(call.Pos(), fmt.Sprintf("Unexpected op with CALL during stenciling: %v", call.X.Op()))
|
||||||
}
|
}
|
||||||
|
|
||||||
case ir.OCLOSURE:
|
case ir.OCLOSURE:
|
||||||
@ -491,17 +519,22 @@ func (subst *subster) node(n ir.Node) ir.Node {
|
|||||||
newfn.OClosure = m.(*ir.ClosureExpr)
|
newfn.OClosure = m.(*ir.ClosureExpr)
|
||||||
|
|
||||||
saveNewf := subst.newf
|
saveNewf := subst.newf
|
||||||
|
ir.CurFunc = newfn
|
||||||
subst.newf = newfn
|
subst.newf = newfn
|
||||||
newfn.Dcl = subst.namelist(oldfn.Dcl)
|
newfn.Dcl = subst.namelist(oldfn.Dcl)
|
||||||
newfn.ClosureVars = subst.namelist(oldfn.ClosureVars)
|
newfn.ClosureVars = subst.namelist(oldfn.ClosureVars)
|
||||||
newfn.Body = subst.list(oldfn.Body)
|
|
||||||
subst.newf = saveNewf
|
|
||||||
|
|
||||||
// Set Ntype for now to be compatible with later parts of compiler
|
// Set Ntype for now to be compatible with later parts of compiler
|
||||||
newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
|
newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
|
||||||
typed(subst.typ(oldfn.Nname.Type()), newfn.Nname)
|
typed(subst.typ(oldfn.Nname.Type()), newfn.Nname)
|
||||||
typed(newfn.Nname.Type(), m)
|
typed(newfn.Nname.Type(), m)
|
||||||
newfn.SetTypecheck(1)
|
newfn.SetTypecheck(1)
|
||||||
|
|
||||||
|
// Make sure type of closure function is set before doing body.
|
||||||
|
newfn.Body = subst.list(oldfn.Body)
|
||||||
|
subst.newf = saveNewf
|
||||||
|
ir.CurFunc = saveNewf
|
||||||
|
|
||||||
subst.g.target.Decls = append(subst.g.target.Decls, newfn)
|
subst.g.target.Decls = append(subst.g.target.Decls, newfn)
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
|
@ -42,6 +42,12 @@ func (g *irgen) stmt(stmt syntax.Stmt) ir.Node {
|
|||||||
return x
|
return x
|
||||||
case *syntax.SendStmt:
|
case *syntax.SendStmt:
|
||||||
n := ir.NewSendStmt(g.pos(stmt), g.expr(stmt.Chan), g.expr(stmt.Value))
|
n := ir.NewSendStmt(g.pos(stmt), g.expr(stmt.Chan), g.expr(stmt.Value))
|
||||||
|
if n.Chan.Type().HasTParam() || n.Value.Type().HasTParam() {
|
||||||
|
// Delay transforming the send if the channel or value
|
||||||
|
// have a type param.
|
||||||
|
n.SetTypecheck(3)
|
||||||
|
return n
|
||||||
|
}
|
||||||
transformSend(n)
|
transformSend(n)
|
||||||
n.SetTypecheck(1)
|
n.SetTypecheck(1)
|
||||||
return n
|
return n
|
||||||
@ -118,6 +124,14 @@ func (g *irgen) stmt(stmt syntax.Stmt) ir.Node {
|
|||||||
return ir.NewGoDeferStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), callOps[:]), g.expr(stmt.Call))
|
return ir.NewGoDeferStmt(g.pos(stmt), g.tokOp(int(stmt.Tok), callOps[:]), g.expr(stmt.Call))
|
||||||
case *syntax.ReturnStmt:
|
case *syntax.ReturnStmt:
|
||||||
n := ir.NewReturnStmt(g.pos(stmt), g.exprList(stmt.Results))
|
n := ir.NewReturnStmt(g.pos(stmt), g.exprList(stmt.Results))
|
||||||
|
for _, e := range n.Results {
|
||||||
|
if e.Type().HasTParam() {
|
||||||
|
// Delay transforming the return statement if any of the
|
||||||
|
// return values have a type param.
|
||||||
|
n.SetTypecheck(3)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
transformReturn(n)
|
transformReturn(n)
|
||||||
n.SetTypecheck(1)
|
n.SetTypecheck(1)
|
||||||
return n
|
return n
|
||||||
|
@ -144,8 +144,10 @@ func transformCall(n *ir.CallExpr) {
|
|||||||
|
|
||||||
typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args)
|
typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args)
|
||||||
if t.NumResults() == 0 {
|
if t.NumResults() == 0 {
|
||||||
|
n.Use = ir.CallUseStmt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
n.Use = ir.CallUseExpr
|
||||||
if t.NumResults() == 1 {
|
if t.NumResults() == 1 {
|
||||||
n.SetType(l.Type().Results().Field(0).Type)
|
n.SetType(l.Type().Results().Field(0).Type)
|
||||||
|
|
||||||
|
64
test/typeparam/typelist.go
Normal file
64
test/typeparam/typelist.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// compile -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.
|
||||||
|
|
||||||
|
// This file tests type lists & structural constraints.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
// Assignability of an unnamed pointer type to a type parameter that
|
||||||
|
// has a matching underlying type.
|
||||||
|
func _[T interface{}, PT interface{type *T}] (x T) PT {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing of generic types containing type parameters in their type list:
|
||||||
|
func at[T interface{ type []E }, E any](x T, i int) E {
|
||||||
|
return x[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic type inside a function acts like a named type. Its underlying
|
||||||
|
// type is itself, its "operational type" is defined by the type list in
|
||||||
|
// the tybe bound, if any.
|
||||||
|
func _[T interface{type int}](x T) {
|
||||||
|
type myint int
|
||||||
|
var _ int = int(x)
|
||||||
|
var _ T = 42
|
||||||
|
var _ T = T(myint(42))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing a generic type which has a structural contraints to be an array.
|
||||||
|
func _[T interface { type [10]int }](x T) {
|
||||||
|
_ = x[9] // ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dereference of a generic type which has a structural contraint to be a pointer.
|
||||||
|
func _[T interface{ type *int }](p T) int {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel send and receive on a generic type which has a structural constraint to
|
||||||
|
// be a channel.
|
||||||
|
func _[T interface{ type chan int }](ch T) int {
|
||||||
|
// This would deadlock if executed (but ok for a compile test)
|
||||||
|
ch <- 0
|
||||||
|
return <- ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling of a generic type which has a structural constraint to be a function.
|
||||||
|
func _[T interface{ type func() }](f T) {
|
||||||
|
f()
|
||||||
|
go f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same, but function has a parameter and return value.
|
||||||
|
func _[T interface{ type func(string) int }](f T) int {
|
||||||
|
return f("hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map access of a generic type which has a structural constraint to be a map.
|
||||||
|
func _[V any, T interface { type map[string]V }](p T) V {
|
||||||
|
return p["test"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user