Skip to content

Add support for projectable constructors#160

Closed
PhenX wants to merge 36 commits intomasterfrom
feature/projectable-constructor
Closed

Add support for projectable constructors#160
PhenX wants to merge 36 commits intomasterfrom
feature/projectable-constructor

Conversation

@PhenX
Copy link
Copy Markdown
Member

@PhenX PhenX commented Mar 1, 2026

No description provided.

Copilot AI and others added 30 commits February 14, 2026 11:26
- 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>
- 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>
- 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>
- 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>
…tion

# Conflicts:
#	tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
- 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>
- 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>
- Add specific diagnostics (EFP0004, EFP0005) for side effects
- Detect property assignments with clear error messages
- Detect compound assignments (+=, -=, etc.)
- Detect increment/decrement operators (++, --)
- Warn about non-projectable method calls
- Error messages now point to the exact problematic line
- All 209 tests passing (102 net8.0 + 107 net10.0)

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
- Document all detected side effects with examples
- Show before/after comparison of error messages
- Explain diagnostic codes EFP0004 and EFP0005
- Provide clear guidance for developers

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
- Add VisitIsPatternExpression to ExpressionSyntaxRewriter
- Convert pattern matching to equivalent expressions:
  - RecursivePattern: entity is { Prop: value } → null check + property checks
  - RelationalPattern: value is > 100 → value > 100
  - ConstantPattern: entity is null → entity == null
  - UnaryPattern: entity is not null → !(entity == null)
- Add comprehensive tests for all pattern types
- All 217 tests passing (106 net8.0 + 111 net10.0)

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
- Document all supported pattern types with examples
- Explain conversion logic and benefits
- Provide complex examples showing nested patterns
- Document limitations and error handling

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Pattern matching support has been moved to a separate branch/PR.
This PR now focuses solely on block-bodied method support:
- If-else statements
- Switch statements
- Local variables
- Side effect detection

Reverted commits:
- adc95f5: Add documentation for pattern matching support
- 31f4267: Fix pattern matching support in block-bodied methods
- f2a805e: Initial exploration - understand pattern matching crash issue

All 209 tests passing (102 net8.0 + 107 net10.0 generator tests, plus functional and unit tests)

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Code Review Fixes:
- Use proper type resolution for [Projectable] attribute check (SymbolEqualityComparer)
- Add parentheses when inlining local variables to preserve operator precedence
- Restrict multiple if-without-else pattern to simple return bodies

Documentation Updates:
- Add block-bodied methods FAQ section to README with examples
- Merge SideEffectDetection.md content into BlockBodiedMethods.md
- Remove standalone SideEffectDetection.md file
- Link to BlockBodiedMethods.md from README

All 209 tests passing

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
fabien.menager added 6 commits February 21, 2026 23:55
# Conflicts:
#	docs/BlockBodiedMembers.md
#	src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md
#	src/EntityFrameworkCore.Projectables.Generator/BlockStatementConverter.cs
#	src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs
#	src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
#	tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_IfWithoutElse_ImplicitReturn.verified.txt
#	tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BlockBodiedMethod_SwitchStatement_WithoutDefault.verified.txt
#	tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for marking constructors with [Projectable], enabling the generator + runtime rewriter to expand new T(...) into expression-tree-friendly projections (typically via object initializers) for EF Core queries.

Changes:

  • Allow [Projectable] on constructors and generate constructor projection expression factories (using a synthetic _ctor member name for lookup).
  • Update runtime expression replacement to expand NewExpression (constructor calls) using the generated projection expressions.
  • Add extensive generator snapshot tests and EF Core functional tests validating constructor projection behavior (including base(...) / this(...) initializer chains).

Reviewed changes

Copilot reviewed 102 out of 102 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs Extends attribute targets to include constructors.
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs Resolves generated expressions for constructors using synthetic _ctor name + parameter types.
src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs Expands new T(...) expressions by inlining the generated constructor projection expression.
src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs Generates projection bodies for projectable constructors (including initializer-chain propagation).
src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs New helper to convert constructor bodies (assignments/locals/if-else) into member-init bindings.
src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs Adds EFP0007 for missing parameterless constructor required by object-initializer projection pattern.
src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md Documents new diagnostic EFP0007.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs Adds generator tests covering many constructor scenarios (body assignments, base/this initializers, locals, if/else, diagnostics).
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt Snapshot for constructor body assignments projection output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt Snapshot for constructor with : base(...) projection output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt Snapshot for overloaded projectable constructors output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt Snapshot for constructor taking an entity/class argument output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt Snapshot for constructor with multiple class args output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt Snapshot for if/else logic in ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt .NET 8 snapshot for if/else ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt Snapshot for local variable in ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt .NET 8 snapshot for local variable in ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt Snapshot for base initializer expression in ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt .NET 8 snapshot for base initializer expression output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt Snapshot for base ctor logic + derived ctor mapping output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt .NET 8 snapshot for base ctor logic + derived ctor mapping output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt Snapshot for if-without-else ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt .NET 8 snapshot for if-without-else ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt Snapshot for referencing previously-assigned properties output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt .NET 8 snapshot for referencing previously-assigned properties output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt Snapshot for derived ctor referencing base-assigned property output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt .NET 8 snapshot for derived ctor referencing base-assigned property output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt Snapshot for const/static member usage in ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt .NET 8 snapshot for const/static member usage output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt Snapshot for derived ctor referencing values assigned in base ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt .NET 8 snapshot for derived ctor referencing values assigned in base ctor output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt Snapshot for : this(...) delegation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt .NET 8 snapshot for : this(...) delegation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt Snapshot for : this(...) + body-after delegation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt .NET 8 snapshot for : this(...) + body-after delegation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt Snapshot for : this(...) where delegated ctor has if/else output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt .NET 8 snapshot for : this(...) delegated if/else output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt Snapshot for chained this(...) then base(...) propagation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt .NET 8 snapshot for chained this(...) then base(...) propagation output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt Snapshot for this-delegation referencing previously-assigned props output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt .NET 8 snapshot for this-delegation referencing previously-assigned props output.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt Snapshot validating explicit parameterless ctor requirement success case.
tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt .NET 8 snapshot for explicit parameterless ctor requirement success case.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs New functional tests exercising constructor projection end-to-end with EF Core SQL generation.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt SQL snapshot for scalar-fields ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt .NET 9 SQL snapshot for scalar-fields ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt .NET 10 SQL snapshot for scalar-fields ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt SQL snapshot for entity-arg ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt .NET 9 SQL snapshot for entity-arg ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt .NET 10 SQL snapshot for entity-arg ctor projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt SQL snapshot for derived ctor with base initializer propagation.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt .NET 9 SQL snapshot for derived/base ctor propagation.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt .NET 10 SQL snapshot for derived/base ctor propagation.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt SQL snapshot for overloaded ctor (3 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt .NET 9 SQL snapshot for overloaded ctor (3 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt .NET 10 SQL snapshot for overloaded ctor (3 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt SQL snapshot for overloaded ctor (2 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt .NET 9 SQL snapshot for overloaded ctor (2 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt .NET 10 SQL snapshot for overloaded ctor (2 args).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt SQL snapshot ensuring unassigned ctor property isn’t projected.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt .NET 9 SQL snapshot for unassigned property not projected.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt .NET 10 SQL snapshot for unassigned property not projected.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt SQL snapshot for ctor if/else logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt .NET 9 SQL snapshot for ctor if/else logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt .NET 10 SQL snapshot for ctor if/else logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt SQL snapshot for ctor local-variable logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt .NET 9 SQL snapshot for ctor local-variable logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt .NET 10 SQL snapshot for ctor local-variable logic.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt SQL snapshot for base initializer expression.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt .NET 9 SQL snapshot for base initializer expression.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt .NET 10 SQL snapshot for base initializer expression.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt SQL snapshot for base ctor logic + derived projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt .NET 9 SQL snapshot for base ctor logic + derived projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt .NET 10 SQL snapshot for base ctor logic + derived projection.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt SQL snapshot for referencing previously assigned props.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt .NET 8 SQL snapshot for referencing previously assigned props.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt .NET 9 SQL snapshot for referencing previously assigned props.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt .NET 10 SQL snapshot for referencing previously assigned props.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt SQL snapshot for derived referencing base-assigned property.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt .NET 8 SQL snapshot for derived referencing base-assigned property.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt .NET 9 SQL snapshot for derived referencing base-assigned property.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt .NET 10 SQL snapshot for derived referencing base-assigned property.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt SQL snapshot for const/static member usage.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt .NET 8 SQL snapshot for const/static member usage.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt .NET 9 SQL snapshot for const/static member usage.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt .NET 10 SQL snapshot for const/static member usage.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt SQL snapshot for this(...) delegation (simple).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt .NET 8 SQL snapshot for this(...) delegation (simple).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt .NET 9 SQL snapshot for this(...) delegation (simple).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt .NET 10 SQL snapshot for this(...) delegation (simple).
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt SQL snapshot for this(...) delegation + body.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt .NET 8 SQL snapshot for this(...) delegation + body.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt .NET 9 SQL snapshot for this(...) delegation + body.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt .NET 10 SQL snapshot for this(...) delegation + body.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt SQL snapshot for this(...) delegation with delegated if/else.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt .NET 8 SQL snapshot for this(...) delegated if/else.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt .NET 9 SQL snapshot for this(...) delegated if/else.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt .NET 10 SQL snapshot for this(...) delegated if/else.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt SQL snapshot for chained this(...)base(...) behavior.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt .NET 8 SQL snapshot for chained this(...)base(...) behavior.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt .NET 9 SQL snapshot for chained this(...)base(...) behavior.
tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt .NET 10 SQL snapshot for chained this(...)base(...) behavior.
Comments suppressed due to low confidence (2)

src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs:384

  • AssignedPropertySubstitutor only inlines member-access when the receiver is an IdentifierNameSyntax ("@this"/"this"), but delegated constructor bodies are processed with the identity rewriter, so property references can appear as this.Prop (ThisExpressionSyntax). Those won’t be inlined today. Consider handling MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax, ... } as well, so this.Prop is substituted consistently in delegated constructor bodies.
            // Catches @this.PropName → inline the accumulated expression for PropName.
            public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
            {
                if (node.Expression is IdentifierNameSyntax thisRef &&
                    (thisRef.Identifier.Text == "@this" || thisRef.Identifier.ValueText == "this") &&
                    node.Name is IdentifierNameSyntax propName &&
                    _accumulated.TryGetValue(propName.Identifier.Text, out var replacement))
                {
                    return SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia())
                        .WithTriviaFrom(node);
                }

                return base.VisitMemberAccessExpression(node);
            }

tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs:291

  • The XML doc says this constructor delegates "using a split on the full name", but the implementation delegates via : this(fullName, "") without splitting. Either update the comment to match the behavior or update the ctor to actually split the full name, so the documentation stays accurate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +624 to +626
// The delegated ctor's initializer is part of the original syntax tree,
// so we can safely use the semantic model to resolve its symbol.
var semanticModel = expressionSyntaxRewriter.GetSemanticModel();
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CollectDelegatedConstructorAssignments resolves delegated initializer symbols using expressionSyntaxRewriter.GetSemanticModel(), but that SemanticModel is bound to the original member’s SyntaxTree. If the delegated/base constructor is declared in a different file (common with partials or base types), SemanticModel.GetSymbolInfo(delegatedInitializer) will throw because the node isn’t from that tree. Consider obtaining the semantic model for syntax.SyntaxTree (e.g., via Compilation.GetSemanticModel(syntax.SyntaxTree)) and using that instead, or pass the Compilation/semantic-model provider into this helper.

Suggested change
// The delegated ctor's initializer is part of the original syntax tree,
// so we can safely use the semantic model to resolve its symbol.
var semanticModel = expressionSyntaxRewriter.GetSemanticModel();
// Obtain a SemanticModel for the delegated constructor's syntax tree so that
// the initializer node belongs to the same tree as the semantic model.
var semanticModel = context.Compilation.GetSemanticModel(syntax.SyntaxTree);

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +121
private bool TryProcessBlock(
IEnumerable<StatementSyntax> statements,
Dictionary<string, ExpressionSyntax> assignments,
string memberName,
IReadOnlyDictionary<string, ExpressionSyntax>? outerContext)
{
foreach (var statement in statements)
{
// Everything accumulated so far (from outer scope + this block) is visible.
var visible = BuildVisible(outerContext, assignments);
if (!TryProcessStatement(statement, assignments, memberName, visible))
{
return false;
}
}
return true;
}

private bool TryProcessStatement(
StatementSyntax statement,
Dictionary<string, ExpressionSyntax> assignments,
string memberName,
IReadOnlyDictionary<string, ExpressionSyntax>? visibleContext)
{
switch (statement)
{
case LocalDeclarationStatementSyntax localDecl:
return TryProcessLocalDeclaration(localDecl, memberName, visibleContext);

case ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax assignment }
when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression):
return TryProcessAssignment(assignment, assignments, memberName, visibleContext);

case IfStatementSyntax ifStmt:
return TryProcessIfStatement(ifStmt, assignments, memberName, visibleContext);

case BlockSyntax block:
return TryProcessBlock(block.Statements, assignments, memberName, visibleContext);

default:
ReportUnsupported(statement, memberName,
$"Statement type '{statement.GetType().Name}' is not supported in a [Projectable] constructor body. " +
"Only assignments, local variable declarations, and if/else statements are supported.");
return false;
}
}

private bool TryProcessLocalDeclaration(
LocalDeclarationStatementSyntax localDecl,
string memberName,
IReadOnlyDictionary<string, ExpressionSyntax>? visibleContext)
{
foreach (var variable in localDecl.Declaration.Variables)
{
if (variable.Initializer == null)
{
ReportUnsupported(localDecl, memberName, "Local variables must have an initializer");
return false;
}

var rewritten = _rewrite(variable.Initializer.Value);
rewritten = ApplySubstitutions(rewritten, visibleContext);
_localVariables[variable.Identifier.Text] = rewritten;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConstructorBodyConverter tracks local variables in a single _localVariables dictionary with no scope management, but it also processes nested blocks (including if/else blocks). A local declared inside a branch/block can leak into later substitutions outside its C# scope and can incorrectly shadow property references (e.g., a local declared in an if-block with the same name as a property). Either disallow local declarations in nested blocks (similar to BlockStatementConverter) or implement a scoped stack (push/pop per block) so locals don’t escape their block/branch.

Copilot uses AI. Check for mistakes.
Comment on lines +533 to +547
// Verify the containing type has a parameterless (instance) constructor.
// The generated projection is: new T() { Prop = ... }, which requires one.
// INamedTypeSymbol.Constructors covers all partial declarations and also
// the implicit parameterless constructor that the compiler synthesizes when
// no constructors are explicitly defined.
var hasParameterlessConstructor = containingType.Constructors
.Any(c => !c.IsStatic && c.Parameters.IsEmpty);

if (!hasParameterlessConstructor)
{
context.ReportDiagnostic(Diagnostic.Create(
Diagnostics.MissingParameterlessConstructor,
constructorDeclarationSyntax.GetLocation(),
containingType.Name));
return null;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameterless-constructor check only verifies existence (any non-static ctor with zero parameters) but doesn’t verify accessibility from the generated projection class. If the only parameterless ctor is private/protected/private-protected, the generator will still emit new T() { ... }, which will fail compilation. Consider checking DeclaredAccessibility (e.g., public/internal/protected internal) and aligning the EFP0007 diagnostic text accordingly.

Copilot uses AI. Check for mistakes.
@PhenX PhenX closed this Mar 1, 2026
@PhenX PhenX deleted the feature/projectable-constructor branch March 1, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants