Skip to content

Commit 7846f12

Browse files
committed
extended coverage
1 parent 26c4f3c commit 7846f12

4 files changed

Lines changed: 161 additions & 2 deletions

File tree

src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,7 +1574,7 @@ private string EmitPattern(IPatternOperation pattern, string operandVar, ITypeSy
15741574
{
15751575
return pattern switch
15761576
{
1577-
IConstantPatternOperation constant => EmitConstantPattern(constant, operandVar),
1577+
IConstantPatternOperation constant => EmitConstantPattern(constant, operandVar, operandType),
15781578
ITypePatternOperation typePattern => EmitTypePattern(typePattern, operandVar),
15791579
IDeclarationPatternOperation declaration => EmitDeclarationPattern(declaration, operandVar),
15801580
IRelationalPatternOperation relational => EmitRelationalPattern(relational, operandVar, operandType),
@@ -1587,10 +1587,11 @@ private string EmitPattern(IPatternOperation pattern, string operandVar, ITypeSy
15871587
};
15881588
}
15891589

1590-
private string EmitConstantPattern(IConstantPatternOperation constant, string operandVar)
1590+
private string EmitConstantPattern(IConstantPatternOperation constant, string operandVar, ITypeSymbol? operandType)
15911591
{
15921592
var resultVar = NextVar();
15931593
var valueVar = EmitOperation(constant.Value);
1594+
AlignNullability(ref operandVar, operandType, ref valueVar, constant.Value.Type);
15941595
AppendLine($"var {resultVar} = {Expr}.Equal({operandVar}, {valueVar});");
15951596
return resultVar;
15961597
}
@@ -1640,11 +1641,47 @@ private string EmitRelationalPattern(IRelationalPatternOperation relational, str
16401641
operandVar = EmitConvert(operandVar, underlyingFqn);
16411642
valueVar = EmitConvert(valueVar, underlyingFqn);
16421643
}
1644+
else
1645+
{
1646+
AlignNullability(ref operandVar, operandType, ref valueVar, relational.Value.Type);
1647+
}
16431648

16441649
AppendLine($"var {resultVar} = {Expr}.MakeBinary(global::System.Linq.Expressions.ExpressionType.{exprType}, {operandVar}, {valueVar});");
16451650
return resultVar;
16461651
}
16471652

1653+
// Pattern-matching keeps the constant typed as T even when the input is Nullable<T>, so
1654+
// Expression.MakeBinary(GreaterThan, int?, int) throws BinaryOperatorNotDefined at runtime.
1655+
// Lift the non-nullable side to Nullable<T> so MakeBinary builds a lifted comparison whose
1656+
// bool result matches pattern semantics (null operand → false).
1657+
private void AlignNullability(ref string leftVar, ITypeSymbol? leftType, ref string rightVar, ITypeSymbol? rightType)
1658+
{
1659+
if (leftType is null || rightType is null)
1660+
return;
1661+
1662+
var leftUnderlying = GetNullableUnderlying(leftType);
1663+
var rightUnderlying = GetNullableUnderlying(rightType);
1664+
1665+
if (leftUnderlying is not null && rightUnderlying is null && SymbolEqualityComparer.Default.Equals(leftUnderlying, rightType))
1666+
{
1667+
rightVar = EmitConvert(rightVar, leftType.ToDisplayString(_fqnFormat));
1668+
}
1669+
else if (rightUnderlying is not null && leftUnderlying is null && SymbolEqualityComparer.Default.Equals(rightUnderlying, leftType))
1670+
{
1671+
leftVar = EmitConvert(leftVar, rightType.ToDisplayString(_fqnFormat));
1672+
}
1673+
}
1674+
1675+
private static ITypeSymbol? GetNullableUnderlying(ITypeSymbol type)
1676+
{
1677+
if (type is INamedTypeSymbol { IsGenericType: true } named
1678+
&& named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
1679+
{
1680+
return named.TypeArguments[0];
1681+
}
1682+
return null;
1683+
}
1684+
16481685
private string EmitNegatedPattern(INegatedPatternOperation negated, string operandVar, ITypeSymbol? operandType)
16491686
{
16501687
var resultVar = NextVar();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
4+
using Foo;
5+
6+
namespace ExpressiveSharp.Generated
7+
{
8+
static partial class Foo_Customer
9+
{
10+
// [Expressive]
11+
// public string Classify() => Email?.Length switch
12+
// {
13+
// 5 => "five",
14+
// _ => "other",
15+
// };
16+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Customer, string>> Classify_Expression()
17+
{
18+
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Customer), "@this");
19+
var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Email
20+
var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(string).GetProperty("Length", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Length
21+
var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(int?));
22+
var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string));
23+
var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_4);
24+
var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?));
25+
var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(int?));
26+
var expr_7 = global::System.Linq.Expressions.Expression.Constant("other", typeof(string)); // "other"
27+
var expr_9 = global::System.Linq.Expressions.Expression.Constant(5, typeof(int)); // 5
28+
var expr_10 = global::System.Linq.Expressions.Expression.Convert(expr_9, typeof(int?));
29+
var expr_8 = global::System.Linq.Expressions.Expression.Equal(expr_3, expr_10);
30+
var expr_11 = global::System.Linq.Expressions.Expression.Constant("five", typeof(string)); // "five"
31+
var expr_12 = global::System.Linq.Expressions.Expression.Condition(expr_8, expr_11, expr_7, typeof(string));
32+
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.Customer, string>>(expr_12, p__this);
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
4+
using Foo;
5+
6+
namespace ExpressiveSharp.Generated
7+
{
8+
static partial class Foo_Customer
9+
{
10+
// [Expressive]
11+
// public bool HasLongEmail() => Email?.Length switch
12+
// {
13+
// > 5 => true,
14+
// _ => false,
15+
// };
16+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Customer, bool>> HasLongEmail_Expression()
17+
{
18+
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Customer), "@this");
19+
var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Customer).GetProperty("Email", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Email
20+
var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(string).GetProperty("Length", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // .Length
21+
var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(int?));
22+
var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string));
23+
var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_4);
24+
var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?));
25+
var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(int?));
26+
var expr_7 = global::System.Linq.Expressions.Expression.Constant(false, typeof(bool)); // false
27+
var expr_9 = global::System.Linq.Expressions.Expression.Constant(5, typeof(int)); // 5
28+
var expr_10 = global::System.Linq.Expressions.Expression.Convert(expr_9, typeof(int?));
29+
var expr_8 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_3, expr_10);
30+
var expr_11 = global::System.Linq.Expressions.Expression.Constant(true, typeof(bool)); // true
31+
var expr_12 = global::System.Linq.Expressions.Expression.Condition(expr_8, expr_11, expr_7, typeof(bool));
32+
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.Customer, bool>>(expr_12, p__this);
33+
}
34+
}
35+
}

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/SwitchPatternTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,58 @@ static class OrderExtensions {
194194
return Verifier.Verify(result.GeneratedTrees[0].ToString());
195195
}
196196

197+
[TestMethod]
198+
public Task SwitchExpression_RelationalPattern_OnNullableOperand()
199+
{
200+
var compilation = CreateCompilation(
201+
"""
202+
namespace Foo {
203+
class Customer {
204+
public string? Email { get; set; }
205+
206+
[Expressive]
207+
public bool HasLongEmail() => Email?.Length switch
208+
{
209+
> 5 => true,
210+
_ => false,
211+
};
212+
}
213+
}
214+
""");
215+
var result = RunExpressiveGenerator(compilation);
216+
217+
Assert.AreEqual(0, result.Diagnostics.Length);
218+
Assert.AreEqual(1, result.GeneratedTrees.Length);
219+
220+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
221+
}
222+
223+
[TestMethod]
224+
public Task SwitchExpression_ConstantPattern_OnNullableOperand()
225+
{
226+
var compilation = CreateCompilation(
227+
"""
228+
namespace Foo {
229+
class Customer {
230+
public string? Email { get; set; }
231+
232+
[Expressive]
233+
public string Classify() => Email?.Length switch
234+
{
235+
5 => "five",
236+
_ => "other",
237+
};
238+
}
239+
}
240+
""");
241+
var result = RunExpressiveGenerator(compilation);
242+
243+
Assert.AreEqual(0, result.Diagnostics.Length);
244+
Assert.AreEqual(1, result.GeneratedTrees.Length);
245+
246+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
247+
}
248+
197249
[TestMethod]
198250
public Task ExpressionBodied_IsPattern_WithAndPattern()
199251
{

0 commit comments

Comments
 (0)