cmd/compile/internal/types2: record all instances, not just inferred instances

This is a port of CL 349629 from go/types to types2, adjusted to
make it work for types2. It also includes the necessary compiler
changes, provided by mdempsky.

Change-Id: If8de174cee9c69df0d0642fcec1ee7622b7c3852
Reviewed-on: https://go-review.googlesource.com/c/go/+/351455
Trust: Robert Griesemer <gri@golang.org>
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Dan Scales <danscales@google.com>
This commit is contained in:
Robert Griesemer 2021-09-22 09:55:10 -07:00
parent 583eeaae50
commit 73418bca34
9 changed files with 207 additions and 174 deletions

View file

@ -114,86 +114,27 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
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.

View file

@ -59,7 +59,7 @@ func checkFiles(noders []*noder) (posMap, *types2.Package, *types2.Info) {
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
}

View file

@ -22,9 +22,10 @@ func (g *irgen) def(name *syntax.Name) (*ir.Name, types2.Object) {
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)
@ -36,6 +37,20 @@ func (g *irgen) use(name *syntax.Name) *ir.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
}

View file

@ -337,7 +337,7 @@ func (pw *pkgWriter) typIdx(typ types2.Type, dict *writerDict) typeInfo {
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)
@ -1158,11 +1158,16 @@ func (w *writer) optLabel(label *syntax.Name) {
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)
@ -1303,16 +1308,7 @@ func (w *writer) expr(expr syntax.Expr) {
}
}
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)
}
@ -1756,31 +1752,17 @@ func isGlobal(obj types2.Object) bool {
}
// 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
}
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
}
targs = types2.NewTypeList(list)
}
expr = index.X
@ -1795,7 +1777,8 @@ func lookupObj(info *types2.Info, expr syntax.Expr) (obj types2.Object, targs *t
}
if name, ok := expr.(*syntax.Name); ok {
obj, _ = info.Uses[name]
obj = info.Uses[name]
inst = info.Instances[name]
}
return
}

View file

@ -214,11 +214,19 @@ type Info struct {
// 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).
@ -375,11 +383,13 @@ func (tv TypeAndValue) HasOk() bool {
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

View file

@ -382,12 +382,12 @@ func TestTypesInfo(t *testing.T) {
}
}
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`,
@ -417,33 +417,33 @@ func TestInferredInfo(t *testing.T) {
// 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)`,
},
@ -480,66 +480,130 @@ func TestInferredInfo(t *testing.T) {
[]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)
}
}
}

View file

@ -38,8 +38,6 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
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 {
@ -49,7 +47,6 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
return
}
got = len(targs)
inferred = true
}
assert(got == want)
@ -62,9 +59,7 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
// 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
@ -346,7 +341,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
// 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

View file

@ -415,14 +415,38 @@ func (check *Checker) recordCommaOkTypes(x syntax.Expr, a [2]Type) {
}
}
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) {
assert(id != nil)
if m := check.Defs; m != nil {

View file

@ -408,6 +408,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def
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