diff --git a/src/Injectio.Generators/ServiceRegistrationGenerator.cs b/src/Injectio.Generators/ServiceRegistrationGenerator.cs index 2bbb1cc..f4e3cb2 100644 --- a/src/Injectio.Generators/ServiceRegistrationGenerator.cs +++ b/src/Injectio.Generators/ServiceRegistrationGenerator.cs @@ -334,7 +334,7 @@ private static (EquatableArray diagnostics, bool hasServiceCollectio // no implementation type set, use class attribute is on if (implementationType.IsNullOrWhiteSpace()) { - implementationType = classSymbol.ToDisplayString(_fullyQualifiedNullableFormat); + implementationType = ToNamedTypeWithoutPlaceholders(classSymbol).ToDisplayString(_fullyQualifiedNullableFormat); } // add implemented interfaces @@ -345,7 +345,7 @@ private static (EquatableArray diagnostics, bool hasServiceCollectio { // This interface is typically not injected into services and, more specifically, record types auto-implement it. if(implementedInterface.ConstructedFrom.ToString() == "System.IEquatable") continue; - var interfaceName = implementedInterface.ToDisplayString(_fullyQualifiedNullableFormat); + var interfaceName = ToNamedTypeWithoutPlaceholders(implementedInterface).ToDisplayString(_fullyQualifiedNullableFormat); serviceTypes.Add(interfaceName); } } @@ -366,6 +366,26 @@ private static (EquatableArray diagnostics, bool hasServiceCollectio tags.ToArray()); } + private static INamedTypeSymbol ToNamedTypeWithoutPlaceholders(INamedTypeSymbol typeSymbol) + { + if (!typeSymbol.IsGenericType + || typeSymbol.IsUnboundGenericType) + { + return typeSymbol; + } + + foreach (var typeArgument in typeSymbol.TypeArguments) + { + // If TypeKind is TypeParameter, it's actually the name of a locally declared type-parameter -> placeholder + if (typeArgument.TypeKind != TypeKind.TypeParameter) + { + return typeSymbol; + } + } + + return typeSymbol.ConstructUnboundGenericType(); + } + private static bool IsKnownAttribute(AttributeData attribute, out string serviceLifetime) { if (IsSingletonAttribute(attribute)) diff --git a/tests/Injectio.Tests.Console/Program.cs b/tests/Injectio.Tests.Console/Program.cs index a7503da..c5a508c 100644 --- a/tests/Injectio.Tests.Console/Program.cs +++ b/tests/Injectio.Tests.Console/Program.cs @@ -18,6 +18,7 @@ var module = provider.GetRequiredService(); var generic = provider.GetRequiredService>(); +var generic2 = provider.GetRequiredService>(); var tagService = provider.GetService(); var alpaService = provider.GetKeyedService("Alpha"); diff --git a/tests/Injectio.Tests.Library/Service.cs b/tests/Injectio.Tests.Library/Service.cs index dccbfa0..b2e1f62 100644 --- a/tests/Injectio.Tests.Library/Service.cs +++ b/tests/Injectio.Tests.Library/Service.cs @@ -79,6 +79,15 @@ public class OpenGeneric : IOpenGeneric { } +public interface IOpenGeneric2 +{ +} + +[RegisterSingleton] +public class OpenGeneric2 : IOpenGeneric2 +{ +} + public interface IServiceTag { } diff --git a/tests/Injectio.Tests/ServiceRegistrationGeneratorTests.cs b/tests/Injectio.Tests/ServiceRegistrationGeneratorTests.cs index cf9057c..a9907c8 100644 --- a/tests/Injectio.Tests/ServiceRegistrationGeneratorTests.cs +++ b/tests/Injectio.Tests/ServiceRegistrationGeneratorTests.cs @@ -396,6 +396,58 @@ public class OpenGeneric : IOpenGeneric .ScrubLinesContaining("GeneratedCodeAttribute"); } + [Fact] + public Task GenerateRegisterSingletonSelfAsOpenGeneric() + { + var source = @" +using Injectio.Attributes; + +namespace Injectio.Sample; + +public interface IOpenGeneric +{ } + +[RegisterSingleton] +public class OpenGeneric : IOpenGeneric +{ } +"; + + var (diagnostics, output) = GetGeneratedOutput(source); + + diagnostics.Should().BeEmpty(); + + return Verifier + .Verify(output) + .UseDirectory("Snapshots") + .ScrubLinesContaining("GeneratedCodeAttribute"); + } + + [Fact] + public Task GenerateRegisterSingletonSelfAsClosedGeneric() + { + var source = @" +using Injectio.Attributes; + +namespace Injectio.Sample; + +public interface IClosedGeneric +{ } + +[RegisterSingleton] +public class Service : IClosedGeneric +{ } +"; + + var (diagnostics, output) = GetGeneratedOutput(source); + + diagnostics.Should().BeEmpty(); + + return Verifier + .Verify(output) + .UseDirectory("Snapshots") + .ScrubLinesContaining("GeneratedCodeAttribute"); + } + [Fact] public Task GenerateRegisterSingletonTags() { diff --git a/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsClosedGeneric.verified.txt b/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsClosedGeneric.verified.txt new file mode 100644 index 0000000..a1c7845 --- /dev/null +++ b/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsClosedGeneric.verified.txt @@ -0,0 +1,42 @@ +// +#nullable enable + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for discovered service registrations + /// + public static class DiscoveredServicesExtensions + { + /// + /// Adds discovered services from Test.Generator to the specified service collection + /// + /// The service collection. + /// The service registration tags to include. + /// The service collection + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddTestGenerator(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection serviceCollection, params string[]? tags) + { + var tagSet = new global::System.Collections.Generic.HashSet(tags ?? global::System.Linq.Enumerable.Empty()); + + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + serviceCollection, + global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Describe( + typeof(global::Injectio.Sample.IClosedGeneric), + typeof(global::Injectio.Sample.Service), + global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton + ) + ); + + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + serviceCollection, + global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Describe( + typeof(global::Injectio.Sample.Service), + typeof(global::Injectio.Sample.Service), + global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton + ) + ); + + return serviceCollection; + } + } +} diff --git a/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsOpenGeneric.verified.txt b/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsOpenGeneric.verified.txt new file mode 100644 index 0000000..4936066 --- /dev/null +++ b/tests/Injectio.Tests/Snapshots/ServiceRegistrationGeneratorTests.GenerateRegisterSingletonSelfAsOpenGeneric.verified.txt @@ -0,0 +1,42 @@ +// +#nullable enable + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for discovered service registrations + /// + public static class DiscoveredServicesExtensions + { + /// + /// Adds discovered services from Test.Generator to the specified service collection + /// + /// The service collection. + /// The service registration tags to include. + /// The service collection + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddTestGenerator(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection serviceCollection, params string[]? tags) + { + var tagSet = new global::System.Collections.Generic.HashSet(tags ?? global::System.Linq.Enumerable.Empty()); + + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + serviceCollection, + global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Describe( + typeof(global::Injectio.Sample.IOpenGeneric<>), + typeof(global::Injectio.Sample.OpenGeneric<>), + global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton + ) + ); + + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + serviceCollection, + global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Describe( + typeof(global::Injectio.Sample.OpenGeneric<>), + typeof(global::Injectio.Sample.OpenGeneric<>), + global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton + ) + ); + + return serviceCollection; + } + } +}