Skip to content

Commit dbac58b

Browse files
authored
Fix implicit new() expression not generating valid code in Projectables
1 parent 455e9b9 commit dbac58b

4 files changed

Lines changed: 147 additions & 0 deletions

File tree

src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,37 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
298298
).WithTriviaFrom(node);
299299
}
300300

301+
public override SyntaxNode? VisitImplicitObjectCreationExpression(ImplicitObjectCreationExpressionSyntax node)
302+
{
303+
// Target-typed new expressions (new() { ... }) are not supported in expression trees.
304+
// Resolve the concrete type from the semantic model and rewrite to an explicit new T() { ... }.
305+
var typeInfo = _semanticModel.GetTypeInfo(node);
306+
if (typeInfo.Type is INamedTypeSymbol namedType)
307+
{
308+
var typeName = SyntaxFactory.ParseTypeName(
309+
namedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
310+
.WithLeadingTrivia(SyntaxFactory.Space);
311+
312+
var visitedArgumentList = node.ArgumentList is not null
313+
? (ArgumentListSyntax?)Visit(node.ArgumentList)
314+
: SyntaxFactory.ArgumentList();
315+
316+
var visitedInitializer = node.Initializer is not null
317+
? (InitializerExpressionSyntax?)Visit(node.Initializer)
318+
: null;
319+
320+
return SyntaxFactory.ObjectCreationExpression(
321+
node.NewKeyword,
322+
typeName,
323+
visitedArgumentList ?? SyntaxFactory.ArgumentList(),
324+
visitedInitializer
325+
).WithLeadingTrivia(node.GetLeadingTrivia())
326+
.WithTrailingTrivia(node.GetTrailingTrivia());
327+
}
328+
329+
return base.VisitImplicitObjectCreationExpression(node);
330+
}
331+
301332
public override SyntaxNode? VisitIsPatternExpression(IsPatternExpressionSyntax node)
302333
{
303334
// Pattern matching is not supported in expression trees (CS8122).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using EntityFrameworkCore.Projectables;
4+
using Foo;
5+
6+
namespace EntityFrameworkCore.Projectables.Generated
7+
{
8+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
9+
static class Foo_Dest_From_P0_Foo_Source
10+
{
11+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Source, global::Foo.Dest>> Expression()
12+
{
13+
return (global::Foo.Source s) => new global::Foo.Dest()
14+
{
15+
Name = s.Name,
16+
Value = s.Value,
17+
};
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// <auto-generated/>
2+
#nullable disable
3+
using EntityFrameworkCore.Projectables;
4+
using Foo;
5+
6+
namespace EntityFrameworkCore.Projectables.Generated
7+
{
8+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
9+
static class Foo_Dest_From_P0_Foo_Source
10+
{
11+
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.Source, global::Foo.Dest>> Expression()
12+
{
13+
return (global::Foo.Source s) => new global::Foo.Dest()
14+
{
15+
Name = s.Name,
16+
Status = s.IsActive ? "Active" : "Inactive",
17+
Code = s.Detail.Code,
18+
};
19+
}
20+
}
21+
}

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,4 +799,79 @@ class Bar {
799799

800800
return Verifier.Verify(result.GeneratedTrees[0].ToString());
801801
}
802+
803+
[Fact]
804+
public Task ImplicitNewExpression_SimpleObjectInitializer_RewritesToExplicitType()
805+
{
806+
var compilation = CreateCompilation(@"
807+
using EntityFrameworkCore.Projectables;
808+
809+
namespace Foo {
810+
class Source {
811+
public string Name { get; set; }
812+
public int Value { get; set; }
813+
}
814+
815+
class Dest {
816+
public string Name { get; set; }
817+
public int Value { get; set; }
818+
819+
[Projectable]
820+
public static Dest From(Source s) => new()
821+
{
822+
Name = s.Name,
823+
Value = s.Value,
824+
};
825+
}
826+
}
827+
");
828+
829+
var result = RunGenerator(compilation);
830+
831+
Assert.Single(result.Diagnostics.Where(d => d.Id == "EFP0012")); // factory method hint
832+
Assert.Single(result.GeneratedTrees);
833+
834+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
835+
}
836+
837+
[Fact]
838+
public Task ImplicitNewExpression_WithTernaryAndNullConditional_RewritesToExplicitType()
839+
{
840+
var compilation = CreateCompilation(@"
841+
using EntityFrameworkCore.Projectables;
842+
843+
namespace Foo {
844+
class Inner {
845+
public string Code { get; set; }
846+
}
847+
848+
class Source {
849+
public string Name { get; set; }
850+
public bool IsActive { get; set; }
851+
public Inner Detail { get; set; }
852+
}
853+
854+
class Dest {
855+
public string Name { get; set; }
856+
public string Status { get; set; }
857+
public string Code { get; set; }
858+
859+
[Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Ignore)]
860+
public static Dest From(Source s) => new()
861+
{
862+
Name = s.Name,
863+
Status = s.IsActive ? ""Active"" : ""Inactive"",
864+
Code = s.Detail.Code,
865+
};
866+
}
867+
}
868+
");
869+
870+
var result = RunGenerator(compilation);
871+
872+
Assert.Single(result.Diagnostics.Where(d => d.Id == "EFP0012")); // factory method hint
873+
Assert.Single(result.GeneratedTrees);
874+
875+
return Verifier.Verify(result.GeneratedTrees[0].ToString());
876+
}
802877
}

0 commit comments

Comments
 (0)