-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn for redundant pattern in not ... or <redundant>
pattern
#75581
base: main
Are you sure you want to change the base?
Conversation
if (i > 0 | ||
&& oneTrue is False | ||
&& this is OrSequence { RemainingTests: [Not { Negated: var negated }, ..] } | ||
&& isRecognizedPartOfNegated(test, negated)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
59d9dd7
to
c4a5bdf
Compare
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: | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
``` | |
```c# | |
```` #Resolved |
@@ -87,3 +87,18 @@ class C | |||
} | |||
} | |||
``` | |||
|
|||
## Warn for redundant pattern in `not ... or <redundant>` pattern |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"
@@ -8017,4 +8017,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
<data name="ERR_IteratorRefLikeElementType" xml:space="preserve"> | |||
<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> |
There was a problem hiding this comment.
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
@dotnet/roslyn-compiler for review. Thanks |
@dotnet/roslyn-compiler for review. I'm happy to offer a walkthrough for more context on DAG construction. Let me know |
There was a problem hiding this 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.
@@ -87,3 +87,18 @@ class C | |||
} | |||
} | |||
``` | |||
|
|||
## Warn for redundant pattern in `not ... or <redundant>` pattern |
There was a problem hiding this comment.
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).
// so the B test could only be true when the `not A` test is true, | ||
// then the user probably made a mistake having the `or B` condition | ||
// Also handles `not "literal" or ...` | ||
if (negated is AndSequence { RemainingTests: [One { Test: BoundDagNonNullTest nonNullTest }, One { Test: BoundDagTypeTest typeTest }, ..] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels unfortunate that we do a "normalization" of the tests at this phase, as it feels like we are already having to work backwards to infer what the user pattern actually looked like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's been the biggest challenge for this PR: CheckConsistentDecision
operates on lowered nodes (BoundDagTest
) and is invoked during the incremental construction of the DAG. There may be a way to implement a similar method to operate on BoundPattern
from initial binding instead. It could be simplified somewhat (we don't need to return 4 flags, just 1)
if (i > 0 | ||
&& oneTrue is False | ||
&& this is OrSequence { RemainingTests: [Not { Negated: var negated }, ..] } | ||
&& isRecognizedPartOfNegated(test, negated)) |
There was a problem hiding this comment.
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.
trueBuilder.Add(oneTrue); | ||
falseBuilder.Add(oneFalse); | ||
|
||
if (i > 0 | ||
&& oneTrue is False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// so the B test could only be true when the `not A` test is true, | ||
// then the user probably made a mistake having the `or B` condition | ||
// Also handles `not "literal" or ...` | ||
if (negated is AndSequence { RemainingTests: [One { Test: BoundDagNonNullTest nonNullTest }, One { Test: BoundDagTypeTest typeTest }, ..] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From discussion with @AlekseyTs, going back to drawing board to explore some alternative design. Moving back to draft |
Addresses #75506
Open questions for LDM: