Skip to content

extended polymorphism, il weavers off-the-shelf support - #49

Merged
lofcz merged 2 commits into
nextfrom
feat-polymorphism
Aug 18, 2026
Merged

extended polymorphism, il weavers off-the-shelf support#49
lofcz merged 2 commits into
nextfrom
feat-polymorphism

Conversation

@lofcz

@lofcz lofcz commented Aug 18, 2026

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends FastCloner’s reflection cloner and source generator to better handle (1) polymorphic cloning through non-abstract base references, (2) assembly-level “external ignore” attributes, and (3) IL-weaver injected runtime state (e.g., PostSharp/Metalama patterns) so cloning remains safe and consistent across both cloning modes.

Changes:

  • Add [FastClonerPolymorphic] support (including generic roots) with subtype dispatch and validation diagnostics.
  • Add [assembly: FastClonerExternalIgnore(...)] support so framework ignore attributes can be honored without per-member FastCloner attributes.
  • Detect and special-case weaver-injected state fields and prefer weaver-declared MemberwiseClone() when present; add regression tests and documentation.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/FastCloner/Code/FastClonerWeaverState.cs Detect weaver state fields and discover weaver-declared MemberwiseClone().
src/FastCloner/Code/FastClonerExternalIgnoreRegistry.cs Reflection-side cache/lookup for assembly-registered external ignore attributes.
src/FastCloner/Code/FastClonerExternalIgnoreAttribute.cs Assembly-level attribute to register external ignore marker types.
src/FastCloner/Code/FastClonerExprGenerator.cs Apply external ignore + weaver-state handling in reflection expression generation.
src/FastCloner/Code/FastClonerCache.cs Extend TypeShape with HasWeaverStateFields.
src/FastCloner/Code/ClonerToExprGenerator.cs Skip copying weaver state fields during DeepCloneTo deep member population.
src/FastCloner.Tests/PostSharpAspectTests.cs Regression tests simulating PostSharp IL shapes (issue #48).
src/FastCloner.Tests/PolymorphicCloneTests.cs End-to-end tests for polymorphic dispatch, including generic roots and fallback.
src/FastCloner.Tests/ExternalIgnoreAttributeTests.cs Tests for [assembly: FastClonerExternalIgnore] behavior and precedence.
src/FastCloner.Tests/DiagnosticTests.cs Generator diagnostics + generated-source structural assertions for polymorphic behavior.
src/FastCloner.Tests/AbstractClassTests.cs Verify abstract root dispatch to derived clonable generators.
src/FastCloner.SourceGenerator/TypeModelFactory.cs Thread external ignore registry; add polymorphic root modeling and derived collection.
src/FastCloner.SourceGenerator/TypeModel.cs Add IsPolymorphicRoot flag to the model.
src/FastCloner.SourceGenerator/SubtypeUsageCollector.cs Collect closed generic subtype constructions from usages for dispatch lists.
src/FastCloner.SourceGenerator/PolymorphicValidationInfo.cs Add validation diagnostics for [FastClonerPolymorphic] misuse.
src/FastCloner.SourceGenerator/MemberModel.cs Track weaver-state members and backing-field storage for safe population.
src/FastCloner.SourceGenerator/MemberCollector.cs Apply external-ignore matching (by declaring assembly) during member collection.
src/FastCloner.SourceGenerator/MemberCloneGenerator.cs Adapt member population for uninitialized instances + weaver state handling.
src/FastCloner.SourceGenerator/IncludeAttributeCollector.cs Avoid treating included subtypes as generic-arg usages; thread external ignores.
src/FastCloner.SourceGenerator/ImplicitTypeAnalyzer.cs Thread external ignore registry through implicit analysis.
src/FastCloner.SourceGenerator/GenericUsageCollector.cs Thread external ignore registry through usage analysis.
src/FastCloner.SourceGenerator/GenericTypeAnalyzer.cs Skip unbound type arguments; thread external ignores into implicit analysis.
src/FastCloner.SourceGenerator/FastClonerIncrementalGenerator.cs Integrate external ignore registry + subtype usage pipeline + polymorphic validation.
src/FastCloner.SourceGenerator/ExternalIgnoreCollector.cs Collect per-assembly external ignore registrations into an equatable registry.
src/FastCloner.SourceGenerator/DerivedTypeCollector.cs Support generic roots + external ignore threading for derived type models.
src/FastCloner.SourceGenerator/ContextCollector.cs Thread external ignore registry through context collection.
src/FastCloner.SourceGenerator/CloneGeneratorContext.cs Ensure derived-type helper method names are unique (generic constructions).
src/FastCloner.SourceGenerator/CloneCodeGenerator.cs Implement polymorphic root dispatch + subtype-usage-derived dispatch branches.
src/FastCloner.SourceGenerator/ClassCloneBodyGenerator.cs Pass “created without ctor” flag to avoid invoking setters and handle weaver state.
src/FastCloner.SourceGenerator.Shared/FastClonerPolymorphicAttribute.cs New attribute enabling polymorphic dispatch on concrete roots.
src/FastCloner.Benchmark/BenchPolymorphic.cs Benchmark for polymorphic root overhead and dispatch cost.
README.md Document external ignore registration and [FastClonerPolymorphic] usage and precedence.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +49 to +52
bool nullability = context.SemanticModel.GetNullableContext(node.SpanStart).HasFlag(NullableContext.Enabled);
TypeModel? model = DerivedTypeCollector.CreateTypeModelForDerived(
symbol, context.SemanticModel.Compilation, nullability, targetFramework);
if (model == null)
Comment on lines +28 to +29
/// Attributes are matched by exact attribute type on presence only; constructors and properties
/// of the external attribute (e.g. <c>JsonIgnore(Condition = ...)</c>) are not evaluated.
Copilot AI review requested due to automatic review settings August 18, 2026 10:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/FastCloner.SourceGenerator/MemberCollector.cs:57

  • ResolveExternalIgnores only takes the first matching ExternalIgnoreAssemblyRegistration and breaks. Since FastClonerExternalIgnoreAttribute is AllowMultiple=true, multiple registrations on the same assembly will be ignored here, diverging from the reflection cloner (and from the attribute contract). Merge all matching registrations for the assembly instead of stopping at the first one.
                foreach (ExternalIgnoreAssemblyRegistration registration in externalIgnores.Registrations)
                {
                    if (registration.AssemblyName == displayName)
                    {
                        resolved = [.. registration.AttributeTypeFqns];

src/FastCloner.SourceGenerator/SubtypeUsageCollector.cs:58

  • SubtypeUsageCollector.Collect computes FindAllDispatchRoots(symbol) twice; the second call can reuse the already-computed 'roots' list to avoid repeating the base-chain walk.
        foreach (INamedTypeSymbol chainRoot in FindAllDispatchRoots(symbol))
        {
            usages.Add(new ClosedSubtypeUsage(
                chainRoot.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
                model));

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Deep Clone Benchmarks

  • OS: ubuntu-latest
  • Generated (UTC): 2026-08-18 10:35:34

Current FastCloner vs DeepCloner

Benchmark DeepCloner FastCloner Delta Time DC Alloc FC Alloc Delta Alloc
SmallObject 65.53 ns 53.66 ns -18% faster 184 B 48 B -74% less
FileSpec 378.41 ns 216.61 ns -43% faster 920 B 416 B -55% less
StringArray_1000 411.31 ns 404.82 ns ~same 8,160 B 8,024 B ~same
SmallObjectWithCollections 477.31 ns 268.67 ns -44% faster 1,096 B 576 B -47% less
DynamicWithDictionary 987.70 ns 790.32 ns -20% faster 2,712 B 1,600 B -41% less
MediumNestedObject 1,221.93 ns 821.00 ns -33% faster 3,416 B 1,616 B -53% less
DynamicWithNestedObject 1,446.37 ns 969.01 ns -33% faster 3,560 B 1,776 B -50% less
DynamicWithArray 4,120.24 ns 3,762.84 ns -9% faster 8,800 B 2,744 B -69% less
LargeEventDocument_10MB 52,053.58 ns 28,536.02 ns -45% faster 129,792 B 49,824 B -62% less
ObjectList_100 125,999.26 ns 79,230.51 ns -37% faster 318,888 B 149,816 B -53% less
ObjectDictionary_50 660,219.70 ns 137,245.00 ns -79% faster 549,654 B 218,768 B -60% less
LargeLogBatch_10MB 4,875,880.56 ns 2,860,742.50 ns -41% faster 3,564,656 B 2,649,102 B -26% less

FastCloner vs latest next baseline

  • Baseline generated (UTC): 2026-08-18 10:35:31
  • Regression thresholds: time > 5%, alloc > 5%
Status Benchmark Delta Time Delta Alloc
🔴 DynamicWithArray +13% slower ~same
🔴 DynamicWithDictionary +5% slower ~same
DynamicWithNestedObject ~same ~same
FileSpec -4% faster ~same
🟢 LargeEventDocument_10MB -5% faster ~same
LargeLogBatch_10MB -3% faster ~same
🔴 MediumNestedObject +6% slower ~same
ObjectDictionary_50 ~same ~same
ObjectList_100 ~same ~same
SmallObject ~same ~same
SmallObjectWithCollections +3% slower ~same
StringArray_1000 -3% faster ~same

Regressions

  • DynamicWithArray: time +13% slower, alloc ~same
  • DynamicWithDictionary: time +5% slower, alloc ~same
  • MediumNestedObject: time +6% slower, alloc ~same

Improvements

  • LargeEventDocument_10MB: time -5% faster, alloc ~same

Mixed changes

  • none

@lofcz
lofcz merged commit 2b641d6 into next Aug 18, 2026
12 checks passed
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.

2 participants