C# source generator that creates combinatorial extension method overloads by treating a parameter window as optional and emitting legal, unique subsequences.
$ dotnet add package Tenekon.MethodOverloads.SourceGeneratorA C# source generator that creates extension overloads by treating a selected parameter window as optional and emitting legal, unique subsequences. It supports matchers, bucketized output, visibility overrides, subsequence strategies, and generic substitution via SupplyParameterType.
<ItemGroup>
<PackageReference Include="Tenekon.MethodOverloads.SourceGenerator" Version="x.y.z" PrivateAssets="all" />
</ItemGroup>
[GenerateOverloads] to a method, or [GenerateMethodOverloads(Matchers = ...)] to a type.[OverloadGenerationOptions(...)] to control matching and output.MethodOverloads_<Namespace>*.g.cs.Input:
namespace Demo;
using Tenekon.MethodOverloads;
public sealed class OrderService
{
[GenerateOverloads(Begin = nameof(tenantId))]
public void CreateOrder(string orderId, string tenantId, bool requireApproval) { }
}Output:
namespace Demo;
public static class MethodOverloads
{
public static void CreateOrder(this OrderService source, string orderId) =>
source.CreateOrder(orderId, tenantId: default(string), requireApproval: default(bool));
public static void CreateOrder(this OrderService source, string orderId, string tenantId) =>
source.CreateOrder(orderId, tenantId, requireApproval: default(bool));
public static void CreateOrder(this OrderService source, string orderId, bool requireApproval) =>
source.CreateOrder(orderId, tenantId: default(string), requireApproval);
}Use Begin, End, BeginExclusive, EndExclusive, or the constructor GenerateOverloads(string beginEnd).
[GenerateOverloads(BeginExclusive = nameof(start), End = nameof(end))]
public void Query(int start, int end, bool includeMetadata, string? tag) { }Input:
namespace Demo;
using Tenekon.MethodOverloads;
public sealed class UserService
{
[GenerateOverloads(Matchers = [typeof(UserMatchers)])]
public void UpdateUser(string id, string name, int level, bool active) { }
}
internal interface UserMatchers
{
[GenerateOverloads(nameof(paramB))]
void UpdateUser(int paramA, bool paramB);
}Output:
namespace Demo;
public static class MethodOverloads
{
public static void UpdateUser(this UserService source, string id, string name, int level) =>
source.UpdateUser(id, name, level, active: default(bool));
}Input:
namespace Demo;
using Tenekon.MethodOverloads;
[GenerateMethodOverloads(Matchers = [typeof(MathMatchers)])]
public static class MathUtils
{
public static void Multiply(int left, int right, bool checkedOverflow) { }
}
internal interface MathMatchers
{
[GenerateOverloads(nameof(paramB))]
void Multiply(int paramA, bool paramB);
}Output:
namespace Demo;
public static class MethodOverloads
{
extension(MathUtils)
{
public static void Multiply(int left, int right) =>
MathUtils.Multiply(left, right, checkedOverflow: default(bool));
}
}RangeAnchorMatchMode.TypeOnly (default) matches by type only.
RangeAnchorMatchMode.TypeAndName requires matching names as well.
[OverloadGenerationOptions(RangeAnchorMatchMode = RangeAnchorMatchMode.TypeAndName)]
[GenerateOverloads(Matchers = [typeof(ServiceMatchers)])]
public void Call(string id, string name, bool active) { }OverloadSubsequenceStrategy.UniqueBySignature (default) generates all unique overloads.
OverloadSubsequenceStrategy.PrefixOnly generates only prefix omissions.
[OverloadGenerationOptions(SubsequenceStrategy = OverloadSubsequenceStrategy.PrefixOnly)]
[GenerateOverloads(Begin = nameof(optionalA))]
public void Configure(int required, string? optionalA, bool optionalB) { }[OverloadGenerationOptions(OverloadVisibility = OverloadVisibility.Internal)]
[GenerateOverloads(Begin = nameof(optionalA))]
public void Configure(int required, string? optionalA, bool optionalB) { }Route generated overloads into a specific static partial class:
public static partial class MyBucket
{
}
[OverloadGenerationOptions(BucketType = typeof(MyBucket))]
[GenerateOverloads(Begin = nameof(optionalA))]
public void Configure(int required, string? optionalA, bool optionalB) { }Output:
public static partial class MyBucket
{
public static void Configure(this /* target type */ source, int required) =>
source.Configure(required, optionalA: default(string), optionalB: default(bool));
}Replace method type parameters with concrete types in generated overloads and invocations.
public sealed class Constraint { }
public interface IService<T> { }
public sealed class Api
{
[GenerateOverloads(nameof(optionalObject))]
[SupplyParameterType(nameof(TConstraint), typeof(Constraint))]
public void Use<TConstraint>(IService<TConstraint>? service, object? optionalObject) { }
}Output:
public static class MethodOverloads
{
public static void Use(this Api source, IService<Constraint>? service) =>
source.Use<Constraint>(service, default(object?));
}If only some method type parameters are supplied, the overload stays generic for the remaining ones.
Containing type type parameters and constraints are preserved on generated overloads.
public sealed class Container<T> where T : class, new()
{
[GenerateOverloads(nameof(optionalObject))]
public void Create(T value, object? optionalObject) { }
}Generated overloads keep T and its constraints.
Emit attributes only (skip generation and diagnostics):
<PropertyGroup>
<TenekonMethodOverloadsSourceGeneratorAttributesOnly>true</TenekonMethodOverloadsSourceGeneratorAttributesOnly>
</PropertyGroup>Diagnostics are reported by the analyzer and surfaced during build:
MOG001 Invalid window anchor.MOG002 Matcher has no subsequence match.MOG003 Defaults inside window.MOG004 Params outside window.MOG005 Ref/out/in omitted.MOG006 Duplicate signature skipped.MOG007 Conflicting window anchors (BeginEnd vs Begin/End).MOG008 Redundant Begin and End.MOG009 Begin and BeginExclusive conflict.MOG010 End and EndExclusive conflict.MOG011 Parameterless target method.MOG012 Matchers + window anchors conflict.MOG013 Invalid bucket type.MOG014 Invalid SupplyParameterType usage.MOG015 SupplyParameterType refers to missing type parameter.MOG016 Conflicting SupplyParameterType mappings.You can downgrade error-level diagnostics in .globalconfig if you need the project to compile with intentional violations.
ref/out/in parameters.params must be inside the optional window to be omitted.internal.See docs/generator.md for detailed behavior and docs/acceptance-criterias.md for the acceptance project structure.