]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: record all instances, not just inferred instances
authorRobert Findley <rfindley@google.com>
Sun, 12 Sep 2021 23:43:25 +0000 (19:43 -0400)
committerRobert Findley <rfindley@google.com>
Tue, 21 Sep 2021 18:08:25 +0000 (18:08 +0000)
This change modifies the way we record instance information. It changes
the Info.Inferred map to use the instantiated *ast.Ident as its key, and
record information for all instances, not just those that were produced
via function type inference. Accordingly, Info.Inferred is renamed to
Info.Instances, and the Inferred type is renamed to Instance, with its
Sig field changed to Type.

This was largely motivated by suggestions from mdempsky on the go/types
API proposal (#47916). In our analysis, always using the *ast.Ident as
key and recording all instances makes the API easier to understand and
use.

Instance.TArgs is also renamed to TypeArgs, consistent with other name
changes.

Updates #47916

Change-Id: Ic25ad0cfd65fee6b05e513843c3866ee7a77cfe3
Reviewed-on: https://go-review.googlesource.com/c/go/+/349629
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
src/go/types/api.go
src/go/types/api_test.go
src/go/types/call.go
src/go/types/check.go
src/go/types/typexpr.go

index ebc3a012668ec9cbad7f05c5a96b617dd027384e..4cf0eb123f9431d66dda012b4f1f41870e53827d 100644 (file)
@@ -203,11 +203,19 @@ type Info struct {
        // qualified identifiers are collected in the Uses map.
        Types map[ast.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[ast.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[*ast.Ident]Instance
 
        // Defs maps identifiers to the objects they define (including
        // package names, dots "." of dot-imports, and blank "_" identifiers).
@@ -365,11 +373,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
index d4f9bb65c954cb4fca009bc4e55d70acc474c3e3..9b584f390ce37593ab44d049c161500b07f38eea 100644 (file)
@@ -402,126 +402,189 @@ 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) }`,
+               {`package p0; func f[T any](T) {}; func _() { f(42) }`,
                        `f`,
                        []string{`int`},
                        `func(int)`,
                },
-               {genericPkg + `p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`,
+               {`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`,
                        `f`,
                        []string{`rune`},
                        `func(rune) rune`,
                },
-               {genericPkg + `p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`,
+               {`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`,
                        `f`,
                        []string{`complex128`},
                        `func(...complex128) complex128`,
                },
-               {genericPkg + `p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`,
+               {`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`,
                        `f`,
                        []string{`float64`, `string`, `byte`},
                        `func(float64, *string, []byte)`,
                },
-               {genericPkg + `p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`,
+               {`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`,
                        `f`,
                        []string{`float64`, `byte`},
                        `func(float64, *byte, ...[]byte)`,
                },
 
-               {genericPkg + `s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`,
+               {`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`,
                        `f`,
                        []string{`string`, `*string`},
                        `func(x string)`,
                },
-               {genericPkg + `s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`,
+               {`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`,
                        `f`,
                        []string{`int`, `*int`},
                        `func(x []int)`,
                },
-               {genericPkg + `s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
+               {`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
                        `f`,
                        []string{`int`, `chan<- int`},
                        `func(x []int)`,
                },
-               {genericPkg + `s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
+               {`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
                        `f`,
                        []string{`int`, `chan<- int`, `chan<- []*chan<- int`},
                        `func(x []int)`,
                },
 
-               {genericPkg + `t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`,
+               {`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`,
                        `f`,
                        []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] }`,
+               {`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
+                       `f`,
+                       []string{`string`, `*string`},
+                       `func() string`,
+               },
+               {`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]]() []T { return nil }; func _() { _ = f[int] }`,
                        `f`,
                        []string{`int`, `chan<- int`},
                        `func() []int`,
                },
-               {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] }`,
+               {`package 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`,
                },
+               {`package i0; import "lib"; func _() { lib.F(42) }`,
+                       `F`,
+                       []string{`int`},
+                       `func(int)`,
+               },
+               {`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`,
+                       `T`,
+                       []string{`int`},
+                       `struct{x int}`,
+               },
+               {`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`,
+                       `T`,
+                       []string{`int`},
+                       `struct{x int}`,
+               },
+               {`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`,
+                       `T`,
+                       []string{`int`},
+                       `struct{x int}`,
+               },
+               {`package 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}`,
+               },
+               {`package type4; import "lib"; var _ lib.T[int]`,
+                       `T`,
+                       []string{`int`},
+                       `[]int`,
+               },
        }
 
        for _, test := range tests {
-               info := Info{}
-               info.Inferred = make(map[ast.Expr]Inferred)
-               name, err := mayTypecheck(t, "InferredInfo", test.src, &info)
-               if err != nil {
-                       t.Errorf("package %s: %v", name, err)
-                       continue
-               }
+               const lib = `package lib
 
-               // look for inferred type arguments and signature
-               var targs *TypeList
-               var sig *Signature
-               for call, inf := range info.Inferred {
-                       var fun ast.Expr
-                       switch x := call.(type) {
-                       case *ast.CallExpr:
-                               fun = x.Fun
-                       case *ast.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[*ast.Ident]Instance)
+               uses := make(map[*ast.Ident]Object)
+               makePkg := func(src string) *Package {
+                       f, err := parser.ParseFile(fset, "p.go", src, 0)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instances, Uses: uses})
+                       if err != nil {
+                               t.Fatal(err)
                        }
-                       if ExprString(fun) == test.fun {
-                               targs = inf.TArgs
-                               sig = inf.Sig
+                       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 ExprString(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)
                }
        }
 }
index 4d14e31730d5d155a8fc32358dbb98096864fe76..cc2be4bec2663756c07ca9bacd5816e0ec0e5a81 100644 (file)
@@ -39,9 +39,6 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) {
                return
        }
 
-       // if we don't have enough type arguments, try type inference
-       inferred := false
-
        if got < want {
                targs = check.infer(ix.Orig, sig.TypeParams().list(), targs, nil, nil, true)
                if targs == nil {
@@ -51,7 +48,6 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) {
                        return
                }
                got = len(targs)
-               inferred = true
        }
        assert(got == want)
 
@@ -66,9 +62,7 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.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(ix.Orig, targs, res)
-       }
+       check.recordInstance(ix.Orig, targs, res)
        x.typ = res
        x.mode = value
        x.expr = ix.Orig
@@ -354,7 +348,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type
                // 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
index 63f4cbd4a0b0c62777712c43e43e40c65f6f7572..a55c01c17d43ad86720bc37557f11fdcdd13f6ef 100644 (file)
@@ -406,12 +406,38 @@ func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
        }
 }
 
-func (check *Checker) recordInferred(call ast.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 ast.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 ast.Expr) *ast.Ident {
+       var selOrIdent ast.Expr
+       switch e := expr.(type) {
+       case *ast.IndexExpr:
+               selOrIdent = e.X
+       case *ast.IndexListExpr:
+               selOrIdent = e.X
+       case *ast.SelectorExpr, *ast.Ident:
+               selOrIdent = e
+       }
+       switch x := selOrIdent.(type) {
+       case *ast.Ident:
+               return x
+       case *ast.SelectorExpr:
+               return x.Sel
        }
+       panic("instantiated ident not found")
 }
 
 func (check *Checker) recordDef(id *ast.Ident, obj Object) {
index 0143f53009e77d1ae7a02b2f6f2a0bc4d9800663..a1b8bae3d5f6d0bf61bad92110e92002213ed4d5 100644 (file)
@@ -398,6 +398,7 @@ func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named
 
        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