Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support value tuples in CSharpHelper #29384

Merged
merged 1 commit into from
Oct 18, 2022
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
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