]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: fix various small bugs related to type lists
authorDan Scales <danscales@google.com>
Mon, 29 Mar 2021 15:28:01 +0000 (08:28 -0700)
committerDan Scales <danscales@google.com>
Tue, 30 Mar 2021 03:05:45 +0000 (03:05 +0000)
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>
src/cmd/compile/internal/noder/expr.go
src/cmd/compile/internal/noder/helpers.go
src/cmd/compile/internal/noder/stencil.go
src/cmd/compile/internal/noder/stmt.go
src/cmd/compile/internal/noder/transform.go
test/typeparam/typelist.go [new file with mode: 0644]

index 9db03a90053c361fa3b654d738c01d99e6dd030d..ecdc7c74b1c2864ed7b82cdc647887c06d2c9a28 100644 (file)
@@ -164,7 +164,7 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
 
        case *syntax.Operation:
                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 {
                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 {
-                                       x = Implicit(Deref(pos, x))
+                                       x = Implicit(Deref(pos, x.Type().Elem(), x))
                                } else {
                                        x = Implicit(Addr(pos, x))
                                }
index cb8052c0cb70a01df7ccc5fa3e74a0922c5042ef..e5a6dbcb013567ce2eb59554c94287cfe81c5d82 100644 (file)
@@ -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().NumResults() == 0 {
-               n.Use = ir.CallUseStmt
+       if fun.Type().HasTParam() {
+               // If the fun arg is or has a type param, don't do any extra
+               // 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 {
@@ -191,9 +195,9 @@ func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) ir.Node {
        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)
-       typed(x.Type().Elem(), n)
+       typed(typ, 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
 }
 
-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 {
        case ir.OADDR:
                return Addr(pos, x)
        case ir.ODEREF:
-               return Deref(pos, x)
+               return Deref(pos, typ, x)
        }
 
-       typ := x.Type()
        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))
 }
index 45864763d460d7f5dda0a82c524c6cf626178a41..8dcc9d811e2545270ecac75d252626bdde16d981 100644 (file)
@@ -270,6 +270,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.No
        newf.Nname.Func = newf
        newf.Nname.Defn = newf
        newsym.Def = newf.Nname
+       ir.CurFunc = newf
 
        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 {
                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
        // 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)
        // TODO(danscales) - remove later, but avoid confusion for now.
        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
 }
 
@@ -396,6 +401,12 @@ func (subst *subster) node(n ir.Node) ir.Node {
                                        as := m.(*ir.AssignOpStmt)
                                        transformCheckAssign(as, as.X)
 
+                               case ir.ORETURN:
+                                       transformReturn(m.(*ir.ReturnStmt))
+
+                               case ir.OSEND:
+                                       transformSend(m.(*ir.SendStmt))
+
                                default:
                                        base.Fatalf("Unexpected node with Typecheck() == 3")
                                }
@@ -435,38 +446,55 @@ func (subst *subster) node(n ir.Node) ir.Node {
 
                case ir.OCALL:
                        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
                                // type argument.
                                m = transformConvCall(m.(*ir.CallExpr))
-                       } else if call.X.Op() == ir.OCALLPART {
+
+                       case ir.OCALLPART:
                                // Redo the transformation of OXDOT, now that we
                                // know the method value is being called. Then
                                // transform the call.
                                call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT)
                                transformDot(call.X.(*ir.SelectorExpr), true)
                                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 access to a field which has a function
                                // value. Transform the call to that function, now
                                // that the OXDOT was resolved.
                                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:
-                                       // Transform these builtins now that we
-                                       // know the type of the args.
-                                       m = transformBuiltin(call)
-                               default:
-                                       base.FatalfAt(call.Pos(), "Unexpected builtin op")
+
+                       case ir.ONAME:
+                               name := call.X.Name()
+                               if name.BuiltinOp != ir.OXXX {
+                                       switch name.BuiltinOp {
+                                       case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OLEN, ir.OCAP, ir.OAPPEND:
+                                               // Transform these builtins now that we
+                                               // 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 {
-                               // A call with an OFUNCINST will get typechecked
+                       case ir.OCLOSURE:
+                               transformCall(call)
+
+                       case ir.OFUNCINST:
+                               // A call with an OFUNCINST will get transformed
                                // in stencil() once we have created & attached the
                                // 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:
@@ -491,17 +519,22 @@ func (subst *subster) node(n ir.Node) ir.Node {
                        newfn.OClosure = m.(*ir.ClosureExpr)
 
                        saveNewf := subst.newf
+                       ir.CurFunc = newfn
                        subst.newf = newfn
                        newfn.Dcl = subst.namelist(oldfn.Dcl)
                        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
                        newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
                        typed(subst.typ(oldfn.Nname.Type()), newfn.Nname)
                        typed(newfn.Nname.Type(), m)
                        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)
                }
                return m
index f85496be40bd05286607d414e1a166f7f88e6166..32a1483b4aabeb2fddeb646fc897ccf216cc52cb 100644 (file)
@@ -42,6 +42,12 @@ func (g *irgen) stmt(stmt syntax.Stmt) ir.Node {
                return x
        case *syntax.SendStmt:
                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)
                n.SetTypecheck(1)
                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))
        case *syntax.ReturnStmt:
                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)
                n.SetTypecheck(1)
                return n
index 489a535231dc9afdbccc61c1e7e890ef355b162e..7f926dc70ae573ac592b93f04f0f092fd05c1350 100644 (file)
@@ -144,8 +144,10 @@ func transformCall(n *ir.CallExpr) {
 
        typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args)
        if t.NumResults() == 0 {
+               n.Use = ir.CallUseStmt
                return
        }
+       n.Use = ir.CallUseExpr
        if t.NumResults() == 1 {
                n.SetType(l.Type().Results().Field(0).Type)
 
diff --git a/test/typeparam/typelist.go b/test/typeparam/typelist.go
new file mode 100644 (file)
index 0000000..4ff3ce2
--- /dev/null
@@ -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"]
+}