From aba8f75e211cfb7effcfcf69f45bc7753d9c2845 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Tue, 5 May 2026 13:20:13 -0500 Subject: [PATCH] chore(core): remove Wolverine-specific Roslyn source generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strips the experimental Wolverine.SourceGeneration analyzer project and its IWolverineTypeLoader / [WolverineTypeManifest] / CompositeWolverineTypeLoader surface. The compile-time handler-discovery branch was never exercised in steady state — handler graph compilation always falls back to compileWithRuntimeScanning, so the source-gen path was carrying weight without pulling any. Removes: - src/Wolverine.SourceGeneration/ (whole project) - IWolverineTypeLoader, CompositeWolverineTypeLoader, WolverineTypeManifestAttribute - The analyzer ProjectReference + Pack include in Wolverine.csproj - The source-gen branches in ExtensionLoader.ApplyExtensions, WolverineRuntime.HostService startup, HandlerGraph.Compile, and HandlerChain.AttachTypes - TypeLoaderManifestModuleA/B test fixtures and their aggregation tests JasperFx.SourceGeneration (separate package, used as analyzer reference) is unaffected. Bumps to 5.37.2. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 15 + Directory.Build.props | 2 +- .../composite_wolverine_typeloader_tests.cs | 97 --- .../typeloader_manifest_aggregation_tests.cs | 51 -- src/Testing/CoreTests/CoreTests.csproj | 2 - .../TypeLoaderManifestModuleA/ModuleA.cs | 38 - .../TypeLoaderManifestModuleA.csproj | 13 - .../TypeLoaderManifestModuleB/ModuleB.cs | 34 - .../TypeLoaderManifestModuleB.csproj | 13 - .../HandlerTypeInfo.cs | 28 - .../MessageTypeInfo.cs | 17 - .../Wolverine.SourceGeneration.csproj | 21 - .../WolverineTypeManifestGenerator.cs | 656 ------------------ .../WolverineTypeManifestAttribute.cs | 28 - src/Wolverine/ExtensionLoader.cs | 60 -- .../Runtime/CompositeWolverineTypeLoader.cs | 83 --- .../Runtime/Handlers/HandlerChain.cs | 19 +- .../Runtime/Handlers/HandlerGraph.cs | 66 +- src/Wolverine/Runtime/IWolverineTypeLoader.cs | 65 -- .../Runtime/WolverineRuntime.HostService.cs | 61 -- src/Wolverine/Wolverine.csproj | 18 - wolverine.slnx | 15 - 22 files changed, 18 insertions(+), 1384 deletions(-) delete mode 100644 src/Testing/CoreTests/Configuration/composite_wolverine_typeloader_tests.cs delete mode 100644 src/Testing/CoreTests/Configuration/typeloader_manifest_aggregation_tests.cs delete mode 100644 src/Testing/TypeLoaderManifestModuleA/ModuleA.cs delete mode 100644 src/Testing/TypeLoaderManifestModuleA/TypeLoaderManifestModuleA.csproj delete mode 100644 src/Testing/TypeLoaderManifestModuleB/ModuleB.cs delete mode 100644 src/Testing/TypeLoaderManifestModuleB/TypeLoaderManifestModuleB.csproj delete mode 100644 src/Wolverine.SourceGeneration/HandlerTypeInfo.cs delete mode 100644 src/Wolverine.SourceGeneration/MessageTypeInfo.cs delete mode 100644 src/Wolverine.SourceGeneration/Wolverine.SourceGeneration.csproj delete mode 100644 src/Wolverine.SourceGeneration/WolverineTypeManifestGenerator.cs delete mode 100644 src/Wolverine/Attributes/WolverineTypeManifestAttribute.cs delete mode 100644 src/Wolverine/Runtime/CompositeWolverineTypeLoader.cs delete mode 100644 src/Wolverine/Runtime/IWolverineTypeLoader.cs 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 @@ - - - -