Skip to content

Commit

Permalink
Support tuple conversions in Expression.Convert and ConvertChecked
Browse files Browse the repository at this point in the history
Closes #18284
  • Loading branch information
JonHanna committed Apr 12, 2017
1 parent 19ce750 commit e5f0f77
Show file tree
Hide file tree
Showing 6 changed files with 659 additions and 13 deletions.
64 changes: 64 additions & 0 deletions src/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,70 @@ public static bool HasIdentityPrimitiveOrNullableConversionTo(this Type source,
return false;
}

public static bool IsValueTupleType(this Type type) =>
type.IsConstructedGenericType && IsValueTupleType(type, type.GetGenericArguments().Length);

private static bool IsValueTupleType(Type type, int argumentCount)
{
Debug.Assert(type.IsConstructedGenericType);
type = type.GetGenericTypeDefinition();
switch (argumentCount)
{
case 1:
return type == typeof(ValueTuple<>);
case 2:
return type == typeof(ValueTuple<,>);
case 3:
return type == typeof(ValueTuple<,,>);
case 4:
return type == typeof(ValueTuple<,,,>);
case 5:
return type == typeof(ValueTuple<,,,,>);
case 6:
return type == typeof(ValueTuple<,,,,,>);
case 7:
return type == typeof(ValueTuple<,,,,,,>);
case 8:
return type == typeof(ValueTuple<,,,,,,,>);
}

return false;
}

public static bool HasTupleConversionTo(this Type source, Type dest)
{
source = source.GetNonNullableType();
dest = dest.GetNonNullableType();
if (source.IsConstructedGenericType && dest.IsConstructedGenericType)
{
Type[] sourceArgs = source.GetGenericArguments();
int argLen = sourceArgs.Length;
if (IsValueTupleType(source, argLen))
{
Type[] destArgs = dest.GetGenericArguments();
if (destArgs.Length == argLen && IsValueTupleType(dest, argLen))
{
for (int i = 0; i < sourceArgs.Length; ++i)
{
Type sourceArg = sourceArgs[i];
Type destArg = destArgs[i];
if (!sourceArg.HasIdentityPrimitiveOrNullableConversionTo(destArg)
&& !sourceArg.HasReferenceConversionTo(destArg)
&& !sourceArg.HasTupleConversionTo(destArg)
&& GetUserDefinedCoercionMethod(sourceArg, destArg) == null)
{
return false;
}
}

return true;
}
}
}

return false;
}

public static bool HasReferenceConversionTo(this Type source, Type dest)
{
Debug.Assert(source != null && dest != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,18 +352,19 @@ private void EmitConvert(UnaryExpression node, CompilationFlags flags)
{
EmitExpressionAsVoid(node.Operand, flags);
}
else if (TypeUtils.AreEquivalent(node.Operand.Type, node.Type))
{
EmitExpression(node.Operand, flags);
}
else if (node.IsTupleConversion)
{
EmitExpression(node.ReduceTupleConversion(), flags);
}
else
{
if (TypeUtils.AreEquivalent(node.Operand.Type, node.Type))
{
EmitExpression(node.Operand, flags);
}
else
{
// A conversion is emitted after emitting the operand, no tail call is emitted
EmitExpression(node.Operand);
_ilg.EmitConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, this);
}
// A conversion is emitted after emitting the operand, no tail call is emitted
EmitExpression(node.Operand);
_ilg.EmitConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, this);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,10 @@ private void CompileConvertUnaryExpression(Expression expr)
{
CompileAsVoid(node.Operand);
}
else if (node.IsTupleConversion)
{
Compile(node.ReduceTupleConversion());
}
else
{
Compile(node.Operand);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,87 @@ public UnaryExpression Update(Expression operand)
}
return Expression.MakeUnary(NodeType, operand, Type, Method);
}

internal bool IsTupleConversion
{
get
{
Debug.Assert(NodeType == ExpressionType.Convert || NodeType == ExpressionType.ConvertChecked);
if (Method != null)
{
return false;
}

Type type = Type.GetNonNullableType();
Type opType = Operand.Type.GetNonNullableType();
return type != opType && type.IsValueTupleType() && opType.IsValueTupleType();
}
}

internal Expression ReduceTupleConversion()
{
Debug.Assert(IsTupleConversion);

Expression operand = Operand;
ParameterExpression nullableOperand = null;
if (operand.Type.IsNullableType())
{
// Handle lifting of this type of conversion within the reduction
if (Type.IsNullableType())
{
// We'll use nullableOperand to test and possibly skip the rest of the conversion
nullableOperand = Variable(operand.Type);
// Which means if the conversion is happening, it's guaranteed not to be null.
operand = Call(nullableOperand, operand.Type.GetMethod("GetValueOrDefault", Type.EmptyTypes));
}
else
{
// Convert to non-nullable first, throwing if null.
operand = Convert(operand, operand.Type.GetNonNullableType());
}
}

ParameterExpression tuple = Variable(operand.Type);
Type type = Type.GetNonNullableType();
Type[] destTypes = type.GetGenericArguments();
Debug.Assert(destTypes.Length <= 8);
Expression[] conversions = new Expression[destTypes.Length];
for (int i = 0; i < destTypes.Length; ++i)
{
Type itemType = destTypes[i];
Expression field = Field(tuple, i == 7 ? "Rest" : $"Item{i + 1}");

if (field.Type == itemType)
{
// No conversion needed for this element.
conversions[i] = field;
}
else
{
UnaryExpression convItem = MakeUnary(NodeType, field, itemType);
conversions[i] = convItem.IsTupleConversion ? convItem.ReduceTupleConversion() : convItem;
}
}

Expression conversion = Block(new[] {tuple}, Assign(tuple, operand), New(type.GetConstructors()[0], conversions));
if (!ReferenceEquals(Type, type))
{
// We've a conversion to non-nullable, add a further conversion to the nullable form.
conversion = Convert(conversion, Type);

if (nullableOperand != null)
{
// If both source and destination types are nullable then if the operand is
// null we want to skip the conversion, and return null of the appropriate
// nullable type.
conversion = Block(
new[] {nullableOperand}, Assign(nullableOperand, Operand),
Condition(NotEqual(nullableOperand, Utils.Null), conversion, Default(Type)));
}
}

return conversion;
}
}

public partial class Expression
Expand Down Expand Up @@ -792,7 +873,8 @@ public static UnaryExpression Convert(Expression expression, Type type, MethodIn
if (method == null)
{
if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type) ||
expression.Type.HasReferenceConversionTo(type))
expression.Type.HasReferenceConversionTo(type) ||
expression.Type.HasTupleConversionTo(type))
{
return new UnaryExpression(ExpressionType.Convert, expression, type, null);
}
Expand Down Expand Up @@ -841,16 +923,25 @@ public static UnaryExpression ConvertChecked(Expression expression, Type type, M

if (method == null)
{
if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type))
Type opType = expression.Type;
if (opType.HasIdentityPrimitiveOrNullableConversionTo(type))
{
return new UnaryExpression(ExpressionType.ConvertChecked, expression, type, null);
}
if (expression.Type.HasReferenceConversionTo(type))

if (opType.HasReferenceConversionTo(type))
{
return new UnaryExpression(ExpressionType.Convert, expression, type, null);
}

if (opType.HasTupleConversionTo(type))
{
return new UnaryExpression(ExpressionType.ConvertChecked, expression, type, null);
}

return GetUserDefinedCoercionOrThrow(ExpressionType.ConvertChecked, expression, type);
}

return GetMethodBasedCoercionOperator(ExpressionType.ConvertChecked, expression, type, method);
}

Expand Down
Loading

0 comments on commit e5f0f77

Please sign in to comment.