diff --git a/compiler/decls.go b/compiler/decls.go index eb95cd2f7..b8ccfe976 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -264,7 +264,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { fc.localVars = nil // Clean up after ourselves. }) - d.Dce().SetName(init.Lhs[0]) + d.Dce().SetName(init.Lhs[0], nil, nil) if len(init.Lhs) != 1 || analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) { d.Dce().SetAsAlive() } @@ -291,7 +291,7 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { FullName: funcVarDeclFullName(o), Vars: []string{objName}, } - varDecl.Dce().SetName(o) + varDecl.Dce().SetName(o, nil, nil) if o.Type().(*types.Signature).TypeParams().Len() != 0 { varDecl.DeclCode = fc.CatchOutput(0, func() { fc.Printf("%s = {};", objName) @@ -331,7 +331,7 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) Blocking: fc.pkgCtx.IsBlocking(inst), LinkingName: symbol.New(o), } - d.Dce().SetName(o, inst.TArgs...) + d.Dce().SetName(o, inst.TNest, inst.TArgs) if typesutil.IsMethod(o) { recv := typesutil.RecvType(o.Type().(*types.Signature)).Obj() @@ -451,35 +451,20 @@ func (fc *funcContext) newNamedTypeVarDecl(obj *types.TypeName) *Decl { func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, error) { originType := inst.Object.Type().(*types.Named) - var nestResolver *typeparams.Resolver - if len(inst.TNest) > 0 { - fn := typeparams.FindNestingFunc(inst.Object) - tp := typeparams.SignatureTypeParams(fn.Type().(*types.Signature)) - nestResolver = typeparams.NewResolver(fc.pkgCtx.typesCtx, tp, inst.TNest, nil) - } - fc.typeResolver = typeparams.NewResolver(fc.pkgCtx.typesCtx, originType.TypeParams(), inst.TArgs, nestResolver) + fc.typeResolver = typeparams.NewResolver(fc.pkgCtx.typesCtx, inst) defer func() { fc.typeResolver = nil }() instanceType := originType if !inst.IsTrivial() { - if len(inst.TArgs) > 0 { - instantiated, err := types.Instantiate(fc.pkgCtx.typesCtx, originType, inst.TArgs, true) - if err != nil { - return nil, fmt.Errorf("failed to instantiate type %v with args %v: %w", originType, inst.TArgs, err) - } - instanceType = instantiated.(*types.Named) - } - if len(inst.TNest) > 0 { - instantiated := nestResolver.Substitute(instanceType) - instanceType = instantiated.(*types.Named) - } + instantiated := fc.typeResolver.Substitute(instanceType) + instanceType = instantiated.(*types.Named) } underlying := instanceType.Underlying() d := &Decl{ FullName: typeDeclFullName(inst), } - d.Dce().SetName(inst.Object, inst.TArgs...) + d.Dce().SetName(inst.Object, inst.TNest, inst.TArgs) fc.pkgCtx.CollectDCEDeps(d, func() { // Code that declares a JS type (i.e. prototype) for each Go type. d.DeclCode = fc.CatchOutput(0, func() { @@ -604,7 +589,7 @@ func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl { FullName: anonTypeDeclFullName(t), Vars: []string{t.Name()}, } - d.Dce().SetName(t) + d.Dce().SetName(t, nil, nil) fc.pkgCtx.CollectDCEDeps(d, func() { d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type()))) }) diff --git a/compiler/expressions.go b/compiler/expressions.go index 781a37a3e..9f391ebf1 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -591,7 +591,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case types.MethodVal: return fc.formatExpr(`$methodVal(%s, "%s")`, fc.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: - fc.pkgCtx.DeclareDCEDep(sel.Obj(), inst.TArgs...) + fc.pkgCtx.DeclareDCEDep(sel.Obj(), inst.TNest, inst.TArgs) if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return fc.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) } @@ -906,7 +906,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { sel, _ := fc.selectionOf(e) if !sel.Obj().Exported() { - fc.pkgCtx.DeclareDCEDep(sel.Obj()) + fc.pkgCtx.DeclareDCEDep(sel.Obj(), nil, nil) } x := e.X diff --git a/compiler/functions.go b/compiler/functions.go index 361c92f0f..e6f525df0 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -40,7 +40,7 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, inst typep flowDatas: map[*types.Label]*flowData{nil: {}}, caseCounter: 1, labelCases: make(map[*types.Label]int), - typeResolver: fc.typeResolver, + typeResolver: typeparams.NewResolver(fc.pkgCtx.typesCtx, inst), objectNames: map[types.Object]string{}, sig: &typesutil.Signature{Sig: sig}, } @@ -48,15 +48,6 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, inst typep c.allVars[k] = v } - if sig.TypeParams().Len() > 0 { - c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, sig.TypeParams(), inst.TArgs, nil) - } else if sig.RecvTypeParams().Len() > 0 { - c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, sig.RecvTypeParams(), inst.TArgs, nil) - } - if c.objectNames == nil { - c.objectNames = map[types.Object]string{} - } - // Synthesize an identifier by which the function may reference itself. Since // it appears in the stack trace, it's useful to include the receiver type in // it. diff --git a/compiler/internal/analysis/info.go b/compiler/internal/analysis/info.go index e400c870c..e55b66245 100644 --- a/compiler/internal/analysis/info.go +++ b/compiler/internal/analysis/info.go @@ -124,11 +124,7 @@ func (info *Info) newFuncInfoInstances(fd *ast.FuncDecl) []*FuncInfo { funcInfos := make([]*FuncInfo, 0, len(instances)) for _, inst := range instances { - var resolver *typeparams.Resolver - if sig, ok := obj.Type().(*types.Signature); ok { - tp := typeparams.SignatureTypeParams(sig) - resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs, nil) - } + resolver := typeparams.NewResolver(info.typeCtx, inst) fi := info.newFuncInfo(fd, inst.Object, inst.TArgs, resolver) funcInfos = append(funcInfos, fi) } diff --git a/compiler/internal/dce/collector.go b/compiler/internal/dce/collector.go index fea52468d..3ddac5e8b 100644 --- a/compiler/internal/dce/collector.go +++ b/compiler/internal/dce/collector.go @@ -39,8 +39,8 @@ func (c *Collector) CollectDCEDeps(decl Decl, f func()) { // The given optional type arguments are used to when the object is a // function with type parameters or anytime the object doesn't carry them. // If not given, this attempts to get the type arguments from the object. -func (c *Collector) DeclareDCEDep(o types.Object, tArgs ...types.Type) { +func (c *Collector) DeclareDCEDep(o types.Object, tNest, tArgs []types.Type) { if c.dce != nil { - c.dce.addDep(o, tArgs) + c.dce.addDep(o, tNest, tArgs) } } diff --git a/compiler/internal/dce/dce_test.go b/compiler/internal/dce/dce_test.go index 3ddeac848..bf0517b65 100644 --- a/compiler/internal/dce/dce_test.go +++ b/compiler/internal/dce/dce_test.go @@ -43,34 +43,34 @@ func Test_Collector_Collecting(t *testing.T) { decl2 := quickTestDecl(obj2) var c Collector - c.DeclareDCEDep(obj1) // no effect since a collection isn't running. + c.DeclareDCEDep(obj1, nil, nil) // no effect since a collection isn't running. depCount(t, decl1, 0) depCount(t, decl2, 0) c.CollectDCEDeps(decl1, func() { - c.DeclareDCEDep(obj2) - c.DeclareDCEDep(obj3) - c.DeclareDCEDep(obj3) // already added so has no effect. + c.DeclareDCEDep(obj2, nil, nil) + c.DeclareDCEDep(obj3, nil, nil) + c.DeclareDCEDep(obj3, nil, nil) // already added so has no effect. }) depCount(t, decl1, 2) depCount(t, decl2, 0) - c.DeclareDCEDep(obj4) // no effect since a collection isn't running. + c.DeclareDCEDep(obj4, nil, nil) // no effect since a collection isn't running. depCount(t, decl1, 2) depCount(t, decl2, 0) c.CollectDCEDeps(decl2, func() { - c.DeclareDCEDep(obj5) - c.DeclareDCEDep(obj6) - c.DeclareDCEDep(obj7) + c.DeclareDCEDep(obj5, nil, nil) + c.DeclareDCEDep(obj6, nil, nil) + c.DeclareDCEDep(obj7, nil, nil) }) depCount(t, decl1, 2) depCount(t, decl2, 3) // The second collection adds to existing dependencies. c.CollectDCEDeps(decl2, func() { - c.DeclareDCEDep(obj4) - c.DeclareDCEDep(obj5) + c.DeclareDCEDep(obj4, nil, nil) + c.DeclareDCEDep(obj5, nil, nil) }) depCount(t, decl1, 2) depCount(t, decl2, 4) @@ -541,7 +541,7 @@ func Test_Info_SetNameAndDep(t *testing.T) { equal(t, d.Dce().String(), `[unnamed] -> []`) t.Log(`object:`, types.ObjectString(tt.obj, nil)) - d.Dce().SetName(tt.obj) + d.Dce().SetName(tt.obj, nil, nil) equal(t, d.Dce().unnamed(), tt.want.unnamed()) equal(t, d.Dce().objectFilter, tt.want.objectFilter) equal(t, d.Dce().methodFilter, tt.want.methodFilter) @@ -568,7 +568,7 @@ func Test_Info_SetNameAndDep(t *testing.T) { c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(tt.obj) + c.DeclareDCEDep(tt.obj, nil, nil) }) equalSlices(t, d.Dce().getDeps(), wantDeps) }) @@ -582,10 +582,10 @@ func Test_Info_SetNameOnlyOnce(t *testing.T) { obj2 := quickVar(pkg, `Stripe`) decl := &testDecl{} - decl.Dce().SetName(obj1) + decl.Dce().SetName(obj1, nil, nil) err := capturePanic(t, func() { - decl.Dce().SetName(obj2) + decl.Dce().SetName(obj2, nil, nil) }) errorMatches(t, err, `^may only set the name once for path/to/mogwai\.Gizmo .*$`) } @@ -743,7 +743,7 @@ func Test_Info_UsesDeps(t *testing.T) { c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(uses, tArgs...) + c.DeclareDCEDep(uses, nil, tArgs) }) equalSlices(t, d.Dce().getDeps(), tt.wantDeps) }) @@ -754,6 +754,7 @@ func Test_Info_SpecificCasesDeps(t *testing.T) { tests := []struct { name string obj types.Object + tNest []types.Type tArgs []types.Type wantDeps []string }{ @@ -808,16 +809,48 @@ func Test_Info_SpecificCasesDeps(t *testing.T) { `astoria.shuffle(string) int`, }, }, + { + name: `a generic method with a nested concrete type instance`, + obj: parseObject(t, `quan`, + `package astoria + func data[T any](v T) any { + type quan struct { V T } + return quan{ V: v } + }`), + tNest: []types.Type{types.Typ[types.Int]}, + // TODO(grantnelson-wf): This should take into account the nested type. + //wantDeps: []string{`astoria.quan[int;]`}, + wantDeps: []string{`astoria.quan`}, + }, + { + name: `a generic method with a nested generic type instance`, + obj: parseObject(t, `matuszak`, + `package astoria + func sloth[T any]() any { + type matuszak[U any] struct { X T; Y U } + return matuszak[T]{} + }`), + tNest: []types.Type{types.Typ[types.String]}, + tArgs: []types.Type{types.Typ[types.Bool]}, + // TODO(grantnelson-wf): This should take into account the nested type. + //wantDeps: []string{`astoria.matuszak[string;bool]`}, + wantDeps: []string{`astoria.matuszak[bool]`}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &testDecl{} - t.Logf(`object: %s with [%s]`, types.ObjectString(tt.obj, nil), (typesutil.TypeList)(tt.tArgs).String()) + tail := `` + if len(tt.tNest) > 0 { + tail += (typesutil.TypeList)(tt.tNest).String() + `;` + } + tail += (typesutil.TypeList)(tt.tArgs).String() + t.Logf(`object: %s with [%s]`, types.ObjectString(tt.obj, nil), tail) c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(tt.obj, tt.tArgs...) + c.DeclareDCEDep(tt.obj, tt.tNest, tt.tArgs) }) equalSlices(t, d.Dce().getDeps(), tt.wantDeps) }) @@ -837,7 +870,7 @@ func Test_Info_SetAsAlive(t *testing.T) { equal(t, decl.Dce().isAlive(), true) // still alive but now explicitly alive equal(t, decl.Dce().String(), `[alive] [unnamed] -> []`) - decl.Dce().SetName(obj) + decl.Dce().SetName(obj, nil, nil) equal(t, decl.Dce().isAlive(), true) // alive because SetAsAlive was called equal(t, decl.Dce().String(), `[alive] path/to/fantasia.Falkor -> []`) }) @@ -848,7 +881,7 @@ func Test_Info_SetAsAlive(t *testing.T) { equal(t, decl.Dce().isAlive(), true) // unnamed is automatically alive equal(t, decl.Dce().String(), `[unnamed] -> []`) - decl.Dce().SetName(obj) + decl.Dce().SetName(obj, nil, nil) equal(t, decl.Dce().isAlive(), false) // named so no longer automatically alive equal(t, decl.Dce().String(), `path/to/fantasia.Artax -> []`) @@ -876,27 +909,27 @@ func Test_Selector_JustVars(t *testing.T) { c := Collector{} c.CollectDCEDeps(frodo, func() { - c.DeclareDCEDep(samwise.obj) - c.DeclareDCEDep(meri.obj) - c.DeclareDCEDep(pippin.obj) + c.DeclareDCEDep(samwise.obj, nil, nil) + c.DeclareDCEDep(meri.obj, nil, nil) + c.DeclareDCEDep(pippin.obj, nil, nil) }) c.CollectDCEDeps(pippin, func() { - c.DeclareDCEDep(meri.obj) + c.DeclareDCEDep(meri.obj, nil, nil) }) c.CollectDCEDeps(aragorn, func() { - c.DeclareDCEDep(boromir.obj) + c.DeclareDCEDep(boromir.obj, nil, nil) }) c.CollectDCEDeps(gimli, func() { - c.DeclareDCEDep(legolas.obj) + c.DeclareDCEDep(legolas.obj, nil, nil) }) c.CollectDCEDeps(legolas, func() { - c.DeclareDCEDep(gimli.obj) + c.DeclareDCEDep(gimli.obj, nil, nil) }) c.CollectDCEDeps(gandalf, func() { - c.DeclareDCEDep(frodo.obj) - c.DeclareDCEDep(aragorn.obj) - c.DeclareDCEDep(gimli.obj) - c.DeclareDCEDep(legolas.obj) + c.DeclareDCEDep(frodo.obj, nil, nil) + c.DeclareDCEDep(aragorn.obj, nil, nil) + c.DeclareDCEDep(gimli.obj, nil, nil) + c.DeclareDCEDep(legolas.obj, nil, nil) }) for _, decl := range fellowship { @@ -1012,16 +1045,16 @@ func Test_Selector_SpecificMethods(t *testing.T) { c := Collector{} c.CollectDCEDeps(rincewindRun, func() { - c.DeclareDCEDep(rincewind.obj) + c.DeclareDCEDep(rincewind.obj, nil, nil) }) c.CollectDCEDeps(rincewindHide, func() { - c.DeclareDCEDep(rincewind.obj) + c.DeclareDCEDep(rincewind.obj, nil, nil) }) c.CollectDCEDeps(vimesRun, func() { - c.DeclareDCEDep(vimes.obj) + c.DeclareDCEDep(vimes.obj, nil, nil) }) c.CollectDCEDeps(vimesRead, func() { - c.DeclareDCEDep(vimes.obj) + c.DeclareDCEDep(vimes.obj, nil, nil) }) vetinari.Dce().SetAsAlive() @@ -1058,7 +1091,7 @@ func Test_Selector_SpecificMethods(t *testing.T) { vetinari.Dce().deps = nil // reset deps c.CollectDCEDeps(vetinari, func() { for _, decl := range tt.deps { - c.DeclareDCEDep(decl.obj) + c.DeclareDCEDep(decl.obj, nil, nil) } }) @@ -1095,7 +1128,7 @@ func testPackage(name string) *types.Package { func quickTestDecl(o types.Object) *testDecl { d := &testDecl{obj: o} - d.Dce().SetName(o) + d.Dce().SetName(o, nil, nil) return d } diff --git a/compiler/internal/dce/filters.go b/compiler/internal/dce/filters.go index 420fd4310..2a6eddefd 100644 --- a/compiler/internal/dce/filters.go +++ b/compiler/internal/dce/filters.go @@ -16,7 +16,7 @@ import ( // the object filter will be empty and only the method filter will be set. // The later shouldn't happen when naming a declaration but only when creating // dependencies. -func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter string) { +func getFilters(o types.Object, tNest, tArgs []types.Type) (objectFilter, methodFilter string) { if f, ok := o.(*types.Func); ok { sig := f.Type().(*types.Signature) if recv := sig.Recv(); recv != nil { @@ -30,7 +30,7 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter tArgs = getTypeArgs(typ) } if named, ok := typ.(*types.Named); ok { - objectFilter = getObjectFilter(named.Obj(), tArgs) + objectFilter = getObjectFilter(named.Obj(), tNest, tArgs) } // The method is not exported so we only need the method filter. @@ -42,7 +42,7 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter } // The object is not a method so we only need the object filter. - objectFilter = getObjectFilter(o, tArgs) + objectFilter = getObjectFilter(o, tNest, tArgs) return } @@ -51,7 +51,8 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter // See [naming design] for more information. // // [naming design]: https://github.com/gopherjs/gopherjs/compiler/internal/dce/README.md#naming -func getObjectFilter(o types.Object, tArgs []types.Type) string { +func getObjectFilter(o types.Object, tNest, tArgs []types.Type) string { + // TODO(grantnelson-wf): This needs to be resolving for nesting types too. return (&filterGen{argTypeRemap: tArgs}).Object(o, tArgs) } @@ -331,6 +332,7 @@ func (gen *filterGen) Struct(s *types.Struct) string { // If there is an argument remap, it will use the remapped type // so long as it doesn't map to itself. func (gen *filterGen) TypeParam(t *types.TypeParam) string { + // TODO(grantnelson-wf): This needs to be resolving for nesting types too. index := t.Index() if index >= 0 && index < len(gen.argTypeRemap) { if inst := gen.argTypeRemap[index]; inst != t { diff --git a/compiler/internal/dce/info.go b/compiler/internal/dce/info.go index 6a45e9ef3..ef310f047 100644 --- a/compiler/internal/dce/info.go +++ b/compiler/internal/dce/info.go @@ -81,19 +81,19 @@ func (d *Info) SetAsAlive() { // The given optional type arguments are used to when the object is a // function with type parameters or anytime the object doesn't carry them. // If not given, this attempts to get the type arguments from the object. -func (d *Info) SetName(o types.Object, tArgs ...types.Type) { +func (d *Info) SetName(o types.Object, tNest, tArgs []types.Type) { if !d.unnamed() { panic(fmt.Errorf(`may only set the name once for %s`, d.String())) } // Determine name(s) for DCE. - d.objectFilter, d.methodFilter = getFilters(o, tArgs) + d.objectFilter, d.methodFilter = getFilters(o, tNest, tArgs) } // addDep add a declaration dependencies used by DCE // for the declaration this DCE info is attached to. -func (d *Info) addDep(o types.Object, tArgs []types.Type) { - objectFilter, methodFilter := getFilters(o, tArgs) +func (d *Info) addDep(o types.Object, tNest, tArgs []types.Type) { + objectFilter, methodFilter := getFilters(o, tNest, tArgs) d.addDepName(objectFilter) d.addDepName(methodFilter) } diff --git a/compiler/internal/typeparams/collect.go b/compiler/internal/typeparams/collect.go index 940690e83..2ad947d58 100644 --- a/compiler/internal/typeparams/collect.go +++ b/compiler/internal/typeparams/collect.go @@ -11,34 +11,89 @@ import ( ) // Resolver translates types defined in terms of type parameters into concrete -// types, given a mapping from type params to type arguments. +// types, given a root instance. The root instance provides context for mapping +// from type parameters to type arguments so that the resolver can substitute +// any type parameters used in types to the corresponding type arguments. +// +// In some cases, a generic type may not be able to be fully instantiated. +// Generic named types that have no type arguments applied will have the +// type parameters substituted, however the type arguments will not be +// applied to instantiate the named type. +// For example, given `func Foo[T any]() { type Bar[U *T] struct { x T; y U } }`, +// and if `Foo[int]` is used as the root for the resolver, then `Bar[U *T]` will +// be substituted to create the generic `Bar[U *int] struct { x int; y U }`, +// and the generic (because of the `T`) `Bar[bool] struct { x T; y bool}` will +// be substituted to create the concrete `Bar[bool] struct { x int; y bool }`. +// Typically the instantiated type from `info.Instances` should be substituted +// to resolve the implicit nesting types and create a concrete type. +// See internal/govendor/subst/subst.go for more details. type Resolver struct { + tc *types.Context tParams *types.TypeParamList tArgs []types.Type - parent *Resolver + root Instance // subster is the substitution helper that will perform the actual // substitutions. This maybe nil when there are no substitutions but - // will still usable when nil. + // will still be usable when nil. subster *subst.Subster selMemo map[typesutil.Selection]typesutil.Selection } -// NewResolver creates a new Resolver with tParams entries mapping to tArgs -// entries with the same index. -func NewResolver(tc *types.Context, tParams *types.TypeParamList, tArgs []types.Type, parent *Resolver) *Resolver { - r := &Resolver{ +// NewResolver creates a new Resolver that will substitute type parameters +// with the type arguments as defined in the provided Instance. +func NewResolver(tc *types.Context, root Instance) *Resolver { + var ( + fn *types.Func + nestTParams *types.TypeParamList + tParams *types.TypeParamList + replacements = map[*types.TypeParam]types.Type{} + ) + + switch typ := root.Object.Type().(type) { + case *types.Signature: + fn = root.Object.(*types.Func) + tParams = SignatureTypeParams(typ) + case *types.Named: + fn = FindNestingFunc(root.Object) + tParams = typ.TypeParams() + if fn != nil { + nestTParams = SignatureTypeParams(fn.Type().(*types.Signature)) + } + default: + panic(fmt.Errorf("unexpected type %T for object %s", typ, root.Object)) + } + + // Check the root's implicit nesting type parameters and arguments match, + // then add them to the replacements. + if nestTParams.Len() != len(root.TNest) { + panic(fmt.Errorf(`number of nesting type parameters and arguments must match: %d => %d`, nestTParams.Len(), len(root.TNest))) + } + for i := 0; i < nestTParams.Len(); i++ { + replacements[nestTParams.At(i)] = root.TNest[i] + } + + // Check the root's type parameters and arguments match, + // then add them to the replacements. + if tParams.Len() != len(root.TArgs) { + panic(fmt.Errorf(`number of type parameters and arguments must match: %d => %d`, tParams.Len(), len(root.TArgs))) + } + for i := 0; i < tParams.Len(); i++ { + replacements[tParams.At(i)] = root.TArgs[i] + } + + return &Resolver{ + tc: tc, tParams: tParams, - tArgs: tArgs, - parent: parent, - subster: subst.New(tc, tParams, tArgs), + tArgs: root.TArgs, + root: root, + subster: subst.New(tc, fn, replacements), selMemo: map[typesutil.Selection]typesutil.Selection{}, } - return r } -// TypeParams is the list of type parameters that this resolver -// (not any parent) will substitute. +// TypeParams is the list of type parameters that this resolver will substitute. +// This will not including any implicit type parameters from a nesting function or method. func (r *Resolver) TypeParams() *types.TypeParamList { if r == nil { return nil @@ -46,8 +101,8 @@ func (r *Resolver) TypeParams() *types.TypeParamList { return r.tParams } -// TypeArgs is the list of type arguments that this resolver -// (not any parent) will resolve to. +// TypeArgs is the list of type arguments that this resolver will resolve to. +// This will not including any implicit type parameters from a nesting function or method. func (r *Resolver) TypeArgs() []types.Type { if r == nil { return nil @@ -55,25 +110,13 @@ func (r *Resolver) TypeArgs() []types.Type { return r.tArgs } -// Parent is the resolver for the function or method that this resolver -// is nested in. This may be nil if the context for this resolver is not -// nested in another generic function or method. -func (r *Resolver) Parent() *Resolver { - if r == nil { - return nil - } - return r.parent -} - // Substitute replaces references to type params in the provided type definition // with the corresponding concrete types. func (r *Resolver) Substitute(typ types.Type) types.Type { if r == nil || typ == nil { return typ // No substitutions to be made. } - typ = r.subster.Type(typ) - typ = r.parent.Substitute(typ) - return typ + return r.subster.Type(typ) } // SubstituteAll same as Substitute, but accepts a TypeList are returns @@ -133,15 +176,10 @@ func (r *Resolver) String() string { for i, ta := range r.tArgs { parts = append(parts, fmt.Sprintf("%s->%s", r.tParams.At(i), ta)) } - - nestStr := `` - if r.parent != nil { - nestStr = r.parent.String() + `:` - } - return nestStr + `{` + strings.Join(parts, `, `) + `}` + return `{` + strings.Join(parts, `, `) + `}` } -// visitor implements ast.Visitor and collects instances of generic types and +// visitor implements ast.Visitor to collect instances of generic types and // functions into an InstanceSet. // // When traversing an AST subtree corresponding to a generic type, method or @@ -358,10 +396,9 @@ func (c *Collector) Scan(pkg *types.Package, files ...*ast.File) { } func (c *Collector) scanSignature(inst Instance, typ *types.Signature, objMap map[types.Object]ast.Node) { - tParams := SignatureTypeParams(typ) v := visitor{ instances: c.Instances, - resolver: NewResolver(c.TContext, tParams, inst.TArgs, nil), + resolver: NewResolver(c.TContext, inst), info: c.Info, tNest: inst.TArgs, } @@ -377,16 +414,9 @@ func (c *Collector) scanNamed(inst Instance, typ *types.Named, objMap map[types. return } - var nestResolver *Resolver - if len(inst.TNest) > 0 { - fn := FindNestingFunc(inst.Object) - tp := SignatureTypeParams(fn.Type().(*types.Signature)) - nestResolver = NewResolver(c.TContext, tp, inst.TNest, nil) - } - v := visitor{ instances: c.Instances, - resolver: NewResolver(c.TContext, typ.TypeParams(), inst.TArgs, nestResolver), + resolver: NewResolver(c.TContext, inst), info: c.Info, tNest: inst.TNest, } diff --git a/compiler/internal/typeparams/collect_test.go b/compiler/internal/typeparams/collect_test.go index 6864e5ead..102f89078 100644 --- a/compiler/internal/typeparams/collect_test.go +++ b/compiler/internal/typeparams/collect_test.go @@ -98,7 +98,11 @@ func TestVisitor(t *testing.T) { info, pkg := f.Check("pkg/test", file) lookupObj := func(name string) types.Object { - return srctesting.LookupObj(pkg, name) + obj := srctesting.LookupObj(pkg, name) + if obj == nil { + t.Fatalf("Object %q not found", name) + } + return obj } lookupType := func(name string) types.Type { return lookupObj(name).Type() } lookupDecl := func(name string) ast.Node { @@ -218,10 +222,10 @@ func TestVisitor(t *testing.T) { descr: "generic function", resolver: NewResolver( types.NewContext(), - lookupType("entry2").(*types.Signature).TypeParams(), - []types.Type{lookupType("B")}, - nil, - ), + Instance{ + Object: lookupObj("entry2"), + TArgs: []types.Type{lookupType("B")}, + }), node: lookupDecl("entry2"), want: append( instancesInFunc(lookupType("B")), @@ -244,10 +248,10 @@ func TestVisitor(t *testing.T) { descr: "generic method", resolver: NewResolver( types.NewContext(), - lookupType("entry3.method").(*types.Signature).RecvTypeParams(), - []types.Type{lookupType("C")}, - nil, - ), + Instance{ + Object: lookupObj("entry3.method"), + TArgs: []types.Type{lookupType("C")}, + }), node: lookupDecl("entry3.method"), want: append( instancesInFunc(lookupType("C")), @@ -278,10 +282,10 @@ func TestVisitor(t *testing.T) { descr: "generic type declaration", resolver: NewResolver( types.NewContext(), - lookupType("entry3").(*types.Named).TypeParams(), - []types.Type{lookupType("D")}, - nil, - ), + Instance{ + Object: lookupObj("entry3"), + TArgs: []types.Type{lookupType("D")}, + }), node: lookupDecl("entry3"), want: instancesInType(lookupType("D")), }, { @@ -607,13 +611,6 @@ func TestCollector_RecursiveTypeParams(t *testing.T) { } func TestCollector_NestedRecursiveTypeParams(t *testing.T) { - t.Skip(`Skipping test due to known issue with nested recursive type parameters.`) - // TODO(grantnelson-wf): This test is failing because the type parameters - // inside of U are not being resolved to concrete types. This is because - // when instantiating X in the collector, we are not resolving the - // nested type of U that is X's type argument. This leave the A in U - // as a type parameter instead of resolving it to string. - // This is based off of part of go1.19.13/test/typeparam/nested.go src := `package test func F[A any]() any { @@ -627,24 +624,30 @@ func TestCollector_NestedRecursiveTypeParams(t *testing.T) { ` f := srctesting.New(t) + tc := types.NewContext() file := f.Parse(`test.go`, src) info, pkg := f.Check(`test`, file) c := Collector{ - TContext: types.NewContext(), + TContext: tc, Info: info, Instances: &PackageInstanceSets{}, } c.Scan(pkg, file) + // Instantiate an `X[int]` then substitute the `A` in `U` with `string`. xAny := srctesting.LookupObj(pkg, `F.X`) - xInt, err := types.Instantiate(types.NewContext(), xAny.Type(), []types.Type{types.Typ[types.Int]}, true) + xInt, err := types.Instantiate(tc, xAny.Type(), []types.Type{types.Typ[types.Int]}, true) if err != nil { t.Fatalf("Failed to instantiate X[int]: %v", err) } - // TODO(grantnelson-wf): Need to instantiate xInt to replace `A` with `int` in the struct. - if isGeneric(xInt) { - t.Errorf("Expected uInt to be non-generic, got %v", xInt.Underlying()) + resolver := NewResolver(tc, Instance{ + Object: srctesting.LookupObj(pkg, `F`), + TArgs: []types.Type{types.Typ[types.String]}, + }) + xStrInt := resolver.Substitute(xInt) + if isGeneric(xStrInt) { + t.Errorf("Expected xStrInt to be non-generic, got %v:%v", xStrInt, xStrInt.Underlying()) } want := []Instance{ @@ -667,20 +670,62 @@ func TestCollector_NestedRecursiveTypeParams(t *testing.T) { } } -func TestCollector_NestedTypeParams(t *testing.T) { - t.Skip(`Skipping test due to known issue with nested recursive type parameters.`) - // TODO(grantnelson-wf): This test is failing because the type parameters - // inside of U are not being resolved to concrete types. This is because - // when instantiating X in the collector, we are not resolving the - // nested type of U that is X's type argument. This leave the A in U - // as a type parameter instead of resolving it to string. +func TestCollector_LooselyRecursiveTypeParams(t *testing.T) { + // This is based off of part of go1.19.13/test/typeparam/nested.go + src := `package test + func main() { + type U[B any] struct{ y *B } + type X[C any] struct{ p U[X[C]] } + print(X[int]{}) + } + ` + f := srctesting.New(t) + tc := types.NewContext() + file := f.Parse(`test.go`, src) + info, pkg := f.Check(`test`, file) + + c := Collector{ + TContext: tc, + Info: info, + Instances: &PackageInstanceSets{}, + } + c.Scan(pkg, file) + + xAny := srctesting.LookupObj(pkg, `main.X`) + xIntInst := Instance{ + Object: xAny, + TArgs: []types.Type{types.Typ[types.Int]}, + } + xInt, err := types.Instantiate(tc, xAny.Type(), xIntInst.TArgs, true) + if err != nil { + t.Fatalf("Failed to instantiate X[int]: %v", err) + } + xInt = NewResolver(tc, xIntInst).Substitute(xInt) + if isGeneric(xInt) { + t.Errorf("Expected xStrInt to be non-generic, got %v:%v", xInt, xInt.Underlying()) + } + + want := []Instance{ + xIntInst, + { + Object: srctesting.LookupObj(pkg, `main.U`), + TArgs: []types.Type{xInt}, + }, + } + got := c.Instances.Pkg(pkg).Values() + if diff := cmp.Diff(want, got, instanceOpts()); diff != `` { + t.Errorf("Instances from Collector contain diff (-want,+got):\n%s", diff) + } +} + +func TestCollector_NestedTypeParams(t *testing.T) { // This is based off of part of go1.19.13/test/typeparam/nested.go src := `package test func F[A any]() any { type T[B any] struct{} type U[_ any] struct{ X A } - return T[U[A]]{} + return T[U[bool]]{} } func main() { print(F[int]()) @@ -688,24 +733,33 @@ func TestCollector_NestedTypeParams(t *testing.T) { ` f := srctesting.New(t) + tc := types.NewContext() file := f.Parse(`test.go`, src) info, pkg := f.Check(`test`, file) c := Collector{ - TContext: types.NewContext(), + TContext: tc, Info: info, Instances: &PackageInstanceSets{}, } c.Scan(pkg, file) uAny := srctesting.LookupObj(pkg, `F.U`) - uInt, err := types.Instantiate(types.NewContext(), uAny.Type(), []types.Type{types.Typ[types.Int]}, true) + resolver := NewResolver(tc, Instance{ + Object: uAny, + TNest: []types.Type{types.Typ[types.Int]}, + TArgs: []types.Type{types.Typ[types.Bool]}, + }) + // Substitute will only substitute type parameters in generic types but + // will not instantiate any generic type by applying the type arguments, + // therefore we need to instantiate first. + uBool, err := types.Instantiate(tc, uAny.Type(), []types.Type{types.Typ[types.Bool]}, true) if err != nil { - t.Fatalf("Failed to instantiate U[int]: %v", err) + t.Fatalf("Failed to instantiate U[bool]: %v", err) } - //TODO(grantnelson-wf): Need to instantiate uInt to replace `A` with `int` in the struct. - if isGeneric(uInt) { - t.Errorf("Expected uInt to be non-generic, got %v", uInt.Underlying()) + uIntBool := resolver.Substitute(uBool) + if isGeneric(uIntBool) { + t.Errorf("Expected uInt to be concrete, got %v:%v", uIntBool, uIntBool.Underlying()) } want := []Instance{ @@ -715,11 +769,11 @@ func TestCollector_NestedTypeParams(t *testing.T) { }, { Object: srctesting.LookupObj(pkg, `F.U`), TNest: []types.Type{types.Typ[types.Int]}, - TArgs: []types.Type{types.Typ[types.Int]}, + TArgs: []types.Type{types.Typ[types.Bool]}, }, { Object: srctesting.LookupObj(pkg, `F.T`), TNest: []types.Type{types.Typ[types.Int]}, - TArgs: []types.Type{uInt}, + TArgs: []types.Type{uIntBool}, }, } got := c.Instances.Pkg(pkg).Values() @@ -858,8 +912,10 @@ func TestResolver_SubstituteSelection(t *testing.T) { file := f.Parse("test.go", test.src) info, pkg := f.Check("pkg/test", file) - method := srctesting.LookupObj(pkg, "g.Method").(*types.Func).Type().(*types.Signature) - resolver := NewResolver(nil, method.RecvTypeParams(), []types.Type{srctesting.LookupObj(pkg, "x").Type()}, nil) + resolver := NewResolver(nil, Instance{ + Object: srctesting.LookupObj(pkg, "g.Method"), + TArgs: []types.Type{srctesting.LookupObj(pkg, "x").Type()}, + }) if l := len(info.Selections); l != 1 { t.Fatalf("Got: %d selections. Want: 1", l) diff --git a/compiler/internal/typeparams/instance.go b/compiler/internal/typeparams/instance.go index 64c67b4b5..c4f3cf29c 100644 --- a/compiler/internal/typeparams/instance.go +++ b/compiler/internal/typeparams/instance.go @@ -3,6 +3,7 @@ package typeparams import ( "fmt" "go/types" + "sort" "strings" "github.com/gopherjs/gopherjs/compiler/internal/symbol" @@ -243,3 +244,23 @@ func (i PackageInstanceSets) Add(instances ...Instance) { func (i PackageInstanceSets) ID(inst Instance) int { return i.Pkg(inst.Object.Pkg()).ID(inst) } + +func (i PackageInstanceSets) String() string { + pkgName := make([]string, 0, len(i)) + for pkg := range i { + pkgName = append(pkgName, pkg) + } + sort.Strings(pkgName) + buf := strings.Builder{} + for _, pkg := range pkgName { + buf.WriteString(pkg) + buf.WriteString(":\n") + iset := i[pkg] + for _, inst := range iset.values { + buf.WriteString("\t") + buf.WriteString(inst.String()) + buf.WriteString("\n") + } + } + return buf.String() +} diff --git a/compiler/internal/typeparams/map.go b/compiler/internal/typeparams/map.go index 7edbdc016..13cf24a30 100644 --- a/compiler/internal/typeparams/map.go +++ b/compiler/internal/typeparams/map.go @@ -174,7 +174,7 @@ func (im *InstanceMap[V]) Keys() []Instance { func (im *InstanceMap[V]) String() string { entries := make([]string, 0, im.Len()) im.Iterate(func(key Instance, value V) { - entries = append(entries, fmt.Sprintf("%v:%v", key, value)) + entries = append(entries, fmt.Sprintf("%v:%v", key.String(), value)) }) sort.Strings(entries) return `{` + strings.Join(entries, `, `) + `}` diff --git a/compiler/internal/typeparams/resolver.go b/compiler/internal/typeparams/resolver.go new file mode 100644 index 000000000..49a751996 --- /dev/null +++ b/compiler/internal/typeparams/resolver.go @@ -0,0 +1 @@ +package typeparams diff --git a/compiler/internal/typeparams/utils.go b/compiler/internal/typeparams/utils.go index ea528314e..2be2d8f68 100644 --- a/compiler/internal/typeparams/utils.go +++ b/compiler/internal/typeparams/utils.go @@ -106,7 +106,7 @@ func isGeneric(typ ...types.Type) bool { } seen := make(map[types.Type]struct{}) - containsTypeParam = func(t types.Type) bool { + containsTypeParam = func(t types.Type) (result bool) { if _, ok := seen[t]; ok { return false } diff --git a/compiler/statements.go b/compiler/statements.go index 17ed8b746..9c097bea3 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -446,7 +446,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { id := spec.(*ast.TypeSpec).Name o := fc.pkgCtx.Defs[id].(*types.TypeName) fc.pkgCtx.typeNames.Add(o) - fc.pkgCtx.DeclareDCEDep(o) + fc.pkgCtx.DeclareDCEDep(o, nil, nil) } case token.CONST: // skip, constants are inlined diff --git a/compiler/utils.go b/compiler/utils.go index 7d286f447..351d48e0c 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -415,7 +415,7 @@ func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bo // allocated as needed. func (fc *funcContext) objectName(o types.Object) string { if isPkgLevel(o) { - fc.pkgCtx.DeclareDCEDep(o) + fc.pkgCtx.DeclareDCEDep(o, nil, nil) if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { return fc.pkgVar(o.Pkg()) + "." + o.Name() @@ -461,7 +461,7 @@ func (fc *funcContext) instName(inst typeparams.Instance) string { if inst.IsTrivial() { return objName } - fc.pkgCtx.DeclareDCEDep(inst.Object, inst.TArgs...) + fc.pkgCtx.DeclareDCEDep(inst.Object, nil, inst.TArgs) label := inst.TypeParamsString(` /* `, ` */`) return fmt.Sprintf("%s[%d%s]", objName, fc.pkgCtx.instanceSet.ID(inst), label) } @@ -547,7 +547,7 @@ func (fc *funcContext) typeName(ty types.Type) string { fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType) fc.pkgCtx.anonTypeMap.Set(ty, anonType) } - fc.pkgCtx.DeclareDCEDep(anonType) + fc.pkgCtx.DeclareDCEDep(anonType, nil, nil) return anonType.Name() } diff --git a/internal/govendor/subst/export.go b/internal/govendor/subst/export.go index 00a77ca49..2899680bc 100644 --- a/internal/govendor/subst/export.go +++ b/internal/govendor/subst/export.go @@ -4,7 +4,6 @@ package subst import ( - "fmt" "go/types" ) @@ -17,17 +16,16 @@ type Subster struct { impl *subster } -// New creates a new Subster with a given list of type parameters and matching args. -func New(tc *types.Context, tParams *types.TypeParamList, tArgs []types.Type) *Subster { - if tParams.Len() != len(tArgs) { - panic(fmt.Errorf("number of type parameters and arguments must match: %d => %d", tParams.Len(), len(tArgs))) - } - - if tParams.Len() == 0 && len(tArgs) == 0 { +// New creates a new Subster with a given a map from type parameters and the arguments +// that should be used to replace them. If the map is empty, nil is returned. +// The function `fn` is used to determine the nesting context of the substitution. +func New(tc *types.Context, fn *types.Func, replacements map[*types.TypeParam]types.Type) *Subster { + if len(replacements) == 0 { return nil } - subst := makeSubster(tc, nil, tParams, tArgs, false) + subst := makeSubster(tc, fn, nil, nil, false) + subst.replacements = replacements return &Subster{impl: subst} } diff --git a/internal/govendor/subst/subst.go b/internal/govendor/subst/subst.go index 825e3c7f1..df1f4cbd8 100644 --- a/internal/govendor/subst/subst.go +++ b/internal/govendor/subst/subst.go @@ -2,71 +2,85 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Copy of https://cs.opensource.google/go/x/tools/+/refs/tags/v0.17.0:go/ssa/subst.go +// Copy of https://cs.opensource.google/go/x/tools/+/refs/tags/v0.33.0:go/ssa/subst.go // Any changes to this copy are labelled with GOPHERJS. package subst import ( "go/types" + + "golang.org/x/tools/go/types/typeutil" ) -// Type substituter for a fixed set of replacement types. +// subster defines a type substitution operation of a set of type parameters +// to type parameter free replacement types. Substitution is done within +// the context of a package-level function instantiation. *Named types +// declared in the function are unique to the instantiation. +// +// For example, given a parameterized function F +// +// func F[S, T any]() any { +// type X struct{ s S; next *X } +// var p *X +// return p +// } // -// A nil *subster is an valid, empty substitution map. It always acts as +// calling the instantiation F[string, int]() returns an interface +// value (*X[string,int], nil) where the underlying value of +// X[string,int] is a struct{s string; next *X[string,int]}. +// +// A nil *subster is a valid, empty substitution map. It always acts as // the identity function. This allows for treating parameterized and // non-parameterized functions identically while compiling to ssa. // // Not concurrency-safe. +// +// Note: Some may find it helpful to think through some of the most +// complex substitution cases using lambda calculus inspired notation. +// subst.typ() solves evaluating a type expression E +// within the body of a function Fn[m] with the type parameters m +// once we have applied the type arguments N. +// We can succinctly write this as a function application: +// +// ((λm. E) N) +// +// go/types does not provide this interface directly. +// So what subster provides is a type substitution operation +// +// E[m:=N] type subster struct { replacements map[*types.TypeParam]types.Type // values should contain no type params cache map[types.Type]types.Type // cache of subst results - ctxt *types.Context // cache for instantiation - scope *types.Scope // *types.Named declared within this scope can be substituted (optional) - debug bool // perform extra debugging checks + origin *types.Func // types.Objects declared within this origin function are unique within this context + ctxt *types.Context // speeds up repeated instantiations + uniqueness typeutil.Map // determines the uniqueness of the instantiations within the function // TODO(taking): consider adding Pos - // TODO(zpavlinovic): replacements can contain type params - // when generating instances inside of a generic function body. } // Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache. // targs should not contain any types in tparams. -// scope is the (optional) lexical block of the generic function for which we are substituting. -func makeSubster(ctxt *types.Context, scope *types.Scope, tparams *types.TypeParamList, targs []types.Type, debug bool) *subster { +// fn is the generic function for which we are substituting. +func makeSubster(ctxt *types.Context, fn *types.Func, tparams *types.TypeParamList, targs []types.Type, debug bool) *subster { assert(tparams.Len() == len(targs), "makeSubster argument count must match") + // GOPHERJS: Made `fn` optional so that we can use this on package level types too. + var origin *types.Func + if fn != nil { + origin = fn.Origin() + } + subst := &subster{ replacements: make(map[*types.TypeParam]types.Type, tparams.Len()), cache: make(map[types.Type]types.Type), + origin: origin, ctxt: ctxt, - scope: scope, - debug: debug, } for i := 0; i < tparams.Len(); i++ { subst.replacements[tparams.At(i)] = targs[i] } - if subst.debug { - subst.wellFormed() - } return subst } -// wellFormed asserts that subst was properly initialized. -func (subst *subster) wellFormed() { - if subst == nil { - return - } - // Check that all of the type params do not appear in the arguments. - s := make(map[types.Type]bool, len(subst.replacements)) - for tparam := range subst.replacements { - s[tparam] = true - } - for _, r := range subst.replacements { - if reaches(r, s) { - panic(subst) - } - } -} - // typ returns the type of t with the type parameter tparams[i] substituted // for the type targs[i] where subst was created using tparams and targs. func (subst *subster) typ(t types.Type) (res types.Type) { @@ -80,11 +94,8 @@ func (subst *subster) typ(t types.Type) (res types.Type) { subst.cache[t] = res }() - // fall through if result r will be identical to t, types.Identical(r, t). switch t := t.(type) { case *types.TypeParam: - // GOPHERJS: Replaced an assert that was causing a panic for nested types with code from - // https://cs.opensource.google/go/x/tools/+/refs/tags/v0.33.0:go/ssa/subst.go;l=92 if r := subst.replacements[t]; r != nil { return r } @@ -140,9 +151,17 @@ func (subst *subster) typ(t types.Type) (res types.Type) { case *types.Interface: return subst.interface_(t) + // GOPHERJS: Removed following case since types.Alias is not supported until go1.22. + //case *types.Alias: + // return subst.alias(t) + case *types.Named: return subst.named(t) + // GOPHERJS: Removed following case since the opaque type is specific to the SSA builder. + //case *opaqueType: + // return t // opaque types are never substituted + default: panic("unreachable") } @@ -192,7 +211,7 @@ func (subst *subster) struct_(t *types.Struct) *types.Struct { return t } -// varlist reutrns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i. +// varlist returns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i. func (subst *subster) varlist(in varlist) []*types.Var { var out []*types.Var // nil => no updates for i, n := 0, in.Len(); i < n; i++ { @@ -217,7 +236,7 @@ func (subst *subster) var_(v *types.Var) *types.Var { if v.IsField() { return types.NewField(v.Pos(), v.Pkg(), v.Name(), typ, v.Embedded()) } - return types.NewVar(v.Pos(), v.Pkg(), v.Name(), typ) + return types.NewParam(v.Pos(), v.Pkg(), v.Name(), typ) } } return v @@ -256,6 +275,8 @@ func (subst *subster) interface_(iface *types.Interface) *types.Interface { var methods []*types.Func initMethods := func(n int) { // copy first n explicit methods methods = make([]*types.Func, iface.NumExplicitMethods()) + // GOPHERJS: Replaced a range over count since that's not supported in go1.22 + //for i := range n { for i := 0; i < n; i++ { f := iface.ExplicitMethod(i) norecv := changeRecv(f.Type().(*types.Signature), nil) @@ -280,6 +301,8 @@ func (subst *subster) interface_(iface *types.Interface) *types.Interface { var embeds []types.Type initEmbeds := func(n int) { // copy first n embedded types embeds = make([]types.Type, iface.NumEmbeddeds()) + // GOPHERJS: Replaced a range over count since that's not supported in go1.22 + //for i := range n { for i := 0; i < n; i++ { embeds[i] = iface.EmbeddedType(i) } @@ -307,72 +330,179 @@ func (subst *subster) interface_(iface *types.Interface) *types.Interface { return types.NewInterfaceType(methods, embeds).Complete() } +// GOPHERJS: removed alias substitution since types.Alias is not supported until go1.22 +//func (subst *subster) alias(t *types.Alias) types.Type { ... } + func (subst *subster) named(t *types.Named) types.Type { - // A named type may be: - // (1) ordinary named type (non-local scope, no type parameters, no type arguments), - // (2) locally scoped type, - // (3) generic (type parameters but no type arguments), or - // (4) instantiated (type parameters and type arguments). - tparams := t.TypeParams() - if tparams.Len() == 0 { - if subst.scope != nil && !subst.scope.Contains(t.Obj().Pos()) { - // Outside the current function scope? - return t // case (1) ordinary + // A Named type is a user defined type. + // Ignoring generics, Named types are canonical: they are identical if + // and only if they have the same defining symbol. + // Generics complicate things, both if the type definition itself is + // parameterized, and if the type is defined within the scope of a + // parameterized function. In this case, two named types are identical if + // and only if their identifying symbols are identical, and all type + // arguments bindings in scope of the named type definition (including the + // type parameters of the definition itself) are equivalent. + // + // Notably: + // 1. For type definition type T[P1 any] struct{}, T[A] and T[B] are identical + // only if A and B are identical. + // 2. Inside the generic func Fn[m any]() any { type T struct{}; return T{} }, + // the result of Fn[A] and Fn[B] have identical type if and only if A and + // B are identical. + // 3. Both 1 and 2 could apply, such as in + // func F[m any]() any { type T[x any] struct{}; return T{} } + // + // A subster replaces type parameters within a function scope, and therefore must + // also replace free type parameters in the definitions of local types. + // + // Note: There are some detailed notes sprinkled throughout that borrow from + // lambda calculus notation. These contain some over simplifying math. + // + // LC: One way to think about subster is that it is a way of evaluating + // ((λm. E) N) as E[m:=N]. + // Each Named type t has an object *TypeName within a scope S that binds an + // underlying type expression U. U can refer to symbols within S (+ S's ancestors). + // Let x = t.TypeParams() and A = t.TypeArgs(). + // Each Named type t is then either: + // U where len(x) == 0 && len(A) == 0 + // λx. U where len(x) != 0 && len(A) == 0 + // ((λx. U) A) where len(x) == len(A) + // In each case, we will evaluate t[m:=N]. + tparams := t.TypeParams() // x + targs := t.TypeArgs() // A + + if !declaredWithin(t.Obj(), subst.origin) { + // t is declared outside of Fn[m]. + // + // In this case, we can skip substituting t.Underlying(). + // The underlying type cannot refer to the type parameters. + // + // LC: Let free(E) be the set of free type parameters in an expression E. + // Then whenever m ∉ free(E), then E = E[m:=N]. + // t ∉ Scope(fn) so therefore m ∉ free(U) and m ∩ x = ∅. + if targs.Len() == 0 { + // t has no type arguments. So it does not need to be instantiated. + // + // This is the normal case in real Go code, where t is not parameterized, + // declared at some package scope, and m is a TypeParam from a parameterized + // function F[m] or method. + // + // LC: m ∉ free(A) lets us conclude m ∉ free(t). So t=t[m:=N]. + return t } - // case (2) locally scoped type. - // Create a new named type to represent this instantiation. - // We assume that local types of distinct instantiations of a - // generic function are distinct, even if they don't refer to - // type parameters, but the spec is unclear; see golang/go#58573. + // t is declared outside of Fn[m] and has type arguments. + // The type arguments may contain type parameters m so + // substitute the type arguments, and instantiate the substituted + // type arguments. + // + // LC: Evaluate this as ((λx. U) A') where A' = A[m := N]. + newTArgs := subst.typelist(targs) + return subst.instantiate(t.Origin(), newTArgs) + } + + // t is declared within Fn[m]. + + if targs.Len() == 0 { // no type arguments? + assert(t == t.Origin(), "local parameterized type abstraction must be an origin type") + + // t has no type arguments. + // The underlying type of t may contain the function's type parameters, + // replace these, and create a new type. // // Subtle: We short circuit substitution and use a newly created type in - // subst, i.e. cache[t]=n, to pre-emptively replace t with n in recursive - // types during traversal. This both breaks infinite cycles and allows for - // constructing types with the replacement applied in subst.typ(under). + // subst, i.e. cache[t]=fresh, to preemptively replace t with fresh + // in recursive types during traversal. This both breaks infinite cycles + // and allows for constructing types with the replacement applied in + // subst.typ(U). // - // Example: - // func foo[T any]() { - // type linkedlist struct { - // next *linkedlist - // val T - // } - // } + // A new copy of the Named and Typename (and constraints) per function + // instantiation matches the semantics of Go, which treats all function + // instantiations F[N] as having distinct local types. // - // When the field `next *linkedlist` is visited during subst.typ(under), - // we want the substituted type for the field `next` to be `*n`. - n := types.NewNamed(t.Obj(), nil, nil) - subst.cache[t] = n - subst.cache[n] = n - n.SetUnderlying(subst.typ(t.Underlying())) - return n + // LC: x.Len()=0 can be thought of as a special case of λx. U. + // LC: Evaluate (λx. U)[m:=N] as (λx'. U') where U'=U[x:=x',m:=N]. + tname := t.Obj() + obj := types.NewTypeName(tname.Pos(), tname.Pkg(), tname.Name(), nil) + fresh := types.NewNamed(obj, nil, nil) + var newTParams []*types.TypeParam + for i := 0; i < tparams.Len(); i++ { + cur := tparams.At(i) + cobj := cur.Obj() + cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil) + ntp := types.NewTypeParam(cname, nil) + // GOPHERJS: The following cache was removed because it causes a + // problem for recursive types, e.g. `type X[T any] Q[X[T]]`. + // When it sees the `X[T]` in `Q[X[T]]`, it creates a `subOrigin` + // (seen below) which caches the old `T` to the new `T'`. + // Then when creating `subTArgs` (seen below), it will return + // `T'` via the cache instead of substituting `T` with `string`. + //subst.cache[cur] = ntp + newTParams = append(newTParams, ntp) + } + fresh.SetTypeParams(newTParams) + subst.cache[t] = fresh + subst.cache[fresh] = fresh + fresh.SetUnderlying(subst.typ(t.Underlying())) + // Substitute into all of the constraints after they are created. + for i, ntp := range newTParams { + bound := tparams.At(i).Constraint() + ntp.SetConstraint(subst.typ(bound)) + } + return fresh + } + + // t is defined within Fn[m] and t has type arguments (an instantiation). + // We reduce this to the two cases above: + // (1) substitute the function's type parameters into t.Origin(). + // (2) substitute t's type arguments A and instantiate the updated t.Origin() with these. + // + // LC: Evaluate ((λx. U) A)[m:=N] as (t' A') where t' = (λx. U)[m:=N] and A'=A [m:=N] + + // GOPHERJS: The following causes problems in recursive types. This is because, + // as the types.Instantiate method states, named types are "substituted lazily". + // This means if we have a recursive type with the type parameter `T` and + // they recreate the type, it will not substitute `T` with the new type argument + // inside of the type because `T` is not in the replacement map. + // The following changes make the substitution right away by temporarily + // adding the `subTArgs` to the replacement map. + // Original code: + //subOrigin := subst.typ(t.Origin()) + //subTArgs := subst.typelist(targs) + //return subst.instantiate(subOrigin, subTArgs) + subTArgs := subst.typelist(targs) + oldReplacemets := subst.replacements + defer func() { + subst.replacements = oldReplacemets + }() + subst.replacements = make(map[*types.TypeParam]types.Type, tparams.Len()) + for k, v := range oldReplacemets { + subst.replacements[k] = v + } + for i := 0; i < tparams.Len(); i++ { + subst.replacements[tparams.At(i)] = subTArgs[i] } - targs := t.TypeArgs() - - // insts are arguments to instantiate using. - insts := make([]types.Type, tparams.Len()) - - // case (3) generic ==> targs.Len() == 0 - // Instantiating a generic with no type arguments should be unreachable. - // Please report a bug if you encounter this. - assert(targs.Len() != 0, "substition into a generic Named type is currently unsupported") - - // case (4) instantiated. - // Substitute into the type arguments and instantiate the replacements/ - // Example: - // type N[A any] func() A - // func Foo[T](g N[T]) {} - // To instantiate Foo[string], one goes through {T->string}. To get the type of g - // one subsitutes T with string in {N with typeargs == {T} and typeparams == {A} } - // to get {N with TypeArgs == {string} and typeparams == {A} }. - assert(targs.Len() == tparams.Len(), "typeargs.Len() must match typeparams.Len() if present") - for i, n := 0, targs.Len(); i < n; i++ { - inst := subst.typ(targs.At(i)) // TODO(generic): Check with rfindley for mutual recursion - insts[i] = inst + subOrigin := subst.typ(t.Origin()) + return subst.instantiate(subOrigin, subTArgs) +} + +func (subst *subster) instantiate(orig types.Type, targs []types.Type) types.Type { + i, err := types.Instantiate(subst.ctxt, orig, targs, false) + assert(err == nil, "failed to Instantiate named (Named or Alias) type") + if c, _ := subst.uniqueness.At(i).(types.Type); c != nil { + return c.(types.Type) + } + subst.uniqueness.Set(i, i) + return i +} + +func (subst *subster) typelist(l *types.TypeList) []types.Type { + res := make([]types.Type, l.Len()) + for i := 0; i < l.Len(); i++ { + res[i] = subst.typ(l.At(i)) } - r, err := types.Instantiate(subst.ctxt, t.Origin(), insts, false) - assert(err == nil, "failed to Instantiate Named type") - return r + return res } func (subst *subster) signature(t *types.Signature) types.Type { @@ -471,6 +601,8 @@ func reaches(t types.Type, c map[types.Type]bool) (res bool) { return true } } + // GOPHERJS: Removed types.Alias from following case since it's not supported until go1.22. + //case *types.Named, *types.Alias: case *types.Named: return reaches(t.Underlying(), c) default: diff --git a/internal/govendor/subst/util.go b/internal/govendor/subst/util.go index 5b55c0310..edfa513ba 100644 --- a/internal/govendor/subst/util.go +++ b/internal/govendor/subst/util.go @@ -4,11 +4,14 @@ package subst -import "go/types" +import ( + "go/token" + "go/types" +) // assert panics with the mesage msg if p is false. // Avoid combining with expensive string formatting. -// From https://cs.opensource.google/go/x/tools/+/refs/tags/v0.17.0:go/ssa/util.go;l=27 +// From https://cs.opensource.google/go/x/tools/+/refs/tags/v0.33.0:go/ssa/util.go;l=28 func assert(p bool, msg string) { if !p { panic(msg) @@ -19,3 +22,29 @@ func assert(p bool, msg string) { func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic()) } + +// declaredWithin reports whether an object is declared within a function. +// +// obj must not be a method or a field. +// From https://cs.opensource.google/go/x/tools/+/refs/tags/v0.33.0:go/ssa/util.go;l=145 +func declaredWithin(obj types.Object, fn *types.Func) bool { + // GOPHERJS: Made `fn` optional so that we can use this on package level types too. + if fn == nil { + return false + } + + if obj.Pos() != token.NoPos { + return fn.Scope().Contains(obj.Pos()) // trust the positions if they exist. + } + if fn.Pkg() != obj.Pkg() { + return false // fast path for different packages + } + + // Traverse Parent() scopes for fn.Scope(). + for p := obj.Parent(); p != nil; p = p.Parent() { + if p == fn.Scope() { + return true + } + } + return false +} diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 6720f50d7..91b145cb7 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -153,10 +153,10 @@ var knownFails = map[string]failReason{ "typeparam/issue51733.go": {category: usesUnsupportedPackage, desc: "unsafe: uintptr to struct pointer conversion is unsupported"}, "typeparam/typeswitch5.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, - // Failures related to the lack of generics support. Ideally, this section - // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is - // fixed. - "typeparam/nested.go": {category: usesUnsupportedGenerics, desc: "incomplete support for generic types inside generic functions"}, + // Failures related to the lack of nested type number indicators. + // For example, this test outputs `4,7: main.T·2[int;main.U·3[int;int]]` in Go (see "typeparam/nested.out" for example output) + // but `4,7: main.T[int;main.U[int;int]]` in GopherJS because we doesn't currently add the `·2` and `·3` indicators to the type names. + "typeparam/nested.go": {category: other, desc: "incomplete support for nested type numbering"}, // These are new tests in Go 1.19 "typeparam/issue51521.go": {category: lowLevelRuntimeDifference, desc: "different panic message when calling a method on nil interface"}, @@ -171,7 +171,6 @@ const ( neverTerminates // Test never terminates (so avoid starting it). usesUnsupportedPackage // Test fails because it imports an unsupported package, e.g., "unsafe". requiresSourceMapSupport // Test fails without source map support (as configured in CI), because it tries to check filename/line number via runtime.Caller. - usesUnsupportedGenerics // Test uses generics (type parameters) that are not currently supported. compilerPanic unsureIfGopherJSSupportsThisFeature lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around.