Skip to content

Allow Behaviors with 0, 1, or 2 generic type parameters#289

Closed
Copilot wants to merge 18 commits intomasterfrom
copilot/update-behavior-generic-parameters
Closed

Allow Behaviors with 0, 1, or 2 generic type parameters#289
Copilot wants to merge 18 commits intomasterfrom
copilot/update-behavior-generic-parameters

Conversation

Copy link

Copilot AI commented Jan 30, 2026

Behaviors previously required exactly 2 generic type parameters. This restriction is now lifted to support 0, 1, or 2 parameters, allowing behaviors with fixed or partially-specified types.

Changes

Analyzer (BehaviorsAnalyzer.cs)

  • Accept behaviors with 0-2 type parameters
  • Warn only on 3+ parameters
  • Handle non-generic type validation correctly
  • New: Added IHR0020 diagnostic to validate behavior type arguments match handler request/response types
  • New: Added handler-level validation to detect type mismatches between behaviors and handlers

Generator (TransformBehaviors.cs, ImmediateHandlersGenerator.cs, Models.cs)

  • Track type parameter count per behavior
  • Extract constraints for 0, 1, or 2 parameter cases
  • New: Detect which position (request or response) is parameterized in single-parameter behaviors
  • Generate correct type arguments based on parameter count
  • Behaviors with 3+ parameters are filtered out and produce no code

Template (Handler.sbntxt)

  • Conditional type parameter generation based on TypeParameterCount
  • Support Behavior, Behavior<TRequest>, and Behavior<TRequest, TResponse> signatures

Example

// Non-generic: fixed to specific types
[assembly: Behaviors(typeof(AuditBehavior))]
public class AuditBehavior : Behavior<MyRequest, MyResponse> { }

// Single parameter: request type varies, response fixed
[assembly: Behaviors(typeof(ValidationBehavior<>))]
public class ValidationBehavior<TRequest> : Behavior<TRequest, Unit> { }

// Single parameter: response type varies, request fixed
[assembly: Behaviors(typeof(LoggingBehavior<>))]
public class LoggingBehavior<TResponse> : Behavior<int, TResponse> { }

// Two parameters: both vary (existing behavior)
[assembly: Behaviors(typeof(LoggingBehavior<,>))]
public class LoggingBehavior<TRequest, TResponse> : Behavior<TRequest, TResponse> { }

Generator constructs behavior instances with handler's request/response types, filtering incompatible behaviors via type matching in the pipeline. The analyzer now validates that behaviors with fixed type arguments match the handler's request/response types, reporting IHR0020 errors for mismatches.

Tests

Analyzer Tests (Tests.BehaviorTypeWithOneGenericParameter.cs)

  • BehaviorTypeWithCorrectTRequest_DoesNotAlert: Validates single-parameter behaviors with correct request type don't trigger errors
  • BehaviorTypeWithIncorrectTRequest_Alerts: Validates IHR0020 diagnostic for request type mismatches
  • BehaviorTypeWithCorrectTResponse_DoesNotAlert: Validates single-parameter behaviors with correct response type don't trigger errors
  • BehaviorTypeWithIncorrectTResponse_Alerts: Validates IHR0020 diagnostic for response type mismatches

Generator Tests (BehaviorTests.cs, InvalidBehaviorsTests.cs)

  • ZeroTypeParameterBehavior: Validates non-generic behaviors generate correct handler code
  • RequestTypeParameterBehavior: Validates single-parameter behaviors (request position) generate correct code
  • ResponseTypeParameterBehavior: Validates single-parameter behaviors (response position) generate correct code
  • ThreeGenericParameterBehaviorShouldProduceNothing: Validates behaviors with 3+ parameters produce no code

Functional Tests (PartiallySpecifiedBehaviorTests.cs)

  • Proves non-generic behaviors execute correctly in isolation
  • Proves single-parameter behaviors execute correctly in isolation
  • Proves both types can be chained together in a pipeline

Status

Work in Progress: Single-parameter behavior code generation is partially implemented. The analyzer and type detection logic are complete, but the template generation needs additional work to properly construct single-parameter behaviors with the correct type argument position.

Current test status: 9 tests failing, all related to single-parameter behavior code generation.

Original prompt

This section details on the original issue you should resolve

<issue_title>Allow Behaviors to be partially or fully specified types</issue_title>
<issue_description>Allow:

public sealed class ShortCircuitBehavior : Behavior<GetCachedOrder, Order>
{
	private readonly Order _cachedOrder = new(999, 49.99m, DateTime.UtcNow);

	public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
	{
		// Always short-circuit with cached result - simulates cache hit scenario
		return (TResponse)_cachedOrder;
	}
}
</issue_description>

<agent_instructions>* Update analyzer to support zero, one, or two generic parameters. Still warn on three or more parameters
* Update generator to match implemented type's generic arguments with handler request or response types</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits January 30, 2026 18:54
Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
…behaviors

Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
Copilot AI changed the title [WIP] Update analyzer and generator for Behavior generic parameters Allow Behaviors with 0, 1, or 2 generic type parameters Jan 30, 2026
Copy link
Member

@viceroypenguin viceroypenguin left a comment

Choose a reason for hiding this comment

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

  • Test changed in InvalidBehaviorsTests should be changed to demonstrate that the generator does not generate any code when a behavior with 3 generic parameters is specified.
  • Add tests in BehaviorTests for both zero and one type-parameter behaviors
  • Add new functional tests under Immediate.Handlers.FunctionalTests; BehaviorTests.cs to prove code builds and runs with zero and one type-parameter behaviors

- Changed InvalidBehaviorsTests to demonstrate that behaviors with 3 generic parameters produce no code
- Added ZeroTypeParameterBehavior and OneTypeParameterBehavior tests to BehaviorTests
- Added comprehensive functional tests in PartiallySpecifiedBehaviorTests to prove zero and one type-parameter behaviors compile and run correctly

Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 30, 2026

  • Test changed in InvalidBehaviorsTests should be changed to demonstrate that the generator does not generate any code when a behavior with 3 generic parameters is specified.
  • Add tests in `Behavio...

Completed in commit c0eab83:

  1. InvalidBehaviorsTests: Changed test to ThreeGenericParameterBehaviorShouldProduceNothing which demonstrates that behaviors with 3 generic parameters generate no code
  2. BehaviorTests: Added ZeroTypeParameterBehavior and OneTypeParameterBehavior tests with verified snapshots
  3. Functional tests: Added PartiallySpecifiedBehaviorTests.cs with 3 tests that prove zero and one type-parameter behaviors compile and execute correctly, including behavior chaining

All 131 tests pass.

…Nothing test

Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
Copy link
Member

@viceroypenguin viceroypenguin left a comment

Choose a reason for hiding this comment

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

Check new tests and update analyzer and generator codes to make tests pass

…iors

- Added IHR0020 diagnostic to detect when behavior type arguments don't match handler types
- Updated analyzer to validate behavior type compatibility with handlers
- Updated generator to detect which position (request/response) is parameterized in single-param behaviors
- Still needs fixes to template generation logic to properly construct single-param behaviors

Co-authored-by: viceroypenguin <1992555+viceroypenguin@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 31, 2026

Check new tests and update analyzer and generator codes to make tests pass

Started working on this in commit 8f2f6e6. I've:

  1. Added IHR0020 diagnostic to detect behavior type argument mismatches
  2. Updated the analyzer to validate behaviors against handler types
  3. Updated the generator to detect which position (request/response) is parameterized in single-parameter behaviors

Still working on fixing the template generation logic - the tests expect behaviors where a single type parameter can map to either the request OR response position, which requires more sophisticated template handling than the current implementation. The generator needs to construct behaviors like RequestLoggingBehavior<string> when the behavior's single parameter is in the response position.

Current status: 9 tests failing, all related to single-parameter behavior code generation.

@viceroypenguin viceroypenguin marked this pull request as ready for review February 8, 2026 02:01
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

Expands the Immediate.Handlers behavior model so behaviors can be declared with 0, 1, or 2 generic type parameters (instead of exactly 2), updates the generator/analyzers accordingly, and adds tests/functional coverage for partially specified behaviors.

Changes:

  • Analyzer: allow 0–2 generic parameters for behaviors; add IHR0020 (behavior request/response compatibility) and IHR0021 (invalid behavior constraints).
  • Generator: compute behavior constraint info and generate correct behavior type arguments for 0/1/2-parameter behaviors; filter out incompatible behaviors.
  • Tests: add analyzer, generator snapshot, and functional tests for non-generic and single-generic behaviors.

Reviewed changes

Copilot reviewed 52 out of 52 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
tests/Immediate.Handlers.Tests/GeneratorTests/InvalidBehaviorsTests.cs Updates generator invalid-behavior test inputs (e.g., static classes become partial); removes cases that are now valid.
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.cs Adds generator tests for 0/1 type-parameter behaviors and constraint-filtering scenarios.
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ZeroTypeParameterBehavior_assemblies=Normal#IH.Dummy.GetUsersQuery.g.verified.cs New verified output snapshot for non-generic behavior pipeline generation (Normal).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ZeroTypeParameterBehavior_assemblies=Msdi#IH.ServiceCollectionExtensions.g.verified.cs New verified output snapshot for DI registrations for non-generic behaviors (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ZeroTypeParameterBehavior_assemblies=Msdi#IH.Dummy.GetUsersQuery.g.verified.cs New verified output snapshot for non-generic behavior pipeline generation (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ResponseTypeParameterBehavior_assemblies=Normal#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for single-parameter behavior (response varies) generation (Normal).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ResponseTypeParameterBehavior_assemblies=Msdi#IH.ServiceCollectionExtensions.g.verified.cs New verified snapshot for DI registrations for single-parameter behavior (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.ResponseTypeParameterBehavior_assemblies=Msdi#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for handler pipeline with single-parameter behavior (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.RequestTypeParameterBehavior_assemblies=Normal#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for single-parameter behavior (request varies) generation (Normal).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.RequestTypeParameterBehavior_assemblies=Msdi#IH.ServiceCollectionExtensions.g.verified.cs New verified snapshot for DI registrations for single-parameter behavior (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.RequestTypeParameterBehavior_assemblies=Msdi#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for handler pipeline with single-parameter behavior (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTResponseConstraints_assemblies=Normal#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot ensuring invalid/multi constraints cause behavior to be dropped from pipeline (Normal).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTResponseConstraints_assemblies=Msdi#IH.ServiceCollectionExtensions.g.verified.cs New verified snapshot for DI registrations in multi-constraint case (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTResponseConstraints_assemblies=Msdi#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for handler pipeline in multi-constraint case (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTRequestConstraints_assemblies=Normal#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot ensuring invalid/multi constraints cause behavior to be dropped from pipeline (Normal).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTRequestConstraints_assemblies=Msdi#IH.ServiceCollectionExtensions.g.verified.cs New verified snapshot for DI registrations in multi-constraint case (Msdi).
tests/Immediate.Handlers.Tests/GeneratorTests/BehaviorTests.BehaviorHasMultipleTRequestConstraints_assemblies=Msdi#IH.Dummy.GetUsersQuery.g.verified.cs New verified snapshot for handler pipeline in multi-constraint case (Msdi).
tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerUsesBehaviorTypeWithZeroGenericParameters.cs Adds handler-level analyzer tests for non-generic behaviors.
tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerUsesBehaviorTypeWithOneGenericParameter.cs Adds handler-level analyzer tests for single-generic behaviors and IHR0020 mismatch cases.
tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerUsesBehaviorTypeIsMultiplyInherited.cs Adds handler-level analyzer tests for behaviors inheriting Behavior<,> indirectly (and mismatch detection).
tests/Immediate.Handlers.Tests/AnalyzerTests/BehaviorClassAnalyzerTests.cs Adds tests for new behavior constraint validation analyzer (IHR0021).
tests/Immediate.Handlers.Tests/AnalyzerTests/BehaviorAnalyzerTests/Tests.BehaviorTypeWithZeroGenericParameters.cs Adds BehaviorsAnalyzer tests validating 0-generic behavior types are accepted.
tests/Immediate.Handlers.Tests/AnalyzerTests/BehaviorAnalyzerTests/Tests.BehaviorTypeWithOneGenericParameter.cs Adds BehaviorsAnalyzer tests validating 1-generic behavior types are accepted.
tests/Immediate.Handlers.Tests/AnalyzerTests/BehaviorAnalyzerTests/Tests.BehaviorTypeHasThreeGenericParameters.cs Renames/adjusts test to match new “3+ generic params invalid” rule.
tests/Immediate.Handlers.FunctionalTests/NoBehaviors/ParameterlessTests.cs Updates functional test handler signatures to include CancellationToken.
tests/Immediate.Handlers.FunctionalTests/Immediate.Handlers.FunctionalTests.csproj Adds analyzer reference + diagnostics testing package for functional tests.
tests/Immediate.Handlers.FunctionalTests/Behavior/PartiallySpecifiedBehaviorTests.cs Adds functional test exercising 0/1/2-parameter behaviors in a pipeline.
tests/Immediate.Handlers.FunctionalTests/Behavior/Constraints/Tests.BehaviorShouldConstrain_A.cs Disables new IHR0020 in constraint functional tests where mismatch is intentional.
tests/Immediate.Handlers.FunctionalTests/Behavior/Constraints/Tests.BehaviorShouldConstrain_B.cs Disables new IHR0020 in constraint functional tests where mismatch is intentional.
tests/Immediate.Handlers.FunctionalTests/Behavior/Constraints/Tests.BehaviorShouldConstrain_C.cs Disables new IHR0020 in constraint functional tests where mismatch is intentional.
tests/Immediate.Handlers.FunctionalTests/Behavior/Constraints/Tests.BehaviorShouldConstrain_D.cs Disables new IHR0020 in constraint functional tests where mismatch is intentional.
src/Immediate.Handlers.Generators/Utility.cs Removes generator-local Utility (migrated/shared via src/Common).
src/Immediate.Handlers.Generators/TransformHandler.cs Uses new return-type helpers; adjusts base-type capture to original definitions.
src/Immediate.Handlers.Generators/TransformBehaviors.cs Accepts 0–2 type parameters; extracts/serializes constraint info for pipeline filtering and rendering.
src/Immediate.Handlers.Generators/Templates/Handler.sbntxt Updates handler template to use a precomputed behavior “type_name” instead of forcing <TReq,TRes>.
src/Immediate.Handlers.Generators/Models.cs Refactors behavior constraint representation for generator filtering/rendering.
src/Immediate.Handlers.Generators/ImmediateHandlersGenerator.cs Filters behaviors via structured constraints; generates correct behavior type arguments for 0/1/2-parameter behaviors.
src/Immediate.Handlers.CodeFixes/StaticToSealedHandlerCodeFixProvider.cs Updates attribute checks to use new extension properties.
src/Immediate.Handlers.CodeFixes/Immediate.Handlers.CodeFixes.csproj Adds Microsoft.Bcl.HashCode package reference.
src/Immediate.Handlers.Analyzers/Properties/launchSettings.json Updates DebugRoslynComponent targets (adds functional tests target).
src/Immediate.Handlers.Analyzers/InvalidIHandlerAnalyzer.cs Updates extension usage (IsIHandler property).
src/Immediate.Handlers.Analyzers/Immediate.Handlers.Analyzers.csproj Adds Microsoft.Bcl.HashCode package reference.
src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs Adds IHR0020 and handler-level behavior type compatibility analysis.
src/Immediate.Handlers.Analyzers/DiagnosticIds.cs Adds IDs for IHR0020 and IHR0021.
src/Immediate.Handlers.Analyzers/BehaviorsClassAnalyzer.cs New analyzer enforcing supported generic constraints on behavior type parameters (IHR0021).
src/Immediate.Handlers.Analyzers/BehaviorsAnalyzer.cs Allows behavior types with 0–2 generic parameters; keeps unbound generic enforcement for generic types.
src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md Adds unshipped analyzer release tracking file (currently empty).
src/Immediate.Handlers.Analyzers/AnalyzerReleases.Shipped.md Documents new analyzer rules in release notes (IHR0020/IHR0021).
src/Common/Utility.cs Adds shared utility extensions (NullIf, WhereNotNull).
src/Common/ITypeSymbolExtensions.cs Adds behavior constraint extraction + constraint satisfaction helpers; refactors existing symbol helpers into extension properties.
src/Common/BehaviorConstraintInfo.cs Adds shared constraint model used by analyzers/generator for behavior compatibility.
Directory.Packages.props Bumps DI package versions and adds diagnostics testing package version.

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

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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md:2

  • AnalyzerReleases.Unshipped.md was added but is currently empty/blank. If release tracking is being used, new rules should typically be recorded in Unshipped until a version is actually released, and empty files tend to cause confusion. Either add the appropriate entries or remove the empty file if it’s not needed.

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

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

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

Comments suppressed due to low confidence (1)

src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md:2

  • The analyzer release tracking files look inconsistent: AnalyzerReleases.Unshipped.md is effectively empty, but AnalyzerReleases.Shipped.md already lists IHR0020/IHR0021 under a new "Release 3.2" section. Unless these diagnostics have actually shipped, they should be documented in AnalyzerReleases.Unshipped.md instead (and only moved to Shipped as part of a release).

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

Comment on lines +104 to +108
constraints.Add(
baseConstraints[index].Item2.ExactType != null
? baseConstraints[index]
: (name, new() { ExactType = baseType.TypeArguments[index], TypeConstraints = [] })
);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

AddMissingConstraint sets ExactType = baseType.TypeArguments[index] when the base Behavior<,> argument is a type parameter. That makes ExactType become something like TRequest/TResponse, which will never equal a concrete handler request/response type, causing IHR0020 to fire incorrectly and behaviors to be filtered out even when they should match (e.g., behaviors that forward a single type parameter through the base Behavior). Consider treating ITypeParameterSymbol arguments as "parameterized" (leave ExactType null and carry constraints), and only set ExactType when the base argument is a non-type-parameter symbol.

Suggested change
constraints.Add(
baseConstraints[index].Item2.ExactType != null
? baseConstraints[index]
: (name, new() { ExactType = baseType.TypeArguments[index], TypeConstraints = [] })
);
var baseConstraint = baseConstraints[index];
var baseConstraintInfo = baseConstraint.Item2;
if (baseConstraintInfo.ExactType is not null)
{
constraints.Add(baseConstraint);
return;
}
var baseTypeArgument = baseType.TypeArguments[index];
if (baseTypeArgument is ITypeParameterSymbol typeParameterSymbol)
{
// Treat type-parameter arguments as parameterized: do not set ExactType,
// but carry over the parameter's own type constraints.
constraints.Add(
(name, new() { ExactType = null, TypeConstraints = typeParameterSymbol.ConstraintTypes })
);
}
else
{
// For non-type-parameter arguments, we can treat the argument as the exact type.
constraints.Add(
(name, new() { ExactType = baseTypeArgument, TypeConstraints = [] })
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +378 to +384
foreach (var attribute in method.ContainingSymbol.GetAttributes())
{
if (attribute is not
{
AttributeClass.IsBehaviorsAttribute: true,
ConstructorArguments: [{ Kind: TypedConstantKind.Array } behaviors]
})
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

IHR0020 validation only scans [Behaviors] attributes on the handler's containing type (method.ContainingSymbol.GetAttributes()), but the generator also applies assembly-level [assembly: Behaviors(...)] attributes. This means mismatched fixed/partially-specified behaviors declared at assembly scope won't be diagnosed. Consider also scanning context.Compilation.Assembly.GetAttributes() (and/or modules) for BehaviorsAttribute and reporting IHR0020 at the corresponding assembly attribute argument location.

Copilot uses AI. Check for mistakes.
@viceroypenguin viceroypenguin deleted the copilot/update-behavior-generic-parameters branch February 9, 2026 18:10
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.

Allow Behaviors to be partially or fully specified types

2 participants