case *syntax.CallExpr:
fun := g.expr(expr.Fun)
-
- // The key for the Inferred map is the CallExpr (if inferring
- // types required the function arguments) or the IndexExpr below
- // (if types could be inferred without the function arguments).
- if inferred, ok := g.info.Inferred[expr]; ok && inferred.TArgs.Len() > 0 {
- // This is the case where inferring types required the
- // types of the function arguments.
- targs := make([]ir.Node, inferred.TArgs.Len())
- for i := range targs {
- targs[i] = ir.TypeNode(g.typ(inferred.TArgs.At(i)))
- }
- if fun.Op() == ir.OFUNCINST {
- if len(fun.(*ir.InstExpr).Targs) < len(targs) {
- // Replace explicit type args with the full list that
- // includes the additional inferred type args.
- // Substitute the type args for the type params in
- // the generic function's type.
- fun.(*ir.InstExpr).Targs = targs
- newt := g.substType(fun.(*ir.InstExpr).X.Type(), fun.(*ir.InstExpr).X.Type().TParams(), targs)
- typed(newt, fun)
- }
- } else {
- // Create a function instantiation here, given there
- // are only inferred type args (e.g. min(5,6), where
- // min is a generic function). Substitute the type
- // args for the type params in the generic function's
- // type.
- inst := ir.NewInstExpr(pos, ir.OFUNCINST, fun, targs)
- newt := g.substType(fun.Type(), fun.Type().TParams(), targs)
- typed(newt, inst)
- fun = inst
- }
-
- }
return Call(pos, g.typ(typ), fun, g.exprs(expr.ArgList), expr.HasDots)
case *syntax.IndexExpr:
- var targs []ir.Node
-
- if inferred, ok := g.info.Inferred[expr]; ok && inferred.TArgs.Len() > 0 {
- // This is the partial type inference case where the types
- // can be inferred from other type arguments without using
- // the types of the function arguments.
- targs = make([]ir.Node, inferred.TArgs.Len())
- for i := range targs {
- targs[i] = ir.TypeNode(g.typ(inferred.TArgs.At(i)))
- }
- } else if _, ok := expr.Index.(*syntax.ListExpr); ok {
- targs = g.exprList(expr.Index)
- } else {
- index := g.expr(expr.Index)
- if index.Op() != ir.OTYPE {
+ args := unpackListExpr(expr.Index)
+ if len(args) == 1 {
+ tv, ok := g.info.Types[args[0]]
+ assert(ok)
+ if tv.IsValue() {
// This is just a normal index expression
- n := Index(pos, g.typ(typ), g.expr(expr.X), index)
+ n := Index(pos, g.typ(typ), g.expr(expr.X), g.expr(args[0]))
if !g.delayTransform() {
// transformIndex will modify n.Type() for OINDEXMAP.
transformIndex(n)
}
return n
}
- // This is generic function instantiation with a single type
- targs = []ir.Node{index}
- }
- // This is a generic function instantiation (e.g. min[int]).
- // Generic type instantiation is handled in the type
- // section of expr() above (using g.typ).
- x := g.expr(expr.X)
- if x.Op() != ir.ONAME || x.Type().Kind() != types.TFUNC {
- panic("Incorrect argument for generic func instantiation")
}
- n := ir.NewInstExpr(pos, ir.OFUNCINST, x, targs)
- newt := g.typ(typ)
- // Substitute the type args for the type params in the uninstantiated
- // function's type. If there aren't enough type args, then the rest
- // will be inferred at the call node, so don't try the substitution yet.
- if x.Type().TParams().NumFields() == len(targs) {
- newt = g.substType(g.typ(typ), x.Type().TParams(), targs)
- }
- typed(newt, n)
- return n
+
+ // expr.Index is a list of type args, so we ignore it, since types2 has
+ // already provided this info with the Info.Instances map.
+ return g.expr(expr.X)
case *syntax.SelectorExpr:
// Qualified identifier.
Selections: make(map[*syntax.SelectorExpr]*types2.Selection),
Implicits: make(map[syntax.Node]types2.Object),
Scopes: make(map[syntax.Node]*types2.Scope),
- Inferred: make(map[syntax.Expr]types2.Inferred),
+ Instances: make(map[*syntax.Name]types2.Instance),
// expand as needed
}
return g.obj(obj), obj
}
-// use returns the Name node associated with the use of name. The returned node
-// will have the correct type and be marked as typechecked.
-func (g *irgen) use(name *syntax.Name) *ir.Name {
+// use returns the Name or InstExpr node associated with the use of name,
+// possibly instantiated by type arguments. The returned node will have
+// the correct type and be marked as typechecked.
+func (g *irgen) use(name *syntax.Name) ir.Node {
obj2, ok := g.info.Uses[name]
if !ok {
base.FatalfAt(g.pos(name), "unknown name %v", name)
obj.SetTypecheck(1)
obj.SetType(obj.Defn.Type())
}
+
+ if obj.Class == ir.PFUNC {
+ if inst, ok := g.info.Instances[name]; ok {
+ // This is the case where inferring types required the
+ // types of the function arguments.
+ targs := make([]ir.Node, inst.TypeArgs.Len())
+ for i := range targs {
+ targs[i] = ir.TypeNode(g.typ(inst.TypeArgs.At(i)))
+ }
+ typ := g.substType(obj.Type(), obj.Type().TParams(), targs)
+ return typed(typ, ir.NewInstExpr(g.pos(name), ir.OFUNCINST, obj, targs))
+ }
+ }
+
return obj
}
w.typ(typ.Elem())
case *types2.Signature:
- assert(typ.TypeParams() == nil)
+ base.Assertf(typ.TypeParams() == nil, "unexpected type params: %v", typ)
w.code(typeSignature)
w.signature(typ)
func (w *writer) expr(expr syntax.Expr) {
expr = unparen(expr) // skip parens; unneeded after typecheck
- obj, targs := lookupObj(w.p.info, expr)
+ obj, inst := lookupObj(w.p.info, expr)
+ targs := inst.TypeArgs
if tv, ok := w.p.info.Types[expr]; ok {
// TODO(mdempsky): Be more judicious about which types are marked as "needed".
- w.needType(tv.Type)
+ if inst.Type != nil {
+ w.needType(inst.Type)
+ } else {
+ w.needType(tv.Type)
+ }
if tv.IsType() {
w.code(exprType)
}
}
- if inf, ok := w.p.info.Inferred[expr]; ok {
- obj, _ := lookupObj(w.p.info, expr.Fun)
- assert(obj != nil)
-
- // As if w.expr(expr.Fun), but using inf.TArgs instead.
- w.code(exprName)
- w.obj(obj, inf.TArgs)
- } else {
- w.expr(expr.Fun)
- }
+ w.expr(expr.Fun)
w.bool(false) // not a method call (i.e., normal function call)
}
}
// lookupObj returns the object that expr refers to, if any. If expr
-// is an explicit instantiation of a generic object, then the type
-// arguments are returned as well.
-func lookupObj(info *types2.Info, expr syntax.Expr) (obj types2.Object, targs *types2.TypeList) {
+// is an explicit instantiation of a generic object, then the instance
+// object is returned as well.
+func lookupObj(info *types2.Info, expr syntax.Expr) (obj types2.Object, inst types2.Instance) {
if index, ok := expr.(*syntax.IndexExpr); ok {
- if inf, ok := info.Inferred[index]; ok {
- targs = inf.TArgs
- } else {
- args := unpackListExpr(index.Index)
-
- if len(args) == 1 {
- tv, ok := info.Types[args[0]]
- assert(ok)
- if tv.IsValue() {
- return // normal index expression
- }
- }
-
- list := make([]types2.Type, len(args))
- for i, arg := range args {
- tv, ok := info.Types[arg]
- assert(ok)
- assert(tv.IsType())
- list[i] = tv.Type
+ args := unpackListExpr(index.Index)
+ if len(args) == 1 {
+ tv, ok := info.Types[args[0]]
+ assert(ok)
+ if tv.IsValue() {
+ return // normal index expression
}
- targs = types2.NewTypeList(list)
}
expr = index.X
}
if name, ok := expr.(*syntax.Name); ok {
- obj, _ = info.Uses[name]
+ obj = info.Uses[name]
+ inst = info.Instances[name]
}
return
}
// qualified identifiers are collected in the Uses map.
Types map[syntax.Expr]TypeAndValue
- // Inferred maps calls of parameterized functions that use
- // type inference to the inferred type arguments and signature
- // of the function called. The recorded "call" expression may be
- // an *ast.CallExpr (as in f(x)), or an *ast.IndexExpr (s in f[T]).
- Inferred map[syntax.Expr]Inferred
+ // Instances maps identifiers denoting parameterized types or functions to
+ // their type arguments and instantiated type.
+ //
+ // For example, Instances will map the identifier for 'T' in the type
+ // instantiation T[int, string] to the type arguments [int, string] and
+ // resulting instantiated *Named type. Given a parameterized function
+ // func F[A any](A), Instances will map the identifier for 'F' in the call
+ // expression F(int(1)) to the inferred type arguments [int], and resulting
+ // instantiated *Signature.
+ //
+ // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs
+ // results in an equivalent of Instances[id].Type.
+ Instances map[*syntax.Name]Instance
// Defs maps identifiers to the objects they define (including
// package names, dots "." of dot-imports, and blank "_" identifiers).
return tv.mode == commaok || tv.mode == mapindex
}
-// Inferred reports the inferred type arguments and signature
-// for a parameterized function call that uses type inference.
-type Inferred struct {
- TArgs *TypeList
- Sig *Signature
+// Instance reports the type arguments and instantiated type for type and
+// function instantiations. For type instantiations, Type will be of dynamic
+// type *Named. For function instantiations, Type will be of dynamic type
+// *Signature.
+type Instance struct {
+ TypeArgs *TypeList
+ Type Type
}
// An Initializer describes a package-level variable, or a list of variables in case
}
}
-func TestInferredInfo(t *testing.T) {
+func TestInstanceInfo(t *testing.T) {
var tests = []struct {
src string
- fun string
+ name string
targs []string
- sig string
+ typ string
}{
{genericPkg + `p0; func f[T any](T) {}; func _() { f(42) }`,
`f`,
// we don't know how to translate these but we can type-check them
{genericPkg + `q0; type T struct{}; func (T) m[P any](P) {}; func _(x T) { x.m(42) }`,
- `x.m`,
+ `m`,
[]string{`int`},
`func(int)`,
},
{genericPkg + `q1; type T struct{}; func (T) m[P any](P) P { panic(0) }; func _(x T) { x.m(42) }`,
- `x.m`,
+ `m`,
[]string{`int`},
`func(int) int`,
},
{genericPkg + `q2; type T struct{}; func (T) m[P any](...P) P { panic(0) }; func _(x T) { x.m(42) }`,
- `x.m`,
+ `m`,
[]string{`int`},
`func(...int) int`,
},
{genericPkg + `q3; type T struct{}; func (T) m[A, B, C any](A, *B, []C) {}; func _(x T) { x.m(1.2, new(string), []byte{}) }`,
- `x.m`,
+ `m`,
[]string{`float64`, `string`, `byte`},
`func(float64, *string, []byte)`,
},
{genericPkg + `q4; type T struct{}; func (T) m[A, B any](A, *B, ...[]B) {}; func _(x T) { x.m(1.2, new(byte)) }`,
- `x.m`,
+ `m`,
[]string{`float64`, `byte`},
`func(float64, *byte, ...[]byte)`,
},
{genericPkg + `r0; type T[P any] struct{}; func (_ T[P]) m[Q any](Q) {}; func _[P any](x T[P]) { x.m(42) }`,
- `x.m`,
+ `m`,
[]string{`int`},
`func(int)`,
},
[]string{`string`, `*string`},
`func() string`,
},
- {genericPkg + `t2; type C[T any] interface{~chan<- T}; func f[T any, P C[T]]() []T { return nil }; func _() { _ = f[int] }`,
+ {genericPkg + `t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
`f`,
- []string{`int`, `chan<- int`},
- `func() []int`,
+ []string{`string`, `*string`},
+ `func() string`,
},
{genericPkg + `t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
},
+ {genericPkg + `t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
+ `f`,
+ []string{`int`, `chan<- int`, `chan<- []*chan<- int`},
+ `func() []int`,
+ },
+ {genericPkg + `i0; import lib "generic_lib"; func _() { lib.F(42) }`,
+ `F`,
+ []string{`int`},
+ `func(int)`,
+ },
+ {genericPkg + `type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`,
+ `T`,
+ []string{`int`},
+ `struct{x int}`,
+ },
+ {genericPkg + `type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`,
+ `T`,
+ []string{`int`},
+ `struct{x int}`,
+ },
+ {genericPkg + `type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`,
+ `T`,
+ []string{`int`},
+ `struct{x int}`,
+ },
+ {genericPkg + `type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`,
+ `T`,
+ []string{`[]int`, `int`},
+ `struct{x []int; y int}`,
+ },
+ {genericPkg + `type4; import lib "generic_lib"; var _ lib.T[int]`,
+ `T`,
+ []string{`int`},
+ `[]int`,
+ },
}
for _, test := range tests {
- info := Info{Inferred: make(map[syntax.Expr]Inferred)}
- name, err := mayTypecheck(t, "InferredInfo", test.src, &info)
- if err != nil {
- t.Errorf("package %s: %v", name, err)
- continue
- }
+ const lib = `package generic_lib
- // look for inferred type arguments and signature
- var targs *TypeList
- var sig *Signature
- for call, inf := range info.Inferred {
- var fun syntax.Expr
- switch x := call.(type) {
- case *syntax.CallExpr:
- fun = x.Fun
- case *syntax.IndexExpr:
- fun = x.X
- default:
- panic(fmt.Sprintf("unexpected call expression type %T", call))
+func F[P any](P) {}
+
+type T[P any] []P
+`
+
+ imports := make(testImporter)
+ conf := Config{Importer: imports}
+ instances := make(map[*syntax.Name]Instance)
+ uses := make(map[*syntax.Name]Object)
+ makePkg := func(src string) *Package {
+ f, err := parseSrc("p.go", src)
+ if err != nil {
+ t.Fatal(err)
}
- if syntax.String(fun) == test.fun {
- targs = inf.TArgs
- sig = inf.Sig
+ pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instances, Uses: uses})
+ if err != nil {
+ t.Fatal(err)
+ }
+ imports[pkg.Name()] = pkg
+ return pkg
+ }
+ makePkg(lib)
+ pkg := makePkg(test.src)
+
+ // look for instance information
+ var targs []Type
+ var typ Type
+ for ident, inst := range instances {
+ if syntax.String(ident) == test.name {
+ for i := 0; i < inst.TypeArgs.Len(); i++ {
+ targs = append(targs, inst.TypeArgs.At(i))
+ }
+ typ = inst.Type
+
+ // Check that we can find the corresponding parameterized type.
+ ptype := uses[ident].Type()
+ lister, _ := ptype.(interface{ TypeParams() *TypeParamList })
+ if lister == nil || lister.TypeParams().Len() == 0 {
+ t.Errorf("package %s: info.Types[%v] = %v, want parameterized type", pkg.Name(), ident, ptype)
+ continue
+ }
+
+ // Verify the invariant that re-instantiating the generic type with
+ // TypeArgs results in an equivalent type.
+ inst2, err := Instantiate(nil, ptype, targs, true)
+ if err != nil {
+ t.Errorf("Instantiate(%v, %v) failed: %v", ptype, targs, err)
+ }
+ if !Identical(inst.Type, inst2) {
+ t.Errorf("%v and %v are not identical", inst.Type, inst2)
+ }
break
}
}
if targs == nil {
- t.Errorf("package %s: no inferred information found for %s", name, test.fun)
+ t.Errorf("package %s: no instance information found for %s", pkg.Name(), test.name)
continue
}
// check that type arguments are correct
- if targs.Len() != len(test.targs) {
- t.Errorf("package %s: got %d type arguments; want %d", name, targs.Len(), len(test.targs))
+ if len(targs) != len(test.targs) {
+ t.Errorf("package %s: got %d type arguments; want %d", pkg.Name(), len(targs), len(test.targs))
continue
}
- for i := 0; i < targs.Len(); i++ {
- targ := targs.At(i)
+ for i, targ := range targs {
if got := targ.String(); got != test.targs[i] {
- t.Errorf("package %s, %d. type argument: got %s; want %s", name, i, got, test.targs[i])
+ t.Errorf("package %s, %d. type argument: got %s; want %s", pkg.Name(), i, got, test.targs[i])
continue
}
}
- // check that signature is correct
- if got := sig.String(); got != test.sig {
- t.Errorf("package %s: got %s; want %s", name, got, test.sig)
+ // check that the types match
+ if got := typ.Underlying().String(); got != test.typ {
+ t.Errorf("package %s: got %s; want %s", pkg.Name(), got, test.typ)
}
}
}
return
}
- // if we don't have enough type arguments, try type inference
- inferred := false
if got < want {
targs = check.infer(inst.Pos(), sig.TypeParams().list(), targs, nil, nil)
if targs == nil {
return
}
got = len(targs)
- inferred = true
}
assert(got == want)
// instantiate function signature
res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature)
assert(res.TypeParams().Len() == 0) // signature is not generic anymore
- if inferred {
- check.recordInferred(inst, targs, res)
- }
+ check.recordInstance(inst.X, targs, res)
x.typ = res
x.mode = value
x.expr = inst
// compute result signature
rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature)
assert(rsig.TypeParams().Len() == 0) // signature is not generic anymore
- check.recordInferred(call, targs, rsig)
+ check.recordInstance(call.Fun, targs, rsig)
// Optimization: Only if the parameter list was adjusted do we
// need to compute it from the adjusted list; otherwise we can
}
}
-func (check *Checker) recordInferred(call syntax.Expr, targs []Type, sig *Signature) {
- assert(call != nil)
- assert(sig != nil)
- if m := check.Inferred; m != nil {
- m[call] = Inferred{NewTypeList(targs), sig}
+// recordInstance records instantiation information into check.Info, if the
+// Instances map is non-nil. The given expr must be an ident, selector, or
+// index (list) expr with ident or selector operand.
+//
+// TODO(rfindley): the expr parameter is fragile. See if we can access the
+// instantiated identifier in some other way.
+func (check *Checker) recordInstance(expr syntax.Expr, targs []Type, typ Type) {
+ ident := instantiatedIdent(expr)
+ assert(ident != nil)
+ assert(typ != nil)
+ if m := check.Instances; m != nil {
+ m[ident] = Instance{NewTypeList(targs), typ}
+ }
+}
+
+func instantiatedIdent(expr syntax.Expr) *syntax.Name {
+ var selOrIdent syntax.Expr
+ switch e := expr.(type) {
+ case *syntax.IndexExpr:
+ selOrIdent = e.X
+ case *syntax.SelectorExpr, *syntax.Name:
+ selOrIdent = e
+ }
+ switch x := selOrIdent.(type) {
+ case *syntax.Name:
+ return x
+ case *syntax.SelectorExpr:
+ return x.Sel
}
+ panic("instantiated ident not found")
}
func (check *Checker) recordDef(id *syntax.Name, obj Object) {
typ := check.instantiate(x.Pos(), base, targs, posList)
def.setUnderlying(typ)
+ check.recordInstance(x, targs, typ)
// make sure we check instantiation works at least once
// and that the resulting type is valid