Skip to content

Commit 1e45615

Browse files
Merge pull request #1376 from Workiva/indexedSel
Adding selectors to index handling for generics
2 parents 120c98d + b2ab7b5 commit 1e45615

File tree

8 files changed

+166
-22
lines changed

8 files changed

+166
-22
lines changed

compiler/astutil/astutil.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,11 @@ func IsTypeExpr(expr ast.Expr, info *types.Info) bool {
5353
_, ok := info.Uses[e].(*types.TypeName)
5454
return ok
5555
case *ast.SelectorExpr:
56-
_, ok := info.Uses[e.Sel].(*types.TypeName)
57-
return ok
56+
return IsTypeExpr(e.Sel, info)
5857
case *ast.IndexExpr:
59-
ident, ok := e.X.(*ast.Ident)
60-
if !ok {
61-
return false
62-
}
63-
_, ok = info.Uses[ident].(*types.TypeName)
64-
return ok
58+
return IsTypeExpr(e.X, info)
6559
case *ast.IndexListExpr:
66-
ident, ok := e.X.(*ast.Ident)
67-
if !ok {
68-
return false
69-
}
70-
_, ok = info.Uses[ident].(*types.TypeName)
71-
return ok
60+
return IsTypeExpr(e.X, info)
7261
case *ast.ParenExpr:
7362
return IsTypeExpr(e.X, info)
7463
default:

compiler/compiler_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,78 @@ func Test_CrossPackageAnalysis(t *testing.T) {
896896
)
897897
}
898898

899+
func Test_IndexedSelectors(t *testing.T) {
900+
src1 := `
901+
package main
902+
import "github.com/gopherjs/gopherjs/compiler/other"
903+
func main() {
904+
// Instance IndexExpr with a package SelectorExpr for a function call.
905+
other.PrintZero[int]()
906+
other.PrintZero[string]()
907+
908+
// Instance IndexListExpr with a package SelectorExpr for a function call.
909+
other.PrintZeroZero[int, string]()
910+
911+
// Index IndexExpr with a struct SelectorExpr for a function call.
912+
f := other.Foo{Ops: []func() {
913+
other.PrintZero[int],
914+
other.PrintZero[string],
915+
other.PrintZeroZero[int, string],
916+
}}
917+
f.Ops[0]()
918+
f.Ops[1]()
919+
920+
// Index IndexExpr with a package/var SelectorExpr for a function call.
921+
other.Bar.Ops[0]()
922+
other.Baz[0]()
923+
924+
// IndexExpr with a SelectorExpr for a cast
925+
_ = other.ZHandle[int](other.PrintZero[int])
926+
927+
// IndexListExpr with a SelectorExpr for a cast
928+
_ = other.ZZHandle[int, string](other.PrintZeroZero[int, string])
929+
}`
930+
src2 := `
931+
package other
932+
func PrintZero[T any]() {
933+
var zero T
934+
println("Zero is ", zero)
935+
}
936+
937+
func PrintZeroZero[T any, U any]() {
938+
PrintZero[T]()
939+
PrintZero[U]()
940+
}
941+
942+
type ZHandle[T any] func()
943+
type ZZHandle[T any, U any] func()
944+
945+
type Foo struct { Ops []func() }
946+
var Bar = Foo{Ops: []func() {
947+
PrintZero[int],
948+
PrintZero[string],
949+
}}
950+
var Baz = Bar.Ops`
951+
952+
root := srctesting.ParseSources(t,
953+
[]srctesting.Source{
954+
{Name: `main.go`, Contents: []byte(src1)},
955+
},
956+
[]srctesting.Source{
957+
{Name: `other/other.go`, Contents: []byte(src2)},
958+
})
959+
960+
archives := compileProject(t, root, false)
961+
// We mostly are checking that the code was turned into decls correctly,
962+
// since the issue was that indexed selectors were not being handled correctly,
963+
// so if it didn't panic by this point, it should be fine.
964+
checkForDeclFullNames(t, archives,
965+
`func:command-line-arguments.main`,
966+
`type:github.com/gopherjs/gopherjs/compiler/other.ZHandle<int>`,
967+
`type:github.com/gopherjs/gopherjs/compiler/other.ZZHandle<int, string>`,
968+
)
969+
}
970+
899971
func TestArchiveSelectionAfterSerialization(t *testing.T) {
900972
src := `
901973
package main

compiler/decls.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,25 +282,34 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) {
282282
var mainFunc *types.Func
283283
for _, fun := range functions {
284284
o := fc.pkgCtx.Defs[fun.Name].(*types.Func)
285+
instances := fc.knownInstances(o)
286+
if len(instances) == 0 {
287+
// No instances of the function, skip it.
288+
continue
289+
}
285290

286291
if fun.Recv == nil {
287292
// Auxiliary decl shared by all instances of the function that defines
288-
// package-level variable by which they all are referenced.
293+
// package-level variable by which they all are referenced,
294+
// e.g. init functions and instances of generic functions.
289295
objName := fc.objectName(o)
290296
varDecl := &Decl{
291297
FullName: funcVarDeclFullName(o),
292298
Vars: []string{objName},
293299
}
294300
varDecl.Dce().SetName(o, nil, nil)
295-
if o.Type().(*types.Signature).TypeParams().Len() != 0 {
301+
if len(instances) > 1 || !instances[0].IsTrivial() {
296302
varDecl.DeclCode = fc.CatchOutput(0, func() {
297303
fc.Printf("%s = {};", objName)
304+
if o.Exported() {
305+
fc.Printf("$pkg.%s = %s;", encodeIdent(fun.Name.Name), objName)
306+
}
298307
})
299308
}
300309
funcDecls = append(funcDecls, varDecl)
301310
}
302311

303-
for _, inst := range fc.knownInstances(o) {
312+
for _, inst := range instances {
304313
funcDecls = append(funcDecls, fc.newFuncDecl(fun, inst))
305314

306315
if o.Name() == "main" {

compiler/expressions.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,14 +529,28 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
529529
case *types.Basic:
530530
return fc.formatExpr("%e.charCodeAt(%f)", e.X, e.Index)
531531
case *types.Signature:
532-
return fc.formatExpr("%s", fc.instName(fc.instanceOf(e.X.(*ast.Ident))))
532+
switch u := e.X.(type) {
533+
case *ast.Ident:
534+
return fc.formatExpr("%s", fc.instName(fc.instanceOf(u)))
535+
case *ast.SelectorExpr:
536+
return fc.formatExpr("%s", fc.instName(fc.instanceOf(u.Sel)))
537+
default:
538+
panic(fmt.Errorf("unhandled IndexExpr of a Signature: %T in %T", u, fc.typeOf(e.X)))
539+
}
533540
default:
534541
panic(fmt.Errorf(`unhandled IndexExpr: %T in %T`, t, fc.typeOf(e.X)))
535542
}
536543
case *ast.IndexListExpr:
537544
switch t := fc.typeOf(e.X).Underlying().(type) {
538545
case *types.Signature:
539-
return fc.formatExpr("%s", fc.instName(fc.instanceOf(e.X.(*ast.Ident))))
546+
switch u := e.X.(type) {
547+
case *ast.Ident:
548+
return fc.formatExpr("%s", fc.instName(fc.instanceOf(u)))
549+
case *ast.SelectorExpr:
550+
return fc.formatExpr("%s", fc.instName(fc.instanceOf(u.Sel)))
551+
default:
552+
panic(fmt.Errorf("unhandled IndexListExpr of a Signature: %T in %T", u, fc.typeOf(e.X)))
553+
}
540554
default:
541555
panic(fmt.Errorf("unhandled IndexListExpr: %T", t))
542556
}

compiler/functions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte {
122122
body := fc.translateFunctionBody(fun.Type, nil, fun.Body)
123123
code := bytes.NewBuffer(nil)
124124
fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body)
125-
if fun.Name.IsExported() {
125+
if fun.Name.IsExported() && fc.instance.IsTrivial() {
126126
fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue)
127127
}
128128
return code.Bytes()

compiler/internal/analysis/info.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,21 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr, deferredCall bool) ast.Visito
570570
if astutil.IsTypeExpr(f.Index, fi.pkgInfo.Info) {
571571
// This is a call of an instantiation of a generic function,
572572
// e.g. `foo[int]` in `func foo[T any]() { ... }; func main() { foo[int]() }`
573-
fi.callToNamedFunc(fi.instanceForIdent(f.X.(*ast.Ident)), deferredCall)
573+
var inst typeparams.Instance
574+
switch fxt := f.X.(type) {
575+
case *ast.Ident:
576+
inst = fi.instanceForIdent(fxt)
577+
case *ast.SelectorExpr:
578+
if sel := fi.pkgInfo.Selections[fxt]; sel != nil {
579+
inst = fi.instanceForSelection(sel)
580+
} else {
581+
// For qualified identifiers like `pkg.Foo`
582+
inst = fi.instanceForIdent(fxt.Sel)
583+
}
584+
default:
585+
panic(fmt.Errorf(`unexpected type %T for index expression %s`, f.X, f.X))
586+
}
587+
fi.callToNamedFunc(inst, deferredCall)
574588
return fi
575589
}
576590
// The called function is gotten with an index or key from a map, array, or slice.
@@ -592,7 +606,21 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr, deferredCall bool) ast.Visito
592606
}
593607
// This is a call of an instantiation of a generic function,
594608
// e.g. `foo[int, bool]` in `func foo[T1, T2 any]() { ... }; func main() { foo[int, bool]() }`
595-
fi.callToNamedFunc(fi.instanceForIdent(f.X.(*ast.Ident)), deferredCall)
609+
var inst typeparams.Instance
610+
switch fxt := f.X.(type) {
611+
case *ast.Ident:
612+
inst = fi.instanceForIdent(fxt)
613+
case *ast.SelectorExpr:
614+
if sel := fi.pkgInfo.Selections[fxt]; sel != nil {
615+
inst = fi.instanceForSelection(sel)
616+
} else {
617+
// For qualified identifiers like `pkg.Foo`
618+
inst = fi.instanceForIdent(fxt.Sel)
619+
}
620+
default:
621+
panic(fmt.Errorf(`unexpected type %T for index list expression %s`, f.X, f.X))
622+
}
623+
fi.callToNamedFunc(inst, deferredCall)
596624
return fi
597625
default:
598626
if astutil.IsTypeExpr(f, fi.pkgInfo.Info) {

tests/misc_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,28 @@ func TestFileSetSize(t *testing.T) {
959959
t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1)
960960
}
961961
}
962+
963+
// TestCrossPackageGenericFuncCalls ensures that generic functions from other
964+
// packages can be called correctly.
965+
func TestCrossPackageGenericFuncCalls(t *testing.T) {
966+
var wantInt int
967+
if got := otherpkg.Zero[int](); got != wantInt {
968+
t.Errorf(`Got: otherpkg.Zero[int]() = %v, Want: %v`, got, wantInt)
969+
}
970+
971+
var wantStr string
972+
if got := otherpkg.Zero[string](); got != wantStr {
973+
t.Errorf(`Got: otherpkg.Zero[string]() = %q, Want: %q`, got, wantStr)
974+
}
975+
}
976+
977+
// TestCrossPackageGenericCasting ensures that generic types from other
978+
// packages can be used in a type cast.
979+
// The cast looks like a function call but should be treated as a type conversion.
980+
func TestCrossPackageGenericCasting(t *testing.T) {
981+
fn := otherpkg.GetterHandle[int](otherpkg.Zero[int])
982+
var wantInt int
983+
if got := fn(); got != wantInt {
984+
t.Errorf(`Got: otherpkg.GetterHandle[int](otherpkg.Zero[int]) = %v, Want: %v`, got, wantInt)
985+
}
986+
}

tests/otherpkg/otherpkg.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
package otherpkg
22

33
var Test float32
4+
5+
func Zero[T any]() T {
6+
var zero T
7+
return zero
8+
}
9+
10+
type GetterHandle[T any] func() T

0 commit comments

Comments
 (0)