From: Robert Findley Date: Sun, 12 Sep 2021 23:43:25 +0000 (-0400) Subject: go/types: record all instances, not just inferred instances X-Git-Tag: go1.18beta1~1265 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6097ebe627b7bc58d63d6765abc005f548b9644c;p=gostls13.git go/types: record all instances, not just inferred instances 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 Run-TryBot: Robert Findley Reviewed-by: Robert Griesemer --- diff --git a/src/go/types/api.go b/src/go/types/api.go index ebc3a01266..4cf0eb123f 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -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 diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index d4f9bb65c9..9b584f390c 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -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) } } } diff --git a/src/go/types/call.go b/src/go/types/call.go index 4d14e31730..cc2be4bec2 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -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 diff --git a/src/go/types/check.go b/src/go/types/check.go index 63f4cbd4a0..a55c01c17d 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -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) { diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 0143f53009..a1b8bae3d5 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -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