Match configured generic types by metadata name - #1631
Match configured generic types by metadata name#1631Andrew Arnott (AArnott) wants to merge 7 commits into
Conversation
Preserve legacy arity-agnostic matching for configuration files without backticks while allowing exact metadata-name matching in versioned files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates analyzer configuration parsing so configured type matches can distinguish generic vs non-generic types (and match CLR metadata names that include generic arity, e.g. DbSet`1). It introduces per-additional-file compatibility: files containing a backtick opt into exact metadata-name matching, while legacy files without backticks continue matching simple type names across all arities.
Changes:
- Add per-additional-file “legacy vs metadata-name” detection based on whether the file contains a backtick, without materializing
SourceTextto a single string. - Update type matching to use
MetadataNamefor exact matching and expand built-in known types to include generic metadata forms (e.g.,Task/Task\1`). - Add tests and documentation covering generic/non-generic separation and legacy matching behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs | Adds tests validating generic vs non-generic exclusion behavior and legacy matching across arities. |
| test/Microsoft.VisualStudio.Threading.Analyzers.Tests/AdditionalFiles/vs-threading.SyncMethodsToExcludeFromVSTHRD103.mocks.txt | Updates mock exclusions to include a generic type entry using metadata arity. |
| test/Microsoft.VisualStudio.Threading.Analyzers.Tests/AdditionalFiles/vs-threading.SyncMethodsToExcludeFromVSTHRD103.legacy.txt | Adds a legacy-mode exclusions file (no backticks) to validate “match all arities” behavior. |
| src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs | Implements per-file mode detection and metadata-name matching; updates parsing/matching utilities accordingly. |
| docfx/analyzers/configuration.md | Documents the two compatibility modes and provides a generic type example using metadata arity. |
Suppressed comments (1)
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:500
- The member-match branch in
TypeMatchSpec.IsMatchalso hard-codestypeSymbol.MetadataName == this.Type.Name, which again ignoresmatchAnyArityfrom the additional file. Switching tothis.Type.IsMatch(typeSymbol)keeps namespace checking and applies the correct name comparison for legacy vs metadata-name modes.
if (this.IsMember
&& memberSymbol?.Name == this.Member.Name
&& typeSymbol.MetadataName == this.Type.Name
&& typeSymbol.BelongsToNamespace(this.Type.Namespace))
{
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Honor legacy arity matching for all configured type paths and surface unreadable additional files explicitly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:331
ParseAdditionalFileMethodLine(string)now hard-codesmatchAnyArity: false, which changes behavior for callers that parse additional-file lines without the per-file backtick scan (e.g.,CommonFixes.ReadMethodsAsync). This will stop legacy entries like[Namespace.Type]::Methodfrom matching generic arities (sinceQualifiedType.IsMatchnow comparesMetadataName). Consider inferringmatchAnyArityin this wrapper when no backtick is present, so legacy callers keep matching across arities whileReadMethods/ReadTypesAndMembersstill enforce per-file mode.
public static QualifiedMember ParseAdditionalFileMethodLine(string line)
=> ParseAdditionalFileMethodLine(line, matchAnyArity: false);
Apply per-file metadata-name mode detection when code fixes read analyzer configuration, while retaining legacy behavior for individual line parsing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:60
SyncBlockingPropertiesstill includes non-genericTask/ValueTaskentries for theResultmember. SinceQualifiedTypenow defaults to exactMetadataNamematching, these entries can never match (onlyTask1/ValueTask1defineResult). Keeping them is dead configuration and adds confusion/extra matching work.
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task) + "`1"), nameof(Task<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(ValueTask)), nameof(ValueTask<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(ValueTask) + "`1"), nameof(ValueTask<int>.Result)), null),
Reuse the allocation-free SourceText search helper across analyzer and code-fix configuration readers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:332
ParseAdditionalFileMethodLine(string line)now infersmatchAnyArityfrom whether that line contains a backtick. That’s inconsistent with the new per-file compatibility mode (a backtick anywhere in the file opts the whole file into metadata-name matching), and it also changes the historical behavior of this public helper to start matching simple names across all arities when the line has no backtick. Consider keeping this overload as the strict/metadata default and requiring callers to pass the per-filematchAnyArityexplicitly via the new overload.
public static QualifiedMember ParseAdditionalFileMethodLine(string line)
=> ParseAdditionalFileMethodLine(line, matchAnyArity: line.IndexOf('`') < 0);
Reserve exact metadata-name matching for callers that can provide the per-file mode explicitly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:60
SyncBlockingPropertiesincludes entries forTaskandValueTaskwith memberResult. With the updatedQualifiedType.IsMatchusingMetadataNameby default, these entries can no longer matchTask<TResult>.Result/ValueTask<TResult>.Result(and the non-generic types don’t defineResultanyway), so they are now dead and potentially misleading. Consider removing the non-generic entries and keeping only the*1` metadata-name entries.
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task)), nameof(Task<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(Task) + "`1"), nameof(Task<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(ValueTask)), nameof(ValueTask<int>.Result)), null),
new SyncBlockingMethod(new QualifiedMember(new QualifiedType(Namespaces.SystemThreadingTasks, nameof(ValueTask) + "`1"), nameof(ValueTask<int>.Result)), null),
src/Microsoft.VisualStudio.Threading.Analyzers/CommonInterest.cs:355
Contains(SourceText text, char value)is a new public API but doesn’t guard againsttext == null, which will currently surface as aNullReferenceException. Other public helpers in this type (e.g.ReadLinesFromAdditionalFile) throwArgumentNullExceptionfor null inputs; consider doing the same here for consistency and clearer failures.
public static bool Contains(SourceText text, char value)
{
for (int i = 0; i < text.Length; i++)
{
if (text[i] == value)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
VSTHRD103 exclusions could not distinguish a non-generic type from a generic type with the same simple name, and CLR metadata names such as
DbSet``1did not match generic symbols.This change adds per-file compatibility modes for analyzer configuration:
SourceTextas a string.Tests cover exact generic/non-generic separation, legacy matching across arities, and mixed legacy/v2 files in one compilation. The configuration documentation now explains both modes and includes an Entity Framework
DbSetexample.