Skip to content

Warn for redundant pattern in not ... or <redundant> pattern#75581

Merged
jcouv merged 63 commits intodotnet:mainfrom
jcouv:not-or
Oct 10, 2025
Merged

Warn for redundant pattern in not ... or <redundant> pattern#75581
jcouv merged 63 commits intodotnet:mainfrom
jcouv:not-or

Conversation

@jcouv
Copy link
Member

@jcouv jcouv commented Oct 22, 2024

This PR is going to wait for an early preview of .NET 10.0.200 since it adds a new warning

Addresses #75506

Filled language issue/discussion for asymmetries in types flowing in patterns: dotnet/csharplang#8888

Validated performance with Jared and Cyrus by replaying a build of roslyn and looking at the trace. With blanked GroupPats and default FoldPats, CheckOrAndAndReachability shows as 0.1 for Inc%. We have an option to skip the analysis entirely based on global nowarn settings, but feel we don't need to implement that yet.

Tested with VS test insertion
Tested building the runtime repo (2025-10-09)

@jcouv jcouv self-assigned this Oct 22, 2024
@ghost ghost added the untriaged Issues and PRs which have not yet been triaged by a lead label Oct 22, 2024
if (i > 0
&& oneTrue is False
&& this is OrSequence { RemainingTests: [Not { Negated: var negated }, ..] }
&& isRecognizedPartOfNegated(test, negated))
Copy link
Member Author

@jcouv jcouv Oct 22, 2024

Choose a reason for hiding this comment

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

📝 Surprisingly, removing this isRecognizedPartOfNegated check doesn't seem to impact any tests or bootstrapping. #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

It feels like if we can't come up with any tests which depend on this filtering (e.g. scenarios where an unwanted warning is reported in absence of this check), then it bears further investigation. We should try to understand this area well enough to either identify such a scenario and test it, or to be confident that this check is not needed and remove it.

Copy link
Member Author

@jcouv jcouv Nov 7, 2024

Choose a reason for hiding this comment

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

I don't think it's possible. We would need a test that appears inside a condition inside Tests.Not but is not a requirement for that condition to succeed.
For example, Or(Not(Not(Test)), RedundantTest or Or(Not(Or(Test, OtherTest)), RedundantTest).
But neither of these structures are possible, as not not Test gets represented as Test and not (Test or OtherTest) gets represented as And(Not(Test), Not(OtherTest)).
I'll add more tests to illustrate.

So all the tests I've added to show the impact of this filtering logic only show that it limits useful warnings. I was not able to show the impact on false warnings.
Still, I feel safer keeping this filtering logic, in case I'm missing something or the above situation changed.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we could, without changing the behavior in this PR, add an assertion if the preceding logic holds, the isRecognizedPartOfNegated must also hold. So, if we ever find a situation where the implication is not met, we will at least know about it.

Copy link
Member

Choose a reason for hiding this comment

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

Followed up offline. It seems this predicate does have effects, in terms of which cases produce warnings or not. Our concern was about does this predicate actually prevent certain subjectively annoying warnings. This can't be enforced with an assertion.

@jcouv jcouv marked this pull request as ready for review October 23, 2024 20:40
@jcouv jcouv requested review from a team as code owners October 23, 2024 20:40
the second pattern is redundant and likely results from misunderstanding the precedence order
of `not` and `or` pattern combinators.
The compiler will provide a warning in such cases:
```
Copy link
Member

@jjonescz jjonescz Oct 24, 2024

Choose a reason for hiding this comment

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

Suggested change
```
```c#
```` #Resolved

}
```

## Warn for redundant pattern in `not ... or <redundant>` pattern
Copy link
Member

@jjonescz jjonescz Oct 24, 2024

Choose a reason for hiding this comment

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

This warning affects existing code and can break builds when users update VS / SDK. I don't see a reason why it shouldn't be a warning-wave warning. #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

It is thought that code which compiler reports this warning on, is likely to be so broken and against developer expectations, that we want to break people's builds on upgrade to make them think about what to do (parenthesize or delete the redundant subpattern).

Copy link
Member Author

Choose a reason for hiding this comment

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

From discussion in LDM, we're okay with taking this break (ie. report a regular warning). Users seem to be making this mistake frequently and the impact can be meaningful, so we're not helping users by letting them keep this redundant pattern.
But we do limit the warning to cases that are definitely redundant (no false alarm) to minimize the break, even if we don't catch all the cases of redundancy.

Copy link
Member

Choose a reason for hiding this comment

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

Aren't most (all?) warnings we add to the compiler are on likely-broken code or redundant things? Other warnings are added to analyzers. So I'm not sure I see the line between "break the build" and "warning wave" warnings. I would expect users that want useful warnings and don't mind broken builds would set their warning level accordingly. Anyway, it's fine, especially if LDM discussed this. Thanks.

_ = o is not null or 42; // warning: pattern "42" is redundant
_ = o is int or string; // warning: pattern "string" is redundant
```
It is likely that the user meant `is not (null or 42)` or `is not (int or string)` instead.
Copy link
Member

@jaredpar jaredpar Oct 24, 2024

Choose a reason for hiding this comment

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

Does this catch all such patterns or just most of them? As we discussed I think even catching most of them, particularly the most common case that motivated this issue, would be a huge win. But I want to make sure that the documentation is clear on this point. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

I handled specific patterns, to be safer. But the PR handles all known patterns that were found in github searches.
I could refine to "The compiler provides a warning in common cases of this mistake"

<value>Element type of an iterator may not be a ref struct or a type parameter allowing ref structs</value>
</data>
<data name="WRN_RedundantPattern" xml:space="preserve">
<value>The pattern is redundant</value>
Copy link
Member

@333fred 333fred Oct 24, 2024

Choose a reason for hiding this comment

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

Can we include something about "did you mean to parenthesize this"? #Resolved

@RikkiGibson RikkiGibson self-assigned this Oct 25, 2024
@jcouv
Copy link
Member Author

jcouv commented Nov 5, 2024

@dotnet/roslyn-compiler for review. Thanks

@jcouv
Copy link
Member Author

jcouv commented Nov 6, 2024

@dotnet/roslyn-compiler for review. I'm happy to offer a walkthrough for more context on DAG construction. Let me know

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

I don't have any fundamental problem with the approach, but I am still interested in taking a little more time on my own to investigate. In particular, the question about 'isRecognizedPartOfNegated' is concerning to me. Apologies for the long turnaround on my review here since we did speak offline about it a few weeks ago.

}
```

## Warn for redundant pattern in `not ... or <redundant>` pattern
Copy link
Member

Choose a reason for hiding this comment

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

It is thought that code which compiler reports this warning on, is likely to be so broken and against developer expectations, that we want to break people's builds on upgrade to make them think about what to do (parenthesize or delete the redundant subpattern).

if (i > 0
&& oneTrue is False
&& this is OrSequence { RemainingTests: [Not { Negated: var negated }, ..] }
&& isRecognizedPartOfNegated(test, negated))
Copy link
Member

Choose a reason for hiding this comment

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

It feels like if we can't come up with any tests which depend on this filtering (e.g. scenarios where an unwanted warning is reported in absence of this check), then it bears further investigation. We should try to understand this area well enough to either identify such a scenario and test it, or to be confident that this check is not needed and remove it.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Oct 1, 2025

Done with review pass (commit 57) #Closed

private static bool IsValidType([NotNullWhen(true)] ITypeSymbol? type)
{
return type is not null or IErrorTypeSymbol && type.Name != "var";
return type is not null && type.Name != "var";
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 2, 2025

Choose a reason for hiding this comment

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

Suggested change
return type is not null && type.Name != "var";
return type is not null and not IErrorTypeSymbol && type.Name != "var";
``` #Pending

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (commit 60)

@@ -154,6 +154,6 @@ private static TextSpan GetSpan(

private static bool IsValidType([NotNullWhen(true)] ITypeSymbol? type)
{
return type is not (null or IErrorTypeSymbol) && type.Name != "var";
return type is not null and not IErrorTypeSymbol && type.Name != "var";
Copy link
Member

@RikkiGibson RikkiGibson Oct 2, 2025

Choose a reason for hiding this comment

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

I am confused, was (null or IErrorTypeSymbol) identified as being a redundant pattern? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

No, only not null or IErrorTypeSymbol was. Both not (null or IErrorTypeSymbol) and not null and not IErrorTypeSymbol are fine.

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

LGTM (incremental review) aside from the one question

@jcouv
Copy link
Member Author

jcouv commented Oct 9, 2025

Completed validation with VS test insertion and building the runtime repo. I'll go ahead and resolve the conflicts and merge.

@jcouv jcouv marked this pull request as ready for review October 9, 2025 23:31
@jcouv jcouv enabled auto-merge (squash) October 9, 2025 23:34
@jcouv jcouv merged commit 579f106 into dotnet:main Oct 10, 2025
27 of 28 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Oct 10, 2025
@jcouv jcouv deleted the not-or branch October 10, 2025 12:25
@davidwengier davidwengier modified the milestones: Next, 18.3 Jan 6, 2026
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.

8 participants