diff --git a/compiler/decls.go b/compiler/decls.go index 63c3d5059..0979a77ef 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -51,23 +51,23 @@ type Decl struct { TypeDeclCode []byte // JavaScript code that assigns exposed named types to the package. ExportTypeCode []byte - // JavaScript code that declares basic information about an anonymous types. + // JavaScript code that declares basic information about an anonymous type. // It configures basic information about the type. - // This is added to the finish setup phase to has access to all packages. + // This is added to the finish setup phase to have access to all packages. AnonTypeDeclCode []byte // JavaScript code that declares basic information about a function or // method symbol. This contains the function's or method's compiled body. - // This is added to the finish setup phase to has access to all packages. + // This is added to the finish setup phase to have access to all packages. FuncDeclCode []byte // JavaScript code that assigns exposed functions to the package. - // This is added to the finish setup phase to has access to all packages. + // This is added to the finish setup phase to have access to all packages. ExportFuncCode []byte - // JavaScript code that initializes reflection metadata about type's method list. - // This is added to the finish setup phase to has access to all packages. + // JavaScript code that initializes reflection metadata about a type's method list. + // This is added to the finish setup phase to have access to all packages. MethodListCode []byte // JavaScript code that initializes the rest of reflection metadata about a type // (e.g. struct fields, array type sizes, element types, etc.). - // This is added to the finish setup phase to has access to all packages. + // This is added to the finish setup phase to have access to all packages. TypeInitCode []byte // JavaScript code that needs to be executed during the package init phase to // set the symbol up (e.g. initialize package-level variable value). diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index 0df26b0b9..4c18d05d4 100644 --- a/compiler/internal/analysis/info_test.go +++ b/compiler/internal/analysis/info_test.go @@ -1632,13 +1632,13 @@ func newBlockingTest(t *testing.T, src string) *blockingTest { tContext := types.NewContext() tc := typeparams.Collector{ TContext: tContext, - Info: f.Info, Instances: &typeparams.PackageInstanceSets{}, } file := f.Parse(`test.go`, src) testInfo, testPkg := f.Check(`pkg/test`, file) - tc.Scan(testPkg, file) + tc.Scan(testInfo, testPkg, file) + tc.Finish() getImportInfo := func(path string) (*Info, error) { return nil, fmt.Errorf(`getImportInfo should not be called in this test, called with %v`, path) @@ -1658,7 +1658,6 @@ func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc stri tContext := types.NewContext() tc := typeparams.Collector{ TContext: tContext, - Info: f.Info, Instances: &typeparams.PackageInstanceSets{}, } @@ -1672,11 +1671,12 @@ func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc stri otherFile := f.Parse(`other.go`, otherSrc) _, otherPkg := f.Check(`pkg/other`, otherFile) - tc.Scan(otherPkg, otherFile) + tc.Scan(f.Info, otherPkg, otherFile) testFile := f.Parse(`test.go`, testSrc) _, testPkg := f.Check(`pkg/test`, testFile) - tc.Scan(testPkg, testFile) + tc.Scan(f.Info, testPkg, testFile) + tc.Finish() otherPkgInfo := AnalyzePkg([]*ast.File{otherFile}, f.FileSet, f.Info, tContext, otherPkg, tc.Instances, getImportInfo) pkgInfo[otherPkg.Path()] = otherPkgInfo diff --git a/compiler/internal/typeparams/collect.go b/compiler/internal/typeparams/collect.go index f18e6eb20..b42c1031d 100644 --- a/compiler/internal/typeparams/collect.go +++ b/compiler/internal/typeparams/collect.go @@ -188,16 +188,26 @@ func (c *seedVisitor) Visit(n ast.Node) ast.Visitor { // set whenever their receiver type instance is encountered. type Collector struct { TContext *types.Context - Info *types.Info Instances *PackageInstanceSets + + objMap map[types.Object]ast.Node + infoMap map[string]*types.Info } // Scan package files for generic instances. -func (c *Collector) Scan(pkg *types.Package, files ...*ast.File) { - if c.Info.Instances == nil || c.Info.Defs == nil { +func (c *Collector) Scan(info *types.Info, pkg *types.Package, files ...*ast.File) { + if c.objMap == nil { + c.objMap = map[types.Object]ast.Node{} + } + if c.infoMap == nil { + c.infoMap = map[string]*types.Info{} + } + + // Check the info for this package then record it for later use. + if info.Instances == nil || info.Defs == nil { panic(fmt.Errorf("types.Info must have Instances and Defs populated")) } - objMap := map[types.Object]ast.Node{} + c.infoMap[pkg.Path()] = info // Collect instances of generic objects in non-generic code in the package and // add then to the existing InstanceSet. @@ -205,42 +215,58 @@ func (c *Collector) Scan(pkg *types.Package, files ...*ast.File) { visitor: visitor{ instances: c.Instances, resolver: nil, - info: c.Info, + info: info, }, - objMap: objMap, + objMap: c.objMap, } for _, file := range files { ast.Walk(&sc, file) } +} + +// Finish will finish the collecting instances by propagating instances of +// generic types and functions found in generic code. The generic code is +// rescanned with in an instances context to find internally defined instances. +// +// This should only be called after all the files are scanned. +func (c *Collector) Finish() { + for !c.Instances.allExhausted() { + for pkgPath, instances := range *c.Instances { + c.propagate(pkgPath, instances) + } + } +} - for iset := c.Instances.Pkg(pkg); !iset.exhausted(); { +func (c *Collector) propagate(pkgPath string, instances *InstanceSet) { + info := c.infoMap[pkgPath] + for iset := instances; !iset.exhausted(); { inst, _ := iset.next() switch typ := inst.Object.Type().(type) { case *types.Signature: - c.scanSignature(inst, typ, objMap) + c.scanSignature(inst, typ, info) case *types.Named: - c.scanNamed(inst, typ, objMap) + c.scanNamed(inst, typ, info) } } } -func (c *Collector) scanSignature(inst Instance, typ *types.Signature, objMap map[types.Object]ast.Node) { +func (c *Collector) scanSignature(inst Instance, typ *types.Signature, info *types.Info) { v := visitor{ instances: c.Instances, resolver: NewResolver(c.TContext, inst), - info: c.Info, + info: info, nestTParams: SignatureTypeParams(typ), nestTArgs: inst.TArgs, } - ast.Walk(&v, objMap[inst.Object]) + ast.Walk(&v, c.objMap[inst.Object]) } -func (c *Collector) scanNamed(inst Instance, typ *types.Named, objMap map[types.Object]ast.Node) { +func (c *Collector) scanNamed(inst Instance, typ *types.Named, info *types.Info) { obj := typ.Obj() - node := objMap[obj] + node := c.objMap[obj] if node == nil { // Types without an entry in objMap are concrete types // that are defined in a generic context. Skip them. @@ -256,7 +282,7 @@ func (c *Collector) scanNamed(inst Instance, typ *types.Named, objMap map[types. v := visitor{ instances: c.Instances, resolver: NewResolver(c.TContext, inst), - info: c.Info, + info: info, nestTParams: nestTParams, nestTArgs: inst.TNest, diff --git a/compiler/internal/typeparams/collect_test.go b/compiler/internal/typeparams/collect_test.go index e122673c5..1021e9dad 100644 --- a/compiler/internal/typeparams/collect_test.go +++ b/compiler/internal/typeparams/collect_test.go @@ -412,10 +412,10 @@ func TestCollector(t *testing.T) { c := Collector{ TContext: types.NewContext(), - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() inst := func(name, tNest, tArg string) Instance { return Instance{ @@ -470,10 +470,10 @@ func TestCollector_MoreNesting(t *testing.T) { c := Collector{ TContext: types.NewContext(), - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() inst := func(name, tNest, tArg string) Instance { return Instance{ @@ -545,10 +545,10 @@ func TestCollector_NestingWithVars(t *testing.T) { c := Collector{ TContext: types.NewContext(), - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() want := []Instance{ inst(`S`, `int`), @@ -585,10 +585,10 @@ func TestCollector_RecursiveTypeParams(t *testing.T) { c := Collector{ TContext: tc, - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() xInst := inst(`main.X`, ``, `int`) xInt := xInst.Resolve(tc) @@ -636,10 +636,10 @@ func TestCollector_NestedRecursiveTypeParams(t *testing.T) { c := Collector{ TContext: tc, - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() xInst := inst(`F.X`, `string`, `int`) xInt := xInst.Resolve(tc) @@ -675,10 +675,10 @@ func TestCollector_LooselyRecursiveTypeParams(t *testing.T) { c := Collector{ TContext: tc, - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() xInst := Instance{ Object: srctesting.LookupObj(pkg, `main.X`), @@ -725,10 +725,10 @@ func TestCollector_NestedTypeParams(t *testing.T) { c := Collector{ TContext: tc, - Info: info, Instances: &PackageInstanceSets{}, } - c.Scan(pkg, file) + c.Scan(info, pkg, file) + c.Finish() uInst := inst(`F.U`, `int`, `bool`) uIntBool := uInst.Resolve(tc) @@ -747,6 +747,129 @@ func TestCollector_NestedTypeParams(t *testing.T) { } } +func TestCollector_ImplicitTypeInstance(t *testing.T) { + f := srctesting.New(t) + tc := types.NewContext() + inst := func(pkg *types.Package, name, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } + + fooSrc := `package foo + type Foo[T any] struct{} + func NewFoo[T any]() *Foo[T] { + return &Foo[T]{} + }` + fooFile := f.Parse(`foo/foo.go`, fooSrc) + _, fooPkg := f.Check(`foo`, fooFile) + + mainSrc := `package test + import "foo" + func main() { + print(foo.NewFoo[int]()) + }` + mainFile := f.Parse(`main.go`, mainSrc) + _, mainPkg := f.Check(`test`, mainFile) + + c := Collector{ + TContext: tc, + Instances: &PackageInstanceSets{}, + } + // The issue which caused this test to be written only occurred when + // fooFile was scanned before mainFile, otherwise it would work fine. + // The problem was that `foo.Foo[int]` was not being collected in this + // order because mainFile adds an instance into the instance set for "foo" + // after "foo" was already propagated. This was fixed by performing + // propagation after all the packages are scanned via `Finish`. + c.Scan(f.Info, fooPkg, fooFile) + c.Scan(f.Info, mainPkg, mainFile) + c.Finish() + + want := []Instance{ + inst(fooPkg, `NewFoo`, `int`), + inst(fooPkg, `Foo`, `int`), + } + got := c.Instances.Pkg(fooPkg).Values() + if diff := cmp.Diff(want, got, instanceOpts()); diff != `` { + t.Errorf("Instances from Collector contain diff (-want,+got):\n%s", diff) + } +} + +func TestCollector_MoreImplicitTypeInstance(t *testing.T) { + f := srctesting.New(t) + tc := types.NewContext() + inst := func(pkg *types.Package, name, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } + + fooSrc := `package foo + type Foo[T, U any] struct{} + func NewFoo[T, U any]() *Foo[T, U] { + return &Foo[T, U]{} + }` + fooFile := f.Parse(`foo/foo.go`, fooSrc) + _, fooPkg := f.Check(`foo`, fooFile) + + barSrc := `package bar + import "foo" + func Bar[T, U any](f *foo.Foo[T, U]) *foo.Foo[U, T] { + return foo.NewFoo[U, T]() + }` + barFile := f.Parse(`bar/bar.go`, barSrc) + _, barPkg := f.Check(`bar`, barFile) + + mainSrc := `package test + import "foo" + import "bar" + func main() { + f := foo.NewFoo[int, string]() + print(bar.Bar[int, string](f)) + }` + mainFile := f.Parse(`main.go`, mainSrc) + _, mainPkg := f.Check(`test`, mainFile) + + want := []Instance{ + inst(fooPkg, `NewFoo`, `int, string`), + inst(fooPkg, `NewFoo`, `string, int`), + inst(fooPkg, `Foo`, `int, string`), + inst(fooPkg, `Foo`, `string, int`), + } + trial := func(order ...int) { + c := Collector{ + TContext: tc, + Instances: &PackageInstanceSets{}, + } + for _, i := range order { + switch i { + case 1: + c.Scan(f.Info, fooPkg, fooFile) + case 2: + c.Scan(f.Info, barPkg, barFile) + case 3: + c.Scan(f.Info, mainPkg, mainFile) + } + } + c.Finish() + + got := c.Instances.Pkg(fooPkg).Values() + if diff := cmp.Diff(want, got, instanceOpts()); diff != `` { + t.Errorf("Instances from Collector trial %v contain diff (-want,+got):\n%s", order, diff) + } + } + + trial(1, 2, 3) + trial(1, 3, 2) + trial(2, 1, 3) + trial(2, 3, 1) + trial(3, 1, 2) + trial(3, 2, 1) +} + func evalTypeArgs(t *testing.T, fSet *token.FileSet, pkg *types.Package, expr string) []types.Type { if len(expr) == 0 { return nil @@ -794,11 +917,11 @@ func TestCollector_CrossPackage(t *testing.T) { c := Collector{ TContext: types.NewContext(), - Info: f.Info, Instances: &PackageInstanceSets{}, } - c.Scan(barPkg, barFile) - c.Scan(fooPkg, fooFile) + c.Scan(f.Info, barPkg, barFile) + c.Scan(f.Info, fooPkg, fooFile) + c.Finish() inst := func(pkg *types.Package, name string, tArg types.BasicKind) Instance { return Instance{ diff --git a/compiler/internal/typeparams/instance.go b/compiler/internal/typeparams/instance.go index 10e0df69f..438239707 100644 --- a/compiler/internal/typeparams/instance.go +++ b/compiler/internal/typeparams/instance.go @@ -202,7 +202,9 @@ func (iset *InstanceSet) next() (Instance, bool) { } // exhausted returns true if there are no unprocessed instances in the set. -func (iset *InstanceSet) exhausted() bool { return len(iset.values) <= iset.unprocessed } +func (iset *InstanceSet) exhausted() bool { + return len(iset.values) <= iset.unprocessed +} // Values returns instances that are currently in the set. Order is not specified. func (iset *InstanceSet) Values() []Instance { @@ -245,6 +247,17 @@ func (iset *InstanceSet) ObjHasInstances(obj types.Object) bool { // by import path. type PackageInstanceSets map[string]*InstanceSet +// allExhausted returns true if there are no unprocessed instances in +// any of the instance sets. +func (i PackageInstanceSets) allExhausted() bool { + for _, iset := range i { + if !iset.exhausted() { + return false + } + } + return true +} + // Pkg returns InstanceSet for objects defined in the given package. func (i PackageInstanceSets) Pkg(pkg *types.Package) *InstanceSet { path := pkg.Path() diff --git a/compiler/package.go b/compiler/package.go index bb94962da..2dcf19d60 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -263,9 +263,14 @@ func PrepareAllSources(allSources []*sources.Sources, importer sources.Importer, // Collect all the generic type instances from all the packages. // This must be done for all sources prior to any analysis. instances := &typeparams.PackageInstanceSets{} + tc := &typeparams.Collector{ + TContext: tContext, + Instances: instances, + } for _, srcs := range allSources { - srcs.CollectInstances(tContext, instances) + srcs.CollectInstances(tc) } + tc.Finish() // Analyze the package to determine type parameters instances, blocking, // and other type information. This will not populate the information. diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index f5317d626..db2c4bc64 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -392,16 +392,13 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { } var n = new t.ptr(); for (var i = 0; i < t.fields.length; i++) { - var f = t.fields[i]; - - if (!f.exported) { - continue; - } - var jsProp = v[f.name]; - - n[f.prop] = $internalize(jsProp, f.typ, recv, seen, makeWrapper); + var f = t.fields[i]; + if (!f.exported) { + continue; + } + var jsProp = v[f.name]; + n[f.prop] = $internalize(jsProp, f.typ, recv, seen, makeWrapper); } - return n; } $throwRuntimeError("cannot internalize " + t.string); diff --git a/compiler/sources/sources.go b/compiler/sources/sources.go index 8e2d12946..1255c1926 100644 --- a/compiler/sources/sources.go +++ b/compiler/sources/sources.go @@ -44,11 +44,11 @@ type Sources struct { // JSFiles is the JavaScript files that are part of the package. JSFiles []jsFile.JSFile - // TypeInfo is the type information this package. + // TypeInfo is the type information for this package. // This is nil until set by Analyze. TypeInfo *analysis.Info - // baseInfo is the base type information this package. + // baseInfo is the base type information for this package. // This is nil until set by TypeCheck. baseInfo *types.Info @@ -163,13 +163,11 @@ func (s *Sources) TypeCheck(importer Importer, sizes types.Sizes, tContext *type // // This must be called before Analyze to have the type parameters instances // needed during analysis. -func (s *Sources) CollectInstances(tContext *types.Context, instances *typeparams.PackageInstanceSets) { - tc := typeparams.Collector{ - TContext: tContext, - Info: s.baseInfo, - Instances: instances, - } - tc.Scan(s.Package, s.Files...) +// +// Note that once all the sources are collected, the collector needs to be +// finished to ensure all the instances are collected. +func (s *Sources) CollectInstances(tc *typeparams.Collector) { + tc.Scan(s.baseInfo, s.Package, s.Files...) } // Analyze will determine the type parameters instances, blocking, diff --git a/tests/gencircle_test.go b/tests/gencircle_test.go index cc326064e..0915e4c18 100644 --- a/tests/gencircle_test.go +++ b/tests/gencircle_test.go @@ -18,27 +18,7 @@ func Test_GenCircle_PingPong(t *testing.T) { runGenCircleTest(t, `pingpong`) } func Test_GenCircle_Burninate(t *testing.T) { runGenCircleTest(t, `burninate`) } -func Test_GenCircle_CatBox(t *testing.T) { - // TODO(grantnelson-wf): This test hits an error similar to - // `panic: info did not have function declaration instance for - // "collections.Push[box.Unboxer[cat.Cat]]"` from `analysis/Info:IsBlocking`. - // - // This is because no instance of `Stack` is used explicitly in code, - // i.e. the code doesn't have an `ast.Ident` for `Stack` found in `types.Info.Instances` - // since the only `Stack` identifiers are the ones for the generic declaration. - // `Stack[box.Unboxer[cat.Cat]]` is implicitly defined via the call - // `collections.NewStack[box.Unboxer[cat.Cat]]()` in main.go. - // - // We need to update the `typeparams.collector` to add these implicit types - // to the `PackageInstanceSets` so that `analysis/info` has the implicit - // instances of `Stack`. - // - // Simply adding `_ = collections.Stack[box.Unboxer[cat.Cat]]{}` is a - // work around for `Stack` issue but the code gets tripped up on `boxImp[T]` - // via `Box[T]` not being defined since again `boxImp` has not been collected. - t.Skip(`Implicit Instance Not Yet Collected`) - runGenCircleTest(t, `catbox`) -} +func Test_GenCircle_CatBox(t *testing.T) { runGenCircleTest(t, `catbox`) } // Cache buster: Keeping the tests from using cached results when only // the test application files are changed.