Do not merge MA0148/MA0149 comparisons when the operand can change between evaluations - #1466
Merged
Merged
Conversation
The code fix merged adjacent comparisons into a single pattern as soon as their operands were syntactically equivalent. `Next() == 0 || Next() == 2` became `Next() is 0 or 2`, which calls `Next()` once instead of twice and changes the result. The comparisons are now merged only when the operand is a constant, a local, a parameter, `this`, or a non-volatile field of such a value (optionally through a built-in conversion). Other operands, such as method calls, properties and indexers, are converted to separate patterns.
This was referenced Sep 12, 2026
This was referenced Sep 17, 2026
Merged
Open
Open
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The MA0148/MA0149 code fix merges adjacent comparisons into a single pattern when their operands are syntactically equivalent. Syntax equivalence does not guarantee the operand returns the same value or has no side effect:
was fixed as
Next() is 0 or 2. The original callsNext()twice (returnstrue), the fixed code calls it once (returnsfalse). The same issue applied to!=/&&merged intois not (… or …).Fix
The comparisons are merged only when the operand is stable, i.e. evaluating it again has no side effect and returns the same value:
thisOther operands (method calls, properties, indexers, …) are still converted to patterns, but kept as separate comparisons:
Next() is 0 || Next() is 2,Next() is not 0 && Next() is not 2.Notes for reviewers
x.Length == 0 || x.Length == 1is no longer merged intox.Length is 0 or 1, as a property getter can execute any code.UseHasFlagMethodCommon.AreEquivalentOperands.docs/Rules/MA0148.mdandMA0149.mddocument when the fix merges comparisons.Tests
Added tests for method calls (
==/||and!=/&&), a property, and a volatile field (not merged), and a static field (merged). The 4 "do not merge" tests fail without the fix. TheUsePatternMatchingForEqualityComparisonsAnalyzer*tests (37) pass on Roslyn 4.8, 4.14, 5.0, 5.6 and 5.9.