Skip to content

Commit 010220a

Browse files
fixing some issues in analysis around generics
1 parent 3c9b135 commit 010220a

File tree

7 files changed

+393
-161
lines changed

7 files changed

+393
-161
lines changed

compiler/functions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext {
8282
// go/types doesn't generate *types.Func objects for function literals, we
8383
// generate a synthetic one for it.
8484
func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext {
85-
info := fc.pkgCtx.FuncLitInfo(fun)
85+
info := fc.pkgCtx.FuncLitInfo(fun, fc.TypeArgs())
8686
sig := fc.pkgCtx.TypeOf(fun).(*types.Signature)
8787
o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig)
8888
inst := typeparams.Instance{Object: o}

compiler/internal/analysis/defer.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package analysis
22

33
import (
44
"go/ast"
5+
"go/types"
56

67
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
8+
"github.com/gopherjs/gopherjs/compiler/typesutil"
79
)
810

911
// deferStmt represents a defer statement that is blocking or not.
@@ -49,8 +51,9 @@ import (
4951
//
5052
// [CFG]: https://en.wikipedia.org/wiki/Control-flow_graph
5153
type deferStmt struct {
52-
inst *typeparams.Instance
53-
lit *ast.FuncLit
54+
obj types.Object
55+
lit *ast.FuncLit
56+
typeArgs typesutil.TypeList
5457
}
5558

5659
// newBlockingDefer creates a new defer statement that is blocking.
@@ -65,25 +68,25 @@ func newBlockingDefer() *deferStmt {
6568
// newInstDefer creates a new defer statement for an instances of a method.
6669
// The instance is used to look up the blocking information later.
6770
func newInstDefer(inst typeparams.Instance) *deferStmt {
68-
return &deferStmt{inst: &inst}
71+
return &deferStmt{obj: inst.Object, typeArgs: inst.TArgs}
6972
}
7073

7174
// newLitDefer creates a new defer statement for a function literal.
7275
// The literal is used to look up the blocking information later.
73-
func newLitDefer(lit *ast.FuncLit) *deferStmt {
74-
return &deferStmt{lit: lit}
76+
func newLitDefer(lit *ast.FuncLit, typeArgs typesutil.TypeList) *deferStmt {
77+
return &deferStmt{lit: lit, typeArgs: typeArgs}
7578
}
7679

7780
// IsBlocking determines if the defer statement is blocking or not.
7881
func (d *deferStmt) IsBlocking(info *Info) bool {
7982
// If the instance or the literal is set then we can look up the blocking,
8083
// otherwise assume blocking because otherwise the defer wouldn't
8184
// have been recorded.
82-
if d.inst != nil {
83-
return info.FuncInfo(*d.inst).IsBlocking()
85+
if d.obj != nil {
86+
return info.IsBlocking(typeparams.Instance{Object: d.obj, TArgs: d.typeArgs})
8487
}
8588
if d.lit != nil {
86-
return info.FuncLitInfo(d.lit).IsBlocking()
89+
return info.FuncLitInfo(d.lit, d.typeArgs).IsBlocking()
8790
}
8891
return true
8992
}

compiler/internal/analysis/info.go

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,24 @@ type Info struct {
5555
instanceSets *typeparams.PackageInstanceSets
5656
HasPointer map[*types.Var]bool
5757
funcInstInfos *typeparams.InstanceMap[*FuncInfo]
58-
funcLitInfos map[*ast.FuncLit]*FuncInfo
58+
funcLitInfos map[*ast.FuncLit][]*FuncInfo
5959
InitFuncInfo *FuncInfo // Context for package variable initialization.
6060

6161
isImportedBlocking func(typeparams.Instance) bool // For functions from other packages.
6262
allInfos []*FuncInfo
6363
}
6464

65-
func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo {
65+
func (info *Info) newFuncInfo(n ast.Node, obj types.Object, typeArgs typesutil.TypeList, resolver *typeparams.Resolver) *FuncInfo {
6666
funcInfo := &FuncInfo{
6767
pkgInfo: info,
6868
Flattened: make(map[ast.Node]bool),
6969
Blocking: make(map[ast.Node]bool),
7070
GotoLabel: make(map[*types.Label]bool),
7171
loopReturnIndex: -1,
72-
localInstCallees: new(typeparams.InstanceMap[[]astPath]),
72+
instCallees: new(typeparams.InstanceMap[[]astPath]),
7373
literalFuncCallees: make(map[*ast.FuncLit]astPath),
74+
typeArgs: typeArgs,
75+
resolver: resolver,
7476
}
7577

7678
// Register the function in the appropriate map.
@@ -86,13 +88,14 @@ func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo {
8688
funcInfo.Blocking[n] = true
8789
}
8890

89-
if inst == nil {
90-
inst = &typeparams.Instance{Object: info.Defs[n.Name]}
91+
if obj == nil {
92+
obj = info.Defs[n.Name]
9193
}
92-
info.funcInstInfos.Set(*inst, funcInfo)
94+
inst := typeparams.Instance{Object: obj, TArgs: typeArgs}
95+
info.funcInstInfos.Set(inst, funcInfo)
9396

9497
case *ast.FuncLit:
95-
info.funcLitInfos[n] = funcInfo
98+
info.funcLitInfos[n] = append(info.funcLitInfos[n], funcInfo)
9699
}
97100

98101
// And add it to the list of all functions.
@@ -105,28 +108,40 @@ func (info *Info) newFuncInfoInstances(fd *ast.FuncDecl) []*FuncInfo {
105108
obj := info.Defs[fd.Name]
106109
instances := info.instanceSets.Pkg(info.Pkg).ForObj(obj)
107110
if len(instances) == 0 {
108-
// No instances found, this is a non-generic function.
109-
return []*FuncInfo{info.newFuncInfo(fd, nil)}
111+
if typeparams.HasTypeParams(obj.Type()) {
112+
// This is a generic function, but no instances were found,
113+
// this is an unused function, so skip over it.
114+
return []*FuncInfo{}
115+
}
116+
117+
// No instances found and this is a non-generic function.
118+
return []*FuncInfo{info.newFuncInfo(fd, nil, nil, nil)}
110119
}
111120

112121
funcInfos := make([]*FuncInfo, 0, len(instances))
113122
for _, inst := range instances {
114-
fi := info.newFuncInfo(fd, &inst)
123+
var resolver *typeparams.Resolver
115124
if sig, ok := obj.Type().(*types.Signature); ok {
116125
tp := typeparams.ToSlice(typeparams.SignatureTypeParams(sig))
117-
fi.resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs)
126+
resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs)
118127
}
128+
fi := info.newFuncInfo(fd, inst.Object, inst.TArgs, resolver)
119129
funcInfos = append(funcInfos, fi)
120130
}
121131
return funcInfos
122132
}
123133

124134
// IsBlocking returns true if the function may contain blocking calls or operations.
135+
// If inst is from a different package, this will use the isImportedBlocking
136+
// to lookup the information from the other package.
125137
func (info *Info) IsBlocking(inst typeparams.Instance) bool {
138+
if inst.Object.Pkg() != info.Pkg {
139+
return info.isImportedBlocking(inst)
140+
}
126141
if funInfo := info.FuncInfo(inst); funInfo != nil {
127142
return funInfo.IsBlocking()
128143
}
129-
panic(fmt.Errorf(`info did not have function declaration instance for %q`, inst))
144+
panic(fmt.Errorf(`info did not have function declaration instance for %q`, inst.TypeString()))
130145
}
131146

132147
// FuncInfo returns information about the given function declaration instance, or nil if not found.
@@ -135,8 +150,16 @@ func (info *Info) FuncInfo(inst typeparams.Instance) *FuncInfo {
135150
}
136151

137152
// FuncLitInfo returns information about the given function literal, or nil if not found.
138-
func (info *Info) FuncLitInfo(fun *ast.FuncLit) *FuncInfo {
139-
return info.funcLitInfos[fun]
153+
// The given type arguments are used to identify the correct instance of the
154+
// function literal in the case the literal was defined inside a generic function.
155+
func (info *Info) FuncLitInfo(fun *ast.FuncLit, typeArgs typesutil.TypeList) *FuncInfo {
156+
lits := info.funcLitInfos[fun]
157+
for _, fi := range lits {
158+
if fi.typeArgs.Equal(typeArgs) {
159+
return fi
160+
}
161+
}
162+
return nil
140163
}
141164

142165
// VarsWithInitializers returns a set of package-level variables that have
@@ -160,9 +183,9 @@ func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info
160183
HasPointer: make(map[*types.Var]bool),
161184
isImportedBlocking: isBlocking,
162185
funcInstInfos: new(typeparams.InstanceMap[*FuncInfo]),
163-
funcLitInfos: make(map[*ast.FuncLit]*FuncInfo),
186+
funcLitInfos: make(map[*ast.FuncLit][]*FuncInfo),
164187
}
165-
info.InitFuncInfo = info.newFuncInfo(nil, nil)
188+
info.InitFuncInfo = info.newFuncInfo(nil, nil, nil, nil)
166189

167190
// Traverse the full AST of the package and collect information about existing
168191
// functions.
@@ -190,19 +213,19 @@ func (info *Info) propagateFunctionBlocking() bool {
190213
done := true
191214
for _, caller := range info.allInfos {
192215
// Check calls to named functions and function-typed variables.
193-
caller.localInstCallees.Iterate(func(callee typeparams.Instance, callSites []astPath) {
194-
if info.FuncInfo(callee).IsBlocking() {
216+
caller.instCallees.Iterate(func(callee typeparams.Instance, callSites []astPath) {
217+
if info.IsBlocking(callee) {
195218
for _, callSite := range callSites {
196219
caller.markBlocking(callSite)
197220
}
198-
caller.localInstCallees.Delete(callee)
221+
caller.instCallees.Delete(callee)
199222
done = false
200223
}
201224
})
202225

203226
// Check direct calls to function literals.
204227
for callee, callSite := range caller.literalFuncCallees {
205-
if info.FuncLitInfo(callee).IsBlocking() {
228+
if info.FuncLitInfo(callee, caller.typeArgs).IsBlocking() {
206229
caller.markBlocking(callSite)
207230
delete(caller.literalFuncCallees, callee)
208231
done = false
@@ -250,15 +273,18 @@ type FuncInfo struct {
250273
// returns defined before any defers in a loop may still be affected by
251274
// those defers because of the loop. See comment on [deferStmt].
252275
loopReturnIndex int
253-
// List of other named functions from the current package this function calls.
276+
// List of other named functions in the current package or another package
277+
// that this function calls.
254278
// If any of them are blocking, this function will become blocking too.
255-
localInstCallees *typeparams.InstanceMap[[]astPath]
279+
instCallees *typeparams.InstanceMap[[]astPath]
256280
// List of function literals directly called from this function (for example:
257281
// `func() { /* do stuff */ }()`). This is distinct from function literals
258282
// assigned to named variables (for example: `doStuff := func() {};
259283
// doStuff()`), which are handled by localInstCallees. If any of them are
260284
// identified as blocking, this function will become blocking too.
261285
literalFuncCallees map[*ast.FuncLit]astPath
286+
// typeArgs are the type arguments for the function instance.
287+
typeArgs typesutil.TypeList
262288
// resolver is used by this function instance to resolve any type arguments
263289
// for internal function calls.
264290
// This may be nil if not an instance of a generic function.
@@ -276,6 +302,12 @@ func (fi *FuncInfo) IsBlocking() bool {
276302
return fi == nil || len(fi.Blocking) != 0
277303
}
278304

305+
// TypeArgs gets the type arguments of this inside of a function instance
306+
// or empty if not in a function instance.
307+
func (fi *FuncInfo) TypeArgs() typesutil.TypeList {
308+
return fi.typeArgs
309+
}
310+
279311
// propagateReturnBlocking updates the blocking on the return statements.
280312
// See comment on [deferStmt].
281313
//
@@ -341,7 +373,7 @@ func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor {
341373
return nil
342374
case *ast.FuncLit:
343375
// Analyze the function literal in its own context.
344-
return fi.pkgInfo.newFuncInfo(n, nil)
376+
return fi.pkgInfo.newFuncInfo(n, nil, fi.typeArgs, fi.resolver)
345377
case *ast.BranchStmt:
346378
switch n.Tok {
347379
case token.GOTO:
@@ -502,7 +534,7 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr, deferredCall bool) ast.Visito
502534
// Register literal function call site in case it is identified as blocking.
503535
fi.literalFuncCallees[f] = fi.visitorStack.copy()
504536
if deferredCall {
505-
fi.deferStmts = append(fi.deferStmts, newLitDefer(f))
537+
fi.deferStmts = append(fi.deferStmts, newLitDefer(f, fi.typeArgs))
506538
}
507539
return nil // No need to walk under this CallExpr, we already did it manually.
508540
case *ast.IndexExpr:
@@ -610,21 +642,11 @@ func (fi *FuncInfo) callToNamedFunc(callee typeparams.Instance, deferredCall boo
610642
}
611643
}
612644

613-
if o.Pkg() != fi.pkgInfo.Pkg {
614-
if fi.pkgInfo.isImportedBlocking(callee) {
615-
fi.markBlocking(fi.visitorStack)
616-
if deferredCall {
617-
fi.deferStmts = append(fi.deferStmts, newBlockingDefer())
618-
}
619-
}
620-
return
621-
}
622-
623645
// We probably don't know yet whether the callee function is blocking.
624646
// Record the calls site for the later stage.
625-
paths := fi.localInstCallees.Get(callee)
647+
paths := fi.instCallees.Get(callee)
626648
paths = append(paths, fi.visitorStack.copy())
627-
fi.localInstCallees.Set(callee, paths)
649+
fi.instCallees.Set(callee, paths)
628650
if deferredCall {
629651
fi.deferStmts = append(fi.deferStmts, newInstDefer(callee))
630652
}

0 commit comments

Comments
 (0)