Implement partial class support for projectable members#193
Implement partial class support for projectable members#193
Conversation
…ess to private fields and methods in generated companions
There was a problem hiding this comment.
Pull request overview
This PR adds generator support for emitting companion types nested inside user-declared partial types, enabling generated expressions to access private/protected members of the declaring type (and updates the projection registry naming accordingly).
Changes:
- Generate companions nested under the user’s partial type chain (instead of the
EntityFrameworkCore.Projectables.Generatednamespace) when the declaring type is partial. - Update registry entries to use CLR nested-type naming (
+) when companions are generated as nested types. - Add new generator snapshot tests and functional EF Core tests validating private-member access via partial types.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs | Nests companions inside partial type shells; adjusts registry full type name for nested companions. |
| src/EntityFrameworkCore.Projectables.Generator/Models/ProjectableDescriptor.cs | Adds IsDeclaringTypePartial flag to drive partial nesting behavior. |
| src/EntityFrameworkCore.Projectables.Generator/Interpretation/ProjectableInterpreter.cs | Detects whether the declaring type is partial and stores it on the descriptor. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/UseMemberBodyTests.Property_UsesExpressionPropertyBody_ExpressionPropertyInDifferentFile.verified.txt | Updates expected generated output to nested-companion form for partial type scenario. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/UseMemberBodyTests.Method_UsesExpressionPropertyBody_ExpressionPropertyInDifferentFile.verified.txt | Updates expected generated output to nested-companion form for partial type scenario. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.cs | Adds generator tests for partial-type nested companion generation and registry output. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_SimpleMethod_GeneratesNestedCompanion.verified.txt | New snapshot for nested companion in partial type. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_PrivateMethodCall_CompanionCanCallPrivateMethod.verified.txt | New snapshot validating nested companion can reference private method. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_PrivateFieldAccess_CompanionCanAccessPrivateField.verified.txt | New snapshot validating nested companion can access private field. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_NestedPartialType_TwoLevelShellWrap.verified.txt | New snapshot validating wrapping through nested partial type chain. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_Registry_UsesClrNestedTypeName.verified.txt | New snapshot validating registry uses CLR nested type names for nested companions. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_MethodOverloads_EachGetsUniqueCompanionInsidePartialType.verified.txt | New snapshot validating overload companions are unique and nested. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.PartialClass_MethodOverloads_Registry_BothEntriesUseClrNestedTypeName.verified.txt | New snapshot validating overload registry entries use CLR nested naming. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/PartialClassTests.NonPartialClass_Unchanged.verified.txt | New snapshot validating non-partial behavior remains unchanged. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.cs | Adds EF Core functional tests exercising private projectable access via partial type nesting. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.FilterOnPrivateProjectableProperty.DotNet8_0.verified.txt | New SQL snapshot for filter scenario. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.FilterOnPrivateProjectableProperty.DotNet9_0.verified.txt | New SQL snapshot for filter scenario. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.FilterOnPrivateProjectableProperty.DotNet10_0.verified.txt | New SQL snapshot for filter scenario. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.SelectPrivateProjectableProperty.DotNet8_0.verified.txt | New SQL snapshot for select scenario. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.SelectPrivateProjectableProperty.DotNet9_0.verified.txt | New SQL snapshot for select scenario. |
| tests/EntityFrameworkCore.Projectables.FunctionalTests/PartialClassWithPrivateMembersTests.SelectPrivateProjectableProperty.DotNet10_0.verified.txt | New SQL snapshot for select scenario. |
| if (projectable.IsDeclaringTypePartial) | ||
| { | ||
| // Nest the companion inside the user's partial type chain so it can access | ||
| // private/protected members of the enclosing type (C# nested-class access rules). | ||
| MemberDeclarationSyntax wrapped = classSyntax; | ||
| var currentType = memberSymbol.ContainingType; | ||
| while (currentType is not null) | ||
| { | ||
| wrapped = BuildPartialTypeShell(currentType).AddMembers(wrapped); | ||
| currentType = currentType.ContainingType; | ||
| } |
There was a problem hiding this comment.
When projectable.IsDeclaringTypePartial is true, the generator wraps the companion in all containing types and emits each wrapper as partial. This will break compilation if the projectable member is declared in a partial nested type whose outer containing type(s) are not partial (the generated partial class Outer { ... } will conflict with the user’s non-partial class Outer). Consider requiring the entire containing type chain to be partial before nesting (or falling back to the EntityFrameworkCore.Projectables.Generated namespace / emitting a diagnostic when an outer type is not partial).
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using EntityFrameworkCore.Projectables.FunctionalTests.Helpers; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using VerifyXunit; | ||
| using Xunit; | ||
|
|
There was a problem hiding this comment.
[Projectable] is used in this file but using EntityFrameworkCore.Projectables; is missing, which will fail compilation unless there is a global using (none appears to exist in this project). Add the missing using (or fully-qualify the attribute).
| [Fact] | ||
| public Task PartialClass_NestedPartialType_TwoLevelShellWrap() | ||
| { | ||
| var compilation = CreateCompilation(@" | ||
| using EntityFrameworkCore.Projectables; | ||
| namespace Foo { | ||
| public partial class Outer { | ||
| public partial class Inner { | ||
| [Projectable] | ||
| public int Value() => 99; | ||
| } | ||
| } | ||
| } | ||
| "); | ||
|
|
||
| var result = RunGenerator(compilation); | ||
|
|
||
| Assert.Empty(result.Diagnostics); | ||
| Assert.Single(result.GeneratedTrees); | ||
|
|
||
| return Verifier.Verify(result.GeneratedTrees[0].ToString()); | ||
| } |
There was a problem hiding this comment.
The new partial-type behavior has good coverage for basic cases, but there’s no test for a projectable member inside a partial nested type where an outer containing type is not partial (e.g., class Outer { public partial class Inner { [Projectable] ... } }). Currently the generator will still emit a partial class Outer { ... } wrapper, which should fail compilation; please add a regression test for the expected behavior (fallback to Generated namespace or a diagnostic).
PhenX
left a comment
There was a problem hiding this comment.
I see you create inner classes instead of "hidden" members, you didn't like it? Anyway, this inner class is fine too!
But I think we should run the benchmarks too
Enable access to private fields and methods in generated companions by implementing support for partial classes. This change allows projectable members to be accessed within their containing types, enhancing encapsulation and usability.
Marking a type as partial is optional, not doing so will still generate the current companion type which doesn't have access to internals (fine for most usecases)