From da8f1eac7f38d68d81d46ae7883ac06ccac66bb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 15:21:28 +0000 Subject: [PATCH 1/3] Initial plan From c0b8ab6009c1aaeeb14682c0ef27e681613a6ff1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 15:27:34 +0000 Subject: [PATCH 2/3] Fix null-coalesce typing for ignored conditional access --- ...onSyntaxRewriter.NullConditionalRewrite.cs | 35 ++++++++++++++++++- ...eConversion_WithIgnoreSupport.verified.txt | 15 ++++++++ .../NullableTests.cs | 28 +++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.NullConditionalNullCoalesceTypeConversion_WithIgnoreSupport.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs b/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs index 2810b4f..31c5697 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs @@ -26,7 +26,25 @@ internal partial class ExpressionSyntaxRewriter else if (_nullConditionalRewriteSupport is NullConditionalRewriteSupport.Ignore) { // Ignore the conditional access and simply visit the WhenNotNull expression - return Visit(node.WhenNotNull); + var rewrittenExpression = (ExpressionSyntax?)Visit(node.WhenNotNull); + if (rewrittenExpression is null) + { + return null; + } + + var typeInfo = _semanticModel.GetTypeInfo(node); + if (IsCoalesceLeftOperand(node) && + typeInfo.ConvertedType is INamedTypeSymbol { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullableType && + _semanticModel.GetTypeInfo(node.WhenNotNull).Type is { IsValueType: true } rewrittenType && + SymbolEqualityComparer.Default.Equals(nullableType.TypeArguments[0], rewrittenType)) + { + return SyntaxFactory.CastExpression( + SyntaxFactory.ParseTypeName(nullableType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), + SyntaxFactory.ParenthesizedExpression(rewrittenExpression) + ); + } + + return rewrittenExpression; } else if (_nullConditionalRewriteSupport is NullConditionalRewriteSupport.Rewrite) @@ -105,4 +123,19 @@ private static bool IsNullableValueType(ITypeSymbol? type) return type is { IsValueType: true } && type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; } + + private static bool IsCoalesceLeftOperand(ConditionalAccessExpressionSyntax node) + { + ExpressionSyntax current = node; + var parent = node.Parent; + + while (parent is ParenthesizedExpressionSyntax parenthesizedExpression) + { + current = parenthesizedExpression; + parent = parenthesizedExpression.Parent; + } + + return parent is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.CoalesceExpression } coalesceExpression && + coalesceExpression.Left == current; + } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.NullConditionalNullCoalesceTypeConversion_WithIgnoreSupport.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.NullConditionalNullCoalesceTypeConversion_WithIgnoreSupport.verified.txt new file mode 100644 index 0000000..caa6ad7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.NullConditionalNullCoalesceTypeConversion_WithIgnoreSupport.verified.txt @@ -0,0 +1,15 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class _Product_GetCost + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Product @this) => (decimal? )(@this.Price.Amount) ?? 0m; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.cs index 00b601d..9d5af33 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/NullableTests.cs @@ -488,6 +488,34 @@ class Foo { return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + [Fact] + public Task NullConditionalNullCoalesceTypeConversion_WithIgnoreSupport() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +public class Price { + public decimal Amount { get; set; } +} + +public class Product { + public Price? Price { get; set; } + + [Projectable(AllowBlockBody = true, NullConditionalRewriteSupport = NullConditionalRewriteSupport.Ignore)] + public decimal GetCost() { + return Price?.Amount ?? 0m; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + [Fact] public Task NullableValueType_MemberAccess_WithRewriteSupport_IsBeingRewritten() { From 8d81f5d432502c41bcbf00acfa3d76a785eaf6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabien=20M=C3=A9nager?= Date: Thu, 28 May 2026 18:18:56 +0200 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../ExpressionSyntaxRewriter.NullConditionalRewrite.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs b/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs index 31c5697..e662c29 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/ExpressionSyntaxRewriter.NullConditionalRewrite.cs @@ -33,10 +33,14 @@ internal partial class ExpressionSyntaxRewriter } var typeInfo = _semanticModel.GetTypeInfo(node); + var whenNotNullType = _semanticModel.GetTypeInfo(node.WhenNotNull).Type; if (IsCoalesceLeftOperand(node) && typeInfo.ConvertedType is INamedTypeSymbol { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullableType && - _semanticModel.GetTypeInfo(node.WhenNotNull).Type is { IsValueType: true } rewrittenType && - SymbolEqualityComparer.Default.Equals(nullableType.TypeArguments[0], rewrittenType)) + whenNotNullType is { IsValueType: true } rewrittenType && + ( + SymbolEqualityComparer.Default.Equals(nullableType.TypeArguments[0], rewrittenType) || + SymbolEqualityComparer.Default.Equals(nullableType, rewrittenType) + )) { return SyntaxFactory.CastExpression( SyntaxFactory.ParseTypeName(nullableType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),