diff --git a/compiler/decls.go b/compiler/decls.go index eb95cd2f7..5c6291ba6 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -451,13 +451,7 @@ 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 @@ -469,10 +463,7 @@ func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, er } instanceType = instantiated.(*types.Named) } - if len(inst.TNest) > 0 { - instantiated := nestResolver.Substitute(instanceType) - instanceType = instantiated.(*types.Named) - } + instanceType = fc.typeResolver.Substitute(instanceType).(*types.Named) } underlying := instanceType.Underlying() diff --git a/compiler/expressions.go b/compiler/expressions.go index 781a37a3e..455549a1d 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -531,7 +531,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case *types.Signature: return fc.formatExpr("%s", fc.instName(fc.instanceOf(e.X.(*ast.Ident)))) default: - panic(fmt.Errorf(`unhandled IndexExpr: %T`, t)) + panic(fmt.Errorf(`unhandled IndexExpr: %T in %T`, t, fc.typeOf(e.X))) } case *ast.IndexListExpr: switch t := fc.typeOf(e.X).Underlying().(type) { diff --git a/compiler/functions.go b/compiler/functions.go index 361c92f0f..d4484101b 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -48,13 +48,9 @@ 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{} + // Use the parent function's resolver unless the function has it's own type arguments. + if !inst.IsTrivial() { + c.typeResolver = typeparams.NewResolver(fc.pkgCtx.typesCtx, inst) } // Synthesize an identifier by which the function may reference itself. Since 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/typeparams/collect.go b/compiler/internal/typeparams/collect.go index 940690e83..f18e6eb20 100644 --- a/compiler/internal/typeparams/collect.go +++ b/compiler/internal/typeparams/collect.go @@ -4,143 +4,8 @@ import ( "fmt" "go/ast" "go/types" - "strings" - - "github.com/gopherjs/gopherjs/compiler/typesutil" - "github.com/gopherjs/gopherjs/internal/govendor/subst" ) -// Resolver translates types defined in terms of type parameters into concrete -// types, given a mapping from type params to type arguments. -type Resolver struct { - tParams *types.TypeParamList - tArgs []types.Type - parent *Resolver - - // 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. - 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{ - tParams: tParams, - tArgs: tArgs, - parent: parent, - subster: subst.New(tc, tParams, tArgs), - selMemo: map[typesutil.Selection]typesutil.Selection{}, - } - return r -} - -// TypeParams is the list of type parameters that this resolver -// (not any parent) will substitute. -func (r *Resolver) TypeParams() *types.TypeParamList { - if r == nil { - return nil - } - return r.tParams -} - -// TypeArgs is the list of type arguments that this resolver -// (not any parent) will resolve to. -func (r *Resolver) TypeArgs() []types.Type { - if r == nil { - return nil - } - 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 -} - -// SubstituteAll same as Substitute, but accepts a TypeList are returns -// substitution results as a slice in the same order. -func (r *Resolver) SubstituteAll(list *types.TypeList) []types.Type { - result := make([]types.Type, list.Len()) - for i := range result { - result[i] = r.Substitute(list.At(i)) - } - return result -} - -// SubstituteSelection replaces a method of field selection on a generic type -// defined in terms of type parameters with a method selection on a concrete -// instantiation of the type. -func (r *Resolver) SubstituteSelection(sel typesutil.Selection) typesutil.Selection { - if r == nil || sel == nil { - return sel // No substitutions to be made. - } - if concrete, ok := r.selMemo[sel]; ok { - return concrete - } - - switch sel.Kind() { - case types.MethodExpr, types.MethodVal, types.FieldVal: - recv := r.Substitute(sel.Recv()) - if types.Identical(recv, sel.Recv()) { - return sel // Non-generic receiver, no substitution necessary. - } - - // Look up the method on the instantiated receiver. - pkg := sel.Obj().Pkg() - obj, index, _ := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name()) - if obj == nil { - panic(fmt.Errorf("failed to lookup field %q in type %v", sel.Obj().Name(), recv)) - } - typ := obj.Type() - - if sel.Kind() == types.MethodExpr { - typ = typesutil.RecvAsFirstArg(typ.(*types.Signature)) - } - concrete := typesutil.NewSelection(sel.Kind(), recv, index, obj, typ) - r.selMemo[sel] = concrete - return concrete - default: - panic(fmt.Errorf("unexpected selection kind %v: %v", sel.Kind(), sel)) - } -} - -// String gets a strings representation of the resolver for debugging. -func (r *Resolver) String() string { - if r == nil { - return `{}` - } - - parts := make([]string, 0, len(r.tArgs)) - 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, `, `) + `}` -} - // visitor implements ast.Visitor and collects instances of generic types and // functions into an InstanceSet. // @@ -151,7 +16,9 @@ type visitor struct { instances *PackageInstanceSets resolver *Resolver info *types.Info - tNest []types.Type // The type arguments for a nested context. + + nestTParams *types.TypeParamList // The type parameters for a nested context. + nestTArgs []types.Type // The type arguments for a nested context. } var _ ast.Visitor = &visitor{} @@ -195,12 +62,14 @@ func (c *visitor) visitInstance(ident *ast.Ident, inst types.Instance) { // If the object is defined in the same scope as the instance, // then we apply the current nested type arguments. - var tNest []types.Type + var nestTParams *types.TypeParamList + var nestTArgs []types.Type if obj.Parent().Contains(ident.Pos()) { - tNest = c.tNest + nestTParams = c.nestTParams + nestTArgs = c.nestTArgs } - c.addInstance(obj, tArgs, tNest) + c.addInstance(obj, tArgs, nestTParams, nestTArgs) } func (c *visitor) visitNestedType(obj types.Object) { @@ -222,12 +91,12 @@ func (c *visitor) visitNestedType(obj types.Object) { return } - c.addInstance(obj, nil, c.resolver.TypeArgs()) + c.addInstance(obj, nil, c.resolver.TypeParams(), c.resolver.TypeArgs()) } -func (c *visitor) addInstance(obj types.Object, tArgList *types.TypeList, tNest []types.Type) { +func (c *visitor) addInstance(obj types.Object, tArgList *types.TypeList, nestTParams *types.TypeParamList, nestTArgs []types.Type) { tArgs := c.resolver.SubstituteAll(tArgList) - if isGeneric(tArgs...) { + if isGeneric(nestTParams, tArgs) { // Skip any instances that still have type parameters in them after // substitution. This occurs when a type is defined while nested // in a generic context and is not fully instantiated yet. @@ -238,7 +107,7 @@ func (c *visitor) addInstance(obj types.Object, tArgList *types.TypeList, tNest c.instances.Add(Instance{ Object: obj, TArgs: tArgs, - TNest: tNest, + TNest: nestTArgs, }) if t, ok := obj.Type().(*types.Named); ok { @@ -247,7 +116,7 @@ func (c *visitor) addInstance(obj types.Object, tArgList *types.TypeList, tNest c.instances.Add(Instance{ Object: method.Origin(), TArgs: tArgs, - TNest: tNest, + TNest: nestTArgs, }) } } @@ -358,12 +227,13 @@ 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, + + nestTParams: SignatureTypeParams(typ), + nestTArgs: inst.TArgs, } ast.Walk(&v, objMap[inst.Object]) } @@ -377,18 +247,19 @@ 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) + var nestTParams *types.TypeParamList + nest := FindNestingFunc(obj) + if nest != nil { + nestTParams = SignatureTypeParams(nest.Type().(*types.Signature)) } 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, + + nestTParams: nestTParams, + nestTArgs: inst.TNest, } ast.Walk(&v, node) } diff --git a/compiler/internal/typeparams/collect_test.go b/compiler/internal/typeparams/collect_test.go index 6864e5ead..26791ea59 100644 --- a/compiler/internal/typeparams/collect_test.go +++ b/compiler/internal/typeparams/collect_test.go @@ -218,10 +218,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 +244,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 +278,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")), }, { @@ -316,7 +316,8 @@ func TestVisitor(t *testing.T) { if test.resolver != nil { // Since we know all the tests are for functions and methods, // set the nested type to the type parameter from the resolver. - v.tNest = test.resolver.tArgs + v.nestTParams = test.resolver.tParams + v.nestTArgs = test.resolver.tArgs } ast.Walk(&v, test.node) got := v.instances.Pkg(pkg).Values() @@ -534,6 +535,12 @@ func TestCollector_NestingWithVars(t *testing.T) { f := srctesting.New(t) file := f.Parse(`test.go`, src) info, pkg := f.Check(`pkg/test`, file) + inst := func(name, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } c := Collector{ TContext: types.NewContext(), @@ -542,16 +549,9 @@ func TestCollector_NestingWithVars(t *testing.T) { } c.Scan(pkg, file) - inst := func(name, tNest, tArg string) Instance { - return Instance{ - Object: srctesting.LookupObj(pkg, name), - TNest: evalTypeArgs(t, f.FileSet, pkg, tNest), - TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), - } - } want := []Instance{ - inst(`S`, ``, `int`), - inst(`S.echo`, ``, `int`), + inst(`S`, `int`), + inst(`S.echo`, `int`), } got := c.Instances.Pkg(pkg).Values() if diff := cmp.Diff(want, got, instanceOpts()); diff != `` { @@ -571,33 +571,34 @@ func TestCollector_RecursiveTypeParams(t *testing.T) { ` f := srctesting.New(t) + tc := types.NewContext() file := f.Parse(`test.go`, src) info, pkg := f.Check(`test`, file) + inst := func(name, tNest, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TNest: evalTypeArgs(t, f.FileSet, pkg, tNest), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } c := Collector{ - TContext: types.NewContext(), + TContext: tc, Info: info, Instances: &PackageInstanceSets{}, } c.Scan(pkg, file) - tInt := types.Typ[types.Int] - xAny := srctesting.LookupObj(pkg, `main.X`) - xInt, err := types.Instantiate(types.NewContext(), xAny.Type(), []types.Type{tInt}, true) - if err != nil { - t.Fatalf("Failed to instantiate X[int]: %v", err) - } - + xInst := inst(`main.X`, ``, `int`) + xInt := xInst.Resolve(tc) want := []Instance{ + xInst, { Object: srctesting.LookupObj(pkg, `F`), TArgs: []types.Type{xInt}, }, { Object: srctesting.LookupObj(pkg, `main.U`), TArgs: []types.Type{xInt}, - }, { - Object: xAny, - TArgs: []types.Type{tInt}, }, } got := c.Instances.Pkg(pkg).Values() @@ -607,13 +608,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,38 +621,72 @@ 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) + inst := func(name, tNest, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TNest: evalTypeArgs(t, f.FileSet, pkg, tNest), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } c := Collector{ - TContext: types.NewContext(), + TContext: tc, Info: info, Instances: &PackageInstanceSets{}, } c.Scan(pkg, file) - xAny := srctesting.LookupObj(pkg, `F.X`) - xInt, err := types.Instantiate(types.NewContext(), xAny.Type(), []types.Type{types.Typ[types.Int]}, true) - if err != nil { - t.Fatalf("Failed to instantiate X[int]: %v", err) + xInst := inst(`F.X`, `string`, `int`) + xInt := xInst.Resolve(tc) + want := []Instance{ + inst(`F`, ``, `string`), + xInst, + { + Object: srctesting.LookupObj(pkg, `F.U`), + TNest: []types.Type{types.Typ[types.String]}, + 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) } - // 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()) +} +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) + + xInst := Instance{ + Object: srctesting.LookupObj(pkg, `main.X`), + TArgs: []types.Type{types.Typ[types.Int]}, + } + xInt := xInst.Resolve(tc) want := []Instance{ + xInst, { - Object: srctesting.LookupObj(pkg, `F`), - TArgs: []types.Type{types.Typ[types.String]}, - }, { - Object: srctesting.LookupObj(pkg, `F.U`), - TNest: []types.Type{types.Typ[types.String]}, + Object: srctesting.LookupObj(pkg, `main.U`), TArgs: []types.Type{xInt}, - }, { - Object: xAny, - TNest: []types.Type{types.Typ[types.String]}, - TArgs: []types.Type{types.Typ[types.Int]}, }, } got := c.Instances.Pkg(pkg).Values() @@ -668,19 +696,12 @@ 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. - // 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,38 +709,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) + inst := func(name, tNest, tArg string) Instance { + return Instance{ + Object: srctesting.LookupObj(pkg, name), + TNest: evalTypeArgs(t, f.FileSet, pkg, tNest), + TArgs: evalTypeArgs(t, f.FileSet, pkg, tArg), + } + } 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) - if err != nil { - t.Fatalf("Failed to instantiate U[int]: %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()) - } - + uInst := inst(`F.U`, `int`, `bool`) + uIntBool := uInst.Resolve(tc) want := []Instance{ + inst(`F`, ``, `int`), + inst(`F.U`, `int`, `bool`), { - Object: srctesting.LookupObj(pkg, `F`), - TArgs: []types.Type{types.Typ[types.Int]}, - }, { - Object: srctesting.LookupObj(pkg, `F.U`), - TNest: []types.Type{types.Typ[types.Int]}, - TArgs: []types.Type{types.Typ[types.Int]}, - }, { 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 +874,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..10e0df69f 100644 --- a/compiler/internal/typeparams/instance.go +++ b/compiler/internal/typeparams/instance.go @@ -106,6 +106,33 @@ func (i Instance) Recv() Instance { } } +// Resolve instantiates and performs a substitution of the instance +// to get the concrete type or function. +// This will panic if the instance is not valid, e.g. if there are a different +// number of type arguments than the type parameters. +// +// If `tc` is non-nil, it de-duplicates the instance against previous +// instances with the same identity. See types.Instantiate for more info. +// +// Instances of named types may be lazily substituted, meaning the underlying +// type may not be fully substituted with the type arguments when returned. +// +// This is useful for quickly resolving an instance for a test or for debugging +// but this uses a temporary Resolver that will not be reused. +// When resolving several instances in the same context, it is more efficient +// to use NewResolver to take advantage of caching. +func (i Instance) Resolve(tc *types.Context) types.Type { + instType := i.Object.Type() + if len(i.TArgs) > 0 { + var err error + instType, err = types.Instantiate(tc, instType, i.TArgs, true) + if err != nil { + panic(fmt.Errorf("failed to instantiate %v: %w", i, err)) + } + } + return NewResolver(tc, i).Substitute(instType) +} + // InstanceSet allows collecting and processing unique Instances. // // Each Instance may be added to the set any number of times, but it will be diff --git a/compiler/internal/typeparams/resolver.go b/compiler/internal/typeparams/resolver.go new file mode 100644 index 000000000..5718c364d --- /dev/null +++ b/compiler/internal/typeparams/resolver.go @@ -0,0 +1,215 @@ +package typeparams + +import ( + "fmt" + "go/types" + "sort" + "strings" + + "github.com/gopherjs/gopherjs/compiler/typesutil" + "github.com/gopherjs/gopherjs/internal/govendor/subst" +) + +// Resolver translates types defined in terms of type parameters into concrete +// 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 }`. +// Alternatively, the instantiated but still generic because of the `T`, +// `Bar[bool] struct { x T; y bool}` will be substituted for `Foo[int]` 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 { + tParams *types.TypeParamList + tArgs []types.Type + nest *types.Func + nestTParams *types.TypeParamList + nestTArgs []types.Type + replacements map[*types.TypeParam]types.Type + root Instance + + // subster is the substitution helper that will perform the actual + // substitutions. This maybe nil when there are no substitutions but + // will still be usable when nil. + subster *subst.Subster + selMemo map[typesutil.Selection]typesutil.Selection +} + +// 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 ( + nest *types.Func + nestTParams *types.TypeParamList + tParams *types.TypeParamList + replacements = map[*types.TypeParam]types.Type{} + ) + + switch typ := root.Object.Type().(type) { + case *types.Signature: + nest = root.Object.(*types.Func) + tParams = SignatureTypeParams(typ) + case *types.Named: + tParams = typ.TypeParams() + nest = FindNestingFunc(root.Object) + if nest != nil { + nestTParams = SignatureTypeParams(nest.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{ + tParams: tParams, + tArgs: root.TArgs, + nest: nest, + nestTParams: nestTParams, + nestTArgs: root.TNest, + replacements: replacements, + root: root, + subster: subst.New(tc, replacements), + selMemo: map[typesutil.Selection]typesutil.Selection{}, + } +} + +// TypeParams is the list of type parameters that this resolver will substitute. +func (r *Resolver) TypeParams() *types.TypeParamList { + if r == nil { + return nil + } + return r.tParams +} + +// TypeArgs is the list of type arguments that this resolver will resolve to. +func (r *Resolver) TypeArgs() []types.Type { + if r == nil { + return nil + } + return r.tArgs +} + +// Nest is the nesting function that this resolver will resolve types with. +// This will be null if the resolver is not for a nested context, +func (r *Resolver) Nest() *types.Func { + if r == nil { + return nil + } + return r.nest +} + +// NestTypeParams is the list of type parameters from the nesting function +// that this resolver will substitute. +func (r *Resolver) NestTypeParams() *types.TypeParamList { + if r == nil { + return nil + } + return r.nestTParams +} + +// NestTypeArgs is the list of type arguments from the nesting function +// that this resolver will resolve to. +func (r *Resolver) NestTypeArgs() []types.Type { + if r == nil { + return nil + } + return r.nestTArgs +} + +// 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. + } + return r.subster.Type(typ) +} + +// SubstituteAll same as Substitute, but accepts a TypeList are returns +// substitution results as a slice in the same order. +func (r *Resolver) SubstituteAll(list *types.TypeList) []types.Type { + result := make([]types.Type, list.Len()) + for i := range result { + result[i] = r.Substitute(list.At(i)) + } + return result +} + +// SubstituteSelection replaces a method of field selection on a generic type +// defined in terms of type parameters with a method selection on a concrete +// instantiation of the type. +func (r *Resolver) SubstituteSelection(sel typesutil.Selection) typesutil.Selection { + if r == nil || sel == nil { + return sel // No substitutions to be made. + } + if concrete, ok := r.selMemo[sel]; ok { + return concrete + } + + switch sel.Kind() { + case types.MethodExpr, types.MethodVal, types.FieldVal: + recv := r.Substitute(sel.Recv()) + if types.Identical(recv, sel.Recv()) { + return sel // Non-generic receiver, no substitution necessary. + } + + // Look up the method on the instantiated receiver. + pkg := sel.Obj().Pkg() + obj, index, _ := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name()) + if obj == nil { + panic(fmt.Errorf("failed to lookup field %q in type %v", sel.Obj().Name(), recv)) + } + typ := obj.Type() + + if sel.Kind() == types.MethodExpr { + typ = typesutil.RecvAsFirstArg(typ.(*types.Signature)) + } + concrete := typesutil.NewSelection(sel.Kind(), recv, index, obj, typ) + r.selMemo[sel] = concrete + return concrete + default: + panic(fmt.Errorf("unexpected selection kind %v: %v", sel.Kind(), sel)) + } +} + +// String gets a strings representation of the resolver for debugging. +func (r *Resolver) String() string { + if r == nil { + return `{}` + } + + parts := make([]string, 0, len(r.replacements)) + for tp, ta := range r.replacements { + parts = append(parts, fmt.Sprintf("%s->%s", tp, ta)) + } + sort.Strings(parts) + return `{` + strings.Join(parts, `, `) + `}` +} diff --git a/compiler/internal/typeparams/utils.go b/compiler/internal/typeparams/utils.go index ea528314e..9fcf53c36 100644 --- a/compiler/internal/typeparams/utils.go +++ b/compiler/internal/typeparams/utils.go @@ -28,16 +28,16 @@ func FindNestingFunc(obj types.Object) *types.Func { return nil } - scope := obj.Parent() + // We can't use `obj.Parent()` here since some types don't have a set + // parent, such as types created with `types.NewTypeName`. Instead find + // the innermost scope from the package to use as the object's parent scope. + scope := obj.Pkg().Scope().Innermost(objPos) for scope != nil { // Iterate over all declarations in the scope. for _, name := range scope.Names() { decl := scope.Lookup(name) - if fn, ok := decl.(*types.Func); ok { - // Check if the object's position is within the function's scope. - if objPos >= fn.Pos() && objPos <= fn.Scope().End() { - return fn - } + if fn, ok := decl.(*types.Func); ok && fn.Scope().Contains(objPos) { + return fn } } scope = scope.Parent() @@ -85,15 +85,22 @@ func RequiresGenericsSupport(info *types.Info) error { return nil } -// isGeneric will search all the given types and their subtypes for a +// isGeneric will search all the given types in `typ` and their subtypes for a // *types.TypeParam. This will not check if a type could be generic, // but if each instantiation is not completely concrete yet. +// The given `ignore` slice is used to ignore type params that are known not +// to be substituted yet, typically the nest type parameters. +// +// This does allow for named types to have lazily substituted underlying types, +// as returned by methods like `types.Instantiate`, +// meaning that the type `B[T]` may be instantiated to `B[int]` but still have +// the underlying type of `struct { t T }` instead of `struct { t int }`. // // This is useful to check for generics types like `X[B[T]]`, where // `X` appears concrete because it is instantiated with the type argument `B[T]`, // however the `T` inside `B[T]` is a type parameter making `X[B[T]]` a generic // type since it required instantiation to a concrete type, e.g. `X[B[int]]`. -func isGeneric(typ ...types.Type) bool { +func isGeneric(ignore *types.TypeParamList, typ []types.Type) bool { var containsTypeParam func(t types.Type) bool foreach := func(count int, getter func(index int) types.Type) bool { @@ -106,19 +113,34 @@ func isGeneric(typ ...types.Type) bool { } seen := make(map[types.Type]struct{}) + managed := make(map[types.Type]struct{}) + for i := ignore.Len() - 1; i >= 0; i-- { + managed[ignore.At(i)] = struct{}{} + } containsTypeParam = func(t types.Type) bool { if _, ok := seen[t]; ok { return false } seen[t] = struct{}{} + if _, ok := managed[t]; ok { + return false + } + switch t := t.(type) { case *types.TypeParam: return true case *types.Named: - return t.TypeParams().Len() != t.TypeArgs().Len() || - foreach(t.TypeArgs().Len(), func(i int) types.Type { return t.TypeArgs().At(i) }) || - containsTypeParam(t.Underlying()) + if t.TypeParams().Len() != t.TypeArgs().Len() || + foreach(t.TypeArgs().Len(), func(i int) types.Type { return t.TypeArgs().At(i) }) { + return true + } + // Add type parameters to managed so that if they are encountered + // we know that they are just lazy substitutions for the checked type arguments. + for i := t.TypeParams().Len() - 1; i >= 0; i-- { + managed[t.TypeParams().At(i)] = struct{}{} + } + return containsTypeParam(t.Underlying()) case *types.Struct: return foreach(t.NumFields(), func(i int) types.Type { return t.Field(i).Type() }) case *types.Interface: diff --git a/internal/govendor/subst/export.go b/internal/govendor/subst/export.go index 00a77ca49..35e38473c 100644 --- a/internal/govendor/subst/export.go +++ b/internal/govendor/subst/export.go @@ -3,10 +3,7 @@ // type arguments. package subst -import ( - "fmt" - "go/types" -) +import "go/types" // To simplify future updates of the borrowed code, we minimize modifications // to it as much as possible. This file implements an exported interface to the @@ -17,17 +14,15 @@ 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. +func New(tc *types.Context, replacements map[*types.TypeParam]types.Type) *Subster { + if len(replacements) == 0 { return nil } - subst := makeSubster(tc, nil, tParams, tArgs, false) + subst := makeSubster(tc, nil, 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..1ac705625 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,157 @@ 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` (also below), it will return + // `T'` via the cache instead of substituting `T` with the + // correct type argument. + //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] + subOrigin := subst.typ(t.Origin()) + subTArgs := subst.typelist(targs) + 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) } - 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 + 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 +579,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/subst_test.go b/internal/govendor/subst/subst_test.go index 832f0ebd4..8f2f629a1 100644 --- a/internal/govendor/subst/subst_test.go +++ b/internal/govendor/subst/subst_test.go @@ -2,7 +2,7 @@ // 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_test.go +// Copy of https://cs.opensource.google/go/x/tools/+/refs/tags/v0.33.0:go/ssa/subst_test.go package subst import ( @@ -17,6 +17,10 @@ func TestSubst(t *testing.T) { const source = ` package P +func within(){ + // Pretend that the instantiation happens within this function. +} + type t0 int func (t0) f() type t1 interface{ f() } @@ -56,6 +60,11 @@ var _ L[int] = Fn0[L[int]](nil) t.Fatal(err) } + within, _ := pkg.Scope().Lookup("within").(*types.Func) + if within == nil { + t.Fatal("Failed to find the function within()") + } + for _, test := range []struct { expr string // type expression of Named parameterized type args []string // type expressions of args for named @@ -95,7 +104,7 @@ var _ L[int] = Fn0[L[int]](nil) T := tv.Type.(*types.Named) - subst := makeSubster(types.NewContext(), nil, T.TypeParams(), targs, true) + subst := makeSubster(types.NewContext(), within, T.TypeParams(), targs, true) sub := subst.typ(T.Underlying()) if got := sub.String(); got != test.want { t.Errorf("subst{%v->%v}.typ(%s) = %v, want %v", test.expr, test.args, T.Underlying(), got, test.want) 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..3685e99d6 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -153,10 +153,12 @@ 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 and deep nested types not printing correctly. + // For example, the following line from the test's outputs (see "typeparam/nested.out") + // `4,7: main.T·2[int;main.U·3[int;int]]` will currently output as `4,7: main.T[int;main.U[int]]` + // in GopherJS because we doesn't currently add the `·2` and `·3` indicators to the type names + // and the nested type arguments to deep nested type, e.g. `U[int;int]` is printed as `U[int]`. + "typeparam/nested.go": {category: usesUnsupportedGenerics, 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"},