Update function info blocking to use instances
diff --git a/compiler/decls.go b/compiler/decls.go
index dbd47b6..37fdcca 100644
--- a/compiler/decls.go
+++ b/compiler/decls.go
@@ -317,7 +317,7 @@
o := fc.pkgCtx.Defs[fun.Name].(*types.Func)
d := &Decl{
FullName: o.FullName(),
- Blocking: fc.pkgCtx.IsBlocking(o),
+ Blocking: fc.pkgCtx.IsBlocking(inst),
LinkingName: symbol.New(o),
}
d.Dce().SetName(o)
@@ -349,7 +349,7 @@
func (fc *funcContext) callInitFunc(init *types.Func) ast.Stmt {
id := fc.newIdentFor(init)
call := &ast.CallExpr{Fun: id}
- if fc.pkgCtx.IsBlocking(init) {
+ if fc.pkgCtx.IsBlocking(typeparams.Instance{Object: init}) {
fc.Blocking[call] = true
}
return &ast.ExprStmt{X: call}
@@ -373,7 +373,7 @@
},
},
}
- if fc.pkgCtx.IsBlocking(main) {
+ if fc.pkgCtx.IsBlocking(typeparams.Instance{Object: main}) {
fc.Blocking[call] = true
fc.Flattened[ifStmt] = true
}
diff --git a/compiler/functions.go b/compiler/functions.go
index 1641174..657bf99 100644
--- a/compiler/functions.go
+++ b/compiler/functions.go
@@ -72,7 +72,7 @@
// namedFuncContext creates a new funcContext for a named Go function
// (standalone or method).
func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext {
- info := fc.pkgCtx.FuncDeclInfos[inst.Object.(*types.Func)]
+ info := fc.pkgCtx.FuncInfo(inst)
c := fc.nestedFunctionContext(info, inst)
return c
@@ -82,7 +82,7 @@
// go/types doesn't generate *types.Func objects for function literals, we
// generate a synthetic one for it.
func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext {
- info := fc.pkgCtx.FuncLitInfos[fun]
+ info := fc.pkgCtx.FuncLitInfo(fun)
sig := fc.pkgCtx.TypeOf(fun).(*types.Signature)
o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig)
inst := typeparams.Instance{Object: o}
@@ -237,7 +237,7 @@
}
bodyOutput := string(fc.CatchOutput(1, func() {
- if len(fc.Blocking) != 0 {
+ if fc.HasBlocking() {
fc.pkgCtx.Scopes[body] = fc.pkgCtx.Scopes[typ]
fc.handleEscapingVars(body)
}
@@ -283,14 +283,14 @@
if fc.HasDefer {
fc.localVars = append(fc.localVars, "$deferred")
suffix = " }" + suffix
- if len(fc.Blocking) != 0 {
+ if fc.HasBlocking() {
suffix = " }" + suffix
}
}
localVarDefs := "" // Function-local var declaration at the top.
- if len(fc.Blocking) != 0 {
+ if fc.HasBlocking() {
localVars := append([]string{}, fc.localVars...)
// There are several special variables involved in handling blocking functions:
// $r is sometimes used as a temporary variable to store blocking call result.
@@ -314,7 +314,7 @@
if fc.HasDefer {
prefix = prefix + " var $err = null; try {"
deferSuffix := " } catch(err) { $err = err;"
- if len(fc.Blocking) != 0 {
+ if fc.HasBlocking() {
deferSuffix += " $s = -1;"
}
if fc.resultNames == nil && fc.sig.HasResults() {
@@ -324,7 +324,7 @@
if fc.resultNames != nil {
deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", fc.translateResults(fc.resultNames))
}
- if len(fc.Blocking) != 0 {
+ if fc.HasBlocking() {
deferSuffix += " if($curGoroutine.asleep) {"
}
suffix = deferSuffix + suffix
diff --git a/compiler/internal/analysis/info.go b/compiler/internal/analysis/info.go
index 44ea6c1..f6edfd8 100644
--- a/compiler/internal/analysis/info.go
+++ b/compiler/internal/analysis/info.go
@@ -8,6 +8,7 @@
"strings"
"github.com/gopherjs/gopherjs/compiler/astutil"
+ "github.com/gopherjs/gopherjs/compiler/internal/typeparams"
"github.com/gopherjs/gopherjs/compiler/typesutil"
)
@@ -50,22 +51,24 @@
type Info struct {
*types.Info
Pkg *types.Package
+ typeCtx *types.Context
+ instanceSets *typeparams.PackageInstanceSets
HasPointer map[*types.Var]bool
- FuncDeclInfos map[*types.Func]*FuncInfo
- FuncLitInfos map[*ast.FuncLit]*FuncInfo
+ funcInstInfos *typeparams.InstanceMap[*FuncInfo]
+ funcLitInfos map[*ast.FuncLit]*FuncInfo
InitFuncInfo *FuncInfo // Context for package variable initialization.
- isImportedBlocking func(*types.Func) bool // For functions from other packages.
+ isImportedBlocking func(typeparams.Instance) bool // For functions from other packages.
allInfos []*FuncInfo
}
-func (info *Info) newFuncInfo(n ast.Node) *FuncInfo {
+func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo {
funcInfo := &FuncInfo{
pkgInfo: info,
Flattened: make(map[ast.Node]bool),
Blocking: make(map[ast.Node]bool),
GotoLabel: make(map[*types.Label]bool),
- localNamedCallees: make(map[*types.Func][]astPath),
+ localInstCallees: new(typeparams.InstanceMap[[]astPath]),
literalFuncCallees: make(map[*ast.FuncLit][]astPath),
}
@@ -76,14 +79,19 @@
// Function body comes from elsewhere (for example, from a go:linkname
// directive), conservatively assume that it may be blocking.
// TODO(nevkontakte): It is possible to improve accuracy of this detection.
- // Since GopherJS supports inly "import-style" go:linkname, at this stage
+ // Since GopherJS supports only "import-style" go:linkname, at this stage
// the compiler already determined whether the implementation function is
// blocking, and we could check that.
funcInfo.Blocking[n] = true
}
- info.FuncDeclInfos[info.Defs[n.Name].(*types.Func)] = funcInfo
+
+ if inst == nil {
+ inst = &typeparams.Instance{Object: info.Defs[n.Name]}
+ }
+ info.funcInstInfos.Set(*inst, funcInfo)
+
case *ast.FuncLit:
- info.FuncLitInfos[n] = funcInfo
+ info.funcLitInfos[n] = funcInfo
}
// And add it to the list of all functions.
@@ -92,12 +100,42 @@
return funcInfo
}
-// IsBlocking returns true if the function may contain blocking calls or operations.
-func (info *Info) IsBlocking(fun *types.Func) bool {
- if funInfo := info.FuncDeclInfos[fun]; funInfo != nil {
- return len(funInfo.Blocking) > 0
+func (info *Info) newFuncInfoInstances(fd *ast.FuncDecl) []*FuncInfo {
+ obj := info.Defs[fd.Name]
+ instances := info.instanceSets.Pkg(info.Pkg).ForObj(obj)
+ if len(instances) == 0 {
+ // No instances found, this is a non-generic function.
+ return []*FuncInfo{info.newFuncInfo(fd, nil)}
}
- panic(fmt.Errorf(`info did not have function declaration for %s`, fun.FullName()))
+
+ funcInfos := make([]*FuncInfo, 0, len(instances))
+ for _, inst := range instances {
+ fi := info.newFuncInfo(fd, &inst)
+ if sig, ok := obj.Type().(*types.Signature); ok {
+ tp := typeparams.ToSlice(typeparams.SignatureTypeParams(sig))
+ fi.resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs)
+ }
+ funcInfos = append(funcInfos, fi)
+ }
+ return funcInfos
+}
+
+// IsBlocking returns true if the function may contain blocking calls or operations.
+func (info *Info) IsBlocking(inst typeparams.Instance) bool {
+ if funInfo := info.FuncInfo(inst); funInfo != nil {
+ return funInfo.HasBlocking()
+ }
+ panic(fmt.Errorf(`info did not have function declaration instance for %q`, inst))
+}
+
+// FuncInfo returns information about the given function declaration instance, or nil if not found.
+func (info *Info) FuncInfo(inst typeparams.Instance) *FuncInfo {
+ return info.funcInstInfos.Get(inst)
+}
+
+// FuncLitInfo returns information about the given function literal, or nil if not found.
+func (info *Info) FuncLitInfo(fun *ast.FuncLit) *FuncInfo {
+ return info.funcLitInfos[fun]
}
// VarsWithInitializers returns a set of package-level variables that have
@@ -112,16 +150,18 @@
return result
}
-func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
+func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typeCtx *types.Context, typesPkg *types.Package, instanceSets *typeparams.PackageInstanceSets, isBlocking func(typeparams.Instance) bool) *Info {
info := &Info{
Info: typesInfo,
Pkg: typesPkg,
+ typeCtx: typeCtx,
+ instanceSets: instanceSets,
HasPointer: make(map[*types.Var]bool),
isImportedBlocking: isBlocking,
- FuncDeclInfos: make(map[*types.Func]*FuncInfo),
- FuncLitInfos: make(map[*ast.FuncLit]*FuncInfo),
+ funcInstInfos: new(typeparams.InstanceMap[*FuncInfo]),
+ funcLitInfos: make(map[*ast.FuncLit]*FuncInfo),
}
- info.InitFuncInfo = info.newFuncInfo(nil)
+ info.InitFuncInfo = info.newFuncInfo(nil, nil)
// Traverse the full AST of the package and collect information about existing
// functions.
@@ -152,19 +192,19 @@
done := true
for _, caller := range info.allInfos {
// Check calls to named functions and function-typed variables.
- for callee, callSites := range caller.localNamedCallees {
- if info.IsBlocking(callee) {
+ caller.localInstCallees.Iterate(func(callee typeparams.Instance, callSites []astPath) {
+ if info.FuncInfo(callee).HasBlocking() {
for _, callSite := range callSites {
caller.markBlocking(callSite)
}
- delete(caller.localNamedCallees, callee)
+ caller.localInstCallees.Delete(callee)
done = false
}
- }
+ })
// Check direct calls to function literals.
for callee, callSites := range caller.literalFuncCallees {
- if len(info.FuncLitInfos[callee].Blocking) > 0 {
+ if info.FuncLitInfo(callee).HasBlocking() {
for _, callSite := range callSites {
caller.markBlocking(callSite)
}
@@ -202,7 +242,7 @@
// Blocking indicates that either the AST node itself or its descendant may
// block goroutine execution (for example, a channel operation).
Blocking map[ast.Node]bool
- // GotoLavel indicates a label referenced by a goto statement, rather than a
+ // GotoLabel indicates a label referenced by a goto statement, rather than a
// named loop.
GotoLabel map[*types.Label]bool
// List of continue statements in the function.
@@ -211,18 +251,30 @@
returnStmts []astPath
// List of other named functions from the current package this function calls.
// If any of them are blocking, this function will become blocking too.
- localNamedCallees map[*types.Func][]astPath
+ localInstCallees *typeparams.InstanceMap[[]astPath]
// List of function literals directly called from this function (for example:
// `func() { /* do stuff */ }()`). This is distinct from function literals
// assigned to named variables (for example: `doStuff := func() {};
// doStuff()`), which are handled by localNamedCallees. If any of them are
// identified as blocking, this function will become blocking too.
literalFuncCallees map[*ast.FuncLit][]astPath
+ // resolver is used by this function instance to resolve any type arguments
+ // for internal function calls.
+ // This may be nil if not an instance of a generic function.
+ resolver *typeparams.Resolver
pkgInfo *Info // Function's parent package.
visitorStack astPath
}
+// HasBlocking indicates if this function may block goroutine execution.
+//
+// For example, a channel operation in a function or a call to another
+// possibly blocking function may block the function.
+func (fi *FuncInfo) HasBlocking() bool {
+ return fi == nil || len(fi.Blocking) != 0
+}
+
func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor {
if node == nil {
if len(fi.visitorStack) != 0 {
@@ -233,9 +285,19 @@
fi.visitorStack = append(fi.visitorStack, node)
switch n := node.(type) {
- case *ast.FuncDecl, *ast.FuncLit:
- // Analyze the function in its own context.
- return fi.pkgInfo.newFuncInfo(n)
+ case *ast.FuncDecl:
+ // Analyze all the instances of the function declarations
+ // in their own context with their own type arguments.
+ fis := fi.pkgInfo.newFuncInfoInstances(n)
+ if n.Body != nil {
+ for _, fi := range fis {
+ ast.Walk(fi, n.Body)
+ }
+ }
+ return nil
+ case *ast.FuncLit:
+ // Analyze the function literal in its own context.
+ return fi.pkgInfo.newFuncInfo(n, nil)
case *ast.BranchStmt:
switch n.Tok {
case token.GOTO:
@@ -334,13 +396,19 @@
func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor {
switch f := astutil.RemoveParens(n.Fun).(type) {
case *ast.Ident:
- fi.callToNamedFunc(fi.pkgInfo.Uses[f])
+ fi.callToNamedFunc(fi.instanceForIdent(f))
case *ast.SelectorExpr:
- if sel := fi.pkgInfo.Selections[f]; sel != nil && typesutil.IsJsObject(sel.Recv()) {
- // js.Object methods are known to be non-blocking, but we still must
- // check its arguments.
+ if sel := fi.pkgInfo.Selections[f]; sel != nil {
+ if typesutil.IsJsObject(sel.Recv()) {
+ // js.Object methods are known to be non-blocking, but we still must
+ // check its arguments.
+ } else {
+ // selection is a method call like `foo.Bar()`, where `foo` might
+ // be generic and needs to be substituted with the type argument.
+ fi.callToNamedFunc(fi.instanceFoSelection(sel))
+ }
} else {
- fi.callToNamedFunc(fi.pkgInfo.Uses[f.Sel])
+ fi.callToNamedFunc(fi.instanceForIdent(f.Sel))
}
case *ast.FuncLit:
// Collect info about the function literal itself.
@@ -353,6 +421,34 @@
// Register literal function call site in case it is identified as blocking.
fi.literalFuncCallees[f] = append(fi.literalFuncCallees[f], fi.visitorStack.copy())
return nil // No need to walk under this CallExpr, we already did it manually.
+ case *ast.IndexExpr:
+ // Collect info about the instantiated type or function, or index expression.
+ if astutil.IsTypeExpr(f, fi.pkgInfo.Info) {
+ // This is a type conversion to an instance of a generic type,
+ // not a call. Type assertion itself is not blocking, but we will
+ // visit the input expression.
+ } else if astutil.IsTypeExpr(f.Index, fi.pkgInfo.Info) {
+ // This is a call of an instantiation of a generic function,
+ // e.g. `foo[int]` in `func foo[T any]() { ... }; func main() { foo[int]() }`
+ fi.callToNamedFunc(fi.instanceForIdent(f.X.(*ast.Ident)))
+ } else {
+ // The called function is gotten with an index or key from a map, array, or slice.
+ // e.g. `m := map[string]func(){}; m["key"]()`, `s := []func(); s[0]()`.
+ // Since we can't predict if the returned function will be blocking
+ // or not, we have to be conservative and assume that function might be blocking.
+ fi.markBlocking(fi.visitorStack)
+ }
+ case *ast.IndexListExpr:
+ // Collect info about the instantiated type or function.
+ if astutil.IsTypeExpr(f, fi.pkgInfo.Info) {
+ // This is a type conversion to an instance of a generic type,
+ // not a call. Type assertion itself is not blocking, but we will
+ // visit the input expression.
+ } else {
+ // This is a call of an instantiation of a generic function,
+ // e.g. `foo[int, bool]` in `func foo[T1, T2 any]() { ... }; func main() { foo[int, bool]() }`
+ fi.callToNamedFunc(fi.instanceForIdent(f.X.(*ast.Ident)))
+ }
default:
if astutil.IsTypeExpr(f, fi.pkgInfo.Info) {
// This is a type conversion, not a call. Type assertion itself is not
@@ -367,8 +463,47 @@
return fi
}
-func (fi *FuncInfo) callToNamedFunc(callee types.Object) {
- switch o := callee.(type) {
+func (fi *FuncInfo) instanceForIdent(fnId *ast.Ident) typeparams.Instance {
+ tArgs := fi.pkgInfo.Info.Instances[fnId].TypeArgs
+ return typeparams.Instance{
+ Object: fi.pkgInfo.Uses[fnId],
+ TArgs: fi.resolver.SubstituteAll(tArgs),
+ }
+}
+
+func (fi *FuncInfo) instanceFoSelection(sel *types.Selection) typeparams.Instance {
+ if _, ok := sel.Obj().Type().(*types.Signature); ok {
+ // Substitute the selection to ensure that the receiver has the correct
+ // type arguments propagated down from the caller.
+ resolved := fi.resolver.SubstituteSelection(sel)
+ sig := resolved.Obj().Type().(*types.Signature)
+
+ // Using the substituted receiver type, find the instance of this call.
+ // This does require looking up the original method in the receiver type
+ // that may or may not have been the receiver prior to the substitution.
+ if recv := sig.Recv(); recv != nil {
+ typ := recv.Type()
+ if ptrType, ok := typ.(*types.Pointer); ok {
+ typ = ptrType.Elem()
+ }
+
+ if rt, ok := typ.(*types.Named); ok {
+ origMethod, _, _ := types.LookupFieldOrMethod(rt.Origin(), true, rt.Obj().Pkg(), resolved.Obj().Name())
+ if origMethod == nil {
+ panic(fmt.Errorf(`failed to lookup field %q in type %v`, resolved.Obj().Name(), rt.Origin()))
+ }
+ return typeparams.Instance{
+ Object: origMethod,
+ TArgs: fi.resolver.SubstituteAll(rt.TypeArgs()),
+ }
+ }
+ }
+ }
+ return typeparams.Instance{Object: sel.Obj()}
+}
+
+func (fi *FuncInfo) callToNamedFunc(callee typeparams.Instance) {
+ switch o := callee.Object.(type) {
case *types.Func:
o = o.Origin()
if recv := o.Type().(*types.Signature).Recv(); recv != nil {
@@ -379,14 +514,16 @@
}
}
if o.Pkg() != fi.pkgInfo.Pkg {
- if fi.pkgInfo.isImportedBlocking(o) {
+ if fi.pkgInfo.isImportedBlocking(callee) {
fi.markBlocking(fi.visitorStack)
}
return
}
// We probably don't know yet whether the callee function is blocking.
// Record the calls site for the later stage.
- fi.localNamedCallees[o] = append(fi.localNamedCallees[o], fi.visitorStack.copy())
+ paths := fi.localInstCallees.Get(callee)
+ paths = append(paths, fi.visitorStack.copy())
+ fi.localInstCallees.Set(callee, paths)
case *types.Var:
// Conservatively assume that a function in a variable might be blocking.
fi.markBlocking(fi.visitorStack)
diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go
index 588f09a..f4475cf 100644
--- a/compiler/internal/analysis/info_test.go
+++ b/compiler/internal/analysis/info_test.go
@@ -5,74 +5,1238 @@
"go/types"
"testing"
+ "github.com/gopherjs/gopherjs/compiler/internal/typeparams"
"github.com/gopherjs/gopherjs/internal/srctesting"
)
-// See: https://github.com/gopherjs/gopherjs/issues/955.
-func TestBlockingFunctionLiteral(t *testing.T) {
- src := `
-package test
+func TestBlocking_Simple(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
-func blocking() {
- c := make(chan bool)
- <-c
+ func notBlocking() {
+ println("hi")
+ }`)
+ bt.assertNotBlocking(`notBlocking`)
}
-func indirectlyBlocking() {
- func() { blocking() }()
+func TestBlocking_Recursive(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func notBlocking(i int) {
+ if i > 0 {
+ println(i)
+ notBlocking(i - 1)
+ }
+ }`)
+ bt.assertNotBlocking(`notBlocking`)
}
-func directlyBlocking() {
- func() {
- c := make(chan bool)
- <-c
- }()
+func TestBlocking_AlternatingRecursive(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func near(i int) {
+ if i > 0 {
+ println(i)
+ far(i)
+ }
+ }
+
+ func far(i int) {
+ near(i - 1)
+ }`)
+ bt.assertNotBlocking(`near`)
+ bt.assertNotBlocking(`far`)
}
-func notBlocking() {
- func() { println() } ()
+func TestBlocking_Channels(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func readFromChannel(c chan bool) {
+ <-c
+ }
+
+ func readFromChannelAssign(c chan bool) {
+ v := <-c
+ println(v)
+ }
+
+ func readFromChannelAsArg(c chan bool) {
+ println(<-c)
+ }
+
+ func sendToChannel(c chan bool) {
+ c <- true
+ }
+
+ func rangeOnChannel(c chan bool) {
+ for v := range c {
+ println(v)
+ }
+ }
+
+ func rangeOnSlice(c []bool) {
+ for v := range c {
+ println(v)
+ }
+ }`)
+ bt.assertBlocking(`readFromChannel`)
+ bt.assertBlocking(`sendToChannel`)
+ bt.assertBlocking(`rangeOnChannel`)
+ bt.assertBlocking(`readFromChannelAssign`)
+ bt.assertBlocking(`readFromChannelAsArg`)
+ bt.assertNotBlocking(`rangeOnSlice`)
}
-`
+
+func TestBlocking_Selects(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func selectReadWithoutDefault(a, b chan bool) {
+ select {
+ case <-a:
+ println("a")
+ case v := <-b:
+ println("b", v)
+ }
+ }
+
+ func selectReadWithDefault(a, b chan bool) {
+ select {
+ case <-a:
+ println("a")
+ case v := <-b:
+ println("b", v)
+ default:
+ println("nothing")
+ }
+ }
+
+ func selectSendWithoutDefault(a, b chan bool) {
+ select {
+ case a <- true:
+ println("a")
+ case b <- false:
+ println("b")
+ }
+ }
+
+ func selectSendWithDefault(a, b chan bool) {
+ select {
+ case a <- true:
+ println("a")
+ case b <- false:
+ println("b")
+ default:
+ println("nothing")
+ }
+ }`)
+ bt.assertBlocking(`selectReadWithoutDefault`)
+ bt.assertBlocking(`selectSendWithoutDefault`)
+ bt.assertNotBlocking(`selectReadWithDefault`)
+ bt.assertNotBlocking(`selectSendWithDefault`)
+}
+
+func TestBlocking_GoRoutines_WithFuncLiterals(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func notBlocking(c chan bool) {
+ go func(c chan bool) { // line 4
+ println(<-c)
+ }(c)
+ }
+
+ func blocking(c chan bool) {
+ go func(v bool) { // line 10
+ println(v)
+ }(<-c)
+ }`)
+ bt.assertNotBlocking(`notBlocking`)
+ bt.assertBlockingLit(4)
+
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlockingLit(10)
+}
+
+func TestBlocking_GoRoutines_WithNamedFuncs(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blockingRoutine(c chan bool) {
+ println(<-c)
+ }
+
+ func nonBlockingRoutine(v bool) {
+ println(v)
+ }
+
+ func notBlocking(c chan bool) {
+ go blockingRoutine(c)
+ }
+
+ func blocking(c chan bool) {
+ go nonBlockingRoutine(<-c)
+ }`)
+ bt.assertBlocking(`blockingRoutine`)
+ bt.assertNotBlocking(`nonBlockingRoutine`)
+
+ bt.assertNotBlocking(`notBlocking`)
+ bt.assertBlocking(`blocking`)
+}
+
+func TestBlocking_Defers_WithoutReturns_WithFuncLiterals(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blockingBody(c chan bool) {
+ defer func(c chan bool) { // line 4
+ println(<-c)
+ }(c)
+ }
+
+ func blockingArg(c chan bool) {
+ defer func(v bool) { // line 10
+ println(v)
+ }(<-c)
+ }
+
+ func notBlocking(c chan bool) {
+ defer func(v bool) { // line 16
+ println(v)
+ }(true)
+ }`)
+ bt.assertBlocking(`blockingBody`)
+ bt.assertBlockingLit(4)
+
+ bt.assertBlocking(`blockingArg`)
+ bt.assertNotBlockingLit(10)
+
+ bt.assertNotBlocking(`notBlocking`)
+ bt.assertNotBlockingLit(16)
+}
+
+func TestBlocking_Defers_WithoutReturns_WithNamedFuncs(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blockingPrint(c chan bool) {
+ println(<-c)
+ }
+
+ func nonBlockingPrint(v bool) {
+ println(v)
+ }
+
+ func blockingBody(c chan bool) {
+ defer blockingPrint(c)
+ }
+
+ func blockingArg(c chan bool) {
+ defer nonBlockingPrint(<-c)
+ }
+
+ func notBlocking(c chan bool) {
+ defer nonBlockingPrint(true)
+ }`)
+ bt.assertBlocking(`blockingPrint`)
+ bt.assertNotBlocking(`nonBlockingPrint`)
+
+ bt.assertBlocking(`blockingBody`)
+ bt.assertBlocking(`blockingArg`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_Defers_WithReturns_WithFuncLiterals(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blockingBody(c chan bool) int {
+ defer func(c chan bool) { // line 4
+ println(<-c)
+ }(c)
+ return 42
+ }
+
+ func blockingArg(c chan bool) int {
+ defer func(v bool) { // line 11
+ println(v)
+ }(<-c)
+ return 42
+ }
+
+ func notBlocking(c chan bool) int {
+ defer func(v bool) { // line 18
+ println(v)
+ }(true)
+ return 42
+ }`)
+ bt.assertBlocking(`blockingBody`)
+ bt.assertBlockingLit(4)
+
+ bt.assertBlocking(`blockingArg`)
+ bt.assertNotBlockingLit(11)
+
+ // TODO: The following is blocking because currently any defer with a return
+ // is assumed to be blocking. This limitation should be fixed in the future.
+ bt.assertBlocking(`notBlocking`)
+ bt.assertNotBlockingLit(18)
+}
+
+func TestBlocking_Defers_WithReturns_WithNamedFuncs(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blockingPrint(c chan bool) {
+ println(<-c)
+ }
+
+ func nonBlockingPrint(v bool) {
+ println(v)
+ }
+
+ func blockingBody(c chan bool) int {
+ defer blockingPrint(c)
+ return 42
+ }
+
+ func blockingArg(c chan bool) int {
+ defer nonBlockingPrint(<-c)
+ return 42
+ }
+
+ func notBlocking(c chan bool) int {
+ defer nonBlockingPrint(true)
+ return 42
+ }`)
+ bt.assertBlocking(`blockingPrint`)
+ bt.assertNotBlocking(`nonBlockingPrint`)
+
+ bt.assertBlocking(`blockingBody`)
+ bt.assertBlocking(`blockingArg`)
+
+ // TODO: The following is blocking because currently any defer with a return
+ // is assumed to be blocking. This limitation should be fixed in the future.
+ bt.assertBlocking(`notBlocking`)
+}
+
+func TestBlocking_Returns_WithoutDefers(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking(c chan bool) bool {
+ return <-c
+ }
+
+ func indirectlyBlocking(c chan bool) bool {
+ return blocking(c)
+ }
+
+ func notBlocking(c chan bool) bool {
+ return true
+ }`)
+ bt.assertBlocking(`blocking`)
+ bt.assertBlocking(`indirectlyBlocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_FunctionLiteral(t *testing.T) {
+ // See: https://github.com/gopherjs/gopherjs/issues/955.
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking() {
+ c := make(chan bool)
+ <-c
+ }
+
+ func indirectlyBlocking() {
+ func() { blocking() }() // line 9
+ }
+
+ func directlyBlocking() {
+ func() { // line 13
+ c := make(chan bool)
+ <-c
+ }()
+ }
+
+ func notBlocking() {
+ func() { println() } () // line 20
+ }`)
+ bt.assertBlocking(`blocking`)
+
+ bt.assertBlocking(`indirectlyBlocking`)
+ bt.assertBlockingLit(9)
+
+ bt.assertBlocking(`directlyBlocking`)
+ bt.assertBlockingLit(13)
+
+ bt.assertNotBlocking(`notBlocking`)
+ bt.assertNotBlockingLit(20)
+}
+
+func TestBlocking_LinkedFunction(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ // linked to some other function
+ func blocking()
+
+ func indirectlyBlocking() {
+ blocking()
+ }`)
+ bt.assertBlocking(`blocking`)
+ bt.assertBlocking(`indirectlyBlocking`)
+}
+
+func TestBlocking_Instances_WithSingleTypeArg(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking[T any]() {
+ c := make(chan T)
+ <-c
+ }
+
+ func notBlocking[T any]() {
+ var v T
+ println(v)
+ }
+
+ func bInt() {
+ blocking[int]()
+ }
+
+ func nbUint() {
+ notBlocking[uint]()
+ }`)
+ bt.assertFuncInstCount(4)
+ // blocking and notBlocking as generics do not have FuncInfo,
+ // only non-generic and instances have FuncInfo.
+
+ bt.assertBlockingInst(`test.blocking[int]`)
+ bt.assertBlocking(`bInt`)
+ bt.assertNotBlockingInst(`test.notBlocking[uint]`)
+ bt.assertNotBlocking(`nbUint`)
+}
+
+func TestBlocking_Instances_WithMultipleTypeArgs(t *testing.T) {
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking[K comparable, V any, M ~map[K]V]() {
+ c := make(chan M)
+ <-c
+ }
+
+ func notBlocking[K comparable, V any, M ~map[K]V]() {
+ var m M
+ println(m)
+ }
+
+ func bInt() {
+ blocking[string, int, map[string]int]()
+ }
+
+ func nbUint() {
+ notBlocking[string, uint, map[string]uint]()
+ }`)
+ bt.assertFuncInstCount(4)
+ // blocking and notBlocking as generics do not have FuncInfo,
+ // only non-generic and instances have FuncInfo.
+
+ bt.assertBlockingInst(`test.blocking[string, int, map[string]int]`)
+ bt.assertBlocking(`bInt`)
+ bt.assertNotBlockingInst(`test.notBlocking[string, uint, map[string]uint]`)
+ bt.assertNotBlocking(`nbUint`)
+}
+
+func TestBlocking_Indexed_FunctionSlice(t *testing.T) {
+ // This calls notBlocking but since the function pointers
+ // are in the slice they will both be considered as blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking() {
+ c := make(chan int)
+ <-c
+ }
+
+ func notBlocking() {
+ println()
+ }
+
+ var funcs = []func() { blocking, notBlocking }
+
+ func indexer(i int) {
+ funcs[i]()
+ }`)
+ bt.assertBlocking(`blocking`)
+ bt.assertBlocking(`indexer`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_Indexed_FunctionMap(t *testing.T) {
+ // This calls notBlocking but since the function pointers
+ // are in the map they will both be considered as blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking() {
+ c := make(chan int)
+ <-c
+ }
+
+ func notBlocking() {
+ println()
+ }
+
+ var funcs = map[string]func() {
+ "b": blocking,
+ "nb": notBlocking,
+ }
+
+ func indexer(key string) {
+ funcs[key]()
+ }`)
+ bt.assertBlocking(`blocking`)
+ bt.assertBlocking(`indexer`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_Indexed_FunctionArray(t *testing.T) {
+ // This calls notBlocking but since the function pointers
+ // are in the array they will both be considered as blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ func blocking() {
+ c := make(chan int)
+ <-c
+ }
+
+ func notBlocking() {
+ println()
+ }
+
+ var funcs = [2]func() { blocking, notBlocking }
+
+ func indexer(i int) {
+ funcs[i]()
+ }`)
+ bt.assertBlocking(`blocking`)
+ bt.assertBlocking(`indexer`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_Casting_InterfaceInstanceWithSingleTypeParam(t *testing.T) {
+ // This checks that casting to an instance type with a single type parameter
+ // is treated as a cast and not accidentally treated as a function call.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo[T any] interface {
+ Baz() T
+ }
+
+ type Bar struct {
+ name string
+ }
+
+ func (b Bar) Baz() string {
+ return b.name
+ }
+
+ func caster() Foo[string] {
+ b := Bar{name: "foo"}
+ return Foo[string](b)
+ }`)
+ bt.assertNotBlocking(`caster`)
+}
+
+func TestBlocking_Casting_InterfaceInstanceWithMultipleTypeParams(t *testing.T) {
+ // This checks that casting to an instance type with multiple type parameters
+ // is treated as a cast and not accidentally treated as a function call.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo[K comparable, V any] interface {
+ Baz(K) V
+ }
+
+ type Bar struct {
+ dat map[string]int
+ }
+
+ func (b Bar) Baz(key string) int {
+ return b.dat[key]
+ }
+
+ func caster() Foo[string, int] {
+ b := Bar{ dat: map[string]int{ "foo": 2 }}
+ return Foo[string, int](b)
+ }`)
+ bt.assertNotBlocking(`caster`)
+}
+
+func TestBlocking_Casting_Interface(t *testing.T) {
+ // This checks that non-generic casting of type is treated as a
+ // cast and not accidentally treated as a function call.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo interface {
+ Baz() string
+ }
+
+ type Bar struct {
+ name string
+ }
+
+ func (b Bar) Baz() string {
+ return b.name
+ }
+
+ func caster() Foo {
+ b := Bar{"foo"}
+ return Foo(b)
+ }`)
+ bt.assertNotBlocking(`caster`)
+}
+
+func TestBlocking_ComplexCasting(t *testing.T) {
+ // This checks a complex casting to a type is treated as a
+ // cast and not accidentally treated as a function call.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo interface {
+ Bar() string
+ }
+
+ func doNothing(f Foo) Foo {
+ return interface{ Bar() string }(f)
+ }`)
+ bt.assertNotBlocking(`doNothing`)
+}
+
+func TestBlocking_ComplexCall(t *testing.T) {
+ // This checks a complex call of a function is defaulted to blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo func() string
+
+ func bar(f any) string {
+ return f.(Foo)()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_CallWithNamedInterfaceReceiver(t *testing.T) {
+ // This checks that calling a named interface function is defaulted to blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo interface {
+ Baz()
+ }
+
+ func bar(f Foo) {
+ f.Baz()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_CallWithUnnamedInterfaceReceiver(t *testing.T) {
+ // This checks that calling an unnamed interface function is defaulted to blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ func bar(f interface { Baz() }) {
+ f.Baz()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_VarFunctionCall(t *testing.T) {
+ // This checks that calling a function in a var is defaulted to blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ var foo = func() { // line 3
+ println("hi")
+ }
+
+ func bar() {
+ foo()
+ }`)
+ bt.assertNotBlockingLit(3)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_FieldFunctionCallOnNamed(t *testing.T) {
+ // This checks that calling a function in a field is defaulted to blocking.
+ // This should be the same as the previous test but with a field since
+ // all function pointers are treated as blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ type foo struct {
+ Baz func()
+ }
+
+ func bar(f foo) {
+ f.Baz()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_FieldFunctionCallOnUnnamed(t *testing.T) {
+ // Same as previous test but with an unnamed struct.
+ bt := newBlockingTest(t,
+ `package test
+
+ func bar(f struct { Baz func() }) {
+ f.Baz()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_ParamFunctionCall(t *testing.T) {
+ // Same as previous test but with an unnamed function parameter.
+ bt := newBlockingTest(t,
+ `package test
+
+ func bar(baz func()) {
+ baz()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_FunctionUnwrapping(t *testing.T) {
+ // Test that calling a function that calls a function etc.
+ // is defaulted to blocking.
+ bt := newBlockingTest(t,
+ `package test
+
+ func bar(baz func()func()func()) {
+ baz()()()
+ }`)
+ bt.assertBlocking(`bar`)
+}
+
+func TestBlocking_MethodCall_NonPointer(t *testing.T) {
+ // Test that calling a method on a non-pointer receiver.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo struct {}
+
+ func (f Foo) blocking() {
+ ch := make(chan bool)
+ <-ch
+ }
+
+ func (f Foo) notBlocking() {
+ println("hi")
+ }
+
+ func blocking(f Foo) {
+ f.blocking()
+ }
+
+ func notBlocking(f Foo) {
+ f.notBlocking()
+ }`)
+ bt.assertBlocking(`Foo.blocking`)
+ bt.assertNotBlocking(`Foo.notBlocking`)
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_MethodCall_Pointer(t *testing.T) {
+ // Test that calling a method on a pointer receiver.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo struct {}
+
+ func (f *Foo) blocking() {
+ ch := make(chan bool)
+ <-ch
+ }
+
+ func (f *Foo) notBlocking() {
+ println("hi")
+ }
+
+ func blocking(f *Foo) {
+ f.blocking()
+ }
+
+ func notBlocking(f *Foo) {
+ f.notBlocking()
+ }`)
+ bt.assertBlocking(`Foo.blocking`)
+ bt.assertNotBlocking(`Foo.notBlocking`)
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_InstantiationBlocking(t *testing.T) {
+ // This checks that the instantiation of a generic function is
+ // being used when checking for blocking not the type argument interface.
+ bt := newBlockingTest(t,
+ `package test
+
+ type BazBlocker struct {
+ c chan bool
+ }
+ func (bb BazBlocker) Baz() {
+ println(<-bb.c)
+ }
+
+ type BazNotBlocker struct {}
+ func (bnb BazNotBlocker) Baz() {
+ println("hi")
+ }
+
+ type Foo interface { Baz() }
+ func FooBaz[T Foo](foo T) {
+ foo.Baz()
+ }
+
+ func blockingViaExplicit() {
+ FooBaz[BazBlocker](BazBlocker{c: make(chan bool)})
+ }
+
+ func notBlockingViaExplicit() {
+ FooBaz[BazNotBlocker](BazNotBlocker{})
+ }
+
+ func blockingViaImplicit() {
+ FooBaz(BazBlocker{c: make(chan bool)})
+ }
+
+ func notBlockingViaImplicit() {
+ FooBaz(BazNotBlocker{})
+ }`)
+ bt.assertFuncInstCount(8)
+ // `FooBaz` as a generic function does not have FuncInfo for it,
+ // only non-generic or instantiations of a generic functions have FuncInfo.
+
+ bt.assertBlocking(`BazBlocker.Baz`)
+ bt.assertBlocking(`blockingViaExplicit`)
+ bt.assertBlocking(`blockingViaImplicit`)
+ bt.assertBlockingInst(`test.FooBaz[pkg/test.BazBlocker]`)
+
+ bt.assertNotBlocking(`BazNotBlocker.Baz`)
+ bt.assertNotBlocking(`notBlockingViaExplicit`)
+ bt.assertNotBlocking(`notBlockingViaImplicit`)
+ bt.assertNotBlockingInst(`test.FooBaz[pkg/test.BazNotBlocker]`)
+}
+
+func TestBlocking_NestedInstantiations(t *testing.T) {
+ // Checking that the type parameters are being propagated down into calls.
+ bt := newBlockingTest(t,
+ `package test
+
+ func Foo[T any](t T) {
+ println(t)
+ }
+
+ func Bar[K comparable, V any, M ~map[K]V](m M) {
+ Foo(m)
+ }
+
+ func Baz[T any, S ~[]T](s S) {
+ m:= map[int]T{}
+ for i, v := range s {
+ m[i] = v
+ }
+ Bar(m)
+ }
+
+ func bazInt() {
+ Baz([]int{1, 2, 3})
+ }
+
+ func bazString() {
+ Baz([]string{"one", "two", "three"})
+ }`)
+ bt.assertFuncInstCount(8)
+ bt.assertNotBlocking(`bazInt`)
+ bt.assertNotBlocking(`bazString`)
+ bt.assertNotBlockingInst(`test.Foo[map[int]int]`)
+ bt.assertNotBlockingInst(`test.Foo[map[int]string]`)
+ bt.assertNotBlockingInst(`test.Bar[int, int, map[int]int]`)
+ bt.assertNotBlockingInst(`test.Bar[int, string, map[int]string]`)
+ bt.assertNotBlockingInst(`test.Baz[int, []int]`)
+ bt.assertNotBlockingInst(`test.Baz[string, []string]`)
+}
+
+func TestBlocking_MethodSelection(t *testing.T) {
+ // This tests method selection using method expression (receiver as the first
+ // argument) selecting on type and method call selecting on a variable.
+ // This tests in both generic (FooBaz[T]) and non-generic contexts.
+ bt := newBlockingTest(t,
+ `package test
+
+ type Foo interface { Baz() }
+
+ type BazBlocker struct {
+ c chan bool
+ }
+ func (bb BazBlocker) Baz() {
+ println(<-bb.c)
+ }
+
+ type BazNotBlocker struct {}
+ func (bnb BazNotBlocker) Baz() {
+ println("hi")
+ }
+
+ type FooBaz[T Foo] struct {}
+ func (fb FooBaz[T]) ByMethodExpression() {
+ var foo T
+ T.Baz(foo)
+ }
+ func (fb FooBaz[T]) ByInstance() {
+ var foo T
+ foo.Baz()
+ }
+
+ func blocking() {
+ fb := FooBaz[BazBlocker]{}
+
+ FooBaz[BazBlocker].ByMethodExpression(fb)
+ FooBaz[BazBlocker].ByInstance(fb)
+
+ fb.ByMethodExpression()
+ fb.ByInstance()
+ }
+
+ func notBlocking() {
+ fb := FooBaz[BazNotBlocker]{}
+
+ FooBaz[BazNotBlocker].ByMethodExpression(fb)
+ FooBaz[BazNotBlocker].ByInstance(fb)
+
+ fb.ByMethodExpression()
+ fb.ByInstance()
+ }`)
+ bt.assertFuncInstCount(8)
+
+ bt.assertBlocking(`BazBlocker.Baz`)
+ bt.assertBlockingInst(`test.ByMethodExpression[pkg/test.BazBlocker]`)
+ bt.assertBlockingInst(`test.ByInstance[pkg/test.BazBlocker]`)
+ bt.assertBlocking(`blocking`)
+
+ bt.assertNotBlocking(`BazNotBlocker.Baz`)
+ bt.assertNotBlockingInst(`test.ByMethodExpression[pkg/test.BazNotBlocker]`)
+ bt.assertNotBlockingInst(`test.ByInstance[pkg/test.BazNotBlocker]`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_IsImportBlocking_Simple(t *testing.T) {
+ otherSrc := `package other
+
+ func Blocking() {
+ ch := make(chan bool)
+ <-ch
+ }
+
+ func NotBlocking() {
+ println("hi")
+ }`
+
+ testSrc := `package test
+
+ import "pkg/other"
+
+ func blocking() {
+ other.Blocking()
+ }
+
+ func notBlocking() {
+ other.NotBlocking()
+ }`
+
+ bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_IsImportBlocking_ForwardInstances(t *testing.T) {
+ otherSrc := `package other
+
+ type BazBlocker struct {
+ c chan bool
+ }
+ func (bb BazBlocker) Baz() {
+ println(<-bb.c)
+ }
+
+ type BazNotBlocker struct {}
+ func (bnb BazNotBlocker) Baz() {
+ println("hi")
+ }`
+
+ testSrc := `package test
+
+ import "pkg/other"
+
+ type Foo interface { Baz() }
+ func FooBaz[T Foo](f T) {
+ f.Baz()
+ }
+
+ func blocking() {
+ FooBaz(other.BazBlocker{})
+ }
+
+ func notBlocking() {
+ FooBaz(other.BazNotBlocker{})
+ }`
+
+ bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+func TestBlocking_IsImportBlocking_BackwardInstances(t *testing.T) {
+ t.Skip(`isImportedBlocking doesn't fully handle instances yet`)
+ // TODO(grantnelson-wf): This test is currently failing because the info
+ // for the test package is need while creating the instances for FooBaz
+ // while analyzing the other package. However the other package is analyzed
+ // first since the test package is dependent on it. One possible fix is that
+ // we add some mechanism similar to the localInstCallees but for remote
+ // instances then perform the blocking propagation steps for all packages
+ // including the localInstCallees propagation at the same time. After all the
+ // propagation of the calls then the flow control statements can be marked.
+
+ otherSrc := `package other
+
+ type Foo interface { Baz() }
+ func FooBaz[T Foo](f T) {
+ f.Baz()
+ }`
+
+ testSrc := `package test
+
+ import "pkg/other"
+
+ type BazBlocker struct {
+ c chan bool
+ }
+ func (bb BazBlocker) Baz() {
+ println(<-bb.c)
+ }
+
+ type BazNotBlocker struct {}
+ func (bnb BazNotBlocker) Baz() {
+ println("hi")
+ }
+
+ func blocking() {
+ other.FooBaz(BazBlocker{})
+ }
+
+ func notBlocking() {
+ other.FooBaz(BazNotBlocker{})
+ }`
+
+ bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
+ bt.assertBlocking(`blocking`)
+ bt.assertNotBlocking(`notBlocking`)
+}
+
+type blockingTest struct {
+ f *srctesting.Fixture
+ file *ast.File
+ pkgInfo *Info
+}
+
+func newBlockingTest(t *testing.T, src string) *blockingTest {
f := srctesting.New(t)
- file := f.Parse("test.go", src)
- typesInfo, typesPkg := f.Check("pkg/test", file)
+ tc := typeparams.Collector{
+ TContext: types.NewContext(),
+ Info: f.Info,
+ Instances: &typeparams.PackageInstanceSets{},
+ }
- pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, typesInfo, typesPkg, func(f *types.Func) bool {
- panic("isBlocking() should be never called for imported functions in this test.")
+ file := f.Parse(`test.go`, src)
+ testInfo, testPkg := f.Check(`pkg/test`, file)
+ tc.Scan(testPkg, file)
+
+ isImportBlocking := func(i typeparams.Instance) bool {
+ t.Fatalf(`isImportBlocking should not be called in this test, called with %v`, i)
+ return true
+ }
+ pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, testInfo, types.NewContext(), testPkg, tc.Instances, isImportBlocking)
+
+ return &blockingTest{
+ f: f,
+ file: file,
+ pkgInfo: pkgInfo,
+ }
+}
+
+func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc string) *blockingTest {
+ f := srctesting.New(t)
+ tc := typeparams.Collector{
+ TContext: types.NewContext(),
+ Info: f.Info,
+ Instances: &typeparams.PackageInstanceSets{},
+ }
+
+ pkgInfo := map[*types.Package]*Info{}
+ isImportBlocking := func(i typeparams.Instance) bool {
+ if info, ok := pkgInfo[i.Object.Pkg()]; ok {
+ return info.IsBlocking(i)
+ }
+ t.Fatalf(`unexpected package in isImportBlocking for %v`, i)
+ return true
+ }
+
+ otherFile := f.Parse(`other.go`, otherSrc)
+ _, otherPkg := f.Check(`pkg/other`, otherFile)
+ tc.Scan(otherPkg, otherFile)
+
+ testFile := f.Parse(`test.go`, testSrc)
+ _, testPkg := f.Check(`pkg/test`, testFile)
+ tc.Scan(testPkg, testFile)
+
+ otherPkgInfo := AnalyzePkg([]*ast.File{otherFile}, f.FileSet, f.Info, types.NewContext(), otherPkg, tc.Instances, isImportBlocking)
+ pkgInfo[otherPkg] = otherPkgInfo
+
+ testPkgInfo := AnalyzePkg([]*ast.File{testFile}, f.FileSet, f.Info, types.NewContext(), testPkg, tc.Instances, isImportBlocking)
+ pkgInfo[testPkg] = testPkgInfo
+
+ return &blockingTest{
+ f: f,
+ file: testFile,
+ pkgInfo: testPkgInfo,
+ }
+}
+
+func (bt *blockingTest) assertFuncInstCount(expCount int) {
+ if got := bt.pkgInfo.funcInstInfos.Len(); got != expCount {
+ bt.f.T.Errorf(`Got %d function infos but expected %d.`, got, expCount)
+ for i, inst := range bt.pkgInfo.funcInstInfos.Keys() {
+ bt.f.T.Logf(` %d. %q`, i+1, inst.TypeString())
+ }
+ }
+}
+
+func (bt *blockingTest) assertBlocking(funcName string) {
+ if !bt.isTypesFuncBlocking(funcName) {
+ bt.f.T.Errorf(`Got %q as not blocking but expected it to be blocking.`, funcName)
+ }
+}
+
+func (bt *blockingTest) assertNotBlocking(funcName string) {
+ if bt.isTypesFuncBlocking(funcName) {
+ bt.f.T.Errorf(`Got %q as blocking but expected it to be not blocking.`, funcName)
+ }
+}
+
+func getFuncDeclName(fd *ast.FuncDecl) string {
+ name := fd.Name.Name
+ if fd.Recv != nil && len(fd.Recv.List) == 1 && fd.Recv.List[0].Type != nil {
+ typ := fd.Recv.List[0].Type
+ if p, ok := typ.(*ast.StarExpr); ok {
+ typ = p.X
+ }
+ if id, ok := typ.(*ast.Ident); ok {
+ name = id.Name + `.` + name
+ }
+ }
+ return name
+}
+
+func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool {
+ var decl *ast.FuncDecl
+ ast.Inspect(bt.file, func(n ast.Node) bool {
+ if f, ok := n.(*ast.FuncDecl); ok && getFuncDeclName(f) == funcName {
+ decl = f
+ return false
+ }
+ return decl == nil
})
- assertBlocking(t, file, pkgInfo, "blocking")
- assertBlocking(t, file, pkgInfo, "indirectlyBlocking")
- assertBlocking(t, file, pkgInfo, "directlyBlocking")
- assertNotBlocking(t, file, pkgInfo, "notBlocking")
-}
-
-func assertBlocking(t *testing.T, file *ast.File, pkgInfo *Info, funcName string) {
- typesFunc := getTypesFunc(t, file, pkgInfo, funcName)
- if !pkgInfo.IsBlocking(typesFunc) {
- t.Errorf("Got: %q is not blocking. Want: %q is blocking.", typesFunc, typesFunc)
+ if decl == nil {
+ bt.f.T.Fatalf(`Declaration of %q is not found in the AST.`, funcName)
}
-}
-func assertNotBlocking(t *testing.T, file *ast.File, pkgInfo *Info, funcName string) {
- typesFunc := getTypesFunc(t, file, pkgInfo, funcName)
- if pkgInfo.IsBlocking(typesFunc) {
- t.Errorf("Got: %q is blocking. Want: %q is not blocking.", typesFunc, typesFunc)
- }
-}
-
-func getTypesFunc(t *testing.T, file *ast.File, pkgInfo *Info, funcName string) *types.Func {
- obj := file.Scope.Lookup(funcName)
- if obj == nil {
- t.Fatalf("Declaration of %q is not found in the AST.", funcName)
- }
- decl, ok := obj.Decl.(*ast.FuncDecl)
+ blockingType, ok := bt.pkgInfo.Defs[decl.Name]
if !ok {
- t.Fatalf("Got: %q is %v. Want: a function declaration.", funcName, obj.Kind)
+ bt.f.T.Fatalf(`No function declaration found for %q.`, decl.Name)
}
- blockingType, ok := pkgInfo.Defs[decl.Name]
- if !ok {
- t.Fatalf("No type information is found for %v.", decl.Name)
+
+ inst := typeparams.Instance{Object: blockingType.(*types.Func)}
+ return bt.pkgInfo.IsBlocking(inst)
+}
+
+func (bt *blockingTest) assertBlockingLit(lineNo int) {
+ if !bt.isFuncLitBlocking(lineNo) {
+ bt.f.T.Errorf(`Got FuncLit at line %d as not blocking but expected it to be blocking.`, lineNo)
}
- return blockingType.(*types.Func)
+}
+
+func (bt *blockingTest) assertNotBlockingLit(lineNo int) {
+ if bt.isFuncLitBlocking(lineNo) {
+ bt.f.T.Errorf(`Got FuncLit at line %d as blocking but expected it to be not blocking.`, lineNo)
+ }
+}
+
+func (bt *blockingTest) getFuncLitLineNo(fl *ast.FuncLit) int {
+ return bt.f.FileSet.Position(fl.Pos()).Line
+}
+
+func (bt *blockingTest) isFuncLitBlocking(lineNo int) bool {
+ var fnLit *ast.FuncLit
+ ast.Inspect(bt.file, func(n ast.Node) bool {
+ if fl, ok := n.(*ast.FuncLit); ok && bt.getFuncLitLineNo(fl) == lineNo {
+ fnLit = fl
+ return false
+ }
+ return fnLit == nil
+ })
+
+ if fnLit == nil {
+ bt.f.T.Fatalf(`FuncLit found on line %d not found in the AST.`, lineNo)
+ }
+ return bt.pkgInfo.FuncLitInfo(fnLit).HasBlocking()
+}
+
+func (bt *blockingTest) assertBlockingInst(instanceStr string) {
+ if !bt.isFuncInstBlocking(instanceStr) {
+ bt.f.T.Errorf(`Got function instance of %q as not blocking but expected it to be blocking.`, instanceStr)
+ }
+}
+
+func (bt *blockingTest) assertNotBlockingInst(instanceStr string) {
+ if bt.isFuncInstBlocking(instanceStr) {
+ bt.f.T.Errorf(`Got function instance of %q as blocking but expected it to be not blocking.`, instanceStr)
+ }
+}
+
+func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool {
+ instances := bt.pkgInfo.funcInstInfos.Keys()
+ for _, inst := range instances {
+ if inst.TypeString() == instanceStr {
+ return bt.pkgInfo.FuncInfo(inst).HasBlocking()
+ }
+ }
+ bt.f.T.Logf(`Function instances found in package info:`)
+ for i, inst := range instances {
+ bt.f.T.Logf(` %d. %s`, i+1, inst.TypeString())
+ }
+ bt.f.T.Fatalf(`No function instance found for %q in package info.`, instanceStr)
+ return false
}
diff --git a/compiler/internal/typeparams/instance.go b/compiler/internal/typeparams/instance.go
index f847a98..763cd64 100644
--- a/compiler/internal/typeparams/instance.go
+++ b/compiler/internal/typeparams/instance.go
@@ -147,6 +147,18 @@
return result
}
+// ForObj returns instances for a given object type belong to. Order is not specified.
+// This returns the same values as `ByObj()[obj]`.
+func (iset *InstanceSet) ForObj(obj types.Object) []Instance {
+ result := []Instance{}
+ for _, inst := range iset.values {
+ if inst.Object == obj {
+ result = append(result, inst)
+ }
+ }
+ return result
+}
+
// PackageInstanceSets stores an InstanceSet for each package in a program, keyed
// by import path.
type PackageInstanceSets map[string]*InstanceSet
diff --git a/compiler/package.go b/compiler/package.go
index 9fcf9d0..4cd8006 100644
--- a/compiler/package.go
+++ b/compiler/package.go
@@ -118,14 +118,14 @@
funcLitCounter int
}
-func newRootCtx(tContext *types.Context, srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool, minify bool) *funcContext {
+func newRootCtx(tContext *types.Context, srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(typeparams.Instance) bool, minify bool) *funcContext {
tc := typeparams.Collector{
TContext: tContext,
Info: typesInfo,
Instances: &typeparams.PackageInstanceSets{},
}
tc.Scan(typesPkg, srcs.Files...)
- pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, typesPkg, isBlocking)
+ pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, tContext, typesPkg, tc.Instances, isBlocking)
funcCtx := &funcContext{
FuncInfo: pkgInfo.InitFuncInfo,
pkgCtx: &pkgContext{
@@ -176,11 +176,20 @@
// Note: see analysis.FuncInfo.Blocking if you need to determine if a function
// in the _current_ package is blocking. Usually available via functionContext
// object.
-func (ic *ImportContext) isBlocking(f *types.Func) bool {
+func (ic *ImportContext) isBlocking(inst typeparams.Instance) bool {
+ f, ok := inst.Object.(*types.Func)
+ if !ok {
+ panic(bailout(fmt.Errorf("can't determine if instance %v is blocking: instance isn't for a function object", inst)))
+ }
+
archive, err := ic.Import(f.Pkg().Path())
if err != nil {
panic(err)
}
+
+ // TODO(grantnelson-wf): f.FullName() does not differentiate between
+ // different instantiations of the same generic function. This needs to be
+ // fixed when the declaration names are updated to better support instances.
fullName := f.FullName()
for _, d := range archive.Declarations {
if string(d.FullName) == fullName {
diff --git a/internal/srctesting/srctesting.go b/internal/srctesting/srctesting.go
index 961dffd..35d9e25 100644
--- a/internal/srctesting/srctesting.go
+++ b/internal/srctesting/srctesting.go
@@ -74,7 +74,7 @@
}
pkg, err := config.Check(importPath, f.FileSet, files, info)
if err != nil {
- f.T.Fatalf("Filed to type check test source: %s", err)
+ f.T.Fatalf("Failed to type check test source: %s", err)
}
f.Packages[importPath] = pkg
return info, pkg