Skip to content

Commit d4d68e3

Browse files
Add more cases for recursive call analyzer (#297)
* Add more cases for recursive call analyzer * Add the case with named parameters * Add comments to RecursiveCallAnalyzer
1 parent 99f99f3 commit d4d68e3

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

src/ErrorProne.NET.CoreAnalyzers.Tests/RecursiveCallAnalyzerTests.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using NUnit.Framework;
22
using System.Threading.Tasks;
3-
using ErrorProne.NET.CoreAnalyzers;
43
using Verify = ErrorProne.NET.TestHelpers.CSharpCodeFixVerifier<
54
ErrorProne.NET.CoreAnalyzers.RecursiveCallAnalyzer,
65
Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
@@ -32,6 +31,48 @@ void Foo(bool b) {
3231
if (b) [|Foo(b)|];
3332
}
3433
}
34+
";
35+
await Verify.VerifyAsync(test);
36+
}
37+
38+
[Test]
39+
public async Task WarnsOnConditionalRecursiveCall_With_Named_Parameters()
40+
{
41+
var test = @"
42+
class C {
43+
void Foo(bool b) {
44+
if (b) [|Foo(b: b)|];
45+
}
46+
}
47+
";
48+
await Verify.VerifyAsync(test);
49+
}
50+
51+
[Test]
52+
public async Task NoWarn_When_Different_Argument_Is_Passed()
53+
{
54+
var test = @"
55+
class C {
56+
void Foo(bool b) {
57+
if (b) Foo(false);
58+
}
59+
}
60+
";
61+
await Verify.VerifyAsync(test);
62+
}
63+
64+
[Test]
65+
public async Task NoWarn_For_Factorial()
66+
{
67+
var test = @"
68+
class C {
69+
int Factorial(int n)
70+
{
71+
if (n <= 1)
72+
return 1; // Base case
73+
return n * Factorial(n - 1); // Recursive call with changing argument
74+
}
75+
}
3576
";
3677
await Verify.VerifyAsync(test);
3778
}

src/ErrorProne.NET.CoreAnalyzers/RecursiveCallAnalyzer.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,19 @@ private static void AnalyzeMethodBody(OperationAnalysisContext context)
2626
var methodBody = (IMethodBodyOperation)context.Operation;
2727
foreach (var invocation in methodBody.Descendants().OfType<IInvocationOperation>())
2828
{
29-
if (SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.OriginalDefinition, method.OriginalDefinition))
29+
// Check if all parameters are passed as-is
30+
// So Factorial(n - 1) should be totally fine!
31+
if (invocation.Arguments.Length == method.Parameters.Length &&
32+
// Checking that the method is the same.
33+
SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.OriginalDefinition, method.OriginalDefinition) &&
34+
35+
// Checking if the parameters are passed as is.
36+
// It is possible to have a false positive here if the parameters are mutable.
37+
// But it is a very rare case, so we will ignore it for now.
38+
invocation.Arguments.Zip(method.Parameters, (arg, param) =>
39+
arg.Value is IParameterReferenceOperation paramRef &&
40+
SymbolEqualityComparer.Default.Equals(paramRef.Parameter, param)
41+
).All(b => b))
3042
{
3143
context.ReportDiagnostic(Diagnostic.Create(
3244
DiagnosticDescriptors.EPC30,

0 commit comments

Comments
 (0)