From 07c7767b43cc2bdcd3aa4531eb0a43bfab003a9e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 11:40:02 -0500 Subject: [PATCH 01/67] refactor(source-generators): simplify tuple access and variable naming in MapHandlerIncrementalGenerator - Replaced nested tuple access with meaningful variable names for readability. - Streamlined length checks for variable groups. --- .../MapHandlerIncrementalGenerator.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs b/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs index 19dabb57..c03f1310 100644 --- a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs @@ -93,21 +93,26 @@ is CSharpCompilation .Select( CompilationInfo? (t, _) => { + var ( + (((mapHandlerInfos, onShutdownInfo), onInitInfo), builderInfo), + useMiddlewareInfo + ) = t; + if ( - t.Left.Left.Left.Left.Length == 0 - && t.Left.Left.Left.Right.Length == 0 - && t.Left.Left.Right.Length == 0 - && t.Left.Right.Length == 0 - && t.Right.Length == 0 + mapHandlerInfos.Length == 0 + && onShutdownInfo.Length == 0 + && onInitInfo.Length == 0 + && builderInfo.Length == 0 + && useMiddlewareInfo.Length == 0 ) return null; return new CompilationInfo( - t.Left.Left.Left.Left.ToEquatableArray(), - t.Left.Left.Left.Right.ToEquatableArray(), - t.Left.Left.Right.ToEquatableArray(), - t.Left.Right.ToEquatableArray(), - t.Right.ToEquatableArray() + mapHandlerInfos.ToEquatableArray(), + onShutdownInfo.ToEquatableArray(), + onInitInfo.ToEquatableArray(), + builderInfo.ToEquatableArray(), + useMiddlewareInfo.ToEquatableArray() ); } ); From 03534cc342a0f15b6acd079df56f5ca7921010a7 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 12:01:57 -0500 Subject: [PATCH 02/67] refactor(source-generators): rename internal classes and methods for consistency - Replaced `LambdaHostOutputGenerator` with `MinimalLambdaEmitter` for naming alignment. - Updated references to reflect the new class and method names across templates and tests. - Removed unused `OpenTelemetrySources.cs`. - Updated `MapHandlerIncrementalGenerator` to `MinimalLambdaGenerator`. --- .../CommonSources.cs | 2 +- .../GenericHandlerSources.cs | 2 +- .../MapHandlerSources.cs | 6 +-- .../MinimalLambdaEmitter.cs} | 2 +- .../{ => Emitters}/TemplateHelper.cs | 0 .../UseMiddlewareTSource.cs | 2 +- ...Generator.cs => MinimalLambdaGenerator.cs} | 4 +- .../OutputGenerators/OpenTelemetrySources.cs | 40 ------------------- .../GeneratorTestHelpers.cs | 2 +- 9 files changed, 8 insertions(+), 52 deletions(-) rename src/MinimalLambda.SourceGenerators/{OutputGenerators => Emitters}/CommonSources.cs (79%) rename src/MinimalLambda.SourceGenerators/{OutputGenerators => Emitters}/GenericHandlerSources.cs (99%) rename src/MinimalLambda.SourceGenerators/{OutputGenerators => Emitters}/MapHandlerSources.cs (96%) rename src/MinimalLambda.SourceGenerators/{OutputGenerators/LambdaHostOutputGenerator.cs => Emitters/MinimalLambdaEmitter.cs} (98%) rename src/MinimalLambda.SourceGenerators/{ => Emitters}/TemplateHelper.cs (100%) rename src/MinimalLambda.SourceGenerators/{OutputGenerators => Emitters}/UseMiddlewareTSource.cs (98%) rename src/MinimalLambda.SourceGenerators/{MapHandlerIncrementalGenerator.cs => MinimalLambdaGenerator.cs} (96%) delete mode 100644 src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs similarity index 79% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs rename to src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs index 571e4b0d..16e1b8d3 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs @@ -4,7 +4,7 @@ internal static class CommonSources { internal static string Generate() { - var model = new { LambdaHostOutputGenerator.GeneratedCodeAttribute }; + var model = new { MinimalLambdaEmitter.GeneratedCodeAttribute }; var template = TemplateHelper.LoadTemplate( GeneratorConstants.InterceptsLocationAttributeTemplateFile diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs similarity index 99% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs rename to src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index 738531d9..a31a748a 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -94,7 +94,7 @@ string targetType { Name = methodName, Calls = calls, - LambdaHostOutputGenerator.GeneratedCodeAttribute, + MinimalLambdaEmitter.GeneratedCodeAttribute, }; var template = TemplateHelper.LoadTemplate(GeneratorConstants.GenericHandlerTemplateFile); diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs similarity index 96% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs rename to src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 660895f3..f1a30c8b 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -66,11 +66,7 @@ EquatableArray builderInfo ); return template.Render( - new - { - LambdaHostOutputGenerator.GeneratedCodeAttribute, - MapHandlerCalls = mapHandlerCalls, - } + new { MinimalLambdaEmitter.GeneratedCodeAttribute, MapHandlerCalls = mapHandlerCalls } ); } diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs similarity index 98% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs rename to src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index dc182be3..7879260e 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -6,7 +6,7 @@ namespace MinimalLambda.SourceGenerators; -internal static class LambdaHostOutputGenerator +internal static class MinimalLambdaEmitter { internal static string GeneratedCodeAttribute { diff --git a/src/MinimalLambda.SourceGenerators/TemplateHelper.cs b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/TemplateHelper.cs rename to src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs similarity index 98% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs rename to src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs index b4f70c58..b182b296 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs @@ -84,7 +84,7 @@ n is AttributeConstants.FromServices or AttributeConstants.FromKeyedService var template = TemplateHelper.LoadTemplate(GeneratorConstants.UseMiddlewareTTemplateFile); return template.Render( - new { LambdaHostOutputGenerator.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } + new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } ); } diff --git a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs similarity index 96% rename from src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs rename to src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index c03f1310..bf3b1df5 100644 --- a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -6,7 +6,7 @@ namespace MinimalLambda.SourceGenerators; [Generator] -public class MapHandlerIncrementalGenerator : IIncrementalGenerator +public class MinimalLambdaGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -125,7 +125,7 @@ is CSharpCompilation if (info is null) return; - LambdaHostOutputGenerator.Generate(productionContext, info.Value); + MinimalLambdaEmitter.Generate(productionContext, info.Value); } ); } diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs deleted file mode 100644 index 958c9f45..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OpenTelemetrySources -{ - internal static string Generate( - EquatableArray useOpenTelemetryTracingInfos, - DelegateInfo delegateInfo, - string generatedCodeAttribute - ) - { - // get the handler input event type - var eventType = delegateInfo.EventParameter is { } p ? p.TypeInfo.FullyQualifiedType : null; - - // get the handler output return type - var responseType = delegateInfo.HasResponse - ? delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType - : null; - - // interceptable locations - var locations = useOpenTelemetryTracingInfos.Select(u => u.InterceptableLocationInfo); - - var model = new - { - Locations = locations, - EventType = eventType, - ResponseType = responseType, - GeneratedCodeAttribute = generatedCodeAttribute, - }; - - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.LambdaHostUseOpenTelemetryTracingExtensionsTemplateFile - ); - - return template.Render(model); - } -} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs index da67c24e..3a593f14 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs @@ -135,7 +135,7 @@ .. Net80.References.All.ToList(), compilationOptions ); - var generator = new MapHandlerIncrementalGenerator().AsSourceGenerator(); + var generator = new MinimalLambdaGenerator().AsSourceGenerator(); var driver = CSharpGeneratorDriver.Create(generator); var updatedDriver = driver.RunGenerators(compilation, CancellationToken.None); From f3b11bef935702a9740cd73a1db016e2f8d29dcd Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 12:09:31 -0500 Subject: [PATCH 03/67] feat(source-generators): introduce WellKnownTypes and supporting extensions - Added `WellKnownTypes` utility for resolving and caching common type metadata. - Implemented `BoundedCacheWithFactory` for efficient caching of compilation-related data. - Introduced several extension methods for simplifying syntax and symbol analysis. - Enhanced generator context handling with a new `GeneratorContext` class. - Added specialized enumerable and functional utilities to support incremental source generation. --- .../Extensions/EnumerableExtensions.cs | 38 ++++ .../Extensions/FunctionalExtensions.cs | 18 ++ .../IncrementalValueProviderExtensions.cs | 11 ++ .../Extensions/SymbolExtensions.cs | 48 +++++ .../Extensions/SyntaxExtensions.cs | 34 ++-- .../GeneratorContext.cs | 34 ++++ .../WellKnownTypes/BoundedCacheWithFactory.cs | 83 +++++++++ .../WellKnownTypes/WellKnownTypeData.cs | 78 ++++++++ .../WellKnownTypes/WellKnownTypes.cs | 166 ++++++++++++++++++ 9 files changed, 494 insertions(+), 16 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs create mode 100644 src/MinimalLambda.SourceGenerators/GeneratorContext.cs create mode 100644 src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs create mode 100644 src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs create mode 100644 src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..3cdaf2d4 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -0,0 +1,38 @@ +using System.Linq; + +namespace System.Collections.Generic; + +internal static class EnumerableExtensions +{ + extension(IEnumerable enumerable) + { + public void ForEach(Action action) + { + foreach (var item in enumerable) + action(item); + } + } + + extension(IEnumerable valueProviders) + where T : struct + { + public IEnumerable WhereNotNull() => + valueProviders.Where(static v => v is not null).Select(static v => v!.Value); + } + + extension(IEnumerable valueProviders) + where T : class + { + public IEnumerable WhereNotNull() => + valueProviders.Where(static v => v is not null).Select(static v => v!); + } + + extension(List list) + { + public List Add(T item) + { + list.Add(item); + return list; + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs new file mode 100644 index 00000000..182f1e3a --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs @@ -0,0 +1,18 @@ +namespace System; + +internal static class FunctionalExtensions +{ + extension(T source) + { + public TResult Map(Func func) => func(source); + } + + extension(T source) + { + public T Tap(Action action) + { + action(source); + return source; + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs new file mode 100644 index 00000000..38f3a417 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs @@ -0,0 +1,11 @@ +namespace Microsoft.CodeAnalysis; + +internal static class IncrementalValueProviderExtensions +{ + extension(IncrementalValuesProvider valueProviders) + where T : struct + { + public IncrementalValuesProvider WhereNotNull() => + valueProviders.Where(static v => v is not null).Select(static (v, _) => v!.Value); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs new file mode 100644 index 00000000..aa9a357e --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs @@ -0,0 +1,48 @@ +using Microsoft.CodeAnalysis.CSharp; +using MinimalLambda.SourceGenerators; +using MinimalLambda.SourceGenerators.WellKnownTypes; + +namespace Microsoft.CodeAnalysis; + +internal static class SymbolExtensions +{ + extension(INamedTypeSymbol sourceType) + { + internal bool IsAssignableTo(INamedTypeSymbol targetType, GeneratorContext context) + { + var conversion = context.SemanticModel.Compilation.ClassifyConversion( + sourceType, + targetType + ); + return conversion.IsImplicit; + } + + internal bool IsAssignableFrom(INamedTypeSymbol targetType, GeneratorContext context) + { + var conversion = context.SemanticModel.Compilation.ClassifyConversion( + targetType, + sourceType + ); + return conversion.IsImplicit; + } + + internal bool IsAssignableFromOrTo(INamedTypeSymbol otherType, GeneratorContext context) => + sourceType.IsAssignableFrom(otherType, context) + || sourceType.IsAssignableTo(otherType, context); + + internal bool IsAssignableTo( + WellKnownTypeData.WellKnownType targetType, + GeneratorContext context + ) => sourceType.IsAssignableTo(context.WellKnownTypes.Get(targetType), context); + + internal bool IsAssignableFrom( + WellKnownTypeData.WellKnownType targetType, + GeneratorContext context + ) => sourceType.IsAssignableFrom(context.WellKnownTypes.Get(targetType), context); + + internal bool IsAssignableFromOrTo( + WellKnownTypeData.WellKnownType otherType, + GeneratorContext context + ) => sourceType.IsAssignableFromOrTo(context.WellKnownTypes.Get(otherType), context); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs index 79ac3c52..430874eb 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs @@ -6,26 +6,28 @@ namespace MinimalLambda.SourceGenerators; internal static class SyntaxExtensions { - internal static bool TryGetMethodName( - this SyntaxNode node, - [NotNullWhen(true)] out string? methodName - ) + extension(SyntaxNode node) { - methodName = null; - if ( - node is InvocationExpressionSyntax + internal bool TryGetMethodName([NotNullWhen(true)] out string? methodName) + { + methodName = null; + if ( + node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: var method, + }, + } + ) { - Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: var method }, + methodName = method; + return true; } - ) - { - methodName = method; - return true; + + return false; } - return false; + internal bool IsGeneratedFile() => node.SyntaxTree.FilePath.EndsWith(".g.cs"); } - - internal static bool IsGeneratedFile(this SyntaxNode node) => - node.SyntaxTree.FilePath.EndsWith(".g.cs"); } diff --git a/src/MinimalLambda.SourceGenerators/GeneratorContext.cs b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs new file mode 100644 index 00000000..5931c801 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs @@ -0,0 +1,34 @@ +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators; + +internal class GeneratorContext +{ + internal WellKnownTypes.WellKnownTypes WellKnownTypes { get; } + internal CancellationToken CancellationToken { get; } + internal SemanticModel SemanticModel { get; } + internal GeneratorAttributeSyntaxContext GeneratorAttributeSyntaxContext { get; } + + internal GeneratorContext( + GeneratorAttributeSyntaxContext context, + CancellationToken cancellationToken + ) + { + GeneratorAttributeSyntaxContext = context; + SemanticModel = GeneratorAttributeSyntaxContext.SemanticModel; + CancellationToken = cancellationToken; + WellKnownTypes = SourceGenerators.WellKnownTypes.WellKnownTypes.GetOrCreate( + context.SemanticModel.Compilation + ); + } +} + +internal static class GeneratorContextExtensions +{ + extension(GeneratorContext context) + { + public void ThrowIfCancellationRequested() => + context.CancellationToken.ThrowIfCancellationRequested(); + } +} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs new file mode 100644 index 00000000..b41c0a0d --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs @@ -0,0 +1,83 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +using System; +using System.Collections.Generic; + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +// This type is copied from +// https://github.com/dotnet/roslyn-analyzers/blob/9b58ec3ad33353d1a523cda8c4be38eaefc80ad8/src/Utilities/Compiler/BoundedCacheWithFactory.cs + +/// +/// Provides bounded cache for analyzers. Acts as a good alternative to +/// when the +/// cached value has a cyclic reference to the key preventing early garbage collection of entries. +/// +internal class BoundedCacheWithFactory + where TKey : class +{ + // Bounded weak reference cache. + // Size 5 is an arbitrarily chosen bound, which can be tuned in future as required. + private readonly List> _weakReferencedEntries = new() + { + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + }; + + public TValue GetOrCreateValue(TKey key, Func valueFactory) + { + lock (_weakReferencedEntries) + { + var indexToSetTarget = -1; + for (var i = 0; i < _weakReferencedEntries.Count; i++) + { + var weakReferencedEntry = _weakReferencedEntries[i]; + if (!weakReferencedEntry.TryGetTarget(out var cachedEntry) || cachedEntry == null) + { + if (indexToSetTarget == -1) + indexToSetTarget = i; + + continue; + } + + if (Equals(cachedEntry.Key, key)) + { + // Move the cache hit item to the end of the list + // so it would be least likely to be evicted on next cache miss. + _weakReferencedEntries.RemoveAt(i); + _weakReferencedEntries.Add(weakReferencedEntry); + return cachedEntry.Value; + } + } + + if (indexToSetTarget == -1) + indexToSetTarget = 0; + + var newEntry = new Entry(key, valueFactory(key)); + _weakReferencedEntries[indexToSetTarget].SetTarget(newEntry); + return newEntry.Value; + } + } + + private sealed class Entry + { + public Entry(TKey key, TValue value) + { + Key = key; + Value = value; + } + + public TKey Key { get; } + + public TValue Value { get; } + } +} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs new file mode 100644 index 00000000..28d420c4 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -0,0 +1,78 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +// ReSharper disable InconsistentNaming + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +internal static class WellKnownTypeData +{ + public enum WellKnownType + { + Microsoft_Extensions_Primitives_StringValues, + System_Threading_CancellationToken, + System_Security_Claims_ClaimsPrincipal, + System_DateOnly, + System_DateTimeOffset, + System_IO_Stream, + System_IO_Pipelines_PipeReader, + System_IFormatProvider, + System_Uri, + System_String, + System_Guid, + System_TimeSpan, + Microsoft_Extensions_Hosting_GenericHostWebHostBuilderExtensions, + Microsoft_Extensions_Hosting_HostingHostBuilderExtensions, + System_Delegate, + System_Threading_Tasks_Task, + System_Threading_Tasks_Task_T, + System_Threading_Tasks_ValueTask, + System_Threading_Tasks_ValueTask_T, + System_Reflection_ParameterInfo, + System_IParsable_T, + Microsoft_Extensions_DependencyInjection_OutputCacheConventionBuilderExtensions, + Microsoft_Extensions_DependencyInjection_PolicyServiceCollectionExtensions, + Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute, + Microsoft_Extensions_DependencyInjection_IServiceCollection, + System_AttributeUsageAttribute, + Amazon_DynamoDBv2_Model_AttributeValue, + System_Collections_Generic_Dictionary_2, + } + + public static readonly string[] WellKnownTypeNames = + [ + "Microsoft.Extensions.Primitives.StringValues", + "System.Threading.CancellationToken", + "System.Security.Claims.ClaimsPrincipal", + "System.DateOnly", + "System.DateTimeOffset", + "System.IO.Stream", + "System.IO.Pipelines.PipeReader", + "System.IFormatProvider", + "System.Uri", + "System.String", + "System.Guid", + "System.TimeSpan", + "Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions", + "Microsoft.Extensions.Hosting.HostingHostBuilderExtensions", + "System.Delegate", + "System.Threading.Tasks.Task", + "System.Threading.Tasks.Task`1", + "System.Threading.Tasks.ValueTask", + "System.Threading.Tasks.ValueTask`1", + "System.Reflection.ParameterInfo", + "System.IParsable`1", + "Microsoft.Extensions.DependencyInjection.OutputCacheConventionBuilderExtensions", + "Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions", + "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute", + "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "System.AttributeUsageAttribute", + "Amazon.DynamoDBv2.Model.AttributeValue", + "System.Collections.Generic.Dictionary`2", + ]; +} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs new file mode 100644 index 00000000..91d66fb5 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs @@ -0,0 +1,166 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +internal class WellKnownTypes +{ + private static readonly BoundedCacheWithFactory< + Compilation, + WellKnownTypes + > LazyWellKnownTypesCache = new(); + + public static WellKnownTypes GetOrCreate(Compilation compilation) => + LazyWellKnownTypesCache.GetOrCreateValue(compilation, static c => new WellKnownTypes(c)); + + private readonly INamedTypeSymbol?[] _lazyWellKnownTypes; + private readonly Compilation _compilation; + + static WellKnownTypes() => AssertEnumAndTableInSync(); + + [Conditional("DEBUG")] + private static void AssertEnumAndTableInSync() + { + for (var i = 0; i < WellKnownTypeData.WellKnownTypeNames.Length; i++) + { + var name = WellKnownTypeData.WellKnownTypeNames[i]; + var typeId = (WellKnownTypeData.WellKnownType)i; + + var typeIdName = typeId.ToString().Replace("__", "+").Replace('_', '.'); + + var separator = name.IndexOf('`'); + if (separator >= 0) + { + // Ignore type parameter qualifier for generic types. + name = name.Substring(0, separator); + typeIdName = typeIdName.Substring(0, separator); + } + + Debug.Assert( + name == typeIdName, + $"Enum name ({typeIdName}) and type name ({name}) must match at {i}" + ); + } + } + + private WellKnownTypes(Compilation compilation) + { + _lazyWellKnownTypes = new INamedTypeSymbol?[WellKnownTypeData.WellKnownTypeNames.Length]; + _compilation = compilation; + } + + public INamedTypeSymbol Get(SpecialType type) => _compilation.GetSpecialType(type); + + public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) + { + var index = (int)type; + var symbol = _lazyWellKnownTypes[index]; + if (symbol is not null) + return symbol; + + // Symbol hasn't been added to the cache yet. + // Resolve symbol from name, cache, and return. + return GetAndCache(index); + } + + private INamedTypeSymbol GetAndCache(int index) + { + var result = GetTypeByMetadataNameInTargetAssembly( + WellKnownTypeData.WellKnownTypeNames[index] + ); + if (result == null) + throw new InvalidOperationException( + $"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'." + ); + + Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null); + + // GetTypeByMetadataName should always return the same instance for a name. + // To ensure we have a consistent value, for thread safety, return symbol set in the array. + return _lazyWellKnownTypes[index]!; + } + + // Filter for types within well-known (framework-owned) assemblies only. + private INamedTypeSymbol? GetTypeByMetadataNameInTargetAssembly(string metadataName) + { + var types = _compilation.GetTypesByMetadataName(metadataName); + if (types.Length == 0) + return null; + + if (types.Length == 1) + return types[0]; + + // Multiple types match the name. This is most likely caused by someone reusing the + // namespace + type name in their apps or libraries. + // Workaround this situation by prioritizing types in System and Microsoft assemblies. + foreach (var type in types) + if ( + type.ContainingAssembly.Identity.Name.StartsWith( + "System.", + StringComparison.Ordinal + ) + || type.ContainingAssembly.Identity.Name.StartsWith( + "Microsoft.", + StringComparison.Ordinal + ) + ) + return type; + + return null; + } + + public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes) => + IsType(type, wellKnownTypes, out _); + + public bool IsType( + ITypeSymbol type, + WellKnownTypeData.WellKnownType[] wellKnownTypes, + [NotNullWhen(true)] out WellKnownTypeData.WellKnownType? match + ) + { + foreach (var wellKnownType in wellKnownTypes) + if (SymbolEqualityComparer.Default.Equals(type, Get(wellKnownType))) + { + match = wellKnownType; + return true; + } + + match = null; + return false; + } + + public bool Implements( + ITypeSymbol type, + WellKnownTypeData.WellKnownType[] interfaceWellKnownTypes + ) + { + foreach (var wellKnownType in interfaceWellKnownTypes) + if (Implements(type, Get(wellKnownType))) + return true; + + return false; + } + + public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType) + { + if (type is null) + return false; + + foreach (var t in type.AllInterfaces) + if (SymbolEqualityComparer.Default.Equals(t, interfaceType)) + return true; + + return false; + } +} From 6000ae762d3ae7304483e63d613b96cbffe60832 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 12:14:37 -0500 Subject: [PATCH 04/67] feat(source-generators): expand WellKnownTypes with additional type metadata - Added multiple AWS Lambda and MinimalLambda-related types for enhanced source generation support. - Updated `WellKnownTypeData` to include both new types and their string representations. --- .../WellKnownTypes/WellKnownTypeData.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs index 28d420c4..e931eec7 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -40,8 +40,21 @@ public enum WellKnownType Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute, Microsoft_Extensions_DependencyInjection_IServiceCollection, System_AttributeUsageAttribute, - Amazon_DynamoDBv2_Model_AttributeValue, System_Collections_Generic_Dictionary_2, + Amazon_Lambda_Core_ILambdaContext, + System_Action, + System_Func, + System_IAsyncDisposable, + System_IDisposable, + System_IServiceProvider, + System_Void, + MinimalLambda_ILambdaInvocationContext, + MinimalLambda_ILambdaLifecycleContext, + MinimalLambda_Builder_EventAttribute, + MinimalLambda_Builder_FromArgumentsAttribute, + MinimalLambda_Builder_FromEventAttribute, + MinimalLambda_Builder_FromServicesAttribute, + MinimalLambda_Builder_MiddlewareConstructorAttribute, } public static readonly string[] WellKnownTypeNames = @@ -72,7 +85,20 @@ public enum WellKnownType "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute", "Microsoft.Extensions.DependencyInjection.IServiceCollection", "System.AttributeUsageAttribute", - "Amazon.DynamoDBv2.Model.AttributeValue", "System.Collections.Generic.Dictionary`2", + "Amazon.Lambda.Core.ILambdaContext", + "System.Action", + "System.Func", + "System.IAsyncDisposable", + "System.IDisposable", + "System.IServiceProvider", + "System.Void", + "MinimalLambda.ILambdaInvocationContext", + "MinimalLambda.ILambdaLifecycleContext", + "MinimalLambda.Builder.EventAttribute", + "MinimalLambda.Builder.FromArgumentsAttribute", + "MinimalLambda.Builder.FromEventAttribute", + "MinimalLambda.Builder.FromServicesAttribute", + "MinimalLambda.Builder.MiddlewareConstructorAttribute", ]; } From f88da6b1b08478b37871b97e0f7661716808d84a Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 12:47:14 -0500 Subject: [PATCH 05/67] feat(source-generators): introduce `HandlerSyntaxProvider` for unified handler analysis - Added `HandlerSyntaxProvider` to consolidate and streamline syntax logic for handler methods. - Refactored `MinimalLambdaGenerator` to use the new `HandlerSyntaxProvider` for handler registration. - Updated `CompilationInfo` to remove `BuilderInfos` and include handler-related simplifications. - Deprecated redundant provider calls like `MapHandlerSyntaxProvider` and `OnInitSyntaxProvider`. --- .../MinimalLambdaGenerator.cs | 123 +++++++++--------- .../Models/CompilationInfo.cs | 1 - .../SyntaxProviders/HandlerSyntaxProvider.cs | 22 ++++ 3 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index bf3b1df5..103d031f 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using MinimalLambda.SourceGenerators.Models; @@ -31,41 +32,49 @@ is CSharpCompilation } ); - // Find all MapHandler method calls with lambda analysis - var mapHandlerCalls = context - .SyntaxProvider.CreateSyntaxProvider( - MapHandlerSyntaxProvider.Predicate, - MapHandlerSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); + // // Find all MapHandler method calls with lambda analysis + // var mapHandlerCalls = context + // .SyntaxProvider.CreateSyntaxProvider( + // MapHandlerSyntaxProvider.Predicate, + // MapHandlerSyntaxProvider.Transformer + // ) + // .Where(static m => m is not null) + // .Select(static (m, _) => m!.Value); + // + // // Find all OnShutdown method calls with lambda analysis + // var onShutdownCalls = context + // .SyntaxProvider.CreateSyntaxProvider( + // OnShutdownSyntaxProvider.Predicate, + // OnShutdownSyntaxProvider.Transformer + // ) + // .Where(static m => m is not null) + // .Select(static (m, _) => m!.Value); + // + // // Find all OnInit method calls with lambda analysis + // var onInitCalls = context + // .SyntaxProvider.CreateSyntaxProvider( + // OnInitSyntaxProvider.Predicate, + // OnInitSyntaxProvider.Transformer + // ) + // .Where(static m => m is not null) + // .Select(static (m, _) => m!.Value); + // + // // find LambdaApplicationBuilder.Build() calls + // var lambdaApplicationBuilderBuildCalls = context + // .SyntaxProvider.CreateSyntaxProvider( + // LambdaApplicationBuilderBuildSyntaxProvider.Predicate, + // LambdaApplicationBuilderBuildSyntaxProvider.Transformer + // ) + // .Where(static m => m is not null) + // .Select(static (m, _) => m!.Value); - // Find all OnShutdown method calls with lambda analysis - var onShutdownCalls = context + // handler registration calls + var registrationCalls = context .SyntaxProvider.CreateSyntaxProvider( - OnShutdownSyntaxProvider.Predicate, - OnShutdownSyntaxProvider.Transformer + HandlerSyntaxProvider.Predicate, + HandlerSyntaxProvider.Transformer ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // Find all OnInit method calls with lambda analysis - var onInitCalls = context - .SyntaxProvider.CreateSyntaxProvider( - OnInitSyntaxProvider.Predicate, - OnInitSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // find LambdaApplicationBuilder.Build() calls - var lambdaApplicationBuilderBuildCalls = context - .SyntaxProvider.CreateSyntaxProvider( - LambdaApplicationBuilderBuildSyntaxProvider.Predicate, - LambdaApplicationBuilderBuildSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); + .WhereNotNull(); // find UseMiddleware() calls var useMiddlewareTCalls = context @@ -77,43 +86,39 @@ is CSharpCompilation .Select(static (m, _) => m!.Value); // collect call - var mapHandlerCallsCollected = mapHandlerCalls.Collect(); - var onShutdownCallsCollected = onShutdownCalls.Collect(); - var onInitCallsCollected = onInitCalls.Collect(); - var lambdaApplicationBuilderBuildCallsCollected = - lambdaApplicationBuilderBuildCalls.Collect(); + // var mapHandlerCallsCollected = mapHandlerCalls.Collect(); + // var onShutdownCallsCollected = onShutdownCalls.Collect(); + // var onInitCallsCollected = onInitCalls.Collect(); + // var lambdaApplicationBuilderBuildCallsCollected = + // lambdaApplicationBuilderBuildCalls.Collect(); + + var registrationCallsCollected = registrationCalls.Collect(); var useMiddlewareTCallsCollected = useMiddlewareTCalls.Collect(); // combine the compilation and map handler calls - var combined = mapHandlerCallsCollected - .Combine(onShutdownCallsCollected) - .Combine(onInitCallsCollected) - .Combine(lambdaApplicationBuilderBuildCallsCollected) + var combined = registrationCallsCollected .Combine(useMiddlewareTCallsCollected) .Select( CompilationInfo? (t, _) => { - var ( - (((mapHandlerInfos, onShutdownInfo), onInitInfo), builderInfo), - useMiddlewareInfo - ) = t; + var (handlerInfos, useMiddlewareInfo) = t; - if ( - mapHandlerInfos.Length == 0 - && onShutdownInfo.Length == 0 - && onInitInfo.Length == 0 - && builderInfo.Length == 0 - && useMiddlewareInfo.Length == 0 - ) + if (handlerInfos.Length == 0 && useMiddlewareInfo.Length == 0) return null; - return new CompilationInfo( - mapHandlerInfos.ToEquatableArray(), - onShutdownInfo.ToEquatableArray(), - onInitInfo.ToEquatableArray(), - builderInfo.ToEquatableArray(), - useMiddlewareInfo.ToEquatableArray() - ); + return new CompilationInfo + { + MapHandlerInvocationInfos = handlerInfos + .Where(h => h.Name == "MapHandler") + .ToEquatableArray(), + OnShutdownInvocationInfos = handlerInfos + .Where(h => h.Name == "OnShutdown") + .ToEquatableArray(), + OnInitInvocationInfos = handlerInfos + .Where(h => h.Name == "OnInit") + .ToEquatableArray(), + UseMiddlewareTInfos = useMiddlewareInfo.ToEquatableArray(), + }; } ); diff --git a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs index 4f45970f..32a38049 100644 --- a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs @@ -6,6 +6,5 @@ internal readonly record struct CompilationInfo( EquatableArray MapHandlerInvocationInfos, EquatableArray OnShutdownInvocationInfos, EquatableArray OnInitInvocationInfos, - EquatableArray BuilderInfos, EquatableArray UseMiddlewareTInfos ); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs new file mode 100644 index 00000000..4db2c50d --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Models; + +namespace MinimalLambda.SourceGenerators; + +internal static class HandlerSyntaxProvider +{ + private static readonly string[] TargetMethodNames = ["MapHandler", "OnInit", "OnShutdown"]; + + internal static bool Predicate(SyntaxNode node, CancellationToken _) => + !node.IsGeneratedFile() + && node.TryGetMethodName(out var name) + && TargetMethodNames.Contains(name); + + internal static HigherOrderMethodInfo? Transformer( + GeneratorSyntaxContext context, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); +} From de0a922d1ffa984ff455df555048ebc0ea8b0190 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 13:47:23 -0500 Subject: [PATCH 06/67] refactor(source-generators): streamline `GeneratorContext` and handler generation logic - Updated `GeneratorContext` to include `Node` for enhanced contextual data access. - Replaced `GeneratorAttributeSyntaxContext` with a more generalized `GeneratorSyntaxContext`. - Simplified `MapHandlerSources.Generate` method by removing unused `builderInfo` parameter. - Removed redundant test for main `Test_ExpressionLambda` overload in unit tests. - Added detailed logic for handler transformation in `HandlerSyntaxProvider` to support route handlers. - Included helper methods for resolving invocation operations and route handler arguments. --- .../Emitters/MapHandlerSources.cs | 5 +- .../Emitters/MinimalLambdaEmitter.cs | 7 +- .../GeneratorContext.cs | 10 +- .../SyntaxProviders/HandlerSyntaxProvider.cs | 143 +++++++++++++++++- .../ExpressionLambdaVerifyTests.cs | 21 --- 5 files changed, 147 insertions(+), 39 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index f1a30c8b..3b118253 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -7,10 +7,7 @@ namespace MinimalLambda.SourceGenerators; internal static class MapHandlerSources { - internal static string Generate( - EquatableArray mapHandlerInvocationInfos, - EquatableArray builderInfo - ) + internal static string Generate(EquatableArray mapHandlerInvocationInfos) { var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandlerInvocationInfo => { diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index 7879260e..327e865e 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -58,12 +58,7 @@ namespace MinimalLambda.Generated // if MapHandler calls found, generate the source code. if (compilationInfo.MapHandlerInvocationInfos.Count >= 1) - outputs.Add( - MapHandlerSources.Generate( - compilationInfo.MapHandlerInvocationInfos, - compilationInfo.BuilderInfos - ) - ); + outputs.Add(MapHandlerSources.Generate(compilationInfo.MapHandlerInvocationInfos)); // add UseMiddleware interceptors if (compilationInfo.UseMiddlewareTInfos.Count >= 1) diff --git a/src/MinimalLambda.SourceGenerators/GeneratorContext.cs b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs index 5931c801..c5556178 100644 --- a/src/MinimalLambda.SourceGenerators/GeneratorContext.cs +++ b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs @@ -8,14 +8,14 @@ internal class GeneratorContext internal WellKnownTypes.WellKnownTypes WellKnownTypes { get; } internal CancellationToken CancellationToken { get; } internal SemanticModel SemanticModel { get; } - internal GeneratorAttributeSyntaxContext GeneratorAttributeSyntaxContext { get; } + internal GeneratorSyntaxContext GeneratorAttributeSyntaxContext { get; } - internal GeneratorContext( - GeneratorAttributeSyntaxContext context, - CancellationToken cancellationToken - ) + internal SyntaxNode Node { get; } + + internal GeneratorContext(GeneratorSyntaxContext context, CancellationToken cancellationToken) { GeneratorAttributeSyntaxContext = context; + Node = context.Node; SemanticModel = GeneratorAttributeSyntaxContext.SemanticModel; CancellationToken = cancellationToken; WellKnownTypes = SourceGenerators.WellKnownTypes.WellKnownTypes.GetOrCreate( diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index 4db2c50d..b5e1fb59 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -1,8 +1,21 @@ -using System; +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/InvocationOperationExtensions.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +// ReSharper disable InconsistentNaming + +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; using MinimalLambda.SourceGenerators.Models; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators; @@ -16,7 +29,131 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _) => && TargetMethodNames.Contains(name); internal static HigherOrderMethodInfo? Transformer( - GeneratorSyntaxContext context, + GeneratorSyntaxContext syntaxContext, CancellationToken cancellationToken - ) => throw new NotImplementedException(); + ) + { + var context = new GeneratorContext(syntaxContext, cancellationToken); + + if (!TryGetInvocationOperation(context, out var targetOperation)) + return null; + + if (!targetOperation.TryGetRouteHandlerMethod(context.SemanticModel, out var methodSymbol)) + return null; + + return null; + } + + private static bool TryGetInvocationOperation( + GeneratorContext context, + [NotNullWhen(true)] out IInvocationOperation? invocationOperation + ) + { + invocationOperation = null; + + var operation = context.SemanticModel.GetOperation(context.Node, context.CancellationToken); + + if ( + operation + is IInvocationOperation + { + TargetMethod.ContainingNamespace: + { + Name: "Builder", + ContainingNamespace: + { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, + }, + } targetOperation + && targetOperation.TargetMethod.ContainingAssembly.Name == "MinimalLambda" + && targetOperation.TryGetRouteHandlerArgument(out var routeHandlerParameter) + && routeHandlerParameter is { Parameter.Type: { } delegateType } + && SymbolEqualityComparer.Default.Equals( + delegateType, + context.WellKnownTypes.Get(WellKnownType.System_Delegate) + ) + ) + { + invocationOperation = targetOperation; + return true; + } + + return false; + } + + private static bool TryGetRouteHandlerMethod( + this IInvocationOperation invocation, + SemanticModel semanticModel, + [NotNullWhen(true)] out IMethodSymbol? method + ) + { + method = null; + if (invocation.TryGetRouteHandlerArgument(out var argument)) + { + method = ResolveMethodFromOperation(argument, semanticModel); + return method is not null; + } + + return false; + } + + private static IMethodSymbol? ResolveMethodFromOperation( + IOperation operation, + SemanticModel semanticModel + ) => + operation switch + { + IArgumentOperation argument => ResolveMethodFromOperation( + argument.Value, + semanticModel + ), + IConversionOperation conv => ResolveMethodFromOperation(conv.Operand, semanticModel), + IDelegateCreationOperation del => ResolveMethodFromOperation(del.Target, semanticModel), + IFieldReferenceOperation { Field.IsReadOnly: true } f + when ResolveDeclarationOperation(f.Field, semanticModel) is { } op => + ResolveMethodFromOperation(op, semanticModel), + IAnonymousFunctionOperation anon => anon.Symbol, + ILocalFunctionOperation local => local.Symbol, + IMethodReferenceOperation method => method.Method, + IParenthesizedOperation parenthesized => ResolveMethodFromOperation( + parenthesized.Operand, + semanticModel + ), + _ => null, + }; + + private static bool TryGetRouteHandlerArgument( + this IInvocationOperation invocation, + [NotNullWhen(true)] out IArgumentOperation? argumentOperation + ) + { + argumentOperation = null; + var routeHandlerArgumentOrdinal = invocation.Arguments.Length - 1; + + foreach (var argument in invocation.Arguments) + if (argument.Parameter?.Ordinal == routeHandlerArgumentOrdinal) + { + argumentOperation = argument; + return true; + } + + return false; + } + + private static IOperation? ResolveDeclarationOperation( + ISymbol symbol, + SemanticModel? semanticModel + ) => + symbol + .DeclaringSyntaxReferences.Select(syntaxReference => syntaxReference.GetSyntax()) + .OfType() + .Where(syn => syn.Initializer?.Value is not null) + .Select(syn => + { + var expr = syn.Initializer!.Value; + var targetSemanticModel = semanticModel?.Compilation.GetSemanticModel( + expr.SyntaxTree + ); + return targetSemanticModel?.GetOperation(expr); + }) + .FirstOrDefault(operation => operation is not null); } diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs index 18a94741..c7b53235 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs @@ -21,27 +21,6 @@ await GeneratorTestHelpers.Verify( """ ); - [Fact] - public async Task Test_ExpressionLambda_MainOverload_DeserializerSerializer_NoOp() => - await GeneratorTestHelpers.Verify( - """ - using System.IO; - using System.Threading.Tasks; - using Amazon.Lambda.Core; - using MinimalLambda; - using MinimalLambda.Builder; - using Microsoft.Extensions.Hosting; - - var builder = LambdaApplication.CreateBuilder(); - - var lambda = builder.Build(); - - lambda.Handle(Task (ILambdaInvocationContext context) => Task.CompletedTask); - - await lambda.RunAsync(); - """ - ); - [Fact] public async Task Test_ExpressionLambda_NoInput_NoOutput() => await GeneratorTestHelpers.Verify( From 0dd9df21e199dfb65623c2a586b1312d87cf2526 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 13:58:13 -0500 Subject: [PATCH 07/67] feat(source-generators): add extensibility to `HigherOrderMethodInfo` and enhance handler logic - Introduced `HigherOrderMethodInfoExtensions` with a `Create` factory method for flexible instantiation. - Updated `HandlerSyntaxProvider` to use `HigherOrderMethodInfo.Create` for handler information generation. - Renamed `TryGetRouteHandlerMethod` to `TryGetHandlerMethod` for clarity and contextual alignment. - Removed redundant code and comments for improved maintainability. --- .../Models/HigherOrderMethodInfo.cs | 13 +++++++++++++ .../SyntaxProviders/HandlerSyntaxProvider.cs | 15 ++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 5a34ffa9..5ed0cde6 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using Microsoft.CodeAnalysis; namespace MinimalLambda.SourceGenerators.Models; @@ -9,3 +10,15 @@ internal readonly record struct HigherOrderMethodInfo( InterceptableLocationInfo InterceptableLocationInfo, ImmutableArray ArgumentsInfos ); + +internal static class HigherOrderMethodInfoExtensions +{ + extension(HigherOrderMethodInfo) + { + internal static HigherOrderMethodInfo? Create( + IMethodSymbol methodSymbol, + string name, + GeneratorContext context + ) => null; + } +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index b5e1fb59..bd87a3da 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -6,8 +6,6 @@ // See THIRD-PARTY-LICENSES.txt file in the project root or visit // https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt -// ReSharper disable InconsistentNaming - using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -38,10 +36,13 @@ CancellationToken cancellationToken if (!TryGetInvocationOperation(context, out var targetOperation)) return null; - if (!targetOperation.TryGetRouteHandlerMethod(context.SemanticModel, out var methodSymbol)) - return null; - - return null; + return !targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol) + ? null + : HigherOrderMethodInfo.Create( + methodSymbol, + targetOperation.TargetMethod.Name, + context + ); } private static bool TryGetInvocationOperation( @@ -80,7 +81,7 @@ is IInvocationOperation return false; } - private static bool TryGetRouteHandlerMethod( + private static bool TryGetHandlerMethod( this IInvocationOperation invocation, SemanticModel semanticModel, [NotNullWhen(true)] out IMethodSymbol? method From 883c494676766769c691ea1e5dbf6b865095d270 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 15:59:27 -0500 Subject: [PATCH 08/67] refactor(source-generators): rename `GetAsGlobal` to `ToGloballyQualifiedName` - Renamed `GetAsGlobal` method to `ToGloballyQualifiedName` for improved clarity and expressiveness. - Updated all references to `GetAsGlobal` across source and test files. - Introduced `GetMethodSignature` utility for extracting method signatures with type details. - Extended unit tests to cover scenarios involving the updated method and handler casting logic. --- .../Extensions/TypeExtractorExtensions.cs | 5 ++- .../Models/ClassInfo.cs | 4 +-- .../Models/HigherOrderMethodInfo.cs | 31 +++++++++++++++++-- .../Models/KeyedServiceKeyInfo.cs | 8 ++--- .../Models/TypeInfo.cs | 8 ++--- .../Extractors/HandlerInfoExtractor.cs | 6 ++-- .../SyntaxProviders/HandlerSyntaxProvider.cs | 10 ++---- .../ExpressionLambdaVerifyTests.cs | 22 +++++++++++++ 8 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs index 36a218e6..73bd4f41 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs @@ -10,7 +10,10 @@ internal static class TypeExtractorExtensions SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier ); - internal static string GetAsGlobal(this ITypeSymbol typeSymbol, TypeSyntax? typeSyntax = null) + internal static string ToGloballyQualifiedName( + this ITypeSymbol typeSymbol, + TypeSyntax? typeSyntax = null + ) { var baseTypeName = typeSymbol.ToDisplayString(Format); diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs index d2181d41..2c72be63 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs @@ -23,7 +23,7 @@ internal static ClassInfo Create(ITypeSymbol typeSymbol) var typeKind = typeSymbol.GetTypeKind(); // get the globally qualified name of the class - var globallyQualifiedName = typeSymbol.GetAsGlobal(); + var globallyQualifiedName = typeSymbol.ToGloballyQualifiedName(); // get short name var shortName = typeSymbol.Name; @@ -35,7 +35,7 @@ internal static ClassInfo Create(ITypeSymbol typeSymbol) // get all interfaces var interfaceNames = typeSymbol - .AllInterfaces.Select(i => i.GetAsGlobal()) + .AllInterfaces.Select(i => i.ToGloballyQualifiedName()) .ToEquatableArray(); return new ClassInfo( diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 5ed0cde6..9db29ee0 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; namespace MinimalLambda.SourceGenerators.Models; @@ -8,7 +10,9 @@ internal readonly record struct HigherOrderMethodInfo( DelegateInfo DelegateInfo, LocationInfo? LocationInfo, InterceptableLocationInfo InterceptableLocationInfo, - ImmutableArray ArgumentsInfos + ImmutableArray ArgumentsInfos, + // ── New ────────────────────────────────────────────────────────────────────────── + string DelegateCastType = "" ); internal static class HigherOrderMethodInfoExtensions @@ -19,6 +23,29 @@ internal static class HigherOrderMethodInfoExtensions IMethodSymbol methodSymbol, string name, GeneratorContext context - ) => null; + ) + { + var handlerCastType = GetMethodSignature(methodSymbol); + + return null; + } + } + + private static string GetMethodSignature(IMethodSymbol method) + { + var returnType = method.ReturnType.ToGloballyQualifiedName(); + var parameters = method + .Parameters.Select( + (p, i) => + { + var type = p.Type.ToGloballyQualifiedName(); + var defaultValue = p.IsOptional ? " = default" : ""; + return $"{type} arg{i}{defaultValue}"; + } + ) + .ToArray(); + var parameterList = string.Join(", ", parameters); + + return $"{returnType} ({parameterList}) => throw null!"; } } diff --git a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs index ccc5085d..d1f00b3e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs @@ -45,8 +45,8 @@ private static (string? Key, string? KeyType, string? KeyBaseType) ExtractKeyedS TypedConstant argument ) { - var keyBaseType = argument.Type?.BaseType?.GetAsGlobal(); - var keyType = argument.Type?.GetAsGlobal(); + var keyBaseType = argument.Type?.BaseType?.ToGloballyQualifiedName(); + var keyType = argument.Type?.ToGloballyQualifiedName(); if (argument.IsNull) return ("null", keyType, keyBaseType); @@ -77,10 +77,10 @@ TypedConstant argument : "false", TypedConstantKind.Primitive or TypedConstantKind.Enum => - $"({argument.Type?.GetAsGlobal()}){value}", + $"({argument.Type?.ToGloballyQualifiedName()}){value}", TypedConstantKind.Type when value is ITypeSymbol typeValue => - $"typeof({typeValue.GetAsGlobal()})", + $"typeof({typeValue.ToGloballyQualifiedName()})", _ => value.ToString(), }; diff --git a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs b/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs index 59d43528..9bcd61fb 100644 --- a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs @@ -20,11 +20,11 @@ internal static class TypeInfoExtensions { internal static TypeInfo Create(ITypeSymbol typeSymbol, TypeSyntax? syntax = null) { - var fullyQualifiedType = typeSymbol.GetAsGlobal(syntax); + var fullyQualifiedType = typeSymbol.ToGloballyQualifiedName(syntax); var unwrappedFullyQualifiedType = typeSymbol.GetUnwrappedFullyQualifiedType(syntax); var isGeneric = typeSymbol is INamedTypeSymbol { IsGenericType: true }; var implementedInterfaces = typeSymbol - .AllInterfaces.Select(i => i.GetAsGlobal()) + .AllInterfaces.Select(i => i.ToGloballyQualifiedName()) .ToImmutableArray(); return new TypeInfo( @@ -48,13 +48,13 @@ internal static TypeInfo CreateFullyQualifiedType(string fullyQualifiedType) => typeSymbol is not INamedTypeSymbol namedTypeSymbol || (!namedTypeSymbol.IsTask() && !namedTypeSymbol.IsValueTask()) ) - return typeSymbol.GetAsGlobal(syntax); + return typeSymbol.ToGloballyQualifiedName(syntax); // if not generic Task or ValueTask, return null as no wrapped return value if (!namedTypeSymbol.IsGenericType || namedTypeSymbol.TypeArguments.Length == 0) return null; - return namedTypeSymbol.TypeArguments.First().GetAsGlobal(syntax); + return namedTypeSymbol.TypeArguments.First().ToGloballyQualifiedName(syntax); } } } diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs index 58eba2f2..3978e5bc 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs @@ -62,7 +62,7 @@ is not IInvocationOperation var argumentInfos = targetOperation .Arguments.Select(argument => { - var typeAsGlobal = argument.Value.Type?.GetAsGlobal(); + var typeAsGlobal = argument.Value.Type?.ToGloballyQualifiedName(); var parameterName = argument.Parameter?.Name; return new ArgumentInfo(typeAsGlobal, parameterName); @@ -215,7 +215,7 @@ originalParam with .ToEquatableArray(); // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = invokeMethod.ReturnType.GetAsGlobal(); + var fullResponseType = invokeMethod.ReturnType.ToGloballyQualifiedName(); // determine if the delegate is returning awaitable value var isAwaitable = @@ -257,7 +257,7 @@ CancellationToken cancellationToken .ToEquatableArray(); // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = methodSymbol.ReturnType.GetAsGlobal(); + var fullResponseType = methodSymbol.ReturnType.ToGloballyQualifiedName(); // determine if the delegate is returning awaitable value var isAwaitable = diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index bd87a3da..96c6e7f8 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -36,13 +36,9 @@ CancellationToken cancellationToken if (!TryGetInvocationOperation(context, out var targetOperation)) return null; - return !targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol) - ? null - : HigherOrderMethodInfo.Create( - methodSymbol, - targetOperation.TargetMethod.Name, - context - ); + return targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol) + ? HigherOrderMethodInfo.Create(methodSymbol, targetOperation.TargetMethod.Name, context) + : null; } private static bool TryGetInvocationOperation( diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs index c7b53235..4e5416f7 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs @@ -435,4 +435,26 @@ await GeneratorTestHelpers.Verify( await lambda.RunAsync(); """ ); + + [Fact] + public async Task Test_ExpressionLambda_OptionalInjectedParam() => + await GeneratorTestHelpers.Verify( + """ + using MinimalLambda; + using MinimalLambda.Builder; + using Microsoft.Extensions.Hosting; + + var builder = LambdaApplication.CreateBuilder(); + var lambda = builder.Build(); + + lambda.MapHandler(string? (IService? service = default) => service.GetMessage()); + + await lambda.RunAsync(); + + public interface IService + { + string? GetMessage(); + } + """ + ); } From 911c560401340fa9d9e9a8e19b16ab21daed00e3 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 22:16:02 -0500 Subject: [PATCH 09/67] feat(source-generators): add support for parameter assignment extraction and diagnostics - Introduced `MapHandlerExtractors` to handle parameter assignment extraction logic with diagnostics. - Added `DiagnosticInfo` and `Result` to encapsulate diagnostic data and improve error handling. - Updated `HigherOrderMethodInfo.Create` to integrate parameter assignment and diagnostics. - Enhanced `HandlerSyntaxProvider` to utilize parameter assignment extraction during handler creation. - Adjusted `InterceptableLocationInfo` with extensions to support diagnostic-related transformations. - Fixed a typo in `MapHandlerSources.cs`, correcting `LocatioOpn` to `Location`. --- .../Emitters/MapHandlerSources.cs | 2 +- .../Models/DiagnosticInfo.cs | 41 ++++ .../Models/HigherOrderMethodInfo.cs | 32 ++- .../Models/InterceptableLocationInfo.cs | 50 +++- .../Models/MapHandlerExtractors.cs | 219 ++++++++++++++++++ .../Models/Result.cs | 52 +++++ .../SyntaxProviders/HandlerSyntaxProvider.cs | 12 +- 7 files changed, 394 insertions(+), 14 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs create mode 100644 src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs create mode 100644 src/MinimalLambda.SourceGenerators/Models/Result.cs diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 3b118253..eb058da5 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -46,7 +46,7 @@ internal static string Generate(EquatableArray mapHandler return new { - Location = mapHandlerInvocationInfo.InterceptableLocationInfo, + LocatioOpn = mapHandlerInvocationInfo.InterceptableLocationInfo, HandlerSignature = handlerSignature, IsEventFeatureRequired = isEventFeatureRequired, IsResponseFeatureRequired = isResponseFeatureRequired, diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs new file mode 100644 index 00000000..d6639230 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs @@ -0,0 +1,41 @@ +using System; +using LayeredCraft.SourceGeneratorTools.Utilities; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.Models; + +internal readonly struct DiagnosticInfo( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo = null, + params object?[] messageArgs +) : IEquatable +{ + public DiagnosticDescriptor DiagnosticDescriptor { get; } = diagnosticDescriptor; + public LocationInfo? LocationInfo { get; } = locationInfo; + public object?[] MessageArgs { get; } = messageArgs; + + public bool Equals(DiagnosticInfo other) => + DiagnosticDescriptor.Id == other.DiagnosticDescriptor.Id + && LocationInfo == other.LocationInfo; + + public override bool Equals(object? obj) => obj is DiagnosticInfo other && Equals(other); + + public override int GetHashCode() => + HashCode.Combine(DiagnosticDescriptor.Id.GetHashCode(), LocationInfo.GetHashCode()); +} + +internal static class DiagnosticInfoExtensions +{ + extension(DiagnosticInfo diagnosticInfo) + { + internal Diagnostic ToDiagnostic() => + Diagnostic.Create( + diagnosticInfo.DiagnosticDescriptor, + diagnosticInfo.LocationInfo?.ToLocation(), + diagnosticInfo.MessageArgs + ); + + internal void ReportDiagnostic(SourceProductionContext context) => + context.ReportDiagnostic(diagnosticInfo.ToDiagnostic()); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 9db29ee0..7739abf2 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; @@ -12,7 +15,9 @@ internal readonly record struct HigherOrderMethodInfo( InterceptableLocationInfo InterceptableLocationInfo, ImmutableArray ArgumentsInfos, // ── New ────────────────────────────────────────────────────────────────────────── - string DelegateCastType = "" + string DelegateCastType = "", + EquatableArray ParameterAssignments = default, + EquatableArray DiagnosticInfos = default ); internal static class HigherOrderMethodInfoExtensions @@ -22,10 +27,35 @@ internal static class HigherOrderMethodInfoExtensions internal static HigherOrderMethodInfo? Create( IMethodSymbol methodSymbol, string name, + Func< + IMethodSymbol, + GeneratorContext, + IEnumerable<(string?, DiagnosticInfo?)> + > getParameterAssignments, GeneratorContext context ) { var handlerCastType = GetMethodSignature(methodSymbol); + var location = context.Node.CreateLocationInfo(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = getParameterAssignments(methodSymbol, context) + .Aggregate( + (Successes: new List(), Diagnostics: new List()), + static (acc, result) => + { + if (result.Item1 is not null) + acc.Successes.Add(result.Item1); + + if (result.Item2 is not null) + acc.Diagnostics.Add(result.Item2.Value); + + return acc; + }, + static acc => (acc.Successes.ToEquatableArray(), acc.Diagnostics.ToArray()) + ); return null; } diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs index 0ecd39eb..0339b727 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MinimalLambda.SourceGenerators.Models; @@ -6,14 +8,44 @@ internal readonly record struct InterceptableLocationInfo( int Version, string Data, string DisplayLocation -) +); + +internal static class InterceptableLocationInfoExtensions { - internal static InterceptableLocationInfo CreateFrom( - InterceptableLocation interceptableLocation - ) => - new( - interceptableLocation.Version, - interceptableLocation.Data, - interceptableLocation.GetDisplayLocation() - ); + extension(InterceptableLocationInfo location) + { + internal static InterceptableLocationInfo CreateFrom( + InterceptableLocation interceptableLocation + ) => + new( + interceptableLocation.Version, + interceptableLocation.Data, + interceptableLocation.GetDisplayLocation() + ); + + internal static bool TryGet( + GeneratorContext context, + [NotNullWhen(true)] out InterceptableLocationInfo? interceptableLocationInfo + ) + { + interceptableLocationInfo = null; + + if (context.Node is not InvocationExpressionSyntax invocationExpr) + return false; + + var interceptableLocation = context.SemanticModel.GetInterceptableLocation( + invocationExpr, + context.CancellationToken + ); + + if (interceptableLocation is null) + return false; + + interceptableLocationInfo = InterceptableLocationInfo.CreateFrom(interceptableLocation); + return true; + } + + internal string ToInterceptsLocationAttribute() => + $""" [InterceptsLocation({location.Version}, "{location.Data}")]"""; + } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs new file mode 100644 index 00000000..597392f3 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs @@ -0,0 +1,219 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal static class MapHandlerExtractors +{ + internal static IEnumerable<(string?, DiagnosticInfo?)> GetParameterAssignments( + IMethodSymbol methodSymbol, + GeneratorContext context + ) + { + var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); + var lambdaContext = context.WellKnownTypes.Get( + WellKnownType.Amazon_Lambda_Core_ILambdaContext + ); + var lambdaInvocationContext = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_ILambdaInvocationContext + ); + var cancellationToken = context.WellKnownTypes.Get( + WellKnownType.System_Threading_CancellationToken + ); + + foreach (var parameter in methodSymbol.Parameters) + { + var paramType = parameter.Type.ToGloballyQualifiedName(); + + var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( + context, + out var keyResult + ); + + // event + if (isEvent) + { + // stream event + if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) + { + yield return ( + "context.Features.GetRequired().EventStream", + null + ); + continue; + } + + // non stream event + yield return ($"context.GetRequiredEvent<{paramType}>()", null); + continue; + } + + // context + if ( + SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) + || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) + ) + { + yield return ("context", null); + continue; + } + + // cancellation token + if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) + { + yield return ("context.CancellationToken", null); + continue; + } + + // keyed services + if (isKeyedServices) + { + // get key for keyed service + if (keyResult?.IsSuccess == false) + { + yield return (null, keyResult.Error!.Value); + continue; + } + + // KeyedService - optional + if (parameter.IsOptional) + { + yield return ( + $"context.ServiceProvider.GetKeyedService<{paramType}>({keyResult?.Value})", + null + ); + continue; + } + + // KeyedService + yield return ( + $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({keyResult?.Value})", + null + ); + continue; + } + + // default - inject required from DI + if (parameter.IsOptional) + { + yield return ($"context.ServiceProvider.GetService<{paramType}>()", null); + continue; + } + + // default - inject required from DI - optional + yield return ($"context.ServiceProvider.GetRequiredService<{paramType}>()", null); + } + } + + extension(IParameterSymbol parameterSymbol) + { + internal (bool IsFromEvent, bool IsFromKeyedService) IsFromEventOrFromKeyedService( + GeneratorContext context, + out DiagnosticResult? keyResult + ) + { + keyResult = null; + + var eventAttr = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_Builder_EventAttribute + ); + var fromEventAttr = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_Builder_FromEventAttribute + ); + var fromKeyedServicesAttr = context.WellKnownTypes.Get( + WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute + ); + + foreach (var attribute in parameterSymbol.GetAttributes()) + { + if (attribute is null) + continue; + + var attrClass = attribute.AttributeClass; + + // check event + if (SymbolEqualityComparer.Default.Equals(attrClass, eventAttr)) + return (true, false); + + // check from event + if (SymbolEqualityComparer.Default.Equals(attrClass, fromEventAttr)) + return (true, false); + + // check keyed service + if (SymbolEqualityComparer.Default.Equals(attrClass, fromKeyedServicesAttr)) + { + keyResult = attribute.ExtractKeyedServiceKey(); + return (false, true); + } + } + + return (false, false); + } + } + + extension(AttributeData attributeData) + { + private DiagnosticResult ExtractKeyedServiceKey() + { + var argument = attributeData.ConstructorArguments[0]; + var keyBaseType = argument.Type?.BaseType?.ToGloballyQualifiedName(); + var keyType = argument.Type?.ToGloballyQualifiedName(); + + if (argument.IsNull) + return DiagnosticResult.Success("null"); + + object? value = null; + try + { + value = argument.Value; + } + catch + { + // ignore + } + + if (value is null) + return DiagnosticResult.Failure( + Diagnostics.InvalidAttributeArgument, + attributeData.GetAttributeArgumentLocation(0), + argument.Type?.ToGloballyQualifiedName() + ); + + return DiagnosticResult.Success( + argument.Kind switch + { + TypedConstantKind.Primitive when value is string strValue => + SymbolDisplay.FormatLiteral(strValue, true), + + TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", + + TypedConstantKind.Primitive when value is bool boolValue => boolValue + ? "true" + : "false", + + TypedConstantKind.Primitive or TypedConstantKind.Enum => + $"({argument.Type?.ToGloballyQualifiedName()}){value}", + + TypedConstantKind.Type when value is ITypeSymbol typeValue => + $"typeof({typeValue.ToGloballyQualifiedName()})", + + _ => value.ToString(), + } + ); + } + + private LocationInfo? GetAttributeArgumentLocation(int index) => + attributeData.ApplicationSyntaxReference?.GetSyntax() + is AttributeSyntax { ArgumentList: { } argumentList } + ? argumentList + .Arguments.ElementAtOrDefault(index) + ?.Expression.GetLocation() + .CreateLocationInfo() + : null; + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Result.cs b/src/MinimalLambda.SourceGenerators/Models/Result.cs new file mode 100644 index 00000000..e725a58a --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Result.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.Models; + +internal class Result +{ + public bool IsSuccess { get; } + public T? Value { get; } + public TError? Error { get; } + + protected Result(bool isSuccess, T? value, TError? error) + { + IsSuccess = isSuccess; + Value = value; + Error = error; + } + + public static Result Success(T value) => new(true, value, default); + + public static Result Failure(TError error) => new(false, default, error); + + // Map transforms the success value + public Result Map(Func map) => + IsSuccess + ? Result.Success(map(Value!)) + : Result.Failure(Error!); + + // Bind chains operations that return Results + public Result Bind(Func> bind) => + IsSuccess ? bind(Value!) : Result.Failure(Error!); + + // Match for pattern matching + public TResult Match(Func onSuccess, Func onFailure) => + IsSuccess ? onSuccess(Value!) : onFailure(Error!); +} + +internal class DiagnosticResult : Result +{ + private DiagnosticResult(bool isSuccess, T? value, DiagnosticInfo? error) + : base(isSuccess, value, error) { } + + public static new DiagnosticResult Success(T value) => new(true, value, null); + + public static DiagnosticResult Failure(DiagnosticInfo error) => new(false, default, error); + + public static DiagnosticResult Failure( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo = null, + params object?[] messageArgs + ) => new(false, default, new DiagnosticInfo(diagnosticDescriptor, locationInfo, messageArgs)); +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index 96c6e7f8..3e84110a 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -36,9 +36,15 @@ CancellationToken cancellationToken if (!TryGetInvocationOperation(context, out var targetOperation)) return null; - return targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol) - ? HigherOrderMethodInfo.Create(methodSymbol, targetOperation.TargetMethod.Name, context) - : null; + if (!targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol)) + return null; + + return HigherOrderMethodInfo.Create( + methodSymbol, + targetOperation.TargetMethod.Name, + MapHandlerExtractors.GetParameterAssignments, + context + ); } private static bool TryGetInvocationOperation( From 8351899f0f297089a7363be6d5782aa5608d6f5f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 22:20:55 -0500 Subject: [PATCH 10/67] refactor(source-generators): streamline parameter extraction in `MapHandlerExtractors` - Replaced `foreach` loops with LINQ-based `Select` for cleaner parameter processing. - Removed redundant code blocks and simplified conditional flows for better readability. - Improved maintainability by consolidating logic for event, context, and DI-based parameter handling. - Enhanced default injection to distinguish required and optional parameters clearly. --- .../Models/MapHandlerExtractors.cs | 123 ++++++++---------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs index 597392f3..a1529107 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs @@ -26,88 +26,73 @@ GeneratorContext context WellKnownType.System_Threading_CancellationToken ); - foreach (var parameter in methodSymbol.Parameters) - { - var paramType = parameter.Type.ToGloballyQualifiedName(); - - var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( - context, - out var keyResult - ); - - // event - if (isEvent) - { - // stream event - if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) - { - yield return ( - "context.Features.GetRequired().EventStream", - null - ); - continue; - } - - // non stream event - yield return ($"context.GetRequiredEvent<{paramType}>()", null); - continue; - } - - // context - if ( - SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) - || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) - ) + return methodSymbol.Parameters.Select( + (string?, DiagnosticInfo?) (parameter) => { - yield return ("context", null); - continue; - } + var paramType = parameter.Type.ToGloballyQualifiedName(); - // cancellation token - if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) - { - yield return ("context.CancellationToken", null); - continue; - } + var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( + context, + out var keyResult + ); - // keyed services - if (isKeyedServices) - { - // get key for keyed service - if (keyResult?.IsSuccess == false) + // event + if (isEvent) { - yield return (null, keyResult.Error!.Value); - continue; + // stream event + if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) + return ( + "context.Features.GetRequired().EventStream", + null + ); + + // non stream event + return ($"context.GetRequiredEvent<{paramType}>()", null); } - // KeyedService - optional - if (parameter.IsOptional) + // context + if ( + SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) + || SymbolEqualityComparer.Default.Equals( + parameter.Type, + lambdaInvocationContext + ) + ) + return ("context", null); + + // cancellation token + if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) + return ("context.CancellationToken", null); + + // keyed services + if (isKeyedServices) { - yield return ( - $"context.ServiceProvider.GetKeyedService<{paramType}>({keyResult?.Value})", + // get key for keyed service + if (keyResult?.IsSuccess == false) + return (null, keyResult.Error!.Value); + + // KeyedService - optional + if (parameter.IsOptional) + return ( + $"context.ServiceProvider.GetKeyedService<{paramType}>({keyResult?.Value})", + null + ); + + // KeyedService + return ( + $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({keyResult?.Value})", null ); - continue; } - // KeyedService - yield return ( - $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({keyResult?.Value})", - null - ); - continue; - } + // default - inject from DI - optional + if (parameter.IsOptional) + return ($"context.ServiceProvider.GetService<{paramType}>()", null); - // default - inject required from DI - if (parameter.IsOptional) - { - yield return ($"context.ServiceProvider.GetService<{paramType}>()", null); - continue; + // default - inject required from DI + return ($"context.ServiceProvider.GetRequiredService<{paramType}>()", null); } - - // default - inject required from DI - optional - yield return ($"context.ServiceProvider.GetRequiredService<{paramType}>()", null); - } + ); } extension(IParameterSymbol parameterSymbol) From 9703df380500c26d95876618df936127e5797383 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 25 Dec 2025 22:23:54 -0500 Subject: [PATCH 11/67] refactor(source-generators): enhance parameter assignment handling in `HigherOrderMethodInfo` - Renamed tuple fields in parameter assignment to improve clarity (`Assignment`, `Diagnostic`). - Updated aggregation logic to use renamed tuple fields within assignment processing. - Added initialization of `HigherOrderMethodInfo` with parameter and diagnostic data for better encapsulation. - Removed unused variable `location` to clean up redundant code. --- .../Models/HigherOrderMethodInfo.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 7739abf2..93fc7b24 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -30,13 +30,12 @@ internal static class HigherOrderMethodInfoExtensions Func< IMethodSymbol, GeneratorContext, - IEnumerable<(string?, DiagnosticInfo?)> + IEnumerable<(string? Assignment, DiagnosticInfo? Diagnostic)> > getParameterAssignments, GeneratorContext context ) { var handlerCastType = GetMethodSignature(methodSymbol); - var location = context.Node.CreateLocationInfo(); if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) throw new InvalidOperationException("Unable to get interceptable location"); @@ -46,18 +45,29 @@ GeneratorContext context (Successes: new List(), Diagnostics: new List()), static (acc, result) => { - if (result.Item1 is not null) - acc.Successes.Add(result.Item1); + if (result.Assignment is not null) + acc.Successes.Add(result.Assignment); - if (result.Item2 is not null) - acc.Diagnostics.Add(result.Item2.Value); + if (result.Diagnostic is not null) + acc.Diagnostics.Add(result.Diagnostic.Value); return acc; }, - static acc => (acc.Successes.ToEquatableArray(), acc.Diagnostics.ToArray()) + static acc => + (acc.Successes.ToEquatableArray(), acc.Diagnostics.ToEquatableArray()) ); - return null; + return new HigherOrderMethodInfo + { + Name = name, + DelegateInfo = default, + LocationInfo = null, + InterceptableLocationInfo = interceptableLocation.Value, + ArgumentsInfos = default, + DelegateCastType = handlerCastType, + ParameterAssignments = assignments, + DiagnosticInfos = diagnostics, + }; } } From 275fdfa76ba03e0ec71b4cffd649a3b19e8871d4 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 15:39:44 -0500 Subject: [PATCH 12/67] feat(source-generators): add `ParameterInfo2` and enhance parameter handling logic - Introduced `ParameterInfo2` and accompanying extensions for structured parameter handling. - Enhanced `MapHandlerExtractors` with detailed logic for context, events, and DI-based parameters. - Refactored `HigherOrderMethodInfo.Create` to utilize `ParameterInfo2` for improved encapsulation. - Added support for extraction of `IsAwaitable`, `HasReturnType`, and return type classifications. - Commented out `OnInit` logic in `OutputFormattingLambdaApplicationExtensions` for future review. - Improved `Result` class with new mapping and binding functionality for diagnostics. --- .../Models/HigherOrderMethodInfo.cs | 15 +++- .../Models/MapHandlerExtractors.cs | 87 ++++++++++++++++++- .../Models/Result.cs | 8 ++ .../SyntaxProviders/HandlerSyntaxProvider.cs | 1 - ...utFormattingLambdaApplicationExtensions.cs | 25 +++--- 5 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 93fc7b24..321e8030 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; @@ -17,6 +18,10 @@ internal readonly record struct HigherOrderMethodInfo( // ── New ────────────────────────────────────────────────────────────────────────── string DelegateCastType = "", EquatableArray ParameterAssignments = default, + bool IsAwaitable = false, + bool HasReturnType = false, + bool IsReturnTypeStream = false, + bool IsReturnTypeBool = false, EquatableArray DiagnosticInfos = default ); @@ -26,7 +31,6 @@ internal static class HigherOrderMethodInfoExtensions { internal static HigherOrderMethodInfo? Create( IMethodSymbol methodSymbol, - string name, Func< IMethodSymbol, GeneratorContext, @@ -35,6 +39,9 @@ internal static class HigherOrderMethodInfoExtensions GeneratorContext context ) { + var gotName = context.Node.TryGetMethodName(out var methodName); + Debug.Assert(gotName, "Could not get method name. This should be unreachable"); + var handlerCastType = GetMethodSignature(methodSymbol); if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) @@ -59,13 +66,17 @@ GeneratorContext context return new HigherOrderMethodInfo { - Name = name, + Name = methodName!, DelegateInfo = default, LocationInfo = null, InterceptableLocationInfo = interceptableLocation.Value, ArgumentsInfos = default, DelegateCastType = handlerCastType, ParameterAssignments = assignments, + IsAwaitable = false, + HasReturnType = false, + IsReturnTypeStream = false, + IsReturnTypeBool = false, DiagnosticInfos = diagnostics, }; } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs index a1529107..bdd0adc7 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -8,6 +9,90 @@ namespace MinimalLambda.SourceGenerators.Models; +internal readonly record struct ParameterInfo2(string Assignment, string InfoComment); + +internal static class ParameterInfo2Extensions +{ + private static Func> Success(string infoComment) + { + var info = new ParameterInfo2 { InfoComment = infoComment }; + return assignment => + DiagnosticResult.Success(info with { Assignment = assignment }); + } + + extension(ParameterInfo2) + { + internal static DiagnosticResult CreateForInvocationHandler( + IParameterSymbol parameter, + GeneratorContext context + ) + { + var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); + var lambdaContext = context.WellKnownTypes.Get( + WellKnownType.Amazon_Lambda_Core_ILambdaContext + ); + var lambdaInvocationContext = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_ILambdaInvocationContext + ); + var cancellationToken = context.WellKnownTypes.Get( + WellKnownType.System_Threading_CancellationToken + ); + + var paramType = parameter.Type.ToGloballyQualifiedName(); + + var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( + context, + out var keyResult + ); + + var success = Success(""); + + // event + if (isEvent) + { + // stream event + if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) + return success( + "context.Features.GetRequired().EventStream" + ); + + // non stream event + return success($"context.GetRequiredEvent<{paramType}>()"); + } + + // context + if ( + SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) + || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) + ) + return success("context"); + + // cancellation token + if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) + return success("context.CancellationToken"); + + // keyed services + if (isKeyedServices) + return keyResult!.Bind(key => + parameter.IsOptional + ? success($"context.ServiceProvider.GetKeyedService<{paramType}>({key})") + : success( + $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})" + ) + ); + + // default - inject from DI - optional + if (parameter.IsOptional) + return success($"context.ServiceProvider.GetService<{paramType}>()"); + + // default - inject required from DI + return success($"context.ServiceProvider.GetRequiredService<{paramType}>()"); + } + + internal static ParameterInfo2 CreateForLifecycleHandler() => new(); + } +} + internal static class MapHandlerExtractors { internal static IEnumerable<(string?, DiagnosticInfo?)> GetParameterAssignments( @@ -146,8 +231,6 @@ out DiagnosticResult? keyResult private DiagnosticResult ExtractKeyedServiceKey() { var argument = attributeData.ConstructorArguments[0]; - var keyBaseType = argument.Type?.BaseType?.ToGloballyQualifiedName(); - var keyType = argument.Type?.ToGloballyQualifiedName(); if (argument.IsNull) return DiagnosticResult.Success("null"); diff --git a/src/MinimalLambda.SourceGenerators/Models/Result.cs b/src/MinimalLambda.SourceGenerators/Models/Result.cs index e725a58a..fe7de80d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Result.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Result.cs @@ -49,4 +49,12 @@ public static DiagnosticResult Failure( LocationInfo? locationInfo = null, params object?[] messageArgs ) => new(false, default, new DiagnosticInfo(diagnosticDescriptor, locationInfo, messageArgs)); + + public new DiagnosticResult Map(Func map) => + IsSuccess + ? DiagnosticResult.Success(map(Value!)) + : DiagnosticResult.Failure(Error!.Value); + + public DiagnosticResult Bind(Func> bind) => + IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!.Value); } diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index 3e84110a..d123b640 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -41,7 +41,6 @@ CancellationToken cancellationToken return HigherOrderMethodInfo.Create( methodSymbol, - targetOperation.TargetMethod.Name, MapHandlerExtractors.GetParameterAssignments, context ); diff --git a/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs b/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs index 58ade079..c9c4a52e 100644 --- a/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs +++ b/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.Logging; - namespace MinimalLambda.Builder; /// Provides extension methods for managing Lambda runtime output formatting. @@ -19,17 +17,18 @@ this ILambdaOnInitBuilder application { ArgumentNullException.ThrowIfNull(application); - application.OnInit( - (ILogger? logger = null) => - { - // This will clear the output formatting set by the Lambda runtime. - Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }); - - logger?.LogInformation("Clearing Lambda output formatting"); - - return Task.FromResult(true); - } - ); + // application.OnInit( + // (ILogger? logger = null) => + // { + // // This will clear the output formatting set by the Lambda runtime. + // Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true + // }); + // + // logger?.LogInformation("Clearing Lambda output formatting"); + // + // return Task.FromResult(true); + // } + // ); return application; } From 97a0507faa602032900ae998934f132ed7bcfc87 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 15:40:14 -0500 Subject: [PATCH 13/67] refactor(source-generators): simplify DI-based parameter handling in `MapHandlerExtractors` - Refactored DI-based parameter resolution logic to use a ternary expression for improved clarity. - Removed redundant comments and unified optional and required DI injection logic. --- .../Models/MapHandlerExtractors.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs index bdd0adc7..9f971951 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs @@ -81,12 +81,13 @@ out var keyResult ) ); - // default - inject from DI - optional - if (parameter.IsOptional) - return success($"context.ServiceProvider.GetService<{paramType}>()"); - - // default - inject required from DI - return success($"context.ServiceProvider.GetRequiredService<{paramType}>()"); + return success( + parameter.IsOptional + // default - inject from DI - optional + ? $"context.ServiceProvider.GetService<{paramType}>()" + // default - inject required from DI + : $"context.ServiceProvider.GetRequiredService<{paramType}>()" + ); } internal static ParameterInfo2 CreateForLifecycleHandler() => new(); From 2f8a0aa8dd778b2458873b756901760664a27b70 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 15:41:31 -0500 Subject: [PATCH 14/67] refactor(source-generators): simplify event handling logic in `MapHandlerExtractors` - Replaced nested `if` structure with a single ternary expression for clarity and conciseness. - Removed redundant comments to improve readability of the event resolution logic. --- .../Models/MapHandlerExtractors.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs index 9f971951..11c9a94a 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs @@ -50,14 +50,13 @@ out var keyResult // event if (isEvent) { - // stream event - if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) - return success( - "context.Features.GetRequired().EventStream" - ); - - // non stream event - return success($"context.GetRequiredEvent<{paramType}>()"); + return success( + SymbolEqualityComparer.Default.Equals(parameter.Type, stream) + // stream event + ? "context.Features.GetRequired().EventStream" + // non stream event + : $"context.GetRequiredEvent<{paramType}>()" + ); } // context From 16e5095c7b3538671898a053cbfdf80662489907 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 16:03:16 -0500 Subject: [PATCH 15/67] refactor(tests): remove redundant comments in snapshot test files - Deleted unnecessary `ParameterInfo` comments in generated snapshot files for clarity. - Ensured no functional changes to existing logic or behavior. --- ...kLambda_AllInputSources#LambdaHandler.g.verified.cs | 9 +-------- ...ambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs | 4 +--- ...bda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs | 4 +--- ...mbda_ReturnExplicitType#LambdaHandler.g.verified.cs | 4 +--- ..._ReturnImplicitNullable#LambdaHandler.g.verified.cs | 4 +--- ...lockLambda_ReturnString#LambdaHandler.g.verified.cs | 3 +-- ...st_BlockLambda_TypeCast#LambdaHandler.g.verified.cs | 3 +-- ..._InputFromKeyedServices#LambdaHandler.g.verified.cs | 3 +-- ...sksForCancellationToken#LambdaHandler.g.verified.cs | 3 +-- ...onTokenAndLambdaContext#LambdaHandler.g.verified.cs | 4 +--- ...kenAndLambdaHostContext#LambdaHandler.g.verified.cs | 4 +--- ...LambdaInvocationContext#LambdaHandler.g.verified.cs | 4 +--- ...plexInput_ComplexOutput#LambdaHandler.g.verified.cs | 5 +---- ...ionLambda_InputDi_Async#LambdaHandler.g.verified.cs | 4 +--- ...a_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs | 4 +--- ...ponseDifferentNamespace#LambdaHandler.g.verified.cs | 4 +--- ...ssionLambda_InputStream#LambdaHandler.g.verified.cs | 3 +-- ..._ReturnExplicitNullable#LambdaHandler.g.verified.cs | 4 +--- ..._ReturnImplicitNullable#LambdaHandler.g.verified.cs | 4 +--- ...mbda_ReturnExplicitType#LambdaHandler.g.verified.cs | 3 +-- ...vice_FloatingPointTypes#LambdaHandler.g.verified.cs | 4 +--- ...dService_IntAndLongKeys#LambdaHandler.g.verified.cs | 4 +--- ...KeyedService_OtherTypes#LambdaHandler.g.verified.cs | 6 +----- ...rvice_SmallIntegerTypes#LambdaHandler.g.verified.cs | 5 +---- ...rvice_StringAndEnumKeys#LambdaHandler.g.verified.cs | 5 +---- ...ce_UnsignedIntegerTypes#LambdaHandler.g.verified.cs | 5 +---- ...dy_InputDiKeyedServices#LambdaHandler.g.verified.cs | 5 +---- ...ethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs | 3 +-- ...AndReturnUnexpectedType#LambdaHandler.g.verified.cs | 3 +-- ...st_OnInit_MultipleCalls#LambdaHandler.g.verified.cs | 10 +++------- ...ValueAndReferenceInputs#LambdaHandler.g.verified.cs | 4 +--- ...EachPossibleKindOfInput#LambdaHandler.g.verified.cs | 7 +------ ...t_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs | 4 +--- ...nShutdown_MultipleCalls#LambdaHandler.g.verified.cs | 10 +++------- ...ValueAndReferenceInputs#LambdaHandler.g.verified.cs | 4 +--- ...EachPossibleKindOfInput#LambdaHandler.g.verified.cs | 7 +------ ...Shutdown_PrimitiveInput#LambdaHandler.g.verified.cs | 4 +--- 37 files changed, 41 insertions(+), 129 deletions(-) diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs index 79505b21..5832736c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs @@ -65,19 +65,12 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = string, Name = request, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = cancellationToken, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg2 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service0, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key0", Type = string, BaseType = object } } var arg3 = context.ServiceProvider.GetRequiredKeyedService("key0"); - // ParameterInfo { Type = global::IService?, Name = service1, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg4 = context.ServiceProvider.GetKeyedService("key1"); - // ParameterInfo { Type = global::IService, Name = service2, Source = Service, IsNullable = False, IsOptional = False} var arg5 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service3, Source = Service, IsNullable = True, IsOptional = False} var arg6 = context.ServiceProvider.GetService(); castHandler.Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6); return Task.CompletedTask; @@ -89,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs index 3d92da48..2b807f81 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -75,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs index 35ffe2a2..34fa03ba 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = string, Name = input, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs index d0c4a47a..ab237dd5 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs index d8373096..ffc90dfe 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs index 345ce2b8..eeb4e588 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs @@ -66,7 +66,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -83,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs index 9f2d746a..3974a582 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs @@ -61,7 +61,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -78,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs index 4b084524..09b43da1 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs @@ -65,7 +65,6 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("key"); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -82,4 +81,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs index a1dceb41..acda23b0 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs @@ -61,7 +61,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = cancellationToken, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -78,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs index 7e4a171d..14d978a6 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs index 070aa8ab..4dca0f3f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs @@ -62,9 +62,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::MinimalLambda.ILambdaInvocationContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -81,4 +79,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs index e1cbf526..f3414fef 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::MinimalLambda.ILambdaInvocationContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs index 37960d92..37d1b8e9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs @@ -66,11 +66,8 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::CustomRequest, Name = request, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg2 = context; var response = await castHandler.Invoke(arg0, arg1, arg2); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -86,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs index 241f6d2a..488b51e2 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs index 241f6d2a..488b51e2 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs index ce029764..00bbfe8c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::MyNamespace.Event, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs index e2bb3a78..94c64b0d 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs @@ -56,7 +56,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.IO.Stream, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.Features.GetRequired().EventStream; castHandler.Invoke(arg0); return Task.CompletedTask; @@ -68,4 +67,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs index e8a71a8d..6c9452a0 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = int?, Name = input, Source = Event, IsNullable = True, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs index e7472deb..ebe652d7 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string?, Name = input, Source = Event, IsNullable = True, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs index bb7d973e..b376fae7 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs @@ -66,7 +66,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -83,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs index f906373c..cf1e35e3 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs @@ -60,9 +60,7 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (double)3.14, Type = double, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((double)3.14); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (float)3.14, Type = float, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((float)3.14); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -74,4 +72,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs index 44d8b19d..5e9a2a45 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs @@ -60,9 +60,7 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (int)42, Type = int, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((int)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (long)42, Type = long, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((long)42); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -74,4 +72,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs index d212a087..7065b4c9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs @@ -60,13 +60,9 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = true, Type = bool, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService(true); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = 'A', Type = char, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService('A'); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = typeof(global::Service), Type = global::System.Type, BaseType = global::System.Reflection.MemberInfo } } var arg2 = context.ServiceProvider.GetRequiredKeyedService(typeof(global::Service)); - // ParameterInfo { Type = global::IService, Name = serviceD, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = null, Type = object, BaseType = } } var arg3 = context.ServiceProvider.GetRequiredKeyedService(null); castHandler.Invoke(arg0, arg1, arg2, arg3); return Task.CompletedTask; @@ -78,4 +74,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs index 21d6c492..2e581b92 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (short)42, Type = short, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((short)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (byte)42, Type = byte, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((byte)42); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (sbyte)42, Type = sbyte, BaseType = global::System.ValueType } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((sbyte)42); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs index 18a0af9b..54aa5c8b 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("myKey"); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "my\nKey", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("my\nKey"); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (global::ServiceType)1, Type = global::ServiceType, BaseType = global::System.Enum } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((global::ServiceType)1); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs index f9fa1e28..762c71f9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (uint)42, Type = uint, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((uint)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (ulong)42, Type = ulong, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((ulong)42); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (ushort)42, Type = ushort, BaseType = global::System.ValueType } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((ushort)42); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs index e0d822a8..8aa525ce 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs @@ -70,11 +70,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetRequiredKeyedService("key"); var response = castHandler.Invoke(arg0, arg1, arg2); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -91,4 +88,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs index a69230bb..644f8dfa 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs @@ -51,7 +51,6 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0); return response; @@ -62,4 +61,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs index 6d1e010a..a932e538 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs @@ -51,7 +51,6 @@ Delegate handler async Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); await castHandler.Invoke(arg0); return true; @@ -62,4 +61,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs index a32ea31c..0d32cd15 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs @@ -55,7 +55,7 @@ Task OnInit(ILambdaLifecycleContext context) return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnInitBuilder OnInitInterceptor1( this ILambdaOnInitBuilder application, @@ -68,15 +68,13 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnInitBuilder OnInitInterceptor2( this ILambdaOnInitBuilder application, @@ -89,9 +87,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -102,4 +98,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs index 96e4ef39..a017c4a9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = True} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs index dae86938..048e53f4 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs @@ -55,15 +55,10 @@ Task OnInit(ILambdaLifecycleContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = token, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service1, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("key1"); - // ParameterInfo { Type = global::IService?, Name = service2, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key2", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetKeyedService("key2"); - // ParameterInfo { Type = global::IService, Name = service3, Source = Service, IsNullable = False, IsOptional = False} var arg3 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service4, Source = Service, IsNullable = True, IsOptional = False} var arg4 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1, arg2, arg3, arg4); return response; @@ -74,4 +69,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs index 5facbd97..1a72ed5e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs index 1498df42..c415e702 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs @@ -55,7 +55,7 @@ Task OnShutdown(ILambdaLifecycleContext context) return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnShutdownBuilder OnShutdownInterceptor1( this ILambdaOnShutdownBuilder application, @@ -68,15 +68,13 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnShutdownBuilder OnShutdownInterceptor2( this ILambdaOnShutdownBuilder application, @@ -89,9 +87,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -102,4 +98,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs index 2221e934..59200694 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs index eb933b99..06950c8e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs @@ -55,15 +55,10 @@ Task OnShutdown(ILambdaLifecycleContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = token, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service1, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("key1"); - // ParameterInfo { Type = global::IService?, Name = service2, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key2", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetKeyedService("key2"); - // ParameterInfo { Type = global::IService, Name = service3, Source = Service, IsNullable = False, IsOptional = False} var arg3 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service4, Source = Service, IsNullable = True, IsOptional = False} var arg4 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1, arg2, arg3, arg4); return response; @@ -74,4 +69,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs index 2ab54a3a..8c26aea1 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} From 4579afdfba39d504394655d7119920ecde02378b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 17:11:20 -0500 Subject: [PATCH 16/67] refactor(source-generators): remove `Result` and simplify parameter handling logic - Removed the `Result` and `DiagnosticResult` classes to streamline diagnostics and result handling. - Refactored `MapHandlerExtractors` to incorporate new parameter extraction logic. - Introduced `ParameterInfo2` to enhance encapsulation of parameter metadata and simplify assignment logic. - Updated `HigherOrderMethodInfo.Create` for improved parameter diagnostics and encapsulation. - Added new utility extensions for handling well-known types and simplified method signatures. - Replaced conditional checks with LINQ and centralized logic for DI and event parameters. - Improved maintainability by consolidating key functionalities into specialized methods. --- .../Extensions/WellKnownTypesExtensions.cs | 15 ++ .../HandlerSyntaxProvider.cs | 6 +- .../Models/DiagnosticResult.cs | 49 +++++ .../Models/HigherOrderMethodInfo.cs | 153 +++++++++++--- ...HandlerExtractors.cs => ParameterInfo2.cs} | 196 ++++++------------ .../Models/Result.cs | 60 ------ .../WellKnownTypes/WellKnownTypeData.cs | 2 + 7 files changed, 250 insertions(+), 231 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs rename src/MinimalLambda.SourceGenerators/{SyntaxProviders => }/HandlerSyntaxProvider.cs (97%) create mode 100644 src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs rename src/MinimalLambda.SourceGenerators/Models/{MapHandlerExtractors.cs => ParameterInfo2.cs} (56%) delete mode 100644 src/MinimalLambda.SourceGenerators/Models/Result.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs new file mode 100644 index 00000000..d02e3b63 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +internal static class WellKnownTypesExtensions +{ + extension(WellKnownTypes wellKnownTypes) + { + internal bool IsTypeMatch(ITypeSymbol type, WellKnownTypeData.WellKnownType wellKnownType) + { + var foundType = wellKnownTypes.Get(wellKnownType); + return type.Equals(foundType, SymbolEqualityComparer.Default); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/HandlerSyntaxProvider.cs similarity index 97% rename from src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs rename to src/MinimalLambda.SourceGenerators/HandlerSyntaxProvider.cs index d123b640..5fda70bd 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/HandlerSyntaxProvider.cs @@ -39,11 +39,7 @@ CancellationToken cancellationToken if (!targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol)) return null; - return HigherOrderMethodInfo.Create( - methodSymbol, - MapHandlerExtractors.GetParameterAssignments, - context - ); + return HigherOrderMethodInfo.Create(methodSymbol, context); } private static bool TryGetInvocationOperation( diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs new file mode 100644 index 00000000..d16f25c4 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.Models; + +internal class DiagnosticResult +{ + internal bool IsSuccess { get; } + internal T? Value { get; } + internal DiagnosticInfo? Error { get; } + + private DiagnosticResult(bool isSuccess, T? value, DiagnosticInfo? error) + { + IsSuccess = isSuccess; + Value = value; + Error = error; + } + + public static DiagnosticResult Success(T value) => new(true, value, null); + + public static DiagnosticResult Failure(DiagnosticInfo error) => new(false, default, error); + + public static DiagnosticResult Failure( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo = null, + params object?[] messageArgs + ) => new(false, default, new DiagnosticInfo(diagnosticDescriptor, locationInfo, messageArgs)); + + public DiagnosticResult Map(Func map) => + IsSuccess + ? DiagnosticResult.Success(map(Value!)) + : DiagnosticResult.Failure(Error!.Value); + + public DiagnosticResult Bind(Func> bind) => + IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!.Value); + + public TResult Match( + Func onSuccess, + Func onFailure + ) => IsSuccess ? onSuccess(Value!) : onFailure(Error!.Value); + + public void Do(Action onSuccess, Action onFailure) + { + if (IsSuccess) + onSuccess(Value!); + else + onFailure(Error!.Value); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 321e8030..37a3928f 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -6,6 +6,13 @@ using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; +using ParameterAssigner = System.Func< + Microsoft.CodeAnalysis.IParameterSymbol, + MinimalLambda.SourceGenerators.GeneratorContext, + MinimalLambda.SourceGenerators.Models.DiagnosticResult +>; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -17,7 +24,7 @@ internal readonly record struct HigherOrderMethodInfo( ImmutableArray ArgumentsInfos, // ── New ────────────────────────────────────────────────────────────────────────── string DelegateCastType = "", - EquatableArray ParameterAssignments = default, + EquatableArray ParameterAssignments = default, bool IsAwaitable = false, bool HasReturnType = false, bool IsReturnTypeStream = false, @@ -31,32 +38,39 @@ internal static class HigherOrderMethodInfoExtensions { internal static HigherOrderMethodInfo? Create( IMethodSymbol methodSymbol, - Func< - IMethodSymbol, - GeneratorContext, - IEnumerable<(string? Assignment, DiagnosticInfo? Diagnostic)> - > getParameterAssignments, GeneratorContext context ) { var gotName = context.Node.TryGetMethodName(out var methodName); Debug.Assert(gotName, "Could not get method name. This should be unreachable"); - var handlerCastType = GetMethodSignature(methodSymbol); + var handlerCastType = methodSymbol.GetCastableSignature(); if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) throw new InvalidOperationException("Unable to get interceptable location"); - var (assignments, diagnostics) = getParameterAssignments(methodSymbol, context) + ParameterAssigner getParameterAssignments = methodName switch + { + "MapHandler" => ParameterInfo2.CreateForInvocationHandler, + "OnInit" or "OnShutdown" => ParameterInfo2.CreateForLifecycleHandler, + _ => throw new InvalidOperationException( + $"Handler with name '{methodName}' is not valid" + ), + }; + + var (assignments, diagnostics) = methodSymbol + .Parameters.Select(parameter => getParameterAssignments(parameter, context)) .Aggregate( - (Successes: new List(), Diagnostics: new List()), + ( + Successes: new List(), + Diagnostics: new List() + ), static (acc, result) => { - if (result.Assignment is not null) - acc.Successes.Add(result.Assignment); - - if (result.Diagnostic is not null) - acc.Diagnostics.Add(result.Diagnostic.Value); + result.Do( + info => acc.Successes.Add(info), + diagnostic => acc.Diagnostics.Add(diagnostic) + ); return acc; }, @@ -64,6 +78,22 @@ GeneratorContext context (acc.Successes.ToEquatableArray(), acc.Diagnostics.ToEquatableArray()) ); + var isAwaitable = methodSymbol.IsAwaitable(context); + var hasReturnType = methodSymbol.HasMeaningfulReturnType(context); + var isReturnTypeStream = + hasReturnType + && context.WellKnownTypes.IsTypeMatch( + methodSymbol.ReturnType, + WellKnownType.System_IO_Stream + ); + var isReturnTypeBool = + hasReturnType + && !isReturnTypeStream + && context.WellKnownTypes.IsTypeMatch( + methodSymbol.ReturnType, + WellKnownType.System_Boolean + ); + return new HigherOrderMethodInfo { Name = methodName!, @@ -73,30 +103,87 @@ GeneratorContext context ArgumentsInfos = default, DelegateCastType = handlerCastType, ParameterAssignments = assignments, - IsAwaitable = false, - HasReturnType = false, - IsReturnTypeStream = false, - IsReturnTypeBool = false, + IsAwaitable = isAwaitable, + HasReturnType = hasReturnType, + IsReturnTypeStream = isReturnTypeStream, + IsReturnTypeBool = isReturnTypeBool, DiagnosticInfos = diagnostics, }; } } - private static string GetMethodSignature(IMethodSymbol method) + extension(IMethodSymbol methodSymbol) { - var returnType = method.ReturnType.ToGloballyQualifiedName(); - var parameters = method - .Parameters.Select( - (p, i) => - { - var type = p.Type.ToGloballyQualifiedName(); - var defaultValue = p.IsOptional ? " = default" : ""; - return $"{type} arg{i}{defaultValue}"; - } - ) - .ToArray(); - var parameterList = string.Join(", ", parameters); - - return $"{returnType} ({parameterList}) => throw null!"; + private string GetCastableSignature() + { + var returnType = methodSymbol.ReturnType.ToGloballyQualifiedName(); + var parameters = methodSymbol + .Parameters.Select( + (p, i) => + { + var type = p.Type.ToGloballyQualifiedName(); + var defaultValue = p.IsOptional ? " = default" : ""; + return $"{type} arg{i}{defaultValue}"; + } + ) + .ToArray(); + var parameterList = string.Join(", ", parameters); + + return $"{returnType} ({parameterList}) => throw null!"; + } + + private bool IsAwaitable(GeneratorContext context) + { + var returnType = methodSymbol.ReturnType; + + // Check for Task and Task + var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); + if (returnType.Equals(task, SymbolEqualityComparer.Default)) + return true; + + var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); + if (returnType.Equals(taskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for ValueTask and ValueTask + var valueTask = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask + ); + if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + return true; + + var valueTaskOfT = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask_T + ); + if (returnType.OriginalDefinition.Equals(valueTaskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for custom awaitable pattern (has GetAwaiter method) + return returnType + .GetMembers("GetAwaiter") + .OfType() + .Any(m => m.Parameters.Length == 0 && !m.IsStatic); + } + + private bool HasMeaningfulReturnType(GeneratorContext context) + { + var returnType = methodSymbol.ReturnType; + + var voidType = context.WellKnownTypes.Get(WellKnownType.System_Void); + if (returnType.Equals(voidType, SymbolEqualityComparer.Default)) + return false; + + var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); + if (returnType.Equals(task, SymbolEqualityComparer.Default)) + return false; + + var valueTask = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask + ); + if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + return false; + + return true; + } } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs b/src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs similarity index 56% rename from src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs rename to src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs index 11c9a94a..6733723d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerExtractors.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -40,16 +39,10 @@ GeneratorContext context var paramType = parameter.Type.ToGloballyQualifiedName(); - var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( - context, - out var keyResult - ); - var success = Success(""); // event - if (isEvent) - { + if (parameter.IsFromEvent(context)) return success( SymbolEqualityComparer.Default.Equals(parameter.Type, stream) // stream event @@ -57,7 +50,6 @@ out var keyResult // non stream event : $"context.GetRequiredEvent<{paramType}>()" ); - } // context if ( @@ -70,131 +62,52 @@ out var keyResult if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) return success("context.CancellationToken"); - // keyed services - if (isKeyedServices) - return keyResult!.Bind(key => - parameter.IsOptional - ? success($"context.ServiceProvider.GetKeyedService<{paramType}>({key})") - : success( - $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})" - ) - ); - - return success( - parameter.IsOptional - // default - inject from DI - optional - ? $"context.ServiceProvider.GetService<{paramType}>()" - // default - inject required from DI - : $"context.ServiceProvider.GetRequiredService<{paramType}>()" - ); + // default assignment from Di + return parameter + .GetDiParameterAssignment(context) + .Bind(assignment => success(assignment)); } - internal static ParameterInfo2 CreateForLifecycleHandler() => new(); + internal static DiagnosticResult CreateForLifecycleHandler( + IParameterSymbol parameter, + GeneratorContext context + ) => throw new NotImplementedException(); } -} -internal static class MapHandlerExtractors -{ - internal static IEnumerable<(string?, DiagnosticInfo?)> GetParameterAssignments( - IMethodSymbol methodSymbol, - GeneratorContext context - ) + extension(IParameterSymbol parameterSymbol) { - var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); - var lambdaContext = context.WellKnownTypes.Get( - WellKnownType.Amazon_Lambda_Core_ILambdaContext - ); - var lambdaInvocationContext = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_ILambdaInvocationContext - ); - var cancellationToken = context.WellKnownTypes.Get( - WellKnownType.System_Threading_CancellationToken - ); - - return methodSymbol.Parameters.Select( - (string?, DiagnosticInfo?) (parameter) => - { - var paramType = parameter.Type.ToGloballyQualifiedName(); - - var (isEvent, isKeyedServices) = parameter.IsFromEventOrFromKeyedService( - context, - out var keyResult - ); - - // event - if (isEvent) - { - // stream event - if (SymbolEqualityComparer.Default.Equals(parameter.Type, stream)) - return ( - "context.Features.GetRequired().EventStream", - null - ); - - // non stream event - return ($"context.GetRequiredEvent<{paramType}>()", null); - } - - // context - if ( - SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) - || SymbolEqualityComparer.Default.Equals( - parameter.Type, - lambdaInvocationContext - ) - ) - return ("context", null); - - // cancellation token - if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) - return ("context.CancellationToken", null); + private bool IsFromEvent(GeneratorContext context) + { + var eventAttr = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_Builder_EventAttribute + ); + var fromEventAttr = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_Builder_FromEventAttribute + ); - // keyed services - if (isKeyedServices) + return parameterSymbol + .GetAttributes() + .Any(attribute => { - // get key for keyed service - if (keyResult?.IsSuccess == false) - return (null, keyResult.Error!.Value); - - // KeyedService - optional - if (parameter.IsOptional) - return ( - $"context.ServiceProvider.GetKeyedService<{paramType}>({keyResult?.Value})", - null - ); - - // KeyedService - return ( - $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({keyResult?.Value})", - null + // check event + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, eventAttr)) + return true; + + // check from event + return SymbolEqualityComparer.Default.Equals( + attribute.AttributeClass, + fromEventAttr ); - } - - // default - inject from DI - optional - if (parameter.IsOptional) - return ($"context.ServiceProvider.GetService<{paramType}>()", null); - - // default - inject required from DI - return ($"context.ServiceProvider.GetRequiredService<{paramType}>()", null); - } - ); - } + }); + } - extension(IParameterSymbol parameterSymbol) - { - internal (bool IsFromEvent, bool IsFromKeyedService) IsFromEventOrFromKeyedService( + private bool IsFromKeyedService( GeneratorContext context, out DiagnosticResult? keyResult ) { keyResult = null; - var eventAttr = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_Builder_EventAttribute - ); - var fromEventAttr = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_Builder_FromEventAttribute - ); var fromKeyedServicesAttr = context.WellKnownTypes.Get( WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute ); @@ -206,23 +119,40 @@ out DiagnosticResult? keyResult var attrClass = attribute.AttributeClass; - // check event - if (SymbolEqualityComparer.Default.Equals(attrClass, eventAttr)) - return (true, false); - - // check from event - if (SymbolEqualityComparer.Default.Equals(attrClass, fromEventAttr)) - return (true, false); - // check keyed service - if (SymbolEqualityComparer.Default.Equals(attrClass, fromKeyedServicesAttr)) - { - keyResult = attribute.ExtractKeyedServiceKey(); - return (false, true); - } + if (!SymbolEqualityComparer.Default.Equals(attrClass, fromKeyedServicesAttr)) + continue; + + keyResult = attribute.ExtractKeyedServiceKey(); + return true; } - return (false, false); + return false; + } + + private DiagnosticResult GetDiParameterAssignment(GeneratorContext context) + { + var paramType = parameterSymbol.Type.ToGloballyQualifiedName(); + + var isKeyedServices = parameterSymbol.IsFromKeyedService(context, out var keyResult); + + // keyed services + if (isKeyedServices) + return keyResult!.Bind(key => + DiagnosticResult.Success( + parameterSymbol.IsOptional + ? $"context.ServiceProvider.GetKeyedService<{paramType}>({key})" + : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})" + ) + ); + + return DiagnosticResult.Success( + parameterSymbol.IsOptional + // default - inject from DI - optional + ? $"context.ServiceProvider.GetService<{paramType}>()" + // default - inject required from DI + : $"context.ServiceProvider.GetRequiredService<{paramType}>()" + ); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/Result.cs b/src/MinimalLambda.SourceGenerators/Models/Result.cs deleted file mode 100644 index fe7de80d..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/Result.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators.Models; - -internal class Result -{ - public bool IsSuccess { get; } - public T? Value { get; } - public TError? Error { get; } - - protected Result(bool isSuccess, T? value, TError? error) - { - IsSuccess = isSuccess; - Value = value; - Error = error; - } - - public static Result Success(T value) => new(true, value, default); - - public static Result Failure(TError error) => new(false, default, error); - - // Map transforms the success value - public Result Map(Func map) => - IsSuccess - ? Result.Success(map(Value!)) - : Result.Failure(Error!); - - // Bind chains operations that return Results - public Result Bind(Func> bind) => - IsSuccess ? bind(Value!) : Result.Failure(Error!); - - // Match for pattern matching - public TResult Match(Func onSuccess, Func onFailure) => - IsSuccess ? onSuccess(Value!) : onFailure(Error!); -} - -internal class DiagnosticResult : Result -{ - private DiagnosticResult(bool isSuccess, T? value, DiagnosticInfo? error) - : base(isSuccess, value, error) { } - - public static new DiagnosticResult Success(T value) => new(true, value, null); - - public static DiagnosticResult Failure(DiagnosticInfo error) => new(false, default, error); - - public static DiagnosticResult Failure( - DiagnosticDescriptor diagnosticDescriptor, - LocationInfo? locationInfo = null, - params object?[] messageArgs - ) => new(false, default, new DiagnosticInfo(diagnosticDescriptor, locationInfo, messageArgs)); - - public new DiagnosticResult Map(Func map) => - IsSuccess - ? DiagnosticResult.Success(map(Value!)) - : DiagnosticResult.Failure(Error!.Value); - - public DiagnosticResult Bind(Func> bind) => - IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!.Value); -} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs index e931eec7..a4a7d49a 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -55,6 +55,7 @@ public enum WellKnownType MinimalLambda_Builder_FromEventAttribute, MinimalLambda_Builder_FromServicesAttribute, MinimalLambda_Builder_MiddlewareConstructorAttribute, + System_Boolean, } public static readonly string[] WellKnownTypeNames = @@ -100,5 +101,6 @@ public enum WellKnownType "MinimalLambda.Builder.FromEventAttribute", "MinimalLambda.Builder.FromServicesAttribute", "MinimalLambda.Builder.MiddlewareConstructorAttribute", + "System.Boolean", ]; } From 34958918bc5740ae6e4d7f2f5ad15e82545e1da9 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 17:12:45 -0500 Subject: [PATCH 17/67] refactor(source-generators): extract method-related logic into `MethodSymbolExtensions` - Moved `GetCastableSignature`, `IsAwaitable`, and `HasMeaningfulReturnType` from `HigherOrderMethodInfo` to `MethodSymbolExtensions` to improve modularity and reusability. - Updated references to encapsulate method-related operations within the new extension class. - Streamlined the structure of `HigherOrderMethodInfo` by removing redundant code logic. --- .../Extensions/MethodSymbolExtensions.cs | 90 +++++++++++++++++++ .../Models/HigherOrderMethodInfo.cs | 75 ---------------- 2 files changed, 90 insertions(+), 75 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs new file mode 100644 index 00000000..c951c9cd --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -0,0 +1,90 @@ +using System.Linq; +using MinimalLambda.SourceGenerators; +using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; + +namespace Microsoft.CodeAnalysis; + +internal static class MethodSymbolExtensions +{ + extension(IMethodSymbol methodSymbol) + { + internal string GetCastableSignature() + { + var returnType = methodSymbol.ReturnType.ToGloballyQualifiedName(); + var parameters = methodSymbol + .Parameters.Select( + (p, i) => + { + var type = p.Type.ToGloballyQualifiedName(); + var defaultValue = p.IsOptional ? " = default" : ""; + return $"{type} arg{i}{defaultValue}"; + } + ) + .ToArray(); + var parameterList = string.Join(", ", parameters); + + return $"{returnType} ({parameterList}) => throw null!"; + } + + internal bool IsAwaitable(GeneratorContext context) + { + var returnType = methodSymbol.ReturnType; + + // Check for Task and Task + var task = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task + ); + if (returnType.Equals(task, SymbolEqualityComparer.Default)) + return true; + + var taskOfT = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T + ); + if (returnType.Equals(taskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for ValueTask and ValueTask + var valueTask = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask + ); + if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + return true; + + var valueTaskOfT = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask_T + ); + if (returnType.OriginalDefinition.Equals(valueTaskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for custom awaitable pattern (has GetAwaiter method) + return returnType + .GetMembers("GetAwaiter") + .OfType() + .Any(m => m.Parameters.Length == 0 && !m.IsStatic); + } + + internal bool HasMeaningfulReturnType(GeneratorContext context) + { + var returnType = methodSymbol.ReturnType; + + var voidType = context.WellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Void); + if (returnType.Equals(voidType, SymbolEqualityComparer.Default)) + return false; + + var task = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task + ); + if (returnType.Equals(task, SymbolEqualityComparer.Default)) + return false; + + var valueTask = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask + ); + if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + return false; + + return true; + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 37a3928f..276c3c0c 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -111,79 +111,4 @@ GeneratorContext context }; } } - - extension(IMethodSymbol methodSymbol) - { - private string GetCastableSignature() - { - var returnType = methodSymbol.ReturnType.ToGloballyQualifiedName(); - var parameters = methodSymbol - .Parameters.Select( - (p, i) => - { - var type = p.Type.ToGloballyQualifiedName(); - var defaultValue = p.IsOptional ? " = default" : ""; - return $"{type} arg{i}{defaultValue}"; - } - ) - .ToArray(); - var parameterList = string.Join(", ", parameters); - - return $"{returnType} ({parameterList}) => throw null!"; - } - - private bool IsAwaitable(GeneratorContext context) - { - var returnType = methodSymbol.ReturnType; - - // Check for Task and Task - var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); - if (returnType.Equals(task, SymbolEqualityComparer.Default)) - return true; - - var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); - if (returnType.Equals(taskOfT, SymbolEqualityComparer.Default)) - return true; - - // Check for ValueTask and ValueTask - var valueTask = context.WellKnownTypes.Get( - WellKnownType.System_Threading_Tasks_ValueTask - ); - if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) - return true; - - var valueTaskOfT = context.WellKnownTypes.Get( - WellKnownType.System_Threading_Tasks_ValueTask_T - ); - if (returnType.OriginalDefinition.Equals(valueTaskOfT, SymbolEqualityComparer.Default)) - return true; - - // Check for custom awaitable pattern (has GetAwaiter method) - return returnType - .GetMembers("GetAwaiter") - .OfType() - .Any(m => m.Parameters.Length == 0 && !m.IsStatic); - } - - private bool HasMeaningfulReturnType(GeneratorContext context) - { - var returnType = methodSymbol.ReturnType; - - var voidType = context.WellKnownTypes.Get(WellKnownType.System_Void); - if (returnType.Equals(voidType, SymbolEqualityComparer.Default)) - return false; - - var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); - if (returnType.Equals(task, SymbolEqualityComparer.Default)) - return false; - - var valueTask = context.WellKnownTypes.Get( - WellKnownType.System_Threading_Tasks_ValueTask - ); - if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) - return false; - - return true; - } - } } From 9feeba919883d9e5ca9c223cf20dfb34601bd11c Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 19:23:18 -0500 Subject: [PATCH 18/67] feat(source-generators): refactor `MapHandler` generation logic to simplify and enhance functionality - Updated parameter handling in `MapHandlerParameterInfo` for better encapsulation and clarity. - Replaced `ParameterInfo2` with `MapHandlerParameterInfo`, introducing new properties like `IsEvent` and `IsStream`. - Simplified response and event feature resolution by removing nested conditionals. - Consolidated logic for parameter assignment extraction into `ParameterSymbolExtensions`. - Introduced support for keyed DI services and streamlined their handling. - Updated `HigherOrderMethodInfo` to reflect enhanced assignment and diagnostic logic. - Modularized logic for stream and non-stream event parameters for improved maintainability. - Added `MethodType` enum to classify supported method types (e.g., `MapHandler`, `OnInit`, `OnShutdown`). --- .../Emitters/MapHandlerSources.cs | 116 ++++-------------- .../ParameterSymbolExtensions.cs} | 83 ++----------- .../MinimalLambdaGenerator.cs | 6 +- .../Models/HigherOrderMethodInfo.cs | 101 ++++++++------- .../Models/MapHandlerParameterInfo.cs | 97 +++++++++++++++ .../Extractors/HandlerInfoExtractor.cs | 11 +- .../Templates/MapHandler.scriban | 31 +++-- 7 files changed, 211 insertions(+), 234 deletions(-) rename src/MinimalLambda.SourceGenerators/{Models/ParameterInfo2.cs => Extensions/ParameterSymbolExtensions.cs} (61%) create mode 100644 src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index eb058da5..66c85b55 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -1,6 +1,4 @@ -using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Extensions; using MinimalLambda.SourceGenerators.Models; namespace MinimalLambda.SourceGenerators; @@ -9,105 +7,33 @@ internal static class MapHandlerSources { internal static string Generate(EquatableArray mapHandlerInvocationInfos) { - var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandlerInvocationInfo => - { - var delegateInfo = mapHandlerInvocationInfo.DelegateInfo; - - // build handler function signature - var handlerSignature = delegateInfo.BuildHandlerCastCall(); - - // build out assignment statements for each handler parameter - var handlerArgs = delegateInfo.BuildHandlerParameterAssignment(); - - // get input event type - var inputEvent = delegateInfo.EventParameter is { } p - ? new - { - IsStream = p.TypeInfo.FullyQualifiedType == TypeConstants.Stream, - Type = p.TypeInfo.FullyQualifiedType, - } - : null; - - // get output response type and whether it is a stream - var outputResponse = delegateInfo.HasResponse - ? new - { - ResponseType = delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType, - ResponseIsStream = delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType - == TypeConstants.Stream, - } - : null; - - // determine if event feature is required - var isEventFeatureRequired = inputEvent is { IsStream: false }; - - // determine if response feature is required - var isResponseFeatureRequired = outputResponse is { ResponseIsStream: false }; - - return new - { - LocatioOpn = mapHandlerInvocationInfo.InterceptableLocationInfo, - HandlerSignature = handlerSignature, - IsEventFeatureRequired = isEventFeatureRequired, - IsResponseFeatureRequired = isResponseFeatureRequired, - delegateInfo.HasAnyKeyedServiceParameter, - HandlerArgs = handlerArgs, - ShouldAwait = delegateInfo.IsAwaitable, - InputEvent = inputEvent, - OutputResponse = outputResponse, - }; - }); + // var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandler => + // { + // return new + // { + // InterceptableLocationAttribute = + // mapHandler.InterceptableLocationInfo.ToInterceptsLocationAttribute(), + // HandlerSignature = handlerSignature, + // IsEventFeatureRequired = isEventFeatureRequired, + // IsResponseFeatureRequired = isResponseFeatureRequired, + // delegateInfo.HasAnyKeyedServiceParameter, + // HandlerArgs = handlerArgs, + // ShouldAwait = delegateInfo.IsAwaitable, + // InputEvent = inputEvent, + // OutputResponse = outputResponse, + // }; + // }); var template = TemplateHelper.LoadTemplate( GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile ); return template.Render( - new { MinimalLambdaEmitter.GeneratedCodeAttribute, MapHandlerCalls = mapHandlerCalls } + new + { + MinimalLambdaEmitter.GeneratedCodeAttribute, + MapHandlerCalls = mapHandlerInvocationInfos, + } ); } - - private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) => - delegateInfo - .Parameters.Select(param => new HandlerArg - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // Event -> deserialize to type - ParameterSource.Event - when param.TypeInfo.FullyQualifiedType == TypeConstants.Stream => - "context.Features.GetRequired().EventStream", - - ParameterSource.Event => - $"context.GetRequiredEvent<{param.TypeInfo.FullyQualifiedType}>()", - - // ILambdaContext OR ILambdaInvocationContext -> use context - // directly - ParameterSource.HostContext => "context", - - // CancellationToken -> get from context - ParameterSource.CancellationToken => "context.CancellationToken", - - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => - $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }) - .ToArray(); - - // ReSharper disable NotAccessedPositionalProperty.Local - private readonly record struct HandlerArg(string String, string Assignment); } diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs similarity index 61% rename from src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs rename to src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 6733723d..4b1a1288 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo2.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -1,82 +1,17 @@ -using System; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using MinimalLambda.SourceGenerators; using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.Models; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; -namespace MinimalLambda.SourceGenerators.Models; +namespace Microsoft.CodeAnalysis; -internal readonly record struct ParameterInfo2(string Assignment, string InfoComment); - -internal static class ParameterInfo2Extensions +internal static class ParameterSymbolExtensions { - private static Func> Success(string infoComment) - { - var info = new ParameterInfo2 { InfoComment = infoComment }; - return assignment => - DiagnosticResult.Success(info with { Assignment = assignment }); - } - - extension(ParameterInfo2) - { - internal static DiagnosticResult CreateForInvocationHandler( - IParameterSymbol parameter, - GeneratorContext context - ) - { - var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); - var lambdaContext = context.WellKnownTypes.Get( - WellKnownType.Amazon_Lambda_Core_ILambdaContext - ); - var lambdaInvocationContext = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_ILambdaInvocationContext - ); - var cancellationToken = context.WellKnownTypes.Get( - WellKnownType.System_Threading_CancellationToken - ); - - var paramType = parameter.Type.ToGloballyQualifiedName(); - - var success = Success(""); - - // event - if (parameter.IsFromEvent(context)) - return success( - SymbolEqualityComparer.Default.Equals(parameter.Type, stream) - // stream event - ? "context.Features.GetRequired().EventStream" - // non stream event - : $"context.GetRequiredEvent<{paramType}>()" - ); - - // context - if ( - SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) - || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) - ) - return success("context"); - - // cancellation token - if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) - return success("context.CancellationToken"); - - // default assignment from Di - return parameter - .GetDiParameterAssignment(context) - .Bind(assignment => success(assignment)); - } - - internal static DiagnosticResult CreateForLifecycleHandler( - IParameterSymbol parameter, - GeneratorContext context - ) => throw new NotImplementedException(); - } - extension(IParameterSymbol parameterSymbol) { - private bool IsFromEvent(GeneratorContext context) + internal bool IsFromEvent(GeneratorContext context) { var eventAttr = context.WellKnownTypes.Get( WellKnownType.MinimalLambda_Builder_EventAttribute @@ -101,7 +36,7 @@ private bool IsFromEvent(GeneratorContext context) }); } - private bool IsFromKeyedService( + internal bool IsFromKeyedService( GeneratorContext context, out DiagnosticResult? keyResult ) @@ -130,7 +65,7 @@ out DiagnosticResult? keyResult return false; } - private DiagnosticResult GetDiParameterAssignment(GeneratorContext context) + internal DiagnosticResult GetDiParameterAssignment(GeneratorContext context) { var paramType = parameterSymbol.Type.ToGloballyQualifiedName(); @@ -177,7 +112,7 @@ private DiagnosticResult ExtractKeyedServiceKey() if (value is null) return DiagnosticResult.Failure( - Diagnostics.InvalidAttributeArgument, + MinimalLambda.SourceGenerators.Diagnostics.InvalidAttributeArgument, attributeData.GetAttributeArgumentLocation(0), argument.Type?.ToGloballyQualifiedName() ); @@ -186,7 +121,7 @@ private DiagnosticResult ExtractKeyedServiceKey() argument.Kind switch { TypedConstantKind.Primitive when value is string strValue => - SymbolDisplay.FormatLiteral(strValue, true), + CSharp.SymbolDisplay.FormatLiteral(strValue, true), TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 103d031f..1b9b0072 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -109,13 +109,13 @@ is CSharpCompilation return new CompilationInfo { MapHandlerInvocationInfos = handlerInfos - .Where(h => h.Name == "MapHandler") + .Where(h => h.MethodType == MethodType.MapHandler) .ToEquatableArray(), OnShutdownInvocationInfos = handlerInfos - .Where(h => h.Name == "OnShutdown") + .Where(h => h.MethodType == MethodType.OnShutdown) .ToEquatableArray(), OnInitInvocationInfos = handlerInfos - .Where(h => h.Name == "OnInit") + .Where(h => h.MethodType == MethodType.OnInit) .ToEquatableArray(), UseMiddlewareTInfos = useMiddlewareInfo.ToEquatableArray(), }; diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 276c3c0c..b611568e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,42 +1,56 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; using MinimalLambda.SourceGenerators.WellKnownTypes; -using ParameterAssigner = System.Func< - Microsoft.CodeAnalysis.IParameterSymbol, - MinimalLambda.SourceGenerators.GeneratorContext, - MinimalLambda.SourceGenerators.Models.DiagnosticResult ->; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; +internal enum MethodType +{ + MapHandler, + OnInit, + OnShutdown, +} + +internal interface IMethodInfo +{ + MethodType MethodType { get; } + InterceptableLocationInfo InterceptableLocationInfo { get; } + string DelegateCastType { get; } + bool IsAwaitable { get; } + bool HasResponse { get; } + bool HasAnyFromKeyedServices { get; } + EquatableArray DiagnosticInfos { get; } +} + internal readonly record struct HigherOrderMethodInfo( - string Name, - DelegateInfo DelegateInfo, - LocationInfo? LocationInfo, InterceptableLocationInfo InterceptableLocationInfo, - ImmutableArray ArgumentsInfos, - // ── New ────────────────────────────────────────────────────────────────────────── - string DelegateCastType = "", - EquatableArray ParameterAssignments = default, - bool IsAwaitable = false, - bool HasReturnType = false, - bool IsReturnTypeStream = false, - bool IsReturnTypeBool = false, - EquatableArray DiagnosticInfos = default -); + string InterceptableLocationAttribute, + string DelegateCastType, + EquatableArray ParameterAssignments, + bool IsAwaitable, + bool HasResponse, + bool IsResponseTypeStream, + bool IsResponseTypeBool, + bool IsEventTypeStream, + bool HasEvent, + string? EventType, + string? ResponseType, + bool HasAnyFromKeyedServices, + EquatableArray DiagnosticInfos, + MethodType MethodType = MethodType.MapHandler +) : IMethodInfo; internal static class HigherOrderMethodInfoExtensions { extension(HigherOrderMethodInfo) { - internal static HigherOrderMethodInfo? Create( + internal static HigherOrderMethodInfo Create( IMethodSymbol methodSymbol, GeneratorContext context ) @@ -49,20 +63,11 @@ GeneratorContext context if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) throw new InvalidOperationException("Unable to get interceptable location"); - ParameterAssigner getParameterAssignments = methodName switch - { - "MapHandler" => ParameterInfo2.CreateForInvocationHandler, - "OnInit" or "OnShutdown" => ParameterInfo2.CreateForLifecycleHandler, - _ => throw new InvalidOperationException( - $"Handler with name '{methodName}' is not valid" - ), - }; - var (assignments, diagnostics) = methodSymbol - .Parameters.Select(parameter => getParameterAssignments(parameter, context)) + .Parameters.Select(parameter => MapHandlerParameterInfo.Create(parameter, context)) .Aggregate( ( - Successes: new List(), + Successes: new List(), Diagnostics: new List() ), static (acc, result) => @@ -79,35 +84,49 @@ GeneratorContext context ); var isAwaitable = methodSymbol.IsAwaitable(context); - var hasReturnType = methodSymbol.HasMeaningfulReturnType(context); + var hasResponse = methodSymbol.HasMeaningfulReturnType(context); var isReturnTypeStream = - hasReturnType + hasResponse && context.WellKnownTypes.IsTypeMatch( methodSymbol.ReturnType, WellKnownType.System_IO_Stream ); var isReturnTypeBool = - hasReturnType + hasResponse && !isReturnTypeStream && context.WellKnownTypes.IsTypeMatch( methodSymbol.ReturnType, WellKnownType.System_Boolean ); + var hasEvent = assignments.Any(a => a.IsEvent); + var eventType = hasEvent + ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() + : null; + var responseType = hasResponse + ? methodSymbol.ReceiverType!.ToGloballyQualifiedName() + : null; + var isEventTypeStream = + hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); return new HigherOrderMethodInfo { - Name = methodName!, - DelegateInfo = default, - LocationInfo = null, InterceptableLocationInfo = interceptableLocation.Value, - ArgumentsInfos = default, + InterceptableLocationAttribute = + interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType = handlerCastType, ParameterAssignments = assignments, IsAwaitable = isAwaitable, - HasReturnType = hasReturnType, - IsReturnTypeStream = isReturnTypeStream, - IsReturnTypeBool = isReturnTypeBool, + HasResponse = hasResponse, + IsResponseTypeStream = isReturnTypeStream, + IsResponseTypeBool = isReturnTypeBool, + IsEventTypeStream = isEventTypeStream, + HasEvent = hasEvent, + EventType = eventType, + ResponseType = responseType, + HasAnyFromKeyedServices = hasAnyKeyedServices, DiagnosticInfos = diagnostics, + MethodType = MethodType.MapHandler, }; } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs new file mode 100644 index 00000000..750b97ab --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -0,0 +1,97 @@ +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal readonly record struct MapHandlerParameterInfo( + string GloballyQualifiedType, + bool IsStream, + string Assignment, + string InfoComment, + bool IsEvent, + bool IsFromKeyedService +); + +internal static class MapHandlerParameterInfoExtensions +{ + extension(MapHandlerParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameter, + GeneratorContext context + ) + { + var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); + var lambdaContext = context.WellKnownTypes.Get( + WellKnownType.Amazon_Lambda_Core_ILambdaContext + ); + var lambdaInvocationContext = context.WellKnownTypes.Get( + WellKnownType.MinimalLambda_ILambdaInvocationContext + ); + var cancellationToken = context.WellKnownTypes.Get( + WellKnownType.System_Threading_CancellationToken + ); + + var isStream = parameter.Type.Equals(stream, SymbolEqualityComparer.Default); + + var paramType = parameter.Type.ToGloballyQualifiedName(); + + var parameterInfo = new MapHandlerParameterInfo + { + GloballyQualifiedType = parameter.Type.ToGloballyQualifiedName(), + IsStream = isStream, + IsEvent = false, + IsFromKeyedService = false, + }; + + // event + if (parameter.IsFromEvent(context)) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = isStream + // stream event + ? "context.Features.GetRequired().EventStream" + // non stream event + : $"context.GetRequiredEvent<{paramType}>()", + IsEvent = true, + } + ); + + // context + if ( + SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) + || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context", + } + ); + + // cancellation token + if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context.CancellationToken", + } + ); + + // default assignment from Di + return parameter + .GetDiParameterAssignment(context) + .Bind(assignment => + DiagnosticResult.Success( + parameterInfo with + { + Assignment = assignment, + IsFromKeyedService = true, + } + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs index 3978e5bc..17982ad7 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs @@ -76,11 +76,12 @@ is not IInvocationOperation )!; return new HigherOrderMethodInfo( - targetOperation.TargetMethod.Name, - LocationInfo: context.Node.CreateLocationInfo(), - DelegateInfo: delegateInfo.Value, - InterceptableLocationInfo: InterceptableLocationInfo.CreateFrom(interceptableLocation), - ArgumentsInfos: argumentInfos + // targetOperation.TargetMethod.Name, + // LocationInfo: context.Node.CreateLocationInfo(), + // DelegateInfo: delegateInfo.Value, + // InterceptableLocationInfo: + // InterceptableLocationInfo.CreateFrom(interceptableLocation), + // ArgumentsInfos: argumentInfos ); } diff --git a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban index 65412123..92d531c0 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban @@ -5,57 +5,56 @@ private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; {{~ for call in map_handler_calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] + {{ call.interceptable_location_attribute }} internal static ILambdaInvocationBuilder MapHandlerInterceptor{{ for.index }}( this ILambdaInvocationBuilder application, Delegate handler ) { - var castHandler = {{ call.handler_signature }}; + var castHandler = Utilities.Cast(handler, {{ call.delegate_cast_type }}); application.Handle(InvocationDelegate); - {{~ if call.is_event_feature_required ~}} + {{~ if call.has_event && !call.is_event_type_stream ~}} if (!application.Properties.ContainsKey(EventFeatureProviderKey)) application.Properties[EventFeatureProviderKey] = application .Services.GetRequiredService() - .Create<{{ call.input_event.type }}>(); + .Create<{{ call.event_type }}>(); {{~ end ~}} - {{~ if call.is_response_feature_required ~}} + {{~ if call.has_response && !call.is_response_type_stream ~}} if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create<{{ call.output_response.response_type }}>(); + .Create<{{ call.response_type }}>(); {{~ end ~}} return application; - {{ if call.should_await ~}} async {{ end ~}}Task InvocationDelegate(ILambdaInvocationContext context) + {{ if call.is_awaitable ~}} async {{ end ~}}Task InvocationDelegate(ILambdaInvocationContext context) { - {{~ if call.has_any_keyed_service_parameter ~}} + {{~ if call.has_any_from_keyed_services ~}} if (context.ServiceProvider.GetService() is not IServiceProviderIsKeyedService) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } {{~ end ~}} - {{~ for handler_arg in call.handler_args ~}} - // {{ handler_arg.string }} + {{~ for handler_arg in call.parameter_assignments ~}} var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} - {{ if call.output_response != null; ~}} var response = {{ end }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.handler_args }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); - {{~ if call.output_response != null; ~}} - {{~ if call.output_response.response_is_stream ~}} + {{ if call.has_response != null; ~}} var response = {{ end }}{{ if call.is_awaitable ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{~ if call.has_response ~}} + {{~ if call.is_response_type_stream ~}} context.Features.GetRequired().ResponseStream = response; {{~ else ~}} - if (context.Features.Get() is not IResponseFeature<{{ call.output_response.response_type }}> responseFeature) + if (context.Features.Get() is not IResponseFeature<{{ call.response_type }}> responseFeature) { - throw new InvalidOperationException($"Response feature for type '{{ call.output_response.response_type }}' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type '{{ call.response_type }}' is not available in the collection."); } responseFeature.SetResponse(response); {{~ end ~}} {{~ end ~}} - {{~ if !call.should_await ~}} + {{~ if !call.is_awaitable ~}} return Task.CompletedTask; {{~ end ~}} } From 5fd92849824671f351bc7726509a83723f1fee22 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 19:35:01 -0500 Subject: [PATCH 19/67] refactor(source-generators): comment out unused code to improve clarity and reduce redundancy - Commented out redundant parameter handling logic in `GenericHandlerSources` and related code blocks. - Simplified `DiagnosticGenerator` by commenting out unused diagnostic generation logic. - Adjusted minor formatting issues in `InterceptableLocationInfo` and `HigherOrderMethodInfo`. - Improved maintainability by isolating unused logic for future iteration or removal. --- .../Diagnostics/DiagnosticGenerator.cs | 168 ++++++------- .../Emitters/GenericHandlerSources.cs | 223 +++++++++--------- .../Models/HigherOrderMethodInfo.cs | 2 +- .../Models/InterceptableLocationInfo.cs | 2 +- .../Templates/MapHandler.scriban | 2 +- 5 files changed, 200 insertions(+), 197 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 5cb8ae21..98f170a7 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Models; @@ -12,90 +10,92 @@ internal static List GenerateDiagnostics(CompilationInfo compilation { var diagnostics = new List(); - var delegateInfos = compilationInfo.MapHandlerInvocationInfos; - - // Validate parameters - foreach (var invocationInfo in delegateInfos) - // check for multiple parameters that use the `[FromEvent]` attribute - if ( - invocationInfo.DelegateInfo.Parameters.Count(p => p.Source == ParameterSource.Event) - > 1 - ) - diagnostics.AddRange( - invocationInfo - .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) - .Select(p => - Diagnostic.Create( - Diagnostics.MultipleParametersUseAttribute, - p.LocationInfo?.ToLocation(), - AttributeConstants.FromEventAttribute - ) - ) - ); - - // check for invalid keyed service usage - MapHandler - diagnostics.AddRange( - compilationInfo.MapHandlerInvocationInfos.GenerateKeyedServiceKeyDiagnostics() - ); - - // check for invalid keyed service usage - OnShutdown - diagnostics.AddRange( - compilationInfo.OnShutdownInvocationInfos.GenerateKeyedServiceKeyDiagnostics() - ); - - foreach (var useMiddlewareTInfo in compilationInfo.UseMiddlewareTInfos) - { - // ensure middleware class is concrete - if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") - { - diagnostics.Add( - Diagnostic.Create( - Diagnostics.MustBeConcreteType, - useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), - useMiddlewareTInfo.ClassInfo.ShortName - ) - ); - } - - // validate that middleware class constructors only use `[MiddlewareConstructor]` once - diagnostics.AddRange( - useMiddlewareTInfo - .ClassInfo.ConstructorInfos.Where(c => - c.AttributeInfos.Any(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - ) - .Skip(1) - .Select(c => - Diagnostic.Create( - Diagnostics.MultipleConstructorsWithAttribute, - c.AttributeInfos.First(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - .LocationInfo?.ToLocation(), - AttributeConstants.MiddlewareConstructor - ) - ) - ); - } + // var delegateInfos = compilationInfo.MapHandlerInvocationInfos; + // + // // // Validate parameters + // // foreach (var invocationInfo in delegateInfos) + // // // check for multiple parameters that use the `[FromEvent]` attribute + // // if ( + // // invocationInfo.DelegateInfo.Parameters.Count(p => p.Source == + // ParameterSource.Event) + // // > 1 + // // ) + // // diagnostics.AddRange( + // // invocationInfo + // // .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) + // // .Select(p => + // // Diagnostic.Create( + // // Diagnostics.MultipleParametersUseAttribute, + // // p.LocationInfo?.ToLocation(), + // // AttributeConstants.FromEventAttribute + // // ) + // // ) + // // ); + // + // // check for invalid keyed service usage - MapHandler + // diagnostics.AddRange( + // compilationInfo.MapHandlerInvocationInfos.GenerateKeyedServiceKeyDiagnostics() + // ); + // + // // check for invalid keyed service usage - OnShutdown + // diagnostics.AddRange( + // compilationInfo.OnShutdownInvocationInfos.GenerateKeyedServiceKeyDiagnostics() + // ); + // + // foreach (var useMiddlewareTInfo in compilationInfo.UseMiddlewareTInfos) + // { + // // ensure middleware class is concrete + // if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") + // { + // diagnostics.Add( + // Diagnostic.Create( + // Diagnostics.MustBeConcreteType, + // useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), + // useMiddlewareTInfo.ClassInfo.ShortName + // ) + // ); + // } + // + // // validate that middleware class constructors only use `[MiddlewareConstructor]` + // once + // diagnostics.AddRange( + // useMiddlewareTInfo + // .ClassInfo.ConstructorInfos.Where(c => + // c.AttributeInfos.Any(a => + // a.FullName == AttributeConstants.MiddlewareConstructor + // ) + // ) + // .Skip(1) + // .Select(c => + // Diagnostic.Create( + // Diagnostics.MultipleConstructorsWithAttribute, + // c.AttributeInfos.First(a => + // a.FullName == AttributeConstants.MiddlewareConstructor + // ) + // .LocationInfo?.ToLocation(), + // AttributeConstants.MiddlewareConstructor + // ) + // ) + // ); + // } return diagnostics; } - private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( - this EquatableArray methodNameInfos - ) => - methodNameInfos - .SelectMany(onShutdownInvocationInfo => - onShutdownInvocationInfo.DelegateInfo.Parameters - ) - .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) - .Select(parameterInfo => - Diagnostic.Create( - Diagnostics.InvalidAttributeArgument, - parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), - parameterInfo.KeyedServiceKey.Value.Type - ) - ) - .ToArray(); + // private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( + // this EquatableArray methodNameInfos + // ) => + // methodNameInfos + // .SelectMany(onShutdownInvocationInfo => + // onShutdownInvocationInfo.DelegateInfo.Parameters + // ) + // .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) + // .Select(parameterInfo => + // Diagnostic.Create( + // Diagnostics.InvalidAttributeArgument, + // parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), + // parameterInfo.KeyedServiceKey.Value.Type + // ) + // ) + // .ToArray(); } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index a31a748a..dc31d35f 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -1,6 +1,4 @@ -using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Extensions; using MinimalLambda.SourceGenerators.Models; namespace MinimalLambda.SourceGenerators; @@ -20,80 +18,81 @@ internal static string Generate( string targetType ) { - var calls = higherOrderMethodInfos - .Select(higherOrderMethodInfo => - { - // build handler function signature - var handlerSignature = higherOrderMethodInfo.DelegateInfo.BuildHandlerCastCall(); - - // get arguments for handler - var handlerArgs = - higherOrderMethodInfo.DelegateInfo.BuildHandlerParameterAssignment(); - - // get the return type of the wrapper function wrapped in a Task - var fullWrapperReturnType = wrapperReturnType is not null - ? $"global::System.Threading.Tasks.Task<{wrapperReturnType}>" - : "global::System.Threading.Tasks.Task"; - - // get the return type of the wrapper function wrapped in a Task - shortened - // to just use Task - var shortFullWrapperReturnType = wrapperReturnType is not null - ? $"Task<{wrapperReturnType}>" - : "Task"; - - // should await determined by whether the delegate is awaitable and if the - // delegate - // return type matches the wrapper return type 1:1 - var shouldAwait = - fullWrapperReturnType - != higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - && higherOrderMethodInfo.DelegateInfo.IsAwaitable; - - // should return response - var shouldReturnResponse = - higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - != TypeConstants.Void - && ( - wrapperReturnType - == higherOrderMethodInfo - .DelegateInfo - .ReturnTypeInfo - .UnwrappedFullyQualifiedType - || fullWrapperReturnType - == higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - ); - - // should wrap the response in a Task - var shouldWrapResponse = - shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.IsAwaitable; - - // default return value - var defaultReturnValueString = !shouldAwait - ? defaultWrapperReturnValue is not null - ? $"Task.FromResult({defaultWrapperReturnValue})" - : "Task.CompletedTask" - : defaultWrapperReturnValue; - - return new - { - Location = higherOrderMethodInfo.InterceptableLocationInfo, - WrapperReturnType = shortFullWrapperReturnType, - HandlerSignature = handlerSignature, - ShouldAwait = shouldAwait, - higherOrderMethodInfo.DelegateInfo.HasAnyKeyedServiceParameter, - HandlerArgs = handlerArgs, - ShouldReturnResponse = shouldReturnResponse, - ShouldWrapResponse = shouldWrapResponse, - DefaultReturnValue = defaultReturnValueString, - TargetType = targetType, - }; - }) - .ToArray(); + // var calls = higherOrderMethodInfos + // .Select(higherOrderMethodInfo => + // { + // // build handler function signature + // var handlerSignature = higherOrderMethodInfo.DelegateInfo.BuildHandlerCastCall(); + // + // // get arguments for handler + // var handlerArgs = + // higherOrderMethodInfo.DelegateInfo.BuildHandlerParameterAssignment(); + // + // // get the return type of the wrapper function wrapped in a Task + // var fullWrapperReturnType = wrapperReturnType is not null + // ? $"global::System.Threading.Tasks.Task<{wrapperReturnType}>" + // : "global::System.Threading.Tasks.Task"; + // + // // get the return type of the wrapper function wrapped in a Task - shortened + // // to just use Task + // var shortFullWrapperReturnType = wrapperReturnType is not null + // ? $"Task<{wrapperReturnType}>" + // : "Task"; + // + // // should await determined by whether the delegate is awaitable and if the + // // delegate + // // return type matches the wrapper return type 1:1 + // var shouldAwait = + // fullWrapperReturnType + // != higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType + // && higherOrderMethodInfo.DelegateInfo.IsAwaitable; + // + // // should return response + // var shouldReturnResponse = + // higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType + // != TypeConstants.Void + // && ( + // wrapperReturnType + // == higherOrderMethodInfo + // .DelegateInfo + // .ReturnTypeInfo + // .UnwrappedFullyQualifiedType + // || fullWrapperReturnType + // == + // higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType + // ); + // + // // should wrap the response in a Task + // var shouldWrapResponse = + // shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.IsAwaitable; + // + // // default return value + // var defaultReturnValueString = !shouldAwait + // ? defaultWrapperReturnValue is not null + // ? $"Task.FromResult({defaultWrapperReturnValue})" + // : "Task.CompletedTask" + // : defaultWrapperReturnValue; + // + // return new + // { + // Location = higherOrderMethodInfo.InterceptableLocationInfo, + // WrapperReturnType = shortFullWrapperReturnType, + // HandlerSignature = handlerSignature, + // ShouldAwait = shouldAwait, + // higherOrderMethodInfo.DelegateInfo.HasAnyKeyedServiceParameter, + // HandlerArgs = handlerArgs, + // ShouldReturnResponse = shouldReturnResponse, + // ShouldWrapResponse = shouldWrapResponse, + // DefaultReturnValue = defaultReturnValueString, + // TargetType = targetType, + // }; + // }) + // .ToArray(); var model = new { Name = methodName, - Calls = calls, + // Calls = calls, MinimalLambdaEmitter.GeneratedCodeAttribute, }; @@ -104,42 +103,46 @@ string targetType return outCode; } - private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) - { - var handlerArgs = delegateInfo - .Parameters.Select(param => new HandlerArg - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // CancellationToken -> get directly from arguments - ParameterSource.CancellationToken => "context.CancellationToken", - - // ILambdaLifecycleContext -> get directly from arguments - ParameterSource.LifecycleContext => "context", - - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => - $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }) - .ToArray(); - - return handlerArgs; - } - - // ReSharper disable NotAccessedPositionalProperty.Local - private readonly record struct HandlerArg(string String, string Assignment); + // private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) + // { + // var handlerArgs = delegateInfo + // .Parameters.Select(param => new HandlerArg + // { + // String = param.ToPublicString(), + // Assignment = param.Source switch + // { + // // CancellationToken -> get directly from arguments + // ParameterSource.CancellationToken => "context.CancellationToken", + // + // // ILambdaLifecycleContext -> get directly from arguments + // ParameterSource.LifecycleContext => "context", + // + // // inject keyed service from the DI container - required + // ParameterSource.KeyedService when param.IsRequired => + // + // $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", + // + // // inject keyed service from the DI container - optional + // ParameterSource.KeyedService => + // + // $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", + // + // // default: inject service from the DI container - required + // _ when param.IsRequired => + // + // $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", + // + // // default: inject service from the DI container - optional + // _ => + // + // $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", + // }, + // }) + // .ToArray(); + // + // return handlerArgs; + // } + // + // // ReSharper disable NotAccessedPositionalProperty.Local + // private readonly record struct HandlerArg(string String, string Assignment); } diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index b611568e..b873b02e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -103,7 +103,7 @@ GeneratorContext context ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() : null; var responseType = hasResponse - ? methodSymbol.ReceiverType!.ToGloballyQualifiedName() + ? methodSymbol.ReturnType.ToGloballyQualifiedName() : null; var isEventTypeStream = hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs index 0339b727..e9b346b7 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs @@ -46,6 +46,6 @@ internal static bool TryGet( } internal string ToInterceptsLocationAttribute() => - $""" [InterceptsLocation({location.Version}, "{location.Data}")]"""; + $"""[InterceptsLocation({location.Version}, "{location.Data}")]"""; } } diff --git a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban index 92d531c0..da620a90 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban @@ -42,7 +42,7 @@ {{~ for handler_arg in call.parameter_assignments ~}} var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} - {{ if call.has_response != null; ~}} var response = {{ end }}{{ if call.is_awaitable ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{ if call.has_response ~}} var response = {{ end }}{{ if call.is_awaitable ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); {{~ if call.has_response ~}} {{~ if call.is_response_type_stream ~}} context.Features.GetRequired().ResponseStream = response; From aaafc18063ca967f87e5174c99fe648df7204677 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 20:13:48 -0500 Subject: [PATCH 20/67] feat(source-generators): enhance `MapHandler` logic and improve nullable handling - Updated `MapHandler.scriban` to use unwrapped response types for improved consistency. - Refactored parameter assignment in `MapHandlerParameterInfo` to handle keyed services more effectively. - Introduced `UnwrapReturnType` in `MethodSymbolExtensions` to simplify handling of task-based return types. - Adjusted generated snapshot files for nullable input and response type scenarios. - Simplified response feature resolution by standardizing on unwrapped types. - Enhanced `HigherOrderMethodInfo` with proper unwrapped response type data. - Updated snapshot tests to cover new nullable and unwrapped type handling. --- .../Extensions/MethodSymbolExtensions.cs | 26 ++++++ .../Extensions/ParameterSymbolExtensions.cs | 34 +++++--- .../Models/HigherOrderMethodInfo.cs | 18 ++-- .../Models/MapHandlerParameterInfo.cs | 6 +- .../Templates/MapHandler.scriban | 6 +- ...plicitNullable#LambdaHandler.g.verified.cs | 10 +-- ...nOverload_NoOp#LambdaHandler.g.verified.cs | 43 ---------- ...plicitNullable#LambdaHandler.g.verified.cs | 10 +-- ...lableValueType#LambdaHandler.g.verified.cs | 86 +++++++++++++++++++ ...lInjectedParam#LambdaHandler.g.verified.cs | 80 +++++++++++++++++ .../ExpressionLambdaVerifyTests.cs | 27 +++++- 11 files changed, 270 insertions(+), 76 deletions(-) delete mode 100644 tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs create mode 100644 tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs create mode 100644 tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs index c951c9cd..f5d6471f 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -86,5 +86,31 @@ internal bool HasMeaningfulReturnType(GeneratorContext context) return true; } + + internal ITypeSymbol UnwrapReturnType(GeneratorContext context) + { + if (methodSymbol.ReturnType is not INamedTypeSymbol namedReturnType) + return (INamedTypeSymbol)methodSymbol.ReturnType; + + var taskOfT = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T + ); + var valueTaskOfT = context.WellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask_T + ); + + var originalDef = namedReturnType.OriginalDefinition; + + if ( + ( + originalDef.Equals(taskOfT, SymbolEqualityComparer.Default) + || originalDef.Equals(valueTaskOfT, SymbolEqualityComparer.Default) + ) + && namedReturnType.TypeArguments.Length > 0 + ) + return namedReturnType.TypeArguments[0]; + + return namedReturnType; + } } } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 4b1a1288..71fa5311 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -65,28 +65,40 @@ out DiagnosticResult? keyResult return false; } - internal DiagnosticResult GetDiParameterAssignment(GeneratorContext context) + internal DiagnosticResult<(string Assignment, bool IsKeyed)> GetDiParameterAssignment( + GeneratorContext context + ) { var paramType = parameterSymbol.Type.ToGloballyQualifiedName(); var isKeyedServices = parameterSymbol.IsFromKeyedService(context, out var keyResult); + var isRequired = + parameterSymbol.IsOptional + || parameterSymbol.NullableAnnotation == NullableAnnotation.Annotated; + // keyed services if (isKeyedServices) return keyResult!.Bind(key => - DiagnosticResult.Success( - parameterSymbol.IsOptional - ? $"context.ServiceProvider.GetKeyedService<{paramType}>({key})" - : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})" + DiagnosticResult<(string, bool)>.Success( + ( + isRequired + ? $"context.ServiceProvider.GetKeyedService<{paramType}>({key})" + : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})", + true + ) ) ); - return DiagnosticResult.Success( - parameterSymbol.IsOptional - // default - inject from DI - optional - ? $"context.ServiceProvider.GetService<{paramType}>()" - // default - inject required from DI - : $"context.ServiceProvider.GetRequiredService<{paramType}>()" + return DiagnosticResult<(string, bool)>.Success( + ( + isRequired + // default - inject from DI - optional + ? $"context.ServiceProvider.GetService<{paramType}>()" + // default - inject required from DI + : $"context.ServiceProvider.GetRequiredService<{paramType}>()", + false + ) ); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index b873b02e..af36a311 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -40,7 +40,7 @@ internal readonly record struct HigherOrderMethodInfo( bool IsEventTypeStream, bool HasEvent, string? EventType, - string? ResponseType, + string? UnwrappedResponseType, bool HasAnyFromKeyedServices, EquatableArray DiagnosticInfos, MethodType MethodType = MethodType.MapHandler @@ -84,13 +84,16 @@ GeneratorContext context ); var isAwaitable = methodSymbol.IsAwaitable(context); + var hasResponse = methodSymbol.HasMeaningfulReturnType(context); + var isReturnTypeStream = hasResponse && context.WellKnownTypes.IsTypeMatch( methodSymbol.ReturnType, WellKnownType.System_IO_Stream ); + var isReturnTypeBool = hasResponse && !isReturnTypeStream @@ -98,17 +101,22 @@ GeneratorContext context methodSymbol.ReturnType, WellKnownType.System_Boolean ); + var hasEvent = assignments.Any(a => a.IsEvent); + var eventType = hasEvent ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() : null; - var responseType = hasResponse - ? methodSymbol.ReturnType.ToGloballyQualifiedName() - : null; + var isEventTypeStream = hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + var unwrappedReturnType = methodSymbol + .UnwrapReturnType(context) + .ToGloballyQualifiedName(); + return new HigherOrderMethodInfo { InterceptableLocationInfo = interceptableLocation.Value, @@ -123,7 +131,7 @@ GeneratorContext context IsEventTypeStream = isEventTypeStream, HasEvent = hasEvent, EventType = eventType, - ResponseType = responseType, + UnwrappedResponseType = unwrappedReturnType, HasAnyFromKeyedServices = hasAnyKeyedServices, DiagnosticInfos = diagnostics, MethodType = MethodType.MapHandler, diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index 750b97ab..c65a104b 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -83,12 +83,12 @@ parameterInfo with // default assignment from Di return parameter .GetDiParameterAssignment(context) - .Bind(assignment => + .Bind(diInfo => DiagnosticResult.Success( parameterInfo with { - Assignment = assignment, - IsFromKeyedService = true, + Assignment = diInfo.Assignment, + IsFromKeyedService = diInfo.IsKeyed, } ) ); diff --git a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban index da620a90..de5291b1 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban @@ -26,7 +26,7 @@ if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create<{{ call.response_type }}>(); + .Create<{{ call.unwrapped_response_type }}>(); {{~ end ~}} return application; @@ -47,9 +47,9 @@ {{~ if call.is_response_type_stream ~}} context.Features.GetRequired().ResponseStream = response; {{~ else ~}} - if (context.Features.Get() is not IResponseFeature<{{ call.response_type }}> responseFeature) + if (context.Features.Get() is not IResponseFeature<{{ call.unwrapped_response_type }}> responseFeature) { - throw new InvalidOperationException($"Response feature for type '{{ call.response_type }}' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type '{{ call.unwrapped_response_type }}' is not available in the collection."); } responseFeature.SetResponse(response); {{~ end ~}} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs index ffc90dfe..6a1fa381 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -48,7 +48,7 @@ internal static ILambdaInvocationBuilder MapHandlerInterceptor0( Delegate handler ) { - var castHandler = Utilities.Cast(handler, string? (string arg0, global::IService arg1) => throw null!); + var castHandler = Utilities.Cast(handler, string (string arg0, global::IService arg1) => throw null!); application.Handle(InvocationDelegate); @@ -60,7 +60,7 @@ Delegate handler if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create(); + .Create(); return application; @@ -69,9 +69,9 @@ Task InvocationDelegate(ILambdaInvocationContext context) var arg0 = context.GetRequiredEvent(); var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); - if (context.Features.Get() is not IResponseFeature responseFeature) + if (context.Features.Get() is not IResponseFeature responseFeature) { - throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type 'string' is not available in the collection."); } responseFeature.SetResponse(response); return Task.CompletedTask; @@ -83,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs index ebe652d7..f6e58e3c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -48,7 +48,7 @@ internal static ILambdaInvocationBuilder MapHandlerInterceptor0( Delegate handler ) { - var castHandler = Utilities.Cast(handler, string? (string? arg0, global::IService arg1) => throw null!); + var castHandler = Utilities.Cast(handler, string (string? arg0, global::IService arg1) => throw null!); application.Handle(InvocationDelegate); @@ -60,7 +60,7 @@ Delegate handler if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create(); + .Create(); return application; @@ -69,9 +69,9 @@ Task InvocationDelegate(ILambdaInvocationContext context) var arg0 = context.GetRequiredEvent(); var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); - if (context.Features.Get() is not IResponseFeature responseFeature) + if (context.Features.Get() is not IResponseFeature responseFeature) { - throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type 'string' is not available in the collection."); } responseFeature.SetResponse(response); return Task.CompletedTask; @@ -83,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs new file mode 100644 index 00000000..e1ef8a9a --- /dev/null +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs @@ -0,0 +1,86 @@ +//HintName: LambdaHandler.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +#nullable enable + +namespace System.Runtime.CompilerServices +{ + using System.CodeDom.Compiler; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace MinimalLambda.Generated +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using MinimalLambda; + using MinimalLambda.Builder; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + file static class GeneratedLambdaInvocationBuilderExtensions + { + private const string EventFeatureProviderKey = "__EventFeatureProvider"; + private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; + + [InterceptsLocation(1, "REPLACED")] + internal static ILambdaInvocationBuilder MapHandlerInterceptor0( + this ILambdaInvocationBuilder application, + Delegate handler + ) + { + var castHandler = Utilities.Cast(handler, global::MyStruct? (int? arg0, global::IService arg1) => throw null!); + + application.Handle(InvocationDelegate); + + if (!application.Properties.ContainsKey(EventFeatureProviderKey)) + application.Properties[EventFeatureProviderKey] = application + .Services.GetRequiredService() + .Create(); + + if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) + application.Properties[ResponseFeatureProviderKey] = application. + Services.GetRequiredService() + .Create(); + + return application; + + Task InvocationDelegate(ILambdaInvocationContext context) + { + var arg0 = context.GetRequiredEvent(); + var arg1 = context.ServiceProvider.GetRequiredService(); + var response = castHandler.Invoke(arg0, arg1); + if (context.Features.Get() is not IResponseFeature responseFeature) + { + throw new InvalidOperationException($"Response feature for type 'global::MyStruct?' is not available in the collection."); + } + responseFeature.SetResponse(response); + return Task.CompletedTask; + } + } + } + + file static class Utilities + { + internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; + } +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs new file mode 100644 index 00000000..342f3aea --- /dev/null +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs @@ -0,0 +1,80 @@ +//HintName: LambdaHandler.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +#nullable enable + +namespace System.Runtime.CompilerServices +{ + using System.CodeDom.Compiler; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace MinimalLambda.Generated +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using MinimalLambda; + using MinimalLambda.Builder; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + file static class GeneratedLambdaInvocationBuilderExtensions + { + private const string EventFeatureProviderKey = "__EventFeatureProvider"; + private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; + + [InterceptsLocation(1, "REPLACED")] + internal static ILambdaInvocationBuilder MapHandlerInterceptor0( + this ILambdaInvocationBuilder application, + Delegate handler + ) + { + var castHandler = Utilities.Cast(handler, string? (global::IService? arg0 = default) => throw null!); + + application.Handle(InvocationDelegate); + + if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) + application.Properties[ResponseFeatureProviderKey] = application. + Services.GetRequiredService() + .Create(); + + return application; + + Task InvocationDelegate(ILambdaInvocationContext context) + { + var arg0 = context.ServiceProvider.GetService(); + var response = castHandler.Invoke(arg0); + if (context.Features.Get() is not IResponseFeature responseFeature) + { + throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + } + responseFeature.SetResponse(response); + return Task.CompletedTask; + } + } + } + + file static class Utilities + { + internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; + } +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs index 4e5416f7..f42534a8 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs @@ -18,7 +18,8 @@ await GeneratorTestHelpers.Verify( lambda.Handle(Task (ILambdaInvocationContext context) => Task.CompletedTask); await lambda.RunAsync(); - """ + """, + 0 ); [Fact] @@ -261,6 +262,30 @@ public interface IService """ ); + [Fact] + public async Task Test_ExpressionLambda_NullableInput_ReturnNullableValueType() => + await GeneratorTestHelpers.Verify( + """ + using MinimalLambda; + using MinimalLambda.Builder; + using Microsoft.Extensions.Hosting; + + var builder = LambdaApplication.CreateBuilder(); + var lambda = builder.Build(); + + lambda.MapHandler(([FromEvent] int? input, IService service) => service.GetMessage()); + + await lambda.RunAsync(); + + public struct MyStruct { } + + public interface IService + { + MyStruct? GetMessage(); + } + """ + ); + // Additional handler type not shown in the examples - generic handlers with complex custom // types [Fact] From 0746be7573f9551221839fdc423be30dd6dc30ea Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 26 Dec 2025 20:14:45 -0500 Subject: [PATCH 21/67] refactor(source-generators): remove `IsResponseTypeBool` from `HigherOrderMethodInfo` - Deleted the `IsResponseTypeBool` property and related logic from `HigherOrderMethodInfo`. - Simplified response type handling by removing unused checks for boolean return types. --- .../Models/HigherOrderMethodInfo.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index af36a311..4234eb04 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -36,7 +36,6 @@ internal readonly record struct HigherOrderMethodInfo( bool IsAwaitable, bool HasResponse, bool IsResponseTypeStream, - bool IsResponseTypeBool, bool IsEventTypeStream, bool HasEvent, string? EventType, @@ -94,14 +93,6 @@ GeneratorContext context WellKnownType.System_IO_Stream ); - var isReturnTypeBool = - hasResponse - && !isReturnTypeStream - && context.WellKnownTypes.IsTypeMatch( - methodSymbol.ReturnType, - WellKnownType.System_Boolean - ); - var hasEvent = assignments.Any(a => a.IsEvent); var eventType = hasEvent @@ -127,7 +118,6 @@ GeneratorContext context IsAwaitable = isAwaitable, HasResponse = hasResponse, IsResponseTypeStream = isReturnTypeStream, - IsResponseTypeBool = isReturnTypeBool, IsEventTypeStream = isEventTypeStream, HasEvent = hasEvent, EventType = eventType, From 9f1eeecd10205c5c0ff57e6b43fea4c77bea0bad Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 08:06:48 -0500 Subject: [PATCH 22/67] feat(source-generators): enhance `MapHandlerParameterInfo` and diagnostic handling - Added `LocationInfo` property to `MapHandlerParameterInfo` for improved diagnostic context. - Refactored parameter handling to use `IsTypeMatch` and `IsAnyTypeMatch` for cleaner code. - Introduced helper methods in `LocationInfo` for creation from various syntax elements. - Replaced `Diagnostic.Create` with `Diagnostic.CreateLocationInfo` for better location tracking. - Improved encapsulation and modularity in well-known types extensions. --- .../Diagnostics/DiagnosticGenerator.cs | 8 ++-- .../Extensions/WellKnownTypesExtensions.cs | 9 +++++ .../Models/LocationInfo.cs | 15 +++++++ .../Models/MapHandlerParameterInfo.cs | 39 ++++++++++--------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 98f170a7..a7e4d941 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -24,7 +24,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // // invocationInfo // // .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) // // .Select(p => - // // Diagnostic.Create( + // // Diagnostic.CreateLocationInfo( // // Diagnostics.MultipleParametersUseAttribute, // // p.LocationInfo?.ToLocation(), // // AttributeConstants.FromEventAttribute @@ -48,7 +48,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") // { // diagnostics.Add( - // Diagnostic.Create( + // Diagnostic.CreateLocationInfo( // Diagnostics.MustBeConcreteType, // useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), // useMiddlewareTInfo.ClassInfo.ShortName @@ -67,7 +67,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // ) // .Skip(1) // .Select(c => - // Diagnostic.Create( + // Diagnostic.CreateLocationInfo( // Diagnostics.MultipleConstructorsWithAttribute, // c.AttributeInfos.First(a => // a.FullName == AttributeConstants.MiddlewareConstructor @@ -91,7 +91,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // ) // .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) // .Select(parameterInfo => - // Diagnostic.Create( + // Diagnostic.CreateLocationInfo( // Diagnostics.InvalidAttributeArgument, // parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), // parameterInfo.KeyedServiceKey.Value.Type diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs index d02e3b63..d796c4da 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs @@ -1,3 +1,4 @@ +using System.Linq; using Microsoft.CodeAnalysis; namespace MinimalLambda.SourceGenerators.WellKnownTypes; @@ -11,5 +12,13 @@ internal bool IsTypeMatch(ITypeSymbol type, WellKnownTypeData.WellKnownType well var foundType = wellKnownTypes.Get(wellKnownType); return type.Equals(foundType, SymbolEqualityComparer.Default); } + + internal bool IsAnyTypeMatch( + ITypeSymbol type, + params WellKnownTypeData.WellKnownType[] types + ) => + types + .Select(wellKnownTypes.Get) + .Any(foundType => type.Equals(foundType, SymbolEqualityComparer.Default)); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs index 2963b5de..919f20b1 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs @@ -16,6 +16,21 @@ internal static class LocationInfoExtensions { internal Location ToLocation() => Location.Create(locationInfo.FilePath, locationInfo.TextSpan, locationInfo.LineSpan); + + internal static LocationInfo? Create(Location? location) => + location?.SourceTree is null + ? null + : new LocationInfo( + location.SourceTree.FilePath, + location.SourceSpan, + location.GetLineSpan().Span + ); + + internal static LocationInfo? Create(ISymbol symbol) => + LocationInfo.Create(symbol.Locations.FirstOrDefault()); + + internal static LocationInfo? Create(SyntaxNode syntaxNode) => + LocationInfo.Create(syntaxNode.GetLocation()); } extension(Location location) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index c65a104b..c04a8132 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -10,7 +11,8 @@ internal readonly record struct MapHandlerParameterInfo( string Assignment, string InfoComment, bool IsEvent, - bool IsFromKeyedService + bool IsFromKeyedService, + LocationInfo? LocationInfo ); internal static class MapHandlerParameterInfoExtensions @@ -22,27 +24,18 @@ internal static DiagnosticResult Create( GeneratorContext context ) { - var stream = context.WellKnownTypes.Get(WellKnownType.System_IO_Stream); - var lambdaContext = context.WellKnownTypes.Get( - WellKnownType.Amazon_Lambda_Core_ILambdaContext - ); - var lambdaInvocationContext = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_ILambdaInvocationContext - ); - var cancellationToken = context.WellKnownTypes.Get( - WellKnownType.System_Threading_CancellationToken - ); - - var isStream = parameter.Type.Equals(stream, SymbolEqualityComparer.Default); - var paramType = parameter.Type.ToGloballyQualifiedName(); var parameterInfo = new MapHandlerParameterInfo { GloballyQualifiedType = parameter.Type.ToGloballyQualifiedName(), - IsStream = isStream, + IsStream = context.WellKnownTypes.IsTypeMatch( + parameter.Type, + WellKnownType.System_IO_Stream + ), IsEvent = false, IsFromKeyedService = false, + LocationInfo = LocationInfo.Create(parameter), }; // event @@ -50,7 +43,7 @@ GeneratorContext context return DiagnosticResult.Success( parameterInfo with { - Assignment = isStream + Assignment = parameterInfo.IsStream // stream event ? "context.Features.GetRequired().EventStream" // non stream event @@ -61,8 +54,11 @@ parameterInfo with // context if ( - SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaContext) - || SymbolEqualityComparer.Default.Equals(parameter.Type, lambdaInvocationContext) + context.WellKnownTypes.IsAnyTypeMatch( + parameter.Type, + WellKnownType.Amazon_Lambda_Core_ILambdaContext, + WellKnownType.MinimalLambda_ILambdaInvocationContext + ) ) return DiagnosticResult.Success( parameterInfo with @@ -72,7 +68,12 @@ parameterInfo with ); // cancellation token - if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationToken)) + if ( + context.WellKnownTypes.IsTypeMatch( + parameter.Type, + WellKnownType.System_Threading_CancellationToken + ) + ) return DiagnosticResult.Success( parameterInfo with { From 93c06a679ce55d1b0aa858e6b59fb2b5f6073bc9 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 09:05:01 -0500 Subject: [PATCH 23/67] feat(source-generators): enhance `MapHandlerParameterInfo` and improve diagnostics flow - Added `MapHandlerParameterSource` enum to classify parameter sources (e.g., Event, Context, Services). - Introduced `KeyedServicesKey` in `MapHandlerParameterInfo` for specifying keys for DI services. - Integrated `ReportMultipleEvents` diagnostics to handle invalid multiple events scenario. - Updated `HigherOrderMethodInfo` creation logic to include new diagnostic flow and parameter sources. - Refactored DI parameter resolution to support keyed services using `GetDiParameterAssignment`. - Streamlined diagnostics collection in `DiagnosticGenerator` for improved modularity. --- .../Diagnostics/DiagnosticGenerator.cs | 7 ++ .../Extensions/ParameterSymbolExtensions.cs | 10 +-- .../Extensions/WellKnownTypesExtensions.cs | 8 +- .../MinimalLambdaGenerator.cs | 3 +- .../Models/DiagnosticInfo.cs | 6 ++ .../Models/HigherOrderMethodInfo.cs | 78 ++++++++++++------- .../Models/MapHandlerParameterInfo.cs | 28 ++++++- .../HandlerSyntaxProvider.cs | 0 .../DiagnosticTests.cs | 2 +- 9 files changed, 96 insertions(+), 46 deletions(-) rename src/MinimalLambda.SourceGenerators/{ => SyntaxProviders}/HandlerSyntaxProvider.cs (100%) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index a7e4d941..62c5d15b 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Models; @@ -10,6 +11,12 @@ internal static List GenerateDiagnostics(CompilationInfo compilation { var diagnostics = new List(); + diagnostics.AddRange( + compilationInfo + .MapHandlerInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); + // var delegateInfos = compilationInfo.MapHandlerInvocationInfos; // // // // Validate parameters diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 71fa5311..820977bb 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -65,7 +65,7 @@ out DiagnosticResult? keyResult return false; } - internal DiagnosticResult<(string Assignment, bool IsKeyed)> GetDiParameterAssignment( + internal DiagnosticResult<(string Assignment, string? Key)> GetDiParameterAssignment( GeneratorContext context ) { @@ -80,24 +80,24 @@ GeneratorContext context // keyed services if (isKeyedServices) return keyResult!.Bind(key => - DiagnosticResult<(string, bool)>.Success( + DiagnosticResult<(string, string?)>.Success( ( isRequired ? $"context.ServiceProvider.GetKeyedService<{paramType}>({key})" : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})", - true + key ) ) ); - return DiagnosticResult<(string, bool)>.Success( + return DiagnosticResult<(string, string?)>.Success( ( isRequired // default - inject from DI - optional ? $"context.ServiceProvider.GetService<{paramType}>()" // default - inject required from DI : $"context.ServiceProvider.GetRequiredService<{paramType}>()", - false + null ) ); } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs index d796c4da..d49d94f1 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs @@ -1,5 +1,6 @@ using System.Linq; using Microsoft.CodeAnalysis; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.WellKnownTypes; @@ -7,16 +8,13 @@ internal static class WellKnownTypesExtensions { extension(WellKnownTypes wellKnownTypes) { - internal bool IsTypeMatch(ITypeSymbol type, WellKnownTypeData.WellKnownType wellKnownType) + internal bool IsTypeMatch(ITypeSymbol type, WellKnownType wellKnownType) { var foundType = wellKnownTypes.Get(wellKnownType); return type.Equals(foundType, SymbolEqualityComparer.Default); } - internal bool IsAnyTypeMatch( - ITypeSymbol type, - params WellKnownTypeData.WellKnownType[] types - ) => + internal bool IsAnyTypeMatch(ITypeSymbol type, WellKnownType[] types) => types .Select(wellKnownTypes.Get) .Any(foundType => type.Equals(foundType, SymbolEqualityComparer.Default)); diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 1b9b0072..011a2bf9 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -82,8 +82,7 @@ is CSharpCompilation UseMiddlewareTSyntaxProvider.Predicate, UseMiddlewareTSyntaxProvider.Transformer ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); + .WhereNotNull(); // collect call // var mapHandlerCallsCollected = mapHandlerCalls.Collect(); diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs index d6639230..ccacaee1 100644 --- a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs @@ -28,6 +28,12 @@ internal static class DiagnosticInfoExtensions { extension(DiagnosticInfo diagnosticInfo) { + internal static DiagnosticInfo Create( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo, + object?[] messageArgs + ) => new(diagnosticDescriptor, locationInfo, messageArgs); + internal Diagnostic ToDiagnostic() => Diagnostic.Create( diagnosticInfo.DiagnosticDescriptor, diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 4234eb04..29d12965 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; @@ -19,13 +18,13 @@ internal enum MethodType internal interface IMethodInfo { - MethodType MethodType { get; } - InterceptableLocationInfo InterceptableLocationInfo { get; } string DelegateCastType { get; } - bool IsAwaitable { get; } - bool HasResponse { get; } - bool HasAnyFromKeyedServices { get; } EquatableArray DiagnosticInfos { get; } + bool HasAnyFromKeyedServices { get; } + bool HasResponse { get; } + InterceptableLocationInfo InterceptableLocationInfo { get; } + bool IsAwaitable { get; } + MethodType MethodType { get; } } internal readonly record struct HigherOrderMethodInfo( @@ -47,6 +46,30 @@ internal readonly record struct HigherOrderMethodInfo( internal static class HigherOrderMethodInfoExtensions { + internal static IEnumerable ReportMultipleEvents( + IEnumerable assignments, + GeneratorContext context + ) + { + string? eventAttribute = null; + + return assignments + .Where(a => a.IsEvent) + .Skip(1) + .Select(a => + { + eventAttribute ??= context + .WellKnownTypes.Get(WellKnownType.MinimalLambda_Builder_FromEventAttribute) + .ToGloballyQualifiedName(); + + return DiagnosticInfo.Create( + Diagnostics.MultipleParametersUseAttribute, + a.LocationInfo, + [eventAttribute] + ); + }); + } + extension(HigherOrderMethodInfo) { internal static HigherOrderMethodInfo Create( @@ -54,9 +77,6 @@ internal static HigherOrderMethodInfo Create( GeneratorContext context ) { - var gotName = context.Node.TryGetMethodName(out var methodName); - Debug.Assert(gotName, "Could not get method name. This should be unreachable"); - var handlerCastType = methodSymbol.GetCastableSignature(); if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) @@ -78,10 +98,12 @@ GeneratorContext context return acc; }, - static acc => - (acc.Successes.ToEquatableArray(), acc.Diagnostics.ToEquatableArray()) + static acc => (acc.Successes.ToEquatableArray(), acc.Diagnostics) ); + // add parameter diagnostics + diagnostics.AddRange(ReportMultipleEvents(assignments, context)); + var isAwaitable = methodSymbol.IsAwaitable(context); var hasResponse = methodSymbol.HasMeaningfulReturnType(context); @@ -108,24 +130,22 @@ GeneratorContext context .UnwrapReturnType(context) .ToGloballyQualifiedName(); - return new HigherOrderMethodInfo - { - InterceptableLocationInfo = interceptableLocation.Value, - InterceptableLocationAttribute = - interceptableLocation.Value.ToInterceptsLocationAttribute(), - DelegateCastType = handlerCastType, - ParameterAssignments = assignments, - IsAwaitable = isAwaitable, - HasResponse = hasResponse, - IsResponseTypeStream = isReturnTypeStream, - IsEventTypeStream = isEventTypeStream, - HasEvent = hasEvent, - EventType = eventType, - UnwrappedResponseType = unwrappedReturnType, - HasAnyFromKeyedServices = hasAnyKeyedServices, - DiagnosticInfos = diagnostics, - MethodType = MethodType.MapHandler, - }; + return new HigherOrderMethodInfo( + MethodType: MethodType.MapHandler, + InterceptableLocationInfo: interceptableLocation.Value, + InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + DelegateCastType: handlerCastType, + ParameterAssignments: assignments, + IsAwaitable: isAwaitable, + HasResponse: hasResponse, + IsResponseTypeStream: isReturnTypeStream, + IsEventTypeStream: isEventTypeStream, + HasEvent: hasEvent, + EventType: eventType, + UnwrappedResponseType: unwrappedReturnType, + HasAnyFromKeyedServices: hasAnyKeyedServices, + DiagnosticInfos: diagnostics.ToEquatableArray() + ); } } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index c04a8132..b8c9d247 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -5,6 +5,15 @@ namespace MinimalLambda.SourceGenerators.Models; +internal enum MapHandlerParameterSource +{ + Event, + Context, + CancellationToken, + KeyedServices, + Services, +} + internal readonly record struct MapHandlerParameterInfo( string GloballyQualifiedType, bool IsStream, @@ -12,7 +21,9 @@ internal readonly record struct MapHandlerParameterInfo( string InfoComment, bool IsEvent, bool IsFromKeyedService, - LocationInfo? LocationInfo + LocationInfo? LocationInfo, + MapHandlerParameterSource Source, + string? KeyedServicesKey ); internal static class MapHandlerParameterInfoExtensions @@ -49,6 +60,7 @@ parameterInfo with // non stream event : $"context.GetRequiredEvent<{paramType}>()", IsEvent = true, + Source = MapHandlerParameterSource.Event, } ); @@ -56,14 +68,17 @@ parameterInfo with if ( context.WellKnownTypes.IsAnyTypeMatch( parameter.Type, - WellKnownType.Amazon_Lambda_Core_ILambdaContext, - WellKnownType.MinimalLambda_ILambdaInvocationContext + [ + WellKnownType.Amazon_Lambda_Core_ILambdaContext, + WellKnownType.MinimalLambda_ILambdaInvocationContext, + ] ) ) return DiagnosticResult.Success( parameterInfo with { Assignment = "context", + Source = MapHandlerParameterSource.Context, } ); @@ -78,6 +93,7 @@ parameterInfo with parameterInfo with { Assignment = "context.CancellationToken", + Source = MapHandlerParameterSource.CancellationToken, } ); @@ -89,7 +105,11 @@ parameterInfo with parameterInfo with { Assignment = diInfo.Assignment, - IsFromKeyedService = diInfo.IsKeyed, + IsFromKeyedService = diInfo.Key is not null, + Source = diInfo.Key is not null + ? MapHandlerParameterSource.KeyedServices + : MapHandlerParameterSource.Services, + KeyedServicesKey = diInfo.Key, } ) ); diff --git a/src/MinimalLambda.SourceGenerators/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/HandlerSyntaxProvider.cs rename to src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs index 90754699..ce61ba28 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs @@ -72,7 +72,7 @@ public void Test_MultipleParametersWithRequestAttribute() """ ); - diagnostics.Length.Should().Be(2); + diagnostics.Length.Should().Be(1); foreach (var diagnostic in diagnostics) { From 09bc18030a96725d6f5590cabb1bc29e0fc1bc03 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 10:30:10 -0500 Subject: [PATCH 24/67] refactor(source-generators): simplify and enhance parameter and method handling logic - Changed `MapHandlerParameterInfo` from `record struct` to `record` for improved extensibility. - Updated `HigherOrderMethodInfo` to align with new parameter handling and diagnostics structure. - Introduced additional constructor usage for `MapHandlerParameterInfo` to streamline initialization. - Made `ReportMultipleEvents` private for better encapsulation and access control. - Added `WhereNotNull` extension method for nullable value providers to improve null handling logic in generators. --- .../IncrementalValueProviderExtensions.cs | 7 ++++++ .../Models/HigherOrderMethodInfo.cs | 11 +++------ .../Models/MapHandlerParameterInfo.cs | 24 +++++++++---------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs index 38f3a417..455de416 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs @@ -8,4 +8,11 @@ internal static class IncrementalValueProviderExtensions public IncrementalValuesProvider WhereNotNull() => valueProviders.Where(static v => v is not null).Select(static (v, _) => v!.Value); } + + extension(IncrementalValuesProvider valueProviders) + where T : class + { + public IncrementalValuesProvider WhereNotNull() => + valueProviders.Where(static v => v is not null).Select(static (v, _) => v!); + } } diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 29d12965..4bd10e03 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -18,16 +18,11 @@ internal enum MethodType internal interface IMethodInfo { - string DelegateCastType { get; } - EquatableArray DiagnosticInfos { get; } - bool HasAnyFromKeyedServices { get; } - bool HasResponse { get; } - InterceptableLocationInfo InterceptableLocationInfo { get; } - bool IsAwaitable { get; } MethodType MethodType { get; } + EquatableArray DiagnosticInfos { get; } } -internal readonly record struct HigherOrderMethodInfo( +internal record HigherOrderMethodInfo( InterceptableLocationInfo InterceptableLocationInfo, string InterceptableLocationAttribute, string DelegateCastType, @@ -46,7 +41,7 @@ internal readonly record struct HigherOrderMethodInfo( internal static class HigherOrderMethodInfoExtensions { - internal static IEnumerable ReportMultipleEvents( + private static IEnumerable ReportMultipleEvents( IEnumerable assignments, GeneratorContext context ) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index b8c9d247..66c5dd77 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -14,7 +14,7 @@ internal enum MapHandlerParameterSource Services, } -internal readonly record struct MapHandlerParameterInfo( +internal record MapHandlerParameterInfo( string GloballyQualifiedType, bool IsStream, string Assignment, @@ -37,17 +37,17 @@ GeneratorContext context { var paramType = parameter.Type.ToGloballyQualifiedName(); - var parameterInfo = new MapHandlerParameterInfo - { - GloballyQualifiedType = parameter.Type.ToGloballyQualifiedName(), - IsStream = context.WellKnownTypes.IsTypeMatch( - parameter.Type, - WellKnownType.System_IO_Stream - ), - IsEvent = false, - IsFromKeyedService = false, - LocationInfo = LocationInfo.Create(parameter), - }; + var parameterInfo = new MapHandlerParameterInfo( + parameter.Type.ToGloballyQualifiedName(), + context.WellKnownTypes.IsTypeMatch(parameter.Type, WellKnownType.System_IO_Stream), + IsEvent: false, + IsFromKeyedService: false, + LocationInfo: LocationInfo.Create(parameter), + Assignment: string.Empty, + InfoComment: string.Empty, + KeyedServicesKey: string.Empty, + Source: MapHandlerParameterSource.Services + ); // event if (parameter.IsFromEvent(context)) From e39aa9f46041feb4322c420f510e1ce4cf607417 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 10:34:03 -0500 Subject: [PATCH 25/67] refactor(source-generators): refactor `ReportMultipleEvents` for improved clarity and lazy initialization - Replaced redundant null-check logic with `Lazy` initialization for `eventAttribute`. - Reordered properties in `IMethodInfo` for consistent ordering and readability. - Simplified LINQ usage in diagnostics generation for `assignments.Where(a => a.IsEvent)`. --- .../Models/HigherOrderMethodInfo.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index 4bd10e03..f0299ad8 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -18,8 +18,8 @@ internal enum MethodType internal interface IMethodInfo { - MethodType MethodType { get; } EquatableArray DiagnosticInfos { get; } + MethodType MethodType { get; } } internal record HigherOrderMethodInfo( @@ -46,23 +46,22 @@ private static IEnumerable ReportMultipleEvents( GeneratorContext context ) { - string? eventAttribute = null; + var eventAttribute = new Lazy(() => + context + .WellKnownTypes.Get(WellKnownType.MinimalLambda_Builder_FromEventAttribute) + .ToGloballyQualifiedName() + ); return assignments .Where(a => a.IsEvent) .Skip(1) .Select(a => - { - eventAttribute ??= context - .WellKnownTypes.Get(WellKnownType.MinimalLambda_Builder_FromEventAttribute) - .ToGloballyQualifiedName(); - - return DiagnosticInfo.Create( + DiagnosticInfo.Create( Diagnostics.MultipleParametersUseAttribute, a.LocationInfo, [eventAttribute] - ); - }); + ) + ); } extension(HigherOrderMethodInfo) From 693e64121408c9472306f5dbecb98a9ac5b08d65 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 10:49:10 -0500 Subject: [PATCH 26/67] feat(source-generators): introduce `CollectDiagnosticResults` for streamlined diagnostics and results - Added `CollectDiagnosticResults` to simplify diagnostics and result aggregation using LINQ. - Replaced existing diagnostics aggregation logic in `HigherOrderMethodInfo` with the new utility method. - Updated `EnumerableExtensions` with new generic helper for enhanced result handling. - Improved maintainability by reducing repetitive code across result and diagnostics collection. --- .../Extensions/EnumerableExtensions.cs | 28 +++++++++++++++++++ .../Models/HigherOrderMethodInfo.cs | 23 +++------------ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index 3cdaf2d4..f8a593b0 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using MinimalLambda.SourceGenerators.Models; namespace System.Collections.Generic; @@ -35,4 +36,31 @@ public List Add(T item) return list; } } + + extension(IEnumerable enumerable) + { + internal (List, List) CollectDiagnosticResults( + Func> extractor + ) + { + var count = 0; + if (enumerable is ICollection collection) + count = collection.Count; + + return enumerable + .Select(extractor) + .Aggregate( + (Successes: new List(count), Diagnostics: new List()), + static (acc, result) => + { + result.Do( + info => acc.Successes.Add(info), + diagnostic => acc.Diagnostics.Add(diagnostic) + ); + + return acc; + } + ); + } + } } diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs index f0299ad8..b6c25f51 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs @@ -76,24 +76,9 @@ GeneratorContext context if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) throw new InvalidOperationException("Unable to get interceptable location"); - var (assignments, diagnostics) = methodSymbol - .Parameters.Select(parameter => MapHandlerParameterInfo.Create(parameter, context)) - .Aggregate( - ( - Successes: new List(), - Diagnostics: new List() - ), - static (acc, result) => - { - result.Do( - info => acc.Successes.Add(info), - diagnostic => acc.Diagnostics.Add(diagnostic) - ); - - return acc; - }, - static acc => (acc.Successes.ToEquatableArray(), acc.Diagnostics) - ); + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => MapHandlerParameterInfo.Create(parameter, context) + ); // add parameter diagnostics diagnostics.AddRange(ReportMultipleEvents(assignments, context)); @@ -129,7 +114,7 @@ GeneratorContext context InterceptableLocationInfo: interceptableLocation.Value, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType: handlerCastType, - ParameterAssignments: assignments, + ParameterAssignments: assignments.ToEquatableArray(), IsAwaitable: isAwaitable, HasResponse: hasResponse, IsResponseTypeStream: isReturnTypeStream, From 06d225f084bfe72f1ccd8bb2952d0facb534a361 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 11:02:34 -0500 Subject: [PATCH 27/67] feat(source-generators): replace `HigherOrderMethodInfo` with `InvocationMethodInfo` and introduce `LifecycleMethodInfo` - Replaced `HigherOrderMethodInfo` with `InvocationMethodInfo` for better encapsulation and clarity. - Introduced `LifecycleMethodInfo` to handle `OnInit` and `OnShutdown` method types. - Refactored method type classification to use `MethodType` enum for improved maintainability and readability. - Updated syntax providers and diagnostic logic to align with the new method info handling structure. - Simplified diagnostic aggregation and method invocation assignment using the updated models. - Improved modularity by extracting shared method-related logic into reusable components. --- .../Diagnostics/DiagnosticGenerator.cs | 2 +- .../Emitters/GenericHandlerSources.cs | 2 +- .../Emitters/MapHandlerSources.cs | 2 +- .../MinimalLambdaGenerator.cs | 4 +- .../Models/CompilationInfo.cs | 6 +- .../Models/IMethodInfo.cs | 9 +++ ...rMethodInfo.cs => InvocationMethodInfo.cs} | 23 ++---- .../Models/LifecycleMethodInfo.cs | 80 +++++++++++++++++++ .../Models/MethodType.cs | 8 ++ .../Extractors/HandlerInfoExtractor.cs | 19 ++--- .../SyntaxProviders/HandlerSyntaxProvider.cs | 13 ++- .../MapHandlerSyntaxProvider.cs | 2 +- .../SyntaxProviders/OnInitSyntaxProvider.cs | 2 +- .../OnShutdownSyntaxProvider.cs | 2 +- 14 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs rename src/MinimalLambda.SourceGenerators/Models/{HigherOrderMethodInfo.cs => InvocationMethodInfo.cs} (90%) create mode 100644 src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs create mode 100644 src/MinimalLambda.SourceGenerators/Models/MethodType.cs diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 62c5d15b..49b96651 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -90,7 +90,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation } // private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( - // this EquatableArray methodNameInfos + // this EquatableArray methodNameInfos // ) => // methodNameInfos // .SelectMany(onShutdownInvocationInfo => diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index dc31d35f..935dab16 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -11,7 +11,7 @@ internal static class GenericHandlerSources /// the return type of the actual handler. /// internal static string Generate( - EquatableArray higherOrderMethodInfos, + EquatableArray higherOrderMethodInfos, string methodName, string? wrapperReturnType, string? defaultWrapperReturnValue, diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 66c85b55..95c43b94 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -5,7 +5,7 @@ namespace MinimalLambda.SourceGenerators; internal static class MapHandlerSources { - internal static string Generate(EquatableArray mapHandlerInvocationInfos) + internal static string Generate(EquatableArray mapHandlerInvocationInfos) { // var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandler => // { diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 011a2bf9..8e9fb1f6 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -108,12 +108,14 @@ is CSharpCompilation return new CompilationInfo { MapHandlerInvocationInfos = handlerInfos - .Where(h => h.MethodType == MethodType.MapHandler) + .OfType() .ToEquatableArray(), OnShutdownInvocationInfos = handlerInfos + .OfType() .Where(h => h.MethodType == MethodType.OnShutdown) .ToEquatableArray(), OnInitInvocationInfos = handlerInfos + .OfType() .Where(h => h.MethodType == MethodType.OnInit) .ToEquatableArray(), UseMiddlewareTInfos = useMiddlewareInfo.ToEquatableArray(), diff --git a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs index 32a38049..3221b30d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs @@ -3,8 +3,8 @@ namespace MinimalLambda.SourceGenerators.Models; internal readonly record struct CompilationInfo( - EquatableArray MapHandlerInvocationInfos, - EquatableArray OnShutdownInvocationInfos, - EquatableArray OnInitInvocationInfos, + EquatableArray MapHandlerInvocationInfos, + EquatableArray OnShutdownInvocationInfos, + EquatableArray OnInitInvocationInfos, EquatableArray UseMiddlewareTInfos ); diff --git a/src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs new file mode 100644 index 00000000..8cd089ca --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs @@ -0,0 +1,9 @@ +using LayeredCraft.SourceGeneratorTools.Types; + +namespace MinimalLambda.SourceGenerators.Models; + +internal interface IMethodInfo +{ + EquatableArray DiagnosticInfos { get; } + MethodType MethodType { get; } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs similarity index 90% rename from src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs index b6c25f51..56b90ed6 100644 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs @@ -9,20 +9,7 @@ namespace MinimalLambda.SourceGenerators.Models; -internal enum MethodType -{ - MapHandler, - OnInit, - OnShutdown, -} - -internal interface IMethodInfo -{ - EquatableArray DiagnosticInfos { get; } - MethodType MethodType { get; } -} - -internal record HigherOrderMethodInfo( +internal record InvocationMethodInfo( InterceptableLocationInfo InterceptableLocationInfo, string InterceptableLocationAttribute, string DelegateCastType, @@ -39,7 +26,7 @@ internal record HigherOrderMethodInfo( MethodType MethodType = MethodType.MapHandler ) : IMethodInfo; -internal static class HigherOrderMethodInfoExtensions +internal static class MapHandlerMethodInfoExtensions { private static IEnumerable ReportMultipleEvents( IEnumerable assignments, @@ -64,9 +51,9 @@ GeneratorContext context ); } - extension(HigherOrderMethodInfo) + extension(InvocationMethodInfo) { - internal static HigherOrderMethodInfo Create( + internal static InvocationMethodInfo Create( IMethodSymbol methodSymbol, GeneratorContext context ) @@ -109,7 +96,7 @@ GeneratorContext context .UnwrapReturnType(context) .ToGloballyQualifiedName(); - return new HigherOrderMethodInfo( + return new InvocationMethodInfo( MethodType: MethodType.MapHandler, InterceptableLocationInfo: interceptableLocation.Value, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs new file mode 100644 index 00000000..536df796 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record LifecycleMethodInfo( + EquatableArray DiagnosticInfos, + MethodType MethodType +) : IMethodInfo; + +internal static class LifecycleMethodInfoExtensions +{ + extension(LifecycleMethodInfo) + { + internal static LifecycleMethodInfo Create( + IMethodSymbol methodSymbol, + MethodType methodType, + GeneratorContext context + ) + { + var handlerCastType = methodSymbol.GetCastableSignature(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => MapHandlerParameterInfo.Create(parameter, context) + ); + + var isAwaitable = methodSymbol.IsAwaitable(context); + + var hasResponse = methodSymbol.HasMeaningfulReturnType(context); + + var isReturnTypeStream = + hasResponse + && context.WellKnownTypes.IsTypeMatch( + methodSymbol.ReturnType, + WellKnownTypeData.WellKnownType.System_IO_Stream + ); + + var hasEvent = assignments.Any(a => a.IsEvent); + + var eventType = hasEvent + ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() + : null; + + var isEventTypeStream = + hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + + var unwrappedReturnType = methodSymbol + .UnwrapReturnType(context) + .ToGloballyQualifiedName(); + + return new LifecycleMethodInfo( + MethodType: methodType, + // InterceptableLocationInfo: interceptableLocation.Value, + // InterceptableLocationAttribute: + // interceptableLocation.Value.ToInterceptsLocationAttribute(), + // DelegateCastType: handlerCastType, + // ParameterAssignments: assignments.ToEquatableArray(), + // IsAwaitable: isAwaitable, + // HasResponse: hasResponse, + // IsResponseTypeStream: isReturnTypeStream, + // IsEventTypeStream: isEventTypeStream, + // HasEvent: hasEvent, + // EventType: eventType, + // UnwrappedResponseType: unwrappedReturnType, + // HasAnyFromKeyedServices: hasAnyKeyedServices, + DiagnosticInfos: diagnostics.ToEquatableArray() + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/MethodType.cs b/src/MinimalLambda.SourceGenerators/Models/MethodType.cs new file mode 100644 index 00000000..2ab84812 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/MethodType.cs @@ -0,0 +1,8 @@ +namespace MinimalLambda.SourceGenerators.Models; + +internal enum MethodType +{ + MapHandler, + OnInit, + OnShutdown, +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs index 17982ad7..d6a5540b 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs @@ -22,7 +22,7 @@ internal static bool Predicate(SyntaxNode node, params string[] methodNames) => && node.TryGetMethodName(out var name) && methodNames.Contains(name); - internal static HigherOrderMethodInfo? Transformer( + internal static InvocationMethodInfo? Transformer( GeneratorSyntaxContext context, Func delegateFilter, CancellationToken cancellationToken @@ -75,14 +75,15 @@ is not IInvocationOperation cancellationToken )!; - return new HigherOrderMethodInfo( - // targetOperation.TargetMethod.Name, - // LocationInfo: context.Node.CreateLocationInfo(), - // DelegateInfo: delegateInfo.Value, - // InterceptableLocationInfo: - // InterceptableLocationInfo.CreateFrom(interceptableLocation), - // ArgumentsInfos: argumentInfos - ); + throw new NotImplementedException(); + // return new InvocationMethodInfo( + // // targetOperation.TargetMethod.Name, + // // LocationInfo: context.Node.CreateLocationInfo(), + // // DelegateInfo: delegateInfo.Value, + // // InterceptableLocationInfo: + // // InterceptableLocationInfo.CreateFrom(interceptableLocation), + // // ArgumentsInfos: argumentInfos + // ); } private static DelegateInfo? ExtractDelegateInfo( diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index 5fda70bd..da285979 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -6,6 +6,7 @@ // See THIRD-PARTY-LICENSES.txt file in the project root or visit // https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt +using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -26,7 +27,7 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _) => && node.TryGetMethodName(out var name) && TargetMethodNames.Contains(name); - internal static HigherOrderMethodInfo? Transformer( + internal static IMethodInfo? Transformer( GeneratorSyntaxContext syntaxContext, CancellationToken cancellationToken ) @@ -36,10 +37,16 @@ CancellationToken cancellationToken if (!TryGetInvocationOperation(context, out var targetOperation)) return null; - if (!targetOperation.TryGetHandlerMethod(context.SemanticModel, out var methodSymbol)) + if (!targetOperation.TryGetHandlerMethod(context.SemanticModel, out var method)) return null; - return HigherOrderMethodInfo.Create(methodSymbol, context); + return targetOperation.TargetMethod.Name switch + { + "MapHandler" => InvocationMethodInfo.Create(method, context), + "OnInit" => LifecycleMethodInfo.Create(method, MethodType.OnInit, context), + "OnShutdown" => LifecycleMethodInfo.Create(method, MethodType.OnShutdown, context), + var methodName => throw new InvalidOperationException($"Unknown method '{methodName}"), + }; } private static bool TryGetInvocationOperation( diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs index 152fbf81..14a12f05 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class MapHandlerSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.MapHandlerMethodName); - internal static HigherOrderMethodInfo? Transformer( + internal static InvocationMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, _ => false, cancellationToken); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs index c94bcced..04cd10a7 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class OnInitSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnInitMethodName); - internal static HigherOrderMethodInfo? Transformer( + internal static InvocationMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs index 142643ea..8d57b6f0 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class OnShutdownSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnShutdownMethodName); - internal static HigherOrderMethodInfo? Transformer( + internal static InvocationMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); From b2f6b494f42066e569ba1b4d9ff2b337d859ebb6 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 11:04:20 -0500 Subject: [PATCH 28/67] feat(source-generators): enhance diagnostic aggregation and refactor method info structures - Added support for aggregating diagnostics from `OnInit` and `OnShutdown` invocation information. - Removed `InterceptableLocationInfo` from `InvocationMethodInfo` and added `LifecycleMethodInfo` fields. - Refactored `LifecycleMethodInfo` to include `InterceptableLocationAttribute` and `DelegateCastType`. - Streamlined diagnostic generation logic in `DiagnosticGenerator` to leverage updated method info models. --- .../Diagnostics/DiagnosticGenerator.cs | 12 ++++++++++++ .../Models/InvocationMethodInfo.cs | 2 -- .../Models/LifecycleMethodInfo.cs | 8 ++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 49b96651..63033be3 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -17,6 +17,18 @@ internal static List GenerateDiagnostics(CompilationInfo compilation .Select(d => d.ToDiagnostic()) ); + diagnostics.AddRange( + compilationInfo + .OnInitInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); + + diagnostics.AddRange( + compilationInfo + .OnShutdownInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); + // var delegateInfos = compilationInfo.MapHandlerInvocationInfos; // // // // Validate parameters diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs index 56b90ed6..ca14a513 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs @@ -10,7 +10,6 @@ namespace MinimalLambda.SourceGenerators.Models; internal record InvocationMethodInfo( - InterceptableLocationInfo InterceptableLocationInfo, string InterceptableLocationAttribute, string DelegateCastType, EquatableArray ParameterAssignments, @@ -98,7 +97,6 @@ GeneratorContext context return new InvocationMethodInfo( MethodType: MethodType.MapHandler, - InterceptableLocationInfo: interceptableLocation.Value, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType: handlerCastType, ParameterAssignments: assignments.ToEquatableArray(), diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index 536df796..f61075c6 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -9,6 +9,8 @@ namespace MinimalLambda.SourceGenerators.Models; internal record LifecycleMethodInfo( + string InterceptableLocationAttribute, + string DelegateCastType, EquatableArray DiagnosticInfos, MethodType MethodType ) : IMethodInfo; @@ -60,10 +62,8 @@ GeneratorContext context return new LifecycleMethodInfo( MethodType: methodType, - // InterceptableLocationInfo: interceptableLocation.Value, - // InterceptableLocationAttribute: - // interceptableLocation.Value.ToInterceptsLocationAttribute(), - // DelegateCastType: handlerCastType, + InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + DelegateCastType: handlerCastType, // ParameterAssignments: assignments.ToEquatableArray(), // IsAwaitable: isAwaitable, // HasResponse: hasResponse, From 444d863e563743420f937ee3918768727a72f076 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 13:06:02 -0500 Subject: [PATCH 29/67] feat(source-generators): refactor and replace `InvocationMethodInfo` with `MapHandlerMethodInfo` - Renamed `InvocationMethodInfo` to `MapHandlerMethodInfo` for improved readability and scope clarity. - Removed `MapHandlerParameterSource` enum from `MapHandlerParameterInfo` and moved it to a separate file. - Introduced `LifecycleHandlerParameterInfo` to enhance parameter handling for lifecycle-related methods. - Updated syntax providers, diagnostics, and emitters to align with the new `MapHandlerMethodInfo` structure. - Refactored parameter resolution logic to improve modularity and streamline handler generation. --- .../Diagnostics/DiagnosticGenerator.cs | 2 +- .../Emitters/GenericHandlerSources.cs | 2 +- .../Emitters/MapHandlerSources.cs | 2 +- .../MinimalLambdaGenerator.cs | 2 +- .../Models/CompilationInfo.cs | 2 +- .../Models/LifecycleHandlerParameterInfo.cs | 109 ++++++++++++++++++ ...nMethodInfo.cs => MapHandlerMethodInfo.cs} | 10 +- .../Models/MapHandlerParameterInfo.cs | 9 -- .../Models/MapHandlerParameterSource.cs | 10 ++ .../Extractors/HandlerInfoExtractor.cs | 4 +- .../SyntaxProviders/HandlerSyntaxProvider.cs | 2 +- .../MapHandlerSyntaxProvider.cs | 2 +- .../SyntaxProviders/OnInitSyntaxProvider.cs | 2 +- .../OnShutdownSyntaxProvider.cs | 2 +- 14 files changed, 135 insertions(+), 25 deletions(-) create mode 100644 src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs rename src/MinimalLambda.SourceGenerators/Models/{InvocationMethodInfo.cs => MapHandlerMethodInfo.cs} (94%) create mode 100644 src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 63033be3..bea250dd 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -102,7 +102,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation } // private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( - // this EquatableArray methodNameInfos + // this EquatableArray methodNameInfos // ) => // methodNameInfos // .SelectMany(onShutdownInvocationInfo => diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index 935dab16..c8b47d1a 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -11,7 +11,7 @@ internal static class GenericHandlerSources /// the return type of the actual handler. /// internal static string Generate( - EquatableArray higherOrderMethodInfos, + EquatableArray higherOrderMethodInfos, string methodName, string? wrapperReturnType, string? defaultWrapperReturnValue, diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 95c43b94..8c210409 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -5,7 +5,7 @@ namespace MinimalLambda.SourceGenerators; internal static class MapHandlerSources { - internal static string Generate(EquatableArray mapHandlerInvocationInfos) + internal static string Generate(EquatableArray mapHandlerInvocationInfos) { // var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandler => // { diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 8e9fb1f6..828e9b2c 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -108,7 +108,7 @@ is CSharpCompilation return new CompilationInfo { MapHandlerInvocationInfos = handlerInfos - .OfType() + .OfType() .ToEquatableArray(), OnShutdownInvocationInfos = handlerInfos .OfType() diff --git a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs index 3221b30d..3862f513 100644 --- a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs @@ -3,7 +3,7 @@ namespace MinimalLambda.SourceGenerators.Models; internal readonly record struct CompilationInfo( - EquatableArray MapHandlerInvocationInfos, + EquatableArray MapHandlerInvocationInfos, EquatableArray OnShutdownInvocationInfos, EquatableArray OnInitInvocationInfos, EquatableArray UseMiddlewareTInfos diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs new file mode 100644 index 00000000..b0660329 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs @@ -0,0 +1,109 @@ +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record LifecycleHandlerParameterInfo( + string GloballyQualifiedType, + bool IsStream, + string Assignment, + string InfoComment, + bool IsEvent, + bool IsFromKeyedService, + LocationInfo? LocationInfo, + MapHandlerParameterSource Source, + string? KeyedServicesKey +); + +internal static class LifecycleHandlerParameterInfoExtensions +{ + extension(LifecycleHandlerParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameter, + GeneratorContext context + ) + { + var paramType = parameter.Type.ToGloballyQualifiedName(); + + var parameterInfo = new LifecycleHandlerParameterInfo( + parameter.Type.ToGloballyQualifiedName(), + context.WellKnownTypes.IsTypeMatch(parameter.Type, WellKnownType.System_IO_Stream), + IsEvent: false, + IsFromKeyedService: false, + LocationInfo: LocationInfo.Create(parameter), + Assignment: string.Empty, + InfoComment: string.Empty, + KeyedServicesKey: string.Empty, + Source: MapHandlerParameterSource.Services + ); + + // event + if (parameter.IsFromEvent(context)) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = parameterInfo.IsStream + // stream event + ? "context.Features.GetRequired().EventStream" + // non stream event + : $"context.GetRequiredEvent<{paramType}>()", + IsEvent = true, + Source = MapHandlerParameterSource.Event, + } + ); + + // context + if ( + context.WellKnownTypes.IsAnyTypeMatch( + parameter.Type, + [ + WellKnownType.Amazon_Lambda_Core_ILambdaContext, + WellKnownType.MinimalLambda_ILambdaInvocationContext, + ] + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context", + Source = MapHandlerParameterSource.Context, + } + ); + + // cancellation token + if ( + context.WellKnownTypes.IsTypeMatch( + parameter.Type, + WellKnownType.System_Threading_CancellationToken + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context.CancellationToken", + Source = MapHandlerParameterSource.CancellationToken, + } + ); + + // default assignment from Di + return parameter + .GetDiParameterAssignment(context) + .Bind(diInfo => + DiagnosticResult.Success( + parameterInfo with + { + Assignment = diInfo.Assignment, + IsFromKeyedService = diInfo.Key is not null, + Source = diInfo.Key is not null + ? MapHandlerParameterSource.KeyedServices + : MapHandlerParameterSource.Services, + KeyedServicesKey = diInfo.Key, + } + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs similarity index 94% rename from src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs index ca14a513..ab12790b 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InvocationMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs @@ -9,7 +9,7 @@ namespace MinimalLambda.SourceGenerators.Models; -internal record InvocationMethodInfo( +internal record MapHandlerMethodInfo( string InterceptableLocationAttribute, string DelegateCastType, EquatableArray ParameterAssignments, @@ -45,14 +45,14 @@ GeneratorContext context DiagnosticInfo.Create( Diagnostics.MultipleParametersUseAttribute, a.LocationInfo, - [eventAttribute] + [eventAttribute.Value] ) ); } - extension(InvocationMethodInfo) + extension(MapHandlerMethodInfo) { - internal static InvocationMethodInfo Create( + internal static MapHandlerMethodInfo Create( IMethodSymbol methodSymbol, GeneratorContext context ) @@ -95,7 +95,7 @@ GeneratorContext context .UnwrapReturnType(context) .ToGloballyQualifiedName(); - return new InvocationMethodInfo( + return new MapHandlerMethodInfo( MethodType: MethodType.MapHandler, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType: handlerCastType, diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index 66c5dd77..c791e197 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -5,15 +5,6 @@ namespace MinimalLambda.SourceGenerators.Models; -internal enum MapHandlerParameterSource -{ - Event, - Context, - CancellationToken, - KeyedServices, - Services, -} - internal record MapHandlerParameterInfo( string GloballyQualifiedType, bool IsStream, diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs new file mode 100644 index 00000000..6f011c93 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs @@ -0,0 +1,10 @@ +namespace MinimalLambda.SourceGenerators.Models; + +internal enum MapHandlerParameterSource +{ + Event, + Context, + CancellationToken, + KeyedServices, + Services, +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs index d6a5540b..7a10f1a3 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs @@ -22,7 +22,7 @@ internal static bool Predicate(SyntaxNode node, params string[] methodNames) => && node.TryGetMethodName(out var name) && methodNames.Contains(name); - internal static InvocationMethodInfo? Transformer( + internal static MapHandlerMethodInfo? Transformer( GeneratorSyntaxContext context, Func delegateFilter, CancellationToken cancellationToken @@ -76,7 +76,7 @@ is not IInvocationOperation )!; throw new NotImplementedException(); - // return new InvocationMethodInfo( + // return new MapHandlerMethodInfo( // // targetOperation.TargetMethod.Name, // // LocationInfo: context.Node.CreateLocationInfo(), // // DelegateInfo: delegateInfo.Value, diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index da285979..f312fe1b 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -42,7 +42,7 @@ CancellationToken cancellationToken return targetOperation.TargetMethod.Name switch { - "MapHandler" => InvocationMethodInfo.Create(method, context), + "MapHandler" => MapHandlerMethodInfo.Create(method, context), "OnInit" => LifecycleMethodInfo.Create(method, MethodType.OnInit, context), "OnShutdown" => LifecycleMethodInfo.Create(method, MethodType.OnShutdown, context), var methodName => throw new InvalidOperationException($"Unknown method '{methodName}"), diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs index 14a12f05..db966a2e 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class MapHandlerSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.MapHandlerMethodName); - internal static InvocationMethodInfo? Transformer( + internal static MapHandlerMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, _ => false, cancellationToken); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs index 04cd10a7..d95c5fd1 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class OnInitSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnInitMethodName); - internal static InvocationMethodInfo? Transformer( + internal static MapHandlerMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs index 8d57b6f0..2336d0fd 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs @@ -9,7 +9,7 @@ internal static class OnShutdownSyntaxProvider internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnShutdownMethodName); - internal static InvocationMethodInfo? Transformer( + internal static MapHandlerMethodInfo? Transformer( GeneratorSyntaxContext context, CancellationToken cancellationToken ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); From 221bca85692732b8f995b3f1174dcb7c6fd3615e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 13:20:40 -0500 Subject: [PATCH 30/67] feat(source-generators): refactor return type handling and improve lifecycle method support - Refactored `HasMeaningfulReturnType` to support unwrapped return types with `out` parameter. - Replaced inline `UnwrapReturnType` calls with method invocation for cleaner logic. - Introduced `CreateForInit` and `CreateForShutdown` methods in `LifecycleMethodInfo` for clarity. - Migrated `IsAnyTypeMatch` to extensions for streamlined type matching logic. - Updated lifecycle method handling to align with refactored return type logic. --- .../Extensions/MethodSymbolExtensions.cs | 53 ++++++++--------- .../Extensions/WellKnownTypesExtensions.cs | 16 +++-- .../Models/LifecycleMethodInfo.cs | 58 +++++++------------ .../Models/MapHandlerMethodInfo.cs | 11 ++-- .../SyntaxProviders/HandlerSyntaxProvider.cs | 4 +- 5 files changed, 64 insertions(+), 78 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs index f5d6471f..f200a977 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -2,6 +2,7 @@ using MinimalLambda.SourceGenerators; using MinimalLambda.SourceGenerators.Extensions; using MinimalLambda.SourceGenerators.WellKnownTypes; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace Microsoft.CodeAnalysis; @@ -32,27 +33,23 @@ internal bool IsAwaitable(GeneratorContext context) var returnType = methodSymbol.ReturnType; // Check for Task and Task - var task = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task - ); + var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); if (returnType.Equals(task, SymbolEqualityComparer.Default)) return true; - var taskOfT = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T - ); + var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); if (returnType.Equals(taskOfT, SymbolEqualityComparer.Default)) return true; // Check for ValueTask and ValueTask var valueTask = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask + WellKnownType.System_Threading_Tasks_ValueTask ); if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) return true; var valueTaskOfT = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask_T + WellKnownType.System_Threading_Tasks_ValueTask_T ); if (returnType.OriginalDefinition.Equals(valueTaskOfT, SymbolEqualityComparer.Default)) return true; @@ -64,39 +61,39 @@ internal bool IsAwaitable(GeneratorContext context) .Any(m => m.Parameters.Length == 0 && !m.IsStatic); } - internal bool HasMeaningfulReturnType(GeneratorContext context) + internal bool HasMeaningfulReturnType( + GeneratorContext context, + out ITypeSymbol unwrappedReturnType + ) { var returnType = methodSymbol.ReturnType; - var voidType = context.WellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Void); - if (returnType.Equals(voidType, SymbolEqualityComparer.Default)) - return false; - - var task = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task - ); - if (returnType.Equals(task, SymbolEqualityComparer.Default)) - return false; - - var valueTask = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask - ); - if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + if (IsVoidLike(returnType)) + { + unwrappedReturnType = returnType; return false; + } + unwrappedReturnType = methodSymbol.UnwrapReturnType(context); return true; + + bool IsVoidLike(ITypeSymbol type) => + context.WellKnownTypes.IsAnyTypeMatch( + type, + WellKnownType.System_Void, + WellKnownType.System_Threading_Tasks_Task, + WellKnownType.System_Threading_Tasks_ValueTask + ); } - internal ITypeSymbol UnwrapReturnType(GeneratorContext context) + private ITypeSymbol UnwrapReturnType(GeneratorContext context) { if (methodSymbol.ReturnType is not INamedTypeSymbol namedReturnType) return (INamedTypeSymbol)methodSymbol.ReturnType; - var taskOfT = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T - ); + var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); var valueTaskOfT = context.WellKnownTypes.Get( - WellKnownTypeData.WellKnownType.System_Threading_Tasks_ValueTask_T + WellKnownType.System_Threading_Tasks_ValueTask_T ); var originalDef = namedReturnType.OriginalDefinition; diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs index d49d94f1..8db15b4a 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs @@ -6,6 +6,17 @@ namespace MinimalLambda.SourceGenerators.WellKnownTypes; internal static class WellKnownTypesExtensions { + // Open issue w/ extension blocks: https://github.com/dotnet/roslyn/issues/80024 + // ReSharper disable once MoveToExtensionBlock + internal static bool IsAnyTypeMatch( + this WellKnownTypes wellKnownTypes, + ITypeSymbol type, + params WellKnownType[] types + ) => + types + .Select(wellKnownTypes.Get) + .Any(foundType => type.Equals(foundType, SymbolEqualityComparer.Default)); + extension(WellKnownTypes wellKnownTypes) { internal bool IsTypeMatch(ITypeSymbol type, WellKnownType wellKnownType) @@ -13,10 +24,5 @@ internal bool IsTypeMatch(ITypeSymbol type, WellKnownType wellKnownType) var foundType = wellKnownTypes.Get(wellKnownType); return type.Equals(foundType, SymbolEqualityComparer.Default); } - - internal bool IsAnyTypeMatch(ITypeSymbol type, WellKnownType[] types) => - types - .Select(wellKnownTypes.Get) - .Any(foundType => type.Equals(foundType, SymbolEqualityComparer.Default)); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index f61075c6..7471246d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -3,8 +3,6 @@ using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.WellKnownTypes; namespace MinimalLambda.SourceGenerators.Models; @@ -12,16 +10,17 @@ internal record LifecycleMethodInfo( string InterceptableLocationAttribute, string DelegateCastType, EquatableArray DiagnosticInfos, - MethodType MethodType + MethodType MethodType, + string HandleResponseAssignment, + string HandleReturningFromMethod ) : IMethodInfo; internal static class LifecycleMethodInfoExtensions { extension(LifecycleMethodInfo) { - internal static LifecycleMethodInfo Create( + internal static LifecycleMethodInfo CreateForInit( IMethodSymbol methodSymbol, - MethodType methodType, GeneratorContext context ) { @@ -31,50 +30,35 @@ GeneratorContext context throw new InvalidOperationException("Unable to get interceptable location"); var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( - parameter => MapHandlerParameterInfo.Create(parameter, context) + parameter => LifecycleHandlerParameterInfo.Create(parameter, context) ); var isAwaitable = methodSymbol.IsAwaitable(context); - var hasResponse = methodSymbol.HasMeaningfulReturnType(context); - - var isReturnTypeStream = - hasResponse - && context.WellKnownTypes.IsTypeMatch( - methodSymbol.ReturnType, - WellKnownTypeData.WellKnownType.System_IO_Stream - ); - - var hasEvent = assignments.Any(a => a.IsEvent); - - var eventType = hasEvent - ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() - : null; - - var isEventTypeStream = - hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); + var hasResponse = methodSymbol.HasMeaningfulReturnType( + context, + out var unwrappedReturnType + ); var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); - var unwrappedReturnType = methodSymbol - .UnwrapReturnType(context) - .ToGloballyQualifiedName(); + // var unwrappedReturnType = methodSymbol + // .UnwrapReturnType(context) + // .ToGloballyQualifiedName(); return new LifecycleMethodInfo( - MethodType: methodType, + MethodType: MethodType.OnInit, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType: handlerCastType, - // ParameterAssignments: assignments.ToEquatableArray(), - // IsAwaitable: isAwaitable, - // HasResponse: hasResponse, - // IsResponseTypeStream: isReturnTypeStream, - // IsEventTypeStream: isEventTypeStream, - // HasEvent: hasEvent, - // EventType: eventType, - // UnwrappedResponseType: unwrappedReturnType, - // HasAnyFromKeyedServices: hasAnyKeyedServices, - DiagnosticInfos: diagnostics.ToEquatableArray() + DiagnosticInfos: diagnostics.ToEquatableArray(), + HandleResponseAssignment: "", + HandleReturningFromMethod: "" ); } + + internal static LifecycleMethodInfo CreateForShutdown( + IMethodSymbol methodSymbol, + GeneratorContext context + ) => throw new NotImplementedException(); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs index ab12790b..3513ce96 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs @@ -71,7 +71,10 @@ GeneratorContext context var isAwaitable = methodSymbol.IsAwaitable(context); - var hasResponse = methodSymbol.HasMeaningfulReturnType(context); + var hasResponse = methodSymbol.HasMeaningfulReturnType( + context, + out var unwrappedReturnType + ); var isReturnTypeStream = hasResponse @@ -91,10 +94,6 @@ GeneratorContext context var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); - var unwrappedReturnType = methodSymbol - .UnwrapReturnType(context) - .ToGloballyQualifiedName(); - return new MapHandlerMethodInfo( MethodType: MethodType.MapHandler, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), @@ -106,7 +105,7 @@ GeneratorContext context IsEventTypeStream: isEventTypeStream, HasEvent: hasEvent, EventType: eventType, - UnwrappedResponseType: unwrappedReturnType, + UnwrappedResponseType: unwrappedReturnType.ToGloballyQualifiedName(), HasAnyFromKeyedServices: hasAnyKeyedServices, DiagnosticInfos: diagnostics.ToEquatableArray() ); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs index f312fe1b..d1594d38 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -43,8 +43,8 @@ CancellationToken cancellationToken return targetOperation.TargetMethod.Name switch { "MapHandler" => MapHandlerMethodInfo.Create(method, context), - "OnInit" => LifecycleMethodInfo.Create(method, MethodType.OnInit, context), - "OnShutdown" => LifecycleMethodInfo.Create(method, MethodType.OnShutdown, context), + "OnInit" => LifecycleMethodInfo.CreateForInit(method, context), + "OnShutdown" => LifecycleMethodInfo.CreateForShutdown(method, context), var methodName => throw new InvalidOperationException($"Unknown method '{methodName}"), }; } From 8f514c55051b9cb15d29ee0a4ad4ccbaacb03d6f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 16:09:31 -0500 Subject: [PATCH 31/67] feat(source-generators): refactor lifecycle handling and cleanup unused code - Removed outdated `LambdaHandler.g.cs` snapshot and related test utilities. - Simplified `MapHandlerParameterInfo` type-matching logic by reducing parameter array allocations. - Cleaned up unused properties (`IsStream`, `IsEvent`) in `LifecycleHandlerParameterInfo`. - Improved lifecycle method assignments and added refined response type handling. - Refactored emitter templates and streamlined `ShouldAwait` and return value logic for clarity. - Enhanced null handling and robustness in `MethodSymbolExtensions` for unwrapped return types. --- .../Emitters/GenericHandlerSources.cs | 11 ++-- .../Emitters/MapHandlerSources.cs | 2 +- .../Extensions/MethodSymbolExtensions.cs | 24 +++++--- .../Models/LifecycleHandlerParameterInfo.cs | 26 +-------- .../Models/LifecycleMethodInfo.cs | 58 ++++++++++++++++--- .../Models/MapHandlerMethodInfo.cs | 2 +- .../Models/MapHandlerParameterInfo.cs | 6 +- .../Templates/GenericHandler.scriban | 29 ++++------ ...BaseMethodCall#LambdaHandler.g.verified.cs | 43 -------------- .../VerifyTests/OnInitVerifyTests.cs | 3 +- 10 files changed, 91 insertions(+), 113 deletions(-) delete mode 100644 tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index c8b47d1a..f8141282 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -1,3 +1,4 @@ +using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using MinimalLambda.SourceGenerators.Models; @@ -11,7 +12,7 @@ internal static class GenericHandlerSources /// the return type of the actual handler. /// internal static string Generate( - EquatableArray higherOrderMethodInfos, + EquatableArray lifecycleMethodInfos, string methodName, string? wrapperReturnType, string? defaultWrapperReturnValue, @@ -45,7 +46,7 @@ string targetType // var shouldAwait = // fullWrapperReturnType // != higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - // && higherOrderMethodInfo.DelegateInfo.IsAwaitable; + // && higherOrderMethodInfo.DelegateInfo.ShouldAwait; // // // should return response // var shouldReturnResponse = @@ -64,7 +65,7 @@ string targetType // // // should wrap the response in a Task // var shouldWrapResponse = - // shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.IsAwaitable; + // shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.ShouldAwait; // // // default return value // var defaultReturnValueString = !shouldAwait @@ -91,8 +92,8 @@ string targetType var model = new { - Name = methodName, - // Calls = calls, + Name = lifecycleMethodInfos.First().MethodType, + Calls = lifecycleMethodInfos, MinimalLambdaEmitter.GeneratedCodeAttribute, }; diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 8c210409..4bb4a4fe 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -18,7 +18,7 @@ internal static string Generate(EquatableArray mapHandlerI // IsResponseFeatureRequired = isResponseFeatureRequired, // delegateInfo.HasAnyKeyedServiceParameter, // HandlerArgs = handlerArgs, - // ShouldAwait = delegateInfo.IsAwaitable, + // ShouldAwait = delegateInfo.ShouldAwait, // InputEvent = inputEvent, // OutputResponse = outputResponse, // }; diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs index f200a977..ba74fb93 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using MinimalLambda.SourceGenerators; using MinimalLambda.SourceGenerators.Extensions; @@ -30,7 +31,10 @@ internal string GetCastableSignature() internal bool IsAwaitable(GeneratorContext context) { - var returnType = methodSymbol.ReturnType; + if (methodSymbol.ReturnType is not INamedTypeSymbol namedTypeSymbol) + return false; + + var returnType = namedTypeSymbol.ConstructedFrom; // Check for Task and Task var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); @@ -63,18 +67,24 @@ internal bool IsAwaitable(GeneratorContext context) internal bool HasMeaningfulReturnType( GeneratorContext context, - out ITypeSymbol unwrappedReturnType + [NotNullWhen(true)] out INamedTypeSymbol? unwrappedReturnType ) { - var returnType = methodSymbol.ReturnType; + unwrappedReturnType = null; + + if (methodSymbol.ReturnType is not INamedTypeSymbol namedTypeSymbol) + return false; - if (IsVoidLike(returnType)) + if (IsVoidLike(namedTypeSymbol.ConstructedFrom)) { - unwrappedReturnType = returnType; + unwrappedReturnType = namedTypeSymbol; return false; } - unwrappedReturnType = methodSymbol.UnwrapReturnType(context); + if (methodSymbol.UnwrapReturnType(context) is not INamedTypeSymbol namedTypeSymbol2) + return false; + + unwrappedReturnType = namedTypeSymbol2; return true; bool IsVoidLike(ITypeSymbol type) => @@ -89,7 +99,7 @@ bool IsVoidLike(ITypeSymbol type) => private ITypeSymbol UnwrapReturnType(GeneratorContext context) { if (methodSymbol.ReturnType is not INamedTypeSymbol namedReturnType) - return (INamedTypeSymbol)methodSymbol.ReturnType; + return methodSymbol.ReturnType; var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); var valueTaskOfT = context.WellKnownTypes.Get( diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs index b0660329..c0237144 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs @@ -7,10 +7,8 @@ namespace MinimalLambda.SourceGenerators.Models; internal record LifecycleHandlerParameterInfo( string GloballyQualifiedType, - bool IsStream, string Assignment, string InfoComment, - bool IsEvent, bool IsFromKeyedService, LocationInfo? LocationInfo, MapHandlerParameterSource Source, @@ -26,12 +24,8 @@ internal static DiagnosticResult Create( GeneratorContext context ) { - var paramType = parameter.Type.ToGloballyQualifiedName(); - var parameterInfo = new LifecycleHandlerParameterInfo( parameter.Type.ToGloballyQualifiedName(), - context.WellKnownTypes.IsTypeMatch(parameter.Type, WellKnownType.System_IO_Stream), - IsEvent: false, IsFromKeyedService: false, LocationInfo: LocationInfo.Create(parameter), Assignment: string.Empty, @@ -40,29 +34,11 @@ GeneratorContext context Source: MapHandlerParameterSource.Services ); - // event - if (parameter.IsFromEvent(context)) - return DiagnosticResult.Success( - parameterInfo with - { - Assignment = parameterInfo.IsStream - // stream event - ? "context.Features.GetRequired().EventStream" - // non stream event - : $"context.GetRequiredEvent<{paramType}>()", - IsEvent = true, - Source = MapHandlerParameterSource.Event, - } - ); - // context if ( context.WellKnownTypes.IsAnyTypeMatch( parameter.Type, - [ - WellKnownType.Amazon_Lambda_Core_ILambdaContext, - WellKnownType.MinimalLambda_ILambdaInvocationContext, - ] + WellKnownType.MinimalLambda_ILambdaLifecycleContext ) ) return DiagnosticResult.Success( diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index 7471246d..da8a28fd 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -3,6 +3,7 @@ using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.WellKnownTypes; namespace MinimalLambda.SourceGenerators.Models; @@ -10,9 +11,13 @@ internal record LifecycleMethodInfo( string InterceptableLocationAttribute, string DelegateCastType, EquatableArray DiagnosticInfos, + EquatableArray ParameterAssignments, + bool ShouldAwait, MethodType MethodType, string HandleResponseAssignment, - string HandleReturningFromMethod + string HandleReturningFromMethod, + string ReturnType, + bool HasAnyFromKeyedServices ) : IMethodInfo; internal static class LifecycleMethodInfoExtensions @@ -40,19 +45,58 @@ GeneratorContext context out var unwrappedReturnType ); - var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + var unwrappedReturnIsBool = + hasResponse + && context.WellKnownTypes.IsTypeMatch( + unwrappedReturnType!, + WellKnownTypeData.WellKnownType.System_Boolean + ); + + /* + * Return rules: + * If handler returns `Task`, no need to await, can be returned on its own + * If handler returns `ValueTask`, must be awaited and then returned + * If handler returns `bool`, it doesn't need to be awaited and can be returned as + * result + * default + async, return true + * default, return Task.FromResult(true); + */ + + var returnIsTaskBool = + methodSymbol.ReturnType is INamedTypeSymbol namedTypeSymbol + && context.WellKnownTypes.IsTypeMatch( + namedTypeSymbol.ConstructedFrom, + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T + ) + && unwrappedReturnIsBool; + + var shouldAwait = isAwaitable && !returnIsTaskBool; - // var unwrappedReturnType = methodSymbol - // .UnwrapReturnType(context) - // .ToGloballyQualifiedName(); + var handleResponseAssignment = + hasResponse && unwrappedReturnIsBool ? "var response = " : string.Empty; + + var handleReturningFromMethod = hasResponse switch + { + true when returnIsTaskBool || (unwrappedReturnIsBool && isAwaitable) => + "return response;", + true when unwrappedReturnIsBool => "return Task.FromResult(response);", + _ when isAwaitable => "return true;", + _ => "return Task.FromResult(true);", + }; + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); return new LifecycleMethodInfo( MethodType: MethodType.OnInit, InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), DelegateCastType: handlerCastType, DiagnosticInfos: diagnostics.ToEquatableArray(), - HandleResponseAssignment: "", - HandleReturningFromMethod: "" + ParameterAssignments: assignments.ToEquatableArray(), + ShouldAwait: shouldAwait, + HandleResponseAssignment: handleResponseAssignment, + HandleReturningFromMethod: handleReturningFromMethod, + ReturnType: "Task", + HasAnyFromKeyedServices: hasAnyKeyedServices ); } diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs index 3513ce96..decaf98f 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs @@ -105,7 +105,7 @@ out var unwrappedReturnType IsEventTypeStream: isEventTypeStream, HasEvent: hasEvent, EventType: eventType, - UnwrappedResponseType: unwrappedReturnType.ToGloballyQualifiedName(), + UnwrappedResponseType: unwrappedReturnType?.ToGloballyQualifiedName(), HasAnyFromKeyedServices: hasAnyKeyedServices, DiagnosticInfos: diagnostics.ToEquatableArray() ); diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index c791e197..42cc90f3 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -59,10 +59,8 @@ parameterInfo with if ( context.WellKnownTypes.IsAnyTypeMatch( parameter.Type, - [ - WellKnownType.Amazon_Lambda_Core_ILambdaContext, - WellKnownType.MinimalLambda_ILambdaInvocationContext, - ] + WellKnownType.Amazon_Lambda_Core_ILambdaContext, + WellKnownType.MinimalLambda_ILambdaInvocationContext ) ) return DiagnosticResult.Success( diff --git a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban index c0ac89ce..d14d0752 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban @@ -2,42 +2,33 @@ file static class GeneratedLambda{{ name }}BuilderExtensions { {{~ for call in calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] - internal static {{ call.target_type }} {{ name }}Interceptor{{ for.index }}{{ call.generic_parameters }}( - this {{ call.target_type }} application, + {{ call.interceptable_location_attribute }} + internal static ILambdaOnInitBuilder {{ name }}Interceptor{{ for.index }}( + this ILambdaOnInitBuilder application, Delegate handler ) { - var castHandler = {{ call.handler_signature }}; + var castHandler = Utilities.Cast(handler, {{ call.delegate_cast_type }}); return application.{{ name }}({{ name }}); - {{ if call.should_await ~}} async {{ end ~}}{{ call.wrapper_return_type }} {{ name }}(ILambdaLifecycleContext context) + {{ if call.should_await ~}} async {{ end ~}}{{ call.return_type }} {{ name }}(ILambdaLifecycleContext context) { - {{~ if call.has_any_keyed_service_parameter ~}} + {{~ if call.has_any_from_keyed_services ~}} if (context.ServiceProvider.GetService() is not IServiceProviderIsKeyedService) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } {{~ end ~}} - {{~ for handler_arg in call.handler_args ~}} - // {{ handler_arg.string }} + {{~ for handler_arg in call.parameter_assignments ~}} var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} - {{ if call.should_return_response ~}} var response = {{ end }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.handler_args }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); - {{~ if call.should_return_response ~}} - {{~ if call.should_wrap_response ~}} - return Task.FromResult(response); - {{~ else ~}} - return response; - {{~ end ~}} - {{~ else if call.default_return_value != null ~}} - return {{ call.default_return_value }}; - {{~ end ~}} + {{ call.handle_response_assignment }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{ call.handle_returning_from_method }} } } {{~ if !for.last ~}} - + {{~ end ~}} {{~ end ~}} } \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs index 90d7e8f9..2f06c99e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs @@ -22,7 +22,8 @@ await GeneratorTestHelpers.Verify( ); await lambda.RunAsync(); - """ + """, + 0 ); [Fact] From 1a937f070c38bfc690d3f416f3ca9b234e9f810b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 16:12:25 -0500 Subject: [PATCH 32/67] fix(source-generators): correct lifecycle method return type documentation - Fixed inaccurate comments regarding the handling of `Task`, `ValueTask`, and `bool` return types. - Updated documentation for better clarity and alignment with the actual return type handling logic. --- .../Models/LifecycleMethodInfo.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index da8a28fd..289517b1 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -57,9 +57,7 @@ out var unwrappedReturnType * If handler returns `Task`, no need to await, can be returned on its own * If handler returns `ValueTask`, must be awaited and then returned * If handler returns `bool`, it doesn't need to be awaited and can be returned as - * result - * default + async, return true - * default, return Task.FromResult(true); + * result default + async, return true default, return Task.FromResult(true); */ var returnIsTaskBool = From 0c7c30ed58088951421f7fe1e5af41a77519dfe8 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 16:13:36 -0500 Subject: [PATCH 33/67] refactor(source-generators): remove `GloballyQualifiedType` from `LifecycleHandlerParameterInfo` - Deleted the unused `GloballyQualifiedType` property from `LifecycleHandlerParameterInfo`. - Updated parameter initialization logic in lifecycle handlers accordingly to reflect the removal. --- .../Models/LifecycleHandlerParameterInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs index c0237144..5e1665b2 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs @@ -6,7 +6,6 @@ namespace MinimalLambda.SourceGenerators.Models; internal record LifecycleHandlerParameterInfo( - string GloballyQualifiedType, string Assignment, string InfoComment, bool IsFromKeyedService, @@ -25,7 +24,6 @@ GeneratorContext context ) { var parameterInfo = new LifecycleHandlerParameterInfo( - parameter.Type.ToGloballyQualifiedName(), IsFromKeyedService: false, LocationInfo: LocationInfo.Create(parameter), Assignment: string.Empty, From 6271d39f80d3db91a271a4044baebec9d6b8ec94 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 27 Dec 2025 16:27:37 -0500 Subject: [PATCH 34/67] feat(source-generators): enhance `OnShutdown` lifecycle handling and cleanup unused files - Implemented `CreateForShutdown` method in `LifecycleMethodInfo` to improve shutdown handling logic. - Refined lifecycle method assignments, including `ShouldAwait` and return response handling. - Updated `GenericHandler.scriban` template to align with the updated shutdown logic and response types. - Removed outdated and unused `LambdaHandler.g.cs` snapshot and related test utilities for cleanup. --- .../Models/LifecycleMethodInfo.cs | 45 ++++++++++++++++++- .../Templates/GenericHandler.scriban | 6 ++- ...BaseMethodCall#LambdaHandler.g.verified.cs | 43 ------------------ .../VerifyTests/OnShutdownVerifyTests.cs | 3 +- 4 files changed, 50 insertions(+), 47 deletions(-) delete mode 100644 tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index 289517b1..2e986d0e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -101,6 +101,49 @@ methodSymbol.ReturnType is INamedTypeSymbol namedTypeSymbol internal static LifecycleMethodInfo CreateForShutdown( IMethodSymbol methodSymbol, GeneratorContext context - ) => throw new NotImplementedException(); + ) + { + var handlerCastType = methodSymbol.GetCastableSignature(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => LifecycleHandlerParameterInfo.Create(parameter, context) + ); + + var isAwaitable = methodSymbol.IsAwaitable(context); + + var returnIsTask = context.WellKnownTypes.IsTypeMatch( + methodSymbol.ReturnType, + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task + ); + + var shouldAwait = isAwaitable && !returnIsTask; + + var handleResponseAssignment = returnIsTask ? "var response = " : string.Empty; + + var handleReturningFromMethod = shouldAwait switch + { + _ when returnIsTask => "return response;", + true => string.Empty, + _ => "return Task.CompletedTask;", + }; + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + + return new LifecycleMethodInfo( + MethodType: MethodType.OnShutdown, + InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + DelegateCastType: handlerCastType, + DiagnosticInfos: diagnostics.ToEquatableArray(), + ParameterAssignments: assignments.ToEquatableArray(), + ShouldAwait: shouldAwait, + HandleResponseAssignment: handleResponseAssignment, + HandleReturningFromMethod: handleReturningFromMethod, + ReturnType: "Task", + HasAnyFromKeyedServices: hasAnyKeyedServices + ); + } } } diff --git a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban index d14d0752..6d9ef09c 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban @@ -3,8 +3,8 @@ { {{~ for call in calls ~}} {{ call.interceptable_location_attribute }} - internal static ILambdaOnInitBuilder {{ name }}Interceptor{{ for.index }}( - this ILambdaOnInitBuilder application, + internal static ILambda{{ name }}Builder {{ name }}Interceptor{{ for.index }}( + this ILambda{{ name }}Builder application, Delegate handler ) { @@ -24,7 +24,9 @@ var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} {{ call.handle_response_assignment }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{~ if call.handle_returning_from_method != "" ~}} {{ call.handle_returning_from_method }} + {{~ end ~}} } } {{~ if !for.last ~}} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs index 9332452b..cd6c2804 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs @@ -22,7 +22,8 @@ await GeneratorTestHelpers.Verify( ); await lambda.RunAsync(); - """ + """, + 0 ); [Fact] From b7cdb0cda4a7a389130112ea5a940d3c57bdb0cb Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:10:14 -0500 Subject: [PATCH 35/67] feat(minimal-lambda): enable `OnInit` lifecycle for output formatting cleanup - Reintroduced and finalized `OnInit` method to reset Lambda runtime output formatting. - Added logging support using `ILogger` to enhance debugging and observability. --- ...utFormattingLambdaApplicationExtensions.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs b/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs index c9c4a52e..58ade079 100644 --- a/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs +++ b/src/MinimalLambda/Builder/OnInit/OutputFormattingLambdaApplicationExtensions.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Logging; + namespace MinimalLambda.Builder; /// Provides extension methods for managing Lambda runtime output formatting. @@ -17,18 +19,17 @@ this ILambdaOnInitBuilder application { ArgumentNullException.ThrowIfNull(application); - // application.OnInit( - // (ILogger? logger = null) => - // { - // // This will clear the output formatting set by the Lambda runtime. - // Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true - // }); - // - // logger?.LogInformation("Clearing Lambda output formatting"); - // - // return Task.FromResult(true); - // } - // ); + application.OnInit( + (ILogger? logger = null) => + { + // This will clear the output formatting set by the Lambda runtime. + Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }); + + logger?.LogInformation("Clearing Lambda output formatting"); + + return Task.FromResult(true); + } + ); return application; } From 769eb0b5a8fcf6f01f6184972165564ce7f51120 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:12:30 -0500 Subject: [PATCH 36/67] refactor(source-generators): simplify `Generate` methods and remove commented-out code - Removed unused parameters from `Generate` methods in `GenericHandlerSources`. - Deleted outdated and commented-out code for improved code readability and maintainability. - Updated `MinimalLambdaEmitter` to align with the simplified `Generate` method signatures. --- .../Emitters/GenericHandlerSources.cs | 122 +----------------- .../Emitters/MapHandlerSources.cs | 17 --- .../Emitters/MinimalLambdaEmitter.cs | 20 +-- 3 files changed, 3 insertions(+), 156 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index f8141282..e80f50fb 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -11,85 +11,8 @@ internal static class GenericHandlerSources /// handler. The return type of the wrapper is a Task or Task<T> depending on /// the return type of the actual handler. /// - internal static string Generate( - EquatableArray lifecycleMethodInfos, - string methodName, - string? wrapperReturnType, - string? defaultWrapperReturnValue, - string targetType - ) + internal static string Generate(EquatableArray lifecycleMethodInfos) { - // var calls = higherOrderMethodInfos - // .Select(higherOrderMethodInfo => - // { - // // build handler function signature - // var handlerSignature = higherOrderMethodInfo.DelegateInfo.BuildHandlerCastCall(); - // - // // get arguments for handler - // var handlerArgs = - // higherOrderMethodInfo.DelegateInfo.BuildHandlerParameterAssignment(); - // - // // get the return type of the wrapper function wrapped in a Task - // var fullWrapperReturnType = wrapperReturnType is not null - // ? $"global::System.Threading.Tasks.Task<{wrapperReturnType}>" - // : "global::System.Threading.Tasks.Task"; - // - // // get the return type of the wrapper function wrapped in a Task - shortened - // // to just use Task - // var shortFullWrapperReturnType = wrapperReturnType is not null - // ? $"Task<{wrapperReturnType}>" - // : "Task"; - // - // // should await determined by whether the delegate is awaitable and if the - // // delegate - // // return type matches the wrapper return type 1:1 - // var shouldAwait = - // fullWrapperReturnType - // != higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - // && higherOrderMethodInfo.DelegateInfo.ShouldAwait; - // - // // should return response - // var shouldReturnResponse = - // higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - // != TypeConstants.Void - // && ( - // wrapperReturnType - // == higherOrderMethodInfo - // .DelegateInfo - // .ReturnTypeInfo - // .UnwrappedFullyQualifiedType - // || fullWrapperReturnType - // == - // higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - // ); - // - // // should wrap the response in a Task - // var shouldWrapResponse = - // shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.ShouldAwait; - // - // // default return value - // var defaultReturnValueString = !shouldAwait - // ? defaultWrapperReturnValue is not null - // ? $"Task.FromResult({defaultWrapperReturnValue})" - // : "Task.CompletedTask" - // : defaultWrapperReturnValue; - // - // return new - // { - // Location = higherOrderMethodInfo.InterceptableLocationInfo, - // WrapperReturnType = shortFullWrapperReturnType, - // HandlerSignature = handlerSignature, - // ShouldAwait = shouldAwait, - // higherOrderMethodInfo.DelegateInfo.HasAnyKeyedServiceParameter, - // HandlerArgs = handlerArgs, - // ShouldReturnResponse = shouldReturnResponse, - // ShouldWrapResponse = shouldWrapResponse, - // DefaultReturnValue = defaultReturnValueString, - // TargetType = targetType, - // }; - // }) - // .ToArray(); - var model = new { Name = lifecycleMethodInfos.First().MethodType, @@ -103,47 +26,4 @@ string targetType return outCode; } - - // private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) - // { - // var handlerArgs = delegateInfo - // .Parameters.Select(param => new HandlerArg - // { - // String = param.ToPublicString(), - // Assignment = param.Source switch - // { - // // CancellationToken -> get directly from arguments - // ParameterSource.CancellationToken => "context.CancellationToken", - // - // // ILambdaLifecycleContext -> get directly from arguments - // ParameterSource.LifecycleContext => "context", - // - // // inject keyed service from the DI container - required - // ParameterSource.KeyedService when param.IsRequired => - // - // $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - // - // // inject keyed service from the DI container - optional - // ParameterSource.KeyedService => - // - // $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - // - // // default: inject service from the DI container - required - // _ when param.IsRequired => - // - // $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - // - // // default: inject service from the DI container - optional - // _ => - // - // $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - // }, - // }) - // .ToArray(); - // - // return handlerArgs; - // } - // - // // ReSharper disable NotAccessedPositionalProperty.Local - // private readonly record struct HandlerArg(string String, string Assignment); } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index 4bb4a4fe..ab1b9c88 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -7,23 +7,6 @@ internal static class MapHandlerSources { internal static string Generate(EquatableArray mapHandlerInvocationInfos) { - // var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandler => - // { - // return new - // { - // InterceptableLocationAttribute = - // mapHandler.InterceptableLocationInfo.ToInterceptsLocationAttribute(), - // HandlerSignature = handlerSignature, - // IsEventFeatureRequired = isEventFeatureRequired, - // IsResponseFeatureRequired = isResponseFeatureRequired, - // delegateInfo.HasAnyKeyedServiceParameter, - // HandlerArgs = handlerArgs, - // ShouldAwait = delegateInfo.ShouldAwait, - // InputEvent = inputEvent, - // OutputResponse = outputResponse, - // }; - // }); - var template = TemplateHelper.LoadTemplate( GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile ); diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index 327e865e..a4728658 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -66,27 +66,11 @@ namespace MinimalLambda.Generated // add OnInit interceptors if (compilationInfo.OnInitInvocationInfos.Count >= 1) - outputs.Add( - GenericHandlerSources.Generate( - compilationInfo.OnInitInvocationInfos, - "OnInit", - "bool", - "true", - "ILambdaOnInitBuilder" - ) - ); + outputs.Add(GenericHandlerSources.Generate(compilationInfo.OnInitInvocationInfos)); // add OnShutdown interceptors if (compilationInfo.OnShutdownInvocationInfos.Count >= 1) - outputs.Add( - GenericHandlerSources.Generate( - compilationInfo.OnShutdownInvocationInfos, - "OnShutdown", - null, - null, - "ILambdaOnShutdownBuilder" - ) - ); + outputs.Add(GenericHandlerSources.Generate(compilationInfo.OnShutdownInvocationInfos)); outputs.Add( """ From 1503153e86397b2273baf3e45d08f1bf19ed6e52 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:18:20 -0500 Subject: [PATCH 37/67] refactor(source-generators): simplify template rendering and enhance `TemplateHelper` usability - Replaced `LoadTemplate` calls with a new `Render` method in `TemplateHelper` for improved readability. - Added caching to `TemplateHelper` for rendering templates to optimize performance. - Removed unused template constants and outdated method references in `GeneratorConstants`. - Updated `Emitters` to align with the simplified and unified `TemplateHelper.Render` functionality. - Enhanced error handling and diagnostics in `TemplateHelper` to improve robustness and maintainability. --- .../Emitters/CommonSources.cs | 13 +-- .../Emitters/GenericHandlerSources.cs | 25 +++--- .../Emitters/MapHandlerSources.cs | 13 ++- .../Emitters/TemplateHelper.cs | 85 ++++++++++++++----- .../Emitters/UseMiddlewareTSource.cs | 5 +- .../GeneratorConstants.cs | 5 -- 6 files changed, 83 insertions(+), 63 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs index 16e1b8d3..e28bcc8f 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs @@ -2,14 +2,9 @@ namespace MinimalLambda.SourceGenerators; internal static class CommonSources { - internal static string Generate() - { - var model = new { MinimalLambdaEmitter.GeneratedCodeAttribute }; - - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.InterceptsLocationAttributeTemplateFile + internal static string Generate() => + TemplateHelper.Render( + GeneratorConstants.InterceptsLocationAttributeTemplateFile, + new { MinimalLambdaEmitter.GeneratedCodeAttribute } ); - - return template.Render(model); - } } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs index e80f50fb..67e858be 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs @@ -11,19 +11,14 @@ internal static class GenericHandlerSources /// handler. The return type of the wrapper is a Task or Task<T> depending on /// the return type of the actual handler. /// - internal static string Generate(EquatableArray lifecycleMethodInfos) - { - var model = new - { - Name = lifecycleMethodInfos.First().MethodType, - Calls = lifecycleMethodInfos, - MinimalLambdaEmitter.GeneratedCodeAttribute, - }; - - var template = TemplateHelper.LoadTemplate(GeneratorConstants.GenericHandlerTemplateFile); - - var outCode = template.Render(model); - - return outCode; - } + internal static string Generate(EquatableArray lifecycleMethodInfos) => + TemplateHelper.Render( + GeneratorConstants.GenericHandlerTemplateFile, + new + { + Name = lifecycleMethodInfos.First().MethodType, + Calls = lifecycleMethodInfos, + MinimalLambdaEmitter.GeneratedCodeAttribute, + } + ); } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs index ab1b9c88..ffe71a8a 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs @@ -5,18 +5,15 @@ namespace MinimalLambda.SourceGenerators; internal static class MapHandlerSources { - internal static string Generate(EquatableArray mapHandlerInvocationInfos) - { - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile - ); - - return template.Render( + internal static string Generate( + EquatableArray mapHandlerInvocationInfos + ) => + TemplateHelper.Render( + GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile, new { MinimalLambdaEmitter.GeneratedCodeAttribute, MapHandlerCalls = mapHandlerInvocationInfos, } ); - } } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs index 0cb4a1a7..f4417a38 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Reflection; @@ -6,49 +7,87 @@ namespace MinimalLambda.SourceGenerators; +/// +/// Helper class for loading, caching, and rendering Scriban templates from embedded +/// resources. +/// internal static class TemplateHelper { - /// Loads a Scriban template from embedded resources - /// Name of the template file (without .scriban extension) - /// Parsed Scriban template - internal static Template LoadTemplate(string relativePath) + private static readonly ConcurrentDictionary Cache = new(); + + /// + /// Renders a Scriban template with the provided model. Templates are cached after first load + /// for performance. + /// + /// The type of the model to render + /// + /// Relative path to the template resource (e.g., + /// "Templates.Common.InterceptsLocationAttribute.scriban") + /// + /// The model object to render with the template + /// Rendered template as a string + /// Thrown if template is not found or has parsing errors + internal static string Render(string resourceName, TModel model) + { + var template = Cache.GetOrAdd(resourceName, LoadTemplate); + return template.Render(model); + } + + /// Loads a Scriban template from embedded resources. + /// + /// Relative path to the template resource (e.g., + /// "Templates.Common.InterceptsLocationAttribute.scriban") + /// + /// Parsed Scriban template ready for rendering + /// Thrown if template is not found or has parsing errors + private static Template LoadTemplate(string relativePath) { - var baseName = Assembly.GetExecutingAssembly().GetName().Name; + var assembly = Assembly.GetExecutingAssembly(); + var baseName = assembly.GetName().Name; + + // Convert relative path to resource name format var templateName = relativePath .TrimStart('.') .Replace(Path.DirectorySeparatorChar, '.') .Replace(Path.AltDirectorySeparatorChar, '.'); - var manifestTemplateName = Assembly - .GetExecutingAssembly() + // Find the manifest resource name that ends with our template name + var manifestTemplateName = assembly .GetManifestResourceNames() - .FirstOrDefault(x => x!.EndsWith(templateName, StringComparison.InvariantCulture)); + .FirstOrDefault(x => x.EndsWith(templateName, StringComparison.InvariantCulture)); if (string.IsNullOrEmpty(manifestTemplateName)) + { + var availableResources = string.Join(", ", assembly.GetManifestResourceNames()); throw new InvalidOperationException( - $"Did not find required resource ending in '{templateName}' in assembly '{baseName}'." + $"Did not find required resource ending in '{templateName}' in assembly '{baseName}'. " + + $"Available resources: {availableResources}" ); + } - using var stream = Assembly - .GetExecutingAssembly() - .GetManifestResourceStream(manifestTemplateName); + // Load the template content + using var stream = assembly.GetManifestResourceStream(manifestTemplateName); if (stream == null) throw new FileNotFoundException( - $"Template '{relativePath}' not found in embedded resources." + $"Template '{relativePath}' not found in embedded resources. " + + $"Manifest resource name: '{manifestTemplateName}'" ); using var reader = new StreamReader(stream); var templateContent = reader.ReadToEnd(); - var template = Template.Parse(templateContent); - if (template.HasErrors) - { - var errors = string.Join("; ", template.Messages.Select(m => m.ToString())); - throw new InvalidOperationException( - $"Template parsing errors in '{templateName}': {errors}" - ); - } - - return template; + // Parse and validate the template + var template = Template.Parse(templateContent, relativePath); + if (!template.HasErrors) + return template; + var errors = string.Join( + "\n", + template.Messages.Select(m => + $"{relativePath}({m.Span.Start.Line},{m.Span.Start.Column}): {m.Message}" + ) + ); + throw new InvalidOperationException( + $"Failed to parse template '{relativePath}':\n{errors}" + ); } } diff --git a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs index b182b296..4f49698a 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs @@ -81,9 +81,8 @@ n is AttributeConstants.FromServices or AttributeConstants.FromKeyedService }; }); - var template = TemplateHelper.LoadTemplate(GeneratorConstants.UseMiddlewareTTemplateFile); - - return template.Render( + return TemplateHelper.Render( + GeneratorConstants.UseMiddlewareTTemplateFile, new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } ); } diff --git a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs index 6bfb933d..6754706c 100644 --- a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs +++ b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs @@ -59,8 +59,6 @@ internal static class GeneratorConstants internal const string OnInitMethodName = "OnInit"; - internal const string UseOpenTelemetryTracingMethodName = "UseOpenTelemetryTracing"; - internal const string InterceptsLocationAttributeTemplateFile = "Templates/InterceptsLocationAttribute.scriban"; @@ -70,7 +68,4 @@ internal static class GeneratorConstants internal const string UseMiddlewareTTemplateFile = "Templates/UseMiddlewareT.scriban"; internal const string GenericHandlerTemplateFile = "Templates/GenericHandler.scriban"; - - internal const string LambdaHostUseOpenTelemetryTracingExtensionsTemplateFile = - "Templates/OpenTelemetry.scriban"; } From 561c37e73f4965216e69e95f45a01dbb8b7fa85a Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:41:06 -0500 Subject: [PATCH 38/67] refactor(source-generators): remove unused syntax providers and related models - Deleted `HandlerInfoExtractor`, `LambdaApplicationBuilderBuildSyntaxProvider`, `MapHandlerSyntaxProvider`, `OnInitSyntaxProvider`, and `OnShutdownSyntaxProvider` due to no longer being in use. - Removed unused models `ArgumentInfo` and `SimpleMethodInfo` to clean up the codebase. - Updated references to align with the removal of outdated syntax providers and models. --- .../Models/ArgumentInfo.cs | 3 - .../Models/SimpleMethodInfo.cs | 7 - .../Extractors/HandlerInfoExtractor.cs | 381 ------------------ ...daApplicationBuilderBuildSyntaxProvider.cs | 54 --- .../MapHandlerSyntaxProvider.cs | 16 - .../SyntaxProviders/OnInitSyntaxProvider.cs | 28 -- .../OnShutdownSyntaxProvider.cs | 28 -- 7 files changed, 517 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs delete mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs delete mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs delete mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs delete mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs delete mode 100644 src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs deleted file mode 100644 index 3b1c1f17..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct ArgumentInfo(string? Type, string? Name); diff --git a/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs deleted file mode 100644 index 082379fc..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct SimpleMethodInfo( - string Name, - LocationInfo? LocationInfo, - InterceptableLocationInfo InterceptableLocationInfo -); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs deleted file mode 100644 index 7a10f1a3..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ /dev/null @@ -1,381 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.Models; -using TypeInfo = MinimalLambda.SourceGenerators.Models.TypeInfo; - -namespace MinimalLambda.SourceGenerators; - -using TypeInfo = TypeInfo; - -internal static class HandlerInfoExtractor -{ - internal static bool Predicate(SyntaxNode node, params string[] methodNames) => - !node.IsGeneratedFile() - && node.TryGetMethodName(out var name) - && methodNames.Contains(name); - - internal static MapHandlerMethodInfo? Transformer( - GeneratorSyntaxContext context, - Func delegateFilter, - CancellationToken cancellationToken - ) - { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); - - if ( - operation - is not IInvocationOperation - { - TargetMethod.ContainingNamespace: - { - Name: "Builder", - ContainingNamespace: - { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, - }, - } targetOperation - || targetOperation.TargetMethod.ContainingAssembly.Name != "MinimalLambda" - ) - return null; - - if (context.Node is not InvocationExpressionSyntax invocationExpr) - return null; - - var handler = invocationExpr.ArgumentList.Arguments.ElementAtOrDefault(0)?.Expression; - - var delegateInfo = handler?.ExtractDelegateInfo(context, cancellationToken); - if (delegateInfo is null) - return null; - - // filter out non-generic shutdown method calls - if (delegateFilter(delegateInfo.Value)) - return null; - - // get method arguments - var argumentInfos = targetOperation - .Arguments.Select(argument => - { - var typeAsGlobal = argument.Value.Type?.ToGloballyQualifiedName(); - var parameterName = argument.Parameter?.Name; - - return new ArgumentInfo(typeAsGlobal, parameterName); - }) - .ToImmutableArray(); - - // get interceptable location - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - invocationExpr, - cancellationToken - )!; - - throw new NotImplementedException(); - // return new MapHandlerMethodInfo( - // // targetOperation.TargetMethod.Name, - // // LocationInfo: context.Node.CreateLocationInfo(), - // // DelegateInfo: delegateInfo.Value, - // // InterceptableLocationInfo: - // // InterceptableLocationInfo.CreateFrom(interceptableLocation), - // // ArgumentsInfos: argumentInfos - // ); - } - - private static DelegateInfo? ExtractDelegateInfo( - this ExpressionSyntax handler, - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) - { - // setup list of mutator functions - List updaters = []; - - // if we are dealing with a cast expression, set up a mutator to update the delegate type - if (handler is CastExpressionSyntax castExpression) - { - var del = GetDelegateFromCast(castExpression, cancellationToken); - if (del is null) - return null; - - handler = del; - - updaters.Add(UpdateTypesFromCast(context, castExpression)); - } - - var result = handler switch - { - IdentifierNameSyntax or MemberAccessExpressionSyntax => ExtractInfoFromDelegate( - context, - handler, - cancellationToken - ), - - LambdaExpressionSyntax lambda => ExtractInfoFromLambda( - context, - lambda, - cancellationToken - ), - - _ => null, - }; - - if (result is null) - return null; - - return updaters.Aggregate( - result.Value, - (current, updater) => updater(current, cancellationToken) - ); - } - - private static ExpressionSyntax? GetDelegateFromCast( - CastExpressionSyntax castExpression, - CancellationToken _ - ) - { - // must have at least 2 children -> expression at index 1, cast at index 0 - var expression = castExpression.ChildNodes().ElementAtOrDefault(1); - if (expression is null) - return null; - - // unwrap parenthesized expressions - while (expression is ParenthesizedExpressionSyntax parenthesizedExpression) - expression = parenthesizedExpression.Expression; - - return expression switch - { - // top level static method - e.g. (Func)Handler - IdentifierNameSyntax identifier => identifier, - - // static method on a class - e.g. (Func)MyClass.Handler - MemberAccessExpressionSyntax memberAccess => memberAccess, - - // parenthesized lambda expression - e.g. (Func)() => 1 - ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda, - - // simple lambda expression - e.g. (Func)x => x + 1 - SimpleLambdaExpressionSyntax simpleLambda => simpleLambda, - - // default, not a supported delegate type - _ => null, - }; - } - - private static Updater UpdateTypesFromCast( - GeneratorSyntaxContext context, - CastExpressionSyntax castExpression - ) => - (delegateInfo, cancellationToken) => - { - var castTypeInfo = ModelExtensions.GetTypeInfo( - context.SemanticModel, - castExpression.Type, - cancellationToken - ); - - if (castTypeInfo.Type is IErrorTypeSymbol) - throw new InvalidOperationException( - $"Failed to resolve type info for {castTypeInfo.Type.ToDisplayString()}." - ); - - if (castTypeInfo.Type is not INamedTypeSymbol namedType) - throw new InvalidOperationException( - $"Cast type must be a named delegate type, but got {castTypeInfo.Type?.ToDisplayString() ?? "null"}." - ); - - var invokeMethod = namedType - .GetMembers("Invoke") - .OfType() - .FirstOrDefault(); - - if (invokeMethod == null) - throw new InvalidOperationException( - $"Cast type {namedType.ToDisplayString()} is not a valid delegate type (missing Invoke method)." - ); - - if (invokeMethod.Parameters.Length != delegateInfo.Parameters.Count) - throw new InvalidOperationException( - $"Parameter count mismatch: cast delegate has {invokeMethod.Parameters.Length} parameters, " - + $"but existing delegate has {delegateInfo.Parameters.Count} parameters." - ); - - var updatedParameters = invokeMethod - .Parameters.Zip( - delegateInfo.Parameters, - (castParam, originalParam) => - originalParam with - { - TypeInfo = TypeInfo.Create(castParam.Type), - LocationInfo = castParam.CreateLocationInfo(), - } - ) - .ToEquatableArray(); - - // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = invokeMethod.ReturnType.ToGloballyQualifiedName(); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (invokeMethod.IsAsync || invokeMethod.ReturnType.IsTypeAwaitable()); - - // get response type TypeInfo - var responseTypeInfo = TypeInfo.Create(invokeMethod.ReturnType); - - return new DelegateInfo( - updatedParameters, - isAwaitable, - delegateInfo.IsAsync, - responseTypeInfo - ); - }; - - private static DelegateInfo? ExtractInfoFromDelegate( - GeneratorSyntaxContext context, - ExpressionSyntax delegateExpression, - CancellationToken cancellationToken - ) - { - var symbolInfo = ModelExtensions.GetSymbolInfo( - context.SemanticModel, - delegateExpression, - cancellationToken - ); - - // if a symbol is not found, try to find a candidate symbol as backup - var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); - - if (symbol is not IMethodSymbol methodSymbol) - return null; - - var parameters = methodSymbol - .Parameters.AsEnumerable() - .Select(ParameterInfo.Create) - .ToEquatableArray(); - - // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = methodSymbol.ReturnType.ToGloballyQualifiedName(); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (methodSymbol.IsAsync || methodSymbol.ReturnType.IsTypeAwaitable()); - - // get response type TypeInfo - var responseTypeInfo = TypeInfo.Create(methodSymbol.ReturnType); - - return new DelegateInfo(parameters, isAwaitable, methodSymbol.IsAsync, responseTypeInfo); - } - - private static DelegateInfo ExtractInfoFromLambda( - GeneratorSyntaxContext context, - LambdaExpressionSyntax lambdaExpression, - CancellationToken cancellationToken - ) - { - var sematicModel = context.SemanticModel; - - var parameterSyntaxes = lambdaExpression switch - { - SimpleLambdaExpressionSyntax simpleLambda => new[] { simpleLambda.Parameter }.Where(p => - p != null - ), - ParenthesizedLambdaExpressionSyntax parenthesizedLambda => - parenthesizedLambda.ParameterList.Parameters.AsEnumerable(), - _ => [], - }; - - // extract parameter information - var parameters = parameterSyntaxes - .Select(p => sematicModel.GetDeclaredSymbol(p, cancellationToken)) - .Where(p => p is not null) - .Select(ParameterInfo.Create!) - .ToEquatableArray(); - - // Hierarchy for determining lambda return type. - // - // 1. type conversion (not handled here) - // 2. explicit return type - // 3. implicit return type in expression body - // 4. implicit return type in block body - // 5. default void (or Task if async) - var (returnType, returnTypeSyntax) = lambdaExpression switch - { - // check for explicit return type - ParenthesizedLambdaExpressionSyntax { ReturnType: { } syntax } => ModelExtensions - .GetTypeInfo(sematicModel, syntax, cancellationToken) - .Type - is { } type - ? (type, syntax) - : (null, null), - - // Handle implicit return type for expression lambda - { Body: var expression and ExpressionSyntax } => ( - sematicModel.GetTypeInfo(expression, cancellationToken).Type, - null - ), - - // Handle implicit return type for block lambda - { Body: var block and BlockSyntax } => block - .DescendantNodes() - .OfType() - .FirstOrDefault(s => s.Expression is not null) - ?.Expression - is { } expr - ? (sematicModel.GetTypeInfo(expr, cancellationToken).Type, null) - : (null, null), - - // Default to void if no return type is found - _ => (null, null), - }; - - // get response type TypeInfo - TypeInfo? responseTypeInfo = returnType is not null - ? TypeInfo.Create(returnType, returnTypeSyntax) - : null; - - // determine if the lambda is async by checking kind - var isAsync = lambdaExpression.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword); - - // the full return type for use in function signatures. - var fullResponseType = ( - ReturnType: responseTypeInfo?.FullyQualifiedType, - IsAsync: isAsync - ) switch - { - (null, true) => TypeConstants.Task, - (null, false) => TypeConstants.Void, - (TypeConstants.Void, _) => TypeConstants.Void, - (TypeConstants.Task, _) => TypeConstants.Task, - (TypeConstants.ValueTask, _) => TypeConstants.ValueTask, - var (type, _) when type.StartsWith(TypeConstants.Task) => type, - var (type, _) when type.StartsWith(TypeConstants.ValueTask) => type, - (var type, true) => $"{TypeConstants.Task}<{type}>", - (_, _) => responseTypeInfo.Value.FullyQualifiedType, - }; - - var updatedResponseType = responseTypeInfo is { } info - ? info with - { - FullyQualifiedType = fullResponseType, - } - : TypeInfo.CreateFullyQualifiedType(fullResponseType); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (isAsync || (returnType?.IsTypeAwaitable() ?? false)); - - return new DelegateInfo(parameters, isAwaitable, isAsync, updatedResponseType); - } - - private delegate DelegateInfo Updater( - DelegateInfo delegateInfo, - CancellationToken cancellationToken - ); -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs deleted file mode 100644 index 08b16bcd..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class LambdaApplicationBuilderBuildSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken _) => - node.TryGetMethodName(out var name) && name == "Build"; - - internal static SimpleMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) - { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); - - if ( - operation - is IInvocationOperation - { - TargetMethod: - { - ContainingType.Name: "LambdaApplicationBuilder", - ContainingNamespace: - { - Name: "Builder", - ContainingNamespace: - { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, - }, - }, - } targetOperation - && targetOperation.TargetMethod.ContainingAssembly.Name == "MinimalLambda" - ) - { - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - (InvocationExpressionSyntax)targetOperation.Syntax, - cancellationToken - )!; - - return new SimpleMethodInfo( - targetOperation.TargetMethod.Name, - context.Node.CreateLocationInfo(), - InterceptableLocationInfo.CreateFrom(interceptableLocation) - ); - } - - return null; - } -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs deleted file mode 100644 index db966a2e..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class MapHandlerSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.MapHandlerMethodName); - - internal static MapHandlerMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, _ => false, cancellationToken); -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs deleted file mode 100644 index d95c5fd1..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OnInitSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnInitMethodName); - - internal static MapHandlerMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); - - // we want to filter out the non-generic init method calls that use the method signature - // defined in ILambdaOnInitBuilder. this is LambdaInitDelegate. - // Func> - private static bool IsBaseOnShutdownCall(this DelegateInfo delegateInfo) => - delegateInfo - is { - ReturnTypeInfo.FullyQualifiedType: TypeConstants.TaskBool, - Parameters: [ - { TypeInfo.FullyQualifiedType: TypeConstants.ILambdaLifecycleContext }, - ], - }; -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs deleted file mode 100644 index 2336d0fd..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OnShutdownSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnShutdownMethodName); - - internal static MapHandlerMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); - - // we want to filter out the non-generic shutdown method calls that use the method signature - // defined in ILambdaOnShutdownBuilder. this is LambdaShutdownDelegate. - // Func - private static bool IsBaseOnShutdownCall(this DelegateInfo delegateInfo) => - delegateInfo - is { - ReturnTypeInfo.FullyQualifiedType: TypeConstants.Task, - Parameters: [ - { TypeInfo.FullyQualifiedType: TypeConstants.ILambdaLifecycleContext }, - ], - }; -} From 22f37d1b4d92a50b7e1fd4bf2628050b0d603787 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:41:13 -0500 Subject: [PATCH 39/67] feat(source-generators): add support for `ILambdaMiddleware` in `UseMiddlewareTSyntaxProvider` - Extended `WellKnownTypeData` with `MinimalLambda_ILambdaMiddleware` for improved type matching. - Refactored `UseMiddlewareTSyntaxProvider` to delegate type validation to `TryGetInvocationOperation`. - Introduced `TryGetInvocationOperation` method for cleaner and reusable operation extraction logic. - Enhanced readability and maintainability of middleware type constraint checks. --- .../UseMiddlewareTSyntaxProvider.cs | 77 ++++++++++++------- .../WellKnownTypes/WellKnownTypeData.cs | 2 + 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index a9a735ef..3eeaa802 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; @@ -5,45 +6,26 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using MinimalLambda.SourceGenerators.Models; +using MinimalLambda.SourceGenerators.WellKnownTypes; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators; internal static class UseMiddlewareTSyntaxProvider { + private static readonly string TargetMethodName = "UseMiddleware"; + internal static bool Predicate(SyntaxNode node, CancellationToken _) => - !node.IsGeneratedFile() && node.TryGetMethodName(out var name) && name == "UseMiddleware"; + !node.IsGeneratedFile() && node.TryGetMethodName(out var name) && name == TargetMethodName; internal static UseMiddlewareTInfo? Transformer( - GeneratorSyntaxContext context, + GeneratorSyntaxContext syntaxContext, CancellationToken cancellationToken ) { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); + var context = new GeneratorContext(syntaxContext, cancellationToken); - if ( - operation - is not IInvocationOperation - { - TargetMethod: - { - IsGenericMethod: true, - ContainingAssembly.Name: "MinimalLambda", - ContainingNamespace: - { - Name: "Builder", - ContainingNamespace: - { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, - }, - }, - } targetOperation - || !targetOperation - .TargetMethod.ConstructedFrom.TypeParameters[0] - .ConstraintTypes.Any(c => - c.Name == "ILambdaMiddleware" - && c.ContainingNamespace - is { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true } - ) - ) + if (!TryGetInvocationOperation(context, out var targetOperation)) return null; // get class TypeInfo @@ -78,4 +60,45 @@ targetOperation.Syntax is InvocationExpressionSyntax return useMiddlewareTInfo; } + + private static bool TryGetInvocationOperation( + GeneratorContext context, + [NotNullWhen(true)] out IInvocationOperation? invocationOperation + ) + { + invocationOperation = null; + + var operation = context.SemanticModel.GetOperation(context.Node, context.CancellationToken); + + if ( + operation + is IInvocationOperation + { + TargetMethod: + { + IsGenericMethod: true, + ContainingAssembly.Name: "MinimalLambda", + ContainingNamespace: + { + Name: "Builder", + ContainingNamespace: + { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, + }, + }, + } targetOperation + && targetOperation.TargetMethod.ConstructedFrom.TypeParameters.FirstOrDefault() + is { } typeParameter + && typeParameter.ConstraintTypes.Any(c => + context.WellKnownTypes.IsTypeMatch(c, WellKnownType.MinimalLambda_ILambdaMiddleware) + && c.ContainingNamespace + is { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true } + ) + ) + { + invocationOperation = targetOperation; + return true; + } + + return false; + } } diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs index a4a7d49a..f26050e4 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -56,6 +56,7 @@ public enum WellKnownType MinimalLambda_Builder_FromServicesAttribute, MinimalLambda_Builder_MiddlewareConstructorAttribute, System_Boolean, + MinimalLambda_ILambdaMiddleware, } public static readonly string[] WellKnownTypeNames = @@ -102,5 +103,6 @@ public enum WellKnownType "MinimalLambda.Builder.FromServicesAttribute", "MinimalLambda.Builder.MiddlewareConstructorAttribute", "System.Boolean", + "MinimalLambda.ILambdaMiddleware", ]; } From 63f6950ae475f0504936062ea80cb295ca578b03 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 28 Dec 2025 08:51:14 -0500 Subject: [PATCH 40/67] refactor(source-generators): replace generated code attribute logic with Lazy initialization - Changed `GeneratedCodeAttribute` to use `Lazy` for thread-safe, on-demand initialization. - Updated templates to access `.value` property of `GeneratedCodeAttribute` instead of direct reference. - Simplified and cleaned up redundant logic in attribute generation for improved readability. - Removed unused namespace constraints in `UseMiddlewareTSyntaxProvider` for cleaner type checking. --- .../Emitters/MinimalLambdaEmitter.cs | 22 ++++++------------- .../UseMiddlewareTSyntaxProvider.cs | 2 -- .../Templates/GenericHandler.scriban | 2 +- .../InterceptsLocationAttribute.scriban | 2 +- .../Templates/MapHandler.scriban | 2 +- .../Templates/UseMiddlewareT.scriban | 2 +- 6 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index a4728658..dc31c348 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -8,23 +9,14 @@ namespace MinimalLambda.SourceGenerators; internal static class MinimalLambdaEmitter { - internal static string GeneratedCodeAttribute + internal static readonly Lazy GeneratedCodeAttribute = new(() => { - get - { - if (field is null) - { - var assembly = Assembly.GetExecutingAssembly(); - var generatorName = assembly.GetName().Name; - var generatorVersion = assembly.GetName().Version.ToString(); - - field = - $"""[global::System.CodeDom.Compiler.GeneratedCode("{generatorName}", "{generatorVersion}")]"""; - } + var assembly = Assembly.GetExecutingAssembly(); + var generatorName = assembly.GetName().Name; + var generatorVersion = assembly.GetName().Version; - return field; - } - } + return $"""[global::System.CodeDom.Compiler.GeneratedCode("{generatorName}", "{generatorVersion}")]"""; + }); internal static void Generate(SourceProductionContext context, CompilationInfo compilationInfo) { diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index 3eeaa802..b74cd1f4 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -90,8 +90,6 @@ is IInvocationOperation is { } typeParameter && typeParameter.ConstraintTypes.Any(c => context.WellKnownTypes.IsTypeMatch(c, WellKnownType.MinimalLambda_ILambdaMiddleware) - && c.ContainingNamespace - is { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true } ) ) { diff --git a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban index 6d9ef09c..b63b9555 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban @@ -1,4 +1,4 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class GeneratedLambda{{ name }}BuilderExtensions { {{~ for call in calls ~}} diff --git a/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban b/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban index 37a13190..b741545d 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban @@ -15,7 +15,7 @@ namespace System.Runtime.CompilerServices { using System.CodeDom.Compiler; - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] file sealed class InterceptsLocationAttribute : Attribute { diff --git a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban index de5291b1..0c80b3bf 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban @@ -1,4 +1,4 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class GeneratedLambdaInvocationBuilderExtensions { private const string EventFeatureProviderKey = "__EventFeatureProvider"; diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index 944a7467..a6c896f1 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -1,4 +1,4 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class UseMiddlewareExtensions { {{~ for call in calls ~}} From 4f9972b6394268f78d45df5771d5f4d0325aca6d Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 29 Dec 2025 13:36:26 -0500 Subject: [PATCH 41/67] refactor(source-generators): remove unused models and consolidate logic in `UseMiddlewareTInfo` - Removed `DelegateInfo` and related extensions as they are no longer in use. - Refactored `UseMiddlewareTInfo` to include diagnostic info and middleware validation logic. - Simplified `UseMiddlewareTSyntaxProvider` by delegating middleware creation to `UseMiddlewareTInfo`. - Replaced `CreateLocationInfo` with `ToLocationInfo` for consistency across extensions. - Cleaned up redundant code and improved overall maintainability. --- .../Diagnostics/DiagnosticGenerator.cs | 8 +- .../Extensions/DelegateInfoExtensions.cs | 37 -------- ...nsions.cs => NamedTypeSymbolExtensions.cs} | 2 +- .../Extensions/ParameterSymbolExtensions.cs | 2 +- .../Extensions/TypeSymbolExtensions.cs | 2 - .../Models/ClassInfo.cs | 23 +++-- .../Models/DelegateInfo.cs | 34 -------- .../Models/DiagnosticResult.cs | 2 + .../Models/InterceptableLocationInfo.cs | 6 ++ .../Models/KeyedServiceKeyInfo.cs | 2 +- .../Models/LocationInfo.cs | 7 +- .../Models/MethodInfo.cs | 2 +- .../Models/UseMiddlewareTInfo.cs | 86 ++++++++++++++++++- .../UseMiddlewareTSyntaxProvider.cs | 64 +++++++------- 14 files changed, 148 insertions(+), 129 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs rename src/MinimalLambda.SourceGenerators/Extensions/{SymbolExtensions.cs => NamedTypeSymbolExtensions.cs} (97%) delete mode 100644 src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index bea250dd..5b23f7e2 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -43,7 +43,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // // invocationInfo // // .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) // // .Select(p => - // // Diagnostic.CreateLocationInfo( + // // Diagnostic.ToLocationInfo( // // Diagnostics.MultipleParametersUseAttribute, // // p.LocationInfo?.ToLocation(), // // AttributeConstants.FromEventAttribute @@ -67,7 +67,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") // { // diagnostics.Add( - // Diagnostic.CreateLocationInfo( + // Diagnostic.ToLocationInfo( // Diagnostics.MustBeConcreteType, // useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), // useMiddlewareTInfo.ClassInfo.ShortName @@ -86,7 +86,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // ) // .Skip(1) // .Select(c => - // Diagnostic.CreateLocationInfo( + // Diagnostic.ToLocationInfo( // Diagnostics.MultipleConstructorsWithAttribute, // c.AttributeInfos.First(a => // a.FullName == AttributeConstants.MiddlewareConstructor @@ -110,7 +110,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // ) // .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) // .Select(parameterInfo => - // Diagnostic.CreateLocationInfo( + // Diagnostic.ToLocationInfo( // Diagnostics.InvalidAttributeArgument, // parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), // parameterInfo.KeyedServiceKey.Value.Type diff --git a/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs deleted file mode 100644 index c9d968c0..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using System.Text; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators.Extensions; - -internal static class DelegateInfoExtensions -{ - extension(DelegateInfo delegateInfo) - { - internal string BuildHandlerCastCall() - { - var signatureBuilder = new StringBuilder(); - signatureBuilder.Append("Utilities.Cast(handler, "); - - signatureBuilder.Append(delegateInfo.ReturnTypeInfo.FullyQualifiedType); - - signatureBuilder.Append(" ("); - - signatureBuilder.Append( - string.Join( - ", ", - delegateInfo.Parameters.Select( - (p, i) => - $"{p.TypeInfo.FullyQualifiedType} arg{i}{(p.IsOptional ? " = default" : "")}" - ) - ) - ); - - signatureBuilder.Append(") => throw null!)"); - - var handlerSignature = signatureBuilder.ToString(); - - return handlerSignature; - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs similarity index 97% rename from src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs rename to src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs index aa9a357e..80241143 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/SymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs @@ -4,7 +4,7 @@ namespace Microsoft.CodeAnalysis; -internal static class SymbolExtensions +internal static class NamedTypeSymbolExtensions { extension(INamedTypeSymbol sourceType) { diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 820977bb..6a40da63 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -158,7 +158,7 @@ private DiagnosticResult ExtractKeyedServiceKey() ? argumentList .Arguments.ElementAtOrDefault(index) ?.Expression.GetLocation() - .CreateLocationInfo() + .ToLocationInfo() : null; } } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs index 2231e75f..a8f4a7c8 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs @@ -6,8 +6,6 @@ internal static class TypeSymbolExtensions { extension(ITypeSymbol typeSymbol) { - internal bool IsTypeAwaitable() => typeSymbol.IsTask() || typeSymbol.IsValueTask(); - internal bool IsTask() => typeSymbol.Name == "Task" && typeSymbol.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs index 2c72be63..a3bfe13e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs @@ -11,14 +11,19 @@ internal readonly record struct ClassInfo( string ShortName, EquatableArray ConstructorInfos, EquatableArray ImplementedInterfaces, - string TypeKind + string TypeKind, + bool ImplementsIDisposable = false, + bool ImplementsIAsyncDisposable = false ); internal static class ClassInfoExtensions { extension(ClassInfo classInfo) { - internal static ClassInfo Create(ITypeSymbol typeSymbol) + internal static DiagnosticResult Create( + INamedTypeSymbol typeSymbol, + GeneratorContext context + ) { var typeKind = typeSymbol.GetTypeKind(); @@ -38,12 +43,14 @@ internal static ClassInfo Create(ITypeSymbol typeSymbol) .AllInterfaces.Select(i => i.ToGloballyQualifiedName()) .ToEquatableArray(); - return new ClassInfo( - globallyQualifiedName, - shortName, - constructorInfo, - interfaceNames, - typeKind + return DiagnosticResult.Success( + new ClassInfo( + globallyQualifiedName, + shortName, + constructorInfo, + interfaceNames, + typeKind + ) ); } diff --git a/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs b/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs deleted file mode 100644 index f31d9f77..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct DelegateInfo( - EquatableArray Parameters, - bool IsAwaitable, - bool IsAsync, - TypeInfo ReturnTypeInfo -) -{ - internal readonly ParameterInfo? EventParameter = GetEventParameter(Parameters); - - internal string DelegateType => - ReturnTypeInfo.FullyQualifiedType == TypeConstants.Void - ? TypeConstants.Action - : TypeConstants.Func; - - internal bool HasAnyKeyedServiceParameter => - Parameters.Any(p => p.Source == ParameterSource.KeyedService); - - internal bool HasEventParameter => EventParameter is not null; - - internal bool HasResponse => - ReturnTypeInfo.FullyQualifiedType - is not (TypeConstants.Void or TypeConstants.Task or TypeConstants.ValueTask); - - private static ParameterInfo? GetEventParameter(EquatableArray parameters) => - parameters - .Where(p => p.Source == ParameterSource.Event) - .Select(p => (ParameterInfo?)p) - .FirstOrDefault(); -} diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs index d16f25c4..7dd1564d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs +++ b/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs @@ -1,6 +1,8 @@ using System; using Microsoft.CodeAnalysis; +// ReSharper disable MemberCanBePrivate.Global + namespace MinimalLambda.SourceGenerators.Models; internal class DiagnosticResult diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs index e9b346b7..5e0c5f6e 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs @@ -48,4 +48,10 @@ internal static bool TryGet( internal string ToInterceptsLocationAttribute() => $"""[InterceptsLocation({location.Version}, "{location.Data}")]"""; } + + extension(InterceptableLocation interceptableLocation) + { + internal InterceptableLocationInfo ToInterceptableLocationInfo() => + InterceptableLocationInfo.CreateFrom(interceptableLocation); + } } diff --git a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs index d1f00b3e..e263ebca 100644 --- a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs @@ -28,7 +28,7 @@ key is null { var argument = argumentList.Arguments[0]; var location = argument.Expression.GetLocation(); - var locationInfo = location.CreateLocationInfo(); + var locationInfo = location.ToLocationInfo(); return keyedServiceKeyInfo with { LocationInfo = locationInfo }; } diff --git a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs index 919f20b1..4949383f 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs @@ -35,7 +35,7 @@ internal Location ToLocation() => extension(Location location) { - internal LocationInfo? CreateLocationInfo() => + internal LocationInfo? ToLocationInfo() => location.SourceTree is null ? null : new LocationInfo( @@ -48,12 +48,11 @@ location.SourceTree is null extension(ISymbol symbol) { internal LocationInfo? CreateLocationInfo() => - symbol.Locations.FirstOrDefault()?.CreateLocationInfo(); + symbol.Locations.FirstOrDefault()?.ToLocationInfo(); } extension(SyntaxNode syntaxNode) { - internal LocationInfo? CreateLocationInfo() => - syntaxNode.GetLocation().CreateLocationInfo(); + internal LocationInfo? CreateLocationInfo() => syntaxNode.GetLocation().ToLocationInfo(); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs index 873f578e..a6749286 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs @@ -42,7 +42,7 @@ internal static AttributeInfo Create(AttributeData attributeData) { var syntax = attributeData.ApplicationSyntaxReference?.GetSyntax(); var location = syntax?.GetLocation(); - var locationData = location?.CreateLocationInfo(); + var locationData = location?.ToLocationInfo(); var name = attributeData.AttributeClass?.ToString() ?? "UNKNOWN"; diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs index 3ff642f3..56466aed 100644 --- a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs @@ -1,7 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + namespace MinimalLambda.SourceGenerators.Models; -internal readonly record struct UseMiddlewareTInfo( - InterceptableLocationInfo InterceptableLocationInfo, - ClassInfo ClassInfo, - LocationInfo? GenericTypeArgumentLocation +internal record UseMiddlewareTInfo( + string? InterceptableLocationAttribute, + ClassInfo? ClassInfo, + EquatableArray DiagnosticInfos ); + +internal static class UseMiddlewareTInfoExtensions +{ + extension(UseMiddlewareTInfo) + { + internal static UseMiddlewareTInfo Create( + IInvocationOperation invocationOperation, + GeneratorContext context + ) + { + if ( + invocationOperation.Syntax + is not InvocationExpressionSyntax invocationExpressionSyntax + ) + throw new InvalidOperationException("Syntax is not InvocationExpressionSyntax"); + + List diagnosticInfos = []; + + TryGetLocationInfo(invocationExpressionSyntax, out var locationInfo); + + var interceptableLocation = ( + context.SemanticModel.GetInterceptableLocation( + invocationExpressionSyntax, + context.CancellationToken + ) ?? throw new InvalidOperationException("Interceptable location is null") + ) + .ToInterceptableLocationInfo() + .ToInterceptsLocationAttribute(); + + var middlewareClassType = invocationOperation + .TargetMethod.TypeArguments.FirstOrDefault() + .Map(typeSymbol => + typeSymbol as INamedTypeSymbol + ?? throw new InvalidOperationException( + "Middleware class type is not INamedTypeSymbol" + ) + ); + + var classInfoResult = ClassInfo.Create(middlewareClassType, context); + if (!classInfoResult.IsSuccess) + diagnosticInfos.Add(classInfoResult.Error!.Value); + + return new UseMiddlewareTInfo( + interceptableLocation, + classInfoResult.Value, + diagnosticInfos.ToEquatableArray() + ); + } + } + + private static bool TryGetLocationInfo( + InvocationExpressionSyntax invocationExpressionSyntax, + out LocationInfo? locationInfo + ) + { + locationInfo = null; + if ( + invocationExpressionSyntax is + { Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName } } + ) + { + var typeArgument = genericName.TypeArgumentList.Arguments[0]; + locationInfo = typeArgument.GetLocation().ToLocationInfo(); + return true; + } + + return false; + } +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index b74cd1f4..67fbdc7c 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -2,8 +2,6 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using MinimalLambda.SourceGenerators.Models; using MinimalLambda.SourceGenerators.WellKnownTypes; @@ -28,37 +26,39 @@ CancellationToken cancellationToken if (!TryGetInvocationOperation(context, out var targetOperation)) return null; - // get class TypeInfo - var middlewareClassType = targetOperation.TargetMethod.TypeArguments[0]; + return UseMiddlewareTInfo.Create(targetOperation, context); - // Get location of the generic argument - Location? genericArgumentLocation = null; - if ( - targetOperation.Syntax is InvocationExpressionSyntax - { - Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName }, - } - ) - { - // Get the first type argument's location - var typeArgument = genericName.TypeArgumentList.Arguments[0]; - genericArgumentLocation = typeArgument.GetLocation(); - } - - var classInfo = ClassInfo.Create(middlewareClassType); - - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - (InvocationExpressionSyntax)targetOperation.Syntax, - cancellationToken - )!; - - var useMiddlewareTInfo = new UseMiddlewareTInfo( - InterceptableLocationInfo.CreateFrom(interceptableLocation), - classInfo, - genericArgumentLocation?.CreateLocationInfo() - ); - - return useMiddlewareTInfo; + // // get class TypeInfo + // var middlewareClassType = targetOperation.TargetMethod.TypeArguments[0]; + // + // // Get location of the generic argument + // Location? genericArgumentLocation = null; + // if ( + // targetOperation.Syntax is InvocationExpressionSyntax + // { + // Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName }, + // } + // ) + // { + // // Get the first type argument's location + // var typeArgument = genericName.TypeArgumentList.Arguments[0]; + // genericArgumentLocation = typeArgument.GetLocation(); + // } + // + // var classInfo = ClassInfo.Create(middlewareClassType); + // + // var interceptableLocation = context.SemanticModel.GetInterceptableLocation( + // (InvocationExpressionSyntax)targetOperation.Syntax, + // cancellationToken + // )!; + // + // var useMiddlewareTInfo = new UseMiddlewareTInfo( + // InterceptableLocationInfo.CreateFrom(interceptableLocation), + // classInfo, + // genericArgumentLocation?.ToLocationInfo() + // ); + // + // return useMiddlewareTInfo; } private static bool TryGetInvocationOperation( From ef657ca1e78f07a4c70654165d809132bc126265 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 29 Dec 2025 13:40:10 -0500 Subject: [PATCH 42/67] refactor(source-generators): inline `Generate` logic and remove unused source files - Removed `CommonSources`, `GenericHandlerSources`, and `MapHandlerSources` as they were unused. - Inlined logic for `Generate` methods directly in `MinimalLambdaEmitter` to simplify generation process. - Updated `MinimalLambdaEmitter` to use `TemplateHelper.Render` directly for improved maintainability. --- .../Emitters/CommonSources.cs | 10 ----- .../Emitters/GenericHandlerSources.cs | 24 ----------- .../Emitters/MapHandlerSources.cs | 19 --------- .../Emitters/MinimalLambdaEmitter.cs | 40 +++++++++++++++++-- 4 files changed, 36 insertions(+), 57 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs diff --git a/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs deleted file mode 100644 index e28bcc8f..00000000 --- a/src/MinimalLambda.SourceGenerators/Emitters/CommonSources.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MinimalLambda.SourceGenerators; - -internal static class CommonSources -{ - internal static string Generate() => - TemplateHelper.Render( - GeneratorConstants.InterceptsLocationAttributeTemplateFile, - new { MinimalLambdaEmitter.GeneratedCodeAttribute } - ); -} diff --git a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs deleted file mode 100644 index 67e858be..00000000 --- a/src/MinimalLambda.SourceGenerators/Emitters/GenericHandlerSources.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class GenericHandlerSources -{ - /// - /// Generates C# code for a generic handler. The handler is a wrapper around the actual - /// handler. The return type of the wrapper is a Task or Task<T> depending on - /// the return type of the actual handler. - /// - internal static string Generate(EquatableArray lifecycleMethodInfos) => - TemplateHelper.Render( - GeneratorConstants.GenericHandlerTemplateFile, - new - { - Name = lifecycleMethodInfos.First().MethodType, - Calls = lifecycleMethodInfos, - MinimalLambdaEmitter.GeneratedCodeAttribute, - } - ); -} diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs deleted file mode 100644 index ffe71a8a..00000000 --- a/src/MinimalLambda.SourceGenerators/Emitters/MapHandlerSources.cs +++ /dev/null @@ -1,19 +0,0 @@ -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class MapHandlerSources -{ - internal static string Generate( - EquatableArray mapHandlerInvocationInfos - ) => - TemplateHelper.Render( - GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile, - new - { - MinimalLambdaEmitter.GeneratedCodeAttribute, - MapHandlerCalls = mapHandlerInvocationInfos, - } - ); -} diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index dc31c348..6fc78d94 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -33,7 +33,10 @@ internal static void Generate(SourceProductionContext context, CompilationInfo c List outputs = [ - CommonSources.Generate(), + TemplateHelper.Render( + GeneratorConstants.InterceptsLocationAttributeTemplateFile, + new { GeneratedCodeAttribute } + ), """ namespace MinimalLambda.Generated { @@ -50,7 +53,16 @@ namespace MinimalLambda.Generated // if MapHandler calls found, generate the source code. if (compilationInfo.MapHandlerInvocationInfos.Count >= 1) - outputs.Add(MapHandlerSources.Generate(compilationInfo.MapHandlerInvocationInfos)); + outputs.Add( + TemplateHelper.Render( + GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile, + new + { + GeneratedCodeAttribute, + MapHandlerCalls = compilationInfo.MapHandlerInvocationInfos, + } + ) + ); // add UseMiddleware interceptors if (compilationInfo.UseMiddlewareTInfos.Count >= 1) @@ -58,11 +70,31 @@ namespace MinimalLambda.Generated // add OnInit interceptors if (compilationInfo.OnInitInvocationInfos.Count >= 1) - outputs.Add(GenericHandlerSources.Generate(compilationInfo.OnInitInvocationInfos)); + outputs.Add( + TemplateHelper.Render( + GeneratorConstants.GenericHandlerTemplateFile, + new + { + Name = compilationInfo.OnInitInvocationInfos.First().MethodType, + Calls = compilationInfo.OnInitInvocationInfos, + GeneratedCodeAttribute, + } + ) + ); // add OnShutdown interceptors if (compilationInfo.OnShutdownInvocationInfos.Count >= 1) - outputs.Add(GenericHandlerSources.Generate(compilationInfo.OnShutdownInvocationInfos)); + outputs.Add( + TemplateHelper.Render( + GeneratorConstants.GenericHandlerTemplateFile, + new + { + Name = compilationInfo.OnShutdownInvocationInfos.First().MethodType, + Calls = compilationInfo.OnShutdownInvocationInfos, + GeneratedCodeAttribute, + } + ) + ); outputs.Add( """ From fc1fb3e71083769c763e067d38b9deb0a143ba7e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 29 Dec 2025 14:40:19 -0500 Subject: [PATCH 43/67] refactor(source-generators): inline `Generate` logic and improve `UseMiddlewareT` maintainability - Removed commented-out code in `UseMiddlewareTSource` for better readability. - Merged `useMiddlewareTCalls` and `useMiddlewareTInfos` to simplify logic. - Updated `ClassInfo` to support diagnostics and middleware constructor validation. - Refactored template to use `class_info` for enhanced maintainability and minimal duplication. --- .../Emitters/UseMiddlewareTSource.cs | 155 +++++++++--------- .../Extensions/WellKnownTypesExtensions.cs | 5 + .../Models/ClassInfo.cs | 109 +++++++++--- .../Templates/UseMiddlewareT.scriban | 12 +- 4 files changed, 181 insertions(+), 100 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs index 4f49698a..78a6afc5 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs @@ -1,4 +1,3 @@ -using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; using MinimalLambda.SourceGenerators.Models; @@ -8,82 +7,88 @@ internal static class UseMiddlewareTSource { internal static string Generate(EquatableArray useMiddlewareTInfos) { - var useMiddlewareTCalls = useMiddlewareTInfos.Select(useMiddlewareTInfo => - { - var classInfo = useMiddlewareTInfo.ClassInfo; - - // choose what constructor to use with the following criteria: - // 1. if it has a `[MiddlewareConstructor]` attribute. Multiple of these are not - // valid. - // 2. default to the constructor with the most arguments - var constructor = classInfo - .ConstructorInfos.Select(c => (MethodInfo?)c) - .FirstOrDefault(c => - c!.Value.AttributeInfos.Any(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - ); - - constructor ??= classInfo - .ConstructorInfos.OrderByDescending(c => c.ArgumentCount) - .First(); - - var parameters = constructor - .Value.Parameters.Select(p => - { - var fromArgs = p.AttributeNames.Any(n => n == AttributeConstants.FromArguments); - - // From services is defined as either having a `[FromServices]` - // attribute or a - // `[FromKeyedServices]` attribute - var fromServices = p.AttributeNames.Any(n => - n is AttributeConstants.FromServices or AttributeConstants.FromKeyedService - ); - - var paramAssignment = p.BuildParameterAssignment(); - - var fullyQualifiedTypeNotNull = - p.TypeInfo.FullyQualifiedType.RemoveTrailingChar("?"); - - return new - { - p.TypeInfo.FullyQualifiedType, - FullyQualifiedTypeNotNull = fullyQualifiedTypeNotNull, - p.Name, - FromArguments = fromArgs, - FromServices = fromServices, - paramAssignment.Assignment, - paramAssignment.String, - }; - }) - .ToArray(); - - var isDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( - TypeConstants.IDisposable - ); - - var isAsyncDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( - TypeConstants.IAsyncDisposable - ); - - var allFromServices = parameters.All(p => p.FromServices); - - return new - { - Location = useMiddlewareTInfo.InterceptableLocationInfo, - FullMiddlewareClassName = classInfo.GloballyQualifiedName, - ShortMiddlewareClassName = classInfo.ShortName, - AllFromServices = allFromServices, - Parameters = parameters, - AnyParameters = parameters.Length > 0, - IsDisposable = isDisposable, - IsAsyncDisposable = isAsyncDisposable, - }; - }); - + // var useMiddlewareTCalls = useMiddlewareTInfos.Select(useMiddlewareTInfo => + // { + // var classInfo = useMiddlewareTInfo.ClassInfo; + // + // // choose what constructor to use with the following criteria: + // // 1. if it has a `[MiddlewareConstructor]` attribute. Multiple of these are not + // // valid. + // // 2. default to the constructor with the most arguments + // var constructor = classInfo + // .ConstructorInfos.Select(c => (MethodInfo?)c) + // .FirstOrDefault(c => + // c!.Value.AttributeInfos.Any(a => + // a.FullName == AttributeConstants.MiddlewareConstructor + // ) + // ); + // + // constructor ??= classInfo + // .ConstructorInfos.OrderByDescending(c => c.ArgumentCount) + // .First(); + // + // var parameters = constructor + // .Value.Parameters.Select(p => + // { + // var fromArgs = p.AttributeNames.Any(n => n == + // AttributeConstants.FromArguments); + // + // // From services is defined as either having a `[FromServices]` + // // attribute or a + // // `[FromKeyedServices]` attribute + // var fromServices = p.AttributeNames.Any(n => + // n is AttributeConstants.FromServices or + // AttributeConstants.FromKeyedService + // ); + // + // var paramAssignment = p.BuildParameterAssignment(); + // + // var fullyQualifiedTypeNotNull = + // p.TypeInfo.FullyQualifiedType.RemoveTrailingChar("?"); + // + // return new + // { + // p.TypeInfo.FullyQualifiedType, + // FullyQualifiedTypeNotNull = fullyQualifiedTypeNotNull, + // p.Name, + // FromArguments = fromArgs, + // FromServices = fromServices, + // paramAssignment.Assignment, + // paramAssignment.String, + // }; + // }) + // .ToArray(); + // + // var isDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( + // TypeConstants.IDisposable + // ); + // + // var isAsyncDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( + // TypeConstants.IAsyncDisposable + // ); + // + // var allFromServices = parameters.All(p => p.FromServices); + // + // return new + // { + // Location = useMiddlewareTInfo.InterceptableLocationInfo, + // FullMiddlewareClassName = classInfo.GloballyQualifiedName, + // ShortMiddlewareClassName = classInfo.ShortName, + // AllFromServices = allFromServices, + // Parameters = parameters, + // AnyParameters = parameters.Length > 0, + // IsDisposable = isDisposable, + // IsAsyncDisposable = isAsyncDisposable, + // }; + // }); + // + // return TemplateHelper.Render( + // GeneratorConstants.UseMiddlewareTTemplateFile, + // new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } + // ); return TemplateHelper.Render( GeneratorConstants.UseMiddlewareTTemplateFile, - new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } + new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTInfos } ); } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs index 8db15b4a..a63305d9 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs @@ -24,5 +24,10 @@ internal bool IsTypeMatch(ITypeSymbol type, WellKnownType wellKnownType) var foundType = wellKnownTypes.Get(wellKnownType); return type.Equals(foundType, SymbolEqualityComparer.Default); } + + internal bool TypeImplementsInterface( + INamedTypeSymbol namedTypeSymbol, + WellKnownType interfaceType + ) => namedTypeSymbol.AllInterfaces.Any(i => wellKnownTypes.IsTypeMatch(i, interfaceType)); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs index a3bfe13e..915ec78a 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs @@ -3,58 +3,129 @@ using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.WellKnownTypes; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; -internal readonly record struct ClassInfo( +internal record ClassInfo( string GloballyQualifiedName, string ShortName, EquatableArray ConstructorInfos, - EquatableArray ImplementedInterfaces, - string TypeKind, - bool ImplementsIDisposable = false, - bool ImplementsIAsyncDisposable = false -); + bool ImplementsDisposable, + bool ImplementsAsyncDisposable +) +{ + internal string NonNullableGloballyQualifiedName + { + get + { + field ??= GloballyQualifiedName.EndsWith("?") + ? GloballyQualifiedName[..^1] + : GloballyQualifiedName; + + return field; + } + } +} internal static class ClassInfoExtensions { extension(ClassInfo classInfo) { - internal static DiagnosticResult Create( + internal bool AnyParameters => true; + + internal static (ClassInfo? classInfo, DiagnosticInfo[] DiagnosticInfos) Create( INamedTypeSymbol typeSymbol, GeneratorContext context ) { - var typeKind = typeSymbol.GetTypeKind(); + List diagnostics = []; // get the globally qualified name of the class var globallyQualifiedName = typeSymbol.ToGloballyQualifiedName(); - // get short name + // get short name, i.e., not qualified var shortName = typeSymbol.Name; + // get constructor + var (constructor, constructorDiagnostics) = GetConstructor(typeSymbol, context); + diagnostics.AddRange(constructorDiagnostics); + // handle each instance constructor on the type var constructorInfo = ((INamedTypeSymbol)typeSymbol) .InstanceConstructors.Select(MethodInfo.Create) .ToEquatableArray(); - // get all interfaces - var interfaceNames = typeSymbol - .AllInterfaces.Select(i => i.ToGloballyQualifiedName()) - .ToEquatableArray(); + // implements IDisposable + var implementsIDisposable = context.WellKnownTypes.TypeImplementsInterface( + typeSymbol, + WellKnownType.System_IDisposable + ); + + // implements IAsyncDisposable + var implementsIAsyncDisposable = context.WellKnownTypes.TypeImplementsInterface( + typeSymbol, + WellKnownType.System_IAsyncDisposable + ); - return DiagnosticResult.Success( + return ( new ClassInfo( globallyQualifiedName, shortName, constructorInfo, - interfaceNames, - typeKind - ) + implementsIDisposable, + implementsIAsyncDisposable + ), + diagnostics.ToArray() ); } + } + + private static (IMethodSymbol? MethodSymbol, DiagnosticInfo[] DiagnosticInfos) GetConstructor( + INamedTypeSymbol namedTypeSymbol, + GeneratorContext context + ) + { + // 1. Get constructors annotated with `[MiddlewareConstructor]` + var constructors = namedTypeSymbol + .InstanceConstructors.Where(c => + c.GetAttributes() + .Any(a => + a.AttributeClass is not null + && context.WellKnownTypes.IsType( + a.AttributeClass, + [WellKnownType.MinimalLambda_Builder_MiddlewareConstructorAttribute] + ) + ) + ) + .ToArray(); + + return constructors.Length switch + { + // if more than one found, we will return diagnostics + > 1 => ( + MethodSymbol: null, + DiagnosticInfos: constructors + .Skip(1) + .Select(c => + DiagnosticInfo.Create( + Diagnostics.MultipleConstructorsWithAttribute, + c.Locations.FirstOrDefault()?.ToLocationInfo(), + [AttributeConstants.MiddlewareConstructor] + ) + ) + .ToArray() + ), + + // return single constructor that has an `[MiddlewareConstructor]` attribute + 1 => (MethodSymbol: constructors.FirstOrDefault(), DiagnosticInfos: []), - internal bool IsInterfaceImplemented(string interfaceName) => - classInfo.ImplementedInterfaces.Any(i => i == interfaceName); + // 2. default to constructor with most parameters + _ => ( + MethodSymbol: constructors.OrderByDescending(c => c.Parameters.Length).First(), + DiagnosticInfos: [] + ), + }; } } diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index a6c896f1..7d7166f0 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -2,16 +2,16 @@ file static class UseMiddlewareExtensions { {{~ for call in calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] + {{ call.interceptable_location_attribute }} internal static ILambdaInvocationBuilder UseMiddleware{{ for.index }}( this ILambdaInvocationBuilder builder, params object[] args ) where T : ILambdaMiddleware { - var resolver = new {{ call.short_middleware_class_name }}Resolver{{ for.index }}(args); + var resolver = new {{ call.class_info.short_name }}Resolver{{ for.index }}(args); - {{~ if call.is_async_disposable ~}} + {{~ if call.class_info.implements_async_disposable ~}} builder.Use(next => { return async context => @@ -20,7 +20,7 @@ await middleware.InvokeAsync(context, next); }; }); - {{~ else if call.is_disposable ~}} + {{~ else if call.class_info.implements_disposable ~}} builder.Use(next => { return async context => @@ -42,7 +42,7 @@ return builder; } - private class {{ call.short_middleware_class_name }}Resolver{{ for.index }} + private class {{ call.class_info.short_name }}Resolver{{ for.index }} { {{~ if call.any_parameters && !call.all_from_services ~}} private const int NotCached = -1; @@ -59,7 +59,7 @@ {{~ if call.any_parameters && !call.all_from_services ~}} {{~ end ~}} - internal {{ call.short_middleware_class_name }}Resolver{{ for.index }}(object[] args) => _args = args; + internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; internal {{ call.full_middleware_class_name }} Create(ILambdaInvocationContext context) { From bdaf37bf270d091b7ac11e7e7508740cd98b4d72 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 29 Dec 2025 14:43:45 -0500 Subject: [PATCH 44/67] refactor(source-generators): simplify `ClassInfo.Create` and improve diagnostics handling - Replaced tuple decomposition in `UseMiddlewareTInfo` with direct `diagnostics` collection. - Changed `DiagnosticInfos` in `ClassInfo.Create` from `array` to `List` for better performance. - Enhanced maintainability and consistency by delegating diagnostics aggregation logic. --- src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs | 4 ++-- .../Models/UseMiddlewareTInfo.cs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs index 915ec78a..930a479d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs @@ -35,7 +35,7 @@ internal static class ClassInfoExtensions { internal bool AnyParameters => true; - internal static (ClassInfo? classInfo, DiagnosticInfo[] DiagnosticInfos) Create( + internal static (ClassInfo? classInfo, List DiagnosticInfos) Create( INamedTypeSymbol typeSymbol, GeneratorContext context ) @@ -77,7 +77,7 @@ GeneratorContext context implementsIDisposable, implementsIAsyncDisposable ), - diagnostics.ToArray() + diagnostics ); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs index 56466aed..f0a14f96 100644 --- a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs @@ -52,13 +52,12 @@ typeSymbol as INamedTypeSymbol ) ); - var classInfoResult = ClassInfo.Create(middlewareClassType, context); - if (!classInfoResult.IsSuccess) - diagnosticInfos.Add(classInfoResult.Error!.Value); + var (classInfo, diagnostics) = ClassInfo.Create(middlewareClassType, context); + diagnosticInfos.AddRange(diagnostics); return new UseMiddlewareTInfo( interceptableLocation, - classInfoResult.Value, + classInfo, diagnosticInfos.ToEquatableArray() ); } From 185fc52939c8a28f77914033d2fbd359077bb789 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 30 Dec 2025 19:34:37 -0500 Subject: [PATCH 45/67] refactor(source-generators): rename and refactor models for middleware parameter handling - Renamed `ParameterInfo` to `MiddlewareParameterInfo` for clarity and better alignment with usage context. - Refactored `ClassInfo` to `MiddlewareClassInfo` to better represent middleware-specific logic. - Updated extensions and syntax providers to accommodate the renamed models. - Improved diagnostics generation and handling in parameter creation methods. - Removed outdated and unused `MethodInfo` model and its related extensions. --- .../Diagnostics/DiagnosticGenerator.cs | 7 +- .../Emitters/UseMiddlewareTSource.cs | 67 +++++++------- .../Extensions/EnumerableExtensions.cs | 2 +- .../Extensions/ParameterSymbolExtensions.cs | 11 +++ .../Extensions/TypeExtractorExtensions.cs | 26 ++++-- .../Models/MethodInfo.cs | 52 ----------- .../{ClassInfo.cs => MiddlewareClassInfo.cs} | 52 +++++------ .../Models/MiddlewareParameterInfo.cs | 76 ++++++++++++++++ .../Models/ParameterInfo.cs | 87 ------------------- .../Models/UseMiddlewareTInfo.cs | 4 +- .../UseMiddlewareTSyntaxProvider.cs | 2 +- .../WellKnownTypes/WellKnownTypeData.cs | 2 +- ...lWorldScenario#LambdaHandler.g.verified.cs | 8 +- ...ructorWithArgs#LambdaHandler.g.verified.cs | 2 +- ...mentsAttribute#LambdaHandler.g.verified.cs | 2 +- ...vicesAttribute#LambdaHandler.g.verified.cs | 2 +- ...vicesAttribute#LambdaHandler.g.verified.cs | 2 +- ...rameterSources#LambdaHandler.g.verified.cs | 8 +- ...lableParameter#LambdaHandler.g.verified.cs | 2 +- ...thDefaultValue#LambdaHandler.g.verified.cs | 2 +- ..._WithArgsArray#LambdaHandler.g.verified.cs | 4 +- 21 files changed, 189 insertions(+), 231 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs rename src/MinimalLambda.SourceGenerators/Models/{ClassInfo.cs => MiddlewareClassInfo.cs} (77%) create mode 100644 src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 5b23f7e2..076c4c5f 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -64,13 +64,14 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // foreach (var useMiddlewareTInfo in compilationInfo.UseMiddlewareTInfos) // { // // ensure middleware class is concrete - // if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") + // if (useMiddlewareTInfo.MiddlewareClassInfo.TypeKind is "interface" or "abstract + // class") // { // diagnostics.Add( // Diagnostic.ToLocationInfo( // Diagnostics.MustBeConcreteType, // useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), - // useMiddlewareTInfo.ClassInfo.ShortName + // useMiddlewareTInfo.MiddlewareClassInfo.ShortName // ) // ); // } @@ -79,7 +80,7 @@ internal static List GenerateDiagnostics(CompilationInfo compilation // once // diagnostics.AddRange( // useMiddlewareTInfo - // .ClassInfo.ConstructorInfos.Where(c => + // .MiddlewareClassInfo.ConstructorInfos.Where(c => // c.AttributeInfos.Any(a => // a.FullName == AttributeConstants.MiddlewareConstructor // ) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs index 78a6afc5..a2360ea1 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs @@ -9,14 +9,14 @@ internal static string Generate(EquatableArray useMiddleware { // var useMiddlewareTCalls = useMiddlewareTInfos.Select(useMiddlewareTInfo => // { - // var classInfo = useMiddlewareTInfo.ClassInfo; + // var classInfo = useMiddlewareTInfo.MiddlewareClassInfo; // // // choose what constructor to use with the following criteria: // // 1. if it has a `[MiddlewareConstructor]` attribute. Multiple of these are not // // valid. // // 2. default to the constructor with the most arguments // var constructor = classInfo - // .ConstructorInfos.Select(c => (MethodInfo?)c) + // .ConstructorInfos.Select(c => (MiddlewareConstructorInfo?)c) // .FirstOrDefault(c => // c!.Value.AttributeInfos.Any(a => // a.FullName == AttributeConstants.MiddlewareConstructor @@ -59,11 +59,12 @@ internal static string Generate(EquatableArray useMiddleware // }) // .ToArray(); // - // var isDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( + // var isDisposable = useMiddlewareTInfo.MiddlewareClassInfo.IsInterfaceImplemented( // TypeConstants.IDisposable // ); // - // var isAsyncDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( + // var isAsyncDisposable = + // useMiddlewareTInfo.MiddlewareClassInfo.IsInterfaceImplemented( // TypeConstants.IAsyncDisposable // ); // @@ -92,31 +93,35 @@ internal static string Generate(EquatableArray useMiddleware ); } - private static ParameterArg BuildParameterAssignment(this ParameterInfo param) => - new() - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }; - - private static string RemoveTrailingChar(this string value, string trailing) => - value.EndsWith(trailing) ? value[..^1] : value; - - private readonly record struct ParameterArg(string String, string Assignment); + // private static ParameterArg BuildParameterAssignment(this MiddlewareParameterInfo param) => + // new() + // { + // String = param.ToPublicString(), + // Assignment = param.ServiceSource switch + // { + // // inject keyed service from the DI container - required + // ParameterSource.KeyedService when param.IsRequired => + // + // $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", + // + // // inject keyed service from the DI container - optional + // ParameterSource.KeyedService => + // + // $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", + // + // // default: inject service from the DI container - required + // _ when param.IsRequired => + // + // $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", + // + // // default: inject service from the DI container - optional + // _ => + // $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", + // }, + // }; + // + // private static string RemoveTrailingChar(this string value, string trailing) => + // value.EndsWith(trailing) ? value[..^1] : value; + // + // private readonly record struct ParameterArg(string String, string Assignment); } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index f8a593b0..2ccd5a16 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -39,7 +39,7 @@ public List Add(T item) extension(IEnumerable enumerable) { - internal (List, List) CollectDiagnosticResults( + internal (List Data, List Diagnostics) CollectDiagnosticResults( Func> extractor ) { diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 6a40da63..34f01621 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -101,6 +101,17 @@ GeneratorContext context ) ); } + + internal bool IsDecoratedWithAttribute( + WellKnownType attributeType, + GeneratorContext context + ) => + parameterSymbol + .GetAttributes() + .Any(a => + a.AttributeClass is not null + && context.WellKnownTypes.IsType(a.AttributeClass, [attributeType]) + ); } extension(AttributeData attributeData) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs index 73bd4f41..bc101ef2 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs @@ -5,20 +5,28 @@ namespace MinimalLambda.SourceGenerators.Extensions; internal static class TypeExtractorExtensions { - private static readonly SymbolDisplayFormat Format = + private static readonly SymbolDisplayFormat NullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier ); - internal static string ToGloballyQualifiedName( - this ITypeSymbol typeSymbol, - TypeSyntax? typeSyntax = null - ) + private static readonly SymbolDisplayFormat NotNullableFormat = + SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + ); + + extension(ITypeSymbol typeSymbol) { - var baseTypeName = typeSymbol.ToDisplayString(Format); + internal string ToGloballyQualifiedName(TypeSyntax? typeSyntax = null) + { + var baseTypeName = typeSymbol.ToDisplayString(NullableFormat); + + return typeSyntax is NullableTypeSyntax && !baseTypeName.EndsWith("?") + ? baseTypeName + "?" + : baseTypeName; + } - return typeSyntax is NullableTypeSyntax && !baseTypeName.EndsWith("?") - ? baseTypeName + "?" - : baseTypeName; + internal string ToNotNullableGloballyQualifiedName() => + typeSymbol.ToDisplayString(NotNullableFormat); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs deleted file mode 100644 index a6749286..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct MethodInfo( - int ArgumentCount, - EquatableArray AttributeInfos, - EquatableArray Parameters -); - -internal static class ConstructorInfoExtensions -{ - extension(MethodInfo) - { - internal static MethodInfo Create(IMethodSymbol constructor) - { - var attributeInfos = constructor - .GetAttributes() - .Where(a => a.AttributeClass is not null) - .Select(AttributeInfo.Create) - .ToEquatableArray(); - - var parameterInfos = constructor - .Parameters.Select(ParameterInfo.Create) - .ToEquatableArray(); - - return new MethodInfo(parameterInfos.Count, attributeInfos, parameterInfos); - } - } -} - -internal readonly record struct AttributeInfo(LocationInfo? LocationInfo, string FullName); - -internal static class AttributeInfoExtensions -{ - extension(AttributeInfo) - { - internal static AttributeInfo Create(AttributeData attributeData) - { - var syntax = attributeData.ApplicationSyntaxReference?.GetSyntax(); - var location = syntax?.GetLocation(); - var locationData = location?.ToLocationInfo(); - - var name = attributeData.AttributeClass?.ToString() ?? "UNKNOWN"; - - return new AttributeInfo(locationData, name); - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs similarity index 77% rename from src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs index 930a479d..f7a4853a 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using LayeredCraft.SourceGeneratorTools.Types; @@ -8,37 +9,24 @@ namespace MinimalLambda.SourceGenerators.Models; -internal record ClassInfo( +internal record MiddlewareClassInfo( string GloballyQualifiedName, string ShortName, - EquatableArray ConstructorInfos, + EquatableArray ParameterInfos, bool ImplementsDisposable, bool ImplementsAsyncDisposable -) -{ - internal string NonNullableGloballyQualifiedName - { - get - { - field ??= GloballyQualifiedName.EndsWith("?") - ? GloballyQualifiedName[..^1] - : GloballyQualifiedName; - - return field; - } - } -} +); -internal static class ClassInfoExtensions +internal static class MiddlewareExtensions { - extension(ClassInfo classInfo) + extension(MiddlewareClassInfo middlewareClassInfo) { internal bool AnyParameters => true; - internal static (ClassInfo? classInfo, List DiagnosticInfos) Create( - INamedTypeSymbol typeSymbol, - GeneratorContext context - ) + internal static ( + MiddlewareClassInfo? classInfo, + List DiagnosticInfos + ) Create(INamedTypeSymbol typeSymbol, GeneratorContext context) { List diagnostics = []; @@ -52,10 +40,18 @@ GeneratorContext context var (constructor, constructorDiagnostics) = GetConstructor(typeSymbol, context); diagnostics.AddRange(constructorDiagnostics); - // handle each instance constructor on the type - var constructorInfo = ((INamedTypeSymbol)typeSymbol) - .InstanceConstructors.Select(MethodInfo.Create) - .ToEquatableArray(); + // get constructor parameters + var parameterInfos = constructor is not null + ? constructor + .Parameters.CollectDiagnosticResults(parameter => + MiddlewareParameterInfo.Create(parameter, context) + ) + .Map(results => + { + diagnostics.AddRange(results.Diagnostics); + return results.Data; + }) + : []; // implements IDisposable var implementsIDisposable = context.WellKnownTypes.TypeImplementsInterface( @@ -70,10 +66,10 @@ GeneratorContext context ); return ( - new ClassInfo( + new MiddlewareClassInfo( globallyQualifiedName, shortName, - constructorInfo, + parameterInfos.ToEquatableArray(), implementsIDisposable, implementsIAsyncDisposable ), diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs new file mode 100644 index 00000000..78ef418e --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs @@ -0,0 +1,76 @@ +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record MiddlewareParameterInfo( + string ParameterName, + string GloballyQualifiedType, + string GloballyQualifiedNotNullableType, + bool FromArguments, + bool FromServices, + string FromServicesAssignment, + string InfoComment, + MapHandlerParameterSource ServiceSource, + string? KeyedServicesKey +); + +internal static class MiddlewareParameterInfoExtensions +{ + extension(MiddlewareParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameterSymbol, + GeneratorContext context + ) + { + context.ThrowIfCancellationRequested(); + + // parameter name + var name = parameterSymbol.Name; + + // globally qualified type + var globallyQualifiedType = parameterSymbol.Type.ToGloballyQualifiedName(); + + // globally qualified type - not nullable + var globallyQualifiedNotNullableType = + parameterSymbol.Type.ToNotNullableGloballyQualifiedName(); + + // determine if it has a `[FromServices]` attribute + var fromServices = parameterSymbol.IsDecoratedWithAttribute( + WellKnownType.MinimalLambda_Builder_FromServicesAttribute, + context + ); + + // determine if it has a `[FromArguments]` attribute + var fromArguments = parameterSymbol.IsDecoratedWithAttribute( + WellKnownType.MinimalLambda_Builder_FromServicesAttribute, + context + ); + + // assignment from arguments + + // assignment from services + return parameterSymbol + .GetDiParameterAssignment(context) + .Bind(diInfo => + DiagnosticResult.Success( + new MiddlewareParameterInfo( + InfoComment: "", + ParameterName: name, + GloballyQualifiedType: globallyQualifiedType, + GloballyQualifiedNotNullableType: globallyQualifiedNotNullableType, + FromArguments: fromServices, + FromServices: fromArguments, + FromServicesAssignment: diInfo.Assignment, + ServiceSource: diInfo.Key is not null + ? MapHandlerParameterSource.KeyedServices + : MapHandlerParameterSource.Services, + KeyedServicesKey: diInfo.Key + ) + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs deleted file mode 100644 index 74a4ae92..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct ParameterInfo( - string Name, - TypeInfo TypeInfo, - LocationInfo? LocationInfo, - ParameterSource Source, - KeyedServiceKeyInfo? KeyedServiceKey, - bool IsNullable, - bool IsOptional, - EquatableArray AttributeNames -) -{ - internal bool IsRequired => !IsOptional && !IsNullable; - - internal static ParameterInfo Create(IParameterSymbol parameter) - { - var typeInfo = TypeInfo.Create(parameter.Type); - var name = parameter.Name; - var location = parameter.CreateLocationInfo(); - var (source, keyedService) = GetSourceFromAttribute( - parameter.GetAttributes(), - typeInfo.FullyQualifiedType - ); - var isNullable = parameter.NullableAnnotation == NullableAnnotation.Annotated; - var isOptional = parameter.IsOptional; - var attributeNames = parameter - .GetAttributes() - .Where(a => a.AttributeClass is not null) - .Select(a => a.AttributeClass!.ToString()) - .ToEquatableArray(); - - return new ParameterInfo( - name, - typeInfo, - location, - source, - keyedService, - isNullable, - isOptional, - attributeNames - ); - } - - internal string ToPublicString() => - $"{nameof(ParameterInfo)} {{ " - + $"Type = {TypeInfo.FullyQualifiedType}, " - + $"{nameof(Name)} = {Name}, " - + $"{nameof(Source)} = {Source}, " - + $"{nameof(IsNullable)} = {IsNullable}, " - + $"{nameof(IsOptional)} = {IsOptional}" - + $"{(KeyedServiceKey.HasValue ? ", " + KeyedServiceKey.Value.ToPublicString() + " " : "")}}}"; - - private static ( - ParameterSource Source, - KeyedServiceKeyInfo? KeyedServiceKey - ) GetSourceFromAttribute(IEnumerable attributes, string type) - { - // try and extract source from attributes - foreach (var attribute in attributes) - switch (attribute.AttributeClass?.ToString()) - { - case AttributeConstants.EventAttribute: - case AttributeConstants.FromEventAttribute: - return (ParameterSource.Event, null); - - case AttributeConstants.FromKeyedService: - var keyedServiceKey = KeyedServiceKeyInfo.Create(attribute); - return (ParameterSource.KeyedService, keyedServiceKey); - } - - // fallback to get source from type - return type switch - { - TypeConstants.CancellationToken => (ParameterSource.CancellationToken, null), - TypeConstants.ILambdaContext => (ParameterSource.HostContext, null), - TypeConstants.ILambdaInvocationContext => (ParameterSource.HostContext, null), - TypeConstants.ILambdaLifecycleContext => (ParameterSource.LifecycleContext, null), - _ => (ParameterSource.Service, null), - }; - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs index f0a14f96..64c832ed 100644 --- a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs @@ -11,7 +11,7 @@ namespace MinimalLambda.SourceGenerators.Models; internal record UseMiddlewareTInfo( string? InterceptableLocationAttribute, - ClassInfo? ClassInfo, + MiddlewareClassInfo? ClassInfo, EquatableArray DiagnosticInfos ); @@ -52,7 +52,7 @@ typeSymbol as INamedTypeSymbol ) ); - var (classInfo, diagnostics) = ClassInfo.Create(middlewareClassType, context); + var (classInfo, diagnostics) = MiddlewareClassInfo.Create(middlewareClassType, context); diagnosticInfos.AddRange(diagnostics); return new UseMiddlewareTInfo( diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index 67fbdc7c..4dd6ce30 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -45,7 +45,7 @@ CancellationToken cancellationToken // genericArgumentLocation = typeArgument.GetLocation(); // } // - // var classInfo = ClassInfo.Create(middlewareClassType); + // var classInfo = MiddlewareClassInfo.Create(middlewareClassType); // // var interceptableLocation = context.SemanticModel.GetInterceptableLocation( // (InvocationExpressionSyntax)targetOperation.Syntax, diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs index f26050e4..01624495 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -80,7 +80,7 @@ public enum WellKnownType "System.Threading.Tasks.Task`1", "System.Threading.Tasks.ValueTask", "System.Threading.Tasks.ValueTask`1", - "System.Reflection.ParameterInfo", + "System.Reflection.MiddlewareParameterInfo", "System.IParsable`1", "Microsoft.Extensions.DependencyInjection.OutputCacheConventionBuilderExtensions", "Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions", diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs index 28145279..223f1aca 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs @@ -76,19 +76,19 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : throw new InvalidOperationException("Parameter 'name' of type 'string' must be provided in args"); - // ParameterInfo { Type = global::ILogger, Name = logger, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "primary", Type = string, BaseType = object } } + // MiddlewareParameterInfo { Type = global::ILogger, Name = logger, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "primary", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("primary"); - // ParameterInfo { Type = global::IMetrics, Name = metrics, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IMetrics, Name = metrics, Source = Service, IsNullable = False, IsOptional = False} var arg2 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IDataService?, Name = dataService, Source = Service, IsNullable = True, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IDataService?, Name = dataService, Source = Service, IsNullable = True, IsOptional = False} var arg3 = _cache3 >= 0 ? (global::IDataService?)_args[_cache3] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs index 2f0fd7c9..38073b63 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs @@ -75,7 +75,7 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService)_args[_cache0] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs index 7753f04a..f615d328 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs @@ -75,7 +75,7 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs index 4c232acb..66e151bb 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } + // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("myKey"); return new global::MyLambdaMiddleware(arg0); diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs index dd30689a..28e1b81e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); return new global::MyLambdaMiddleware(arg0); diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs index aca3e663..44873acd 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs @@ -76,19 +76,19 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::ILogger, Name = logger, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = global::ILogger, Name = logger, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::ICache, Name = cache, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "cache", Type = string, BaseType = object } } + // MiddlewareParameterInfo { Type = global::ICache, Name = cache, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "cache", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("cache"); - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg2 = _cache2 >= 0 ? (string)_args[_cache2] : throw new InvalidOperationException("Parameter 'apiKey' of type 'string' must be provided in args"); - // ParameterInfo { Type = global::IMetrics?, Name = metrics, Source = Service, IsNullable = True, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IMetrics?, Name = metrics, Source = Service, IsNullable = True, IsOptional = False} var arg3 = _cache3 >= 0 ? (global::IMetrics?)_args[_cache3] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs index 0d9446e3..7bde4dc8 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs @@ -75,7 +75,7 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::IService?, Name = service, Source = Service, IsNullable = True, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IService?, Name = service, Source = Service, IsNullable = True, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService?)_args[_cache0] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs index 3c086068..6bf21dde 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs @@ -75,7 +75,7 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = True} + // MiddlewareParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = True} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs index 23db5e79..b2ef8440 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs @@ -76,13 +76,13 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} + // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = _cache1 >= 0 ? (global::IService)_args[_cache1] From ff9f06e96876fb9270588b861b8fd820cf86abe8 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 30 Dec 2025 19:43:58 -0500 Subject: [PATCH 46/67] refactor(tests): remove commented-out `MiddlewareParameterInfo` diagnostics from snapshot tests - Cleaned up outdated diagnostic comments across multiple `LambdaHandler.g.verified.cs` snapshot files. - Improved readability and maintainability of the test snapshots by removing unnecessary noise. - Ensured no functional changes to the test logic or middleware handling. --- ...lWorldScenario#LambdaHandler.g.verified.cs | 20 ++++++++----------- ...ructorWithArgs#LambdaHandler.g.verified.cs | 11 +++++----- ...mentsAttribute#LambdaHandler.g.verified.cs | 11 +++++----- ...vicesAttribute#LambdaHandler.g.verified.cs | 7 +++---- ...vicesAttribute#LambdaHandler.g.verified.cs | 7 +++---- ...rameterSources#LambdaHandler.g.verified.cs | 20 ++++++++----------- ...lableParameter#LambdaHandler.g.verified.cs | 11 +++++----- ...thDefaultValue#LambdaHandler.g.verified.cs | 11 +++++----- ..._WithArgsArray#LambdaHandler.g.verified.cs | 14 ++++++------- 9 files changed, 48 insertions(+), 64 deletions(-) diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs index 223f1aca..e57e42be 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache0 = NotCached; // string private int _cache3 = NotCached; // global::IDataService? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,27 +76,23 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : throw new InvalidOperationException("Parameter 'name' of type 'string' must be provided in args"); - - // MiddlewareParameterInfo { Type = global::ILogger, Name = logger, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "primary", Type = string, BaseType = object } } + var arg1 = context.ServiceProvider.GetRequiredKeyedService("primary"); - - // MiddlewareParameterInfo { Type = global::IMetrics, Name = metrics, Source = Service, IsNullable = False, IsOptional = False} + var arg2 = context.ServiceProvider.GetRequiredService(); - - // MiddlewareParameterInfo { Type = global::IDataService?, Name = dataService, Source = Service, IsNullable = True, IsOptional = False} + var arg3 = _cache3 >= 0 ? (global::IDataService?)_args[_cache3] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0, arg1, arg2, arg3); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -127,4 +123,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs index 38073b63..259fd390 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // global::IService - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService)_args[_cache0] : context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs index f615d328..c93faa51 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // string - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : throw new InvalidOperationException("Parameter 'apiKey' of type 'string' must be provided in args"); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs index 66e151bb..adf3093e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,9 +67,8 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("myKey"); - + return new global::MyLambdaMiddleware(arg0); } } @@ -79,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs index 28e1b81e..d2e6b09f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,9 +67,8 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0); } } @@ -79,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs index 44873acd..7eef3be6 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache2 = NotCached; // string private int _cache3 = NotCached; // global::IMetrics? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,27 +76,23 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = global::ILogger, Name = logger, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - - // MiddlewareParameterInfo { Type = global::ICache, Name = cache, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "cache", Type = string, BaseType = object } } + var arg1 = context.ServiceProvider.GetRequiredKeyedService("cache"); - - // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} + var arg2 = _cache2 >= 0 ? (string)_args[_cache2] : throw new InvalidOperationException("Parameter 'apiKey' of type 'string' must be provided in args"); - - // MiddlewareParameterInfo { Type = global::IMetrics?, Name = metrics, Source = Service, IsNullable = True, IsOptional = False} + var arg3 = _cache3 >= 0 ? (global::IMetrics?)_args[_cache3] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0, arg1, arg2, arg3); } - + private void BuildResolutionCache() { _cache2 = FromServices; @@ -127,4 +123,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs index 7bde4dc8..cae4cafc 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // global::IService? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = global::IService?, Name = service, Source = Service, IsNullable = True, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService?)_args[_cache0] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs index 6bf21dde..1bd48759 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // string - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = True} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs index b2ef8440..960c6d7e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache0 = NotCached; // string private int _cache1 = NotCached; // global::IService - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,21 +76,19 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // MiddlewareParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : context.ServiceProvider.GetRequiredService(); - - // MiddlewareParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} + var arg1 = _cache1 >= 0 ? (global::IService)_args[_cache1] : context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0, arg1); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -121,4 +119,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} From 1fa903c80f3392b77ff4a2a1c544b5338ec88777 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 30 Dec 2025 20:04:00 -0500 Subject: [PATCH 47/67] feat(source-generators): add `AllFromServices` property and refactor template for parameter handling - Introduced `AllFromServices` property in `MiddlewareClassInfo` for streamlined parameter checks. - Updated `UseMiddlewareT` template to utilize `AllFromServices` and `parameter_infos` for clarity. - Replaced outdated `parameters` logic with `parameter_infos` and refactored related assignments. - Improved maintainability by consolidating and simplifying parameter resolution logic. --- .../Models/MiddlewareClassInfo.cs | 5 ++- .../Templates/UseMiddlewareT.scriban | 39 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs index f7a4853a..70f6a357 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs @@ -15,7 +15,10 @@ internal record MiddlewareClassInfo( EquatableArray ParameterInfos, bool ImplementsDisposable, bool ImplementsAsyncDisposable -); +) +{ + internal bool AllFromServices => ParameterInfos.All(p => p.FromServices); +} internal static class MiddlewareExtensions { diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index 7d7166f0..c360ff67 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -44,55 +44,57 @@ private class {{ call.class_info.short_name }}Resolver{{ for.index }} { - {{~ if call.any_parameters && !call.all_from_services ~}} + {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} private const int NotCached = -1; private const int FromServices = -2; private bool _cacheBuilt = false; {{~ end ~}} private readonly object[] _args; - {{~ for parameter in call.parameters ~}} + {{ ~ for parameter in call.parameter_infos ~ }} {{~ if !parameter.from_services ~}} - private int _cache{{ for.index }} = NotCached; // {{ parameter.fully_qualified_type }} + private int _cache{{ for.index }} = NotCached; // {{ parameter.globally_qualified_type }} {{~ end ~}} {{~ end ~}} - {{~ if call.any_parameters && !call.all_from_services ~}} + {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} {{~ end ~}} internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; - internal {{ call.full_middleware_class_name }} Create(ILambdaInvocationContext context) + internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext + context) { - {{~ if call.any_parameters ~}} - {{~ if !call.all_from_services ~}} + {{ ~ if call.parameter_infos.size > 0 ~ }} + {{ ~ if !call.parameter_infos.all_from_services ~ }} if (!_cacheBuilt) BuildResolutionCache(); {{~ end ~}} - {{~ for parameter in call.parameters ~}} - // {{ parameter.string }} + {{ ~ for parameter in call.parameter_infos ~ }} {{~ if parameter.from_services ~}} - var arg{{ for.index }} = {{ parameter.assignment }}; + var arg{{ for.index }} = {{ parameter.from_services_assignment }}; {{~ else ~}} var arg{{ for.index }} = _cache{{ for.index }} >= 0 - ? ({{ parameter.fully_qualified_type }})_args[_cache{{ for.index }}] + ? ({{ parameter.globally_qualified_type }})_args[_cache{{ for.index }}] {{~ if parameter.from_arguments ~}} - : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type '{{ parameter.fully_qualified_type }}' must be provided in args"); + : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type + '{{ parameter.globally_qualified_type }}' must be provided in args"); {{~ else ~}} - : {{ parameter.assignment }}; + : {{ parameter.from_services_assignment }}; {{~ end ~}} {{~ end ~}} {{~ end ~}} {{~ end ~}} - return new {{ call.full_middleware_class_name }}({{ for arg in call.parameters }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + return new {{ call.class_info.globally_qualified_name }}({{ for arg in call.parameter_infos }} + arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); } - {{~ if call.any_parameters && !call.all_from_services ~}} + {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} private void BuildResolutionCache() { - {{~ for parameter in call.parameters ~}} + {{ ~ for parameter in call.parameter_infos ~ }} {{~ if !parameter.from_services ~}} _cache{{ for.index }} = FromServices; {{~ end ~}} @@ -106,9 +108,10 @@ switch (arg) { - {{~ for parameter in call.parameters ~}} + {{ ~ for parameter in call.parameter_infos ~ }} {{~ if !parameter.from_services ~}} - case {{ parameter.fully_qualified_type_not_null }} when _cache{{ for.index }} == FromServices: + case {{ parameter.globally_qualified_not_nullable_type }} when _cache{{ for.index }} == + FromServices: _cache{{ for.index }} = i; break; {{~ end ~}} From eb55e0a9fcecd21ce6109c51ae4c244b0ef6b85f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 30 Dec 2025 20:10:26 -0500 Subject: [PATCH 48/67] refactor(source-generators): replace `MiddlewareParameterInfo` with `ParameterInfo` in type mappings - Updated `WellKnownTypeData` to use `System.Reflection.ParameterInfo` for consistency with standard types. - Removed outdated reference to `MiddlewareParameterInfo` in the well-known types array. --- .../WellKnownTypes/WellKnownTypeData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs index 01624495..f26050e4 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -80,7 +80,7 @@ public enum WellKnownType "System.Threading.Tasks.Task`1", "System.Threading.Tasks.ValueTask", "System.Threading.Tasks.ValueTask`1", - "System.Reflection.MiddlewareParameterInfo", + "System.Reflection.ParameterInfo", "System.IParsable`1", "Microsoft.Extensions.DependencyInjection.OutputCacheConventionBuilderExtensions", "Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions", From f3215e853620f89536809c737abf604d75bbdcd6 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 31 Dec 2025 19:21:12 -0500 Subject: [PATCH 49/67] feat(source-generators): enhance parameter handling and resolution logic - Refactored `MiddlewareParameterInfo` to include improved attribute checks for `FromArguments` and `FromServices`. - Added `AllParametersFromServices` property to `MiddlewareClassInfo` to simplify parameter validation. - Updated `UseMiddlewareT` template to leverage `AllParametersFromServices` for cleaner code generation. - Consolidated `IsDecoratedWithAttribute` logic in `ParameterSymbolExtensions` for improved reusability. - Renamed `MiddlewareParameterInfo` fields for better clarity and alignment with usage context. - Removed outdated and unused commented code across test snapshots for better readability. --- .../Extensions/ParameterSymbolExtensions.cs | 23 ++++++------ .../Extensions/TypeExtractorExtensions.cs | 2 +- .../Models/MiddlewareClassInfo.cs | 18 ++++++---- .../Models/MiddlewareParameterInfo.cs | 27 +++++++------- .../Templates/UseMiddlewareT.scriban | 35 ++++++++++--------- .../GeneratorTestHelpers.cs | 2 -- ...ractMiddleware#LambdaHandler.g.verified.cs | 4 +-- ...syncDisposable#LambdaHandler.g.verified.cs | 4 +-- ...ss_IDisposable#LambdaHandler.g.verified.cs | 4 +-- ...areConstructor#LambdaHandler.g.verified.cs | 4 +-- ...reClass_Simple#LambdaHandler.g.verified.cs | 4 +-- 11 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 34f01621..b2af1ec9 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -9,6 +9,18 @@ namespace Microsoft.CodeAnalysis; internal static class ParameterSymbolExtensions { + internal static bool IsDecoratedWithAttribute( + this IParameterSymbol parameterSymbol, + GeneratorContext context, + params WellKnownType[] attributeType + ) => + parameterSymbol + .GetAttributes() + .Any(a => + a.AttributeClass is not null + && context.WellKnownTypes.IsType(a.AttributeClass, attributeType) + ); + extension(IParameterSymbol parameterSymbol) { internal bool IsFromEvent(GeneratorContext context) @@ -101,17 +113,6 @@ GeneratorContext context ) ); } - - internal bool IsDecoratedWithAttribute( - WellKnownType attributeType, - GeneratorContext context - ) => - parameterSymbol - .GetAttributes() - .Any(a => - a.AttributeClass is not null - && context.WellKnownTypes.IsType(a.AttributeClass, [attributeType]) - ); } extension(AttributeData attributeData) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs index bc101ef2..f2e744a6 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs @@ -12,7 +12,7 @@ internal static class TypeExtractorExtensions private static readonly SymbolDisplayFormat NotNullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( - SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + SymbolDisplayMiscellaneousOptions.ExpandNullable ); extension(ITypeSymbol typeSymbol) diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs index 70f6a357..52fbe0e6 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs @@ -14,11 +14,9 @@ internal record MiddlewareClassInfo( string ShortName, EquatableArray ParameterInfos, bool ImplementsDisposable, - bool ImplementsAsyncDisposable -) -{ - internal bool AllFromServices => ParameterInfos.All(p => p.FromServices); -} + bool ImplementsAsyncDisposable, + bool AllParametersFromServices +); internal static class MiddlewareExtensions { @@ -68,13 +66,17 @@ List DiagnosticInfos WellKnownType.System_IAsyncDisposable ); + // are all parameters for the constructor from services + var allParametersFromServices = parameterInfos.All(p => p.FromServices); + return ( new MiddlewareClassInfo( globallyQualifiedName, shortName, parameterInfos.ToEquatableArray(), implementsIDisposable, - implementsIAsyncDisposable + implementsIAsyncDisposable, + allParametersFromServices ), diagnostics ); @@ -122,7 +124,9 @@ a.AttributeClass is not null // 2. default to constructor with most parameters _ => ( - MethodSymbol: constructors.OrderByDescending(c => c.Parameters.Length).First(), + MethodSymbol: namedTypeSymbol + .InstanceConstructors.OrderByDescending(c => c.Parameters.Length) + .First(), DiagnosticInfos: [] ), }; diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs index 78ef418e..0c7cce1b 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs @@ -5,7 +5,7 @@ namespace MinimalLambda.SourceGenerators.Models; internal record MiddlewareParameterInfo( - string ParameterName, + string Name, string GloballyQualifiedType, string GloballyQualifiedNotNullableType, bool FromArguments, @@ -37,19 +37,20 @@ GeneratorContext context var globallyQualifiedNotNullableType = parameterSymbol.Type.ToNotNullableGloballyQualifiedName(); - // determine if it has a `[FromServices]` attribute - var fromServices = parameterSymbol.IsDecoratedWithAttribute( - WellKnownType.MinimalLambda_Builder_FromServicesAttribute, - context - ); - // determine if it has a `[FromArguments]` attribute var fromArguments = parameterSymbol.IsDecoratedWithAttribute( - WellKnownType.MinimalLambda_Builder_FromServicesAttribute, - context + context, + WellKnownType.MinimalLambda_Builder_FromArgumentsAttribute ); - // assignment from arguments + // determine if it has a `[FromServices]` attribute + var fromServices = + !fromArguments + && parameterSymbol.IsDecoratedWithAttribute( + context, + WellKnownType.MinimalLambda_Builder_FromServicesAttribute, + WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute + ); // assignment from services return parameterSymbol @@ -58,11 +59,11 @@ GeneratorContext context DiagnosticResult.Success( new MiddlewareParameterInfo( InfoComment: "", - ParameterName: name, + Name: name, GloballyQualifiedType: globallyQualifiedType, GloballyQualifiedNotNullableType: globallyQualifiedNotNullableType, - FromArguments: fromServices, - FromServices: fromArguments, + FromArguments: fromArguments, + FromServices: fromServices, FromServicesAssignment: diInfo.Assignment, ServiceSource: diInfo.Key is not null ? MapHandlerParameterSource.KeyedServices diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index c360ff67..d2def4e2 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -10,7 +10,7 @@ where T : ILambdaMiddleware { var resolver = new {{ call.class_info.short_name }}Resolver{{ for.index }}(args); - + {{~ if call.class_info.implements_async_disposable ~}} builder.Use(next => { @@ -44,33 +44,33 @@ private class {{ call.class_info.short_name }}Resolver{{ for.index }} { - {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} + {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} private const int NotCached = -1; private const int FromServices = -2; private bool _cacheBuilt = false; {{~ end ~}} private readonly object[] _args; - {{ ~ for parameter in call.parameter_infos ~ }} + {{ ~ for parameter in call.class_info.parameter_infos ~ }} {{~ if !parameter.from_services ~}} private int _cache{{ for.index }} = NotCached; // {{ parameter.globally_qualified_type }} {{~ end ~}} {{~ end ~}} - {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} - - {{~ end ~}} + {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} + + {{~ end ~}} internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext context) { - {{ ~ if call.parameter_infos.size > 0 ~ }} - {{ ~ if !call.parameter_infos.all_from_services ~ }} + {{ ~ if call.class_info.parameter_infos.count > 0 ~ }} + {{ ~ if !call.class_info.all_parameters_from_services ~ }} if (!_cacheBuilt) BuildResolutionCache(); {{~ end ~}} - {{ ~ for parameter in call.parameter_infos ~ }} + {{ ~ for parameter in call.class_info.parameter_infos ~ }} {{~ if parameter.from_services ~}} var arg{{ for.index }} = {{ parameter.from_services_assignment }}; {{~ else ~}} @@ -84,17 +84,18 @@ : {{ parameter.from_services_assignment }}; {{~ end ~}} {{~ end ~}} - + {{~ end ~}} {{~ end ~}} - return new {{ call.class_info.globally_qualified_name }}({{ for arg in call.parameter_infos }} - arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + return new {{ call.class_info.globally_qualified_name }} + ({{ for arg in call.class_info.parameter_infos }}arg{{ for.index }}{{ if !for.last }} + , {{ end }}{{ end }}); } - {{ ~ if call.parameter_infos.size > 0 && !call.parameter_infos.all_from_services ~ }} - - private void BuildResolutionCache() + {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} + + private void BuildResolutionCache() { - {{ ~ for parameter in call.parameter_infos ~ }} + {{ ~ for parameter in call.class_info.parameter_infos ~ }} {{~ if !parameter.from_services ~}} _cache{{ for.index }} = FromServices; {{~ end ~}} @@ -108,7 +109,7 @@ switch (arg) { - {{ ~ for parameter in call.parameter_infos ~ }} + {{ ~ for parameter in call.class_info.parameter_infos ~ }} {{~ if !parameter.from_services ~}} case {{ parameter.globally_qualified_not_nullable_type }} when _cache{{ for.index }} == FromServices: diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs index 3a593f14..61f1160b 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs @@ -24,8 +24,6 @@ internal static Task Verify(string source, int expectedTrees = 1) var result = driver.GetRunResult(); - // result.Diagnostics.Length.Should().Be(0);s - // Reparse generated trees with the same parse options as the original compilation // to ensure consistent syntax tree features (e.g., InterceptorsNamespaces) var parseOptions = originalCompilation.SyntaxTrees.First().Options; diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs index 9fc2dc04..f244b32c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddleware2Resolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs index 31f82828..f0719af3 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return async context => @@ -77,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs index d5208f9d..3b0c8cf5 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return async context => @@ -77,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs index 5e76e9ab..403974bd 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs index 5e76e9ab..403974bd 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} From d76a819f6451d0aca07f040c3872d2a0be7e3d76 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 31 Dec 2025 19:23:54 -0500 Subject: [PATCH 50/67] refactor(source-generators): remove unused models and simplify parameter source handling - Deleted `KeyedServiceKeyInfo`, `TypeInfo`, and `MapHandlerParameterSource` as they are no longer used. - Consolidated the usage of `MapHandlerParameterSource` into `ParameterSource` for reduced redundancy. - Updated parameter source assignments in related models for consistency. - Improved maintainability by cleaning up outdated logic and references. --- .../Models/KeyedServiceKeyInfo.cs | 90 ------------------- .../Models/LifecycleHandlerParameterInfo.cs | 13 ++- .../Models/MapHandlerParameterInfo.cs | 14 +-- .../Models/MapHandlerParameterSource.cs | 10 --- .../Models/MiddlewareParameterInfo.cs | 6 +- .../Models/ParameterSource.cs | 7 +- .../Models/TypeInfo.cs | 60 ------------- 7 files changed, 19 insertions(+), 181 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs delete mode 100644 src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs deleted file mode 100644 index e263ebca..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using MinimalLambda.SourceGenerators.Extensions; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct KeyedServiceKeyInfo( - string? DisplayValue, - string? Type, - string? BaseType, - LocationInfo? LocationInfo -) -{ - internal static KeyedServiceKeyInfo Create(AttributeData attribute) - { - var (key, keyType, keyBaseType) = ExtractKeyedServiceKey(attribute.ConstructorArguments[0]); - - var keyedServiceKeyInfo = new KeyedServiceKeyInfo(key, keyType, keyBaseType, null); - - // conditionally get location info only if a key is null as a diagnostic needs to be - // provided in that case. - if ( - key is null - && attribute.ApplicationSyntaxReference?.GetSyntax() - is AttributeSyntax { ArgumentList: { Arguments.Count: > 0 } argumentList } - ) - { - var argument = argumentList.Arguments[0]; - var location = argument.Expression.GetLocation(); - var locationInfo = location.ToLocationInfo(); - return keyedServiceKeyInfo with { LocationInfo = locationInfo }; - } - - return keyedServiceKeyInfo; - } - - internal string ToPublicString() => - $"{nameof(KeyedServiceKeyInfo)} {{ " - + $"{nameof(DisplayValue)} = {DisplayValue}, " - + $"{nameof(Type)} = {Type}, " - + $"{nameof(BaseType)} = {BaseType} }}"; - - private static (string? Key, string? KeyType, string? KeyBaseType) ExtractKeyedServiceKey( - TypedConstant argument - ) - { - var keyBaseType = argument.Type?.BaseType?.ToGloballyQualifiedName(); - var keyType = argument.Type?.ToGloballyQualifiedName(); - - if (argument.IsNull) - return ("null", keyType, keyBaseType); - - object? value; - try - { - value = argument.Value; - } - catch - { - return (null, keyType, keyBaseType); - } - - if (value is null) - return (null, keyType, keyBaseType); - - // Generate the literal C# code to recreate this value - var keyLiteral = argument.Kind switch - { - TypedConstantKind.Primitive when value is string strValue => - SymbolDisplay.FormatLiteral(strValue, true), - - TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", - - TypedConstantKind.Primitive when value is bool boolValue => boolValue - ? "true" - : "false", - - TypedConstantKind.Primitive or TypedConstantKind.Enum => - $"({argument.Type?.ToGloballyQualifiedName()}){value}", - - TypedConstantKind.Type when value is ITypeSymbol typeValue => - $"typeof({typeValue.ToGloballyQualifiedName()})", - - _ => value.ToString(), - }; - - return (keyLiteral, keyType, keyBaseType); - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs index 5e1665b2..7011077d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs @@ -1,5 +1,4 @@ using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Extensions; using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; @@ -10,7 +9,7 @@ internal record LifecycleHandlerParameterInfo( string InfoComment, bool IsFromKeyedService, LocationInfo? LocationInfo, - MapHandlerParameterSource Source, + ParameterSource Source, string? KeyedServicesKey ); @@ -29,7 +28,7 @@ GeneratorContext context Assignment: string.Empty, InfoComment: string.Empty, KeyedServicesKey: string.Empty, - Source: MapHandlerParameterSource.Services + Source: ParameterSource.Services ); // context @@ -43,7 +42,7 @@ GeneratorContext context parameterInfo with { Assignment = "context", - Source = MapHandlerParameterSource.Context, + Source = ParameterSource.Context, } ); @@ -58,7 +57,7 @@ parameterInfo with parameterInfo with { Assignment = "context.CancellationToken", - Source = MapHandlerParameterSource.CancellationToken, + Source = ParameterSource.CancellationToken, } ); @@ -72,8 +71,8 @@ parameterInfo with Assignment = diInfo.Assignment, IsFromKeyedService = diInfo.Key is not null, Source = diInfo.Key is not null - ? MapHandlerParameterSource.KeyedServices - : MapHandlerParameterSource.Services, + ? ParameterSource.KeyedServices + : ParameterSource.Services, KeyedServicesKey = diInfo.Key, } ) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index 42cc90f3..1ece18b2 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -13,7 +13,7 @@ internal record MapHandlerParameterInfo( bool IsEvent, bool IsFromKeyedService, LocationInfo? LocationInfo, - MapHandlerParameterSource Source, + ParameterSource Source, string? KeyedServicesKey ); @@ -37,7 +37,7 @@ GeneratorContext context Assignment: string.Empty, InfoComment: string.Empty, KeyedServicesKey: string.Empty, - Source: MapHandlerParameterSource.Services + Source: ParameterSource.Services ); // event @@ -51,7 +51,7 @@ parameterInfo with // non stream event : $"context.GetRequiredEvent<{paramType}>()", IsEvent = true, - Source = MapHandlerParameterSource.Event, + Source = ParameterSource.Event, } ); @@ -67,7 +67,7 @@ parameterInfo with parameterInfo with { Assignment = "context", - Source = MapHandlerParameterSource.Context, + Source = ParameterSource.Context, } ); @@ -82,7 +82,7 @@ parameterInfo with parameterInfo with { Assignment = "context.CancellationToken", - Source = MapHandlerParameterSource.CancellationToken, + Source = ParameterSource.CancellationToken, } ); @@ -96,8 +96,8 @@ parameterInfo with Assignment = diInfo.Assignment, IsFromKeyedService = diInfo.Key is not null, Source = diInfo.Key is not null - ? MapHandlerParameterSource.KeyedServices - : MapHandlerParameterSource.Services, + ? ParameterSource.KeyedServices + : ParameterSource.Services, KeyedServicesKey = diInfo.Key, } ) diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs deleted file mode 100644 index 6f011c93..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterSource.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal enum MapHandlerParameterSource -{ - Event, - Context, - CancellationToken, - KeyedServices, - Services, -} diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs index 0c7cce1b..720fabff 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs @@ -12,7 +12,7 @@ internal record MiddlewareParameterInfo( bool FromServices, string FromServicesAssignment, string InfoComment, - MapHandlerParameterSource ServiceSource, + ParameterSource ServiceSource, string? KeyedServicesKey ); @@ -66,8 +66,8 @@ GeneratorContext context FromServices: fromServices, FromServicesAssignment: diInfo.Assignment, ServiceSource: diInfo.Key is not null - ? MapHandlerParameterSource.KeyedServices - : MapHandlerParameterSource.Services, + ? ParameterSource.KeyedServices + : ParameterSource.Services, KeyedServicesKey: diInfo.Key ) ) diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs b/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs index 61315319..abec383b 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs +++ b/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs @@ -3,9 +3,8 @@ namespace MinimalLambda.SourceGenerators.Models; internal enum ParameterSource { Event, - KeyedService, + Context, CancellationToken, - HostContext, - LifecycleContext, - Service, + KeyedServices, + Services, } diff --git a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs b/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs deleted file mode 100644 index 9bcd61fb..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using MinimalLambda.SourceGenerators.Extensions; - -namespace MinimalLambda.SourceGenerators.Models; - -/// Represents the information associated with a named type in C# source code. -internal readonly record struct TypeInfo( - string FullyQualifiedType, - string? UnwrappedFullyQualifiedType, - bool IsGeneric, - ImmutableArray ImplementedInterfaces -); - -internal static class TypeInfoExtensions -{ - extension(TypeInfo typeInfo) - { - internal static TypeInfo Create(ITypeSymbol typeSymbol, TypeSyntax? syntax = null) - { - var fullyQualifiedType = typeSymbol.ToGloballyQualifiedName(syntax); - var unwrappedFullyQualifiedType = typeSymbol.GetUnwrappedFullyQualifiedType(syntax); - var isGeneric = typeSymbol is INamedTypeSymbol { IsGenericType: true }; - var implementedInterfaces = typeSymbol - .AllInterfaces.Select(i => i.ToGloballyQualifiedName()) - .ToImmutableArray(); - - return new TypeInfo( - fullyQualifiedType, - unwrappedFullyQualifiedType, - isGeneric, - implementedInterfaces - ); - } - - internal static TypeInfo CreateFullyQualifiedType(string fullyQualifiedType) => - new(fullyQualifiedType, null, false, ImmutableArray.Empty); - } - - extension(ITypeSymbol typeSymbol) - { - /// Gets a fully qualified type name without it being wrapped in Task or ValueTask - private string? GetUnwrappedFullyQualifiedType(TypeSyntax? syntax = null) - { - if ( - typeSymbol is not INamedTypeSymbol namedTypeSymbol - || (!namedTypeSymbol.IsTask() && !namedTypeSymbol.IsValueTask()) - ) - return typeSymbol.ToGloballyQualifiedName(syntax); - - // if not generic Task or ValueTask, return null as no wrapped return value - if (!namedTypeSymbol.IsGenericType || namedTypeSymbol.TypeArguments.Length == 0) - return null; - - return namedTypeSymbol.TypeArguments.First().ToGloballyQualifiedName(syntax); - } - } -} From 3f5263cc7e1933912625613b5d906783b84ccf2f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 1 Jan 2026 15:06:12 -0500 Subject: [PATCH 51/67] feat(source-generators): enhance middleware diagnostics and improve template clarity - Added support for diagnostic generation from `UseMiddlewareTInfos` in `DiagnosticGenerator`. - Updated `UseMiddlewareT` template to improve formatting and reduce redundant whitespace. - Included `liquid` language settings in `.DotSettings` with default `Reformat` configuration. - Improved consistency in `Rearrange` and `Reformat` settings across various language configurations. --- MinimalLambda.sln.DotSettings | 11 +++++++---- .../Diagnostics/DiagnosticGenerator.cs | 6 ++++++ .../Templates/UseMiddlewareT.scriban | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/MinimalLambda.sln.DotSettings b/MinimalLambda.sln.DotSettings index 7497ea41..17c2743e 100644 --- a/MinimalLambda.sln.DotSettings +++ b/MinimalLambda.sln.DotSettings @@ -9,8 +9,8 @@ &lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt; &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; &lt;Language id="CSS"&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;/Language&gt; &lt;Language id="EditorConfig"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -20,8 +20,8 @@ &lt;/Language&gt; &lt;Language id="HTML"&gt; &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;/Language&gt; &lt;Language id="HTTP Request"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -40,8 +40,8 @@ &lt;/Language&gt; &lt;Language id="JavaScript"&gt; &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;/Language&gt; &lt;Language id="Markdown"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; @@ -72,8 +72,11 @@ &lt;/Language&gt; &lt;Language id="XML"&gt; &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;/Language&gt; + &lt;Language id="liquid"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="yaml"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 076c4c5f..fb937978 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -29,6 +29,12 @@ internal static List GenerateDiagnostics(CompilationInfo compilation .Select(d => d.ToDiagnostic()) ); + diagnostics.AddRange( + compilationInfo + .UseMiddlewareTInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); + // var delegateInfos = compilationInfo.MapHandlerInvocationInfos; // // // // Validate parameters diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index d2def4e2..7308aa22 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -58,7 +58,7 @@ {{~ end ~}} {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} - {{~ end ~}} + {{ ~ end ~ }} internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext From 2a4cdbec53292528c689f2b705a281d13309992c Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Thu, 1 Jan 2026 21:06:06 -0500 Subject: [PATCH 52/67] refactor(source-generators): update `DiagnosticInfo` to use `record` and clean up duplication - Converted `DiagnosticInfo` from `struct` to `record` for better immutability and pattern matching. - Simplified equality checks and hash code generation in `DiagnosticInfo`. - Removed redundant value access in `DiagnosticResult` methods to streamline code. --- .../DiagnosticResult.cs | 8 +++--- .../Models/DiagnosticInfo.cs | 27 +++++++------------ 2 files changed, 14 insertions(+), 21 deletions(-) rename src/MinimalLambda.SourceGenerators/{Models => Diagnostics}/DiagnosticResult.cs (89%) diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs similarity index 89% rename from src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs rename to src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs index 7dd1564d..5e3aa0e0 100644 --- a/src/MinimalLambda.SourceGenerators/Models/DiagnosticResult.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs @@ -31,21 +31,21 @@ params object?[] messageArgs public DiagnosticResult Map(Func map) => IsSuccess ? DiagnosticResult.Success(map(Value!)) - : DiagnosticResult.Failure(Error!.Value); + : DiagnosticResult.Failure(Error!); public DiagnosticResult Bind(Func> bind) => - IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!.Value); + IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!); public TResult Match( Func onSuccess, Func onFailure - ) => IsSuccess ? onSuccess(Value!) : onFailure(Error!.Value); + ) => IsSuccess ? onSuccess(Value!) : onFailure(Error!); public void Do(Action onSuccess, Action onFailure) { if (IsSuccess) onSuccess(Value!); else - onFailure(Error!.Value); + onFailure(Error!); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs index ccacaee1..dee169c3 100644 --- a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs @@ -1,27 +1,20 @@ -using System; using LayeredCraft.SourceGeneratorTools.Utilities; using Microsoft.CodeAnalysis; namespace MinimalLambda.SourceGenerators.Models; -internal readonly struct DiagnosticInfo( - DiagnosticDescriptor diagnosticDescriptor, - LocationInfo? locationInfo = null, - params object?[] messageArgs -) : IEquatable +internal sealed record DiagnosticInfo( + DiagnosticDescriptor DiagnosticDescriptor, + LocationInfo? LocationInfo = null, + params object?[] MessageArgs +) { - public DiagnosticDescriptor DiagnosticDescriptor { get; } = diagnosticDescriptor; - public LocationInfo? LocationInfo { get; } = locationInfo; - public object?[] MessageArgs { get; } = messageArgs; + public bool Equals(DiagnosticInfo? other) => + other is not null + && Equals(DiagnosticDescriptor.Id, other.DiagnosticDescriptor.Id) + && Equals(LocationInfo, other.LocationInfo); - public bool Equals(DiagnosticInfo other) => - DiagnosticDescriptor.Id == other.DiagnosticDescriptor.Id - && LocationInfo == other.LocationInfo; - - public override bool Equals(object? obj) => obj is DiagnosticInfo other && Equals(other); - - public override int GetHashCode() => - HashCode.Combine(DiagnosticDescriptor.Id.GetHashCode(), LocationInfo.GetHashCode()); + public override int GetHashCode() => HashCode.Combine(DiagnosticDescriptor, LocationInfo); } internal static class DiagnosticInfoExtensions From afdb0c955ede783310eec2990f5ae7dcf2ac7a96 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 6 Jan 2026 12:55:43 -0500 Subject: [PATCH 53/67] refactor(source-generators): replace type symbol extension methods with consolidated properties - Removed `TypeSymbolExtensions.IsTask` and `IsValueTask` methods as they were unused. - Replaced `ToGloballyQualifiedName` and `ToNotNullableGloballyQualifiedName` methods with `QualifiedName` and `QualifiedNullableName` properties for simplicity. - Updated all references to use `QualifiedName` and `QualifiedNullableName`. - Removed redundant `GetTypeKind` logic from `TypeSymbolExtensions` to streamline type analysis. - Improved readability and maintainability by removing outdated code and consolidation of extension logic. --- .../Extensions/MethodSymbolExtensions.cs | 4 +- .../Extensions/ParameterSymbolExtensions.cs | 8 +-- .../Extensions/TypeExtractorExtensions.cs | 13 +--- .../Extensions/TypeSymbolExtensions.cs | 69 ------------------- .../Models/MapHandlerMethodInfo.cs | 4 +- .../Models/MapHandlerParameterInfo.cs | 4 +- .../Models/MiddlewareClassInfo.cs | 2 +- .../Models/MiddlewareParameterInfo.cs | 5 +- 8 files changed, 15 insertions(+), 94 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs index ba74fb93..dd0b4a88 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -13,12 +13,12 @@ internal static class MethodSymbolExtensions { internal string GetCastableSignature() { - var returnType = methodSymbol.ReturnType.ToGloballyQualifiedName(); + var returnType = methodSymbol.ReturnType.QualifiedNullableName; var parameters = methodSymbol .Parameters.Select( (p, i) => { - var type = p.Type.ToGloballyQualifiedName(); + var type = p.Type.QualifiedNullableName; var defaultValue = p.IsOptional ? " = default" : ""; return $"{type} arg{i}{defaultValue}"; } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index b2af1ec9..46f2a1d7 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -81,7 +81,7 @@ out DiagnosticResult? keyResult GeneratorContext context ) { - var paramType = parameterSymbol.Type.ToGloballyQualifiedName(); + var paramType = parameterSymbol.Type.QualifiedNullableName; var isKeyedServices = parameterSymbol.IsFromKeyedService(context, out var keyResult); @@ -138,7 +138,7 @@ private DiagnosticResult ExtractKeyedServiceKey() return DiagnosticResult.Failure( MinimalLambda.SourceGenerators.Diagnostics.InvalidAttributeArgument, attributeData.GetAttributeArgumentLocation(0), - argument.Type?.ToGloballyQualifiedName() + argument.Type?.QualifiedNullableName ); return DiagnosticResult.Success( @@ -154,10 +154,10 @@ private DiagnosticResult ExtractKeyedServiceKey() : "false", TypedConstantKind.Primitive or TypedConstantKind.Enum => - $"({argument.Type?.ToGloballyQualifiedName()}){value}", + $"({argument.Type?.QualifiedNullableName}){value}", TypedConstantKind.Type when value is ITypeSymbol typeValue => - $"typeof({typeValue.ToGloballyQualifiedName()})", + $"typeof({typeValue.QualifiedNullableName})", _ => value.ToString(), } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs index f2e744a6..8e99f256 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MinimalLambda.SourceGenerators.Extensions; @@ -17,16 +16,8 @@ internal static class TypeExtractorExtensions extension(ITypeSymbol typeSymbol) { - internal string ToGloballyQualifiedName(TypeSyntax? typeSyntax = null) - { - var baseTypeName = typeSymbol.ToDisplayString(NullableFormat); + internal string QualifiedName => typeSymbol.ToDisplayString(NotNullableFormat); - return typeSyntax is NullableTypeSyntax && !baseTypeName.EndsWith("?") - ? baseTypeName + "?" - : baseTypeName; - } - - internal string ToNotNullableGloballyQualifiedName() => - typeSymbol.ToDisplayString(NotNullableFormat); + internal string QualifiedNullableName => typeSymbol.ToDisplayString(NullableFormat); } } diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs deleted file mode 100644 index a8f4a7c8..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators; - -internal static class TypeSymbolExtensions -{ - extension(ITypeSymbol typeSymbol) - { - internal bool IsTask() => - typeSymbol.Name == "Task" - && typeSymbol.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; - - internal bool IsValueTask() => - typeSymbol.Name == "ValueTask" - && typeSymbol.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; - - internal string GetTypeKind() - { - // Check if it's an interface - if (typeSymbol.TypeKind == TypeKind.Interface) - return "interface"; - - // Check if it's a class - if (typeSymbol.TypeKind == TypeKind.Class) - { - // Check if it's abstract - if (typeSymbol.IsAbstract && typeSymbol.IsSealed) - return "static class"; - - if (typeSymbol.IsAbstract) - return "abstract class"; - - if (typeSymbol.IsSealed) - return "sealed class"; - - if (typeSymbol.IsRecord) - return "record class"; - - return "class"; - } - - // Check if it's a struct - if (typeSymbol.TypeKind == TypeKind.Struct) - { - if (typeSymbol.IsRecord) - return "record struct"; - - if (typeSymbol.IsReadOnly) - return "readonly struct"; - - if (typeSymbol.IsRefLikeType) - return "ref struct"; - - return "struct"; - } - - // Check if it's an enum - if (typeSymbol.TypeKind == TypeKind.Enum) - return "enum"; - - // Check if it's a delegate - if (typeSymbol.TypeKind == TypeKind.Delegate) - return "delegate"; - - // Other types - return typeSymbol.TypeKind.ToString().ToLower(); - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs index decaf98f..c35e1531 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs @@ -35,7 +35,7 @@ GeneratorContext context var eventAttribute = new Lazy(() => context .WellKnownTypes.Get(WellKnownType.MinimalLambda_Builder_FromEventAttribute) - .ToGloballyQualifiedName() + .QualifiedNullableName ); return assignments @@ -105,7 +105,7 @@ out var unwrappedReturnType IsEventTypeStream: isEventTypeStream, HasEvent: hasEvent, EventType: eventType, - UnwrappedResponseType: unwrappedReturnType?.ToGloballyQualifiedName(), + UnwrappedResponseType: unwrappedReturnType?.QualifiedNullableName, HasAnyFromKeyedServices: hasAnyKeyedServices, DiagnosticInfos: diagnostics.ToEquatableArray() ); diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs index 1ece18b2..ab6188cf 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs @@ -26,10 +26,10 @@ internal static DiagnosticResult Create( GeneratorContext context ) { - var paramType = parameter.Type.ToGloballyQualifiedName(); + var paramType = parameter.Type.QualifiedNullableName; var parameterInfo = new MapHandlerParameterInfo( - parameter.Type.ToGloballyQualifiedName(), + parameter.Type.QualifiedNullableName, context.WellKnownTypes.IsTypeMatch(parameter.Type, WellKnownType.System_IO_Stream), IsEvent: false, IsFromKeyedService: false, diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs index 52fbe0e6..714b3078 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs @@ -32,7 +32,7 @@ List DiagnosticInfos List diagnostics = []; // get the globally qualified name of the class - var globallyQualifiedName = typeSymbol.ToGloballyQualifiedName(); + var globallyQualifiedName = typeSymbol.QualifiedNullableName; // get short name, i.e., not qualified var shortName = typeSymbol.Name; diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs index 720fabff..3ad20474 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs @@ -31,11 +31,10 @@ GeneratorContext context var name = parameterSymbol.Name; // globally qualified type - var globallyQualifiedType = parameterSymbol.Type.ToGloballyQualifiedName(); + var globallyQualifiedType = parameterSymbol.Type.QualifiedNullableName; // globally qualified type - not nullable - var globallyQualifiedNotNullableType = - parameterSymbol.Type.ToNotNullableGloballyQualifiedName(); + var globallyQualifiedNotNullableType = parameterSymbol.Type.QualifiedName; // determine if it has a `[FromArguments]` attribute var fromArguments = parameterSymbol.IsDecoratedWithAttribute( From b354ea8aed23e9dcec261194c4aa86955db2d713 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 6 Jan 2026 12:57:58 -0500 Subject: [PATCH 54/67] refactor(source-generators): remove `UseMiddlewareTSource` class and inline code generation - Deleted `UseMiddlewareTSource` class as it is no longer needed for `UseMiddlewareT` generation logic. - Inlined template rendering directly in `MinimalLambdaEmitter` for simplifying code and reducing indirection. - Improved maintainability by eliminating a redundant layer of abstraction. --- .../Emitters/MinimalLambdaEmitter.cs | 7 +- .../Emitters/UseMiddlewareTSource.cs | 127 ------------------ 2 files changed, 6 insertions(+), 128 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index 6fc78d94..76780a20 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -66,7 +66,12 @@ namespace MinimalLambda.Generated // add UseMiddleware interceptors if (compilationInfo.UseMiddlewareTInfos.Count >= 1) - outputs.Add(UseMiddlewareTSource.Generate(compilationInfo.UseMiddlewareTInfos)); + outputs.Add( + TemplateHelper.Render( + GeneratorConstants.UseMiddlewareTTemplateFile, + new { GeneratedCodeAttribute, Calls = compilationInfo.UseMiddlewareTInfos } + ) + ); // add OnInit interceptors if (compilationInfo.OnInitInvocationInfos.Count >= 1) diff --git a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs deleted file mode 100644 index a2360ea1..00000000 --- a/src/MinimalLambda.SourceGenerators/Emitters/UseMiddlewareTSource.cs +++ /dev/null @@ -1,127 +0,0 @@ -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class UseMiddlewareTSource -{ - internal static string Generate(EquatableArray useMiddlewareTInfos) - { - // var useMiddlewareTCalls = useMiddlewareTInfos.Select(useMiddlewareTInfo => - // { - // var classInfo = useMiddlewareTInfo.MiddlewareClassInfo; - // - // // choose what constructor to use with the following criteria: - // // 1. if it has a `[MiddlewareConstructor]` attribute. Multiple of these are not - // // valid. - // // 2. default to the constructor with the most arguments - // var constructor = classInfo - // .ConstructorInfos.Select(c => (MiddlewareConstructorInfo?)c) - // .FirstOrDefault(c => - // c!.Value.AttributeInfos.Any(a => - // a.FullName == AttributeConstants.MiddlewareConstructor - // ) - // ); - // - // constructor ??= classInfo - // .ConstructorInfos.OrderByDescending(c => c.ArgumentCount) - // .First(); - // - // var parameters = constructor - // .Value.Parameters.Select(p => - // { - // var fromArgs = p.AttributeNames.Any(n => n == - // AttributeConstants.FromArguments); - // - // // From services is defined as either having a `[FromServices]` - // // attribute or a - // // `[FromKeyedServices]` attribute - // var fromServices = p.AttributeNames.Any(n => - // n is AttributeConstants.FromServices or - // AttributeConstants.FromKeyedService - // ); - // - // var paramAssignment = p.BuildParameterAssignment(); - // - // var fullyQualifiedTypeNotNull = - // p.TypeInfo.FullyQualifiedType.RemoveTrailingChar("?"); - // - // return new - // { - // p.TypeInfo.FullyQualifiedType, - // FullyQualifiedTypeNotNull = fullyQualifiedTypeNotNull, - // p.Name, - // FromArguments = fromArgs, - // FromServices = fromServices, - // paramAssignment.Assignment, - // paramAssignment.String, - // }; - // }) - // .ToArray(); - // - // var isDisposable = useMiddlewareTInfo.MiddlewareClassInfo.IsInterfaceImplemented( - // TypeConstants.IDisposable - // ); - // - // var isAsyncDisposable = - // useMiddlewareTInfo.MiddlewareClassInfo.IsInterfaceImplemented( - // TypeConstants.IAsyncDisposable - // ); - // - // var allFromServices = parameters.All(p => p.FromServices); - // - // return new - // { - // Location = useMiddlewareTInfo.InterceptableLocationInfo, - // FullMiddlewareClassName = classInfo.GloballyQualifiedName, - // ShortMiddlewareClassName = classInfo.ShortName, - // AllFromServices = allFromServices, - // Parameters = parameters, - // AnyParameters = parameters.Length > 0, - // IsDisposable = isDisposable, - // IsAsyncDisposable = isAsyncDisposable, - // }; - // }); - // - // return TemplateHelper.Render( - // GeneratorConstants.UseMiddlewareTTemplateFile, - // new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } - // ); - return TemplateHelper.Render( - GeneratorConstants.UseMiddlewareTTemplateFile, - new { MinimalLambdaEmitter.GeneratedCodeAttribute, Calls = useMiddlewareTInfos } - ); - } - - // private static ParameterArg BuildParameterAssignment(this MiddlewareParameterInfo param) => - // new() - // { - // String = param.ToPublicString(), - // Assignment = param.ServiceSource switch - // { - // // inject keyed service from the DI container - required - // ParameterSource.KeyedService when param.IsRequired => - // - // $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - // - // // inject keyed service from the DI container - optional - // ParameterSource.KeyedService => - // - // $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - // - // // default: inject service from the DI container - required - // _ when param.IsRequired => - // - // $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - // - // // default: inject service from the DI container - optional - // _ => - // $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - // }, - // }; - // - // private static string RemoveTrailingChar(this string value, string trailing) => - // value.EndsWith(trailing) ? value[..^1] : value; - // - // private readonly record struct ParameterArg(string String, string Assignment); -} From 1384e33327415964a6f663c8aff319f4d31b38f5 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 6 Jan 2026 13:13:30 -0500 Subject: [PATCH 55/67] refactor(source-generators): streamline enumerable diagnostic results and enhance `DiagnosticResult` - Replaced method body in `CollectDiagnosticResults` with lambda expression for simplicity. - Consolidated list initialization within `Aggregate` for improved readability. - Renamed `Do` method to `Switch` in `DiagnosticResult` for better clarity and intent. - Made `DiagnosticResult` a `sealed` class to prevent inheritance. - Added implicit conversion from `T` to `DiagnosticResult` for cleaner usage. --- .../Diagnostics/DiagnosticResult.cs | 6 ++++-- .../Extensions/EnumerableExtensions.cs | 17 +++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs index 5e3aa0e0..c113ffe2 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs @@ -5,7 +5,7 @@ namespace MinimalLambda.SourceGenerators.Models; -internal class DiagnosticResult +internal sealed class DiagnosticResult { internal bool IsSuccess { get; } internal T? Value { get; } @@ -20,6 +20,8 @@ private DiagnosticResult(bool isSuccess, T? value, DiagnosticInfo? error) public static DiagnosticResult Success(T value) => new(true, value, null); + public static implicit operator DiagnosticResult(T value) => Success(value); + public static DiagnosticResult Failure(DiagnosticInfo error) => new(false, default, error); public static DiagnosticResult Failure( @@ -41,7 +43,7 @@ public TResult Match( Func onFailure ) => IsSuccess ? onSuccess(Value!) : onFailure(Error!); - public void Do(Action onSuccess, Action onFailure) + public void Switch(Action onSuccess, Action onFailure) { if (IsSuccess) onSuccess(Value!); diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index 2ccd5a16..e9a1f846 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -41,19 +41,17 @@ public List Add(T item) { internal (List Data, List Diagnostics) CollectDiagnosticResults( Func> extractor - ) - { - var count = 0; - if (enumerable is ICollection collection) - count = collection.Count; - - return enumerable + ) => + enumerable .Select(extractor) .Aggregate( - (Successes: new List(count), Diagnostics: new List()), + ( + Successes: new List(enumerable is ICollection c ? c.Count : 0), + Diagnostics: new List() + ), static (acc, result) => { - result.Do( + result.Switch( info => acc.Successes.Add(info), diagnostic => acc.Diagnostics.Add(diagnostic) ); @@ -61,6 +59,5 @@ Func> extractor return acc; } ); - } } } From 111a0d3ed2e8602f5a6219e0042a59cba6d5f7a9 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 6 Jan 2026 13:17:03 -0500 Subject: [PATCH 56/67] refactor(source-generators): simplify `InterceptableLocationInfo` usage and refactor attribute logic - Replaced `ToInterceptsLocationAttribute` method with `Attribute` property for better clarity and brevity. - Made `InterceptableLocationInfo` a `sealed` record for improved immutability and design consistency. - Updated all references to use the new `Attribute` property, reducing redundant method calls. - Improved maintainability and readability by streamlining attribute handling logic. --- .../Models/InterceptableLocationInfo.cs | 8 ++------ .../Models/LifecycleMethodInfo.cs | 4 ++-- .../Models/MapHandlerMethodInfo.cs | 2 +- .../Models/UseMiddlewareTInfo.cs | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs index 5e0c5f6e..3550eae6 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs @@ -4,11 +4,7 @@ namespace MinimalLambda.SourceGenerators.Models; -internal readonly record struct InterceptableLocationInfo( - int Version, - string Data, - string DisplayLocation -); +internal sealed record InterceptableLocationInfo(int Version, string Data, string DisplayLocation); internal static class InterceptableLocationInfoExtensions { @@ -45,7 +41,7 @@ internal static bool TryGet( return true; } - internal string ToInterceptsLocationAttribute() => + internal string Attribute => $"""[InterceptsLocation({location.Version}, "{location.Data}")]"""; } diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs index 2e986d0e..0182355d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs @@ -86,7 +86,7 @@ methodSymbol.ReturnType is INamedTypeSymbol namedTypeSymbol return new LifecycleMethodInfo( MethodType: MethodType.OnInit, - InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + InterceptableLocationAttribute: interceptableLocation.Attribute, DelegateCastType: handlerCastType, DiagnosticInfos: diagnostics.ToEquatableArray(), ParameterAssignments: assignments.ToEquatableArray(), @@ -134,7 +134,7 @@ GeneratorContext context return new LifecycleMethodInfo( MethodType: MethodType.OnShutdown, - InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + InterceptableLocationAttribute: interceptableLocation.Attribute, DelegateCastType: handlerCastType, DiagnosticInfos: diagnostics.ToEquatableArray(), ParameterAssignments: assignments.ToEquatableArray(), diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs index c35e1531..851e9a12 100644 --- a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs @@ -96,7 +96,7 @@ out var unwrappedReturnType return new MapHandlerMethodInfo( MethodType: MethodType.MapHandler, - InterceptableLocationAttribute: interceptableLocation.Value.ToInterceptsLocationAttribute(), + InterceptableLocationAttribute: interceptableLocation.Attribute, DelegateCastType: handlerCastType, ParameterAssignments: assignments.ToEquatableArray(), IsAwaitable: isAwaitable, diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs index 64c832ed..658da916 100644 --- a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs @@ -41,7 +41,7 @@ is not InvocationExpressionSyntax invocationExpressionSyntax ) ?? throw new InvalidOperationException("Interceptable location is null") ) .ToInterceptableLocationInfo() - .ToInterceptsLocationAttribute(); + .Attribute; var middlewareClassType = invocationOperation .TargetMethod.TypeArguments.FirstOrDefault() From ea7adb867b3591c558fe6597ca83ac86f2db9614 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 6 Jan 2026 19:47:44 -0500 Subject: [PATCH 57/67] refactor(source-generators): combine generic type constraints in `FunctionalExtensions.Map` - Updated `Map` method in `FunctionalExtensions` to combine type constraints on generic parameters. - Improved readability and reduced redundancy by merging method overloads. --- .../Extensions/FunctionalExtensions.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs index 182f1e3a..da86fc43 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs @@ -2,13 +2,10 @@ namespace System; internal static class FunctionalExtensions { - extension(T source) - { - public TResult Map(Func func) => func(source); - } - extension(T source) { + public TResult Map(Func func) => func(source); + public T Tap(Action action) { action(source); From 8baf1d4f20dfab2dc2853cbc256b0e3c9ab33e83 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 13:35:16 -0500 Subject: [PATCH 58/67] refactor(source-generators): clean up templates, diagnostics, and unused code - Improved formatting and reduced redundant whitespace in `UseMiddlewareT` template for better clarity. - Enhanced diagnostics in tests to ensure generation errors are caught and reported comprehensively. - Removed unused mappings, constants, and commented-out code throughout the source generator. - Simplified `SyntaxProviders` and refactored invocation operation logic for readability and efficiency. - Consolidated redundant extension methods and improved code maintainability. --- .../IncrementalValueProviderExtensions.cs | 7 --- .../GeneratorConstants.cs | 50 ------------------- .../MinimalLambdaGenerator.cs | 43 ---------------- .../UseMiddlewareTSyntaxProvider.cs | 41 ++------------- .../Templates/UseMiddlewareT.scriban | 43 +++++++--------- .../GeneratorTestHelpers.cs | 12 +++++ 6 files changed, 35 insertions(+), 161 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs index 455de416..58f1f66a 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs @@ -2,13 +2,6 @@ namespace Microsoft.CodeAnalysis; internal static class IncrementalValueProviderExtensions { - extension(IncrementalValuesProvider valueProviders) - where T : struct - { - public IncrementalValuesProvider WhereNotNull() => - valueProviders.Where(static v => v is not null).Select(static (v, _) => v!.Value); - } - extension(IncrementalValuesProvider valueProviders) where T : class { diff --git a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs index 6754706c..ae0e2a6c 100644 --- a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs +++ b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs @@ -1,64 +1,14 @@ namespace MinimalLambda.SourceGenerators; -/// Constants for common .NET and AWS Lambda types used in source generation. -internal static class TypeConstants -{ - internal const string ILambdaContext = "global::Amazon.Lambda.Core.ILambdaContext"; - - internal const string ILambdaInvocationContext = - "global::MinimalLambda.ILambdaInvocationContext"; - - internal const string ILambdaLifecycleContext = "global::MinimalLambda.ILambdaLifecycleContext"; - - internal const string IDisposable = "global::System.IDisposable"; - - internal const string IAsyncDisposable = "global::System.IAsyncDisposable"; - - internal const string CancellationToken = "global::System.Threading.CancellationToken"; - - internal const string Task = "global::System.Threading.Tasks.Task"; - - internal const string ValueTask = "global::System.Threading.Tasks.ValueTask"; - - internal const string TaskBool = "global::System.Threading.Tasks.Task"; - - internal const string Void = "void"; - - internal const string Action = "global::System.Action"; - - internal const string Func = "global::System.Func"; - - internal const string Stream = "global::System.IO.Stream"; - - internal const string IServiceProvider = "global::System.IServiceProvider"; -} - /// Constants for attribute names used in source generation. internal static class AttributeConstants { - internal const string EventAttribute = "MinimalLambda.Builder.EventAttribute"; - - internal const string FromEventAttribute = "MinimalLambda.Builder.FromEventAttribute"; - - internal const string FromKeyedService = - "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute"; - internal const string MiddlewareConstructor = "MinimalLambda.Builder.MiddlewareConstructorAttribute"; - - internal const string FromArguments = "MinimalLambda.Builder.FromArgumentsAttribute"; - - internal const string FromServices = "MinimalLambda.Builder.FromServicesAttribute"; } internal static class GeneratorConstants { - internal const string MapHandlerMethodName = "MapHandler"; - - internal const string OnShutdownMethodName = "OnShutdown"; - - internal const string OnInitMethodName = "OnInit"; - internal const string InterceptsLocationAttributeTemplateFile = "Templates/InterceptsLocationAttribute.scriban"; diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 828e9b2c..40e191b2 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -32,42 +32,6 @@ is CSharpCompilation } ); - // // Find all MapHandler method calls with lambda analysis - // var mapHandlerCalls = context - // .SyntaxProvider.CreateSyntaxProvider( - // MapHandlerSyntaxProvider.Predicate, - // MapHandlerSyntaxProvider.Transformer - // ) - // .Where(static m => m is not null) - // .Select(static (m, _) => m!.Value); - // - // // Find all OnShutdown method calls with lambda analysis - // var onShutdownCalls = context - // .SyntaxProvider.CreateSyntaxProvider( - // OnShutdownSyntaxProvider.Predicate, - // OnShutdownSyntaxProvider.Transformer - // ) - // .Where(static m => m is not null) - // .Select(static (m, _) => m!.Value); - // - // // Find all OnInit method calls with lambda analysis - // var onInitCalls = context - // .SyntaxProvider.CreateSyntaxProvider( - // OnInitSyntaxProvider.Predicate, - // OnInitSyntaxProvider.Transformer - // ) - // .Where(static m => m is not null) - // .Select(static (m, _) => m!.Value); - // - // // find LambdaApplicationBuilder.Build() calls - // var lambdaApplicationBuilderBuildCalls = context - // .SyntaxProvider.CreateSyntaxProvider( - // LambdaApplicationBuilderBuildSyntaxProvider.Predicate, - // LambdaApplicationBuilderBuildSyntaxProvider.Transformer - // ) - // .Where(static m => m is not null) - // .Select(static (m, _) => m!.Value); - // handler registration calls var registrationCalls = context .SyntaxProvider.CreateSyntaxProvider( @@ -84,13 +48,6 @@ is CSharpCompilation ) .WhereNotNull(); - // collect call - // var mapHandlerCallsCollected = mapHandlerCalls.Collect(); - // var onShutdownCallsCollected = onShutdownCalls.Collect(); - // var onInitCallsCollected = onInitCalls.Collect(); - // var lambdaApplicationBuilderBuildCallsCollected = - // lambdaApplicationBuilderBuildCalls.Collect(); - var registrationCallsCollected = registrationCalls.Collect(); var useMiddlewareTCallsCollected = useMiddlewareTCalls.Collect(); diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index 4dd6ce30..2bf24b25 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -11,7 +11,7 @@ namespace MinimalLambda.SourceGenerators; internal static class UseMiddlewareTSyntaxProvider { - private static readonly string TargetMethodName = "UseMiddleware"; + private const string TargetMethodName = "UseMiddleware"; internal static bool Predicate(SyntaxNode node, CancellationToken _) => !node.IsGeneratedFile() && node.TryGetMethodName(out var name) && name == TargetMethodName; @@ -23,42 +23,9 @@ CancellationToken cancellationToken { var context = new GeneratorContext(syntaxContext, cancellationToken); - if (!TryGetInvocationOperation(context, out var targetOperation)) - return null; - - return UseMiddlewareTInfo.Create(targetOperation, context); - - // // get class TypeInfo - // var middlewareClassType = targetOperation.TargetMethod.TypeArguments[0]; - // - // // Get location of the generic argument - // Location? genericArgumentLocation = null; - // if ( - // targetOperation.Syntax is InvocationExpressionSyntax - // { - // Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName }, - // } - // ) - // { - // // Get the first type argument's location - // var typeArgument = genericName.TypeArgumentList.Arguments[0]; - // genericArgumentLocation = typeArgument.GetLocation(); - // } - // - // var classInfo = MiddlewareClassInfo.Create(middlewareClassType); - // - // var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - // (InvocationExpressionSyntax)targetOperation.Syntax, - // cancellationToken - // )!; - // - // var useMiddlewareTInfo = new UseMiddlewareTInfo( - // InterceptableLocationInfo.CreateFrom(interceptableLocation), - // classInfo, - // genericArgumentLocation?.ToLocationInfo() - // ); - // - // return useMiddlewareTInfo; + return !TryGetInvocationOperation(context, out var targetOperation) + ? null + : UseMiddlewareTInfo.Create(targetOperation, context); } private static bool TryGetInvocationOperation( diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index 7308aa22..6ce077fd 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -44,58 +44,54 @@ private class {{ call.class_info.short_name }}Resolver{{ for.index }} { - {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} private const int NotCached = -1; private const int FromServices = -2; private bool _cacheBuilt = false; {{~ end ~}} private readonly object[] _args; - {{ ~ for parameter in call.class_info.parameter_infos ~ }} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} - private int _cache{{ for.index }} = NotCached; // {{ parameter.globally_qualified_type }} + private int _cache{{ for.index }} = NotCached; // {{ parameter.globally_qualified_type }} {{~ end ~}} {{~ end ~}} - {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} - {{ ~ end ~ }} + {{~ end ~}} internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; - internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext - context) + internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext context) { - {{ ~ if call.class_info.parameter_infos.count > 0 ~ }} - {{ ~ if !call.class_info.all_parameters_from_services ~ }} + {{~ if call.class_info.parameter_infos.count > 0 ~}} + {{~ if !call.class_info.all_parameters_from_services ~}} if (!_cacheBuilt) BuildResolutionCache(); {{~ end ~}} - {{ ~ for parameter in call.class_info.parameter_infos ~ }} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if parameter.from_services ~}} - var arg{{ for.index }} = {{ parameter.from_services_assignment }}; + var arg{{ for.index }} = {{ parameter.from_services_assignment }}; {{~ else ~}} var arg{{ for.index }} = _cache{{ for.index }} >= 0 - ? ({{ parameter.globally_qualified_type }})_args[_cache{{ for.index }}] + ? ({{ parameter.globally_qualified_type }})_args[_cache{{ for.index }}] {{~ if parameter.from_arguments ~}} - : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type - '{{ parameter.globally_qualified_type }}' must be provided in args"); + : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type '{{ parameter.globally_qualified_type }}' must be provided in args"); {{~ else ~}} - : {{ parameter.from_services_assignment }}; + : {{ parameter.from_services_assignment }}; {{~ end ~}} {{~ end ~}} {{~ end ~}} {{~ end ~}} - return new {{ call.class_info.globally_qualified_name }} - ({{ for arg in call.class_info.parameter_infos }}arg{{ for.index }}{{ if !for.last }} - , {{ end }}{{ end }}); + return new {{ call.class_info.globally_qualified_name }}({{ for arg in call.class_info.parameter_infos }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); } - {{ ~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~ }} + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} - private void BuildResolutionCache() + private void BuildResolutionCache() { - {{ ~ for parameter in call.class_info.parameter_infos ~ }} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} _cache{{ for.index }} = FromServices; {{~ end ~}} @@ -109,10 +105,9 @@ switch (arg) { - {{ ~ for parameter in call.class_info.parameter_infos ~ }} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} - case {{ parameter.globally_qualified_not_nullable_type }} when _cache{{ for.index }} == - FromServices: + case {{ parameter.globally_qualified_not_nullable_type }} when _cache{{ for.index }} == FromServices: _cache{{ for.index }} = i; break; {{~ end ~}} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs index 61f1160b..c709cb4f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs @@ -24,6 +24,18 @@ internal static Task Verify(string source, int expectedTrees = 1) var result = driver.GetRunResult(); + result + .Diagnostics.Should() + .BeEmpty( + "code should be generated without errors, but found:\n" + + string.Join( + "\n---\n", + result.Diagnostics.Select(e => + $" - {e.Id}: {e.GetMessage()} at {e.Location}" + ) + ) + ); + // Reparse generated trees with the same parse options as the original compilation // to ensure consistent syntax tree features (e.g., InterceptorsNamespaces) var parseOptions = originalCompilation.SyntaxTrees.First().Options; From bd9bba9bbb7498035ed7643cc5313d40133b01dc Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 13:42:10 -0500 Subject: [PATCH 59/67] refactor(source-generators): remove redundant extension methods and cleanup legacy code - Deleted `NamedTypeSymbolExtensions` and related methods due to redundancy and lack of usage. - Removed unnecessary extension methods from `EnumerableExtensions` to reduce duplication. - Cleaned up obsolete commented-out code in `DiagnosticGenerator` for better maintainability. - Reorganized folder structure for models to align with updated source generator logic. --- .../Diagnostics/DiagnosticGenerator.cs | 87 ------------------- .../{Models => Diagnostics}/DiagnosticInfo.cs | 0 .../Extensions/EnumerableExtensions.cs | 23 ----- .../Extensions/NamedTypeSymbolExtensions.cs | 48 ---------- .../MapHandlerMethodInfo.cs | 0 .../MapHandlerParameterInfo.cs | 0 .../LifecycleHandlerParameterInfo.cs | 0 .../LifecycleMethodInfo.cs | 0 .../{ => Middleware}/MiddlewareClassInfo.cs | 0 .../MiddlewareParameterInfo.cs | 0 .../{ => Middleware}/UseMiddlewareTInfo.cs | 0 .../Models/{ => Shared}/CompilationInfo.cs | 0 .../Models/{ => Shared}/IMethodInfo.cs | 0 .../{ => Shared}/InterceptableLocationInfo.cs | 0 .../Models/{ => Shared}/LocationInfo.cs | 0 .../Models/{ => Shared}/MethodType.cs | 0 .../Models/{ => Shared}/ParameterSource.cs | 0 17 files changed, 158 deletions(-) rename src/MinimalLambda.SourceGenerators/{Models => Diagnostics}/DiagnosticInfo.cs (100%) delete mode 100644 src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs rename src/MinimalLambda.SourceGenerators/Models/{ => InvocationHandlers}/MapHandlerMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => InvocationHandlers}/MapHandlerParameterInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => LifecycleHandlers}/LifecycleHandlerParameterInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => LifecycleHandlers}/LifecycleMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Middleware}/MiddlewareClassInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Middleware}/MiddlewareParameterInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Middleware}/UseMiddlewareTInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/CompilationInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/IMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/InterceptableLocationInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/LocationInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/MethodType.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{ => Shared}/ParameterSource.cs (100%) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index fb937978..99dcc35e 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -35,93 +35,6 @@ internal static List GenerateDiagnostics(CompilationInfo compilation .Select(d => d.ToDiagnostic()) ); - // var delegateInfos = compilationInfo.MapHandlerInvocationInfos; - // - // // // Validate parameters - // // foreach (var invocationInfo in delegateInfos) - // // // check for multiple parameters that use the `[FromEvent]` attribute - // // if ( - // // invocationInfo.DelegateInfo.Parameters.Count(p => p.Source == - // ParameterSource.Event) - // // > 1 - // // ) - // // diagnostics.AddRange( - // // invocationInfo - // // .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) - // // .Select(p => - // // Diagnostic.ToLocationInfo( - // // Diagnostics.MultipleParametersUseAttribute, - // // p.LocationInfo?.ToLocation(), - // // AttributeConstants.FromEventAttribute - // // ) - // // ) - // // ); - // - // // check for invalid keyed service usage - MapHandler - // diagnostics.AddRange( - // compilationInfo.MapHandlerInvocationInfos.GenerateKeyedServiceKeyDiagnostics() - // ); - // - // // check for invalid keyed service usage - OnShutdown - // diagnostics.AddRange( - // compilationInfo.OnShutdownInvocationInfos.GenerateKeyedServiceKeyDiagnostics() - // ); - // - // foreach (var useMiddlewareTInfo in compilationInfo.UseMiddlewareTInfos) - // { - // // ensure middleware class is concrete - // if (useMiddlewareTInfo.MiddlewareClassInfo.TypeKind is "interface" or "abstract - // class") - // { - // diagnostics.Add( - // Diagnostic.ToLocationInfo( - // Diagnostics.MustBeConcreteType, - // useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), - // useMiddlewareTInfo.MiddlewareClassInfo.ShortName - // ) - // ); - // } - // - // // validate that middleware class constructors only use `[MiddlewareConstructor]` - // once - // diagnostics.AddRange( - // useMiddlewareTInfo - // .MiddlewareClassInfo.ConstructorInfos.Where(c => - // c.AttributeInfos.Any(a => - // a.FullName == AttributeConstants.MiddlewareConstructor - // ) - // ) - // .Skip(1) - // .Select(c => - // Diagnostic.ToLocationInfo( - // Diagnostics.MultipleConstructorsWithAttribute, - // c.AttributeInfos.First(a => - // a.FullName == AttributeConstants.MiddlewareConstructor - // ) - // .LocationInfo?.ToLocation(), - // AttributeConstants.MiddlewareConstructor - // ) - // ) - // ); - // } - return diagnostics; } - - // private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( - // this EquatableArray methodNameInfos - // ) => - // methodNameInfos - // .SelectMany(onShutdownInvocationInfo => - // onShutdownInvocationInfo.DelegateInfo.Parameters - // ) - // .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) - // .Select(parameterInfo => - // Diagnostic.ToLocationInfo( - // Diagnostics.InvalidAttributeArgument, - // parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), - // parameterInfo.KeyedServiceKey.Value.Type - // ) - // ) - // .ToArray(); } diff --git a/src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/DiagnosticInfo.cs rename to src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index e9a1f846..851aae0d 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -14,29 +14,6 @@ public void ForEach(Action action) } } - extension(IEnumerable valueProviders) - where T : struct - { - public IEnumerable WhereNotNull() => - valueProviders.Where(static v => v is not null).Select(static v => v!.Value); - } - - extension(IEnumerable valueProviders) - where T : class - { - public IEnumerable WhereNotNull() => - valueProviders.Where(static v => v is not null).Select(static v => v!); - } - - extension(List list) - { - public List Add(T item) - { - list.Add(item); - return list; - } - } - extension(IEnumerable enumerable) { internal (List Data, List Diagnostics) CollectDiagnosticResults( diff --git a/src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs deleted file mode 100644 index 80241143..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/NamedTypeSymbolExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp; -using MinimalLambda.SourceGenerators; -using MinimalLambda.SourceGenerators.WellKnownTypes; - -namespace Microsoft.CodeAnalysis; - -internal static class NamedTypeSymbolExtensions -{ - extension(INamedTypeSymbol sourceType) - { - internal bool IsAssignableTo(INamedTypeSymbol targetType, GeneratorContext context) - { - var conversion = context.SemanticModel.Compilation.ClassifyConversion( - sourceType, - targetType - ); - return conversion.IsImplicit; - } - - internal bool IsAssignableFrom(INamedTypeSymbol targetType, GeneratorContext context) - { - var conversion = context.SemanticModel.Compilation.ClassifyConversion( - targetType, - sourceType - ); - return conversion.IsImplicit; - } - - internal bool IsAssignableFromOrTo(INamedTypeSymbol otherType, GeneratorContext context) => - sourceType.IsAssignableFrom(otherType, context) - || sourceType.IsAssignableTo(otherType, context); - - internal bool IsAssignableTo( - WellKnownTypeData.WellKnownType targetType, - GeneratorContext context - ) => sourceType.IsAssignableTo(context.WellKnownTypes.Get(targetType), context); - - internal bool IsAssignableFrom( - WellKnownTypeData.WellKnownType targetType, - GeneratorContext context - ) => sourceType.IsAssignableFrom(context.WellKnownTypes.Get(targetType), context); - - internal bool IsAssignableFromOrTo( - WellKnownTypeData.WellKnownType otherType, - GeneratorContext context - ) => sourceType.IsAssignableFromOrTo(context.WellKnownTypes.Get(otherType), context); - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/MapHandlerMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/MapHandlerParameterInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/LifecycleHandlerParameterInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/LifecycleMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/MiddlewareClassInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareParameterInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/MiddlewareParameterInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/CompilationInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/CompilationInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/IMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/IMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/IMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/InterceptableLocationInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/InterceptableLocationInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/LocationInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/LocationInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/MethodType.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/MethodType.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/MethodType.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/MethodType.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/ParameterSource.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/ParameterSource.cs From a2e291616ac70cdf5ceb3537dda42d58ecd1dfb7 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 13:48:52 -0500 Subject: [PATCH 60/67] refactor(source-generators): remove redundant WellKnownTypes extensions and refactor usages - Deleted `WellKnownTypesExtensions` to eliminate redundancy and unused methods. - Updated `IsAnyTypeMatch` and `IsTypeMatch` calls to use `IsType` for consistency and simplicity. - Removed `TypeImplementsInterface` and replaced its logic with direct interface checks. - Simplified type matching throughout models and syntax providers for better maintainability. - Improved code readability by consolidating type-checking logic. --- .../Extensions/MethodSymbolExtensions.cs | 3 +- .../Extensions/WellKnownTypesExtensions.cs | 33 ------------------- .../MapHandlerMethodInfo.cs | 3 +- .../MapHandlerParameterInfo.cs | 7 ++-- .../LifecycleHandlerParameterInfo.cs | 5 ++- .../LifecycleHandlers/LifecycleMethodInfo.cs | 6 ++-- .../Models/Middleware/MiddlewareClassInfo.cs | 11 +++---- .../UseMiddlewareTSyntaxProvider.cs | 3 +- .../WellKnownTypes/WellKnownTypes.cs | 2 +- 9 files changed, 16 insertions(+), 57 deletions(-) delete mode 100644 src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs index dd0b4a88..740e2521 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using MinimalLambda.SourceGenerators; using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace Microsoft.CodeAnalysis; @@ -88,7 +87,7 @@ internal bool HasMeaningfulReturnType( return true; bool IsVoidLike(ITypeSymbol type) => - context.WellKnownTypes.IsAnyTypeMatch( + context.WellKnownTypes.IsType( type, WellKnownType.System_Void, WellKnownType.System_Threading_Tasks_Task, diff --git a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs deleted file mode 100644 index a63305d9..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/WellKnownTypesExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; -using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; - -namespace MinimalLambda.SourceGenerators.WellKnownTypes; - -internal static class WellKnownTypesExtensions -{ - // Open issue w/ extension blocks: https://github.com/dotnet/roslyn/issues/80024 - // ReSharper disable once MoveToExtensionBlock - internal static bool IsAnyTypeMatch( - this WellKnownTypes wellKnownTypes, - ITypeSymbol type, - params WellKnownType[] types - ) => - types - .Select(wellKnownTypes.Get) - .Any(foundType => type.Equals(foundType, SymbolEqualityComparer.Default)); - - extension(WellKnownTypes wellKnownTypes) - { - internal bool IsTypeMatch(ITypeSymbol type, WellKnownType wellKnownType) - { - var foundType = wellKnownTypes.Get(wellKnownType); - return type.Equals(foundType, SymbolEqualityComparer.Default); - } - - internal bool TypeImplementsInterface( - INamedTypeSymbol namedTypeSymbol, - WellKnownType interfaceType - ) => namedTypeSymbol.AllInterfaces.Any(i => wellKnownTypes.IsTypeMatch(i, interfaceType)); - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs index 851e9a12..8f1d853a 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs @@ -4,7 +4,6 @@ using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -78,7 +77,7 @@ out var unwrappedReturnType var isReturnTypeStream = hasResponse - && context.WellKnownTypes.IsTypeMatch( + && context.WellKnownTypes.IsType( methodSymbol.ReturnType, WellKnownType.System_IO_Stream ); diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs index ab6188cf..4942e399 100644 --- a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -30,7 +29,7 @@ GeneratorContext context var parameterInfo = new MapHandlerParameterInfo( parameter.Type.QualifiedNullableName, - context.WellKnownTypes.IsTypeMatch(parameter.Type, WellKnownType.System_IO_Stream), + context.WellKnownTypes.IsType(parameter.Type, WellKnownType.System_IO_Stream), IsEvent: false, IsFromKeyedService: false, LocationInfo: LocationInfo.Create(parameter), @@ -57,7 +56,7 @@ parameterInfo with // context if ( - context.WellKnownTypes.IsAnyTypeMatch( + context.WellKnownTypes.IsType( parameter.Type, WellKnownType.Amazon_Lambda_Core_ILambdaContext, WellKnownType.MinimalLambda_ILambdaInvocationContext @@ -73,7 +72,7 @@ parameterInfo with // cancellation token if ( - context.WellKnownTypes.IsTypeMatch( + context.WellKnownTypes.IsType( parameter.Type, WellKnownType.System_Threading_CancellationToken ) diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs index 7011077d..f37eeb1a 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs @@ -1,5 +1,4 @@ using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -33,7 +32,7 @@ GeneratorContext context // context if ( - context.WellKnownTypes.IsAnyTypeMatch( + context.WellKnownTypes.IsType( parameter.Type, WellKnownType.MinimalLambda_ILambdaLifecycleContext ) @@ -48,7 +47,7 @@ parameterInfo with // cancellation token if ( - context.WellKnownTypes.IsTypeMatch( + context.WellKnownTypes.IsType( parameter.Type, WellKnownType.System_Threading_CancellationToken ) diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs index 0182355d..f5eb1e76 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs @@ -47,7 +47,7 @@ out var unwrappedReturnType var unwrappedReturnIsBool = hasResponse - && context.WellKnownTypes.IsTypeMatch( + && context.WellKnownTypes.IsType( unwrappedReturnType!, WellKnownTypeData.WellKnownType.System_Boolean ); @@ -62,7 +62,7 @@ out var unwrappedReturnType var returnIsTaskBool = methodSymbol.ReturnType is INamedTypeSymbol namedTypeSymbol - && context.WellKnownTypes.IsTypeMatch( + && context.WellKnownTypes.IsType( namedTypeSymbol.ConstructedFrom, WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T ) @@ -114,7 +114,7 @@ GeneratorContext context var isAwaitable = methodSymbol.IsAwaitable(context); - var returnIsTask = context.WellKnownTypes.IsTypeMatch( + var returnIsTask = context.WellKnownTypes.IsType( methodSymbol.ReturnType, WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task ); diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs index 714b3078..54736fa3 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs @@ -4,7 +4,6 @@ using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators.Models; @@ -55,15 +54,13 @@ List DiagnosticInfos : []; // implements IDisposable - var implementsIDisposable = context.WellKnownTypes.TypeImplementsInterface( - typeSymbol, - WellKnownType.System_IDisposable + var implementsIDisposable = typeSymbol.AllInterfaces.Any(i => + context.WellKnownTypes.IsType(i, WellKnownType.System_IDisposable) ); // implements IAsyncDisposable - var implementsIAsyncDisposable = context.WellKnownTypes.TypeImplementsInterface( - typeSymbol, - WellKnownType.System_IAsyncDisposable + var implementsIAsyncDisposable = typeSymbol.AllInterfaces.Any(i => + context.WellKnownTypes.IsType(i, WellKnownType.System_IAsyncDisposable) ); // are all parameters for the constructor from services diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index 2bf24b25..5588f487 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; using MinimalLambda.SourceGenerators.Models; -using MinimalLambda.SourceGenerators.WellKnownTypes; using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators; @@ -56,7 +55,7 @@ is IInvocationOperation && targetOperation.TargetMethod.ConstructedFrom.TypeParameters.FirstOrDefault() is { } typeParameter && typeParameter.ConstraintTypes.Any(c => - context.WellKnownTypes.IsTypeMatch(c, WellKnownType.MinimalLambda_ILambdaMiddleware) + context.WellKnownTypes.IsType(c, WellKnownType.MinimalLambda_ILambdaMiddleware) ) ) { diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs index 91d66fb5..bdda23b7 100644 --- a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs @@ -120,7 +120,7 @@ private INamedTypeSymbol GetAndCache(int index) return null; } - public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes) => + public bool IsType(ITypeSymbol type, params WellKnownTypeData.WellKnownType[] wellKnownTypes) => IsType(type, wellKnownTypes, out _); public bool IsType( From 3853c8a35ffd0f084ad7097b0eca065b9997e62f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 13:50:38 -0500 Subject: [PATCH 61/67] refactor(source-generators): rename extension classes and streamline diagnostics collection - Renamed `SyntaxExtensions` to `SyntaxNodeExtensions` for better semantic clarity. - Renamed `TypeExtractorExtensions` to `TypeSymbolExtensions` to align with updated naming conventions. - Updated `CollectDiagnosticResults` method to fix type parameter naming inconsistency and improve readability. --- .../Extensions/EnumerableExtensions.cs | 7 ++----- .../{SyntaxExtensions.cs => SyntaxNodeExtensions.cs} | 2 +- ...{TypeExtractorExtensions.cs => TypeSymbolExtensions.cs} | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) rename src/MinimalLambda.SourceGenerators/Extensions/{SyntaxExtensions.cs => SyntaxNodeExtensions.cs} (95%) rename src/MinimalLambda.SourceGenerators/Extensions/{TypeExtractorExtensions.cs => TypeSymbolExtensions.cs} (94%) diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index 851aae0d..dbd2d98c 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -12,18 +12,15 @@ public void ForEach(Action action) foreach (var item in enumerable) action(item); } - } - extension(IEnumerable enumerable) - { internal (List Data, List Diagnostics) CollectDiagnosticResults( - Func> extractor + Func> extractor ) => enumerable .Select(extractor) .Aggregate( ( - Successes: new List(enumerable is ICollection c ? c.Count : 0), + Successes: new List(enumerable is ICollection c ? c.Count : 0), Diagnostics: new List() ), static (acc, result) => diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs similarity index 95% rename from src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs rename to src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs index 430874eb..6107bfde 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs @@ -4,7 +4,7 @@ namespace MinimalLambda.SourceGenerators; -internal static class SyntaxExtensions +internal static class SyntaxNodeExtensions { extension(SyntaxNode node) { diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs similarity index 94% rename from src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs rename to src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs index 8e99f256..e8d810f9 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs @@ -2,7 +2,7 @@ namespace MinimalLambda.SourceGenerators.Extensions; -internal static class TypeExtractorExtensions +internal static class TypeSymbolExtensions { private static readonly SymbolDisplayFormat NullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( From df56b320e23d7afcace39309f28a62a59ad68be9 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 16:55:44 -0500 Subject: [PATCH 62/67] refactor(source-generators): enhance middleware diagnostics and streamline processing - Improved error messages in `UseMiddlewareTInfo` and `MiddlewareClassInfo` for better debugging. - Added validation for concrete middleware class types to ensure they are non-abstract and non-interface. - Refactored constructor logic to simplify diagnostics collection and streamline parameter handling. - Optimized `TryGetInvocationOperation` in `UseMiddlewareTSyntaxProvider` for readability and efficiency. - Enhanced type argument location handling for better accuracy in diagnostics. --- .../Models/Middleware/MiddlewareClassInfo.cs | 36 +++++++++++++------ .../Models/Middleware/UseMiddlewareTInfo.cs | 24 ++++++++++--- .../UseMiddlewareTSyntaxProvider.cs | 6 ++-- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs index 54736fa3..e8d1f385 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs @@ -19,17 +19,29 @@ bool AllParametersFromServices internal static class MiddlewareExtensions { - extension(MiddlewareClassInfo middlewareClassInfo) + extension(MiddlewareClassInfo) { - internal bool AnyParameters => true; - - internal static ( - MiddlewareClassInfo? classInfo, - List DiagnosticInfos - ) Create(INamedTypeSymbol typeSymbol, GeneratorContext context) + internal static (MiddlewareClassInfo? Info, List Diagnostics) Create( + INamedTypeSymbol typeSymbol, + Location? location, + GeneratorContext context + ) { List diagnostics = []; + // validate that middleware class is a concrete -> not interface or abstract class + if (typeSymbol.TypeKind == TypeKind.Interface || typeSymbol.IsAbstract) + { + diagnostics.Add( + DiagnosticInfo.Create( + Diagnostics.MustBeConcreteType, + location?.ToLocationInfo(), + [typeSymbol.QualifiedName] + ) + ); + return (null, diagnostics); + } + // get the globally qualified name of the class var globallyQualifiedName = typeSymbol.QualifiedNullableName; @@ -37,8 +49,12 @@ List DiagnosticInfos var shortName = typeSymbol.Name; // get constructor - var (constructor, constructorDiagnostics) = GetConstructor(typeSymbol, context); - diagnostics.AddRange(constructorDiagnostics); + var constructor = GetConstructor(typeSymbol, context) + .Map(result => + { + diagnostics.AddRange(result.DiagnosticInfos); + return result.MethodSymbol; + }); // get constructor parameters var parameterInfos = constructor is not null @@ -93,7 +109,7 @@ GeneratorContext context a.AttributeClass is not null && context.WellKnownTypes.IsType( a.AttributeClass, - [WellKnownType.MinimalLambda_Builder_MiddlewareConstructorAttribute] + WellKnownType.MinimalLambda_Builder_MiddlewareConstructorAttribute ) ) ) diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs index 658da916..1a56a00d 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs @@ -38,7 +38,10 @@ is not InvocationExpressionSyntax invocationExpressionSyntax context.SemanticModel.GetInterceptableLocation( invocationExpressionSyntax, context.CancellationToken - ) ?? throw new InvalidOperationException("Interceptable location is null") + ) + ?? throw new InvalidOperationException( + "Interceptable location is null (Should not happen)" + ) ) .ToInterceptableLocationInfo() .Attribute; @@ -48,12 +51,25 @@ is not InvocationExpressionSyntax invocationExpressionSyntax .Map(typeSymbol => typeSymbol as INamedTypeSymbol ?? throw new InvalidOperationException( - "Middleware class type is not INamedTypeSymbol" + "Middleware class type is not INamedTypeSymbol (Should not happen)" ) ); - var (classInfo, diagnostics) = MiddlewareClassInfo.Create(middlewareClassType, context); - diagnosticInfos.AddRange(diagnostics); + var typeArgumentLocation = invocationExpressionSyntax.Expression + is MemberAccessExpressionSyntax + { + Name: GenericNameSyntax { TypeArgumentList.Arguments.Count: > 0 } genericName, + } + ? genericName.TypeArgumentList.Arguments[0].GetLocation() + : null; + + var classInfo = MiddlewareClassInfo + .Create(middlewareClassType, typeArgumentLocation, context) + .Map(result => + { + diagnosticInfos.AddRange(result.Diagnostics); + return result.Info; + }); return new UseMiddlewareTInfo( interceptableLocation, diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index 5588f487..52281d89 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -22,9 +22,9 @@ CancellationToken cancellationToken { var context = new GeneratorContext(syntaxContext, cancellationToken); - return !TryGetInvocationOperation(context, out var targetOperation) - ? null - : UseMiddlewareTInfo.Create(targetOperation, context); + return TryGetInvocationOperation(context, out var targetOperation) + ? UseMiddlewareTInfo.Create(targetOperation, context) + : null; } private static bool TryGetInvocationOperation( From f5d69d8de01a48cf2850166bf1b09f008956e306 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 16:56:42 -0500 Subject: [PATCH 63/67] refactor(source-generators): simplify diagnostics collection in `MiddlewareClassInfo` - Removed unused diagnostics list initialization for efficiency. - Updated validation to return diagnostics directly, reducing intermediate steps. - Refactored `TryCreate` method to streamline logic and improve readability. --- .../Models/Middleware/MiddlewareClassInfo.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs index e8d1f385..e8503fb3 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs @@ -27,20 +27,20 @@ internal static (MiddlewareClassInfo? Info, List Diagnostics) Cr GeneratorContext context ) { - List diagnostics = []; - // validate that middleware class is a concrete -> not interface or abstract class if (typeSymbol.TypeKind == TypeKind.Interface || typeSymbol.IsAbstract) - { - diagnostics.Add( - DiagnosticInfo.Create( - Diagnostics.MustBeConcreteType, - location?.ToLocationInfo(), - [typeSymbol.QualifiedName] - ) + return ( + null, + [ + DiagnosticInfo.Create( + Diagnostics.MustBeConcreteType, + location?.ToLocationInfo(), + [typeSymbol.QualifiedName] + ), + ] ); - return (null, diagnostics); - } + + List diagnostics = []; // get the globally qualified name of the class var globallyQualifiedName = typeSymbol.QualifiedNullableName; From f4c2d89eef014d7bdf9a004722deed45f1798bb7 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 17:02:31 -0500 Subject: [PATCH 64/67] refactor(source-generators): reorganize `Models` folder structure for `Handlers` grouping - Moved files from `LifecycleHandlers`, `InvocationHandlers`, and `Shared` to a unified `Handlers` folder. - Renamed files and namespaces to align with the updated folder structure. - Improved code organization for better maintainability and discoverability. --- .../Models/{Shared => Handlers}/IMethodInfo.cs | 0 .../LifecycleHandlerParameterInfo.cs | 0 .../Models/{LifecycleHandlers => Handlers}/LifecycleMethodInfo.cs | 0 .../{InvocationHandlers => Handlers}/MapHandlerMethodInfo.cs | 0 .../{InvocationHandlers => Handlers}/MapHandlerParameterInfo.cs | 0 .../Models/{Shared => Handlers}/MethodType.cs | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename src/MinimalLambda.SourceGenerators/Models/{Shared => Handlers}/IMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{LifecycleHandlers => Handlers}/LifecycleHandlerParameterInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{LifecycleHandlers => Handlers}/LifecycleMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{InvocationHandlers => Handlers}/MapHandlerMethodInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{InvocationHandlers => Handlers}/MapHandlerParameterInfo.cs (100%) rename src/MinimalLambda.SourceGenerators/Models/{Shared => Handlers}/MethodType.cs (100%) diff --git a/src/MinimalLambda.SourceGenerators/Models/Shared/IMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/IMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/Shared/IMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/IMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleHandlerParameterInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleHandlerParameterInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleHandlerParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/LifecycleHandlers/LifecycleMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerMethodInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerMethodInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerMethodInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerParameterInfo.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/InvocationHandlers/MapHandlerParameterInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerParameterInfo.cs diff --git a/src/MinimalLambda.SourceGenerators/Models/Shared/MethodType.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MethodType.cs similarity index 100% rename from src/MinimalLambda.SourceGenerators/Models/Shared/MethodType.cs rename to src/MinimalLambda.SourceGenerators/Models/Handlers/MethodType.cs From 70d5e1020b37908fb9cf60311094808291ae7a77 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 18:14:32 -0500 Subject: [PATCH 65/67] refactor(source-generators): update namespace structure to improve clarity and consistency - Changed namespaces in `Emitters` and `Models` to align with folder structure. - Updated imports to reflect the new organization. - Improved project maintainability by enforcing a consistent namespace hierarchy. --- .../Diagnostics/DiagnosticResult.cs | 3 ++- .../Emitters/MinimalLambdaEmitter.cs | 2 +- src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs | 2 +- .../Extensions/EnumerableExtensions.cs | 1 + src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs index c113ffe2..fe071c68 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs @@ -1,9 +1,10 @@ using System; using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Models; // ReSharper disable MemberCanBePrivate.Global -namespace MinimalLambda.SourceGenerators.Models; +namespace MinimalLambda.SourceGenerators; internal sealed class DiagnosticResult { diff --git a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index 76780a20..a643b72d 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Models; -namespace MinimalLambda.SourceGenerators; +namespace MinimalLambda.SourceGenerators.Emitters; internal static class MinimalLambdaEmitter { diff --git a/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs index f4417a38..f45c3403 100644 --- a/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs @@ -5,7 +5,7 @@ using System.Reflection; using Scriban; -namespace MinimalLambda.SourceGenerators; +namespace MinimalLambda.SourceGenerators.Emitters; /// /// Helper class for loading, caching, and rendering Scriban templates from embedded diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs index dbd2d98c..6677974b 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using MinimalLambda.SourceGenerators; using MinimalLambda.SourceGenerators.Models; namespace System.Collections.Generic; diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs index 40e191b2..69eda7fd 100644 --- a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -2,6 +2,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using MinimalLambda.SourceGenerators.Emitters; using MinimalLambda.SourceGenerators.Models; namespace MinimalLambda.SourceGenerators; From 4c1167942fdb1f4fe6c51c8724b5181b2efbbc32 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 19:23:15 -0500 Subject: [PATCH 66/67] refactor(source-generators): simplify attribute handling and refactor parameter symbol extensions - Made `IsFromEvent` and `IsFromKeyedService` methods more concise using lambda expressions. - Refactored keyed service detection logic into `IsFromKeyedService` for clearer separation of concerns. - Simplified `GetDiParameterAssignment` to streamline type and key handling in DI contexts. - Cleaned up redundant usages of `DiagnosticResult` in extracted keyed service attributes. - Adjusted settings in `.DotSettings` file to improve consistency in code formatting rules. --- MinimalLambda.sln.DotSettings | 16 +- .../Extensions/ParameterSymbolExtensions.cs | 140 +++++++----------- 2 files changed, 60 insertions(+), 96 deletions(-) diff --git a/MinimalLambda.sln.DotSettings b/MinimalLambda.sln.DotSettings index 17c2743e..7fa61dc4 100644 --- a/MinimalLambda.sln.DotSettings +++ b/MinimalLambda.sln.DotSettings @@ -1,6 +1,6 @@  DO_NOT_SHOW - <?xml version="1.0" encoding="utf-16"?><Profile name="Full Custom Cleanup"><CppReformatCode>True</CppReformatCode><FSharpReformatCode>True</FSharpReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSharpReformatComments>True</CSharpReformatComments><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortDefinitions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSReorderTypeMembers>True</CSReorderTypeMembers><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppRedundantDereferences>True</CppRedundantDereferences><CppDeleteRedundantAccessSpecifier>True</CppDeleteRedundantAccessSpecifier><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><VBMakeFieldReadonly>True</VBMakeFieldReadonly><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + <?xml version="1.0" encoding="utf-16"?><Profile name="Full Custom Cleanup"><CppReformatCode>True</CppReformatCode><FSharpReformatCode>True</FSharpReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSharpReformatComments>True</CSharpReformatComments><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortDefinitions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppRedundantDereferences>True</CppRedundantDereferences><CppDeleteRedundantAccessSpecifier>True</CppDeleteRedundantAccessSpecifier><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><VBMakeFieldReadonly>True</VBMakeFieldReadonly><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Full Custom Cleanup" /&gt; &lt;inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt; &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" /&gt; @@ -9,8 +9,8 @@ &lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt; &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;/Language&gt; &lt;Language id="EditorConfig"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -19,9 +19,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="HTML"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; &lt;/Language&gt; &lt;Language id="HTTP Request"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -39,9 +39,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="JavaScript"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; &lt;/Language&gt; &lt;Language id="Markdown"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; @@ -71,9 +71,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="XML"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; &lt;/Language&gt; &lt;Language id="liquid"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs index 46f2a1d7..17eb5cef 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -23,59 +23,17 @@ a.AttributeClass is not null extension(IParameterSymbol parameterSymbol) { - internal bool IsFromEvent(GeneratorContext context) - { - var eventAttr = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_Builder_EventAttribute - ); - var fromEventAttr = context.WellKnownTypes.Get( - WellKnownType.MinimalLambda_Builder_FromEventAttribute - ); - - return parameterSymbol + internal bool IsFromEvent(GeneratorContext context) => + parameterSymbol .GetAttributes() .Any(attribute => - { - // check event - if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, eventAttr)) - return true; - - // check from event - return SymbolEqualityComparer.Default.Equals( + attribute.AttributeClass is not null + && context.WellKnownTypes.IsType( attribute.AttributeClass, - fromEventAttr - ); - }); - } - - internal bool IsFromKeyedService( - GeneratorContext context, - out DiagnosticResult? keyResult - ) - { - keyResult = null; - - var fromKeyedServicesAttr = context.WellKnownTypes.Get( - WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute - ); - - foreach (var attribute in parameterSymbol.GetAttributes()) - { - if (attribute is null) - continue; - - var attrClass = attribute.AttributeClass; - - // check keyed service - if (!SymbolEqualityComparer.Default.Equals(attrClass, fromKeyedServicesAttr)) - continue; - - keyResult = attribute.ExtractKeyedServiceKey(); - return true; - } - - return false; - } + WellKnownType.MinimalLambda_Builder_EventAttribute, + WellKnownType.MinimalLambda_Builder_FromEventAttribute + ) + ); internal DiagnosticResult<(string Assignment, string? Key)> GetDiParameterAssignment( GeneratorContext context @@ -83,36 +41,44 @@ GeneratorContext context { var paramType = parameterSymbol.Type.QualifiedNullableName; - var isKeyedServices = parameterSymbol.IsFromKeyedService(context, out var keyResult); - var isRequired = parameterSymbol.IsOptional || parameterSymbol.NullableAnnotation == NullableAnnotation.Annotated; - // keyed services - if (isKeyedServices) - return keyResult!.Bind(key => - DiagnosticResult<(string, string?)>.Success( - ( + return parameterSymbol + .IsFromKeyedService(context) + .Bind<(string, string?)>(result => + result.IsKeyed + ? ( isRequired - ? $"context.ServiceProvider.GetKeyedService<{paramType}>({key})" - : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({key})", - key + ? $"context.ServiceProvider.GetKeyedService<{paramType}>({result.Key})" + : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({result.Key})", + result.Key + ) + : ( + isRequired + ? $"context.ServiceProvider.GetService<{paramType}>()" + : $"context.ServiceProvider.GetRequiredService<{paramType}>()", + null ) - ) ); + } - return DiagnosticResult<(string, string?)>.Success( - ( - isRequired - // default - inject from DI - optional - ? $"context.ServiceProvider.GetService<{paramType}>()" - // default - inject required from DI - : $"context.ServiceProvider.GetRequiredService<{paramType}>()", - null + private DiagnosticResult<(bool IsKeyed, string? Key)> IsFromKeyedService( + GeneratorContext context + ) => + parameterSymbol + .GetAttributes() + .FirstOrDefault(attribute => + attribute is { AttributeClass: not null } + && context.WellKnownTypes.IsType( + attribute.AttributeClass, + WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute + ) ) - ); - } + ?.ExtractKeyedServiceKey() + .Bind<(bool, string?)>(key => (true, key)) + ?? (false, null); } extension(AttributeData attributeData) @@ -122,7 +88,7 @@ private DiagnosticResult ExtractKeyedServiceKey() var argument = attributeData.ConstructorArguments[0]; if (argument.IsNull) - return DiagnosticResult.Success("null"); + return "null"; object? value = null; try @@ -141,27 +107,25 @@ private DiagnosticResult ExtractKeyedServiceKey() argument.Type?.QualifiedNullableName ); - return DiagnosticResult.Success( - argument.Kind switch - { - TypedConstantKind.Primitive when value is string strValue => - CSharp.SymbolDisplay.FormatLiteral(strValue, true), + return argument.Kind switch + { + TypedConstantKind.Primitive when value is string strValue => + CSharp.SymbolDisplay.FormatLiteral(strValue, true), - TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", + TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", - TypedConstantKind.Primitive when value is bool boolValue => boolValue - ? "true" - : "false", + TypedConstantKind.Primitive when value is bool boolValue => boolValue + ? "true" + : "false", - TypedConstantKind.Primitive or TypedConstantKind.Enum => - $"({argument.Type?.QualifiedNullableName}){value}", + TypedConstantKind.Primitive or TypedConstantKind.Enum => + $"({argument.Type?.QualifiedNullableName}){value}", - TypedConstantKind.Type when value is ITypeSymbol typeValue => - $"typeof({typeValue.QualifiedNullableName})", + TypedConstantKind.Type when value is ITypeSymbol typeValue => + $"typeof({typeValue.QualifiedNullableName})", - _ => value.ToString(), - } - ); + _ => value.ToString(), + }; } private LocationInfo? GetAttributeArgumentLocation(int index) => From ff3395787ed367cd9b06de6d41967077de3a8fb7 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Wed, 7 Jan 2026 19:39:45 -0500 Subject: [PATCH 67/67] refactor(source-generators): simplify location handling in `UseMiddlewareTInfo` - Replaced redundant `TryGetLocationInfo` calls with a concise implementation for type argument location. - Removed unused `locationInfo` variable and associated logic to streamline code. - Refactored `TryGetLocationInfo` method to directly return `Location`. - Improved readability by consolidating type argument diagnostics logic. --- .../Models/Middleware/UseMiddlewareTInfo.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs index 1a56a00d..6fd7ee1c 100644 --- a/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs @@ -32,8 +32,6 @@ is not InvocationExpressionSyntax invocationExpressionSyntax List diagnosticInfos = []; - TryGetLocationInfo(invocationExpressionSyntax, out var locationInfo); - var interceptableLocation = ( context.SemanticModel.GetInterceptableLocation( invocationExpressionSyntax, @@ -55,13 +53,7 @@ typeSymbol as INamedTypeSymbol ) ); - var typeArgumentLocation = invocationExpressionSyntax.Expression - is MemberAccessExpressionSyntax - { - Name: GenericNameSyntax { TypeArgumentList.Arguments.Count: > 0 } genericName, - } - ? genericName.TypeArgumentList.Arguments[0].GetLocation() - : null; + TryGetLocationInfo(invocationExpressionSyntax, out var typeArgumentLocation); var classInfo = MiddlewareClassInfo .Create(middlewareClassType, typeArgumentLocation, context) @@ -81,7 +73,7 @@ is MemberAccessExpressionSyntax private static bool TryGetLocationInfo( InvocationExpressionSyntax invocationExpressionSyntax, - out LocationInfo? locationInfo + out Location? locationInfo ) { locationInfo = null; @@ -91,7 +83,7 @@ invocationExpressionSyntax is ) { var typeArgument = genericName.TypeArgumentList.Arguments[0]; - locationInfo = typeArgument.GetLocation().ToLocationInfo(); + locationInfo = typeArgument.GetLocation(); return true; }