Skip to content

Commit

Permalink
Removed extra conversion emitted into expression trees for a lifted b…
Browse files Browse the repository at this point in the history
…inary enum operator.

Fixes #5734
  • Loading branch information
VSadov committed Oct 7, 2015
1 parent c816547 commit 5c602fc
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,13 @@ private BoundExpression VisitBinaryOperator(BinaryOperatorKind opKind, MethodSym
right = _bound.Default(left.Type);
}

var loweredLeft = Visit(left);
var loweredRight = Visit(right);

// Enums are handled as per their promoted underlying type
switch (opKind.OperandTypes())
{
case BinaryOperatorKind.Enum:
case BinaryOperatorKind.EnumAndUnderlying:
case BinaryOperatorKind.UnderlyingAndEnum:
case BinaryOperatorKind.Enum:
{
var enumOperand = (opKind.OperandTypes() == BinaryOperatorKind.UnderlyingAndEnum) ? right : left;
var promotedType = PromotedType(enumOperand.Type.StrippedType().GetEnumUnderlyingType());
Expand All @@ -431,18 +429,38 @@ private BoundExpression VisitBinaryOperator(BinaryOperatorKind opKind, MethodSym
promotedType = _nullableType.Construct(promotedType);
}

loweredLeft = PromoteEnumOperand(left, loweredLeft, promotedType, isChecked);
loweredRight = PromoteEnumOperand(right, loweredRight, promotedType, isChecked);
var loweredLeft = VisitAndPromoteEnumOperand(left, promotedType, isChecked);
var loweredRight = VisitAndPromoteEnumOperand(right, promotedType, isChecked);

var result = MakeBinary(methodOpt, type, isLifted, requiresLifted, opName, loweredLeft, loweredRight);
return Demote(result, type, isChecked);
}
default:
return MakeBinary(methodOpt, type, isLifted, requiresLifted, opName, loweredLeft, loweredRight);
{
var loweredLeft = Visit(left);
var loweredRight = Visit(right);
return MakeBinary(methodOpt, type, isLifted, requiresLifted, opName, loweredLeft, loweredRight);
}
}
}

private static BoundExpression DemoteEnumOperand(BoundExpression left)
{
if (left.Kind == BoundKind.Conversion)
{
var conversion = (BoundConversion)left;
if (!conversion.ConversionKind.IsUserDefinedConversion() &&
conversion.ConversionKind.IsImplicitConversion() &&
conversion.Type.StrippedType().IsEnumType())
{
left = conversion.Operand;
}
}

return left;
}

private BoundExpression PromoteEnumOperand(BoundExpression operand, BoundExpression loweredOperand, TypeSymbol promotedType, bool isChecked)
private BoundExpression VisitAndPromoteEnumOperand(BoundExpression operand, TypeSymbol promotedType, bool isChecked)
{
var literal = operand as BoundLiteral;
if (literal != null)
Expand All @@ -452,6 +470,11 @@ private BoundExpression PromoteEnumOperand(BoundExpression operand, BoundExpress
}
else
{
// COMPAT: if we have an operand converted to enum, we should unconvert it first
// Otherwise we will have an extra conversion in the tree: op -> enum -> underlying
// where native compiler would just directly convert to underlying
var demotedOperand = DemoteEnumOperand(operand);
var loweredOperand = Visit(demotedOperand);
return Convert(loweredOperand, operand.Type, promotedType, isChecked, false);
}
}
Expand Down
168 changes: 168 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenExprLambdaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4536,6 +4536,174 @@ public static void Main()
expectedOutput: expectedOutput);
}

[Fact, WorkItem(4471, "https://github.com/dotnet/roslyn/issues/5734")]
public void EnumEquality001()
{
string source =
@"
using System;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
enum YesNo
{
Yes,
No
}
class MyType
{
public string Name { get; set; }
public YesNo? YesNo { get; set; }
public int? Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
Expression<Func<MyType, bool>> expr = (MyType x) => x.YesNo == YesNo.Yes;
Console.WriteLine(expr.Dump());
}
}
}";
string expectedOutput = "Equal(Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:System.Nullable`1[ConsoleApplication1.YesNo]) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Convert(Constant(Yes Type:ConsoleApplication1.YesNo) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Lifted Type:System.Boolean)";
CompileAndVerify(
new[] { source, ExpressionTestLibrary },
new[] { ExpressionAssemblyRef },
expectedOutput: expectedOutput);
}

[Fact, WorkItem(4471, "https://github.com/dotnet/roslyn/issues/5734")]
public void EnumEquality002()
{
string source =
@"
using System;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
enum YesNo
{
Yes,
No
}
class MyType
{
public string Name { get; set; }
public YesNo? YesNo { get; set; }
public int? Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
Expression<Func<MyType, bool>> expr = (MyType x) => x.YesNo == x.YesNo;
Console.WriteLine(expr.Dump());
}
}
}";
string expectedOutput = "Equal(Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:System.Nullable`1[ConsoleApplication1.YesNo]) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:System.Nullable`1[ConsoleApplication1.YesNo]) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Lifted Type:System.Boolean)";
CompileAndVerify(
new[] { source, ExpressionTestLibrary },
new[] { ExpressionAssemblyRef },
expectedOutput: expectedOutput);
}

[Fact, WorkItem(4471, "https://github.com/dotnet/roslyn/issues/5734")]
public void EnumEquality003()
{
string source =
@"
using System;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
enum YesNo
{
Yes,
No
}
class MyType
{
public string Name { get; set; }
public YesNo YesNo { get; set; }
public int? Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
Expression<Func<MyType, bool>> expr = (MyType x) => x.YesNo == x.YesNo;
Console.WriteLine(expr.Dump());
}
}
}";
string expectedOutput = "Equal(Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:ConsoleApplication1.YesNo) Type:System.Int32) Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:ConsoleApplication1.YesNo) Type:System.Int32) Type:System.Boolean)";
CompileAndVerify(
new[] { source, ExpressionTestLibrary },
new[] { ExpressionAssemblyRef },
expectedOutput: expectedOutput);
}

[Fact, WorkItem(4471, "https://github.com/dotnet/roslyn/issues/5734")]
public void EnumEquality004()
{
string source =
@"
using System;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
enum YesNo
{
Yes,
No
}
class MyType
{
public string Name { get; set; }
public YesNo? YesNo { get; set; }
public int? Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
Expression<Func<MyType, bool>> expr = (MyType x) => x.YesNo == (YesNo)1;
Console.WriteLine(expr.Dump());
}
}
}";
string expectedOutput = "Equal(Convert(MemberAccess(Parameter(x Type:ConsoleApplication1.MyType).YesNo Type:System.Nullable`1[ConsoleApplication1.YesNo]) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Convert(Constant(No Type:ConsoleApplication1.YesNo) Lifted LiftedToNull Type:System.Nullable`1[System.Int32]) Lifted Type:System.Boolean)";
CompileAndVerify(
new[] { source, ExpressionTestLibrary },
new[] { ExpressionAssemblyRef },
expectedOutput: expectedOutput);
}

[WorkItem(546618, "DevDiv")]
[Fact]
public void TildeNullableEnum()
Expand Down

0 comments on commit 5c602fc

Please sign in to comment.