From ef840f69385b4da55e0b43c31488be8bcfa47bff Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 14 Feb 2026 11:26:52 +0000
Subject: [PATCH 01/13] Initial plan
From 8468dc516a7faee3e87eaf4974abbc00108224a1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 14 Feb 2026 11:36:26 +0000
Subject: [PATCH 02/13] Add support for block-bodied methods with common
statements
- Created BlockStatementConverter to transform block bodies to expressions
- Added support for simple return statements
- Added support for if-else statements (converted to ternary)
- Added support for local variable declarations (inlined)
- Added diagnostics for unsupported statements (EFP0003)
- Added comprehensive test cases
- Updated existing test that expected block methods to fail
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
.../AnalyzerReleases.Unshipped.md | 1 +
.../BlockStatementConverter.cs | 222 ++++++++++++++++
.../Diagnostics.cs | 8 +
.../ProjectableInterpreter.cs | 30 ++-
...lockBodiedMethod_SimpleReturn.verified.txt | 17 ++
...upportedStatement_WithoutElse.verified.txt | 3 +
....BlockBodiedMethod_WithIfElse.verified.txt | 17 ++
...Method_WithIfElseAndCondition.verified.txt | 17 ++
...odiedMethod_WithLocalVariable.verified.txt | 17 ++
...Method_WithMultipleParameters.verified.txt | 17 ++
...BodiedMethod_WithNestedIfElse.verified.txt | 17 ++
...diedMethod_WithPropertyAccess.verified.txt | 17 ++
.../ProjectionExpressionGeneratorTests.cs | 250 +++++++++++++++++-
13 files changed, 628 insertions(+), 5 deletions(-)
create mode 100644 src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SimpleReturn.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElse.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElseAndCondition.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithLocalVariable.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithNestedIfElse.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithPropertyAccess.verified.txt
diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md
index ef168b8..4911eaa 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md
+++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md
@@ -3,3 +3,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
EFP0002 | Design | Error |
+EFP0003 | Design | Warning |
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
new file mode 100644
index 0000000..7c000af
--- /dev/null
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -0,0 +1,222 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EntityFrameworkCore.Projectables.Generator
+{
+ ///
+ /// Converts block-bodied methods to expression syntax that can be used in expression trees.
+ /// Only supports a subset of C# statements.
+ ///
+ public class BlockStatementConverter
+ {
+ private readonly SemanticModel _semanticModel;
+ private readonly SourceProductionContext _context;
+ private readonly ExpressionSyntaxRewriter _expressionRewriter;
+ private readonly Dictionary _localVariables = new();
+
+ public BlockStatementConverter(SemanticModel semanticModel, SourceProductionContext context, ExpressionSyntaxRewriter expressionRewriter)
+ {
+ _semanticModel = semanticModel;
+ _context = context;
+ _expressionRewriter = expressionRewriter;
+ }
+
+ ///
+ /// Attempts to convert a block statement into a single expression.
+ /// Returns null if the block contains unsupported statements.
+ ///
+ public ExpressionSyntax? TryConvertBlock(BlockSyntax block, string memberName)
+ {
+ if (block == null || block.Statements.Count == 0)
+ {
+ return null;
+ }
+
+ // Try to convert the block statements into an expression
+ var result = TryConvertStatements(block.Statements.ToList(), memberName);
+ return result;
+ }
+
+ private ExpressionSyntax? TryConvertStatements(List statements, string memberName)
+ {
+ if (statements.Count == 0)
+ {
+ return null;
+ }
+
+ if (statements.Count == 1)
+ {
+ return TryConvertStatement(statements[0], memberName);
+ }
+
+ // Multiple statements - try to convert them into a chain of expressions
+ // This is done by converting local variable declarations and then the final return
+ var nonReturnStatements = statements.Take(statements.Count - 1).ToList();
+ var lastStatement = statements.Last();
+
+ // Process local variable declarations
+ foreach (var stmt in nonReturnStatements)
+ {
+ if (stmt is LocalDeclarationStatementSyntax localDecl)
+ {
+ if (!TryProcessLocalDeclaration(localDecl, memberName))
+ {
+ return null;
+ }
+ }
+ else
+ {
+ ReportUnsupportedStatement(stmt, memberName, "Only local variable declarations are supported before the return statement");
+ return null;
+ }
+ }
+
+ // Convert the final statement (should be a return)
+ return TryConvertStatement(lastStatement, memberName);
+ }
+
+ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDecl, string memberName)
+ {
+ foreach (var variable in localDecl.Declaration.Variables)
+ {
+ if (variable.Initializer == null)
+ {
+ ReportUnsupportedStatement(localDecl, memberName, "Local variables must have an initializer");
+ return false;
+ }
+
+ var variableName = variable.Identifier.Text;
+ // Rewrite the initializer expression NOW while it's still in the tree
+ var rewrittenInitializer = (ExpressionSyntax)_expressionRewriter.Visit(variable.Initializer.Value);
+ _localVariables[variableName] = rewrittenInitializer;
+ }
+
+ return true;
+ }
+
+ private ExpressionSyntax? TryConvertStatement(StatementSyntax statement, string memberName)
+ {
+ switch (statement)
+ {
+ case ReturnStatementSyntax returnStmt:
+ return TryConvertReturnStatement(returnStmt, memberName);
+
+ case IfStatementSyntax ifStmt:
+ return TryConvertIfStatement(ifStmt, memberName);
+
+ case BlockSyntax blockStmt:
+ return TryConvertStatements(blockStmt.Statements.ToList(), memberName);
+
+ case ExpressionStatementSyntax exprStmt:
+ // Expression statements are generally not useful in expression trees
+ ReportUnsupportedStatement(statement, memberName, "Expression statements are not supported");
+ return null;
+
+ case LocalDeclarationStatementSyntax:
+ // Local declarations should be handled before the return statement
+ ReportUnsupportedStatement(statement, memberName, "Local declarations must appear before the return statement");
+ return null;
+
+ default:
+ ReportUnsupportedStatement(statement, memberName, $"Statement type '{statement.GetType().Name}' is not supported");
+ return null;
+ }
+ }
+
+ private ExpressionSyntax? TryConvertReturnStatement(ReturnStatementSyntax returnStmt, string memberName)
+ {
+ if (returnStmt.Expression == null)
+ {
+ ReportUnsupportedStatement(returnStmt, memberName, "Return statement must have an expression");
+ return null;
+ }
+
+ // First rewrite the return expression
+ var expression = (ExpressionSyntax)_expressionRewriter.Visit(returnStmt.Expression);
+
+ // Then replace any local variable references with their already-rewritten initializers
+ expression = ReplaceLocalVariables(expression);
+
+ return expression;
+ }
+
+ private ExpressionSyntax? TryConvertIfStatement(IfStatementSyntax ifStmt, string memberName)
+ {
+ // Convert if-else to conditional (ternary) expression
+ // First, rewrite the condition using the expression rewriter
+ var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifStmt.Condition);
+
+ var whenTrue = TryConvertStatement(ifStmt.Statement, memberName);
+ if (whenTrue == null)
+ {
+ return null;
+ }
+
+ ExpressionSyntax? whenFalse;
+ if (ifStmt.Else != null)
+ {
+ whenFalse = TryConvertStatement(ifStmt.Else.Statement, memberName);
+ if (whenFalse == null)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ // If there's no else clause, we can't convert to a ternary
+ ReportUnsupportedStatement(ifStmt, memberName, "If statements must have an else clause to be converted to expressions");
+ return null;
+ }
+
+ // Create a conditional expression with the rewritten nodes
+ return SyntaxFactory.ConditionalExpression(
+ condition,
+ whenTrue,
+ whenFalse
+ );
+ }
+
+ private ExpressionSyntax ReplaceLocalVariables(ExpressionSyntax expression)
+ {
+ // Use a rewriter to replace local variable references with their initializer expressions
+ var rewriter = new LocalVariableReplacer(_localVariables);
+ return (ExpressionSyntax)rewriter.Visit(expression);
+ }
+
+ private void ReportUnsupportedStatement(StatementSyntax statement, string memberName, string reason)
+ {
+ var diagnostic = Diagnostic.Create(
+ Diagnostics.UnsupportedStatementInBlockBody,
+ statement.GetLocation(),
+ memberName,
+ reason
+ );
+ _context.ReportDiagnostic(diagnostic);
+ }
+
+ private class LocalVariableReplacer : CSharpSyntaxRewriter
+ {
+ private readonly Dictionary _localVariables;
+
+ public LocalVariableReplacer(Dictionary localVariables)
+ {
+ _localVariables = localVariables;
+ }
+
+ public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
+ {
+ var identifier = node.Identifier.Text;
+ if (_localVariables.TryGetValue(identifier, out var replacement))
+ {
+ // Replace the identifier with the expression it was initialized with
+ return replacement.WithTriviaFrom(node);
+ }
+
+ return base.VisitIdentifierName(node);
+ }
+ }
+ }
+}
diff --git a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs
index 18f87c1..d98a1b8 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs
@@ -25,5 +25,13 @@ public static class Diagnostics
DiagnosticSeverity.Error,
isEnabledByDefault: true);
+ public static readonly DiagnosticDescriptor UnsupportedStatementInBlockBody = new DiagnosticDescriptor(
+ id: "EFP0003",
+ title: "Unsupported statement in block-bodied method",
+ messageFormat: "Method '{0}' contains an unsupported statement: {1}",
+ category: "Design",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
}
}
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
index 339b46b..36bd51c 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
@@ -97,7 +97,7 @@ x is IPropertySymbol xProperty &&
return false;
}
else if (x is MethodDeclarationSyntax xMethod &&
- xMethod.ExpressionBody is not null)
+ (xMethod.ExpressionBody is not null || xMethod.Body is not null))
{
return true;
}
@@ -212,7 +212,28 @@ x is IPropertySymbol xProperty &&
if (memberBody is MethodDeclarationSyntax methodDeclarationSyntax)
{
- if (methodDeclarationSyntax.ExpressionBody is null)
+ ExpressionSyntax? bodyExpression = null;
+
+ if (methodDeclarationSyntax.ExpressionBody is not null)
+ {
+ // Expression-bodied method (e.g., int Foo() => 1;)
+ bodyExpression = methodDeclarationSyntax.ExpressionBody.Expression;
+ }
+ else if (methodDeclarationSyntax.Body is not null)
+ {
+ // Block-bodied method (e.g., int Foo() { return 1; })
+ var blockConverter = new BlockStatementConverter(semanticModel, context, expressionSyntaxRewriter);
+ bodyExpression = blockConverter.TryConvertBlock(methodDeclarationSyntax.Body, memberSymbol.Name);
+
+ if (bodyExpression is null)
+ {
+ // Diagnostics already reported by BlockStatementConverter
+ return null;
+ }
+
+ // The expression has already been rewritten by BlockStatementConverter, so we don't rewrite it again
+ }
+ else
{
var diagnostic = Diagnostic.Create(Diagnostics.RequiresExpressionBodyDefinition, methodDeclarationSyntax.GetLocation(), memberSymbol.Name);
context.ReportDiagnostic(diagnostic);
@@ -222,7 +243,10 @@ x is IPropertySymbol xProperty &&
var returnType = declarationSyntaxRewriter.Visit(methodDeclarationSyntax.ReturnType);
descriptor.ReturnTypeName = returnType.ToString();
- descriptor.ExpressionBody = (ExpressionSyntax)expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
+ // Only rewrite expression-bodied methods, block-bodied methods are already rewritten
+ descriptor.ExpressionBody = methodDeclarationSyntax.ExpressionBody is not null
+ ? (ExpressionSyntax)expressionSyntaxRewriter.Visit(bodyExpression)
+ : bodyExpression;
foreach (var additionalParameter in ((ParameterListSyntax)declarationSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList)).Parameters)
{
descriptor.ParametersList = descriptor.ParametersList.AddParameters(additionalParameter);
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SimpleReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SimpleReturn.verified.txt
new file mode 100644
index 0000000..eeb0754
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SimpleReturn.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => 42;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
new file mode 100644
index 0000000..ed766a2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
@@ -0,0 +1,3 @@
+[
+ (11,13): warning EFP0003: Method 'Foo' contains an unsupported statement: Only local variable declarations are supported before the return statement
+]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElse.verified.txt
new file mode 100644
index 0000000..c22d885
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElse.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar > 10 ? 1 : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElseAndCondition.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElseAndCondition.verified.txt
new file mode 100644
index 0000000..ef8f31a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithIfElseAndCondition.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.IsActive && @this.Bar > 0 ? @this.Bar * 2 : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithLocalVariable.verified.txt
new file mode 100644
index 0000000..d863659
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithLocalVariable.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar * 2 + 5;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
new file mode 100644
index 0000000..c454e34
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Add
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this, int a, int b) => a + b;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithNestedIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithNestedIfElse.verified.txt
new file mode 100644
index 0000000..216b8f2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithNestedIfElse.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar > 10 ? "High" : @this.Bar > 5 ? "Medium" : "Low";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithPropertyAccess.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithPropertyAccess.verified.txt
new file mode 100644
index 0000000..19e29c9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithPropertyAccess.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar + 10;
+ }
+ }
+}
\ 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 eb50a78..c408db9 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -493,7 +493,7 @@ public int Foo
}
[Fact]
- public void BlockBodiedMethod_RaisesDiagnostics()
+ public void BlockBodiedMethod_NoLongerRaisesDiagnostics()
{
var compilation = CreateCompilation(@"
using System;
@@ -511,7 +511,9 @@ public int Foo()
var result = RunGenerator(compilation);
- Assert.Single(result.Diagnostics);
+ // Block-bodied methods are now supported, so no diagnostics should be raised
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
}
[Fact]
@@ -1977,6 +1979,250 @@ public static Dictionary ToDictionary(this Entity entity)
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+ [Fact]
+ public Task BlockBodiedMethod_SimpleReturn()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ [Projectable]
+ public int Foo()
+ {
+ return 42;
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithPropertyAccess()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ return Bar + 10;
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithIfElse()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ if (Bar > 10)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithNestedIfElse()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public string Foo()
+ {
+ if (Bar > 10)
+ {
+ return ""High"";
+ }
+ else if (Bar > 5)
+ {
+ return ""Medium"";
+ }
+ else
+ {
+ return ""Low"";
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithLocalVariable()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ var temp = Bar * 2;
+ return temp + 5;
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithMultipleParameters()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ [Projectable]
+ public int Add(int a, int b)
+ {
+ return a + b;
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_WithIfElseAndCondition()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+ public bool IsActive { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ if (IsActive && Bar > 0)
+ {
+ return Bar * 2;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_UnsupportedStatement_WithoutElse()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ if (Bar > 10)
+ {
+ return 1;
+ }
+ return 0;
+ }
+ }
+}
+", expectedToCompile: true);
+
+ var result = RunGenerator(compilation);
+
+ // Should have a warning diagnostic
+ Assert.NotEmpty(result.Diagnostics);
+ Assert.Contains(result.Diagnostics, d => d.Id == "EFP0003");
+
+ return Verifier.Verify(result.Diagnostics.Select(d => d.ToString()));
+ }
+
#region Helpers
Compilation CreateCompilation(string source, bool expectedToCompile = true)
From f68c572e7eff047ee6dd4e4a04c491d054088a9b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 14 Feb 2026 11:40:27 +0000
Subject: [PATCH 03/13] Add functional tests and documentation for block-bodied
methods
- Created 7 functional tests demonstrating EF Core SQL translation
- Added comprehensive documentation explaining feature, limitations, and benefits
- All 174 tests passing across all projects
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
docs/BlockBodiedMethods.md | 159 ++++++++++++++++
...ers_WorksCorrectly.DotNet10_0.verified.txt | 2 +
...ters_WorksCorrectly.DotNet9_0.verified.txt | 2 +
...WithParameters_WorksCorrectly.verified.txt | 2 +
...ranslatedCorrectly.DotNet10_0.verified.txt | 5 +
...TranslatedCorrectly.DotNet9_0.verified.txt | 5 +
...itional_IsTranslatedCorrectly.verified.txt | 5 +
...ranslatedToTernary.DotNet10_0.verified.txt | 5 +
...TranslatedToTernary.DotNet9_0.verified.txt | 5 +
...atement_IsTranslatedToTernary.verified.txt | 5 +
...Variable_IsInlined.DotNet10_0.verified.txt | 2 +
...lVariable_IsInlined.DotNet9_0.verified.txt | 2 +
...Tests.LocalVariable_IsInlined.verified.txt | 2 +
...tedToNestedTernary.DotNet10_0.verified.txt | 6 +
...atedToNestedTernary.DotNet9_0.verified.txt | 6 +
...e_IsTranslatedToNestedTernary.verified.txt | 6 +
..._IsTranslatedToSql.DotNet10_0.verified.txt | 2 +
...s_IsTranslatedToSql.DotNet9_0.verified.txt | 2 +
...pertyAccess_IsTranslatedToSql.verified.txt | 2 +
..._IsTranslatedToSql.DotNet10_0.verified.txt | 2 +
...n_IsTranslatedToSql.DotNet9_0.verified.txt | 2 +
...impleReturn_IsTranslatedToSql.verified.txt | 2 +
.../BlockBodiedMethodTests.cs | 169 ++++++++++++++++++
23 files changed, 400 insertions(+)
create mode 100644 docs/BlockBodiedMethods.md
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
diff --git a/docs/BlockBodiedMethods.md b/docs/BlockBodiedMethods.md
new file mode 100644
index 0000000..a62cace
--- /dev/null
+++ b/docs/BlockBodiedMethods.md
@@ -0,0 +1,159 @@
+# Block-Bodied Methods Support
+
+As of this version, EntityFrameworkCore.Projectables now supports "classic" block-bodied methods decorated with `[Projectable]`, in addition to expression-bodied methods.
+
+## What's Supported
+
+Block-bodied methods can now be transformed into expression trees when they contain:
+
+### 1. Simple Return Statements
+```csharp
+[Projectable]
+public int GetConstant()
+{
+ return 42;
+}
+```
+
+### 2. If-Else Statements (converted to ternary expressions)
+```csharp
+[Projectable]
+public string GetCategory()
+{
+ if (Value > 100)
+ {
+ return "High";
+ }
+ else
+ {
+ return "Low";
+ }
+}
+```
+
+### 3. Nested If-Else Statements
+```csharp
+[Projectable]
+public string GetLevel()
+{
+ if (Value > 100)
+ {
+ return "High";
+ }
+ else if (Value > 50)
+ {
+ return "Medium";
+ }
+ else
+ {
+ return "Low";
+ }
+}
+```
+
+### 4. Local Variable Declarations (inlined into the expression)
+```csharp
+[Projectable]
+public int CalculateDouble()
+{
+ var doubled = Value * 2;
+ return doubled + 5;
+}
+```
+
+## Limitations and Warnings
+
+The source generator will produce **warning EFP0003** when it encounters unsupported statements in block-bodied methods:
+
+### Unsupported Statements:
+- If statements without else clauses
+- While, for, foreach loops
+- Switch statements (use switch expressions instead)
+- Try-catch-finally blocks
+- Throw statements
+- New object instantiation in statement position
+- Multiple statements (except local variable declarations before return)
+
+### Example of Unsupported Pattern:
+```csharp
+[Projectable]
+public int GetValue()
+{
+ if (IsActive) // ❌ No else clause - will produce EFP0003 warning
+ {
+ return Value;
+ }
+ return 0;
+}
+```
+
+Should be written as:
+```csharp
+[Projectable]
+public int GetValue()
+{
+ if (IsActive) // ✅ Has else clause
+ {
+ return Value;
+ }
+ else
+ {
+ return 0;
+ }
+}
+```
+
+Or as expression-bodied:
+```csharp
+[Projectable]
+public int GetValue() => IsActive ? Value : 0; // ✅ Expression-bodied
+```
+
+## How It Works
+
+The source generator:
+1. Parses block-bodied methods
+2. Converts if-else statements to conditional (ternary) expressions
+3. Inlines local variables into the return expression
+4. Rewrites the resulting expression using the existing expression transformation pipeline
+5. Generates the same output as expression-bodied methods
+
+## Benefits
+
+- **More readable code**: Complex logic with nested conditions is often easier to read with if-else blocks than with nested ternary operators
+- **Gradual migration**: Existing code with block bodies can now be marked as `[Projectable]` without rewriting
+- **Intermediate variables**: Local variables can make complex calculations more understandable
+
+## Example Output
+
+Given this code:
+```csharp
+public record Entity
+{
+ public int Value { get; set; }
+ public bool IsActive { get; set; }
+
+ [Projectable]
+ public int GetAdjustedValue()
+ {
+ if (IsActive && Value > 0)
+ {
+ return Value * 2;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
+```
+
+The generated SQL will be:
+```sql
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 0
+ THEN [e].[Value] * 2
+ ELSE 0
+END
+FROM [Entity] AS [e]
+```
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..dab6bd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 15
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..dab6bd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 15
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..dab6bd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT 15
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..a19f725
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 0 THEN [e].[Value] * 2
+ ELSE 0
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..a19f725
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 0 THEN [e].[Value] * 2
+ ELSE 0
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
new file mode 100644
index 0000000..a19f725
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 0 THEN [e].[Value] * 2
+ ELSE 0
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
new file mode 100644
index 0000000..26ac26b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
new file mode 100644
index 0000000..26ac26b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
new file mode 100644
index 0000000..26ac26b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9689484
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 5
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9689484
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 5
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
new file mode 100644
index 0000000..9689484
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 5
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
new file mode 100644
index 0000000..06a56fa
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
new file mode 100644
index 0000000..06a56fa
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
new file mode 100644
index 0000000..06a56fa
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
new file mode 100644
index 0000000..6efc8d2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
new file mode 100644
index 0000000..6efc8d2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
new file mode 100644
index 0000000..6efc8d2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
@@ -0,0 +1,2 @@
+SELECT 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
new file mode 100644
index 0000000..435c2a6
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
@@ -0,0 +1,169 @@
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using System.Linq;
+using System.Threading.Tasks;
+using VerifyXunit;
+using Xunit;
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests
+{
+ [UsesVerify]
+ public class BlockBodiedMethodTests
+ {
+ public record Entity
+ {
+ public int Id { get; set; }
+ public int Value { get; set; }
+ public bool IsActive { get; set; }
+ public string? Name { get; set; }
+ }
+
+ [Fact]
+ public Task SimpleReturn_IsTranslatedToSql()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetConstant());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ReturnWithPropertyAccess_IsTranslatedToSql()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValuePlusTen());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task IfElseStatement_IsTranslatedToTernary()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetCategory());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task NestedIfElse_IsTranslatedToNestedTernary()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetLevel());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task LocalVariable_IsInlined()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.CalculateDouble());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ComplexConditional_IsTranslatedCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetAdjustedValue());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockMethodWithParameters_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.Add(5, 10));
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+
+ public static class EntityExtensions
+ {
+ [Projectable]
+ public static int GetConstant(this BlockBodiedMethodTests.Entity entity)
+ {
+ return 42;
+ }
+
+ [Projectable]
+ public static int GetValuePlusTen(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Value + 10;
+ }
+
+ [Projectable]
+ public static string GetCategory(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.Value > 100)
+ {
+ return "High";
+ }
+ else
+ {
+ return "Low";
+ }
+ }
+
+ [Projectable]
+ public static string GetLevel(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.Value > 100)
+ {
+ return "High";
+ }
+ else if (entity.Value > 50)
+ {
+ return "Medium";
+ }
+ else
+ {
+ return "Low";
+ }
+ }
+
+ [Projectable]
+ public static int CalculateDouble(this BlockBodiedMethodTests.Entity entity)
+ {
+ var doubled = entity.Value * 2;
+ return doubled + 5;
+ }
+
+ [Projectable]
+ public static int GetAdjustedValue(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive && entity.Value > 0)
+ {
+ return entity.Value * 2;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ [Projectable]
+ public static int Add(this BlockBodiedMethodTests.Entity entity, int a, int b)
+ {
+ return a + b;
+ }
+ }
+}
From 7067bc28c48a95e4068ff23f38bda9e7c1ad5e2e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 14 Feb 2026 12:05:34 +0000
Subject: [PATCH 04/13] Add support for if-without-else and switch statements
- Support if statements without else clause (uses default or fallback return)
- Support switch statements (converted to nested conditionals)
- Handle if { return x; } return y; pattern
- Added 5 generator tests and 4 functional tests
- Updated documentation with new features and SQL examples
- All 182 tests passing (84 generator + 76 functional + 22 unit)
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
docs/BlockBodiedMethods.md | 124 +++++++++++--
.../BlockStatementConverter.cs | 164 +++++++++++++++++-
...utElse_UsesDefault.DotNet10_0.verified.txt | 4 +
...outElse_UsesDefault.DotNet9_0.verified.txt | 4 +
...sts.IfWithoutElse_UsesDefault.verified.txt | 5 +
...WithFallbackReturn.DotNet10_0.verified.txt | 5 +
..._WithFallbackReturn.DotNet9_0.verified.txt | 5 +
...ithoutElse_WithFallbackReturn.verified.txt | 5 +
...chStatement_Simple.DotNet10_0.verified.txt | 7 +
...tchStatement_Simple.DotNet9_0.verified.txt | 7 +
...dTests.SwitchStatement_Simple.verified.txt | 7 +
..._WithMultipleCases.DotNet10_0.verified.txt | 7 +
...t_WithMultipleCases.DotNet9_0.verified.txt | 7 +
...chStatement_WithMultipleCases.verified.txt | 7 +
.../BlockBodiedMethodTests.cs | 101 +++++++++++
..._IfWithoutElse_ReturnsDefault.verified.txt | 17 ++
...hod_IfWithoutElse_UsesDefault.verified.txt | 17 ++
...Method_SwitchStatement_Simple.verified.txt | 17 ++
...chStatement_WithMultipleCases.verified.txt | 17 ++
...witchStatement_WithoutDefault.verified.txt | 17 ++
...upportedStatement_WithoutElse.verified.txt | 3 -
.../ProjectionExpressionGeneratorTests.cs | 148 +++++++++++++++-
22 files changed, 671 insertions(+), 24 deletions(-)
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_UsesDefault.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_Simple.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithMultipleCases.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithoutDefault.verified.txt
delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
diff --git a/docs/BlockBodiedMethods.md b/docs/BlockBodiedMethods.md
index a62cace..9dc6e7a 100644
--- a/docs/BlockBodiedMethods.md
+++ b/docs/BlockBodiedMethods.md
@@ -61,38 +61,78 @@ public int CalculateDouble()
}
```
+### 5. Switch Statements (converted to nested ternary expressions)
+```csharp
+[Projectable]
+public string GetValueLabel()
+{
+ switch (Value)
+ {
+ case 1:
+ return "One";
+ case 2:
+ return "Two";
+ case 3:
+ return "Three";
+ default:
+ return "Many";
+ }
+}
+```
+
+### 6. If Statements Without Else (uses default value)
+```csharp
+[Projectable]
+public int? GetPremiumIfActive()
+{
+ if (IsActive)
+ {
+ return Value * 2;
+ }
+ // Implicitly returns null (default for int?)
+}
+
+// Or with explicit fallback:
+[Projectable]
+public string GetStatus()
+{
+ if (IsActive)
+ {
+ return "Active";
+ }
+ return "Inactive"; // Explicit fallback
+}
+```
+
## Limitations and Warnings
The source generator will produce **warning EFP0003** when it encounters unsupported statements in block-bodied methods:
### Unsupported Statements:
-- If statements without else clauses
- While, for, foreach loops
-- Switch statements (use switch expressions instead)
- Try-catch-finally blocks
- Throw statements
- New object instantiation in statement position
-- Multiple statements (except local variable declarations before return)
### Example of Unsupported Pattern:
```csharp
[Projectable]
public int GetValue()
{
- if (IsActive) // ❌ No else clause - will produce EFP0003 warning
+ for (int i = 0; i < 10; i++) // ❌ Loops not supported
{
- return Value;
+ // ...
}
return 0;
}
```
-Should be written as:
+Supported patterns:
```csharp
[Projectable]
public int GetValue()
{
- if (IsActive) // ✅ Has else clause
+ if (IsActive) // ✅ If without else is now supported!
{
return Value;
}
@@ -103,6 +143,35 @@ public int GetValue()
}
```
+Additional supported patterns:
+```csharp
+// If without else using fallback return:
+[Projectable]
+public int GetValue()
+{
+ if (IsActive)
+ {
+ return Value;
+ }
+ return 0; // ✅ Fallback return
+}
+
+// Switch statement:
+[Projectable]
+public string GetLabel()
+{
+ switch (Value) // ✅ Switch statements now supported!
+ {
+ case 1:
+ return "One";
+ case 2:
+ return "Two";
+ default:
+ return "Other";
+ }
+}
+```
+
Or as expression-bodied:
```csharp
[Projectable]
@@ -114,17 +183,48 @@ public int GetValue() => IsActive ? Value : 0; // ✅ Expression-bodied
The source generator:
1. Parses block-bodied methods
2. Converts if-else statements to conditional (ternary) expressions
-3. Inlines local variables into the return expression
-4. Rewrites the resulting expression using the existing expression transformation pipeline
-5. Generates the same output as expression-bodied methods
+3. Converts switch statements to nested conditional expressions
+4. Inlines local variables into the return expression
+5. Rewrites the resulting expression using the existing expression transformation pipeline
+6. Generates the same output as expression-bodied methods
## Benefits
-- **More readable code**: Complex logic with nested conditions is often easier to read with if-else blocks than with nested ternary operators
+- **More readable code**: Complex logic with nested conditions and switch statements is often easier to read than nested ternary operators
- **Gradual migration**: Existing code with block bodies can now be marked as `[Projectable]` without rewriting
- **Intermediate variables**: Local variables can make complex calculations more understandable
+- **Switch support**: Traditional switch statements now work alongside switch expressions
+
+## SQL Output Examples
+
+### Switch Statement with Multiple Cases
+Given this code:
+```csharp
+switch (Value)
+{
+ case 1:
+ case 2:
+ return "Low";
+ case 3:
+ case 4:
+ case 5:
+ return "Medium";
+ default:
+ return "High";
+}
+```
+
+Generates optimized SQL:
+```sql
+SELECT CASE
+ WHEN [e].[Value] IN (1, 2) THEN N'Low'
+ WHEN [e].[Value] IN (3, 4, 5) THEN N'Medium'
+ ELSE N'High'
+END
+FROM [Entity] AS [e]
+```
-## Example Output
+### If-Else Example Output
Given this code:
```csharp
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index 7c000af..db9170c 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -57,6 +57,31 @@ public BlockStatementConverter(SemanticModel semanticModel, SourceProductionCont
var nonReturnStatements = statements.Take(statements.Count - 1).ToList();
var lastStatement = statements.Last();
+ // Check if we have a pattern like: if { return x; } return y;
+ // This can be converted to: condition ? x : y
+ if (nonReturnStatements.Count == 1 &&
+ nonReturnStatements[0] is IfStatementSyntax ifWithoutElse &&
+ ifWithoutElse.Else == null &&
+ lastStatement is ReturnStatementSyntax finalReturn)
+ {
+ // Convert: if (condition) { return x; } return y;
+ // To: condition ? x : y
+ var ifBody = TryConvertStatement(ifWithoutElse.Statement, memberName);
+ if (ifBody == null)
+ {
+ return null;
+ }
+
+ var elseBody = TryConvertReturnStatement(finalReturn, memberName);
+ if (elseBody == null)
+ {
+ return null;
+ }
+
+ var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifWithoutElse.Condition);
+ return SyntaxFactory.ConditionalExpression(condition, ifBody, elseBody);
+ }
+
// Process local variable declarations
foreach (var stmt in nonReturnStatements)
{
@@ -107,6 +132,9 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
case IfStatementSyntax ifStmt:
return TryConvertIfStatement(ifStmt, memberName);
+ case SwitchStatementSyntax switchStmt:
+ return TryConvertSwitchStatement(switchStmt, memberName);
+
case BlockSyntax blockStmt:
return TryConvertStatements(blockStmt.Statements.ToList(), memberName);
@@ -166,9 +194,12 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
}
else
{
- // If there's no else clause, we can't convert to a ternary
- ReportUnsupportedStatement(ifStmt, memberName, "If statements must have an else clause to be converted to expressions");
- return null;
+ // If there's no else clause, use a default literal
+ // This will be inferred to the correct type by the compiler
+ whenFalse = SyntaxFactory.LiteralExpression(
+ SyntaxKind.DefaultLiteralExpression,
+ SyntaxFactory.Token(SyntaxKind.DefaultKeyword)
+ );
}
// Create a conditional expression with the rewritten nodes
@@ -179,6 +210,133 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
);
}
+ private ExpressionSyntax? TryConvertSwitchStatement(SwitchStatementSyntax switchStmt, string memberName)
+ {
+ // Convert switch statement to nested conditional expressions
+ // Process sections in reverse order to build from the default case up
+
+ var switchExpression = (ExpressionSyntax)_expressionRewriter.Visit(switchStmt.Expression);
+ ExpressionSyntax? currentExpression = null;
+
+ // Find default case first
+ SwitchSectionSyntax? defaultSection = null;
+ var nonDefaultSections = new List();
+
+ foreach (var section in switchStmt.Sections)
+ {
+ bool hasDefault = section.Labels.Any(label => label is DefaultSwitchLabelSyntax);
+ if (hasDefault)
+ {
+ defaultSection = section;
+ }
+ else
+ {
+ nonDefaultSections.Add(section);
+ }
+ }
+
+ // Start with default case or null
+ if (defaultSection != null)
+ {
+ currentExpression = ConvertSwitchSection(defaultSection, memberName);
+ if (currentExpression == null)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ // No default case - use default literal
+ currentExpression = SyntaxFactory.LiteralExpression(
+ SyntaxKind.DefaultLiteralExpression,
+ SyntaxFactory.Token(SyntaxKind.DefaultKeyword)
+ );
+ }
+
+ // Process non-default sections in reverse order
+ for (int i = nonDefaultSections.Count - 1; i >= 0; i--)
+ {
+ var section = nonDefaultSections[i];
+ var sectionExpression = ConvertSwitchSection(section, memberName);
+ if (sectionExpression == null)
+ {
+ return null;
+ }
+
+ // Build condition for all labels in this section (OR'd together)
+ ExpressionSyntax? condition = null;
+ foreach (var label in section.Labels)
+ {
+ if (label is CaseSwitchLabelSyntax caseLabel)
+ {
+ var labelCondition = SyntaxFactory.BinaryExpression(
+ SyntaxKind.EqualsExpression,
+ switchExpression,
+ (ExpressionSyntax)_expressionRewriter.Visit(caseLabel.Value)
+ );
+
+ condition = condition == null
+ ? labelCondition
+ : SyntaxFactory.BinaryExpression(
+ SyntaxKind.LogicalOrExpression,
+ condition,
+ labelCondition
+ );
+ }
+ else if (label is not DefaultSwitchLabelSyntax)
+ {
+ // Unsupported label type (e.g., pattern-based switch in older syntax)
+ ReportUnsupportedStatement(switchStmt, memberName,
+ $"Switch label type '{label.GetType().Name}' is not supported. Use case labels or switch expressions instead.");
+ return null;
+ }
+ }
+
+ if (condition != null)
+ {
+ currentExpression = SyntaxFactory.ConditionalExpression(
+ condition,
+ sectionExpression,
+ currentExpression
+ );
+ }
+ }
+
+ return currentExpression;
+ }
+
+ private ExpressionSyntax? ConvertSwitchSection(SwitchSectionSyntax section, string memberName)
+ {
+ // Convert the statements in the switch section
+ // Most switch sections end with break, return, or throw
+ var statements = section.Statements.ToList();
+
+ // Remove trailing break statements as they're not needed in expressions
+ if (statements.Count > 0 && statements.Last() is BreakStatementSyntax)
+ {
+ statements = statements.Take(statements.Count - 1).ToList();
+ }
+
+ if (statements.Count == 0)
+ {
+ // Use the section's first label location for error reporting
+ var firstLabel = section.Labels.FirstOrDefault();
+ if (firstLabel != null)
+ {
+ var diagnostic = Diagnostic.Create(
+ Diagnostics.UnsupportedStatementInBlockBody,
+ firstLabel.GetLocation(),
+ memberName,
+ "Switch section must have at least one statement"
+ );
+ _context.ReportDiagnostic(diagnostic);
+ }
+ return null;
+ }
+
+ return TryConvertStatements(statements, memberName);
+ }
+
private ExpressionSyntax ReplaceLocalVariables(ExpressionSyntax expression)
{
// Use a rewriter to replace local variable references with their initializer expressions
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
new file mode 100644
index 0000000..0c5fe1e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
@@ -0,0 +1,4 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN [e].[Value] * 2
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
new file mode 100644
index 0000000..0c5fe1e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
@@ -0,0 +1,4 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN [e].[Value] * 2
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
new file mode 100644
index 0000000..7e3c8c6
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN [e].[Value] * 2
+ ELSE NULL
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9c8b78e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] IN (1, 2) THEN N'Low'
+ WHEN [e].[Value] IN (3, 4, 5) THEN N'Medium'
+ WHEN [e].[Value] IN (6, 7, 8) THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9c8b78e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] IN (1, 2) THEN N'Low'
+ WHEN [e].[Value] IN (3, 4, 5) THEN N'Medium'
+ WHEN [e].[Value] IN (6, 7, 8) THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
new file mode 100644
index 0000000..9c8b78e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] IN (1, 2) THEN N'Low'
+ WHEN [e].[Value] IN (3, 4, 5) THEN N'Medium'
+ WHEN [e].[Value] IN (6, 7, 8) THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
index 435c2a6..71ae4d9 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
@@ -94,6 +94,50 @@ public Task BlockMethodWithParameters_WorksCorrectly()
return Verifier.Verify(query.ToQueryString());
}
+
+ [Fact]
+ public Task IfWithoutElse_UsesDefault()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetPremiumIfActive());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task IfWithoutElse_WithFallbackReturn()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetStatus());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SwitchStatement_Simple()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValueLabel());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SwitchStatement_WithMultipleCases()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetPriority());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
}
public static class EntityExtensions
@@ -165,5 +209,62 @@ public static int Add(this BlockBodiedMethodTests.Entity entity, int a, int b)
{
return a + b;
}
+
+ [Projectable]
+ public static int? GetPremiumIfActive(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive)
+ {
+ return entity.Value * 2;
+ }
+ return null;
+ }
+
+ [Projectable]
+ public static string GetStatus(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive)
+ {
+ return "Active";
+ }
+ return "Inactive";
+ }
+
+ [Projectable]
+ public static string GetValueLabel(this BlockBodiedMethodTests.Entity entity)
+ {
+ switch (entity.Value)
+ {
+ case 1:
+ return "One";
+ case 2:
+ return "Two";
+ case 3:
+ return "Three";
+ default:
+ return "Many";
+ }
+ }
+
+ [Projectable]
+ public static string GetPriority(this BlockBodiedMethodTests.Entity entity)
+ {
+ switch (entity.Value)
+ {
+ case 1:
+ case 2:
+ return "Low";
+ case 3:
+ case 4:
+ case 5:
+ return "Medium";
+ case 6:
+ case 7:
+ case 8:
+ return "High";
+ default:
+ return "Critical";
+ }
+ }
}
}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt
new file mode 100644
index 0000000..b5f9f5b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar > 10 ? 1 : default;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_UsesDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_UsesDefault.verified.txt
new file mode 100644
index 0000000..c22d885
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_UsesDefault.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar > 10 ? 1 : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_Simple.verified.txt
new file mode 100644
index 0000000..d1a7eb5
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_Simple.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar == 1 ? "One" : @this.Bar == 2 ? "Two" : "Other";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithMultipleCases.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithMultipleCases.verified.txt
new file mode 100644
index 0000000..c90d6b7
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithMultipleCases.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar == 1 || @this.Bar == 2 ? "Low" : @this.Bar == 3 || @this.Bar == 4 || @this.Bar == 5 ? "Medium" : "High";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithoutDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithoutDefault.verified.txt
new file mode 100644
index 0000000..0a4d15d
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithoutDefault.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar == 1 ? "One" : @this.Bar == 2 ? "Two" : default;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
deleted file mode 100644
index ed766a2..0000000
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_UnsupportedStatement_WithoutElse.verified.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[
- (11,13): warning EFP0003: Method 'Foo' contains an unsupported statement: Only local variable declarations are supported before the return statement
-]
\ 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 c408db9..8baad79 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -2191,8 +2191,9 @@ public int Foo()
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+
[Fact]
- public Task BlockBodiedMethod_UnsupportedStatement_WithoutElse()
+ public Task BlockBodiedMethod_IfWithoutElse_UsesDefault()
{
var compilation = CreateCompilation(@"
using System;
@@ -2212,15 +2213,150 @@ public int Foo()
}
}
}
-", expectedToCompile: true);
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_IfWithoutElse_ReturnsDefault()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int? Foo()
+ {
+ if (Bar > 10)
+ {
+ return 1;
+ }
+ }
+ }
+}
+", expectedToCompile: false);
var result = RunGenerator(compilation);
- // Should have a warning diagnostic
- Assert.NotEmpty(result.Diagnostics);
- Assert.Contains(result.Diagnostics, d => d.Id == "EFP0003");
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_SwitchStatement_Simple()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public string Foo()
+ {
+ switch (Bar)
+ {
+ case 1:
+ return ""One"";
+ case 2:
+ return ""Two"";
+ default:
+ return ""Other"";
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
- return Verifier.Verify(result.Diagnostics.Select(d => d.ToString()));
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_SwitchStatement_WithMultipleCases()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public string Foo()
+ {
+ switch (Bar)
+ {
+ case 1:
+ case 2:
+ return ""Low"";
+ case 3:
+ case 4:
+ case 5:
+ return ""Medium"";
+ default:
+ return ""High"";
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_SwitchStatement_WithoutDefault()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public string? Foo()
+ {
+ switch (Bar)
+ {
+ case 1:
+ return ""One"";
+ case 2:
+ return ""Two"";
+ }
+ }
+ }
+}
+", expectedToCompile: false);
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
#region Helpers
From 75c638e3d0d2681fa9a03459f90c471ed15f1d78 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 14 Feb 2026 12:08:51 +0000
Subject: [PATCH 05/13] Address code review feedback
- Rename test to better reflect implicit return pattern
- Add clarifying comment about control flow in BlockStatementConverter
- All tests still passing
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
.../BlockStatementConverter.cs | 3 ++-
...lockBodiedMethod_IfWithoutElse_ImplicitReturn.verified.txt} | 0
.../ProjectionExpressionGeneratorTests.cs | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt => ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ImplicitReturn.verified.txt} (100%)
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index db9170c..59ede35 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -82,7 +82,8 @@ nonReturnStatements[0] is IfStatementSyntax ifWithoutElse &&
return SyntaxFactory.ConditionalExpression(condition, ifBody, elseBody);
}
- // Process local variable declarations
+ // If we reach here, the pattern was not detected
+ // Process local variable declarations before the final return
foreach (var stmt in nonReturnStatements)
{
if (stmt is LocalDeclarationStatementSyntax localDecl)
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ImplicitReturn.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ReturnsDefault.verified.txt
rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ImplicitReturn.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
index 8baad79..9e2d34a 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -2224,7 +2224,7 @@ public int Foo()
}
[Fact]
- public Task BlockBodiedMethod_IfWithoutElse_ReturnsDefault()
+ public Task BlockBodiedMethod_IfWithoutElse_ImplicitReturn()
{
var compilation = CreateCompilation(@"
using System;
From 06627c292ceea0ad05389a1cc5ae3495c9bf8ed8 Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 10:04:14 +0100
Subject: [PATCH 06/13] Remove unused code and add support for multiple early
returns
---
.../BlockStatementConverter.cs | 66 +++++++++++++------
.../ProjectableInterpreter.cs | 2 +-
.../BlockBodiedMethodTests.cs | 32 +++++++++
...Method_WithMultipleParameters.verified.txt | 2 +-
4 files changed, 81 insertions(+), 21 deletions(-)
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index 59ede35..d5817e2 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -1,8 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using System.Collections.Generic;
-using System.Linq;
namespace EntityFrameworkCore.Projectables.Generator
{
@@ -12,14 +10,12 @@ namespace EntityFrameworkCore.Projectables.Generator
///
public class BlockStatementConverter
{
- private readonly SemanticModel _semanticModel;
private readonly SourceProductionContext _context;
private readonly ExpressionSyntaxRewriter _expressionRewriter;
private readonly Dictionary _localVariables = new();
- public BlockStatementConverter(SemanticModel semanticModel, SourceProductionContext context, ExpressionSyntaxRewriter expressionRewriter)
+ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntaxRewriter expressionRewriter)
{
- _semanticModel = semanticModel;
_context = context;
_expressionRewriter = expressionRewriter;
}
@@ -30,7 +26,7 @@ public BlockStatementConverter(SemanticModel semanticModel, SourceProductionCont
///
public ExpressionSyntax? TryConvertBlock(BlockSyntax block, string memberName)
{
- if (block == null || block.Statements.Count == 0)
+ if (block.Statements.Count == 0)
{
return null;
}
@@ -57,12 +53,44 @@ public BlockStatementConverter(SemanticModel semanticModel, SourceProductionCont
var nonReturnStatements = statements.Take(statements.Count - 1).ToList();
var lastStatement = statements.Last();
- // Check if we have a pattern like: if { return x; } return y;
- // This can be converted to: condition ? x : y
- if (nonReturnStatements.Count == 1 &&
- nonReturnStatements[0] is IfStatementSyntax ifWithoutElse &&
- ifWithoutElse.Else == null &&
- lastStatement is ReturnStatementSyntax finalReturn)
+ // Check if we have a pattern like multiple if statements without else followed by a final return:
+ // if (a) return 1; if (b) return 2; return 3;
+ // This can be converted to nested ternaries: a ? 1 : (b ? 2 : 3)
+ if (lastStatement is ReturnStatementSyntax finalReturn &&
+ nonReturnStatements.All(s => s is IfStatementSyntax { Else: null }))
+ {
+ // All non-return statements are if statements without else
+ var ifStatements = nonReturnStatements.Cast().ToList();
+
+ // Start with the final return as the base expression
+ var elseBody = TryConvertReturnStatement(finalReturn, memberName);
+ if (elseBody == null)
+ {
+ return null;
+ }
+
+ // Build nested conditionals from right to left (last to first)
+ for (var i = ifStatements.Count - 1; i >= 0; i--)
+ {
+ var ifStmt = ifStatements[i];
+ var ifBody = TryConvertStatement(ifStmt.Statement, memberName);
+ if (ifBody == null)
+ {
+ return null;
+ }
+
+ var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifStmt.Condition);
+ elseBody = SyntaxFactory.ConditionalExpression(condition, ifBody, elseBody);
+ }
+
+ return elseBody;
+ }
+
+ // Check if we have a single if without else followed by a return (legacy path)
+ // This is now redundant with the above logic but kept for clarity and potential optimization
+ if (nonReturnStatements.Count == 1 &&
+ nonReturnStatements[0] is IfStatementSyntax { Else: null } ifWithoutElse &&
+ lastStatement is ReturnStatementSyntax singleFinalReturn)
{
// Convert: if (condition) { return x; } return y;
// To: condition ? x : y
@@ -72,7 +100,7 @@ nonReturnStatements[0] is IfStatementSyntax ifWithoutElse &&
return null;
}
- var elseBody = TryConvertReturnStatement(finalReturn, memberName);
+ var elseBody = TryConvertReturnStatement(singleFinalReturn, memberName);
if (elseBody == null)
{
return null;
@@ -115,9 +143,9 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
}
var variableName = variable.Identifier.Text;
+
// Rewrite the initializer expression NOW while it's still in the tree
- var rewrittenInitializer = (ExpressionSyntax)_expressionRewriter.Visit(variable.Initializer.Value);
- _localVariables[variableName] = rewrittenInitializer;
+ _localVariables[variableName] = (ExpressionSyntax)_expressionRewriter.Visit(variable.Initializer.Value);
}
return true;
@@ -139,7 +167,7 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
case BlockSyntax blockStmt:
return TryConvertStatements(blockStmt.Statements.ToList(), memberName);
- case ExpressionStatementSyntax exprStmt:
+ case ExpressionStatementSyntax:
// Expression statements are generally not useful in expression trees
ReportUnsupportedStatement(statement, memberName, "Expression statements are not supported");
return null;
@@ -217,7 +245,7 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
// Process sections in reverse order to build from the default case up
var switchExpression = (ExpressionSyntax)_expressionRewriter.Visit(switchStmt.Expression);
- ExpressionSyntax? currentExpression = null;
+ ExpressionSyntax? currentExpression;
// Find default case first
SwitchSectionSyntax? defaultSection = null;
@@ -225,7 +253,7 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
foreach (var section in switchStmt.Sections)
{
- bool hasDefault = section.Labels.Any(label => label is DefaultSwitchLabelSyntax);
+ var hasDefault = section.Labels.Any(label => label is DefaultSwitchLabelSyntax);
if (hasDefault)
{
defaultSection = section;
@@ -255,7 +283,7 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
}
// Process non-default sections in reverse order
- for (int i = nonDefaultSections.Count - 1; i >= 0; i--)
+ for (var i = nonDefaultSections.Count - 1; i >= 0; i--)
{
var section = nonDefaultSections[i];
var sectionExpression = ConvertSwitchSection(section, memberName);
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
index 7bca8cc..5006081 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
@@ -312,7 +312,7 @@ x is IPropertySymbol xProperty &&
else if (methodDeclarationSyntax.Body is not null)
{
// Block-bodied method (e.g., int Foo() { return 1; })
- var blockConverter = new BlockStatementConverter(semanticModel, context, expressionSyntaxRewriter);
+ var blockConverter = new BlockStatementConverter(context, expressionSyntaxRewriter);
bodyExpression = blockConverter.TryConvertBlock(methodDeclarationSyntax.Body, memberSymbol.Name);
if (bodyExpression is null)
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
index 71ae4d9..a961442 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
@@ -138,6 +138,17 @@ public Task SwitchStatement_WithMultipleCases()
return Verifier.Verify(query.ToQueryString());
}
+
+ [Fact]
+ public Task MultipleEarlyReturns_ConvertedToNestedTernaries()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValueCategory());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
}
public static class EntityExtensions
@@ -266,5 +277,26 @@ public static string GetPriority(this BlockBodiedMethodTests.Entity entity)
return "Critical";
}
}
+
+ [Projectable]
+ public static string GetValueCategory(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.Value > 100)
+ {
+ return "Very High";
+ }
+
+ if (entity.Value > 50)
+ {
+ return "High";
+ }
+
+ if (entity.Value > 10)
+ {
+ return "Medium";
+ }
+
+ return "Low";
+ }
}
}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
index c454e34..7c1426a 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithMultipleParameters.verified.txt
@@ -7,7 +7,7 @@ using Foo;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Foo_C_Add
+ static class Foo_C_Add_P0_int_P1_int
{
static global::System.Linq.Expressions.Expression> Expression()
{
From 9add2c922dd3af070f451632bc6b1531cc19d8cf Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 10:08:46 +0100
Subject: [PATCH 07/13] Missing verify files
---
...urns_ConvertedToNestedTernaries.DotNet10_0.verified.txt | 7 +++++++
...turns_ConvertedToNestedTernaries.DotNet9_0.verified.txt | 7 +++++++
...pleEarlyReturns_ConvertedToNestedTernaries.verified.txt | 7 +++++++
3 files changed, 21 insertions(+)
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
new file mode 100644
index 0000000..1ae6355
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Very High'
+ WHEN [e].[Value] > 50 THEN N'High'
+ WHEN [e].[Value] > 10 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
new file mode 100644
index 0000000..1ae6355
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Very High'
+ WHEN [e].[Value] > 50 THEN N'High'
+ WHEN [e].[Value] > 10 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
new file mode 100644
index 0000000..1ae6355
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Very High'
+ WHEN [e].[Value] > 50 THEN N'High'
+ WHEN [e].[Value] > 10 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
From 3c91bf9ff734f380a4396ec4ff5346a823eba93f Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 10:09:03 +0100
Subject: [PATCH 08/13] Update docs
---
docs/BlockBodiedMethods.md | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/docs/BlockBodiedMethods.md b/docs/BlockBodiedMethods.md
index 9dc6e7a..c74bed8 100644
--- a/docs/BlockBodiedMethods.md
+++ b/docs/BlockBodiedMethods.md
@@ -104,6 +104,32 @@ public string GetStatus()
}
```
+### 7. Multiple Early Returns (converted to nested ternary expressions)
+```csharp
+[Projectable]
+public string GetValueCategory()
+{
+ if (Value > 100)
+ {
+ return "Very High";
+ }
+
+ if (Value > 50)
+ {
+ return "High";
+ }
+
+ if (Value > 10)
+ {
+ return "Medium";
+ }
+
+ return "Low";
+}
+
+// Converted to: Value > 100 ? "Very High" : (Value > 50 ? "High" : (Value > 10 ? "Medium" : "Low"))
+```
+
## Limitations and Warnings
The source generator will produce **warning EFP0003** when it encounters unsupported statements in block-bodied methods:
From e2b1fad5504c358eaed019d542231be3c9c248ef Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 09:32:52 +0000
Subject: [PATCH 09/13] Address code review feedback - fix semantics and
scoping issues
- Add diagnostic for empty block bodies (EFP0003)
- Fix transitive local variable inlining (var a = 1; var b = a + 2; now fully expands)
- Add warning when local variables are used multiple times (semantics preservation)
- Prevent locals in nested blocks from leaking into outer scopes
- Fix documentation to show compilable C# code (no implicit returns)
- Add tests for transitive locals and nested block restrictions
- All 197 tests passing (96 net8.0 + 101 net10.0)
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
docs/BlockBodiedMethods.md | 20 ++++-
.../BlockStatementConverter.cs | 75 ++++++++++++++++++-
...estedBlock_ProducesDiagnostic.verified.txt | 3 +
..._WithTransitiveLocalVariables.verified.txt | 17 +++++
.../ProjectionExpressionGeneratorTests.cs | 62 +++++++++++++++
5 files changed, 174 insertions(+), 3 deletions(-)
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithTransitiveLocalVariables.verified.txt
diff --git a/docs/BlockBodiedMethods.md b/docs/BlockBodiedMethods.md
index c74bed8..494b5fe 100644
--- a/docs/BlockBodiedMethods.md
+++ b/docs/BlockBodiedMethods.md
@@ -59,8 +59,23 @@ public int CalculateDouble()
var doubled = Value * 2;
return doubled + 5;
}
+
+// Transitive inlining is also supported:
+[Projectable]
+public int CalculateComplex()
+{
+ var a = Value * 2;
+ var b = a + 5;
+ return b + 10; // Fully expanded to: Value * 2 + 5 + 10
+}
```
+**⚠️ Important Notes:**
+- Local variables are inlined at each usage point, which duplicates the initializer expression
+- If a local variable is used multiple times, the generator will emit a warning (EFP0003) as this could change semantics if the initializer has side effects
+- Local variables can only be declared at the method body level, not inside nested blocks (if/switch/etc.)
+- Variables are fully expanded transitively (variables that reference other variables are fully inlined)
+
### 5. Switch Statements (converted to nested ternary expressions)
```csharp
[Projectable]
@@ -82,6 +97,7 @@ public string GetValueLabel()
### 6. If Statements Without Else (uses default value)
```csharp
+// Pattern 1: Explicit null return
[Projectable]
public int? GetPremiumIfActive()
{
@@ -89,10 +105,10 @@ public int? GetPremiumIfActive()
{
return Value * 2;
}
- // Implicitly returns null (default for int?)
+ return null; // Explicit return for all code paths
}
-// Or with explicit fallback:
+// Pattern 2: Explicit fallback return
[Projectable]
public string GetStatus()
{
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index d5817e2..eead01d 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -28,6 +30,13 @@ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntax
{
if (block.Statements.Count == 0)
{
+ var diagnostic = Diagnostic.Create(
+ Diagnostics.UnsupportedStatementInBlockBody,
+ block.GetLocation(),
+ memberName,
+ "Block body must contain at least one statement"
+ );
+ _context.ReportDiagnostic(diagnostic);
return null;
}
@@ -145,7 +154,13 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
var variableName = variable.Identifier.Text;
// Rewrite the initializer expression NOW while it's still in the tree
- _localVariables[variableName] = (ExpressionSyntax)_expressionRewriter.Visit(variable.Initializer.Value);
+ var rewrittenInitializer = (ExpressionSyntax)_expressionRewriter.Visit(variable.Initializer.Value);
+
+ // Also expand any previously defined local variables in this initializer
+ // This ensures transitive inlining (e.g., var a = 1; var b = a + 2; return b; -> 1 + 2)
+ rewrittenInitializer = ReplaceLocalVariables(rewrittenInitializer);
+
+ _localVariables[variableName] = rewrittenInitializer;
}
return true;
@@ -165,6 +180,17 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
return TryConvertSwitchStatement(switchStmt, memberName);
case BlockSyntax blockStmt:
+ // Prevent locals declared in nested blocks from leaking into outer scopes
+ var nestedLocal = blockStmt.DescendantNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ if (nestedLocal is not null)
+ {
+ ReportUnsupportedStatement(nestedLocal, memberName, "Local declarations in nested blocks are not supported");
+ return null;
+ }
+
return TryConvertStatements(blockStmt.Statements.ToList(), memberName);
case ExpressionStatementSyntax:
@@ -368,6 +394,28 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
private ExpressionSyntax ReplaceLocalVariables(ExpressionSyntax expression)
{
+ // Count how many times each local variable is referenced
+ var referenceCounter = new LocalVariableReferenceCounter(_localVariables.Keys);
+ referenceCounter.Visit(expression);
+
+ // Warn if any local variable is referenced more than once (semantics could change due to duplication)
+ foreach (var kvp in referenceCounter.ReferenceCounts)
+ {
+ if (kvp.Value > 1)
+ {
+ // This is a warning because inlining still produces correct results for pure expressions,
+ // but could change behavior if the initializer has side effects or is expensive
+ var diagnostic = Diagnostic.Create(
+ Diagnostics.UnsupportedStatementInBlockBody,
+ expression.GetLocation(),
+ "local variable",
+ $"Local variable '{kvp.Key}' is referenced {kvp.Value} times and will be inlined at each use. " +
+ "This may change semantics if the initializer has side effects or is evaluated multiple times."
+ );
+ _context.ReportDiagnostic(diagnostic);
+ }
+ }
+
// Use a rewriter to replace local variable references with their initializer expressions
var rewriter = new LocalVariableReplacer(_localVariables);
return (ExpressionSyntax)rewriter.Visit(expression);
@@ -384,6 +432,31 @@ private void ReportUnsupportedStatement(StatementSyntax statement, string member
_context.ReportDiagnostic(diagnostic);
}
+ private class LocalVariableReferenceCounter : CSharpSyntaxWalker
+ {
+ private readonly HashSet _localVariableNames;
+ public Dictionary ReferenceCounts { get; } = new Dictionary();
+
+ public LocalVariableReferenceCounter(IEnumerable localVariableNames)
+ {
+ _localVariableNames = new HashSet(localVariableNames);
+ foreach (var name in localVariableNames)
+ {
+ ReferenceCounts[name] = 0;
+ }
+ }
+
+ public override void VisitIdentifierName(IdentifierNameSyntax node)
+ {
+ var identifier = node.Identifier.Text;
+ if (_localVariableNames.Contains(identifier))
+ {
+ ReferenceCounts[identifier]++;
+ }
+ base.VisitIdentifierName(node);
+ }
+ }
+
private class LocalVariableReplacer : CSharpSyntaxRewriter
{
private readonly Dictionary _localVariables;
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic.verified.txt
new file mode 100644
index 0000000..587f792
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic.verified.txt
@@ -0,0 +1,3 @@
+[
+ (13,17): warning EFP0003: Method 'Foo' contains an unsupported statement: Local declarations in nested blocks are not supported
+]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithTransitiveLocalVariables.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithTransitiveLocalVariables.verified.txt
new file mode 100644
index 0000000..24ae821
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_WithTransitiveLocalVariables.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar * 2 + 5 + 10;
+ }
+ }
+}
\ 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 3d220b1..c11232b 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -2131,6 +2131,68 @@ public int Foo()
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+ [Fact]
+ public Task BlockBodiedMethod_WithTransitiveLocalVariables()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ var a = Bar * 2;
+ var b = a + 5;
+ return b + 10;
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ if (Bar > 10)
+ {
+ var temp = Bar * 2;
+ return temp;
+ }
+ return 0;
+ }
+ }
+}
+", expectedToCompile: true);
+
+ var result = RunGenerator(compilation);
+
+ // Should have a diagnostic about locals in nested blocks
+ Assert.NotEmpty(result.Diagnostics);
+ Assert.Contains(result.Diagnostics, d => d.Id == "EFP0003");
+
+ return Verifier.Verify(result.Diagnostics.Select(d => d.ToString()));
+ }
+
[Fact]
public Task BlockBodiedMethod_WithMultipleParameters()
{
From f7f296b4efb35d1bcda48b3baac7daa2f7f639ff Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 19:14:01 +0100
Subject: [PATCH 10/13] Improve switch expression support and improve variable
handling
---
.../BlockStatementConverter.cs | 46 ---
.../ExpressionSyntaxRewriter.cs | 43 +-
...urn_WorksCorrectly.DotNet10_0.verified.txt | 2 +
...turn_WorksCorrectly.DotNet9_0.verified.txt | 2 +
...hmeticInReturn_WorksCorrectly.verified.txt | 2 +
...urn_WorksCorrectly.DotNet10_0.verified.txt | 5 +
...turn_WorksCorrectly.DotNet9_0.verified.txt | 5 +
....BooleanReturn_WorksCorrectly.verified.txt | 5 +
...ess_WorksCorrectly.DotNet10_0.verified.txt | 2 +
...cess_WorksCorrectly.DotNet9_0.verified.txt | 2 +
...ditionalAccess_WorksCorrectly.verified.txt | 2 +
...ion_WorksCorrectly.DotNet10_0.verified.txt | 5 +
...tion_WorksCorrectly.DotNet9_0.verified.txt | 5 +
...alWithNegation_WorksCorrectly.verified.txt | 5 +
...se_WithEarlyReturn.DotNet10_0.verified.txt | 6 +
...use_WithEarlyReturn.DotNet9_0.verified.txt | 6 +
...s.GuardClause_WithEarlyReturn.verified.txt | 6 +
...linedMultipleTimes.DotNet10_0.verified.txt | 2 +
...nlinedMultipleTimes.DotNet9_0.verified.txt | 2 +
...eReuse_IsInlinedMultipleTimes.verified.txt | 2 +
...thMultiplePatterns.DotNet10_0.verified.txt | 9 +
...ithMultiplePatterns.DotNet9_0.verified.txt | 9 +
...ndSwitch_WithMultiplePatterns.verified.txt | 9 +
...reInlinedCorrectly.DotNet10_0.verified.txt | 2 +
...AreInlinedCorrectly.DotNet9_0.verified.txt | 2 +
...Variables_AreInlinedCorrectly.verified.txt | 2 +
...thLogicalOperators.DotNet10_0.verified.txt | 7 +
...ithLogicalOperators.DotNet9_0.verified.txt | 7 +
...itionals_WithLogicalOperators.verified.txt | 7 +
...nIf_WorksCorrectly.DotNet10_0.verified.txt | 9 +
...InIf_WorksCorrectly.DotNet9_0.verified.txt | 9 +
...stedSwitchInIf_WorksCorrectly.verified.txt | 9 +
...ary_WorksCorrectly.DotNet10_0.verified.txt | 6 +
...nary_WorksCorrectly.DotNet9_0.verified.txt | 6 +
....NestedTernary_WorksCorrectly.verified.txt | 6 +
...ing_WorksCorrectly.DotNet10_0.verified.txt | 2 +
...cing_WorksCorrectly.DotNet9_0.verified.txt | 2 +
...NullCoalescing_WorksCorrectly.verified.txt | 2 +
...ion_WorksCorrectly.DotNet10_0.verified.txt | 2 +
...tion_WorksCorrectly.DotNet9_0.verified.txt | 2 +
...gInterpolation_WorksCorrectly.verified.txt | 2 +
...hExpression_Simple.DotNet10_0.verified.txt | 7 +
...chExpression_Simple.DotNet9_0.verified.txt | 7 +
...Tests.SwitchExpression_Simple.verified.txt | 7 +
...ession_WithDiscard.DotNet10_0.verified.txt | 7 +
...ression_WithDiscard.DotNet9_0.verified.txt | 7 +
....SwitchExpression_WithDiscard.verified.txt | 7 +
...use_WorksCorrectly.DotNet10_0.verified.txt | 7 +
...ause_WorksCorrectly.DotNet9_0.verified.txt | 7 +
...WithWhenClause_WorksCorrectly.verified.txt | 7 +
...ion_WorksCorrectly.DotNet10_0.verified.txt | 5 +
...sion_WorksCorrectly.DotNet9_0.verified.txt | 5 +
...naryExpression_WorksCorrectly.verified.txt | 5 +
.../BlockBodiedMethodTests.cs | 370 ++++++++++++++++++
54 files changed, 666 insertions(+), 48 deletions(-)
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index eead01d..e768940 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -394,28 +394,6 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
private ExpressionSyntax ReplaceLocalVariables(ExpressionSyntax expression)
{
- // Count how many times each local variable is referenced
- var referenceCounter = new LocalVariableReferenceCounter(_localVariables.Keys);
- referenceCounter.Visit(expression);
-
- // Warn if any local variable is referenced more than once (semantics could change due to duplication)
- foreach (var kvp in referenceCounter.ReferenceCounts)
- {
- if (kvp.Value > 1)
- {
- // This is a warning because inlining still produces correct results for pure expressions,
- // but could change behavior if the initializer has side effects or is expensive
- var diagnostic = Diagnostic.Create(
- Diagnostics.UnsupportedStatementInBlockBody,
- expression.GetLocation(),
- "local variable",
- $"Local variable '{kvp.Key}' is referenced {kvp.Value} times and will be inlined at each use. " +
- "This may change semantics if the initializer has side effects or is evaluated multiple times."
- );
- _context.ReportDiagnostic(diagnostic);
- }
- }
-
// Use a rewriter to replace local variable references with their initializer expressions
var rewriter = new LocalVariableReplacer(_localVariables);
return (ExpressionSyntax)rewriter.Visit(expression);
@@ -432,30 +410,6 @@ private void ReportUnsupportedStatement(StatementSyntax statement, string member
_context.ReportDiagnostic(diagnostic);
}
- private class LocalVariableReferenceCounter : CSharpSyntaxWalker
- {
- private readonly HashSet _localVariableNames;
- public Dictionary ReferenceCounts { get; } = new Dictionary();
-
- public LocalVariableReferenceCounter(IEnumerable localVariableNames)
- {
- _localVariableNames = new HashSet(localVariableNames);
- foreach (var name in localVariableNames)
- {
- ReferenceCounts[name] = 0;
- }
- }
-
- public override void VisitIdentifierName(IdentifierNameSyntax node)
- {
- var identifier = node.Identifier.Text;
- if (_localVariableNames.Contains(identifier))
- {
- ReferenceCounts[identifier]++;
- }
- base.VisitIdentifierName(node);
- }
- }
private class LocalVariableReplacer : CSharpSyntaxRewriter
{
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
index f51c8f6..2b152dd 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
@@ -1,4 +1,4 @@
-using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
@@ -386,8 +386,47 @@ private ExpressionSyntax CreateMethodCallOnEnumValue(IMethodSymbol methodSymbol,
continue;
}
+ // Handle relational patterns (<=, <, >=, >)
+ if (arm.Pattern is RelationalPatternSyntax relational)
+ {
+ // Map the pattern operator token to a binary expression kind
+ var binaryKind = relational.OperatorToken.Kind() switch
+ {
+ SyntaxKind.LessThanToken => SyntaxKind.LessThanExpression,
+ SyntaxKind.LessThanEqualsToken => SyntaxKind.LessThanOrEqualExpression,
+ SyntaxKind.GreaterThanToken => SyntaxKind.GreaterThanExpression,
+ SyntaxKind.GreaterThanEqualsToken => SyntaxKind.GreaterThanOrEqualExpression,
+ _ => throw new InvalidOperationException(
+ $"Unsupported relational operator in switch expression: {relational.OperatorToken.Kind()}")
+ };
+
+ var condition = SyntaxFactory.BinaryExpression(
+ binaryKind,
+ (ExpressionSyntax)Visit(node.GoverningExpression),
+ (ExpressionSyntax)Visit(relational.Expression)
+ );
+
+ // Add the when clause as a AND expression
+ if (arm.WhenClause != null)
+ {
+ condition = SyntaxFactory.BinaryExpression(
+ SyntaxKind.LogicalAndExpression,
+ condition,
+ (ExpressionSyntax)Visit(arm.WhenClause.Condition)
+ );
+ }
+
+ currentExpression = SyntaxFactory.ConditionalExpression(
+ condition,
+ armExpression,
+ currentExpression
+ );
+
+ continue;
+ }
+
throw new InvalidOperationException(
- $"Switch expressions rewriting supports only constant values and declaration patterns (Type var). " +
+ $"Switch expressions rewriting supports constant values, relational patterns (<=, <, >=, >), and declaration patterns (Type var). " +
$"Unsupported pattern: {arm.Pattern.GetType().Name}"
);
}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..3eaf767
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT (CAST([e].[Value] AS float) / 100.0E0) * 50.0E0
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..3eaf767
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT (CAST([e].[Value] AS float) / 100.0E0) * 50.0E0
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..3eaf767
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT (CAST([e].[Value] AS float) / 100.0E0) * 50.0E0
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..e5b6efb
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..e5b6efb
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..e5b6efb
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..ba1f2c1
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT CAST(LEN([e].[Name]) AS int)
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..ba1f2c1
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT CAST(LEN([e].[Name]) AS int)
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..ba1f2c1
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT CAST(LEN([e].[Name]) AS int)
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..4d0592a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN N'Not Active'
+ ELSE N'Active'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..4d0592a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN N'Not Active'
+ ELSE N'Active'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..4d0592a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN N'Not Active'
+ ELSE N'Active'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
new file mode 100644
index 0000000..a29be77
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN 0
+ WHEN [e].[Value] < 0 THEN 0
+ ELSE [e].[Value] * 2
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
new file mode 100644
index 0000000..a29be77
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN 0
+ WHEN [e].[Value] < 0 THEN 0
+ ELSE [e].[Value] * 2
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
new file mode 100644
index 0000000..a29be77
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(0 AS bit) THEN 0
+ WHEN [e].[Value] < 0 THEN 0
+ ELSE [e].[Value] * 2
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
new file mode 100644
index 0000000..eec38d9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
new file mode 100644
index 0000000..eec38d9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
new file mode 100644
index 0000000..eec38d9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
new file mode 100644
index 0000000..257f6f0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[Value] > 50 THEN N'Active Medium'
+ ELSE N'Active Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
new file mode 100644
index 0000000..257f6f0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[Value] > 50 THEN N'Active Medium'
+ ELSE N'Active Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
new file mode 100644
index 0000000..257f6f0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[Value] > 50 THEN N'Active Medium'
+ ELSE N'Active Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..4a903b0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 3 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..4a903b0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 3 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
new file mode 100644
index 0000000..4a903b0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + [e].[Value] * 3 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
new file mode 100644
index 0000000..6973619
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[IsActive] = CAST(1 AS bit) OR [e].[Value] > 50 THEN N'Active or Medium'
+ WHEN [e].[IsActive] = CAST(0 AS bit) AND [e].[Value] <= 10 THEN N'Inactive Low'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
new file mode 100644
index 0000000..6973619
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[IsActive] = CAST(1 AS bit) OR [e].[Value] > 50 THEN N'Active or Medium'
+ WHEN [e].[IsActive] = CAST(0 AS bit) AND [e].[Value] <= 10 THEN N'Inactive Low'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
new file mode 100644
index 0000000..6973619
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100 THEN N'Active High'
+ WHEN [e].[IsActive] = CAST(1 AS bit) OR [e].[Value] > 50 THEN N'Active or Medium'
+ WHEN [e].[IsActive] = CAST(0 AS bit) AND [e].[Value] <= 10 THEN N'Inactive Low'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..5f5a209
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] = 1 THEN N'Active One'
+ WHEN [e].[Value] = 2 THEN N'Active Two'
+ ELSE N'Active Other'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..5f5a209
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] = 1 THEN N'Active One'
+ WHEN [e].[Value] = 2 THEN N'Active Two'
+ ELSE N'Active Other'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..5f5a209
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] = 1 THEN N'Active One'
+ WHEN [e].[Value] = 2 THEN N'Active Two'
+ ELSE N'Active Other'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..9d42002
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ WHEN [e].[Value] > 50 THEN N'Medium'
+ ELSE N'Low'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..52f2a3e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT COALESCE([e].[Name], N'Unknown')
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..52f2a3e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT COALESCE([e].[Name], N'Unknown')
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..52f2a3e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT COALESCE([e].[Name], N'Unknown')
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..e6bf43e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..e6bf43e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..e6bf43e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
new file mode 100644
index 0000000..9ed7fa8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 THEN N'One'
+ WHEN [e].[Value] = 2 THEN N'Two'
+ WHEN [e].[Value] = 3 THEN N'Three'
+ ELSE N'Many'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
new file mode 100644
index 0000000..727148f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] <= 2 THEN N'Low'
+ WHEN [e].[Value] <= 5 THEN N'Medium'
+ WHEN [e].[Value] <= 8 THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
new file mode 100644
index 0000000..727148f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] <= 2 THEN N'Low'
+ WHEN [e].[Value] <= 5 THEN N'Medium'
+ WHEN [e].[Value] <= 8 THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
new file mode 100644
index 0000000..727148f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] <= 2 THEN N'Low'
+ WHEN [e].[Value] <= 5 THEN N'Medium'
+ WHEN [e].[Value] <= 8 THEN N'High'
+ ELSE N'Critical'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..f2343d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active One'
+ WHEN [e].[Value] = 1 THEN N'Inactive One'
+ WHEN [e].[Value] > 10 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active High'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..f2343d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active One'
+ WHEN [e].[Value] = 1 THEN N'Inactive One'
+ WHEN [e].[Value] > 10 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active High'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..f2343d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [e].[Value] = 1 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active One'
+ WHEN [e].[Value] = 1 THEN N'Inactive One'
+ WHEN [e].[Value] > 10 AND [e].[IsActive] = CAST(1 AS bit) THEN N'Active High'
+ ELSE N'Other'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
new file mode 100644
index 0000000..f3f5c43
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN N'Active'
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
index a961442..37a6898 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
@@ -149,6 +149,193 @@ public Task MultipleEarlyReturns_ConvertedToNestedTernaries()
return Verifier.Verify(query.ToQueryString());
}
+
+ [Fact]
+ public Task NullCoalescing_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetNameOrDefault());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ConditionalAccess_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetNameLength());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SwitchExpression_Simple()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValueLabelModern());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SwitchExpression_WithDiscard()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetPriorityModern());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task MultipleLocalVariables_AreInlinedCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.CalculateComplex());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task NestedConditionals_WithLogicalOperators()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetComplexCategory());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task GuardClause_WithEarlyReturn()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetGuardedValue());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task NestedSwitchInIf_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetCombinedLogic());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task TernaryExpression_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValueUsingTernary());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task NestedTernary_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetNestedTernary());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task MixedIfAndSwitch_WithMultiplePatterns()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetComplexMix());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SwitchWithWhenClause_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetValueWithCondition());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task LocalVariableReuse_IsInlinedMultipleTimes()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.CalculateWithReuse());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BooleanReturn_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.IsHighValue());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ConditionalWithNegation_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetInactiveStatus());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task StringInterpolation_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.GetFormattedValue());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ArithmeticInReturn_WorksCorrectly()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.CalculatePercentage());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
}
public static class EntityExtensions
@@ -298,5 +485,188 @@ public static string GetValueCategory(this BlockBodiedMethodTests.Entity entity)
return "Low";
}
+
+ [Projectable]
+ public static string GetNameOrDefault(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Name ?? "Unknown";
+ }
+
+ [Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
+ public static int? GetNameLength(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Name?.Length;
+ }
+
+ [Projectable]
+ public static string GetValueLabelModern(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Value switch
+ {
+ 1 => "One",
+ 2 => "Two",
+ 3 => "Three",
+ _ => "Many"
+ };
+ }
+
+ [Projectable]
+ public static string GetPriorityModern(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Value switch
+ {
+ <= 2 => "Low",
+ <= 5 => "Medium",
+ <= 8 => "High",
+ _ => "Critical"
+ };
+ }
+
+ [Projectable]
+ public static int CalculateComplex(this BlockBodiedMethodTests.Entity entity)
+ {
+ var doubled = entity.Value * 2;
+ var tripled = entity.Value * 3;
+ var sum = doubled + tripled;
+ return sum + 10;
+ }
+
+ [Projectable]
+ public static string GetComplexCategory(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive && entity.Value > 100)
+ {
+ return "Active High";
+ }
+
+ if (entity.IsActive || entity.Value > 50)
+ {
+ return "Active or Medium";
+ }
+
+ if (!entity.IsActive && entity.Value <= 10)
+ {
+ return "Inactive Low";
+ }
+
+ return "Other";
+ }
+
+ [Projectable]
+ public static int GetGuardedValue(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (!entity.IsActive)
+ {
+ return 0;
+ }
+
+ if (entity.Value < 0)
+ {
+ return 0;
+ }
+
+ return entity.Value * 2;
+ }
+
+ [Projectable]
+ public static string GetCombinedLogic(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive)
+ {
+ switch (entity.Value)
+ {
+ case 1:
+ return "Active One";
+ case 2:
+ return "Active Two";
+ default:
+ return "Active Other";
+ }
+ }
+
+ return "Inactive";
+ }
+
+ [Projectable]
+ public static string GetValueUsingTernary(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.IsActive ? "Active" : "Inactive";
+ }
+
+ [Projectable]
+ public static string GetNestedTernary(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Value > 100 ? "High" : entity.Value > 50 ? "Medium" : "Low";
+ }
+
+ [Projectable]
+ public static string GetComplexMix(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.IsActive)
+ {
+ return entity.Value switch
+ {
+ > 100 => "Active High",
+ > 50 => "Active Medium",
+ _ => "Active Low"
+ };
+ }
+
+ return "Inactive";
+ }
+
+ [Projectable]
+ public static string GetValueWithCondition(this BlockBodiedMethodTests.Entity entity)
+ {
+ return entity.Value switch
+ {
+ 1 when entity.IsActive => "Active One",
+ 1 => "Inactive One",
+ > 10 when entity.IsActive => "Active High",
+ _ => "Other"
+ };
+ }
+
+ [Projectable]
+ public static int CalculateWithReuse(this BlockBodiedMethodTests.Entity entity)
+ {
+ var doubled = entity.Value * 2;
+ return doubled + doubled;
+ }
+
+ [Projectable]
+ public static bool IsHighValue(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (entity.Value > 100)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ [Projectable]
+ public static string GetInactiveStatus(this BlockBodiedMethodTests.Entity entity)
+ {
+ if (!entity.IsActive)
+ {
+ return "Not Active";
+ }
+ else
+ {
+ return "Active";
+ }
+ }
+
+ [Projectable]
+ public static string GetFormattedValue(this BlockBodiedMethodTests.Entity entity)
+ {
+ return $"Value: {entity.Value}";
+ }
+
+ [Projectable]
+ public static double CalculatePercentage(this BlockBodiedMethodTests.Entity entity)
+ {
+ return (double)entity.Value / 100.0 * 50.0;
+ }
}
}
From 3b56faaca7a0f81e22ccde7b3656f47eaa2cfcf1 Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 22:20:17 +0100
Subject: [PATCH 11/13] Remove redundant code and add test for projectables in
block bodied methods
---
.../BlockStatementConverter.cs | 64 ++--
...urn_WorksCorrectly.DotNet10_0.verified.txt | 0
...turn_WorksCorrectly.DotNet9_0.verified.txt | 0
...hmeticInReturn_WorksCorrectly.verified.txt | 0
...ers_WorksCorrectly.DotNet10_0.verified.txt | 0
...ters_WorksCorrectly.DotNet9_0.verified.txt | 0
...WithParameters_WorksCorrectly.verified.txt | 0
...urn_WorksCorrectly.DotNet10_0.verified.txt | 0
...turn_WorksCorrectly.DotNet9_0.verified.txt | 0
....BooleanReturn_WorksCorrectly.verified.txt | 0
...ranslatedCorrectly.DotNet10_0.verified.txt | 0
...TranslatedCorrectly.DotNet9_0.verified.txt | 0
...itional_IsTranslatedCorrectly.verified.txt | 0
...ess_WorksCorrectly.DotNet10_0.verified.txt | 0
...cess_WorksCorrectly.DotNet9_0.verified.txt | 0
...ditionalAccess_WorksCorrectly.verified.txt | 0
...ion_WorksCorrectly.DotNet10_0.verified.txt | 0
...tion_WorksCorrectly.DotNet9_0.verified.txt | 0
...alWithNegation_WorksCorrectly.verified.txt | 0
...se_WithEarlyReturn.DotNet10_0.verified.txt | 0
...use_WithEarlyReturn.DotNet9_0.verified.txt | 0
...s.GuardClause_WithEarlyReturn.verified.txt | 0
...ranslatedToTernary.DotNet10_0.verified.txt | 0
...TranslatedToTernary.DotNet9_0.verified.txt | 0
...atement_IsTranslatedToTernary.verified.txt | 0
...utElse_UsesDefault.DotNet10_0.verified.txt | 0
...outElse_UsesDefault.DotNet9_0.verified.txt | 0
...sts.IfWithoutElse_UsesDefault.verified.txt | 0
...WithFallbackReturn.DotNet10_0.verified.txt | 0
..._WithFallbackReturn.DotNet9_0.verified.txt | 0
...ithoutElse_WithFallbackReturn.verified.txt | 0
...linedMultipleTimes.DotNet10_0.verified.txt | 0
...nlinedMultipleTimes.DotNet9_0.verified.txt | 0
...eReuse_IsInlinedMultipleTimes.verified.txt | 0
...Variable_IsInlined.DotNet10_0.verified.txt | 0
...lVariable_IsInlined.DotNet9_0.verified.txt | 0
...Tests.LocalVariable_IsInlined.verified.txt | 0
...thMultiplePatterns.DotNet10_0.verified.txt | 0
...ithMultiplePatterns.DotNet9_0.verified.txt | 0
...ndSwitch_WithMultiplePatterns.verified.txt | 0
...dToNestedTernaries.DotNet10_0.verified.txt | 0
...edToNestedTernaries.DotNet9_0.verified.txt | 0
...ns_ConvertedToNestedTernaries.verified.txt | 0
...reInlinedCorrectly.DotNet10_0.verified.txt | 0
...AreInlinedCorrectly.DotNet9_0.verified.txt | 0
...Variables_AreInlinedCorrectly.verified.txt | 0
...thLogicalOperators.DotNet10_0.verified.txt | 0
...ithLogicalOperators.DotNet9_0.verified.txt | 0
...itionals_WithLogicalOperators.verified.txt | 0
...tedToNestedTernary.DotNet10_0.verified.txt | 0
...atedToNestedTernary.DotNet9_0.verified.txt | 0
...e_IsTranslatedToNestedTernary.verified.txt | 0
...nIf_WorksCorrectly.DotNet10_0.verified.txt | 0
...InIf_WorksCorrectly.DotNet9_0.verified.txt | 0
...stedSwitchInIf_WorksCorrectly.verified.txt | 0
...ary_WorksCorrectly.DotNet10_0.verified.txt | 0
...nary_WorksCorrectly.DotNet9_0.verified.txt | 0
....NestedTernary_WorksCorrectly.verified.txt | 0
...ing_WorksCorrectly.DotNet10_0.verified.txt | 0
...cing_WorksCorrectly.DotNet9_0.verified.txt | 0
...NullCoalescing_WorksCorrectly.verified.txt | 0
..._IsTranslatedToSql.DotNet10_0.verified.txt | 0
...s_IsTranslatedToSql.DotNet9_0.verified.txt | 0
...pertyAccess_IsTranslatedToSql.verified.txt | 0
..._IsTranslatedToSql.DotNet10_0.verified.txt | 0
...n_IsTranslatedToSql.DotNet9_0.verified.txt | 0
...impleReturn_IsTranslatedToSql.verified.txt | 0
...ion_WorksCorrectly.DotNet10_0.verified.txt | 0
...tion_WorksCorrectly.DotNet9_0.verified.txt | 0
...gInterpolation_WorksCorrectly.verified.txt | 0
...hExpression_Simple.DotNet10_0.verified.txt | 0
...chExpression_Simple.DotNet9_0.verified.txt | 0
...Tests.SwitchExpression_Simple.verified.txt | 0
...ession_WithDiscard.DotNet10_0.verified.txt | 0
...ression_WithDiscard.DotNet9_0.verified.txt | 0
....SwitchExpression_WithDiscard.verified.txt | 0
...chStatement_Simple.DotNet10_0.verified.txt | 0
...tchStatement_Simple.DotNet9_0.verified.txt | 0
...dTests.SwitchStatement_Simple.verified.txt | 0
..._WithMultipleCases.DotNet10_0.verified.txt | 0
...t_WithMultipleCases.DotNet9_0.verified.txt | 0
...chStatement_WithMultipleCases.verified.txt | 0
...use_WorksCorrectly.DotNet10_0.verified.txt | 0
...ause_WorksCorrectly.DotNet9_0.verified.txt | 0
...WithWhenClause_WorksCorrectly.verified.txt | 0
...ion_WorksCorrectly.DotNet10_0.verified.txt | 0
...sion_WorksCorrectly.DotNet9_0.verified.txt | 0
...naryExpression_WorksCorrectly.verified.txt | 0
.../BlockBodiedMethodTests.cs | 2 +-
.../BlockBodyProjectableCallTest.cs | 273 ++++++++++++++++++
...Method_InCondition.DotNet10_0.verified.txt | 5 +
...eMethod_InCondition.DotNet9_0.verified.txt | 5 +
...ProjectableMethod_InCondition.verified.txt | 5 +
...thod_InEarlyReturn.DotNet10_0.verified.txt | 9 +
...ethod_InEarlyReturn.DotNet9_0.verified.txt | 9 +
...ojectableMethod_InEarlyReturn.verified.txt | 9 +
...nLogicalExpression.DotNet10_0.verified.txt | 5 +
...InLogicalExpression.DotNet9_0.verified.txt | 5 +
...bleMethod_InLogicalExpression.verified.txt | 5 +
...bleMethod_InReturn.DotNet10_0.verified.txt | 2 +
...ableMethod_InReturn.DotNet9_0.verified.txt | 2 +
...ingProjectableMethod_InReturn.verified.txt | 2 +
...bleMethod_InSwitch.DotNet10_0.verified.txt | 12 +
...ableMethod_InSwitch.DotNet9_0.verified.txt | 12 +
...ingProjectableMethod_InSwitch.verified.txt | 12 +
...InSwitchExpression.DotNet10_0.verified.txt | 19 ++
..._InSwitchExpression.DotNet9_0.verified.txt | 19 ++
...ableMethod_InSwitchExpression.verified.txt | 19 ++
...leMethod_InTernary.DotNet10_0.verified.txt | 8 +
...bleMethod_InTernary.DotNet9_0.verified.txt | 8 +
...ngProjectableMethod_InTernary.verified.txt | 8 +
...bleMethod_Multiple.DotNet10_0.verified.txt | 2 +
...ableMethod_Multiple.DotNet9_0.verified.txt | 2 +
...ingProjectableMethod_Multiple.verified.txt | 2 +
...tableMethod_Nested.DotNet10_0.verified.txt | 2 +
...ctableMethod_Nested.DotNet9_0.verified.txt | 2 +
...llingProjectableMethod_Nested.verified.txt | 2 +
...tableMethod_Simple.DotNet10_0.verified.txt | 2 +
...ctableMethod_Simple.DotNet9_0.verified.txt | 2 +
...llingProjectableMethod_Simple.verified.txt | 2 +
..._WithLocalVariable.DotNet10_0.verified.txt | 2 +
...d_WithLocalVariable.DotNet9_0.verified.txt | 2 +
...tableMethod_WithLocalVariable.verified.txt | 2 +
123 files changed, 499 insertions(+), 44 deletions(-)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt (100%)
rename tests/EntityFrameworkCore.Projectables.FunctionalTests/{ => BlockBodiedMethods}/BlockBodiedMethodTests.cs (99%)
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet10_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet9_0.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.verified.txt
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index e768940..843930f 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -41,8 +41,7 @@ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntax
}
// Try to convert the block statements into an expression
- var result = TryConvertStatements(block.Statements.ToList(), memberName);
- return result;
+ return TryConvertStatements(block.Statements.ToList(), memberName);
}
private ExpressionSyntax? TryConvertStatements(List statements, string memberName)
@@ -95,30 +94,6 @@ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntax
return elseBody;
}
- // Check if we have a single if without else followed by a return (legacy path)
- // This is now redundant with the above logic but kept for clarity and potential optimization
- if (nonReturnStatements.Count == 1 &&
- nonReturnStatements[0] is IfStatementSyntax { Else: null } ifWithoutElse &&
- lastStatement is ReturnStatementSyntax singleFinalReturn)
- {
- // Convert: if (condition) { return x; } return y;
- // To: condition ? x : y
- var ifBody = TryConvertStatement(ifWithoutElse.Statement, memberName);
- if (ifBody == null)
- {
- return null;
- }
-
- var elseBody = TryConvertReturnStatement(singleFinalReturn, memberName);
- if (elseBody == null)
- {
- return null;
- }
-
- var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifWithoutElse.Condition);
- return SyntaxFactory.ConditionalExpression(condition, ifBody, elseBody);
- }
-
// If we reach here, the pattern was not detected
// Process local variable declarations before the final return
foreach (var stmt in nonReturnStatements)
@@ -226,7 +201,7 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
return expression;
}
- private ExpressionSyntax? TryConvertIfStatement(IfStatementSyntax ifStmt, string memberName)
+ private ConditionalExpressionSyntax? TryConvertIfStatement(IfStatementSyntax ifStmt, string memberName)
{
// Convert if-else to conditional (ternary) expression
// First, rewrite the condition using the expression rewriter
@@ -371,25 +346,28 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
{
statements = statements.Take(statements.Count - 1).ToList();
}
-
- if (statements.Count == 0)
+
+ if (statements.Count != 0)
+ {
+ return TryConvertStatements(statements, memberName);
+ }
+
+ // Use the section's first label location for error reporting
+ var firstLabel = section.Labels.FirstOrDefault();
+ if (firstLabel == null)
{
- // Use the section's first label location for error reporting
- var firstLabel = section.Labels.FirstOrDefault();
- if (firstLabel != null)
- {
- var diagnostic = Diagnostic.Create(
- Diagnostics.UnsupportedStatementInBlockBody,
- firstLabel.GetLocation(),
- memberName,
- "Switch section must have at least one statement"
- );
- _context.ReportDiagnostic(diagnostic);
- }
return null;
}
-
- return TryConvertStatements(statements, memberName);
+
+ var diagnostic = Diagnostic.Create(
+ Diagnostics.UnsupportedStatementInBlockBody,
+ firstLabel.GetLocation(),
+ memberName,
+ "Switch section must have at least one statement"
+ );
+ _context.ReportDiagnostic(diagnostic);
+ return null;
+
}
private ExpressionSyntax ReplaceLocalVariables(ExpressionSyntax expression)
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ArithmeticInReturn_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BlockMethodWithParameters_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.BooleanReturn_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ComplexConditional_IsTranslatedCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalAccess_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ConditionalWithNegation_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.GuardClause_WithEarlyReturn.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfElseStatement_IsTranslatedToTernary.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_UsesDefault.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.IfWithoutElse_WithFallbackReturn.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariableReuse_IsInlinedMultipleTimes.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.LocalVariable_IsInlined.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MixedIfAndSwitch_WithMultiplePatterns.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleEarlyReturns_ConvertedToNestedTernaries.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.MultipleLocalVariables_AreInlinedCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedConditionals_WithLogicalOperators.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedIfElse_IsTranslatedToNestedTernary.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedSwitchInIf_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NestedTernary_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.NullCoalescing_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.ReturnWithPropertyAccess_IsTranslatedToSql.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SimpleReturn_IsTranslatedToSql.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.StringInterpolation_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_Simple.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchExpression_WithDiscard.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_Simple.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchStatement_WithMultipleCases.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.SwitchWithWhenClause_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet10_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.DotNet9_0.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
similarity index 100%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.TernaryExpression_WorksCorrectly.verified.txt
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.cs
similarity index 99%
rename from tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
rename to tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.cs
index 37a6898..9622f34 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethodTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodiedMethodTests.cs
@@ -5,7 +5,7 @@
using VerifyXunit;
using Xunit;
-namespace EntityFrameworkCore.Projectables.FunctionalTests
+namespace EntityFrameworkCore.Projectables.FunctionalTests.BlockBodiedMethods
{
[UsesVerify]
public class BlockBodiedMethodTests
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
new file mode 100644
index 0000000..4d3fa96
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
@@ -0,0 +1,273 @@
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using System.Linq;
+using System.Threading.Tasks;
+using VerifyXunit;
+using Xunit;
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests.BlockBodiedMethods
+{
+ ///
+ /// Tests for calling projectable methods from within block-bodied methods
+ ///
+ [UsesVerify]
+ public class BlockBodyProjectableCallTests
+ {
+ public record Entity
+ {
+ public int Id { get; set; }
+ public int Value { get; set; }
+ public bool IsActive { get; set; }
+ public string? Name { get; set; }
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_Simple()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetAdjustedWithConstant());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InReturn()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetDoubledValue());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InCondition()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetCategoryBasedOnAdjusted());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_Multiple()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.CombineProjectableMethods());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InSwitch()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetLabelBasedOnCategory());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InSwitchExpression()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetDescriptionByLevel());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_WithLocalVariable()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.CalculateUsingProjectable());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_Nested()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetNestedProjectableCall());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InEarlyReturn()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetStatusWithProjectableCheck());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InTernary()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.GetConditionalProjectable());
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task BlockBodyCallingProjectableMethod_InLogicalExpression()
+ {
+ using var dbContext = new SampleDbContext();
+ var query = dbContext.Set()
+ .Select(x => x.IsComplexCondition());
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+
+ public static class ProjectableCallExtensions
+ {
+ // Base projectable methods (helper methods)
+ [Projectable]
+ public static int GetConstant(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return 42;
+ }
+
+ [Projectable]
+ public static int GetDoubled(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.Value * 2;
+ }
+
+ [Projectable]
+ public static string GetCategory(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ if (entity.Value > 100)
+ return "High";
+ else
+ return "Low";
+ }
+
+ [Projectable]
+ public static string GetLevel(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ if (entity.Value > 100) return "Level3";
+ if (entity.Value > 50) return "Level2";
+ return "Level1";
+ }
+
+ [Projectable]
+ public static bool IsHighValue(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.Value > 100;
+ }
+
+ // Block-bodied methods calling projectable methods
+
+ [Projectable]
+ public static int GetAdjustedWithConstant(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.Value + entity.GetConstant();
+ }
+
+ [Projectable]
+ public static int GetDoubledValue(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ var doubled = entity.GetDoubled();
+ return doubled;
+ }
+
+ [Projectable]
+ public static string GetCategoryBasedOnAdjusted(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ if (entity.GetDoubled() > 200)
+ {
+ return "Very High";
+ }
+ else
+ {
+ return "Normal";
+ }
+ }
+
+ [Projectable]
+ public static int CombineProjectableMethods(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.GetDoubled() + entity.GetConstant();
+ }
+
+ [Projectable]
+ public static string GetLabelBasedOnCategory(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ switch (entity.GetCategory())
+ {
+ case "High":
+ return "Premium";
+ case "Low":
+ return "Standard";
+ default:
+ return "Unknown";
+ }
+ }
+
+ [Projectable]
+ public static string GetDescriptionByLevel(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.GetLevel() switch
+ {
+ "Level3" => "Expert",
+ "Level2" => "Intermediate",
+ "Level1" => "Beginner",
+ _ => "Unknown"
+ };
+ }
+
+ [Projectable]
+ public static int CalculateUsingProjectable(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ var doubled = entity.GetDoubled();
+ var withConstant = doubled + entity.GetConstant();
+ return withConstant * 2;
+ }
+
+ [Projectable]
+ public static int GetNestedProjectableCall(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.GetAdjustedWithConstant() + 10;
+ }
+
+ [Projectable]
+ public static string GetStatusWithProjectableCheck(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ if (entity.IsHighValue())
+ return "Premium";
+
+ if (entity.GetCategory() == "High")
+ return "Standard High";
+
+ return "Normal";
+ }
+
+ [Projectable]
+ public static string GetConditionalProjectable(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.IsActive ? entity.GetCategory() : "Inactive";
+ }
+
+ // [Projectable]
+ // public static string GetChainedResult(this BlockBodyProjectableCallTests.Entity entity)
+ // {
+ // var doubled = entity.GetDoubled();
+ //
+ // if (doubled > 200)
+ // {
+ // return entity.GetCategory() + " Priority";
+ // }
+ //
+ // return entity.GetLevel();
+ // }
+
+ [Projectable]
+ public static bool IsComplexCondition(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ return entity.IsActive && entity.IsHighValue() || entity.GetDoubled() > 150;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet10_0.verified.txt
new file mode 100644
index 0000000..478d0ba
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] * 2 > 200 THEN N'Very High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet9_0.verified.txt
new file mode 100644
index 0000000..478d0ba
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] * 2 > 200 THEN N'Very High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.verified.txt
new file mode 100644
index 0000000..478d0ba
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InCondition.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN [e].[Value] * 2 > 200 THEN N'Very High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet10_0.verified.txt
new file mode 100644
index 0000000..bd650a0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet10_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Standard High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet9_0.verified.txt
new file mode 100644
index 0000000..bd650a0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.DotNet9_0.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Standard High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.verified.txt
new file mode 100644
index 0000000..bd650a0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InEarlyReturn.verified.txt
@@ -0,0 +1,9 @@
+SELECT CASE
+ WHEN [e].[Value] > 100 THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Standard High'
+ ELSE N'Normal'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet10_0.verified.txt
new file mode 100644
index 0000000..de3373a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet10_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN ([e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100) OR [e].[Value] * 2 > 150 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet9_0.verified.txt
new file mode 100644
index 0000000..de3373a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.DotNet9_0.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN ([e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100) OR [e].[Value] * 2 > 150 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.verified.txt
new file mode 100644
index 0000000..de3373a
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InLogicalExpression.verified.txt
@@ -0,0 +1,5 @@
+SELECT CASE
+ WHEN ([e].[IsActive] = CAST(1 AS bit) AND [e].[Value] > 100) OR [e].[Value] * 2 > 150 THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet10_0.verified.txt
new file mode 100644
index 0000000..dea1914
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet9_0.verified.txt
new file mode 100644
index 0000000..dea1914
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.verified.txt
new file mode 100644
index 0000000..dea1914
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InReturn.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet10_0.verified.txt
new file mode 100644
index 0000000..927c6ff
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet10_0.verified.txt
@@ -0,0 +1,12 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'Low' THEN N'Standard'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet9_0.verified.txt
new file mode 100644
index 0000000..927c6ff
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.DotNet9_0.verified.txt
@@ -0,0 +1,12 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'Low' THEN N'Standard'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.verified.txt
new file mode 100644
index 0000000..927c6ff
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitch.verified.txt
@@ -0,0 +1,12 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'High' THEN N'Premium'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END = N'Low' THEN N'Standard'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet10_0.verified.txt
new file mode 100644
index 0000000..409a445
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet10_0.verified.txt
@@ -0,0 +1,19 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level3' THEN N'Expert'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level2' THEN N'Intermediate'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level1' THEN N'Beginner'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet9_0.verified.txt
new file mode 100644
index 0000000..409a445
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.DotNet9_0.verified.txt
@@ -0,0 +1,19 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level3' THEN N'Expert'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level2' THEN N'Intermediate'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level1' THEN N'Beginner'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.verified.txt
new file mode 100644
index 0000000..409a445
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InSwitchExpression.verified.txt
@@ -0,0 +1,19 @@
+SELECT CASE
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level3' THEN N'Expert'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level2' THEN N'Intermediate'
+ WHEN CASE
+ WHEN [e].[Value] > 100 THEN N'Level3'
+ WHEN [e].[Value] > 50 THEN N'Level2'
+ ELSE N'Level1'
+ END = N'Level1' THEN N'Beginner'
+ ELSE N'Unknown'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet10_0.verified.txt
new file mode 100644
index 0000000..ad971d0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet10_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet9_0.verified.txt
new file mode 100644
index 0000000..ad971d0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.DotNet9_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.verified.txt
new file mode 100644
index 0000000..ad971d0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_InTernary.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [e].[IsActive] = CAST(1 AS bit) THEN CASE
+ WHEN [e].[Value] > 100 THEN N'High'
+ ELSE N'Low'
+ END
+ ELSE N'Inactive'
+END
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet10_0.verified.txt
new file mode 100644
index 0000000..69eb4b8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet9_0.verified.txt
new file mode 100644
index 0000000..69eb4b8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.verified.txt
new file mode 100644
index 0000000..69eb4b8
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Multiple.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet10_0.verified.txt
new file mode 100644
index 0000000..72fc7ea
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet9_0.verified.txt
new file mode 100644
index 0000000..72fc7ea
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.verified.txt
new file mode 100644
index 0000000..72fc7ea
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Nested.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42 + 10
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet10_0.verified.txt
new file mode 100644
index 0000000..0bb6121
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet9_0.verified.txt
new file mode 100644
index 0000000..0bb6121
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.verified.txt
new file mode 100644
index 0000000..0bb6121
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_Simple.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] + 42
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet10_0.verified.txt
new file mode 100644
index 0000000..0294ea7
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 84
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet9_0.verified.txt
new file mode 100644
index 0000000..0294ea7
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 84
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.verified.txt
new file mode 100644
index 0000000..0294ea7
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTests.BlockBodyCallingProjectableMethod_WithLocalVariable.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Value] * 2 + 84
+FROM [Entity] AS [e]
\ No newline at end of file
From ff4feb1670e7fbff8b4a9af25b7ca9eea527dfb6 Mon Sep 17 00:00:00 2001
From: "fabien.menager"
Date: Sun, 15 Feb 2026 22:27:12 +0100
Subject: [PATCH 12/13] Fix new case
---
.../BlockStatementConverter.cs | 56 ++++++++++++-------
.../BlockBodyProjectableCallTest.cs | 24 ++++----
2 files changed, 49 insertions(+), 31 deletions(-)
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index 843930f..2093d98 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -61,14 +61,39 @@ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntax
var nonReturnStatements = statements.Take(statements.Count - 1).ToList();
var lastStatement = statements.Last();
+ // First, process any local variable declarations at the beginning
+ var localDeclStatements = new List();
+ var remainingStatements = new List();
+
+ foreach (var stmt in nonReturnStatements)
+ {
+ if (stmt is LocalDeclarationStatementSyntax localDecl)
+ {
+ localDeclStatements.Add(localDecl);
+ }
+ else
+ {
+ remainingStatements.Add(stmt);
+ }
+ }
+
+ // Process local variable declarations first
+ foreach (var localDecl in localDeclStatements)
+ {
+ if (!TryProcessLocalDeclaration(localDecl, memberName))
+ {
+ return null;
+ }
+ }
+
// Check if we have a pattern like multiple if statements without else followed by a final return:
- // if (a) return 1; if (b) return 2; return 3;
+ // var x = ...; if (a) return 1; if (b) return 2; return 3;
// This can be converted to nested ternaries: a ? 1 : (b ? 2 : 3)
if (lastStatement is ReturnStatementSyntax finalReturn &&
- nonReturnStatements.All(s => s is IfStatementSyntax { Else: null }))
+ remainingStatements.All(s => s is IfStatementSyntax { Else: null }))
{
- // All non-return statements are if statements without else
- var ifStatements = nonReturnStatements.Cast().ToList();
+ // All remaining non-return statements are if statements without else
+ var ifStatements = remainingStatements.Cast().ToList();
// Start with the final return as the base expression
var elseBody = TryConvertReturnStatement(finalReturn, memberName);
@@ -87,29 +112,22 @@ public BlockStatementConverter(SourceProductionContext context, ExpressionSyntax
return null;
}
+ // Rewrite the condition and replace any local variables
var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifStmt.Condition);
+ condition = ReplaceLocalVariables(condition);
+
elseBody = SyntaxFactory.ConditionalExpression(condition, ifBody, elseBody);
}
return elseBody;
}
- // If we reach here, the pattern was not detected
- // Process local variable declarations before the final return
- foreach (var stmt in nonReturnStatements)
+ // If there are any remaining non-if statements, they're not supported
+ if (remainingStatements.Count > 0)
{
- if (stmt is LocalDeclarationStatementSyntax localDecl)
- {
- if (!TryProcessLocalDeclaration(localDecl, memberName))
- {
- return null;
- }
- }
- else
- {
- ReportUnsupportedStatement(stmt, memberName, "Only local variable declarations are supported before the return statement");
- return null;
- }
+ ReportUnsupportedStatement(remainingStatements[0], memberName,
+ "Only local variable declarations and if statements without else (with return) are supported before the final return statement");
+ return null;
}
// Convert the final statement (should be a return)
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
index 4d3fa96..c09237b 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/BlockBodiedMethods/BlockBodyProjectableCallTest.cs
@@ -251,18 +251,18 @@ public static string GetConditionalProjectable(this BlockBodyProjectableCallTest
return entity.IsActive ? entity.GetCategory() : "Inactive";
}
- // [Projectable]
- // public static string GetChainedResult(this BlockBodyProjectableCallTests.Entity entity)
- // {
- // var doubled = entity.GetDoubled();
- //
- // if (doubled > 200)
- // {
- // return entity.GetCategory() + " Priority";
- // }
- //
- // return entity.GetLevel();
- // }
+ [Projectable]
+ public static string GetChainedResult(this BlockBodyProjectableCallTests.Entity entity)
+ {
+ var doubled = entity.GetDoubled();
+
+ if (doubled > 200)
+ {
+ return entity.GetCategory() + " Priority";
+ }
+
+ return entity.GetLevel();
+ }
[Projectable]
public static bool IsComplexCondition(this BlockBodyProjectableCallTests.Entity entity)
From 7ba8a84d5ab03529d0f550711fccd6a2bb3a8f60 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Feb 2026 07:35:16 +0000
Subject: [PATCH 13/13] Fix local variable replacement in conditions and switch
expressions
- Apply ReplaceLocalVariables to if statement conditions
- Apply ReplaceLocalVariables to switch expressions
- Apply ReplaceLocalVariables to case label values
- Remove double BOM character from ExpressionSyntaxRewriter.cs
- Fix documentation to match actual behavior (no multiple usage warning)
- Add tests for locals in if conditions and switch expressions
- All 201 tests passing (98 net8.0 + 103 net10.0)
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
---
docs/BlockBodiedMethods.md | 2 +-
.../BlockStatementConverter.cs | 12 +++-
.../ExpressionSyntaxRewriter.cs | 2 +-
...diedMethod_LocalInIfCondition.verified.txt | 17 +++++
...ethod_LocalInSwitchExpression.verified.txt | 17 +++++
.../ProjectionExpressionGeneratorTests.cs | 71 +++++++++++++++++++
6 files changed, 118 insertions(+), 3 deletions(-)
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInIfCondition.verified.txt
create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInSwitchExpression.verified.txt
diff --git a/docs/BlockBodiedMethods.md b/docs/BlockBodiedMethods.md
index 494b5fe..fe19c69 100644
--- a/docs/BlockBodiedMethods.md
+++ b/docs/BlockBodiedMethods.md
@@ -72,7 +72,7 @@ public int CalculateComplex()
**⚠️ Important Notes:**
- Local variables are inlined at each usage point, which duplicates the initializer expression
-- If a local variable is used multiple times, the generator will emit a warning (EFP0003) as this could change semantics if the initializer has side effects
+- If a local variable is used multiple times, its initializer expression is duplicated at each usage, which can change semantics if the initializer has side effects
- Local variables can only be declared at the method body level, not inside nested blocks (if/switch/etc.)
- Variables are fully expanded transitively (variables that reference other variables are fully inlined)
diff --git a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
index 2093d98..5192612 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
@@ -224,6 +224,9 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
// Convert if-else to conditional (ternary) expression
// First, rewrite the condition using the expression rewriter
var condition = (ExpressionSyntax)_expressionRewriter.Visit(ifStmt.Condition);
+
+ // Then replace any local variable references with their already-rewritten initializers
+ condition = ReplaceLocalVariables(condition);
var whenTrue = TryConvertStatement(ifStmt.Statement, memberName);
if (whenTrue == null)
@@ -264,6 +267,9 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
// Process sections in reverse order to build from the default case up
var switchExpression = (ExpressionSyntax)_expressionRewriter.Visit(switchStmt.Expression);
+ // Replace any local variable references in the switch expression
+ switchExpression = ReplaceLocalVariables(switchExpression);
+
ExpressionSyntax? currentExpression;
// Find default case first
@@ -317,10 +323,14 @@ private bool TryProcessLocalDeclaration(LocalDeclarationStatementSyntax localDec
{
if (label is CaseSwitchLabelSyntax caseLabel)
{
+ // Rewrite and replace locals in case label value
+ var caseLabelValue = (ExpressionSyntax)_expressionRewriter.Visit(caseLabel.Value);
+ caseLabelValue = ReplaceLocalVariables(caseLabelValue);
+
var labelCondition = SyntaxFactory.BinaryExpression(
SyntaxKind.EqualsExpression,
switchExpression,
- (ExpressionSyntax)_expressionRewriter.Visit(caseLabel.Value)
+ caseLabelValue
);
condition = condition == null
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
index 2b152dd..ef46396 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
@@ -1,4 +1,4 @@
-using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInIfCondition.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInIfCondition.verified.txt
new file mode 100644
index 0000000..e940c26
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInIfCondition.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar * 2 > 10 ? 1 : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInSwitchExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInSwitchExpression.verified.txt
new file mode 100644
index 0000000..0a7e7da
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_LocalInSwitchExpression.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_C_Foo
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.C @this) => @this.Bar * 2 == 2 ? "Two" : @this.Bar * 2 == 4 ? "Four" : "Other";
+ }
+ }
+}
\ 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 c11232b..3254530 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
@@ -2160,6 +2160,77 @@ public int Foo()
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
+ [Fact]
+ public Task BlockBodiedMethod_LocalInIfCondition()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public int Foo()
+ {
+ var threshold = Bar * 2;
+ if (threshold > 10)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
+ [Fact]
+ public Task BlockBodiedMethod_LocalInSwitchExpression()
+ {
+ var compilation = CreateCompilation(@"
+using System;
+using EntityFrameworkCore.Projectables;
+namespace Foo {
+ class C {
+ public int Bar { get; set; }
+
+ [Projectable]
+ public string Foo()
+ {
+ var value = Bar * 2;
+ switch (value)
+ {
+ case 2:
+ return ""Two"";
+ case 4:
+ return ""Four"";
+ default:
+ return ""Other"";
+ }
+ }
+ }
+}
+");
+
+ var result = RunGenerator(compilation);
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedTrees);
+
+ return Verifier.Verify(result.GeneratedTrees[0].ToString());
+ }
+
[Fact]
public Task BlockBodiedMethod_LocalsInNestedBlock_ProducesDiagnostic()
{