Skip to content

Commit 040ef66

Browse files
committed
Add support for CSharpHelper for List literals
Fixes dotnet#19274 Also relates to npgsql/efcore.pg#2402
1 parent 27a83b9 commit 040ef66

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

src/EFCore.Design/Design/Internal/CSharpHelper.cs

+99
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,71 @@ public virtual string Literal(object?[,] values)
737737
return builder.ToString();
738738
}
739739

740+
/// <summary>
741+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
742+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
743+
/// any release. You should only use it directly in your code with extreme caution and knowing that
744+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
745+
/// </summary>
746+
public virtual string Literal<T>(IList<T> values, bool vertical = false)
747+
=> List(typeof(T), values, vertical);
748+
749+
private string List(Type type, IEnumerable values, bool vertical = false)
750+
{
751+
var builder = new IndentedStringBuilder();
752+
753+
builder.Append("new List<")
754+
.Append(Reference(type))
755+
.Append("> {");
756+
757+
var first = true;
758+
foreach (var value in values)
759+
{
760+
if (first)
761+
{
762+
if (vertical)
763+
{
764+
builder.AppendLine();
765+
builder.IncrementIndent();
766+
}
767+
else
768+
{
769+
builder.Append(" ");
770+
}
771+
first = false;
772+
}
773+
else
774+
{
775+
builder.Append(",");
776+
777+
if (vertical)
778+
{
779+
builder.AppendLine();
780+
}
781+
else
782+
{
783+
builder.Append(" ");
784+
}
785+
}
786+
787+
builder.Append(UnknownLiteral(value));
788+
}
789+
790+
if (vertical)
791+
{
792+
builder.AppendLine();
793+
builder.DecrementIndent();
794+
}
795+
else
796+
{
797+
builder.Append(" ");
798+
}
799+
800+
builder.Append("}");
801+
802+
return builder.ToString();
803+
}
804+
740805
/// <summary>
741806
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
742807
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -844,6 +909,11 @@ public virtual string UnknownLiteral(object? value)
844909
return Array(literalType.GetElementType()!, array);
845910
}
846911

912+
if (value is IList list && value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>))
913+
{
914+
return List(value.GetType().GetGenericArguments()[0], list);
915+
}
916+
847917
var mapping = _typeMappingSource.FindMapping(literalType);
848918
if (mapping != null)
849919
{
@@ -878,6 +948,35 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool
878948

879949
HandleList(((NewArrayExpression)expression).Expressions, builder, simple: true);
880950

951+
builder
952+
.Append(" }");
953+
954+
return true;
955+
case ExpressionType.ListInit:
956+
var listExpr = (ListInitExpression)expression;
957+
if (listExpr.Initializers.Any(i => i.Arguments.Count != 1))
958+
{
959+
// If there is an initializer with more than one argument we can't make a literal cleanly
960+
return false;
961+
}
962+
963+
builder
964+
.Append("new ")
965+
.Append(Reference(expression.Type));
966+
967+
if (listExpr.NewExpression.Arguments.Count > 0 && !HandleArguments(listExpr.NewExpression.Arguments, builder))
968+
{
969+
return false;
970+
}
971+
972+
builder
973+
.Append(" { ");
974+
975+
if (!HandleList(listExpr.Initializers.Select(_ => _.Arguments.First()), builder, simple: true))
976+
{
977+
return false;
978+
}
979+
881980
builder
882981
.Append(" }");
883982

test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs

+45
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ public void Literal_works_when_many_ByteArray()
121121
new byte[] { 1, 2 },
122122
"new byte[] { 1, 2 }");
123123

124+
[ConditionalFact]
125+
public void Literal_works_when_empty_list()
126+
=> Literal_works(
127+
new List<string>(),
128+
@"new List<string> { }");
129+
130+
[ConditionalFact]
131+
public void Literal_works_when_list_without_ctor_arguments()
132+
=> Literal_works(
133+
new List<string> { "one", "two" },
134+
@"new List<string> { ""one"", ""two"" }");
135+
136+
[ConditionalFact]
137+
public void Literal_works_when_list_with_ctor_arguments()
138+
=> Literal_works(
139+
new List<string>(new [] { "one" }) { "two", "three" },
140+
@"new List<string> { ""one"", ""two"", ""three"" }");
141+
124142
[ConditionalFact]
125143
public void Literal_works_when_multiline_string()
126144
=> Literal_works(
@@ -607,6 +625,33 @@ public void Literal_with_add()
607625
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
608626
}
609627

628+
[ConditionalFact]
629+
public void Literal_with_list_of_string_init_no_arguments()
630+
{
631+
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
632+
v => Expression.ListInit(
633+
Expression.New(typeof(List<>).MakeGenericType(typeof(string))),
634+
Expression.Constant("one"), Expression.Constant("two")));
635+
636+
Assert.Equal(
637+
@"new List<string> { ""one"", ""two"" }",
638+
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
639+
}
640+
641+
[ConditionalFact]
642+
public void Literal_with_list_of_string_init_with_arguments()
643+
{
644+
var constructor = typeof(List<>).MakeGenericType((typeof(string))).GetConstructor(new[] { typeof(IEnumerable<string>) })!;
645+
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
646+
v => Expression.ListInit(
647+
Expression.New(constructor, Expression.NewArrayInit(typeof(string), Expression.Constant("one"))),
648+
Expression.Constant("one"), Expression.Constant("two")));
649+
650+
Assert.Equal(
651+
@"new List<string>(new string[] { ""one"" }) { ""one"", ""two"" }",
652+
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
653+
}
654+
610655
[ConditionalFact]
611656
public void Literal_with_unsupported_node_throws()
612657
{

0 commit comments

Comments
 (0)