diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39a23956a..c02d5a89e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
## Unreleased
+## 5.37.2
+
+### WolverineFx (core)
+
+- Removed the experimental Wolverine-specific Roslyn source generator (`Wolverine.SourceGeneration`)
+ and the `IWolverineTypeLoader` / `[WolverineTypeManifest]` / `CompositeWolverineTypeLoader`
+ surface it produced. The compile-time handler-discovery path was never wired up to anything in
+ steady state — handler graph compilation always falls back to `compileWithRuntimeScanning`, which
+ has been the only code path exercised by tests and downstream consumers. Stripping it removes a
+ netstandard2.0 analyzer DLL from the WolverineFx NuGet, the analyzer ProjectReference from
+ `Wolverine.csproj`, the source-gen branches in `ExtensionLoader.ApplyExtensions`,
+ `WolverineRuntime.HostService` startup, `HandlerGraph.Compile`, and `HandlerChain.AttachTypes`,
+ plus the two `TypeLoaderManifestModule*` test fixtures and their aggregation tests. The
+ `JasperFx.SourceGeneration` analyzer (separate package) is unaffected.
+
## 5.37.0
### WolverineFx.Marten
diff --git a/Directory.Build.props b/Directory.Build.props
index 5f3aa9f9e..0edb5f313 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -11,7 +11,7 @@
1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618;VSTHRD200
true
enable
- 5.37.1
+ 5.37.2
$(PackageProjectUrl)
true
true
diff --git a/src/Testing/CoreTests/Configuration/composite_wolverine_typeloader_tests.cs b/src/Testing/CoreTests/Configuration/composite_wolverine_typeloader_tests.cs
deleted file mode 100644
index e5deff38f..000000000
--- a/src/Testing/CoreTests/Configuration/composite_wolverine_typeloader_tests.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using Shouldly;
-using Wolverine.Runtime;
-using Xunit;
-
-namespace CoreTests.Configuration;
-
-public class composite_wolverine_typeloader_tests
-{
- [Fact]
- public void unions_handler_types_across_inner_loaders_and_dedupes()
- {
- var a = new StubLoader(handlers: [typeof(string), typeof(int)]);
- var b = new StubLoader(handlers: [typeof(int), typeof(double)]);
-
- var composite = new CompositeWolverineTypeLoader(new[] { a, b });
-
- composite.DiscoveredHandlerTypes
- .ShouldBe(new[] { typeof(string), typeof(int), typeof(double) }, ignoreOrder: true);
- }
-
- [Fact]
- public void unions_message_types_and_dedupes_by_message_type()
- {
- var a = new StubLoader(messages: [(typeof(string), "alias-a"), (typeof(int), "alias-int-a")]);
- var b = new StubLoader(messages: [(typeof(int), "alias-int-b"), (typeof(double), "alias-d")]);
-
- var composite = new CompositeWolverineTypeLoader(new[] { a, b });
-
- composite.DiscoveredMessageTypes.Select(t => t.MessageType).ShouldBe(
- new[] { typeof(string), typeof(int), typeof(double) }, ignoreOrder: true);
-
- // First loader wins on alias collision — matches single-loader semantics.
- composite.DiscoveredMessageTypes
- .Single(t => t.MessageType == typeof(int)).Alias
- .ShouldBe("alias-int-a");
- }
-
- [Fact]
- public void pre_generated_handler_types_unioned_when_any_inner_has_them()
- {
- var a = new StubLoader(preGenerated: new Dictionary { ["A"] = typeof(string) });
- var b = new StubLoader(preGenerated: new Dictionary { ["B"] = typeof(int) });
-
- var composite = new CompositeWolverineTypeLoader(new[] { a, b });
-
- composite.HasPreGeneratedHandlers.ShouldBeTrue();
- composite.PreGeneratedHandlerTypes.ShouldNotBeNull();
- composite.PreGeneratedHandlerTypes!["A"].ShouldBe(typeof(string));
- composite.PreGeneratedHandlerTypes!["B"].ShouldBe(typeof(int));
-
- composite.TryFindPreGeneratedType("A").ShouldBe(typeof(string));
- composite.TryFindPreGeneratedType("B").ShouldBe(typeof(int));
- composite.TryFindPreGeneratedType("missing").ShouldBeNull();
- }
-
- [Fact]
- public void pre_generated_handler_types_are_null_when_no_inner_has_them()
- {
- var a = new StubLoader();
- var b = new StubLoader();
-
- var composite = new CompositeWolverineTypeLoader(new[] { a, b });
-
- composite.HasPreGeneratedHandlers.ShouldBeFalse();
- composite.PreGeneratedHandlerTypes.ShouldBeNull();
- }
-
- [Fact]
- public void empty_inner_list_throws()
- {
- Should.Throw(() => new CompositeWolverineTypeLoader(Array.Empty()));
- }
-
- private sealed class StubLoader : IWolverineTypeLoader
- {
- public StubLoader(
- IReadOnlyList? handlers = null,
- IReadOnlyList<(Type, string)>? messages = null,
- IReadOnlyDictionary? preGenerated = null)
- {
- DiscoveredHandlerTypes = handlers ?? Array.Empty();
- DiscoveredMessageTypes = messages ?? Array.Empty<(Type, string)>();
- PreGeneratedHandlerTypes = preGenerated;
- HasPreGeneratedHandlers = preGenerated is { Count: > 0 };
- }
-
- public IReadOnlyList DiscoveredHandlerTypes { get; }
- public IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes { get; }
- public IReadOnlyList DiscoveredHttpEndpointTypes { get; } = Array.Empty();
- public IReadOnlyList DiscoveredExtensionTypes { get; } = Array.Empty();
- public bool HasPreGeneratedHandlers { get; }
- public IReadOnlyDictionary? PreGeneratedHandlerTypes { get; }
-
- public Type? TryFindPreGeneratedType(string typeName) =>
- PreGeneratedHandlerTypes is not null && PreGeneratedHandlerTypes.TryGetValue(typeName, out var t) ? t : null;
- }
-}
diff --git a/src/Testing/CoreTests/Configuration/typeloader_manifest_aggregation_tests.cs b/src/Testing/CoreTests/Configuration/typeloader_manifest_aggregation_tests.cs
deleted file mode 100644
index 5b63269e7..000000000
--- a/src/Testing/CoreTests/Configuration/typeloader_manifest_aggregation_tests.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Shouldly;
-using Wolverine;
-using Wolverine.Runtime;
-using TypeLoaderManifestModuleA;
-using TypeLoaderManifestModuleB;
-using Xunit;
-
-namespace CoreTests.Configuration;
-
-// Regression coverage for #2632.
-//
-// Bug: since 5.34.0, WolverineRuntime only inspects [WolverineTypeManifest] on
-// Options.ApplicationAssembly when picking the source-generated IWolverineTypeLoader.
-// Handlers in *referenced* assemblies that also carry [WolverineTypeManifest] are
-// silently dropped (they would surface as IndeterminateRoutesException on first
-// invocation). opts.Discovery.IncludeAssembly(...) is ignored on the source-gen
-// path; the runtime-scanning path still works.
-//
-// Fixture assemblies TypeLoaderManifestModuleA and TypeLoaderManifestModuleB each
-// carry [assembly: WolverineTypeManifest(typeof(...))] and a stub IWolverineTypeLoader
-// that lists exactly one handler type. The test below pins down that — when both
-// assemblies are presented to the host (one as ApplicationAssembly, one via
-// IncludeAssembly) — handlers from BOTH assemblies appear in the HandlerGraph.
-// Pre-fix it fails because only the ApplicationAssembly's loader is consulted.
-public class typeloader_manifest_aggregation_tests
-{
- [Fact]
- public async Task aggregates_typeloader_manifests_across_application_and_discovery_assemblies()
- {
- using var host = await Host.CreateDefaultBuilder()
- .UseWolverine(opts =>
- {
- opts.ApplicationAssembly = typeof(ModuleATypeLoader).Assembly;
- opts.Discovery.IncludeAssembly(typeof(ModuleBTypeLoader).Assembly);
- })
- .StartAsync();
-
- var runtime = (WolverineRuntime)host.Services.GetRequiredService();
- var chains = runtime.Handlers.Chains;
-
- chains.Any(c => c.MessageType == typeof(ModuleAMessage))
- .ShouldBeTrue("Handler from the application assembly should be discovered");
-
- chains.Any(c => c.MessageType == typeof(ModuleBMessage))
- .ShouldBeTrue(
- "Handler from a referenced assembly with its own [WolverineTypeManifest] " +
- "should be discovered too — it's currently dropped on the source-gen path. See #2632.");
- }
-}
diff --git a/src/Testing/CoreTests/CoreTests.csproj b/src/Testing/CoreTests/CoreTests.csproj
index 21308dd78..a6a780711 100644
--- a/src/Testing/CoreTests/CoreTests.csproj
+++ b/src/Testing/CoreTests/CoreTests.csproj
@@ -21,8 +21,6 @@
-
-
diff --git a/src/Testing/TypeLoaderManifestModuleA/ModuleA.cs b/src/Testing/TypeLoaderManifestModuleA/ModuleA.cs
deleted file mode 100644
index 836dfad9b..000000000
--- a/src/Testing/TypeLoaderManifestModuleA/ModuleA.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Wolverine.Attributes;
-using Wolverine.Runtime;
-using Wolverine.Runtime.Handlers;
-
-// Marks this assembly as having a source-generated IWolverineTypeLoader.
-// Mirrors what the Wolverine.SourceGeneration analyzer emits when it processes
-// a project. Used by tests covering #2632 (aggregating manifests across
-// referenced assemblies, not just Options.ApplicationAssembly).
-[assembly: WolverineTypeManifest(typeof(TypeLoaderManifestModuleA.ModuleATypeLoader))]
-
-namespace TypeLoaderManifestModuleA;
-
-public record ModuleAMessage(string Name);
-
-public class ModuleAHandler
-{
- public void Handle(ModuleAMessage message)
- {
- }
-}
-
-public class ModuleATypeLoader : IWolverineTypeLoader
-{
- public IReadOnlyList DiscoveredHandlerTypes { get; } = new[] { typeof(ModuleAHandler) };
-
- public IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes { get; } =
- new (Type, string)[] { (typeof(ModuleAMessage), "module-a-message") };
-
- public IReadOnlyList DiscoveredHttpEndpointTypes { get; } = Array.Empty();
-
- public IReadOnlyList DiscoveredExtensionTypes { get; } = Array.Empty();
-
- public bool HasPreGeneratedHandlers => false;
-
- public IReadOnlyDictionary? PreGeneratedHandlerTypes => null;
-
- public Type? TryFindPreGeneratedType(string typeName) => null;
-}
diff --git a/src/Testing/TypeLoaderManifestModuleA/TypeLoaderManifestModuleA.csproj b/src/Testing/TypeLoaderManifestModuleA/TypeLoaderManifestModuleA.csproj
deleted file mode 100644
index 64df70d35..000000000
--- a/src/Testing/TypeLoaderManifestModuleA/TypeLoaderManifestModuleA.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- false
- false
- false
- false
- false
- false
-
-
-
-
-
diff --git a/src/Testing/TypeLoaderManifestModuleB/ModuleB.cs b/src/Testing/TypeLoaderManifestModuleB/ModuleB.cs
deleted file mode 100644
index 7b29ca495..000000000
--- a/src/Testing/TypeLoaderManifestModuleB/ModuleB.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Wolverine.Attributes;
-using Wolverine.Runtime;
-using Wolverine.Runtime.Handlers;
-
-[assembly: WolverineTypeManifest(typeof(TypeLoaderManifestModuleB.ModuleBTypeLoader))]
-
-namespace TypeLoaderManifestModuleB;
-
-public record ModuleBMessage(string Name);
-
-public class ModuleBHandler
-{
- public void Handle(ModuleBMessage message)
- {
- }
-}
-
-public class ModuleBTypeLoader : IWolverineTypeLoader
-{
- public IReadOnlyList DiscoveredHandlerTypes { get; } = new[] { typeof(ModuleBHandler) };
-
- public IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes { get; } =
- new (Type, string)[] { (typeof(ModuleBMessage), "module-b-message") };
-
- public IReadOnlyList DiscoveredHttpEndpointTypes { get; } = Array.Empty();
-
- public IReadOnlyList DiscoveredExtensionTypes { get; } = Array.Empty();
-
- public bool HasPreGeneratedHandlers => false;
-
- public IReadOnlyDictionary? PreGeneratedHandlerTypes => null;
-
- public Type? TryFindPreGeneratedType(string typeName) => null;
-}
diff --git a/src/Testing/TypeLoaderManifestModuleB/TypeLoaderManifestModuleB.csproj b/src/Testing/TypeLoaderManifestModuleB/TypeLoaderManifestModuleB.csproj
deleted file mode 100644
index 64df70d35..000000000
--- a/src/Testing/TypeLoaderManifestModuleB/TypeLoaderManifestModuleB.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- false
- false
- false
- false
- false
- false
-
-
-
-
-
diff --git a/src/Wolverine.SourceGeneration/HandlerTypeInfo.cs b/src/Wolverine.SourceGeneration/HandlerTypeInfo.cs
deleted file mode 100644
index 13d241f78..000000000
--- a/src/Wolverine.SourceGeneration/HandlerTypeInfo.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-using System.Collections.Generic;
-
-namespace Wolverine.SourceGeneration
-{
- ///
- /// Metadata about a discovered handler type collected during syntax/semantic analysis.
- ///
- internal sealed class HandlerTypeInfo
- {
- public HandlerTypeInfo(string fullName, string namespaceName, string className)
- {
- FullName = fullName;
- NamespaceName = namespaceName;
- ClassName = className;
- }
-
- public string FullName { get; }
- public string NamespaceName { get; }
- public string ClassName { get; }
-
- ///
- /// Message types discovered from handler method first parameters.
- /// Each entry is (FullTypeName, Alias) where alias is the simple type name.
- ///
- public List<(string FullTypeName, string Alias)> MessageTypes { get; } = new List<(string, string)>();
- }
-}
diff --git a/src/Wolverine.SourceGeneration/MessageTypeInfo.cs b/src/Wolverine.SourceGeneration/MessageTypeInfo.cs
deleted file mode 100644
index 2ecba1fb3..000000000
--- a/src/Wolverine.SourceGeneration/MessageTypeInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Wolverine.SourceGeneration
-{
- ///
- /// Metadata about a discovered message type.
- ///
- internal sealed class MessageTypeInfo
- {
- public MessageTypeInfo(string fullName, string alias)
- {
- FullName = fullName;
- Alias = alias;
- }
-
- public string FullName { get; }
- public string Alias { get; }
- }
-}
diff --git a/src/Wolverine.SourceGeneration/Wolverine.SourceGeneration.csproj b/src/Wolverine.SourceGeneration/Wolverine.SourceGeneration.csproj
deleted file mode 100644
index bf07449a0..000000000
--- a/src/Wolverine.SourceGeneration/Wolverine.SourceGeneration.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- Roslyn source generator for compile-time Wolverine handler and message type discovery
- netstandard2.0
- 12
- $(NoWarn);CS8603
- true
- true
- enable
- false
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
diff --git a/src/Wolverine.SourceGeneration/WolverineTypeManifestGenerator.cs b/src/Wolverine.SourceGeneration/WolverineTypeManifestGenerator.cs
deleted file mode 100644
index 250545649..000000000
--- a/src/Wolverine.SourceGeneration/WolverineTypeManifestGenerator.cs
+++ /dev/null
@@ -1,656 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-
-namespace Wolverine.SourceGeneration
-{
- ///
- /// Roslyn incremental source generator that discovers Wolverine handler types,
- /// message types, pre-generated handler code, and extension types at compile time,
- /// emitting an IWolverineTypeLoader implementation that eliminates runtime assembly
- /// scanning during startup.
- ///
- [Generator]
- public class WolverineTypeManifestGenerator : IIncrementalGenerator
- {
- // Handler type name suffixes matching Wolverine conventions
- private const string HandlerSuffix = "Handler";
- private const string ConsumerSuffix = "Consumer";
-
- // Well-known Wolverine attribute and interface full names
- private const string WolverineHandlerAttributeFullName = "Wolverine.Attributes.WolverineHandlerAttribute";
- private const string WolverineIgnoreAttributeFullName = "Wolverine.Attributes.WolverineIgnoreAttribute";
- private const string WolverineMessageAttributeFullName = "Wolverine.Attributes.WolverineMessageAttribute";
- private const string IWolverineHandlerFullName = "Wolverine.IWolverineHandler";
- private const string SagaFullName = "Wolverine.Saga";
- private const string IMessageFullName = "Wolverine.IMessage";
-
- // Phase D: Pre-generated handler types
- private const string MessageHandlerFullName = "Wolverine.Runtime.Handlers.MessageHandler";
- internal const string WolverineHandlersNamespaceConst = "WolverineHandlers";
-
- // Phase E: Extension discovery
- private const string IWolverineExtensionFullName = "Wolverine.IWolverineExtension";
- private const string WolverineModuleAttributeFullName = "Wolverine.Attributes.WolverineModuleAttribute";
-
- // Valid handler method names (matching HandlerChain and SagaChain constants)
- private static readonly HashSet ValidMethodNames = new HashSet(StringComparer.Ordinal)
- {
- "Handle", "Handles", "HandleAsync", "HandlesAsync",
- "Consume", "Consumes", "ConsumeAsync", "ConsumesAsync",
- "Orchestrate", "Orchestrates", "OrchestrateAsync", "OrchestratesAsync",
- "Start", "Starts", "StartAsync", "StartsAsync",
- "StartOrHandle", "StartsOrHandles", "StartOrHandleAsync", "StartsOrHandlesAsync",
- "NotFound", "NotFoundAsync"
- };
-
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- // Step 1: Find candidate class declarations that might be handlers or message types
- var classDeclarations = context.SyntaxProvider
- .CreateSyntaxProvider(
- predicate: static (node, _) => IsCandidate(node),
- transform: static (ctx, _) => ClassifyType(ctx))
- .Where(static result => result != null);
-
- // Step 2: Combine with compilation for final resolution
- var compilationAndClasses = context.CompilationProvider
- .Combine(classDeclarations.Collect());
-
- // Step 3: Emit the source
- context.RegisterSourceOutput(compilationAndClasses,
- static (spc, source) => Execute(source.Left, source.Right!, spc));
- }
-
- ///
- /// Fast syntactic predicate: is this node a class/record declaration that could
- /// potentially be a handler or message type?
- ///
- private static bool IsCandidate(SyntaxNode node)
- {
- // Accept class declarations and record declarations
- if (node is ClassDeclarationSyntax classDecl)
- {
- // Must be public (or nested public)
- if (!HasPublicModifier(classDecl.Modifiers))
- return false;
-
- // Must not be abstract (unless it's checked later for Saga subclass)
- // We let everything through that's public; semantic analysis will refine
- return true;
- }
-
- if (node is RecordDeclarationSyntax recordDecl)
- {
- if (!HasPublicModifier(recordDecl.Modifiers))
- return false;
- return true;
- }
-
- return false;
- }
-
- private static bool HasPublicModifier(SyntaxTokenList modifiers)
- {
- foreach (var modifier in modifiers)
- {
- if (modifier.IsKind(SyntaxKind.PublicKeyword))
- return true;
- }
- return false;
- }
-
- ///
- /// Semantic transform: classify a type as a handler, message type, or both.
- /// Returns null if the type doesn't match any Wolverine conventions.
- ///
- private static DiscoveredType? ClassifyType(GeneratorSyntaxContext context)
- {
- INamedTypeSymbol? classSymbol = null;
-
- if (context.Node is ClassDeclarationSyntax)
- {
- classSymbol = context.SemanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol;
- }
- else if (context.Node is RecordDeclarationSyntax)
- {
- classSymbol = context.SemanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol;
- }
-
- if (classSymbol == null) return null;
- if (classSymbol.IsAbstract) return null;
- if (classSymbol.IsGenericType && !classSymbol.IsDefinition) return null;
- // Skip open generic type definitions (e.g., Handler) -- we only want concrete types
- if (classSymbol.IsGenericType) return null;
- if (classSymbol.DeclaredAccessibility != Accessibility.Public) return null;
-
- // Check for [WolverineIgnore]
- if (HasAttribute(classSymbol, WolverineIgnoreAttributeFullName))
- return null;
-
- var isHandler = IsHandlerType(classSymbol);
- var isMessage = IsMessageType(classSymbol);
-
- if (!isHandler && !isMessage) return null;
-
- var result = new DiscoveredType(
- classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
- classSymbol.Name,
- classSymbol.ContainingNamespace?.ToDisplayString() ?? "",
- isHandler,
- isMessage);
-
- // If it's a handler, find message types from method parameters
- if (isHandler)
- {
- FindMessageTypesFromMethods(classSymbol, result);
- }
-
- return result;
- }
-
- ///
- /// Determines if a type qualifies as a Wolverine handler.
- /// Matches the same rules as HandlerDiscovery.specifyConventionalHandlerDiscovery().
- ///
- private static bool IsHandlerType(INamedTypeSymbol symbol)
- {
- // Rule 1: Name ends with "Handler" or "Consumer"
- if (symbol.Name.EndsWith(HandlerSuffix, StringComparison.Ordinal) ||
- symbol.Name.EndsWith(ConsumerSuffix, StringComparison.Ordinal))
- {
- return true;
- }
-
- // Rule 2: Decorated with [WolverineHandler]
- if (HasAttribute(symbol, WolverineHandlerAttributeFullName))
- {
- return true;
- }
-
- // Rule 3: Implements IWolverineHandler
- if (ImplementsInterface(symbol, IWolverineHandlerFullName))
- {
- return true;
- }
-
- // Rule 4: Inherits from Saga (directly or indirectly)
- if (InheritsFrom(symbol, SagaFullName))
- {
- return true;
- }
-
- // Rule 5: Has methods decorated with [WolverineHandler]
- foreach (var member in symbol.GetMembers())
- {
- if (member is IMethodSymbol method && HasAttribute(method, WolverineHandlerAttributeFullName))
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// Determines if a type qualifies as a Wolverine message type.
- ///
- private static bool IsMessageType(INamedTypeSymbol symbol)
- {
- if (symbol.IsStatic) return false;
- if (!symbol.IsReferenceType && !symbol.IsValueType) return false;
-
- // Implements IMessage
- if (ImplementsInterface(symbol, IMessageFullName))
- return true;
-
- // Decorated with [WolverineMessage]
- if (HasAttribute(symbol, WolverineMessageAttributeFullName))
- return true;
-
- return false;
- }
-
- ///
- /// Find message types by inspecting the first parameter of handler methods.
- ///
- private static void FindMessageTypesFromMethods(INamedTypeSymbol handlerType, DiscoveredType result)
- {
- foreach (var member in handlerType.GetMembers())
- {
- if (!(member is IMethodSymbol method)) continue;
- if (method.DeclaredAccessibility != Accessibility.Public) continue;
- if (method.MethodKind != MethodKind.Ordinary) continue;
- if (method.Parameters.Length == 0) continue;
-
- // Check if method name matches handler conventions or has [WolverineHandler]
- var isHandlerMethod = ValidMethodNames.Contains(method.Name) ||
- HasAttribute(method, WolverineHandlerAttributeFullName);
-
- if (!isHandlerMethod) continue;
-
- // Check for [WolverineIgnore]
- if (HasAttribute(method, WolverineIgnoreAttributeFullName)) continue;
-
- // First parameter is the message type
- var firstParam = method.Parameters[0];
- var paramType = firstParam.Type;
-
- // Skip primitives, object, arrays of object, etc.
- if (paramType.SpecialType != SpecialType.None) continue;
- if (paramType.TypeKind == TypeKind.Interface) continue; // Skip interface params
- if (paramType.TypeKind == TypeKind.TypeParameter) continue; // Skip generic params
-
- // Skip open generic types (e.g., when handler method uses T as parameter type)
- if (paramType is INamedTypeSymbol namedParamType && namedParamType.IsGenericType)
- {
- // Only allow closed generic types (all type arguments are concrete)
- var hasUnboundTypeArgs = false;
- foreach (var typeArg in namedParamType.TypeArguments)
- {
- if (typeArg.TypeKind == TypeKind.TypeParameter)
- {
- hasUnboundTypeArgs = true;
- break;
- }
- }
- if (hasUnboundTypeArgs) continue;
- }
-
- var fullTypeName = paramType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- var alias = paramType.Name;
-
- result.MethodMessageTypes.Add((fullTypeName, alias));
- }
- }
-
- private static bool HasAttribute(ISymbol symbol, string attributeFullName)
- {
- foreach (var attr in symbol.GetAttributes())
- {
- var attrClass = attr.AttributeClass;
- if (attrClass != null && attrClass.ToDisplayString() == attributeFullName)
- return true;
- }
- return false;
- }
-
- private static bool ImplementsInterface(INamedTypeSymbol symbol, string interfaceFullName)
- {
- foreach (var iface in symbol.AllInterfaces)
- {
- if (iface.ToDisplayString() == interfaceFullName)
- return true;
- }
- return false;
- }
-
- private static bool InheritsFrom(INamedTypeSymbol symbol, string baseClassFullName)
- {
- var current = symbol.BaseType;
- while (current != null)
- {
- if (current.ToDisplayString() == baseClassFullName)
- return true;
- current = current.BaseType;
- }
- return false;
- }
-
- ///
- /// Final emission step: generate the IWolverineTypeLoader implementation.
- ///
- private static void Execute(
- Compilation compilation,
- ImmutableArray discoveredTypes,
- SourceProductionContext context)
- {
- if (discoveredTypes.IsDefaultOrEmpty) return;
-
- // Check if the compilation references Wolverine (has IWolverineTypeLoader)
- var typeLoaderSymbol = compilation.GetTypeByMetadataName("Wolverine.Runtime.IWolverineTypeLoader");
- if (typeLoaderSymbol == null)
- {
- // This assembly doesn't reference Wolverine, skip generation
- return;
- }
-
- // Deduplicate and categorize
- var handlerTypes = new List();
- var messageTypes = new Dictionary(); // FullName -> Alias
- var handlerTypeNames = new HashSet();
-
- foreach (var type in discoveredTypes)
- {
- if (type == null) continue;
-
- if (type.IsHandler && handlerTypeNames.Add(type.FullName))
- {
- handlerTypes.Add(type.FullName);
-
- // Add message types from handler method params
- foreach (var (msgFullName, msgAlias) in type.MethodMessageTypes)
- {
- if (!messageTypes.ContainsKey(msgFullName))
- {
- messageTypes[msgFullName] = msgAlias;
- }
- }
- }
-
- if (type.IsMessage)
- {
- var fullName = type.FullName;
- if (!messageTypes.ContainsKey(fullName))
- {
- messageTypes[fullName] = type.ClassName;
- }
- }
- }
-
- // Phase D: Find pre-generated handler types in the WolverineHandlers namespace
- // that inherit from MessageHandler. These are emitted by Wolverine's code generation
- // (dotnet run -- codegen) and can be looked up via dictionary instead of linear scan.
- var preGenHandlerTypes = FindPreGeneratedHandlerTypes(compilation);
-
- // Phase E: Find extension types implementing IWolverineExtension
- var extensionTypes = FindExtensionTypes(compilation);
-
- // Don't emit if we found nothing
- if (handlerTypes.Count == 0 && messageTypes.Count == 0
- && preGenHandlerTypes.Count == 0 && extensionTypes.Count == 0) return;
-
- var source = EmitTypeLoaderSource(handlerTypes, messageTypes, preGenHandlerTypes, extensionTypes);
- context.AddSource("WolverineTypeManifest.g.cs", SourceText.From(source, Encoding.UTF8));
- }
-
- ///
- /// Phase D: Scan for types in the WolverineHandlers namespace that inherit from
- /// Wolverine.Runtime.Handlers.MessageHandler. These are pre-generated handler types
- /// emitted by Wolverine's code generation step (dotnet run -- codegen).
- ///
- private static List<(string FullName, string ClassName)> FindPreGeneratedHandlerTypes(Compilation compilation)
- {
- var result = new List<(string, string)>();
-
- var messageHandlerSymbol = compilation.GetTypeByMetadataName(MessageHandlerFullName);
- if (messageHandlerSymbol == null) return result;
-
- // Scan all types in the compilation's source assembly
- var visitor = new PreGeneratedHandlerVisitor(messageHandlerSymbol, result);
- visitor.Visit(compilation.Assembly.GlobalNamespace);
-
- return result;
- }
-
- ///
- /// Phase E: Scan for types implementing IWolverineExtension or decorated with
- /// [WolverineModule] attribute in the compilation's source assembly.
- ///
- private static List FindExtensionTypes(Compilation compilation)
- {
- var result = new List();
-
- var extensionInterfaceSymbol = compilation.GetTypeByMetadataName(IWolverineExtensionFullName);
- if (extensionInterfaceSymbol == null) return result;
-
- var visitor = new ExtensionTypeVisitor(extensionInterfaceSymbol, result);
- visitor.Visit(compilation.Assembly.GlobalNamespace);
-
- return result;
- }
-
- private static string EmitTypeLoaderSource(
- List handlerTypes,
- Dictionary messageTypes,
- List<(string FullName, string ClassName)> preGenHandlerTypes,
- List extensionTypes)
- {
- var sb = new StringBuilder();
- var hasPreGenHandlers = preGenHandlerTypes.Count > 0;
-
- sb.AppendLine("// ");
- sb.AppendLine("// Generated by Wolverine.SourceGeneration");
- sb.AppendLine("#nullable enable");
- sb.AppendLine();
- sb.AppendLine("using System;");
- sb.AppendLine("using System.Collections.Generic;");
- sb.AppendLine("using Wolverine.Attributes;");
- sb.AppendLine("using Wolverine.Runtime;");
- sb.AppendLine();
- sb.AppendLine("[assembly: WolverineTypeManifest(typeof(Wolverine.Generated.GeneratedWolverineTypeLoader))]");
- sb.AppendLine();
- sb.AppendLine("namespace Wolverine.Generated");
- sb.AppendLine("{");
- sb.AppendLine(" /// ");
- sb.AppendLine(" /// Source-generated implementation of IWolverineTypeLoader that provides");
- sb.AppendLine(" /// compile-time handler and message type discovery, eliminating runtime");
- sb.AppendLine(" /// assembly scanning during Wolverine startup.");
- sb.AppendLine(" /// ");
- sb.AppendLine(" internal sealed class GeneratedWolverineTypeLoader : IWolverineTypeLoader");
- sb.AppendLine(" {");
-
- // DiscoveredHandlerTypes
- sb.AppendLine(" private static readonly IReadOnlyList _handlerTypes = new Type[]");
- sb.AppendLine(" {");
- foreach (var handler in handlerTypes)
- {
- sb.AppendLine($" typeof({handler}),");
- }
- sb.AppendLine(" };");
- sb.AppendLine();
- sb.AppendLine(" public IReadOnlyList DiscoveredHandlerTypes => _handlerTypes;");
- sb.AppendLine();
-
- // DiscoveredMessageTypes
- sb.AppendLine(" private static readonly IReadOnlyList<(Type MessageType, string Alias)> _messageTypes = new (Type, string)[]");
- sb.AppendLine(" {");
- foreach (var kvp in messageTypes)
- {
- sb.AppendLine($" (typeof({kvp.Key}), \"{kvp.Value}\"),");
- }
- sb.AppendLine(" };");
- sb.AppendLine();
- sb.AppendLine(" public IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes => _messageTypes;");
- sb.AppendLine();
-
- // DiscoveredHttpEndpointTypes (not yet implemented)
- sb.AppendLine(" public IReadOnlyList DiscoveredHttpEndpointTypes => Array.Empty();");
- sb.AppendLine();
-
- // Phase E: DiscoveredExtensionTypes
- if (extensionTypes.Count > 0)
- {
- sb.AppendLine(" private static readonly IReadOnlyList _extensionTypes = new Type[]");
- sb.AppendLine(" {");
- foreach (var ext in extensionTypes)
- {
- sb.AppendLine($" typeof({ext}),");
- }
- sb.AppendLine(" };");
- sb.AppendLine();
- sb.AppendLine(" public IReadOnlyList DiscoveredExtensionTypes => _extensionTypes;");
- }
- else
- {
- sb.AppendLine(" public IReadOnlyList DiscoveredExtensionTypes => Array.Empty();");
- }
- sb.AppendLine();
-
- // Phase D: HasPreGeneratedHandlers and PreGeneratedHandlerTypes
- sb.AppendLine($" public bool HasPreGeneratedHandlers => {(hasPreGenHandlers ? "true" : "false")};");
- sb.AppendLine();
-
- if (hasPreGenHandlers)
- {
- sb.AppendLine(" private static Dictionary? _preGenTypes;");
- sb.AppendLine();
- sb.AppendLine(" public IReadOnlyDictionary? PreGeneratedHandlerTypes => _preGenTypes ??= BuildPreGenTypes();");
- sb.AppendLine();
- sb.AppendLine(" private static Dictionary BuildPreGenTypes()");
- sb.AppendLine(" {");
- sb.AppendLine($" var dict = new Dictionary({preGenHandlerTypes.Count});");
- foreach (var (fullName, className) in preGenHandlerTypes)
- {
- sb.AppendLine($" dict[\"{className}\"] = typeof({fullName});");
- }
- sb.AppendLine(" return dict;");
- sb.AppendLine(" }");
- sb.AppendLine();
- sb.AppendLine(" public Type? TryFindPreGeneratedType(string typeName)");
- sb.AppendLine(" {");
- sb.AppendLine(" var types = PreGeneratedHandlerTypes;");
- sb.AppendLine(" if (types != null && types.TryGetValue(typeName, out var type))");
- sb.AppendLine(" {");
- sb.AppendLine(" return type;");
- sb.AppendLine(" }");
- sb.AppendLine(" return null;");
- sb.AppendLine(" }");
- }
- else
- {
- sb.AppendLine(" public IReadOnlyDictionary? PreGeneratedHandlerTypes => null;");
- sb.AppendLine();
- sb.AppendLine(" public Type? TryFindPreGeneratedType(string typeName) => null;");
- }
-
- sb.AppendLine(" }");
- sb.AppendLine("}");
-
- return sb.ToString();
- }
- }
-
- ///
- /// Intermediate result from the syntax/semantic analysis phase.
- ///
- internal sealed class DiscoveredType
- {
- public DiscoveredType(string fullName, string className, string namespaceName, bool isHandler, bool isMessage)
- {
- FullName = fullName;
- ClassName = className;
- NamespaceName = namespaceName;
- IsHandler = isHandler;
- IsMessage = isMessage;
- }
-
- public string FullName { get; }
- public string ClassName { get; }
- public string NamespaceName { get; }
- public bool IsHandler { get; }
- public bool IsMessage { get; }
-
- ///
- /// Message types discovered from handler method first parameters.
- /// (FullTypeName, Alias)
- ///
- public List<(string FullTypeName, string Alias)> MethodMessageTypes { get; } = new List<(string, string)>();
- }
-
- ///
- /// Phase D: Visits all namespaces in the compilation to find types in the
- /// WolverineHandlers namespace that inherit from MessageHandler.
- /// These represent pre-generated handler code from Wolverine's codegen step.
- ///
- internal sealed class PreGeneratedHandlerVisitor : SymbolVisitor
- {
- private readonly INamedTypeSymbol _messageHandlerSymbol;
- private readonly List<(string FullName, string ClassName)> _result;
-
- public PreGeneratedHandlerVisitor(
- INamedTypeSymbol messageHandlerSymbol,
- List<(string FullName, string ClassName)> result)
- {
- _messageHandlerSymbol = messageHandlerSymbol;
- _result = result;
- }
-
- public override void VisitNamespace(INamespaceSymbol symbol)
- {
- foreach (var member in symbol.GetMembers())
- {
- member.Accept(this);
- }
- }
-
- public override void VisitNamedType(INamedTypeSymbol symbol)
- {
- // Only consider types in a namespace ending with WolverineHandlers
- var ns = symbol.ContainingNamespace?.ToDisplayString() ?? "";
- if (!ns.EndsWith(WolverineTypeManifestGenerator.WolverineHandlersNamespaceConst))
- return;
-
- // Must not be abstract and must inherit from MessageHandler
- if (symbol.IsAbstract) return;
- if (!InheritsFrom(symbol, _messageHandlerSymbol)) return;
-
- var fullName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- _result.Add((fullName, symbol.Name));
- }
-
- private static bool InheritsFrom(INamedTypeSymbol symbol, INamedTypeSymbol baseType)
- {
- var current = symbol.BaseType;
- while (current != null)
- {
- if (SymbolEqualityComparer.Default.Equals(current, baseType))
- return true;
- current = current.BaseType;
- }
- return false;
- }
- }
-
- ///
- /// Phase E: Visits all namespaces in the compilation to find concrete types
- /// implementing IWolverineExtension.
- ///
- internal sealed class ExtensionTypeVisitor : SymbolVisitor
- {
- private readonly INamedTypeSymbol _extensionInterfaceSymbol;
- private readonly List _result;
-
- public ExtensionTypeVisitor(
- INamedTypeSymbol extensionInterfaceSymbol,
- List result)
- {
- _extensionInterfaceSymbol = extensionInterfaceSymbol;
- _result = result;
- }
-
- public override void VisitNamespace(INamespaceSymbol symbol)
- {
- foreach (var member in symbol.GetMembers())
- {
- member.Accept(this);
- }
- }
-
- public override void VisitNamedType(INamedTypeSymbol symbol)
- {
- // Must be a concrete, non-abstract class
- if (symbol.IsAbstract) return;
- if (symbol.TypeKind != TypeKind.Class) return;
- if (symbol.DeclaredAccessibility != Accessibility.Public &&
- symbol.DeclaredAccessibility != Accessibility.Internal) return;
-
- // Check if it implements IWolverineExtension
- foreach (var iface in symbol.AllInterfaces)
- {
- if (SymbolEqualityComparer.Default.Equals(iface, _extensionInterfaceSymbol))
- {
- var fullName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- _result.Add(fullName);
- return;
- }
- }
- }
- }
-}
diff --git a/src/Wolverine/Attributes/WolverineTypeManifestAttribute.cs b/src/Wolverine/Attributes/WolverineTypeManifestAttribute.cs
deleted file mode 100644
index e18ee44b4..000000000
--- a/src/Wolverine/Attributes/WolverineTypeManifestAttribute.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Wolverine.Attributes;
-
-///
-/// Applied by the Wolverine source generator to mark an assembly as containing
-/// a compile-time type manifest. During startup, Wolverine checks for this
-/// attribute on the application assembly. If found, it instantiates the
-/// specified IWolverineTypeLoader to bypass runtime assembly scanning.
-///
-/// This attribute is not intended to be applied manually. It is emitted
-/// by the Wolverine.SourceGeneration package.
-///
-[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
-public sealed class WolverineTypeManifestAttribute : Attribute
-{
- ///
- /// The Type that implements IWolverineTypeLoader, generated by the source generator.
- ///
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
- public Type LoaderType { get; }
-
- public WolverineTypeManifestAttribute(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type loaderType)
- {
- LoaderType = loaderType ?? throw new ArgumentNullException(nameof(loaderType));
- }
-}
diff --git a/src/Wolverine/ExtensionLoader.cs b/src/Wolverine/ExtensionLoader.cs
index a4939693e..0cf2d553f 100644
--- a/src/Wolverine/ExtensionLoader.cs
+++ b/src/Wolverine/ExtensionLoader.cs
@@ -3,7 +3,6 @@
using JasperFx.Core.Reflection;
using JasperFx.Core.TypeScanning;
using Wolverine.Attributes;
-using Wolverine.Runtime;
namespace Wolverine;
@@ -69,16 +68,6 @@ Assembly[] FindDependencies(Assembly a)
internal static void ApplyExtensions(WolverineOptions options)
{
- // Phase E: Check if we have a source-generated type loader with pre-discovered
- // extension types. If so, use those instead of scanning all assemblies.
- var typeLoader = TryFindTypeLoader(options.ApplicationAssembly);
- if (typeLoader?.DiscoveredExtensionTypes?.Count > 0)
- {
- ApplyExtensionsFromTypeLoader(options, typeLoader);
- return;
- }
-
- // Fallback to runtime assembly scanning
var assemblies = FindExtensionAssemblies();
if (assemblies.Length == 0)
@@ -95,53 +84,4 @@ internal static void ApplyExtensions(WolverineOptions options)
options.ApplyExtensions(extensions);
}
-
- ///
- /// Phase E: Apply extensions discovered at compile time by the source generator,
- /// bypassing the expensive AssemblyFinder scanning.
- ///
- private static void ApplyExtensionsFromTypeLoader(WolverineOptions options, IWolverineTypeLoader typeLoader)
- {
- var extensions = typeLoader.DiscoveredExtensionTypes
- .Where(t => t != null && !t.IsAbstract && typeof(IWolverineExtension).IsAssignableFrom(t))
- .Select(t => (IWolverineExtension)Activator.CreateInstance(t)!)
- .ToArray();
-
- if (extensions.Length == 0) return;
-
- // Include the assemblies that contain extension types for handler discovery
- var extensionAssemblies = extensions
- .Select(e => e.GetType().Assembly)
- .Distinct()
- .Where(a => a.HasAttribute())
- .ToArray();
-
- if (extensionAssemblies.Length > 0)
- {
- options.IncludeExtensionAssemblies(extensionAssemblies);
- }
-
- options.ApplyExtensions(extensions);
- }
-
- ///
- /// Try to find a source-generated IWolverineTypeLoader from the application assembly
- /// via the [WolverineTypeManifest] assembly attribute.
- ///
- internal static IWolverineTypeLoader? TryFindTypeLoader(Assembly? applicationAssembly)
- {
- if (applicationAssembly == null) return null;
-
- var attr = applicationAssembly.GetCustomAttribute();
- if (attr?.LoaderType == null) return null;
-
- try
- {
- return (IWolverineTypeLoader)Activator.CreateInstance(attr.LoaderType)!;
- }
- catch
- {
- return null;
- }
- }
}
diff --git a/src/Wolverine/Runtime/CompositeWolverineTypeLoader.cs b/src/Wolverine/Runtime/CompositeWolverineTypeLoader.cs
deleted file mode 100644
index 229aae13c..000000000
--- a/src/Wolverine/Runtime/CompositeWolverineTypeLoader.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using Wolverine.Runtime.Handlers;
-
-namespace Wolverine.Runtime;
-
-///
-/// Aggregates several source-generated instances
-/// into a single loader that exposes the union of their discovered types. The
-/// Wolverine source generator emits a [WolverineTypeManifest] attribute on
-/// every handler-bearing assembly; this composite is what the runtime hands to
-/// when more than one such assembly is loaded so that
-/// handlers in referenced assemblies are not silently dropped (#2632).
-///
-internal sealed class CompositeWolverineTypeLoader : IWolverineTypeLoader
-{
- private readonly IReadOnlyList _inner;
-
- public CompositeWolverineTypeLoader(IReadOnlyList inner)
- {
- if (inner == null) throw new ArgumentNullException(nameof(inner));
- if (inner.Count == 0)
- throw new ArgumentException("At least one inner loader is required", nameof(inner));
-
- _inner = inner;
-
- DiscoveredHandlerTypes = _inner.SelectMany(l => l.DiscoveredHandlerTypes).Distinct().ToList();
-
- DiscoveredMessageTypes = _inner.SelectMany(l => l.DiscoveredMessageTypes)
- .GroupBy(t => t.MessageType)
- .Select(g => g.First())
- .ToList();
-
- DiscoveredHttpEndpointTypes = _inner.SelectMany(l => l.DiscoveredHttpEndpointTypes).Distinct().ToList();
-
- DiscoveredExtensionTypes = _inner.SelectMany(l => l.DiscoveredExtensionTypes).Distinct().ToList();
-
- HasPreGeneratedHandlers = _inner.Any(l => l.HasPreGeneratedHandlers);
-
- if (HasPreGeneratedHandlers)
- {
- var merged = new Dictionary(StringComparer.Ordinal);
- foreach (var loader in _inner)
- {
- if (loader.PreGeneratedHandlerTypes is null) continue;
- foreach (var kvp in loader.PreGeneratedHandlerTypes)
- {
- // First loader wins on collision — matches single-loader semantics for
- // duplicate type names within one manifest. Logged at registration time
- // by HandlerGraph; not re-logged here to avoid double noise.
- merged.TryAdd(kvp.Key, kvp.Value);
- }
- }
-
- PreGeneratedHandlerTypes = merged;
- }
- else
- {
- PreGeneratedHandlerTypes = null;
- }
- }
-
- public IReadOnlyList DiscoveredHandlerTypes { get; }
-
- public IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes { get; }
-
- public IReadOnlyList DiscoveredHttpEndpointTypes { get; }
-
- public IReadOnlyList DiscoveredExtensionTypes { get; }
-
- public bool HasPreGeneratedHandlers { get; }
-
- public IReadOnlyDictionary? PreGeneratedHandlerTypes { get; }
-
- public Type? TryFindPreGeneratedType(string typeName)
- {
- foreach (var loader in _inner)
- {
- var hit = loader.TryFindPreGeneratedType(typeName);
- if (hit is not null) return hit;
- }
-
- return null;
- }
-}
diff --git a/src/Wolverine/Runtime/Handlers/HandlerChain.cs b/src/Wolverine/Runtime/Handlers/HandlerChain.cs
index fbb800d68..46651c664 100644
--- a/src/Wolverine/Runtime/Handlers/HandlerChain.cs
+++ b/src/Wolverine/Runtime/Handlers/HandlerChain.cs
@@ -277,24 +277,7 @@ Task ICodeFile.AttachTypes(GenerationRules rules, Assembly assembly, IServ
bool ICodeFile.AttachTypesSynchronously(GenerationRules rules, Assembly assembly, IServiceProvider? services,
string containingNamespace)
{
- // Use the source-generated type loader for O(1) lookup when available,
- // falling back to linear scan of assembly.ExportedTypes.
- // Phase D: First try the PreGeneratedHandlerTypes dictionary for O(1) lookup,
- // then fall back to TryFindPreGeneratedType for backward compatibility.
- var typeLoader = _parent?.TypeLoader;
- if (typeLoader is { HasPreGeneratedHandlers: true })
- {
- if (typeLoader.PreGeneratedHandlerTypes?.TryGetValue(TypeName, out var preGenType) == true)
- {
- _handlerType = preGenType;
- }
- else
- {
- _handlerType = typeLoader.TryFindPreGeneratedType(TypeName);
- }
- }
-
- _handlerType ??= assembly.ExportedTypes.FirstOrDefault(x => x.Name == TypeName);
+ _handlerType = assembly.ExportedTypes.FirstOrDefault(x => x.Name == TypeName);
if (_handlerType == null)
{
diff --git a/src/Wolverine/Runtime/Handlers/HandlerGraph.cs b/src/Wolverine/Runtime/Handlers/HandlerGraph.cs
index a776033f7..40c33dcc9 100644
--- a/src/Wolverine/Runtime/Handlers/HandlerGraph.cs
+++ b/src/Wolverine/Runtime/Handlers/HandlerGraph.cs
@@ -37,8 +37,6 @@ public partial class HandlerGraph : ICodeFileCollectionWithServices, IWithFailur
internal readonly HandlerDiscovery Discovery = new();
- private IWolverineTypeLoader? _typeLoader;
-
private ImHashMap _chains = ImHashMap.Empty;
private ImHashMap _handlers = ImHashMap.Empty;
@@ -65,21 +63,6 @@ public HandlerGraph()
RegisterMessageType(typeof(FailureAcknowledgement));
}
- ///
- /// Set a source-generated type loader to bypass runtime assembly scanning.
- /// When set, Compile() will use the loader's pre-discovered types instead
- /// of running HandlerDiscovery.FindCalls().
- ///
- internal void UseTypeLoader(IWolverineTypeLoader typeLoader)
- {
- _typeLoader = typeLoader;
- }
-
- ///
- /// Returns the currently configured type loader, if any.
- ///
- internal IWolverineTypeLoader? TypeLoader => _typeLoader;
-
public Dictionary MappedGenericMessageTypes { get; } = new();
internal IServiceContainer Container { get; set; } = null!;
@@ -342,14 +325,7 @@ internal void Compile(WolverineOptions options, IServiceContainer container)
Rules = options.CodeGeneration;
- if (_typeLoader != null)
- {
- compileWithTypeLoader(options, logger);
- }
- else
- {
- compileWithRuntimeScanning(options, logger);
- }
+ compileWithRuntimeScanning(options, logger);
Group(options);
@@ -400,46 +376,6 @@ IEnumerable explodeChains(HandlerChain chain)
options.MessagePartitioning.MaybeInferGrouping(this);
}
- private void compileWithTypeLoader(WolverineOptions options, ILogger logger)
- {
- logger.LogInformation(
- "Using source-generated type loader for handler discovery, bypassing runtime assembly scanning");
-
- var handlerTypes = _typeLoader!.DiscoveredHandlerTypes.Concat(Discovery.ExplicitTypes);
-
- // Still use Discovery's method filtering on the pre-discovered types,
- // but skip the expensive assembly scanning to find those types
- var methods = new List<(Type, System.Reflection.MethodInfo)>();
- foreach (var handlerType in handlerTypes)
- {
- var typeMethods = handlerType
- .GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)
- .Where(x => x.DeclaringType != typeof(object))
- .Where(m => Discovery.MethodIncludes.Matches(m) && !Discovery.MethodExcludes.Matches(m))
- .Select(m => (handlerType, m));
-
- methods.AddRange(typeMethods);
- }
-
- var calls = methods.Select(x => new HandlerCall(x.Item1, x.Item2));
-
- if (methods.Count == 0)
- {
- logger.LogWarning(
- "Source-generated type loader found no handler methods. If this is unexpected, verify the source generator is correctly discovering handler types");
- }
- else
- {
- AddRange(calls);
- }
-
- // Also pre-register message types from the loader
- foreach (var (messageType, alias) in _typeLoader.DiscoveredMessageTypes)
- {
- RegisterMessageType(messageType);
- }
- }
-
private void compileWithRuntimeScanning(WolverineOptions options, ILogger logger)
{
foreach (var assembly in Discovery.Assemblies)
diff --git a/src/Wolverine/Runtime/IWolverineTypeLoader.cs b/src/Wolverine/Runtime/IWolverineTypeLoader.cs
deleted file mode 100644
index d684a89f7..000000000
--- a/src/Wolverine/Runtime/IWolverineTypeLoader.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using Wolverine.Runtime.Handlers;
-
-namespace Wolverine.Runtime;
-
-///
-/// Implemented by source generators to provide compile-time discovery
-/// of handlers, message types, and endpoints. When registered in DI,
-/// Wolverine will use this instead of runtime assembly scanning during
-/// startup, dramatically reducing cold start time.
-///
-/// If no IWolverineTypeLoader is registered, Wolverine falls back to
-/// its current runtime assembly scanning behavior with zero regression.
-///
-public interface IWolverineTypeLoader
-{
- ///
- /// Handler types discovered at compile time. These are classes matching
- /// Wolverine handler conventions: *Handler/*Consumer suffix, implementing
- /// IWolverineHandler, decorated with [WolverineHandler], or Saga types.
- ///
- IReadOnlyList DiscoveredHandlerTypes { get; }
-
- ///
- /// Message types discovered at compile time, with their serialization aliases.
- /// Includes types implementing IMessage, decorated with [WolverineMessage],
- /// and types used as parameters in handler methods.
- ///
- IReadOnlyList<(Type MessageType, string Alias)> DiscoveredMessageTypes { get; }
-
- ///
- /// HTTP endpoint types discovered at compile time. These are classes matching
- /// Wolverine.HTTP conventions: *Endpoint/*Endpoints suffix or containing
- /// methods with [WolverineGet], [WolverinePost], etc. attributes.
- ///
- IReadOnlyList DiscoveredHttpEndpointTypes { get; }
-
- ///
- /// Extension types discovered at compile time from assemblies marked with
- /// [WolverineModule]. Returns the IWolverineExtension implementation types
- /// in dependency order.
- ///
- IReadOnlyList DiscoveredExtensionTypes { get; }
-
- ///
- /// Whether this loader includes a pre-generated handler type dictionary
- /// that can replace the linear scan in AttachTypesSynchronously.
- ///
- bool HasPreGeneratedHandlers { get; }
-
- ///
- /// A dictionary mapping handler chain TypeName to the pre-generated Type,
- /// enabling O(1) lookup instead of O(N) assembly scanning in AttachTypesSynchronously.
- /// Returns null if no pre-generated handler types are available.
- ///
- IReadOnlyDictionary? PreGeneratedHandlerTypes { get; }
-
- ///
- /// Look up a pre-generated handler type by its generated class name.
- /// Returns null if the type name is not in the pre-generated manifest.
- /// This replaces the O(N) scan of assembly.ExportedTypes with an O(1) dictionary lookup.
- ///
- /// The generated handler type name (e.g., "PlaceOrderHandler")
- /// The pre-generated Type, or null if not found
- Type? TryFindPreGeneratedType(string typeName);
-}
diff --git a/src/Wolverine/Runtime/WolverineRuntime.HostService.cs b/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
index eb351296c..78ddfb407 100644
--- a/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
+++ b/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
@@ -84,21 +84,6 @@ public async Task StartAsync(CancellationToken cancellationToken)
}
}
- // Check for a source-generated type loader to bypass runtime assembly scanning
- var typeLoader = _container.Services.GetService(typeof(IWolverineTypeLoader)) as IWolverineTypeLoader;
- if (typeLoader == null)
- {
- // Also check for the assembly-level attribute as a discovery mechanism
- typeLoader = tryDiscoverTypeLoaderFromAttribute();
- }
-
- if (typeLoader != null)
- {
- Logger.LogInformation(
- "Source-generated IWolverineTypeLoader detected, using compile-time discovery to reduce startup time");
- Handlers.UseTypeLoader(typeLoader);
- }
-
// Build up the message handlers
Handlers.Compile(Options, _container);
@@ -511,52 +496,6 @@ private void discoverListenersFromConventions()
Options.LocalRouting.DiscoverListeners(this, handledMessageTypes);
}
- private IWolverineTypeLoader? tryDiscoverTypeLoaderFromAttribute()
- {
- // Walk the application assembly *and* every assembly the user added via
- // Discovery.IncludeAssembly. The Wolverine source generator emits a
- // [WolverineTypeManifest] attribute on every handler-bearing assembly; reading
- // only Options.ApplicationAssembly silently drops handlers that live in
- // referenced projects. See #2632.
- var seen = new HashSet();
- var loaders = new List();
-
- var candidates = new List { Options.ApplicationAssembly };
- candidates.AddRange(Options.Discovery.Assemblies);
-
- foreach (var assembly in candidates)
- {
- if (assembly is null || !seen.Add(assembly)) continue;
-
- try
- {
- var attribute = assembly
- .GetCustomAttributes(typeof(WolverineTypeManifestAttribute), false)
- .FirstOrDefault() as WolverineTypeManifestAttribute;
-
- if (attribute?.LoaderType is null) continue;
-
- if (Activator.CreateInstance(attribute.LoaderType) is IWolverineTypeLoader loader)
- {
- loaders.Add(loader);
- }
- }
- catch (Exception e)
- {
- Logger.LogWarning(e,
- "Failed to instantiate source-generated IWolverineTypeLoader from {Assembly}; the loaders from other assemblies will still be used",
- assembly.FullName);
- }
- }
-
- return loaders.Count switch
- {
- 0 => null,
- 1 => loaders[0],
- _ => new CompositeWolverineTypeLoader(loaders)
- };
- }
-
internal Task StartLightweightAsync()
{
if (_hasStarted)
diff --git a/src/Wolverine/Wolverine.csproj b/src/Wolverine/Wolverine.csproj
index 44ef5407f..dd07e3d51 100644
--- a/src/Wolverine/Wolverine.csproj
+++ b/src/Wolverine/Wolverine.csproj
@@ -31,23 +31,5 @@
-
-
-
-
-
-
-
-
-
diff --git a/wolverine.slnx b/wolverine.slnx
index 5431b09b0..3773df5e8 100644
--- a/wolverine.slnx
+++ b/wolverine.slnx
@@ -237,8 +237,6 @@
-
-
@@ -297,19 +295,6 @@
-
-
-
-