Skip to content

Add an option to include extension methods from namespaces that are not imported in the rules using OverloadFinder - #1521

Merged
meziantou merged 3 commits into
mainfrom
feature/ma0042-add-missing-using
Sep 12, 2026
Merged

meziantou merged 3 commits into
mainfrom
feature/ma0042-add-missing-using

Conversation

@meziantou

@meziantou meziantou commented Sep 12, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #1520.

Problem

The rules that suggest another overload only consider the overloads in scope at the call site. An extension method declared in a namespace that the file does not import is ignored, even though adding a using directive is enough to call it:

using System.Threading.Tasks;

class Test
{
    public async Task A(Sample sample)
    {
        sample.Do(); // not reported, although Ext.SampleExtensions.DoAsync exists
    }
}

Changes

New option, opt-in, for every rule using OverloadFinder

<RuleId>.include_extension_methods_from_not_imported_namespaces = false
Rules Analyzer Code fix adds the using
MA0001, MA0074 UseStringComparisonAnalyzer ✔️
MA0002 UseStringComparerAnalyzer (invocations) ✔️
MA0011 UseIFormatProviderAnalyzer ✔️
MA0032, MA0040 UseAnOverloadThatHasCancellationTokenAnalyzer ✔️ (MA0040)
MA0042, MA0045 DoNotUseBlockingCallInAsyncContextAnalyzer ✔️
MA0166, MA0167 UseAnOverloadThatHasTimeProviderAnalyzer ✔️ (MA0166)
MA0210 UseInKeywordForInParameterAnalyzer ✔️
  • The option is per rule. When an analyzer reports two rules, the overloads are searched with the extension methods when the option is set for one of them, but the diagnostic is only reported when the option is set for the rule being reported (e.g. MA0045.… does not enable MA0042).
  • An overload that does not require a using directive is always preferred.
  • No option for MA0054 (only searches constructors, which cannot be extension methods) and MA0193 (its analyzer does not use OverloadFinder). MA0079, MA0080 and MA0209 do not search overloads themselves; MA0079/MA0080 stay consistent with what MA0032/MA0040 report.

OverloadFinder

  • New OverloadOptions.IncludeExtensionMethodsFromNotImportedNamespaces. When set (with a SyntaxNode), the candidates also include the extension methods of the compilation and of its references that are accessible at the call site, apply to the receiver, and are not already in scope. They are added after the candidates in scope and go through the same filters.
  • The candidates come from an index of the extension methods by name, built lazily once per compilation, only when the option is used. Extension methods of the global namespace are not indexed, as they are always in scope.
  • New GetNamespaceToImport(method, syntaxNode), returning the namespace to import when the overload requires a using directive. The analyzers pass it to the code fixes in the OverloadFinder.NamespaceToImportPropertyName diagnostic property.

Code fixes

  • New UsingDirectiveHelper: adds the using directive next to the existing ones (innermost namespace declaration that has using directives, otherwise the compilation unit), at its sorted position (System first) when they are sorted, after the global using directives, keeping the file header at the top, with the line endings of the document. FixAll adds it once.
  • ArgumentListHelper.AddArgument / GetTargetMethod accept the namespace to import and bind the new invocation in a copy of the compilation where the document imports it. Without it, the overload is not in scope, the invocation does not bind, and the fix would never be offered.
  • MA0042/MA0045: the fix renames the method and awaits it. It also fixes an existing bug with an implicit receiver: Do() became await DoAsync(), which does not compile for an extension method; the receiver is now explicit (await this.DoAsync()) when the method cannot be called with a simple name.

Notes for reviewers

  • Behavior change: none by default, as the option is false. The first commit of this PR enabled the MA0042 suggestion unconditionally; the second commit makes it opt-in.
  • OverloadOptions defaults: the call sites that used options: default keep a zero-initialized OverloadOptions (new OverloadOptions { … }), as its constructor defaults differ (e.g. AllowNumericConversion).
  • Cost (only when the option is set): indexing the extension methods of the .NET + ASP.NET reference assemblies (307 assemblies, 4,030 extension methods) takes about 15 ms per compilation once the metadata is loaded. Checking whether an overload requires a using directive repeats a symbol lookup, only for the overload that is found.
  • Not handled: the code fix does not detect a name conflict introduced by the new using directive. C# 14 extension(...) block members are only suggested when their namespace is imported.
  • Documentation: each rule documentation lists the new option in its .editorconfig section, as the documentation generator requires every configuration key to be documented. docs/README.md is regenerated.

Tests

  • Per rule: disabled by default, and diagnostic + code fix adding the using directive when enabled. Per-rule option tests for MA0045/MA0042, MA0001/MA0074, MA0032/MA0040 and MA0167/MA0166.
  • MA0042: using placement (sorted, no using directive with a file header, inside a namespace), FixAll, namespace imported in another file, candidates that are not async equivalents, implicit receiver.
  • The full suite passes on Roslyn 5.9 (4,717) and 4.8 (4,596); the tests of the affected rules pass on 4.14, 5.0 and 5.6. DocumentationGenerator reports no changes.

… in MA0042

MA0042 and MA0045 only suggested the async equivalents in scope at the call
site: an async extension method declared in a namespace that the file does
not import was ignored, even though adding the using directive is enough to
call it.

OverloadFinder has a new IncludeExtensionMethodsFromNotImportedNamespaces
option. When it is set, the candidates also contain the extension methods of
the compilation and of its references that are accessible at the call site,
apply to the receiver, and are not already in scope. They come from an index
of the extension methods by name, built lazily once per compilation, and are
returned after the candidates in scope. IsExtensionMethodFromNotImportedNamespace
indicates whether a candidate requires a using directive.

The analyzer still prefers an async equivalent in scope, and only suggests an
extension method from a namespace that is not imported when there is none. The
namespace is passed to the code fix, which adds the using directive next to the
existing ones, at its sorted position when they are sorted, and keeps the file
header at the top of the file.

The code fix also qualifies an implicit receiver with "this", as an extension
method cannot be called with a simple name: Do() became "await DoAsync()",
which did not compile, and is now "await this.DoAsync()".
…in for the rules using OverloadFinder

The rules using OverloadFinder to find an overload now have an option to include
the extension methods declared in a namespace that is not imported:
<RuleId>.include_extension_methods_from_not_imported_namespaces, false by default.
MA0042 used to always include them; it now requires the option too.

The option exists for MA0001, MA0002, MA0011, MA0032, MA0040, MA0042, MA0045,
MA0074, MA0166, MA0167 and MA0210. When an analyzer reports two rules, the
overloads are searched with the extension methods when the option is set for one
of them, and the diagnostic is only reported when the option is set for the rule
being reported. An overload that does not require a using directive is still
preferred.

OverloadFinder.GetNamespaceToImport indicates whether an overload requires a using
directive, and the analyzers pass the namespace to the code fixes in the
NamespaceToImport diagnostic property. The code fixes add the using directive with
UsingDirectiveHelper, extracted from the MA0042 code fix. ArgumentListHelper binds
the new invocation in a copy of the compilation where the document imports the
namespace, as the overload is not in scope otherwise and the code fix would never
be offered.

MA0054 only searches constructors, and the MA0193 analyzer does not use
OverloadFinder, so they have no option. The rule documentation lists the new
options, as the documentation generator requires every configuration key to be
documented.
@meziantou meziantou changed the title Suggest async extension methods from namespaces that are not imported in MA0042 Add an option to include extension methods from namespaces that are not imported in the rules using OverloadFinder Sep 12, 2026
@meziantou
meziantou merged commit 52a17a4 into main Sep 12, 2026
13 checks passed
@meziantou
meziantou deleted the feature/ma0042-add-missing-using branch September 12, 2026 21:43
This was referenced Sep 12, 2026
This was referenced Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant