From 6708a5d15987b6adcab2325e3371bf34f8ba6a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Tue, 22 Sep 2026 21:04:01 -0400 Subject: [PATCH] Fix MA0109 false positive on generic methods with a Span overload --- docs/Rules/MA0109.md | 7 ++ .../AddOverloadWithSpanOrMemoryAnalyzer.cs | 6 ++ ...ddOverloadWithSpanOrMemoryAnalyzerTests.cs | 64 +++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/docs/Rules/MA0109.md b/docs/Rules/MA0109.md index 2b02e3599..c07d2bafc 100644 --- a/docs/Rules/MA0109.md +++ b/docs/Rules/MA0109.md @@ -12,4 +12,11 @@ void A(System.ReadOnlySpan a) { } void A(string[] a) { } // report diagnostic ```` +The type parameters of generic methods are matched by position: + +````c# +void A(T[] a) { } // ok as there is an overload with Span +void A(System.ReadOnlySpan a) { } +```` + The rule does not report a diagnostic for the program entry point (`Main(string[] args)`), as its signature is mandated by the runtime. diff --git a/src/Meziantou.Analyzer/Rules/AddOverloadWithSpanOrMemoryAnalyzer.cs b/src/Meziantou.Analyzer/Rules/AddOverloadWithSpanOrMemoryAnalyzer.cs index c5e6768d0..e9e34f375 100644 --- a/src/Meziantou.Analyzer/Rules/AddOverloadWithSpanOrMemoryAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/AddOverloadWithSpanOrMemoryAnalyzer.cs @@ -108,6 +108,12 @@ private bool IsValidOverload(IMethodSymbol method, IMethodSymbol overload) if (overload.Parameters.Length != method.Parameters.Length) return false; + // Match the type parameters of the methods by ordinal, so M(T[]) and M(ReadOnlySpan) are compared using the same T + if (method.TypeParameters.Length > 0 && overload.TypeParameters.Length == method.TypeParameters.Length) + { + overload = overload.Construct([.. method.TypeParameters]); + } + for (var i = 0; i < method.Parameters.Length; i++) { var methodParameter = method.Parameters[i].Type; diff --git a/tests/Meziantou.Analyzer.Test/Rules/AddOverloadWithSpanOrMemoryAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/AddOverloadWithSpanOrMemoryAnalyzerTests.cs index 0cf0e8ef2..a6c3c099c 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/AddOverloadWithSpanOrMemoryAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/AddOverloadWithSpanOrMemoryAnalyzerTests.cs @@ -175,4 +175,68 @@ public void A({{overloadType}} a) { } return test.RunAsync(); } + + [Theory] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + [InlineData("System.ReadOnlyMemory")] + public Task GenericArrayWithSpanOverload(string overloadType) + { + var test = CreateTest(); + test.TestCode = $$""" + public class Sample + { + public void M(T[] values) { } + public void M({{overloadType}} values) { } + } + """; + + return test.RunAsync(); + } + + [Fact] + public Task GenericArrayWithSpanOverload_DifferentTypeParameterNames() + { + var test = CreateTest(); + test.TestCode = """ + public class Sample + { + public void M(T[] values, T value) { } + public void M(System.ReadOnlySpan values, TItem value) { } + } + """; + + return test.RunAsync(); + } + + [Fact] + public Task GenericArrayWithNonGenericSpanOverload() + { + var test = CreateTest(); + test.TestCode = """ + public class Sample + { + public void {|MA0109:M|}(T[] values) { } + public void M(System.ReadOnlySpan values) { } + } + """; + + return test.RunAsync(); + } + + [Fact] + public Task GenericArrayWithSpanOverloadOfAnotherTypeParameter() + { + var test = CreateTest(); + test.TestCode = """ + public class Sample + { + public void {|MA0109:M|}(T1[] values, T2 value) { } + public void M(System.ReadOnlySpan values, T2 value) { } + } + """; + + return test.RunAsync(); + } }