Skip to content

Commit 5f9c860

Browse files
authored
Merge pull request #146 from koenbeuk/handle-dictionary-index-initializer
Add support for dictionary index initializers in expressions
2 parents 3d128d8 + 08d5acf commit 5f9c860

File tree

4 files changed

+186
-1
lines changed

4 files changed

+186
-1
lines changed

src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,63 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
385385

386386
return base.VisitNullableType(node);
387387
}
388-
388+
389+
public override SyntaxNode? VisitInitializerExpression(InitializerExpressionSyntax node)
390+
{
391+
// Only handle object initializers that might contain indexer assignments
392+
if (!node.IsKind(SyntaxKind.ObjectInitializerExpression))
393+
{
394+
return base.VisitInitializerExpression(node);
395+
}
396+
397+
// Check if any expression is an indexer assignment (e.g., ["key"] = value)
398+
var hasIndexerAssignment = node.Expressions.Any(e =>
399+
e is AssignmentExpressionSyntax { Left: ImplicitElementAccessSyntax });
400+
401+
if (!hasIndexerAssignment)
402+
{
403+
return base.VisitInitializerExpression(node);
404+
}
405+
406+
var newExpressions = new SeparatedSyntaxList<ExpressionSyntax>();
407+
408+
foreach (var expression in node.Expressions)
409+
{
410+
if (expression is AssignmentExpressionSyntax assignment &&
411+
assignment.Left is ImplicitElementAccessSyntax implicitElementAccess)
412+
{
413+
// Transform ["key"] = value into { "key", value }
414+
var arguments = new SeparatedSyntaxList<ExpressionSyntax>();
415+
416+
foreach (var argument in implicitElementAccess.ArgumentList.Arguments)
417+
{
418+
var visitedArgument = (ExpressionSyntax?)Visit(argument.Expression) ?? argument.Expression;
419+
arguments = arguments.Add(visitedArgument);
420+
}
421+
422+
var visitedValue = (ExpressionSyntax?)Visit(assignment.Right) ?? assignment.Right;
423+
arguments = arguments.Add(visitedValue);
424+
425+
var complexElementInitializer = SyntaxFactory.InitializerExpression(
426+
SyntaxKind.ComplexElementInitializerExpression,
427+
arguments
428+
);
429+
430+
newExpressions = newExpressions.Add(complexElementInitializer);
431+
}
432+
else
433+
{
434+
var visitedExpression = (ExpressionSyntax?)Visit(expression) ?? expression;
435+
newExpressions = newExpressions.Add(visitedExpression);
436+
}
437+
}
438+
439+
return SyntaxFactory.InitializerExpression(
440+
SyntaxKind.CollectionInitializerExpression,
441+
newExpressions
442+
).WithTriviaFrom(node);
443+
}
444+
389445
private ExpressionSyntax ReplaceVariableWithCast(ExpressionSyntax expression, DeclarationPatternSyntax declaration, ExpressionSyntax governingExpression)
390446
{
391447
if (declaration.Designation is SingleVariableDesignationSyntax variableDesignation)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using System;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
using EntityFrameworkCore.Projectables;
7+
using Foo;
8+
9+
namespace EntityFrameworkCore.Projectables.Generated
10+
{
11+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
12+
static class Foo_EntityExtensions_ToDictionary
13+
{
14+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.EntityExtensions.Entity, global::System.Collections.Generic.Dictionary<string, object>>> Expression()
15+
{
16+
return (global::Foo.EntityExtensions.Entity entity) => new Dictionary<string, object>
17+
{
18+
{
19+
"FullName",
20+
entity.FullName ?? "N/A"
21+
},
22+
{
23+
"Id",
24+
entity.Id.ToString()
25+
}
26+
};
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using System;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
using EntityFrameworkCore.Projectables;
7+
using Foo;
8+
9+
namespace EntityFrameworkCore.Projectables.Generated
10+
{
11+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
12+
static class Foo_EntityExtensions_ToDictionary
13+
{
14+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.EntityExtensions.Entity, global::System.Collections.Generic.Dictionary<string, string>>> Expression()
15+
{
16+
return (global::Foo.EntityExtensions.Entity entity) => new Dictionary<string, string>
17+
{
18+
{
19+
"FullName",
20+
entity.FullName ?? "N/A"
21+
}
22+
};
23+
}
24+
}
25+
}

tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,81 @@ public Task GenericTypesWithConstraints()
19011901

19021902
return Verifier.Verify(result.GeneratedTrees[0].ToString());
19031903
}
1904+
1905+
[Fact]
1906+
public Task DictionaryIndexInitializer_IsBeingRewritten()
1907+
{
1908+
// lang=csharp
1909+
var compilation = CreateCompilation(@"
1910+
using System;
1911+
using System.Linq;
1912+
using System.Collections.Generic;
1913+
using EntityFrameworkCore.Projectables;
1914+
1915+
namespace Foo {
1916+
public static class EntityExtensions
1917+
{
1918+
public record Entity
1919+
{
1920+
public int Id { get; set; }
1921+
public string? FullName { get; set; }
1922+
}
1923+
1924+
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
1925+
public static Dictionary<string, object> ToDictionary(this Entity entity)
1926+
=> new Dictionary<string, object>
1927+
{
1928+
[""FullName""] = entity.FullName ?? ""N/A"",
1929+
[""Id""] = entity.Id.ToString(),
1930+
};
1931+
}
1932+
}
1933+
");
1934+
1935+
var result = RunGenerator(compilation);
1936+
1937+
Assert.Empty(result.Diagnostics);
1938+
Assert.Single(result.GeneratedTrees);
1939+
1940+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
1941+
}
1942+
1943+
[Fact]
1944+
public Task DictionaryObjectInitializer_PreservesCollectionInitializerSyntax()
1945+
{
1946+
// lang=csharp
1947+
var compilation = CreateCompilation(@"
1948+
using System;
1949+
using System.Linq;
1950+
using System.Collections.Generic;
1951+
using EntityFrameworkCore.Projectables;
1952+
1953+
namespace Foo {
1954+
public static class EntityExtensions
1955+
{
1956+
public record Entity
1957+
{
1958+
public int Id { get; set; }
1959+
public string? FullName { get; set; }
1960+
}
1961+
1962+
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
1963+
public static Dictionary<string, string> ToDictionary(this Entity entity)
1964+
=> new Dictionary<string, string>
1965+
{
1966+
{ ""FullName"", entity.FullName ?? ""N/A"" }
1967+
};
1968+
}
1969+
}
1970+
");
1971+
1972+
var result = RunGenerator(compilation);
1973+
1974+
Assert.Empty(result.Diagnostics);
1975+
Assert.Single(result.GeneratedTrees);
1976+
1977+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
1978+
}
19041979

19051980
#region Helpers
19061981

0 commit comments

Comments
 (0)