Skip to content

Commit

Permalink
Report not ... or <redundant> patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Oct 22, 2024
1 parent cb0f932 commit 59d9dd7
Show file tree
Hide file tree
Showing 21 changed files with 894 additions and 12 deletions.
84 changes: 74 additions & 10 deletions src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ private void SplitCase(
out StateForCase whenFalse,
ref bool foundExplicitNullTest)
{
stateForCase.RemainingTests.Filter(this, test, state, whenTrueValues, whenFalseValues, out Tests whenTrueTests, out Tests whenFalseTests, ref foundExplicitNullTest);
stateForCase.RemainingTests.Filter(this, test, state, whenTrueValues, whenFalseValues, out Tests whenTrueTests, out Tests whenFalseTests, ref foundExplicitNullTest, this._diagnostics);
whenTrue = stateForCase.WithRemainingTests(whenTrueTests);
whenFalse = stateForCase.WithRemainingTests(whenFalseTests);
}
Expand Down Expand Up @@ -2072,7 +2072,8 @@ public abstract void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest);
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics);
public virtual BoundDagTest ComputeSelectedTest() => throw ExceptionUtilities.Unreachable();
public virtual Tests RemoveEvaluation(BoundDagEvaluation e) => this;
/// <summary>
Expand All @@ -2096,7 +2097,8 @@ public override void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest)
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics)
{
whenTrue = whenFalse = this;
}
Expand All @@ -2117,7 +2119,8 @@ public override void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest)
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics)
{
whenTrue = whenFalse = this;
}
Expand All @@ -2141,7 +2144,8 @@ public override void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest)
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics)
{
SyntaxNode syntax = test.Syntax;
BoundDagTest other = this.Test;
Expand Down Expand Up @@ -2308,9 +2312,10 @@ public override void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest)
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics)
{
Negated.Filter(builder, test, state, whenTrueValues, whenFalseValues, out var whenTestTrue, out var whenTestFalse, ref foundExplicitNullTest);
Negated.Filter(builder, test, state, whenTrueValues, whenFalseValues, out var whenTestTrue, out var whenTestFalse, ref foundExplicitNullTest, diagnostics);
whenTrue = Not.Create(whenTestTrue);
whenFalse = Not.Create(whenTestFalse);
}
Expand All @@ -2335,20 +2340,79 @@ public sealed override void Filter(
IValueSet? whenFalseValues,
out Tests whenTrue,
out Tests whenFalse,
ref bool foundExplicitNullTest)
ref bool foundExplicitNullTest,
BindingDiagnosticBag diagnostics)
{
var trueBuilder = ArrayBuilder<Tests>.GetInstance(RemainingTests.Length);
var falseBuilder = ArrayBuilder<Tests>.GetInstance(RemainingTests.Length);
foreach (var other in RemainingTests)
for (int i = 0; i < RemainingTests.Length; i++)
{
other.Filter(builder, test, state, whenTrueValues, whenFalseValues, out Tests oneTrue, out Tests oneFalse, ref foundExplicitNullTest);
var other = RemainingTests[i];
other.Filter(builder, test, state, whenTrueValues, whenFalseValues, out Tests oneTrue, out Tests oneFalse, ref foundExplicitNullTest, diagnostics);
trueBuilder.Add(oneTrue);
falseBuilder.Add(oneFalse);

if (i > 0
&& oneTrue is False
&& this is OrSequence { RemainingTests: [Not { Negated: var negated }, ..] }
&& isRecognizedPartOfNegated(test, negated))
{
// We report a warning based on heuristic to catch a common mistake (`not ... or <redundant>` pattern)
diagnostics.Add(ErrorCode.WRN_RedundantPattern, getSyntax(other));
}
}

whenTrue = Update(trueBuilder);
whenFalse = Update(falseBuilder);

return;

// Check that the given test, which causes the `or` condition to become redundant,
// is indeed the test being negated by the `not`
static bool isRecognizedPartOfNegated(BoundDagTest test, Tests negated)
{
// If we have something like `not A or ... or B` where the A test being true implies that the B test is false,
// 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 }, ..] }
&& nonNullTest.Input == typeTest.Input
&& test == typeTest)
{
return true;
}

// If we have something like `not null or ... or C`,
// the C test could only be true when the `not null` test is true,
// then the user probably made a mistake having the `or C` condition
if (negated is One { Test: BoundDagExplicitNullTest nonNullTest2 }
&& test == nonNullTest2)
{
return true;
}

// Handle `not 42 or ...`
if (negated is One { Test: BoundDagValueTest valueTest }
&& test == valueTest)
{
return true;
}

return false;
}

static SyntaxNode getSyntax(Tests tests)
{
return tests switch
{
One { Test: var oneTest } => oneTest.Syntax,
Not not => getSyntax(not.Negated),
SequenceTests sequence => getSyntax(sequence.RemainingTests[0]),
_ => throw ExceptionUtilities.UnexpectedValue(tests),
};
}
}

public sealed override Tests RemoveEvaluation(BoundDagEvaluation e)
{
var builder = ArrayBuilder<Tests>.GetInstance(RemainingTests.Length);
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -8014,4 +8014,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_AccessorDoesNotUseBackingField_Title" xml:space="preserve">
<value>Property accessor should use 'field' because the other accessor is using it.</value>
</data>
<data name="WRN_RedundantPattern" xml:space="preserve">
<value>The pattern is redundant</value>
</data>
<data name="WRN_RedundantPattern_Title" xml:space="preserve">
<value>The pattern is redundant</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2348,6 +2348,7 @@ internal enum ErrorCode
WRN_UninitializedNonNullableBackingField = 9264,
WRN_UnassignedInternalRefField = 9265,
WRN_AccessorDoesNotUseBackingField = 9266,
WRN_RedundantPattern = 9267,

// Note: you will need to do the following after adding errors:
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_FieldIsAmbiguous:
case ErrorCode.WRN_UninitializedNonNullableBackingField:
case ErrorCode.WRN_AccessorDoesNotUseBackingField:
case ErrorCode.WRN_RedundantPattern:
return 1;
default:
return 0;
Expand Down Expand Up @@ -2466,6 +2467,7 @@ or ErrorCode.ERR_PartialPropertyDuplicateInitializer
or ErrorCode.WRN_UninitializedNonNullableBackingField
or ErrorCode.WRN_UnassignedInternalRefField
or ErrorCode.WRN_AccessorDoesNotUseBackingField
or ErrorCode.WRN_RedundantPattern
=> false,
};
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 59d9dd7

Please sign in to comment.