From 8c611b9fd4515fb995a871e5217430c028c84763 Mon Sep 17 00:00:00 2001 From: Koen Bekkenutte <2912652+kbekkenutte@users.noreply.github.com> Date: Mon, 18 Oct 2021 23:40:43 +0800 Subject: [PATCH] Fixed nullability rewrites with parameters and return types --- .../ExpressionSyntaxRewriter.cs | 24 ++++-- .../ParameterSyntaxRewriter.cs | 37 +++++++-- .../ProjectableInterpreter.cs | 9 +- .../ReturnTypeSyntaxRewriter.cs | 4 +- ...erenceTypesAreBeingEliminated.verified.txt | 15 ++++ ...gnoreSupport_IsBeingRewritten.verified.txt | 2 +- ...writeSupport_IsBeingRewritten.verified.txt | 2 +- ...ypeCastOperatorGetsEliminated.verified.txt | 15 ++++ ...erenceTypesAreBeingEliminated.verified.txt | 4 +- ...ableValueCastOperatorsPersist.verified.txt | 15 ++++ .../ProjectionExpressionGeneratorTests.cs | 82 +++++++++++++++++++ 11 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs index ca101e7..23da059 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs @@ -126,13 +126,7 @@ namespace EntityFrameworkCore.Projectables.Generator { if (symbolInfo.Symbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod) { - if (SymbolEqualityComparer.Default.Equals(symbolInfo.Symbol.ContainingType, _targetTypeSymbol)) - { - //return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - // methodSymbol.ReducedFrom.Sym - //); - //throw new Exception("foo"); - } + } else if (symbolInfo.Symbol.Kind is SymbolKind.Property or SymbolKind.Method or SymbolKind.Field && SymbolEqualityComparer.Default.Equals(symbolInfo.Symbol.ContainingType, _targetTypeSymbol)) { @@ -178,5 +172,21 @@ namespace EntityFrameworkCore.Projectables.Generator return base.VisitQualifiedName(node); } + + public override SyntaxNode? VisitNullableType(NullableTypeSyntax node) + { + var typeInfo = _semanticModel.GetTypeInfo(node); + if (typeInfo.Type is not null) + { + if (typeInfo.Type.TypeKind is not TypeKind.Struct) + { + return Visit(node.ElementType) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(node.GetTrailingTrivia()); + } + } + + return base.VisitNullableType(node); + } } } diff --git a/src/EntityFrameworkCore.Projectables.Generator/ParameterSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ParameterSyntaxRewriter.cs index 76d2dc6..6bc2018 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ParameterSyntaxRewriter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ParameterSyntaxRewriter.cs @@ -17,25 +17,48 @@ namespace EntityFrameworkCore.Projectables.Generator public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = _semanticModel.GetDeclaredSymbol(node); + var visitedNode = base.VisitIdentifierName(node); + + var symbol = _semanticModel.GetDeclaredSymbol(visitedNode); if (symbol is not null) { - node = SyntaxFactory.IdentifierName(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + return SyntaxFactory.IdentifierName(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } - return base.VisitIdentifierName(node); + return visitedNode; } public override SyntaxNode? VisitParameter(ParameterSyntax node) { - var thisKeywordIndex = node.Modifiers.IndexOf(SyntaxKind.ThisKeyword); - if (thisKeywordIndex != -1) + var visitedNode = base.VisitParameter(node); + + if (visitedNode is ParameterSyntax visitedParameterSyntax) { - node = node.WithModifiers(node.Modifiers.RemoveAt(thisKeywordIndex)); + var thisKeywordIndex = visitedParameterSyntax.Modifiers.IndexOf(SyntaxKind.ThisKeyword); + if (thisKeywordIndex != -1) + { + return visitedParameterSyntax.WithModifiers(node.Modifiers.RemoveAt(thisKeywordIndex)); + } } - return base.VisitParameter(node); + return visitedNode; + } + + public override SyntaxNode? VisitNullableType(NullableTypeSyntax node) + { + var typeInfo = _semanticModel.GetTypeInfo(node); + if (typeInfo.Type is not null) + { + if (typeInfo.Type.TypeKind is not TypeKind.Struct) + { + return Visit(node.ElementType) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(node.GetTrailingTrivia()); + } + } + + return base.VisitNullableType(node); } } } diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index 7e76686..df7a1a1 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -100,13 +100,10 @@ namespace EntityFrameworkCore.Projectables.Generator return null; } - var returnTypeSymbol = semanticModel.GetSymbolInfo(returnTypeSyntaxRewriter.Visit(methodDeclarationSyntax.ReturnType)).Symbol; - if (returnTypeSymbol is null) - { - return null; - } + var returnType = returnTypeSyntaxRewriter.Visit(methodDeclarationSyntax.ReturnType); + - descriptor.ReturnTypeName = returnTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + descriptor.ReturnTypeName = returnType.ToString(); descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression); foreach (var additionalParameter in ((ParameterListSyntax)parameterSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList)).Parameters) { diff --git a/src/EntityFrameworkCore.Projectables.Generator/ReturnTypeSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ReturnTypeSyntaxRewriter.cs index 2e0c90b..eca178f 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ReturnTypeSyntaxRewriter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ReturnTypeSyntaxRewriter.cs @@ -25,7 +25,9 @@ namespace EntityFrameworkCore.Projectables.Generator { if (typeInfo.Type.TypeKind is not TypeKind.Struct) { - return Visit(node.ElementType); + return Visit(node.ElementType) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(node.GetTrailingTrivia()); } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt new file mode 100644 index 0000000..8c7c916 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +#nullable disable +{ + public static class Foo_C_NextFoo + { + public static System.Linq.Expressions.Expression ,List, List>> Expression => + (List input,List nullablePrimitiveArgument) => input; + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt index 5d463d7..614f02b 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt @@ -9,7 +9,7 @@ namespace EntityFrameworkCore.Projectables.Generated { public static class Foo_EntityExtensions_GetFirstRelatedIgnoreNulls { - public static System.Linq.Expressions.Expression> Expression => + public static System.Linq.Expressions.Expression> Expression => (Entity entity) => entity.RelatedEntities[0]; } } \ 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 a25a92e..1d1c6d0 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 @@ -9,7 +9,7 @@ namespace EntityFrameworkCore.Projectables.Generated { public static class Foo_EntityExtensions_GetFirstRelatedIgnoreNulls { - public static System.Linq.Expressions.Expression> Expression => + public static System.Linq.Expressions.Expression> Expression => (Entity entity) => entity != null ? (entity.RelatedEntities != null ? (entity.RelatedEntities[0]) : null) : null; } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt new file mode 100644 index 0000000..b8d948b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +#nullable disable +{ + public static class Foo_C_NullableReferenceType + { + public static System.Linq.Expressions.Expression> Expression => + (object input) => (string)input; + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt index 1ccc9af..7995003 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt @@ -8,7 +8,7 @@ namespace EntityFrameworkCore.Projectables.Generated { public static class Foo_C_NextFoo { - public static System.Linq.Expressions.Expression> Expression => - (object? unusedArgument,int? nullablePrimitiveArgument) => null; + public static System.Linq.Expressions.Expression> Expression => + (object unusedArgument,int? nullablePrimitiveArgument) => null; } } \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt new file mode 100644 index 0000000..e9aa28a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +#nullable disable +{ + public static class Foo_C_NullableValueType + { + public static System.Linq.Expressions.Expression> Expression => + (object input) => (int?)input; + } +} \ 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 4c5b777..e1a8c68 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -487,6 +487,88 @@ namespace Foo { return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + [Fact] + public Task GenericNullableReferenceTypesAreBeingEliminated() + { + var compilation = CreateCompilation(@" +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; + +#nullable enable + +namespace Foo { + static class C { + [Projectable] + public static List NextFoo(this List input, List nullablePrimitiveArgument) => input; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task NullableReferenceTypeCastOperatorGetsEliminated() + { + var compilation = CreateCompilation(@" +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; + +#nullable enable + +namespace Foo { + static class C { + [Projectable] + public static string? NullableReferenceType(object? input) => (string?)input; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task NullableValueCastOperatorsPersist() + { + var compilation = CreateCompilation(@" +using System; +using System.Collections.Generic; +using System.Linq; +using EntityFrameworkCore.Projectables; + +#nullable enable + +namespace Foo { + static class C { + [Projectable] + public static int? NullableValueType(object? input) => (int?)input; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] public void NullableMemberBinding_WithoutSupport_IsBeingReported() {