From: Robert Griesemer Date: Wed, 22 Sep 2021 16:55:10 +0000 (-0700) Subject: cmd/compile/internal/types2: record all instances, not just inferred instances X-Git-Tag: go1.18beta1~1150 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=73418bca347c94560a6cb605d8eb393b56941446;p=gostls13.git 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 Trust: Dan Scales Run-TryBot: Robert Griesemer TryBot-Result: Go Bot Reviewed-by: Robert Findley Reviewed-by: Dan Scales --- diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go index 9852ad964c..3dd7737c9f 100644 --- a/src/cmd/compile/internal/noder/expr.go +++ b/src/cmd/compile/internal/noder/expr.go @@ -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. diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go index e01e753a1d..c1a4f30f4a 100644 --- a/src/cmd/compile/internal/noder/irgen.go +++ b/src/cmd/compile/internal/noder/irgen.go @@ -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 } diff --git a/src/cmd/compile/internal/noder/object.go b/src/cmd/compile/internal/noder/object.go index 40c0b9cf42..37a995b519 100644 --- a/src/cmd/compile/internal/noder/object.go +++ b/src/cmd/compile/internal/noder/object.go @@ -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 } diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 9edf5fc97a..47de992033 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -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 - } - } - - 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 @@ -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 } diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index 6914e6c89f..b0e86357b7 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -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 diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index cd5a61332a..ca81620a78 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -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) } } } diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 99afecaf19..5cf292ce8a 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -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 diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index 24a05e6b37..e45598e0ef 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -415,12 +415,36 @@ 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) { diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 7f75a96bd8..3bfce2ebf2 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -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