Skip to content

Commit 0b48abd

Browse files
committed
Fixed false positive for ambiguous method overloads in SA1410
1 parent 90b322a commit 0b48abd

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1410UnitTests.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,109 @@ public void Bar()
9999
await this.VerifyCSharpFixAsync(oldSource, newSource, cancellationToken: CancellationToken.None).ConfigureAwait(false);
100100
}
101101

102+
/// <summary>
103+
/// Verify that no diagnostic is produced when removing the parentheses from the delegate statement
104+
/// would result in ambiguity on which method overload to call.
105+
/// </summary>
106+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
107+
[Fact]
108+
[WorkItem(2572, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2572")]
109+
public async Task TestMethodOverloadAmbiguityAsync()
110+
{
111+
var testCode = @"public class TestClass
112+
{
113+
public delegate bool Delegate1();
114+
public delegate bool Delegate2(int x);
115+
116+
public void TestMethod()
117+
{
118+
var thread = new System.Threading.Thread(delegate()
119+
{
120+
// ...
121+
});
122+
123+
this.TestMethod2(delegate() { return true; });
124+
}
125+
126+
public void TestMethod2(Delegate1 d)
127+
{
128+
}
129+
130+
public void TestMethod2(Delegate2 d)
131+
{
132+
}
133+
}
134+
";
135+
136+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
137+
}
138+
139+
/// <summary>
140+
/// Verify that the ambiguity detection is specific enough
141+
/// </summary>
142+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
143+
[Fact]
144+
[WorkItem(2572, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2572")]
145+
public async Task TestMethodOverloadNonAmbiguityAsync()
146+
{
147+
var testCode = @"public class TestClass
148+
{
149+
public delegate bool Delegate1();
150+
public delegate bool Delegate2(int x);
151+
152+
public void TestMethod()
153+
{
154+
this.TestMethod2(delegate() { return true; });
155+
this.TestMethod3(0, delegate() { return true; });
156+
this.TestMethod4(delegate() { return true; }, 1);
157+
this.TestMethod5(delegate() { return true; });
158+
}
159+
160+
public void TestMethod2(Delegate1 d)
161+
{
162+
}
163+
164+
public void TestMethod2(int d)
165+
{
166+
}
167+
168+
public void TestMethod3(int x, Delegate1 d)
169+
{
170+
}
171+
172+
public void TestMethod3(double x, Delegate2 d)
173+
{
174+
}
175+
176+
public void TestMethod4(Delegate1 d, int x)
177+
{
178+
}
179+
180+
public void TestMethod4(Delegate2 d, double x)
181+
{
182+
}
183+
184+
public void TestMethod5(Delegate1 d)
185+
{
186+
}
187+
188+
public void TestMethod5(Delegate2 d, double x)
189+
{
190+
}
191+
}
192+
";
193+
194+
DiagnosticResult[] expectedResults =
195+
{
196+
this.CSharpDiagnostic().WithLocation(8, 34),
197+
this.CSharpDiagnostic().WithLocation(9, 37),
198+
this.CSharpDiagnostic().WithLocation(10, 34),
199+
this.CSharpDiagnostic().WithLocation(11, 34),
200+
};
201+
202+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
203+
}
204+
102205
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
103206
{
104207
yield return new SA1410RemoveDelegateParenthesisWhenPossible();

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1410RemoveDelegateParenthesisWhenPossible.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace StyleCop.Analyzers.MaintainabilityRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Linq;
9+
using System.Threading;
810
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.CSharp;
1012
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -81,8 +83,59 @@ private static void HandleAnonymousMethodExpression(SyntaxNodeAnalysisContext co
8183
return;
8284
}
8385

86+
// if the delegate is passed as a parameter, verify that there is no ambiguity.
87+
if (syntax.Parent.IsKind(SyntaxKind.Argument))
88+
{
89+
var argumentSyntax = (ArgumentSyntax)syntax.Parent;
90+
var argumentListSyntax = (ArgumentListSyntax)argumentSyntax.Parent;
91+
92+
switch (argumentListSyntax.Parent.Kind())
93+
{
94+
case SyntaxKind.ObjectCreationExpression:
95+
case SyntaxKind.InvocationExpression:
96+
if (HasAmbiguousOverload(context, argumentListSyntax.Arguments.IndexOf(argumentSyntax), argumentListSyntax.Parent))
97+
{
98+
return;
99+
}
100+
101+
break;
102+
}
103+
}
104+
84105
// Remove delegate parenthesis when possible
85106
context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.ParameterList.GetLocation()));
86107
}
108+
109+
private static bool HasAmbiguousOverload(SyntaxNodeAnalysisContext context, int parameterIndex, SyntaxNode methodCallSyntax)
110+
{
111+
var methodSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(methodCallSyntax, context.CancellationToken).Symbol;
112+
113+
var nameOverloads = methodSymbol.ContainingType.GetMembers(methodSymbol.Name);
114+
var parameterCountMatchingOverloads = nameOverloads.OfType<IMethodSymbol>().Where(symbol => (symbol != methodSymbol) && (symbol.Parameters.Length == methodSymbol.Parameters.Length));
115+
116+
foreach (var overload in parameterCountMatchingOverloads)
117+
{
118+
var isAmbiguousOverload = true;
119+
120+
for (var i = 0; isAmbiguousOverload && (i < methodSymbol.Parameters.Length); i++)
121+
{
122+
if (i == parameterIndex)
123+
{
124+
isAmbiguousOverload = overload.Parameters[i].Type.TypeKind == TypeKind.Delegate;
125+
}
126+
else
127+
{
128+
isAmbiguousOverload = methodSymbol.Parameters[i].Type == overload.Parameters[i].Type;
129+
}
130+
}
131+
132+
if (isAmbiguousOverload)
133+
{
134+
return true;
135+
}
136+
}
137+
138+
return false;
139+
}
87140
}
88141
}

0 commit comments

Comments
 (0)