Skip to content

Commit

Permalink
Support value tuples in CSharpHelper (#29384)
Browse files Browse the repository at this point in the history
Closes #29383
  • Loading branch information
roji authored Oct 18, 2022
1 parent df614b8 commit db4f92c
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 22 deletions.
55 changes: 55 additions & 0 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using Microsoft.EntityFrameworkCore.Internal;
Expand Down Expand Up @@ -693,6 +694,54 @@ private string Array(Type type, IEnumerable values, bool vertical = false)
return builder.ToString();
}

private string ValueTuple(ITuple tuple)
{
var builder = new StringBuilder();

Type[]? typeArguments = null;
var i = 0;

if (tuple.Length == 1)
{
builder.Append("ValueTuple.Create(");
AppendItem(tuple[i]);
builder.Append(')');

return builder.ToString();
}

builder.Append('(');

for (; i < tuple.Length; i++)
{
if (i > 0)
{
builder.Append(", ");
}

AppendItem(tuple[i]);
}

builder.Append(')');

return builder.ToString();

void AppendItem(object? item)
{
if (item is null)
{
typeArguments ??= tuple.GetType().GenericTypeArguments;

builder
.Append('(')
.Append(Reference(typeArguments[i]))
.Append(')');
}

builder.Append(UnknownLiteral(item));
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -970,6 +1019,12 @@ public virtual string UnknownLiteral(object? value)
return Array(literalType.GetElementType()!, array);
}

if (value is ITuple tuple
&& value.GetType().FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true)
{
return ValueTuple(tuple);
}

var valueType = value.GetType();
if (valueType.IsGenericType && !valueType.IsGenericTypeDefinition)
{
Expand Down
65 changes: 43 additions & 22 deletions test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace Microsoft.EntityFrameworkCore.Design.Internal;

#nullable enable

public class CSharpHelperTest
{
private static readonly string EOL = Environment.NewLine;
Expand Down Expand Up @@ -183,6 +185,26 @@ public void Literal_works_when_multiline_string()
"multi-line\r\nstring\nwith\r\"",
"\"multi-line\\r\\nstring\\nwith\\r\\\"\"");

[ConditionalFact]
public void Literal_works_when_value_tuple()
=> Literal_works((1, "hello"), "(1, \"hello\")");

[ConditionalFact]
public void Literal_works_when_value_tuple_with_null_value_type()
=> Literal_works((1, (int?)null, "hello"), "(1, (int?)null, \"hello\")");

[ConditionalFact]
public void Literal_works_when_value_tuple_with_null_reference_type()
=> Literal_works((1, (string?)null, "hello"), "(1, (string)null, \"hello\")");

[ConditionalFact]
public void Literal_works_when_value_tuple_of_length_1()
=> Literal_works(ValueTuple.Create(1), "ValueTuple.Create(1)");

[ConditionalFact]
public void Literal_works_when_value_tuple_of_length_9()
=> Literal_works((1, 2, 3, 4, 5, 6, 7, 8, 9), "(1, 2, 3, 4, 5, 6, 7, 8, 9)");

[ConditionalFact]
[UseCulture("de-DE")]
public void Literal_works_when_DateTime()
Expand Down Expand Up @@ -331,7 +353,7 @@ public void Identifier_works(string input, string expected)
[InlineData(new[] { "#", "$" }, "_._")]
[InlineData(new[] { "" }, "_")]
[InlineData(new string[] { }, "_")]
[InlineData(new string[] { null }, "_")]
[InlineData(new string?[] { null }, "_")]
public void Namespace_works(string[] input, string excepted)
=> Assert.Equal(excepted, new CSharpHelper(TypeMappingSource).Namespace(input));

Expand Down Expand Up @@ -587,7 +609,7 @@ public void Literal_with_one_parameter_constructor()
{
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
v => Expression.New(
typeof(SimpleTestType).GetConstructor(new[] { typeof(string) }),
typeof(SimpleTestType).GetConstructor(new[] { typeof(string) })!,
Expression.Constant(v.Arg1, typeof(string))));

Assert.Equal(
Expand All @@ -600,7 +622,7 @@ public void Literal_with_two_parameter_constructor()
{
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
v => Expression.New(
typeof(SimpleTestType).GetConstructor(new[] { typeof(string), typeof(int?) }),
typeof(SimpleTestType).GetConstructor(new[] { typeof(string), typeof(int?) })!,
Expression.Constant(v.Arg1, typeof(string)),
Expression.Constant(v.Arg2, typeof(int?))));

Expand All @@ -616,7 +638,7 @@ public void Literal_with_parameterless_static_factory()
v => Expression.Call(
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.StaticCreate),
new Type[0])));
Type.EmptyTypes)!));

Assert.Equal(
"Microsoft.EntityFrameworkCore.Design.Internal.SimpleTestTypeFactory.StaticCreate()",
Expand All @@ -630,7 +652,7 @@ public void Literal_with_one_parameter_static_factory()
v => Expression.Call(
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.StaticCreate),
new[] { typeof(string) }),
new[] { typeof(string) })!,
Expression.Constant(v.Arg1, typeof(string))));

Assert.Equal(
Expand All @@ -645,7 +667,7 @@ public void Literal_with_two_parameter_static_factory()
v => Expression.Call(
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.StaticCreate),
new[] { typeof(string), typeof(int?) }),
new[] { typeof(string), typeof(int?) })!,
Expression.Constant(v.Arg1, typeof(string)),
Expression.Constant(v.Arg2, typeof(int?))));

Expand All @@ -662,7 +684,7 @@ public void Literal_with_parameterless_instance_factory()
Expression.New(typeof(SimpleTestTypeFactory)),
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.Create),
new Type[0])));
new Type[0])!));

Assert.Equal(
"new Microsoft.EntityFrameworkCore.Design.Internal.SimpleTestTypeFactory().Create()",
Expand All @@ -678,7 +700,7 @@ public void Literal_with_one_parameter_instance_factory()
Expression.New(typeof(SimpleTestTypeFactory)),
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.Create),
new[] { typeof(string) }),
new[] { typeof(string) })!,
Expression.Constant(v.Arg1, typeof(string))),
typeof(SimpleTestType)));

Expand All @@ -694,11 +716,11 @@ public void Literal_with_two_parameter_instance_factory()
v => Expression.Convert(
Expression.Call(
Expression.New(
typeof(SimpleTestTypeFactory).GetConstructor(new[] { typeof(string) }),
typeof(SimpleTestTypeFactory).GetConstructor(new[] { typeof(string) })!,
Expression.Constant("4096", typeof(string))),
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.Create),
new[] { typeof(string), typeof(int?) }),
new[] { typeof(string), typeof(int?) })!,
Expression.Constant(v.Arg1, typeof(string)),
Expression.Constant(v.Arg2, typeof(int?))),
typeof(SimpleTestType)));
Expand All @@ -715,11 +737,11 @@ public void Literal_with_two_parameter_instance_factory_and_internal_cast()
v => Expression.Convert(
Expression.Call(
Expression.New(
typeof(SimpleTestTypeFactory).GetConstructor(new[] { typeof(string) }),
typeof(SimpleTestTypeFactory).GetConstructor(new[] { typeof(string) })!,
Expression.Constant("4096", typeof(string))),
typeof(SimpleTestTypeFactory).GetMethod(
nameof(SimpleTestTypeFactory.Create),
new[] { typeof(string), typeof(int?) }),
new[] { typeof(string), typeof(int?) })!,
Expression.Constant(v.Arg1, typeof(string)),
Expression.Convert(
Expression.Constant(v.Arg2, typeof(int)),
Expand All @@ -735,7 +757,7 @@ public void Literal_with_two_parameter_instance_factory_and_internal_cast()
public void Literal_with_static_field()
{
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
v => Expression.Field(null, typeof(SimpleTestType).GetField(nameof(SimpleTestType.SomeStaticField))));
v => Expression.Field(null, typeof(SimpleTestType).GetField(nameof(SimpleTestType.SomeStaticField))!));

Assert.Equal(
"Microsoft.EntityFrameworkCore.Design.Internal.SimpleTestType.SomeStaticField",
Expand All @@ -746,7 +768,7 @@ public void Literal_with_static_field()
public void Literal_with_static_property()
{
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
v => Expression.Property(null, typeof(SimpleTestType).GetProperty(nameof(SimpleTestType.SomeStaticProperty))));
v => Expression.Property(null, typeof(SimpleTestType).GetProperty(nameof(SimpleTestType.SomeStaticProperty))!));

Assert.Equal(
"Microsoft.EntityFrameworkCore.Design.Internal.SimpleTestType.SomeStaticProperty",
Expand All @@ -759,7 +781,7 @@ public void Literal_with_instance_property()
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
v => Expression.Property(
Expression.New(typeof(SimpleTestType)),
typeof(SimpleTestType).GetProperty(nameof(SimpleTestType.SomeInstanceProperty))));
typeof(SimpleTestType).GetProperty(nameof(SimpleTestType.SomeInstanceProperty))!));

Assert.Equal(
"new Microsoft.EntityFrameworkCore.Design.Internal.SimpleTestType().SomeInstanceProperty",
Expand Down Expand Up @@ -797,8 +819,7 @@ public void Literal_with_unsupported_node_throws()

private IRelationalTypeMappingSource TypeMappingSource { get; } = CreateTypeMappingSource();

private static SqlServerTypeMappingSource CreateTypeMappingSource<T>(
Func<T, Expression> literalExpressionFunc)
private static SqlServerTypeMappingSource CreateTypeMappingSource<T>(Func<T, Expression>? literalExpressionFunc)
=> CreateTypeMappingSource(new TestTypeMappingPlugin<T>(literalExpressionFunc));

private static SqlServerTypeMappingSource CreateTypeMappingSource(
Expand All @@ -810,9 +831,9 @@ private static SqlServerTypeMappingSource CreateTypeMappingSource(

private class TestTypeMappingPlugin<T> : IRelationalTypeMappingSourcePlugin
{
private readonly Func<T, Expression> _literalExpressionFunc;
private readonly Func<T, Expression>? _literalExpressionFunc;

public TestTypeMappingPlugin(Func<T, Expression> literalExpressionFunc)
public TestTypeMappingPlugin(Func<T, Expression>? literalExpressionFunc)
{
_literalExpressionFunc = literalExpressionFunc;
}
Expand Down Expand Up @@ -855,7 +876,7 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
private static readonly MethodInfo _testFuncMethodInfo
= typeof(CSharpHelperTest).GetRuntimeMethod(
nameof(TestFunc),
new[] { typeof(object), typeof(object), typeof(object), typeof(object) });
new[] { typeof(object), typeof(object), typeof(object), typeof(object) })!;

public static void TestFunc(object builder, object o1, object o2, object o3)
=> throw new NotSupportedException();
Expand Down Expand Up @@ -883,7 +904,7 @@ public SimpleTestType(string arg1, int? arg2)
Arg2 = arg2;
}

public string Arg1 { get; }
public string Arg1 { get; } = null!;
public int? Arg2 { get; }
}

Expand All @@ -898,7 +919,7 @@ public SimpleTestTypeFactory(string factoryArg)
FactoryArg = factoryArg;
}

public string FactoryArg { get; }
public string FactoryArg { get; } = null!;

public SimpleTestType Create()
=> new();
Expand Down

0 comments on commit db4f92c

Please sign in to comment.