Skip to content

Commit

Permalink
Merge pull request #10 from shuebner/support_destructure_pattern
Browse files Browse the repository at this point in the history
feat: add support for positional pattern with Deconstruct
  • Loading branch information
shuebner authored Oct 10, 2024
2 parents b325a68 + 665fc6d commit 5519a66
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,86 @@ public static int DoSwitch(Root root)

return EnsureNotSuppressed(code, NullableContextOptions.Disable);
}

[Test]
public Task When_type_with_destructure_And_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static int DoSwitch(Root root)
{
return root switch
{
Root.Leaf1(object value) => 0,
Root.Leaf2 => 1,
};
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_And_other_Deconstruct_methods_has_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static int DoSwitch(Root root)
{
return root switch
{
Root.Leaf1(object value, string s) => 0,
Root.Leaf2 => 1,
};
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_extension_method_has_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static int DoSwitch(Root root)
{
return root switch
{
Root.Leaf1(object value, string s, object otherValue) => 0,
Root.Leaf2 => 1,
};
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_And_only_base_type_is_matched_Then_do_not_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static int DoSwitch(Root root)
{
return root switch
{
Root.Leaf1(string value) => 0,
Root.Leaf2 => 1,
};
}
}
");

return EnsureNotSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_nullable_is_disabled_And_null_is_matched_on_its_own_Then_suppress()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,94 @@ public static void DoSwitch(Root root)
return EnsureNotSuppressed(code, NullableContextOptions.Disable);
}

[Test]
public Task When_type_with_destructure_And_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static void DoSwitch(Root root)
{
switch(root)
{
case Root.Leaf1(object value):
break;
case Root.Leaf2:
break;
}
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_And_other_Deconstruct_methods_has_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static void DoSwitch(Root root)
{
switch(root)
{
case Root.Leaf1(object value, string s):
break;
case Root.Leaf2:
break;
}
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_extension_method_has_all_subtypes_match_Then_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static void DoSwitch(Root root)
{
switch(root)
{
case Root.Leaf1(object value, string s, object otherValue):
break;
case Root.Leaf2:
break;
}
}
}
");

return EnsureSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_type_with_destructure_And_only_base_type_is_matched_Then_do_not_suppress()
{
var code = CodeHelper.WrapInNamespace(TypeHierarchies.Closed.Deconstruct + @"
static class SwitchTest
{
public static void DoSwitch(Root root)
{
switch(root)
{
case Root.Leaf1(string value):
break;
case Root.Leaf2:
break;
};
}
}
");

return EnsureNotSuppressed(code, NullableContextOptions.Enable);
}

[Test]
public Task When_nullable_is_disabled_And_null_is_matched_on_its_own_Then_suppress()
{
Expand Down
42 changes: 42 additions & 0 deletions ClosedTypeHierarchyDiagnosticSuppressor.Tests/TypeHierarchies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,48 @@ public sealed class Leaf2 : Root<T> { public Leaf2(T value) : base(value) {} }
public T Value { get; }
}
";
public const string Deconstruct = @"
abstract class Root
{
Root() {}
public sealed class Leaf1 : Root
{
public Leaf1(object value, string s, object otherValue)
{
Value = value;
S = s;
OtherValue = otherValue;
}
public object Value { get; }
public string S { get; }
public object OtherValue { get; }
public void Deconstruct(out object value)
{
value = Value;
}
public void Deconstruct(out object value, out string s)
{
value = Value;
s = S;
}
}
public sealed class Leaf2 : Root {}
}
static class RootExtensions
{
public static void Deconstruct(this Root.Leaf1 leaf1, out object value, out string s, out object otherValue)
{
value = leaf1.Value;
s = leaf1.S;
otherValue = leaf1.OtherValue;
}
}
";
}

Expand Down
35 changes: 27 additions & 8 deletions ClosedTypeHierarchyDiagnosticSuppressor/PatternHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,23 @@ public static bool HandlesTypeWithoutRestrictions(PatternSyntax patternSyntax, I
_ => matchedTypeIsSubtype
};

bool IsSubpatternNonRestrictive(SubpatternSyntax subpatternSyntax) =>
bool IsRecursivePatternNonRestrictive(RecursivePatternSyntax recursivePatternSyntax) =>
(recursivePatternSyntax.PropertyPatternClause?.Subpatterns ?? recursivePatternSyntax.PositionalPatternClause?.Subpatterns)
?.Select((p, i) => (Pattern: p, Index: i)).All(pair => IsSubpatternNonRestrictive(pair.Pattern, pair.Index)) ?? false;

bool IsSubpatternNonRestrictive(SubpatternSyntax subpatternSyntax, int index) =>
subpatternSyntax.Pattern switch
{
VarPatternSyntax => true,
RecursivePatternSyntax sub => IsRecursivePatternNonRestrictive(sub),
DeclarationPatternSyntax declaration => IsDeclarationPatternNonRestrictive(declaration, subpatternSyntax),
DeclarationPatternSyntax declaration => IsDeclarationPatternNonRestrictive(declaration, subpatternSyntax, index),
_ => false


};

bool IsRecursivePatternNonRestrictive(RecursivePatternSyntax recursivePatternSyntax) =>
recursivePatternSyntax.PropertyPatternClause?.Subpatterns.All(IsSubpatternNonRestrictive) ?? false;

bool IsDeclarationPatternNonRestrictive(DeclarationPatternSyntax declarationPatternSyntax, SubpatternSyntax containingSubpatternSyntax)
bool IsDeclarationPatternNonRestrictive(
DeclarationPatternSyntax declarationPatternSyntax,
SubpatternSyntax containingSubpatternSyntax,
int index)
{
SymbolInfo declaredSymbolInfo = model.GetSymbolInfo(declarationPatternSyntax.Type);

Expand All @@ -70,6 +72,23 @@ bool IsDeclarationPatternNonRestrictive(DeclarationPatternSyntax declarationPatt
}
}
}

if (containingSubpatternSyntax is { Parent: PositionalPatternClauseSyntax positionalPattern, Pattern: DeclarationPatternSyntax declaration })
{
var symbol = model.GetSymbolInfo(positionalPattern).Symbol;
if (symbol is IMethodSymbol { Name: "Deconstruct" } deconstructMethod)
{
var correspondingParameterIndex = deconstructMethod.IsExtensionMethod
? index + 1
: index;
var positionalType = deconstructMethod.Parameters[correspondingParameterIndex].Type;

if (compilation.HasImplicitConversion(positionalType, declaredType))
{
return true;
}
}
}
}

return false;
Expand Down

0 comments on commit 5519a66

Please sign in to comment.