Skip to content

Fix SYSLIB1045 code fixer generating duplicate MyRegex names across partial class declarations#124698

Open
danmoseley wants to merge 5 commits intodotnet:mainfrom
danmoseley:fix/77409-codefixer-duplicate-namesc
Open

Fix SYSLIB1045 code fixer generating duplicate MyRegex names across partial class declarations#124698
danmoseley wants to merge 5 commits intodotnet:mainfrom
danmoseley:fix/77409-codefixer-duplicate-namesc

Conversation

@danmoseley
Copy link
Member

@danmoseley danmoseley commented Feb 21, 2026

Fixes #77409

Problem

When the SYSLIB1045 BatchFixer applies multiple code fixes concurrently to separate partial class declarations, each fix independently checks existing members via the semantic model. Since all fixers see the original compilation (before any fix is applied), they all conclude MyRegex is available and generate duplicate property names, causing CS0756.

Fix

In UpgradeToGeneratedRegexCodeFixer.CreateGeneratedRegexProperty, after finding the first available name via the semantic model, the new CountPrecedingRegexCallSites helper scans all DeclaringSyntaxReferences of the containing type for Regex constructor/static-method call sites that would also generate new property names. These are sorted deterministically (by file path, then span position). The current node's index in that sorted list determines how many names to skip, giving each concurrent fixer a unique name.

Test

Added CodeFixerGeneratesUniqueNamesAcrossPartialClassDeclarations — two new Regex(...) calls in separate partial class declarations of the same type, verifying MyRegex and MyRegex1 are generated. Verified the test fails without the fix (both declarations produce MyRegex).

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

This PR fixes issue #77409 where the SYSLIB1045 code fixer (which converts Regex constructor calls to GeneratedRegex) generates duplicate property names when applied to multiple partial class declarations via batch fixing. The root cause is that concurrent batch fixers all see the original compilation state before any fixes are applied, causing them to independently conclude that the same name (e.g., "MyRegex") is available.

The PR also includes two unrelated changes: a performance optimization using SetSearchValues for the regex interpreter, and a regression test for character class encoding validation.

Changes:

  • Adds CountPrecedingRegexCallSites helper to deterministically order all Regex call sites across partial declarations by file path and position, allowing each concurrent fixer to offset its name generation
  • Implements SetSearchValues optimization in the regex interpreter for vectorized character class matching
  • Adds validation for character class encoding before calling GetSetChars to prevent IndexOutOfRangeException on malformed input

Reviewed changes

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

Show a summary per file
File Description
src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs Adds CountPrecedingRegexCallSites helper and name offset logic to prevent duplicate names in batch fixing
src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs Adds test validating unique name generation across partial class declarations
src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreterCode.cs Adds SetSearchValues precomputation and character class encoding validation (unrelated optimization)
src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs Uses SetSearchValues for vectorized Set opcode matching (unrelated optimization)
src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs Adds regression test for Multi literal resembling character class encoding (unrelated)

@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch 2 times, most recently from 259abc1 to 3e46fc1 Compare February 21, 2026 05:04
Copilot AI review requested due to automatic review settings February 21, 2026 05:04
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 2 out of 2 changed files in this pull request and generated no new comments.

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 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings February 21, 2026 05: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 2 out of 2 changed files in this pull request and generated 2 comments.

@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch from ec91456 to ca22fda Compare February 21, 2026 05:24
Copilot AI review requested due to automatic review settings February 21, 2026 05:29
@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch from ca22fda to ed09d48 Compare February 21, 2026 05:29
@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch from ed09d48 to b83f949 Compare February 21, 2026 05: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 3 out of 3 changed files in this pull request and generated 2 comments.

…artial class declarations

When the BatchFixer applies multiple fixes concurrently, each fix sees the
original compilation and independently picks the same first-available name.
This causes CS0756 when two partial declarations of the same class both
contain Regex calls that need fixing.

The fix scans all partial declarations of the containing type for Regex call
sites that would generate new property names, orders them deterministically
(by file path, then position), and offsets the generated name by the current
node's index in that list.

Fixes dotnet#77409

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 3 out of 3 changed files in this pull request and generated 1 comment.

Extract fixable method names into shared FixableMethodNames set on the
analyzer, referenced by both the analyzer and the fixer. Add test
verifying Regex.Escape calls don't inflate the generated name offset.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch from 8af55cd to c9e46b8 Compare February 21, 2026 06:19
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 3 out of 3 changed files in this pull request and generated 1 comment.

@danmoseley
Copy link
Member Author

ready for review

@danmoseley
Copy link
Member Author

Dan to try more manual validation locally.

… bug

Add 5 edge case tests for the CountPrecedingRegexCallSites logic:
- Nested type calls excluded from outer type naming
- Non-constant patterns ignored in counting
- Partial struct support
- Mixed constructor and static method calls across partials
- Nested partial types with independent naming

Fix bug in CountPrecedingRegexCallSites where nested types inside
an outer class would have all their calls skipped because the
ancestor check matched the outer TypeDeclarationSyntax. Use
TakeWhile to only check ancestors within the declaring syntax node.

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

Added edge case tests and found/fixed a bug in CountPrecedingRegexCallSites:

Bug fix: The nested-type exclusion check descendant.Ancestors().Any(a => a is TypeDeclarationSyntax && a != declSyntax) incorrectly skipped calls when the type being fixed is itself nested inside another type. For example, if Inner is nested inside Outer, calls inside Inner's partial declarations would be skipped because Outer is an ancestor TypeDeclarationSyntax != declSyntax. This caused both calls in Inner to get the same name MyRegex. Fixed by using .TakeWhile(a => a != declSyntax) to only check ancestors within the declaring syntax node.

Tests added:

  • NestedTypeCallsDoNotAffectOuterTypeNaming — inner class Regex calls excluded from outer type naming
  • NonConstantPatternDoesNotAffectNamingnew Regex(variable) not counted
  • PartialStructGeneratesUniqueNames — partial structs work, not just classes
  • MixedConstructorAndStaticMethodCallsAcrossPartialsnew Regex() + Regex.IsMatch() get unique names
  • NestedPartialTypeDoesNotInterfereWithOuterPartial — nested partial type has independent naming (exercises the bug fix)

@danmoseley
Copy link
Member Author

@stephentoub I can't remember what I volunteered to try out locally. I remember looking more at nested types, added various tests above and these found one bug. Anything else here?

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 3 out of 3 changed files in this pull request and generated 3 comments.

@stephentoub
Copy link
Member

I can't remember what I volunteered to try out locally.

Making sure that lots of regexes that would all conflict in names, spread out in various ways throughout the project, all auto-fix automatically / correctly in a batch.

- Add SyntaxTree index as tiebreaker in sort key to prevent
  indeterminate ordering when FilePath is null/empty across
  different in-memory SyntaxTrees with same SpanStart.
- Cache SemanticModel per SyntaxTree to avoid redundant calls
  when multiple partial declarations share the same tree.
- Fix doc comment: 'method' to 'property'.

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

Addressed review feedback in 0fa6a45:

  • Tree-index tiebreaker: Sort key is now (FilePath, TreeIndex, Position) instead of (FilePath, Position), preventing indeterminate ordering when SyntaxTree.FilePath is null/empty (e.g., in-memory documents).
  • SemanticModel cache: Avoids redundant GetSemanticModel calls when multiple partial declarations share the same SyntaxTree.
  • Dismissed the ImmutableHashSet suggestion — for 7 strings in an internal static readonly field that's never mutated, ImmutableHashSet is strictly slower (O(log n) tree lookups vs O(1) hash) with no practical safety benefit.

@danmoseley
Copy link
Member Author

danmoseley commented Mar 18, 2026

Did an integration test with a test project containing 28 fixable Regex calls spread across 12 files and 7 types (partial classes, partial structs, nested types, static methods, mixed fixable/non-fixable). Ran dotnet format analyzers --diagnostics SYSLIB1045 using the locally-built analyzer and verified all calls get unique names, unfixable calls are correctly skipped, and the result compiles and runs (diff).

Initially noticed a missing blank line between generated properties -- fixed that by adding ElasticCarriageReturnLineFeed leading trivia before AddMembers. Properties may appear in a different order than their corresponding call sites -- based on inspection of Roslyn's BatchFixAllProvider.cs, this appears to be its merge behavior rather than something the fixer controls.

@danmoseley
Copy link
Member Author

Need to check blank lines/ordering aren't the fixer.

Copilot AI review requested due to automatic review settings March 18, 2026 18:25
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 3 out of 3 changed files in this pull request and generated 2 comments.

The fixer was not including leading trivia when inserting new members
via AddMembers, causing generated properties to lack blank line
separation from preceding members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danmoseley danmoseley force-pushed the fix/77409-codefixer-duplicate-namesc branch from b96b339 to 0da74f1 Compare March 18, 2026 18:40
@danmoseley danmoseley requested a review from Copilot March 18, 2026 18:47
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 3 out of 3 changed files in this pull request and generated 2 comments.

@danmoseley
Copy link
Member Author

Feedback addressed.

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.

SYSLIB1045 Use GeneratedRegex auto-correct causes CS0756 multiple defining declarations

3 participants