From 3e2bf5c6aece22ff75503348562d3297b60614be Mon Sep 17 00:00:00 2001 From: Koen Date: Sun, 6 Nov 2022 15:59:01 +0000 Subject: [PATCH] Support conditional access rewriting for value types --- .../ExpressionSyntaxRewriter.cs | 58 +++++++++++-------- ...nalNullCoalesceTypeConversion.verified.txt | 16 +++++ ...writeSupport_IsBeingRewritten.verified.txt | 2 +- ...writeSupport_IsBeingRewritten.verified.txt | 2 +- ...writeSupport_IsBeingRewritten.verified.txt | 2 +- ...writeSupport_IsBeingRewritten.verified.txt | 2 +- .../ProjectionExpressionGeneratorTests.cs | 24 ++++++++ 7 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs index ab86497..0c53cc2 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs @@ -74,28 +74,40 @@ namespace EntityFrameworkCore.Projectables.Generator _context.ReportDiagnostic(diagnostic); } - return _nullConditionalRewriteSupport switch { - NullConditionalRewriteSupport.Ignore => Visit(node.WhenNotNull), - NullConditionalRewriteSupport.Rewrite => - SyntaxFactory.ConditionalExpression( + else if (_nullConditionalRewriteSupport is NullConditionalRewriteSupport.Ignore) + { + // Ignore the conditional accesss and simply visit the WhenNotNull expression + return Visit(node.WhenNotNull); + } + + else if (_nullConditionalRewriteSupport is NullConditionalRewriteSupport.Rewrite) + { + var whenNotNullSymbol = _semanticModel.GetSymbolInfo(node.WhenNotNull).Symbol as IPropertySymbol; + var typeInfo = _semanticModel.GetTypeInfo(node); + + // Do not translate until we can resolve the target type + if (typeInfo.ConvertedType is not null) + { + // Translate null-conditional into a conditional expression + return SyntaxFactory.ConditionalExpression( SyntaxFactory.BinaryExpression( SyntaxKind.NotEqualsExpression, - targetExpression - .WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), + targetExpression.WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) + ).WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), + SyntaxFactory.ParenthesizedExpression( + (ExpressionSyntax)Visit(node.WhenNotNull) + ).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")).WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), + SyntaxFactory.CastExpression( + SyntaxFactory.ParseName(typeInfo.ConvertedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression) - .WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) - ) - .WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), - SyntaxFactory.ParenthesizedExpression( - (ExpressionSyntax)Visit(node.WhenNotNull) - ) - .WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) - .WithTrailingTrivia(SyntaxFactory.Whitespace(" ")), - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression) - .WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) - ), - _ => base.VisitConditionalAccessExpression(node) - }; + ).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) + ).WithLeadingTrivia(node.GetLeadingTrivia()).WithTrailingTrivia(node.GetTrailingTrivia()); + } + } + + return base.VisitConditionalAccessExpression(node); + } public override SyntaxNode? VisitMemberBindingExpression(MemberBindingExpressionSyntax node) @@ -147,15 +159,13 @@ namespace EntityFrameworkCore.Projectables.Generator var symbol = _semanticModel.GetSymbolInfo(node).Symbol; if (symbol is not null) { - var operation = node switch { - { Parent: { } parent } when parent.IsKind(SyntaxKind.InvocationExpression) => _semanticModel.GetOperation(node.Parent), + var operation = node switch { { Parent: { } parent } when parent.IsKind(SyntaxKind.InvocationExpression) => _semanticModel.GetOperation(node.Parent), _ => _semanticModel.GetOperation(node!) }; if (operation is IMemberReferenceOperation memberReferenceOperation) { - var memberAccessCanBeQualified = node switch { - { Parent: { Parent: { } parent } } when parent.IsKind(SyntaxKind.ObjectInitializerExpression) => false, + var memberAccessCanBeQualified = node switch { { Parent: { Parent: { } parent } } when parent.IsKind(SyntaxKind.ObjectInitializerExpression) => false, _ => true }; @@ -169,7 +179,7 @@ namespace EntityFrameworkCore.Projectables.Generator node.WithoutLeadingTrivia() ).WithLeadingTrivia(node.GetLeadingTrivia()); } - + // if this operation is targeting a static member on our targetType implicitly if (memberReferenceOperation.Instance is null && SymbolEqualityComparer.Default.Equals(memberReferenceOperation.Member.ContainingType, _targetTypeSymbol)) { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt new file mode 100644 index 0000000..f4b5dcf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt @@ -0,0 +1,16 @@ +// +using EntityFrameworkCore.Projectables; + +namespace EntityFrameworkCore.Projectables.Generated +#nullable disable +{ + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static class _Foo_SomeNumber + { + public static System.Linq.Expressions.Expression> Expression() + { + return (global::Foo fancyClass) => + fancyClass != null ? (fancyClass.FancyNumber ) : (int?)null ?? 3; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 85e8d58..4535132 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt @@ -14,7 +14,7 @@ namespace EntityFrameworkCore.Projectables.Generated public static System.Linq.Expressions.Expression> Expression() { return (global::Foo.EntityExtensions.Entity entity) => - entity != null ? (entity.RelatedEntities != null ? (entity.RelatedEntities[0]) : null) : null; + entity != null ? (entity.RelatedEntities != null ? (entity.RelatedEntities[0]) : (global::Foo.EntityExtensions.Entity)null) : (global::Foo.EntityExtensions.Entity)null; } } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 3d55c17..e3d5466 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt @@ -13,7 +13,7 @@ namespace EntityFrameworkCore.Projectables.Generated public static System.Linq.Expressions.Expression> Expression() { return (string input) => - input != null ? (input.Length) : null; + input != null ? (input.Length) : (int?)null; } } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt index ce9d542..40a4027 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt @@ -14,7 +14,7 @@ namespace EntityFrameworkCore.Projectables.Generated public static System.Linq.Expressions.Expression> Expression() { return (global::Foo.EntityExtensions.Entity entity) => - entity.FullName != null ? (entity.FullName.Substring(entity.FullName != null ? (entity.FullName.IndexOf(' ') ) : null?? 0)) : null; + entity.FullName != null ? (entity.FullName.Substring(entity.FullName != null ? (entity.FullName.IndexOf(' ') ) : (int?)null ?? 0)) : (string)null; } } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 6c58d92..5a3c009 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt @@ -13,7 +13,7 @@ namespace EntityFrameworkCore.Projectables.Generated public static System.Linq.Expressions.Expression> Expression() { return (string input) => - input != null ? (input[0]) : null; + input != null ? (input[0]) : (char?)null; } } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 10a6cec..e75ec62 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -1542,6 +1542,30 @@ namespace One.Two { return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + [Fact] + public Task NullConditionalNullCoalesceTypeConversion() + { + // issue: https://github.com/koenbeuk/EntityFrameworkCore.Projectables/issues/48 + + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +class Foo { + public int? FancyNumber { get; set; } + + [Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)] + public static int SomeNumber(Foo fancyClass) => fancyClass?.FancyNumber ?? 3; +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + #region Helpers Compilation CreateCompilation(string source, bool expectedToCompile = true)