Skip to content

Commit 552908d

Browse files
authored
Merge pull request #136 from 7645re/feature/support-switch-expression-with-type-pattern
Add support for declaration patterns in switch expressions by convert…
2 parents 0231edf + 4a3a38a commit 552908d

File tree

5 files changed

+163
-2
lines changed

5 files changed

+163
-2
lines changed

src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,46 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
190190
continue;
191191
}
192192

193-
throw new InvalidOperationException("Switch expressions rewriting is only supported with constant values");
193+
if (arm.Pattern is DeclarationPatternSyntax declaration)
194+
{
195+
var getTypeExpression = SyntaxFactory.MemberAccessExpression(
196+
SyntaxKind.SimpleMemberAccessExpression,
197+
(ExpressionSyntax)Visit(node.GoverningExpression),
198+
SyntaxFactory.IdentifierName("GetType")
199+
);
200+
201+
var getTypeCall = SyntaxFactory.InvocationExpression(getTypeExpression);
202+
var typeofExpression = SyntaxFactory.TypeOfExpression(declaration.Type);
203+
var equalsExpression = SyntaxFactory.BinaryExpression(
204+
SyntaxKind.EqualsExpression,
205+
getTypeCall,
206+
typeofExpression
207+
);
208+
209+
ExpressionSyntax condition = equalsExpression;
210+
if (arm.WhenClause != null)
211+
{
212+
condition = SyntaxFactory.BinaryExpression(
213+
SyntaxKind.LogicalAndExpression,
214+
equalsExpression,
215+
(ExpressionSyntax)Visit(arm.WhenClause.Condition)
216+
);
217+
}
218+
219+
var modifiedArmExpression = ReplaceVariableWithCast(armExpression, declaration, node.GoverningExpression);
220+
currentExpression = SyntaxFactory.ConditionalExpression(
221+
condition,
222+
modifiedArmExpression,
223+
currentExpression
224+
);
225+
226+
continue;
227+
}
228+
229+
throw new InvalidOperationException(
230+
$"Switch expressions rewriting supports only constant values and declaration patterns (Type var). " +
231+
$"Unsupported pattern: {arm.Pattern.GetType().Name}"
232+
);
194233
}
195234

196235
return currentExpression;
@@ -346,5 +385,25 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
346385

347386
return base.VisitNullableType(node);
348387
}
388+
389+
private ExpressionSyntax ReplaceVariableWithCast(ExpressionSyntax expression, DeclarationPatternSyntax declaration, ExpressionSyntax governingExpression)
390+
{
391+
if (declaration.Designation is SingleVariableDesignationSyntax variableDesignation)
392+
{
393+
var variableName = variableDesignation.Identifier.ValueText;
394+
395+
var castExpression = SyntaxFactory.ParenthesizedExpression(
396+
SyntaxFactory.CastExpression(
397+
declaration.Type,
398+
(ExpressionSyntax)Visit(governingExpression)
399+
)
400+
);
401+
402+
var rewriter = new VariableReplacementRewriter(variableName, castExpression);
403+
return (ExpressionSyntax)rewriter.Visit(expression);
404+
}
405+
406+
return expression;
407+
}
349408
}
350409
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
namespace EntityFrameworkCore.Projectables.Generator;
5+
6+
public class VariableReplacementRewriter : CSharpSyntaxRewriter
7+
{
8+
private readonly string _variableName;
9+
private readonly ExpressionSyntax _replacement;
10+
11+
public VariableReplacementRewriter(string variableName, ExpressionSyntax replacement)
12+
{
13+
_variableName = variableName;
14+
_replacement = replacement;
15+
}
16+
17+
public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
18+
{
19+
if (node.Identifier.ValueText == _variableName)
20+
{
21+
return _replacement;
22+
}
23+
24+
return base.VisitIdentifierName(node);
25+
}
26+
27+
public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
28+
{
29+
if (node.Expression is IdentifierNameSyntax identifier &&
30+
identifier.Identifier.ValueText == _variableName)
31+
{
32+
return SyntaxFactory.MemberAccessExpression(
33+
SyntaxKind.SimpleMemberAccessExpression,
34+
_replacement,
35+
node.Name
36+
);
37+
}
38+
39+
return base.VisitMemberAccessExpression(node);
40+
}
41+
}

tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpression.verified.txt renamed to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithConstantPattern.verified.txt

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using EntityFrameworkCore.Projectables;
4+
5+
namespace EntityFrameworkCore.Projectables.Generated
6+
{
7+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
8+
static class _ItemMapper_ToData
9+
{
10+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Item, global::ItemData>> Expression()
11+
{
12+
return (global::Item item) => item.GetType() == typeof(GroupItem) ? new global::GroupData(((GroupItem)item).Id, ((GroupItem)item).Name, ((GroupItem)item).Description) : item.GetType() == typeof(DocumentItem) ? new global::DocumentData(((DocumentItem)item).Id, ((DocumentItem)item).Name, ((DocumentItem)item).Priority) : null !;
13+
}
14+
}
15+
}

tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ class Foo {
17851785
}
17861786

17871787
[Fact]
1788-
public Task SwitchExpression()
1788+
public Task SwitchExpressionWithConstantPattern()
17891789
{
17901790
var compilation = CreateCompilation(@"
17911791
using EntityFrameworkCore.Projectables;
@@ -1811,6 +1811,52 @@ class Foo {
18111811
return Verifier.Verify(result.GeneratedTrees[0].ToString());
18121812
}
18131813

1814+
[Fact]
1815+
public Task SwitchExpressionWithTypePattern()
1816+
{
1817+
var compilation = CreateCompilation(@"
1818+
using EntityFrameworkCore.Projectables;
1819+
1820+
public abstract class Item
1821+
{
1822+
public int Id { get; set; }
1823+
public string Name { get; set; }
1824+
}
1825+
1826+
public class GroupItem : Item
1827+
{
1828+
public string Description { get; set; }
1829+
}
1830+
1831+
public class DocumentItem : Item
1832+
{
1833+
public int Priority { get; set; }
1834+
}
1835+
1836+
public abstract record ItemData(int Id, string Name);
1837+
public record GroupData(int Id, string Name, string Description) : ItemData(Id, Name);
1838+
public record DocumentData(int Id, string Name, int Priority) : ItemData(Id, Name);
1839+
1840+
public static class ItemMapper
1841+
{
1842+
[Projectable]
1843+
public static ItemData ToData(this Item item) =>
1844+
item switch
1845+
{
1846+
GroupItem groupItem => new GroupData(groupItem.Id, groupItem.Name, groupItem.Description),
1847+
DocumentItem documentItem => new DocumentData(documentItem.Id, documentItem.Name, documentItem.Priority),
1848+
_ => null!
1849+
};
1850+
}
1851+
");
1852+
1853+
var result = RunGenerator(compilation);
1854+
1855+
Assert.Empty(result.Diagnostics);
1856+
1857+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
1858+
}
1859+
18141860
[Fact]
18151861
public Task GenericTypes()
18161862
{

0 commit comments

Comments
 (0)