From c2cbb60ec4273d08b8294571decd6b1dbdeed4c7 Mon Sep 17 00:00:00 2001 From: ppipada Date: Wed, 10 Sep 2025 14:13:47 +0530 Subject: [PATCH 1/2] allow direct self recursion --- analysis/refdir/analyzer.go | 10 +++++ .../defaultdirs/func_recursive.go | 32 ++++++++++++++ .../defaultdirs/method_recursive.go | 42 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 analysis/refdir/testdata/analysistest/defaultdirs/func_recursive.go create mode 100644 analysis/refdir/testdata/analysistest/defaultdirs/method_recursive.go diff --git a/analysis/refdir/analyzer.go b/analysis/refdir/analyzer.go index fa40a7a..cb7c33e 100644 --- a/analysis/refdir/analyzer.go +++ b/analysis/refdir/analyzer.go @@ -199,6 +199,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) +} From ab746986b1717673f8c9bf7aeba9dfc17ab1c884 Mon Sep 17 00:00:00 2001 From: ppipada Date: Wed, 10 Sep 2025 14:17:11 +0530 Subject: [PATCH 2/2] ignore actually if a kind is ignored --- analysis/refdir/analyzer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/analysis/refdir/analyzer.go b/analysis/refdir/analyzer.go index cb7c33e..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() {