diff --git a/analysis/refdir/analyzer.go b/analysis/refdir/analyzer.go index fa40a7a..303d818 100644 --- a/analysis/refdir/analyzer.go +++ b/analysis/refdir/analyzer.go @@ -113,6 +113,7 @@ func run(pass *analysis.Pass) (interface{}, error) { if RefOrder[kind] == Ignore { printer.Info(ref.Pos(), fmt.Sprintf("%s reference %s ignored by options", kind, ref.Name)) + return } if pass.Fset.File(ref.Pos()).Name() != pass.Fset.File(def).Name() { @@ -199,6 +200,16 @@ func run(pass *analysis.Pass) (interface{}, error) { case *types.Func: def = def.Origin() + // Allow direct self-recursion (call to the function we're inside). + if funcDecl != nil { + curr, ok := pass.TypesInfo.Defs[funcDecl.Name].(*types.Func) + if ok && curr != nil && curr.Origin() == def { + // For a recursive call, pass.TypesInfo.Uses[node] returns the current function’s object; + // comparing its Origin() to the current func’s Origin() lets us detect direct recursion even with generics instantiation. + break + } + } + if def.Parent() != nil && def.Parent() != def.Pkg().Scope() { printer.Info(node.Pos(), fmt.Sprintf("skipping func ident %s with inner parent scope %s", node.Name, pass.Fset.Position(def.Parent().Pos()))) } else { diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/func_recursive.go b/analysis/refdir/testdata/analysistest/defaultdirs/func_recursive.go new file mode 100644 index 0000000..f338275 --- /dev/null +++ b/analysis/refdir/testdata/analysistest/defaultdirs/func_recursive.go @@ -0,0 +1,32 @@ +package defaultdirs + +// Direct recursion with a base case: function. +func RecursiveFunctionSafe(n int) int { + if n <= 0 { + return 0 + } + return 1 + RecursiveFunctionSafe(n-1) +} + +// Generic direct recursion with a base case: function. +func RecursiveGenericSafe[T any](n int, x T) int { + if n <= 0 { + return 0 + } + return 1 + RecursiveGenericSafe[T](n-1, x) +} + +// Mutual recursion: only this second call should be flagged (A is defined above). +func MutualA(n int) int { + if n <= 0 { + return 0 + } + return MutualB(n - 1) // OK: call before MutualB's definition +} + +func MutualB(n int) int { + if n <= 0 { + return 0 + } + return MutualA(n - 1) // want "func reference MutualA is after definition" +} diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/method_recursive.go b/analysis/refdir/testdata/analysistest/defaultdirs/method_recursive.go new file mode 100644 index 0000000..314d50c --- /dev/null +++ b/analysis/refdir/testdata/analysistest/defaultdirs/method_recursive.go @@ -0,0 +1,42 @@ +package defaultdirs + +// Value receiver recursion with a base case: method. +type FooValue struct{} + +func (f FooValue) Bar(n int) int { + if n <= 0 { + return 0 + } + return 1 + f.Bar(n-1) +} + +// Pointer receiver recursion with a base case: method. +type FooPtr struct{} + +func (f *FooPtr) Bar(n int) int { + if f == nil || n <= 0 { + return 0 + } + return 1 + f.Bar(n-1) +} + +// Generic receiver type recursion with a base case: method. +type Box[T any] struct{} + +func (b Box[T]) Beat(n int) int { + if n <= 0 { + return 0 + } + return 1 + b.Beat(n-1) +} + +// Method value recursion with a base case: ensure we also don't flag taking method values. +type RecMethodValue struct{} + +func (r RecMethodValue) MethodVal(n int) int { + f := r.MethodVal + if n <= 0 { + return 0 + } + return 1 + f(n-1) +}