Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,11 @@ private string EmitFieldReference(IFieldReferenceOperation fieldRef)
return resultVar;
}

// Enum members and const fields are inlined as constants, matching the C# compiler. A reflected
// field access would be funcletized by query providers into a parameter instead.
if (fieldRef.Field.HasConstantValue)
return EmitConstantField(fieldRef.Field);

var fieldResultVar = NextVar();
var fieldName = _fieldCache.EnsureFieldInfo(fieldRef.Field);

Expand All @@ -682,6 +687,30 @@ private string EmitFieldReference(IFieldReferenceOperation fieldRef)
return fieldResultVar;
}

private string EmitConstantField(IFieldSymbol field)
{
var resultVar = NextVar();
var typeFqn = field.Type.ToDisplayString(_fqnFormat);

string valueLiteral;
if (field.ContainingType.TypeKind == TypeKind.Enum)
{
valueLiteral = $"{typeFqn}.{EscapeIdentifier(field.Name)}";
}
else if (field.Type.TypeKind == TypeKind.Enum)
{
// The const itself may be inaccessible from the generated class, so emit its value rather than its name.
valueLiteral = $"({typeFqn})({FormatConstantValue(field.ConstantValue, field.Type)})";
}
else
{
valueLiteral = FormatConstantValue(field.ConstantValue, field.Type);
}

AppendLine($"var {resultVar} = {Expr}.Constant({valueLiteral}, typeof({typeFqn}));");
return resultVar;
}

private bool TryEmitEnumMethodExpansion(IInvocationOperation invocation, out string resultVar)
{
resultVar = "";
Expand Down Expand Up @@ -793,7 +822,7 @@ string BuildCall(string operandVar)
foreach (var member in enumMembers.AsEnumerable().Reverse())
{
var enumValueVar = NextVar();
AppendLine($"var {enumValueVar} = {Expr}.Constant({enumTypeFqn}.{member.Name}, typeof({enumTypeFqn}));");
AppendLine($"var {enumValueVar} = {Expr}.Constant({enumTypeFqn}.{EscapeIdentifier(member.Name)}, typeof({enumTypeFqn}));");

// The MethodInfo is bound on the original receiver type — for an instance method on
// Nullable<TEnum> or an extension whose first param is Nullable<TEnum>, the per-arm
Expand Down Expand Up @@ -3573,6 +3602,10 @@ private static string SanitizeIdentifier(string name)
return name.Replace("@", "_").Replace(".", "_").Replace("<", "_").Replace(">", "_");
}

// Symbol display's EscapeKeywordIdentifiers does not cover enum members, so escape by hand.
private static string EscapeIdentifier(string name) =>
SyntaxFacts.GetKeywordKind(name) != SyntaxKind.None ? "@" + name : name;

private static string FormatConstantValue(object? value, ITypeSymbol? type)
{
if (value is null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Linq.Expressions;
using ExpressiveSharp.EntityFrameworkCore.IntegrationTests.Infrastructure;
using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand All @@ -13,4 +15,16 @@ protected override IAsyncDisposable CreateContextHandle(out DbContext context)
context = handle.Context;
return handle;
}

[TestMethod]
public void Select_EnumSwitch_InlinesEnumConstantsIntoSql()
{
Expression<Func<Order, string>> expr = o => o.GetStatusLabelSwitchStatement();
var expanded = (Expression<Func<Order, string>>)expr.ExpandExpressives();

var sql = Context.Set<Order>().Select(expanded).ToQueryString();

StringAssert.Contains(sql, "CASE");
Assert.IsFalse(sql.Contains('@'));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace ExpressiveSharp.Generated
var p_first = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "first");
var p_last = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "last");
var expr_0 = global::System.Linq.Expressions.Expression.New(typeof(global::Foo.PersonDto).GetConstructor(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null));
var expr_3 = global::System.Linq.Expressions.Expression.Field(null, typeof(global::Foo.PersonDto).GetField("Separator", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)); // Separator
var expr_3 = global::System.Linq.Expressions.Expression.Constant(" - ", typeof(string)); // Separator
var expr_2 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), p_first, expr_3);
var expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), expr_2, p_last);
var expr_4 = global::System.Linq.Expressions.Expression.Bind(typeof(global::Foo.PersonDto).GetProperty("FullName", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance), expr_1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ namespace ExpressiveSharp.Generated
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_3 = global::System.Linq.Expressions.Expression.Field(null, typeof(global::Foo.Bucket).GetField("Low", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)); // Bucket.Low
var expr_3 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Bucket.Low, typeof(global::Foo.Bucket)); // Bucket.Low
var expr_4 = global::System.Linq.Expressions.Expression.Convert(expr_2, typeof(int));
var expr_5 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(int));
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThanOrEqual, expr_4, expr_5);
var expr_6 = global::System.Linq.Expressions.Expression.Constant("low", typeof(string)); // "low"
var expr_9 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_10 = global::System.Linq.Expressions.Expression.Field(null, typeof(global::Foo.Bucket).GetField("Mid", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)); // Bucket.Mid
var expr_10 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Bucket.Mid, typeof(global::Foo.Bucket)); // Bucket.Mid
var expr_11 = global::System.Linq.Expressions.Expression.Convert(expr_9, typeof(int));
var expr_12 = global::System.Linq.Expressions.Expression.Convert(expr_10, typeof(int));
var expr_8 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThanOrEqual, expr_11, expr_12);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace ExpressiveSharp.Generated
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_3 = global::System.Linq.Expressions.Expression.Field(null, typeof(global::Foo.Bucket).GetField("Mid", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)); // Bucket.Mid
var expr_3 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Bucket.Mid, typeof(global::Foo.Bucket)); // Bucket.Mid
var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_3, typeof(global::Foo.Bucket?));
var expr_4 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(global::System.Nullable<int>));
var expr_5 = global::System.Linq.Expressions.Expression.Convert(expr_2, typeof(global::System.Nullable<int>));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace ExpressiveSharp.Generated
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_2 = global::System.Linq.Expressions.Expression.Field(null, typeof(global::Foo.Bucket).GetField("Mid", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)); // Bucket.Mid
var expr_2 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Bucket.Mid, typeof(global::Foo.Bucket)); // Bucket.Mid
var expr_3 = global::System.Linq.Expressions.Expression.Convert(expr_0, typeof(int));
var expr_4 = global::System.Linq.Expressions.Expression.Convert(expr_2, typeof(int));
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThanOrEqual, expr_3, expr_4);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <auto-generated/>
#nullable disable

using Foo;

namespace ExpressiveSharp.Generated
{
static partial class Foo_Entity
{
// [Expressive]
// public GeneralStatus General => Status switch
// {
// ActualStatus.New => GeneralStatus.Defined,
// ActualStatus.InProgress => GeneralStatus.PartiallyProcessed,
// ActualStatus.Archived => GeneralStatus.Annulled,
// _ => throw new System.NotImplementedException()};
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity, global::Foo.GeneralStatus>> General_Expression()
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Status", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Status
var expr_2 = global::System.Linq.Expressions.Expression.New(typeof(global::System.NotImplementedException).GetConstructor(global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { }, null)); // new System.NotImplementedException()
var expr_1 = global::System.Linq.Expressions.Expression.Throw(expr_2, typeof(global::Foo.GeneralStatus));
var expr_4 = global::System.Linq.Expressions.Expression.Constant(global::Foo.ActualStatus.Archived, typeof(global::Foo.ActualStatus)); // ActualStatus.Archived
var expr_3 = global::System.Linq.Expressions.Expression.Equal(expr_0, expr_4);
var expr_5 = global::System.Linq.Expressions.Expression.Constant(global::Foo.GeneralStatus.Annulled, typeof(global::Foo.GeneralStatus)); // GeneralStatus.Annulled
var expr_6 = global::System.Linq.Expressions.Expression.Condition(expr_3, expr_5, expr_1, typeof(global::Foo.GeneralStatus));
var expr_8 = global::System.Linq.Expressions.Expression.Constant(global::Foo.ActualStatus.InProgress, typeof(global::Foo.ActualStatus)); // ActualStatus.InProgress
var expr_7 = global::System.Linq.Expressions.Expression.Equal(expr_0, expr_8);
var expr_9 = global::System.Linq.Expressions.Expression.Constant(global::Foo.GeneralStatus.PartiallyProcessed, typeof(global::Foo.GeneralStatus)); // GeneralStatus.PartiallyProcessed
var expr_10 = global::System.Linq.Expressions.Expression.Condition(expr_7, expr_9, expr_6, typeof(global::Foo.GeneralStatus));
var expr_12 = global::System.Linq.Expressions.Expression.Constant(global::Foo.ActualStatus.New, typeof(global::Foo.ActualStatus)); // ActualStatus.New
var expr_11 = global::System.Linq.Expressions.Expression.Equal(expr_0, expr_12);
var expr_13 = global::System.Linq.Expressions.Expression.Constant(global::Foo.GeneralStatus.Defined, typeof(global::Foo.GeneralStatus)); // GeneralStatus.Defined
var expr_14 = global::System.Linq.Expressions.Expression.Condition(expr_11, expr_13, expr_10, typeof(global::Foo.GeneralStatus));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.Entity, global::Foo.GeneralStatus>>(expr_14, p__this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <auto-generated/>
#nullable disable

using Foo;

namespace ExpressiveSharp.Generated
{
static partial class Foo_Entity
{
// [Expressive]
// public bool IsClass => Value == Kind.@class;
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity, bool>> IsClass_Expression()
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_2 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Kind.@class, typeof(global::Foo.Kind)); // Kind.@class
var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_1, expr_2);
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.Entity, bool>>(expr_0, p__this);
}
}
}

// <auto-generated/>
#nullable disable

using Foo;

namespace ExpressiveSharp.Generated
{
static partial class Foo_Entity
{
// [Expressive]
// public string Description => Value.Describe();
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Entity, string>> Description_Expression()
{
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.Entity), "@this");
var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.Entity).GetProperty("Value", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Value
var expr_1 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string));
var expr_2 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Kind.@default, typeof(global::Foo.Kind));
var expr_3 = global::System.Linq.Expressions.Expression.Call(typeof(global::Foo.KindExtensions).GetMethod("Describe", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(global::Foo.Kind) }, null), new global::System.Linq.Expressions.Expression[] { expr_2 });
var expr_4 = global::System.Linq.Expressions.Expression.Equal(expr_0, expr_2);
var expr_5 = global::System.Linq.Expressions.Expression.Condition(expr_4, expr_3, expr_1, typeof(string));
var expr_6 = global::System.Linq.Expressions.Expression.Constant(global::Foo.Kind.@class, typeof(global::Foo.Kind));
var expr_7 = global::System.Linq.Expressions.Expression.Call(typeof(global::Foo.KindExtensions).GetMethod("Describe", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(global::Foo.Kind) }, null), new global::System.Linq.Expressions.Expression[] { expr_6 });
var expr_8 = global::System.Linq.Expressions.Expression.Equal(expr_0, expr_6);
var expr_9 = global::System.Linq.Expressions.Expression.Condition(expr_8, expr_7, expr_5, typeof(string));
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.Entity, string>>(expr_9, p__this);
}
}
}
Loading
Loading