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() {