Skip to content

Commit

Permalink
Fixing selection objects to instances.
Browse files Browse the repository at this point in the history
  • Loading branch information
grantnelson-wf committed Oct 21, 2024
1 parent ec62eb1 commit 0500101
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 34 deletions.
24 changes: 10 additions & 14 deletions compiler/internal/analysis/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func newContinueStmt(forStmt *ast.ForStmt, stack astPath) continueStmt {
return cs
}

type IsBlockingQuerier func(typeparams.Instance) bool

// astPath is a list of AST nodes where each previous node is a parent of the
// next node.
type astPath []ast.Node
Expand Down Expand Up @@ -58,7 +60,7 @@ type Info struct {
funcLitInfos map[*ast.FuncLit]*FuncInfo
InitFuncInfo *FuncInfo // Context for package variable initialization.

isImportedBlocking func(*types.Func) bool // For functions from other packages.
isImportedBlocking IsBlockingQuerier // For functions from other packages.
allInfos []*FuncInfo
}

Expand Down Expand Up @@ -100,23 +102,17 @@ func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo {
return funcInfo
}

func (info *Info) newFuncInfoInstances(n ast.Node) []*FuncInfo {
fd, ok := n.(*ast.FuncDecl)
if !ok {
// This is not a function declaration, so it has no instances.
return []*FuncInfo{info.newFuncInfo(n, nil)}
}

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(n, nil)}
return []*FuncInfo{info.newFuncInfo(fd, nil)}
}

funcInfos := make([]*FuncInfo, 0, len(instances))
for _, inst := range instances {
fi := info.newFuncInfo(n, &inst)
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)
Expand Down Expand Up @@ -157,7 +153,7 @@ func (info *Info) VarsWithInitializers() map[*types.Var]bool {
return result
}

func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typeCtx *types.Context, typesPkg *types.Package, instanceSets *typeparams.PackageInstanceSets, 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 IsBlockingQuerier) *Info {
info := &Info{
Info: typesInfo,
Pkg: typesPkg,
Expand Down Expand Up @@ -293,7 +289,8 @@ func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor {

switch n := node.(type) {
case *ast.FuncDecl:
// Analyze the function in its own context.
// 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 {
Expand Down Expand Up @@ -513,8 +510,7 @@ func (fi *FuncInfo) callToNamedFunc(callee typeparams.Instance) {
}
}
if o.Pkg() != fi.pkgInfo.Pkg {
// TODO: make isImportedBlocking take an instance type.
if fi.pkgInfo.isImportedBlocking(o) {
if fi.pkgInfo.isImportedBlocking(callee) {
fi.markBlocking(fi.visitorStack)
}
return
Expand Down
228 changes: 210 additions & 18 deletions compiler/internal/analysis/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,54 @@ func TestBlocking_ComplexCall(t *testing.T) {
bt.assertBlocking(`bar`)
}

func TestBlocking_CallWithInterfaceReceiver(t *testing.T) {
// This checks that calling an 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_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_FieldFunctionCall(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 {
bar func()
}
func bar(f foo) {
f.bar()
}`)
bt.assertBlocking(`bar`)
}

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.
Expand Down Expand Up @@ -791,6 +839,119 @@ func TestBlocking_MethodSelection(t *testing.T) {
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
Expand All @@ -799,25 +960,64 @@ type blockingTest struct {

func newBlockingTest(t *testing.T, src string) *blockingTest {
f := srctesting.New(t)
tc := typeparams.Collector{
TContext: types.NewContext(),
Info: f.Info,
Instances: &typeparams.PackageInstanceSets{},
}

file := f.Parse(`test.go`, src)
typesInfo, typesPkg := f.Check(`pkg/test`, file)
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: typesInfo,
Info: f.Info,
Instances: &typeparams.PackageInstanceSets{},
}
tc.Scan(typesPkg, file)

pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, typesInfo, types.NewContext(), typesPkg, tc.Instances, func(f *types.Func) bool {
panic(`isBlocking() should be never called for imported functions in this test.`)
})
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: file,
pkgInfo: pkgInfo,
file: testFile,
pkgInfo: testPkgInfo,
}
}

Expand Down Expand Up @@ -870,11 +1070,7 @@ func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool {
}

inst := typeparams.Instance{Object: blockingType.(*types.Func)}
funcInfo := bt.pkgInfo.funcInstInfos.Get(inst)
if funcInfo == nil {
bt.f.T.Fatalf(`No function instance info found for %q in package info.`, funcName)
}
return funcInfo.HasBlocking()
return bt.pkgInfo.IsBlocking(inst)
}

func (bt *blockingTest) assertBlockingLit(lineNo int) {
Expand Down Expand Up @@ -904,11 +1100,7 @@ func (bt *blockingTest) isFuncLitBlocking(lineNo int) bool {
if fnLit == nil {
bt.f.T.Fatalf(`FuncLit found on line %d not found in the AST.`, lineNo)
}
info, ok := bt.pkgInfo.funcLitInfos[fnLit]
if !ok {
bt.f.T.Fatalf(`No type information is found for FuncLit at line %d.`, lineNo)
}
return info.HasBlocking()
return bt.pkgInfo.FuncLitInfo(fnLit).HasBlocking()
}

func (bt *blockingTest) assertBlockingInst(instanceStr string) {
Expand Down
13 changes: 11 additions & 2 deletions compiler/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ type funcContext struct {
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 analysis.IsBlockingQuerier, minify bool) *funcContext {
tc := typeparams.Collector{
TContext: tContext,
Info: typesInfo,
Expand Down Expand Up @@ -176,11 +176,20 @@ type ImportContext struct {
// 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 {
Expand Down

0 comments on commit 0500101

Please sign in to comment.