Skip to content

Conversation

@ncipollina
Copy link
Contributor

Summary

Implements Phase 1 of #3: Factory Delegate Support

This PR adds support for factory delegate registrations, allowing decorators to work with custom initialization logic while preserving the factory's behavior.

What's Added

  • Two-parameter generic factory: AddScoped<TService, TImpl>(sp => new Impl(...))
  • Single-parameter generic factory: AddScoped<TService>(sp => new Impl(...))
  • All lifetimes supported: AddScoped, AddTransient, AddSingleton
  • Complex dependency resolution: Factory delegates can resolve dependencies from IServiceProvider
  • Factory preservation: User's factory logic is preserved while decorators wrap the result

Technical Changes

  • Added RegistrationKind enum (Parameterless, FactoryTwoTypeParams, FactorySingleTypeParam)
  • Extended ClosedGenericRegistration model with optional FactoryParameterName field
  • Updated ClosedGenericRegistrationProvider to detect Func<IServiceProvider, T> signatures
  • Modified InterceptorEmitter to generate three different interceptor types based on registration kind
  • Factory delegates registered as keyed services, then wrapped with decorators in outer factory

Testing

  • Updated test case 022 (renamed to FactoryDelegate_SingleDecorator)
  • Added 6 comprehensive test cases (033-038):
    • 033: Single-parameter generic factory
    • 034: Multiple decorators with factory
    • 035: Factory without decorators (pass-through)
    • 036: AddTransient with factory
    • 037: AddSingleton with factory
    • 038: Complex dependencies with IServiceProvider resolution
  • Updated sample project with factory delegate examples demonstrating ILogger dependencies
  • All 38 tests passing on net8.0, net9.0, net10.0

Documentation

  • Updated version to 1.0.2-beta
  • Added comprehensive release notes in releasenotes.props
  • Updated README.md and CLAUDE.md with factory delegate feature
  • Added factory delegate section to docs/usage/class-level-decorators.md
  • Updated docs/changelog.md and docs/index.md

Example Usage

[DecoratedBy<CachingRepository>]
public class UserRepository : IUserRepository
{
    private readonly ILogger _logger;
    
    public UserRepository(ILogger logger)
    {
        _logger = logger;
    }
}

// Factory delegate with dependency resolution
services.AddScoped<IUserRepository, UserRepository>(sp =>
{
    var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger<UserRepository>();
    return new UserRepository(logger);
});

// Result: CachingRepository wrapping UserRepository
// Factory logic preserved, decorators applied automatically

Test Plan

  • Build solution successfully
  • All 38 tests pass on net8.0
  • All 38 tests pass on net9.0
  • All 38 tests pass on net10.0
  • Sample project runs successfully with factory examples
  • Generated code verified via snapshot tests
  • Documentation builds without errors

Breaking Changes

None - this is a purely additive change. All existing functionality remains unchanged.

Related Issues

Related to #3 (Phase 1 complete)

🤖 Generated with Claude Code

Implements Phase 1 of #3: Factory Delegate Support

This change adds support for factory delegate registrations, allowing
decorators to work with custom initialization logic while preserving
the factory's behavior.

**What's Added:**
- Support for two-parameter generic factory: `AddScoped<TService, TImpl>(sp => ...)`
- Support for single-parameter generic factory: `AddScoped<TService>(sp => ...)`
- All lifetimes supported: Scoped, Transient, Singleton
- Complex dependency resolution from IServiceProvider
- Factory logic preserved while decorators wrap the result

**Technical Changes:**
- Added `RegistrationKind` enum to distinguish factory patterns
- Extended `ClosedGenericRegistration` with optional `FactoryParameterName`
- Updated `ClosedGenericRegistrationProvider` to detect factory signatures
- Modified `InterceptorEmitter` to generate factory-specific interceptor code
- Factory delegates registered as keyed services, then wrapped with decorators

**Testing:**
- Updated test case 022 (renamed to `FactoryDelegate_SingleDecorator`)
- Added 6 comprehensive test cases (033-038) covering all scenarios
- Updated sample project with factory delegate examples
- All 38 tests passing on net8.0, net9.0, net10.0

**Documentation:**
- Updated version to 1.0.2-beta
- Added release notes in releasenotes.props
- Updated README.md and CLAUDE.md with factory delegate examples
- Added comprehensive documentation in docs/usage/class-level-decorators.md
- Updated changelog and index.md

Related to #3

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@ncipollina ncipollina requested review from Copilot and j-d-ha November 13, 2025 01:02
@ncipollina
Copy link
Contributor Author

@codex review

Copy link

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

This PR implements Phase 1 of factory delegate support for DecoWeaver, enabling decorators to work with custom initialization logic via factory delegates. The implementation handles both two-parameter (AddScoped<TService, TImpl>(factory)) and single-parameter (AddScoped<TService>(factory)) generic factories across all DI lifetimes, while preserving user-defined factory logic and applying decorators around the result.

Key changes:

  • Extended registration detection to identify and intercept factory delegate signatures
  • Implemented three distinct interceptor generation patterns based on registration kind
  • Factory delegates are registered as keyed services, then wrapped with decorator composition logic

Reviewed Changes

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

Show a summary per file
File Description
ClosedGenericRegistrationProvider.cs Extended provider to detect factory delegate signatures and classify registration kind
InterceptorEmitter.cs Added factory-specific interceptor generation for two-parameter and single-parameter overloads
TypeId.cs / TypeDefId.cs Refactored type identity code into dedicated files with extension methods
Test cases (033-038) Added comprehensive test coverage for factory delegate scenarios
Sample project Updated with factory delegate examples demonstrating complex dependency resolution
Documentation Added factory delegate documentation and updated version to 1.0.2-beta

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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…d add 1.0.1-beta notes

- Changed condition syntax from exact match to StartsWith() to support build number suffixes
- Added missing 1.0.1-beta release notes from changelog
- Now supports versions like 1.0.2-beta.1, 1.0.2-beta.2, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Collaborator

@j-d-ha j-d-ha left a comment

Choose a reason for hiding this comment

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

👍 Looks good! One question/suggestion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You need to start auto-generating this stuff 😄


internal static class TypeDefIdExtensions
{
extension(TypeDefId typeDefId)
Copy link
Collaborator

Choose a reason for hiding this comment

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

NICEEEEE

internal static TypeDefId Create(ITypeSymbol t)
{
// For named types: collect containers + assembly
var assembly = t.ContainingAssembly?.Name ?? "unknown";
Copy link
Collaborator

Choose a reason for hiding this comment

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

This unknown bothers me a little. Will this cause issues if the assembly is undefined? I almost feel that this might be error-worthy, and then just catch the error and emit an error diagnostic.

@ncipollina ncipollina merged commit e90c98e into main Nov 13, 2025
3 checks passed
@ncipollina ncipollina deleted the feature/issue-3-phase-1-factory-delegates branch November 13, 2025 01:35
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