Skip to content

Fix source generator diagnostics to support #pragma warning disable#124994

Draft
eiriktsarpalis wants to merge 9 commits intomainfrom
fix/sourcegen-suppressions
Draft

Fix source generator diagnostics to support #pragma warning disable#124994
eiriktsarpalis wants to merge 9 commits intomainfrom
fix/sourcegen-suppressions

Conversation

@eiriktsarpalis
Copy link
Member

@eiriktsarpalis eiriktsarpalis commented Feb 27, 2026

Summary

Source generator diagnostics emitted by incremental generators in dotnet/runtime can now be suppressed using #pragma warning disable inline directives.

Fixes #92509

Problem

Incremental source generators that store diagnostic locations in equatable intermediate representations "trim" the Location to avoid holding references to the Compilation object. The standard workaround — Location.Create(filePath, textSpan, linePositionSpan) — creates an ExternalFileLocation (LocationKind.ExternalFile) which bypasses Roslyn's pragma suppression checks. Only SourceLocation (LocationKind.SourceFile) instances, created via Location.Create(SyntaxTree, TextSpan), are checked against the SyntaxTree's pragma directive table.

This is the issue described in dotnet/roslyn#68291.

Solution

Following the technique first applied in eiriktsarpalis/PolyType#401, split each affected generator's RegisterSourceOutput pipeline into two separate pipelines:

  1. Source generation pipeline — Fully incremental. Uses Select to extract just the equatable model (which contains no Location/SyntaxTree references), deduplicates by model equality, only re-fires on structural changes.
  2. Diagnostic pipeline — Reports raw Diagnostic objects that preserve the original SourceLocation (LocationKind.SourceFile) from the syntax tree. This enables Roslyn to check the SyntaxTree's pragma directive table for #pragma warning disable directives.

Parsers now create Diagnostic.Create(descriptor, location, ...) directly instead of wrapping in DiagnosticInfo.Create(...) which trimmed the location. Since ImmutableArray<Diagnostic> does not have value equality, the diagnostic pipeline fires more frequently but the work is trivially cheap (just iterating and reporting).

RegexGenerator specifics

The RegexGenerator's pipeline uses a heterogeneous ImmutableArray<object> and mixes diagnostic data with source generation data. To fully segregate diagnostics from the incremental model:

  • DiagnosticData and EquatableLocation types have been removed.
  • DiagnosticLocation has been removed from RegexPatternAndSyntax and RegexMethod records — the model is now completely free of Location/SyntaxTree references.
  • The pipeline tuple type is (object? Model, Location? DiagnosticLocation, ImmutableArray<Diagnostic> Diagnostics) where:
    • Model has value equality and drives incremental caching for source generation.
    • DiagnosticLocation carries the raw SourceLocation for creating diagnostics in later pipeline steps (regex parse failures, limited support detection).
    • Diagnostics accumulates all diagnostics across pipeline steps.
  • The source pipeline extracts just Model via Select, collects, and applies ObjectImmutableArraySequenceEqualityComparer.
  • The diagnostic pipeline collects the full tuples and reports Diagnostics.

Affected Generators

Generator Trimming Method Fix
JsonSourceGenerator DiagnosticInfo.GetTrimmedLocation() Parser returns List<Diagnostic>; split pipeline
LoggerMessageGenerator DiagnosticInfo.GetTrimmedLocation() Parser returns List<Diagnostic>; split pipeline with dedup
ConfigurationBindingGenerator DiagnosticInfo.GetTrimmedLocation() Parser returns List<Diagnostic>; split pipeline
RegexGenerator GetComparableLocation() Diagnostics fully segregated from model; split pipeline

Not Affected

  • Interop generators (LibraryImport, ComInterface, JSImport, JSExport): Store raw Location (no trimming), already support pragma suppression.
  • OptionsValidatorGenerator: Reports diagnostics directly via SourceProductionContext.ReportDiagnostic with Compilation access.

Changes

File Change
JsonSourceGenerator.Parser.cs List<DiagnosticInfo>List<Diagnostic>
JsonSourceGenerator.Roslyn4.0.cs Split into source + diagnostic pipelines
JsonSourceGenerator.Roslyn3.11.cs Updated to use Diagnostic directly
LoggerMessageGenerator.Parser.cs List<DiagnosticInfo>List<Diagnostic>
LoggerMessageGenerator.Roslyn4.0.cs Split into source + diagnostic pipelines with dedup
ConfigurationBindingGenerator.Parser.cs List<DiagnosticInfo>List<Diagnostic>
ConfigurationBindingGenerator.cs Split into source + diagnostic pipelines
RegexGenerator.Parser.cs Removed GetComparableLocation; removed DiagnosticLocation from records; returns (Model, Location, Diagnostics) tuple
RegexGenerator.cs Removed DiagnosticData/EquatableLocation; pipeline fully segregates diagnostics from incremental model
JsonSourceGeneratorIncrementalTests.cs Updated model encapsulation test (diagnostics now reference SyntaxTree)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables Roslyn pragma-based suppression (#pragma warning disable) for diagnostics emitted by several incremental source generators by separating source emission from diagnostic emission and recreating diagnostics with SourceFile locations recovered from the current Compilation.

Changes:

  • Split generator pipelines into (1) fully-incremental source generation and (2) diagnostics combined with CompilationProvider for pragma-suppressible locations.
  • Add DiagnosticInfo.CreateDiagnostic(Compilation) (and Regex-specific equivalent) to rebuild Location as LocationKind.SourceFile.
  • Update Json source generator incremental test commentary to reflect the new diagnostics pipeline behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs Adds a diagnostics-only pipeline and recreates diagnostics with SourceFile locations.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs Adjusts incremental-model encapsulation test commentary given diagnostics now reference SyntaxTree.
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs Splits Json SG into separate source + diagnostics pipelines; diagnostics now created with compilation context.
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs Splits LoggerMessage SG pipelines and preserves diagnostic deduping while making locations pragma-suppressible.
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs Splits binder SG pipelines and reports diagnostics using compilation-backed SourceFile locations.
src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs Adds CreateDiagnostic(Compilation) to recreate pragma-suppressible SourceFile locations from trimmed locations.

Comment on lines +51 to +60
string filePath = location.GetLineSpan().Path;
TextSpan textSpan = location.SourceSpan;
foreach (SyntaxTree tree in compilation.SyntaxTrees)
{
if (tree.FilePath == filePath)
{
location = Location.Create(tree, textSpan);
break;
}
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateDiagnostic(Compilation) does a full scan of compilation.SyntaxTrees for every diagnostic instance. Since this is invoked in loops (and some generators can produce many diagnostics), consider building a filePath→SyntaxTree lookup once per source-output callback and reusing it when creating diagnostics.

Copilot uses AI. Check for mistakes.
Split each affected generator's RegisterSourceOutput pipeline into two
separate pipelines:

1. Source generation pipeline - fully incremental, uses Select to extract
   just the equatable model. Only re-fires on structural changes.

2. Diagnostic pipeline - combines with CompilationProvider to recover the
   SyntaxTree from the Compilation at emission time. Uses
   Location.Create(SyntaxTree, TextSpan) to produce SourceLocation
   instances that support pragma suppression checks.

Affected generators:
- System.Text.Json (JsonSourceGenerator)
- Microsoft.Extensions.Logging (LoggerMessageGenerator)
- Microsoft.Extensions.Configuration.Binder (ConfigurationBindingGenerator)
- System.Text.RegularExpressions (RegexGenerator)

The shared DiagnosticInfo type gains a CreateDiagnostic(Compilation) overload
that recovers the SyntaxTree from the trimmed ExternalFileLocation's file
path, converting it back to a SourceLocation. The RegexGenerator's private
DiagnosticData type gets an analogous ToDiagnostic(Compilation) overload.

Fixes #92509

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eiriktsarpalis eiriktsarpalis force-pushed the fix/sourcegen-suppressions branch from 50000b3 to e95c4ad Compare February 28, 2026 09:42
Copilot AI review requested due to automatic review settings February 28, 2026 09:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs:66

  • The catch block rethrows with throw ex;, which resets the exception stack trace. Use throw; (or remove the catch entirely) to preserve the original call stack for diagnosing generator failures.
                    catch (Exception ex)
                    {
                        throw ex;
                    }

…tors

Verifies that diagnostics from all 4 affected source generators (Regex,
JSON, Logger, ConfigBinder) have LocationKind.SourceFile, which is the
prerequisite for #pragma warning disable to work. Before this fix,
diagnostics had LocationKind.ExternalFile which bypasses Roslyn's pragma
suppression checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds RegexGeneratorIncrementalTests with 4 tests following the patterns
established by JsonSourceGeneratorIncrementalTests and ConfigBinder's
GeneratorTests.Incremental.cs:

- SameInput_DoesNotRegenerate: verifies caching on identical compilations
- EquivalentSources_Regenerates: documents that semantically equivalent
  sources trigger regeneration (pre-existing limitation due to Dictionary
  in model lacking value equality)
- DifferentSources_Regenerates: verifies model changes trigger output
- SourceGenModelDoesNotEncapsulateSymbolsOrCompilationData: walks the
  object graph to ensure no Compilation/ISymbol references leak

Also adds SourceGenerationTrackingName constant and WithTrackingName()
to the source pipeline to enable step tracking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 28, 2026 14:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs:66

  • The catch (Exception ex) { throw ex; } pattern resets the original stack trace. If the intent is just to propagate, use throw; (or remove the try/catch entirely) so failures in the generator preserve useful call stacks.
                    catch (Exception ex)
                    {
                        throw ex;
                    }

Replace inline lambda callbacks in the diagnostic pipelines with named
EmitDiagnostics static methods, complementing the existing EmitSource
methods in all 4 generators (JSON, Logger, ConfigBinder, Regex).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment on lines +97 to +100
compilation = compilation.ReplaceSyntaxTree(
compilation.SyntaxTrees.First(),
CSharpSyntaxTree.ParseText(SourceText.From(source2, Encoding.UTF8), s_parseOptions));
driver = driver.RunGenerators(compilation);
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encoding.UTF8 is used here but the file doesn’t import System.Text (and there doesn’t appear to be a global using for it in this test project). This will fail to compile unless you add using System.Text; or fully-qualify System.Text.Encoding.UTF8 at the call sites.

Copilot uses AI. Check for mistakes.
Comment on lines +812 to +819
// this list is exposed via parser.Diagnostics (as ImmutableEquatableArray<DiagnosticInfo>) and reported in Execute.
Diagnostics.Add(DiagnosticInfo.Create(desc, location, messageArgs));
// this list is exposed via parser.Diagnostics and reported in the diagnostic pipeline.
Diagnostics.Add(Diagnostic.Create(desc, location, messageArgs));
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diag currently creates two separate Diagnostic instances with identical content (one for _reportDiagnostic and one to store in Diagnostics). Consider creating the Diagnostic once and reusing it to avoid duplicate allocations and ensure both paths observe the exact same instance.

Copilot uses AI. Check for mistakes.
Create named IncrementalValueProvider variables for the equatable model
projections in all 4 generators, with detailed comments explaining how
Roslyn's Select operator uses model equality to guard source production.

For the Regex generator, also extract the source emission lambda into a
named EmitSource method, complementing EmitDiagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eiriktsarpalis eiriktsarpalis force-pushed the fix/sourcegen-suppressions branch from 3323beb to a4e500e Compare March 2, 2026 17:10
Create named IncrementalValueProvider variables for the diagnostic
projections in all 4 generators, with comments explaining that
ImmutableArray<Diagnostic> uses reference equality in the incremental
pipeline — the callback fires on every compilation change by design.

This also simplifies the EmitDiagnostics signatures to accept just
the projected diagnostics rather than the full model+diagnostics tuple.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 2, 2026 17:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs:66

  • Avoid throw ex; here; it resets the original stack trace and makes failures harder to diagnose. Use throw; to preserve the stack trace, or remove the try/catch entirely if it's only rethrowing.
                    catch (Exception ex)
                    {
                        throw ex;
                    }

Comment on lines 812 to 820
private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs)
{
// Report immediately if callback is provided (preserves pragma suppression with original locations)
_reportDiagnostic?.Invoke(Diagnostic.Create(desc, location, messageArgs));

// Also collect for scenarios that need the diagnostics list; in Roslyn 4.0+ incremental generators,
// this list is exposed via parser.Diagnostics (as ImmutableEquatableArray<DiagnosticInfo>) and reported in Execute.
Diagnostics.Add(DiagnosticInfo.Create(desc, location, messageArgs));
// this list is exposed via parser.Diagnostics and reported in the diagnostic pipeline.
Diagnostics.Add(Diagnostic.Create(desc, location, messageArgs));
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnostic.Create(...) is called twice here (once for immediate reporting and once for storing), which does redundant work and may lead to subtle inconsistencies if creation behavior ever changes. Create the Diagnostic once, then both invoke the callback and add the same instance to Diagnostics.

Copilot uses AI. Check for mistakes.
Replace the untyped object?-based pipeline model with a deeply equatable
RegexSourceGenerationResult record containing ImmutableEquatableArray fields.
This makes the Regex generator fully incremental — equivalent sources now
produce Cached outputs instead of re-emitting code on every compilation.

Key changes:
- Add ImmutableEquatableArray.cs and HashHelpers.cs to the Regex gen csproj
- Define RegexMethodEntry, HelperMethod, and RegexSourceGenerationResult records
  with deep value equality via ImmutableEquatableArray fields
- Pre-compute XML expression description and capture metadata during pipeline
  so the equatable model contains no RegexTree/AnalysisResults references
- Collect + aggregate step deduplicates helpers across all regex methods
- Remove ObjectImmutableArraySequenceEqualityComparer (no longer needed)
- Remove mutable IsDuplicate/GeneratedName from RegexMethod (computed at emit time)
- Update EmitRegexPartialMethod/EmitRegexLimitedBoilerplate/EmitRegexDerivedImplementation
  to accept typed RegexMethodEntry + generatedName instead of RegexMethod
- Update incremental tests: EquivalentSources now expects Cached

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 3, 2026 09:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment on lines +4 to +15
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions.Generator;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Xunit;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encoding.UTF8 is used in this file (e.g., when parsing/replacing syntax trees), but System.Text isn’t imported, so this won’t compile. Add using System.Text; or fully-qualify Encoding usages.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +119
? cnsm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexMethod.Tree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collections.DictionaryEntry is referenced here, but there’s no using Collections = System.Collections; alias (and Collections is not otherwise defined), so this will not compile. Use System.Collections.DictionaryEntry (or add the alias) for these casts.

Suggested change
? cnsm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexMethod.Tree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()
? cnsm.Cast<System.Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexMethod.Tree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<System.Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()

Copilot uses AI. Check for mistakes.
Move all regex parsing, code generation, and data extraction into the
ForAttributeWithMetadataName transform. The pipeline returns the final
typed (RegexMethodEntry?, ImmutableEquatableArray<HelperMethod>,
ImmutableArray<Diagnostic>) tuple directly — no intermediate object?
boxing and no DiagnosticLocation sidecar.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 3, 2026 18:34
@eiriktsarpalis eiriktsarpalis force-pushed the fix/sourcegen-suppressions branch from 584568b to 8e7da78 Compare March 3, 2026 18:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment on lines +290 to +293
? cnsm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexTree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collections.DictionaryEntry is referenced here, but this file doesn't define a Collections alias (e.g., using Collections = System.Collections;). As-is, this won't compile. Use System.Collections.DictionaryEntry (or add the missing alias / using System.Collections; and use DictionaryEntry) for these Cast<...>() calls.

Suggested change
? cnsm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexTree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()
? cnsm.Cast<System.Collections.DictionaryEntry>().Select(de => (Key: (int)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key).ToImmutableEquatableArray()
: null;
ImmutableEquatableArray<(string Key, int Value)>? captureNameToNumberMapping = regexTree.CaptureNameToNumberMapping is { } cntnm
? cntnm.Cast<System.Collections.DictionaryEntry>().Select(de => (Key: (string)de.Key, Value: (int)de.Value!)).OrderBy(p => p.Key, StringComparer.Ordinal).ToImmutableEquatableArray()

Copilot uses AI. Check for mistakes.
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file adds using System.Threading; but doesn't appear to use anything from that namespace. If warnings are treated as errors, this will break the build with CS8019. Please remove the unused using (or use the imported type if intended).

Suggested change
using System.Threading;

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +132
var reportedDiagnostics = new HashSet<(string Id, TextSpan? Span, string? FilePath)>();
foreach (ImmutableArray<Diagnostic> diagnosticBatch in items)
{
foreach (Diagnostic diagnostic in diagnosticBatch)
{
if (reportedDiagnostics.Add((diagnostic.Id, diagnostic.Location?.SourceSpan, diagnostic.Location?.SourceTree?.FilePath)))
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnostic deduplication key only considers (Id, SourceSpan, FilePath). The parser can legitimately emit multiple diagnostics with the same Id at the same location but different message args (e.g., one per missing template name), and this HashSet will incorrectly drop all but the first. Include something that distinguishes message/args (e.g., diagnostic.GetMessage() or diagnostic.ToString() / Descriptor + message) in the dedup key, or use a more faithful comparer.

Suggested change
var reportedDiagnostics = new HashSet<(string Id, TextSpan? Span, string? FilePath)>();
foreach (ImmutableArray<Diagnostic> diagnosticBatch in items)
{
foreach (Diagnostic diagnostic in diagnosticBatch)
{
if (reportedDiagnostics.Add((diagnostic.Id, diagnostic.Location?.SourceSpan, diagnostic.Location?.SourceTree?.FilePath)))
var reportedDiagnostics = new HashSet<(string Id, TextSpan? Span, string? FilePath, string Message)>();
foreach (ImmutableArray<Diagnostic> diagnosticBatch in items)
{
foreach (Diagnostic diagnostic in diagnosticBatch)
{
string message = diagnostic.GetMessage();
if (reportedDiagnostics.Add((diagnostic.Id, diagnostic.Location?.SourceSpan, diagnostic.Location?.SourceTree?.FilePath, message)))

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.


You can also share your feedback on Copilot code review. Take the survey.

using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using System.Collections.Immutable; is not referenced in this test file. Consider removing the unused using to avoid IDE0005 / style warnings during build.

Suggested change
using System.Linq;

Copilot uses AI. Check for mistakes.
Refactor GetRegexMethodDataOrFailureDiagnostic to return RegexPatternAndSyntax?
and accept an ImmutableArray<Diagnostic>.Builder accumulator. Refactor
ParseAndGenerateRegex to return RegexMethodEntry? and accept diagnostic and
helper accumulators. This eliminates the verbose 3-element tuple returns
throughout both methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eiriktsarpalis eiriktsarpalis force-pushed the fix/sourcegen-suppressions branch from ec6fd58 to 8547065 Compare March 4, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Address diagnostic issues in runtime incremental source generators

2 participants