@@ -256,6 +256,7 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
256256 string ? registrationStrategy = null ;
257257 var tags = new HashSet < string > ( ) ;
258258 string ? serviceKey = null ;
259+ bool isOpenGeneric = false ;
259260
260261 var attributeClass = attribute . AttributeClass ;
261262 if ( attributeClass is { IsGenericType : true } && attributeClass . TypeArguments . Length == attributeClass . TypeParameters . Length )
@@ -290,13 +291,20 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
290291 switch ( name )
291292 {
292293 case "ServiceType" :
293- serviceTypes . Add ( value . ToString ( ) ) ;
294+ var serviceTypeSymbol = value as INamedTypeSymbol ;
295+ isOpenGeneric = isOpenGeneric || IsOpenGeneric ( serviceTypeSymbol ) ;
296+
297+ var serviceType = serviceTypeSymbol ? . ToDisplayString ( _fullyQualifiedNullableFormat ) ?? value . ToString ( ) ;
298+ serviceTypes . Add ( serviceType ) ;
294299 break ;
295300 case "ServiceKey" :
296301 serviceKey = parameter . Value . ToCSharpString ( ) ;
297302 break ;
298303 case "ImplementationType" :
299- implementationType = value . ToString ( ) ;
304+ var implementationTypeSymbol = value as INamedTypeSymbol ;
305+ isOpenGeneric = isOpenGeneric || IsOpenGeneric ( implementationTypeSymbol ) ;
306+
307+ implementationType = implementationTypeSymbol ? . ToDisplayString ( _fullyQualifiedNullableFormat ) ?? value . ToString ( ) ;
300308 break ;
301309 case "Factory" :
302310 implementationFactory = value . ToString ( ) ;
@@ -334,7 +342,9 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
334342 // no implementation type set, use class attribute is on
335343 if ( implementationType . IsNullOrWhiteSpace ( ) )
336344 {
337- implementationType = ToNamedTypeWithoutPlaceholders ( classSymbol ) . ToDisplayString ( _fullyQualifiedNullableFormat ) ;
345+ var unboundType = ToUnboundGenericType ( classSymbol ) ;
346+ isOpenGeneric = isOpenGeneric || IsOpenGeneric ( unboundType ) ;
347+ implementationType = unboundType . ToDisplayString ( _fullyQualifiedNullableFormat ) ;
338348 }
339349
340350 // add implemented interfaces
@@ -344,8 +354,13 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
344354 foreach ( var implementedInterface in classSymbol . AllInterfaces )
345355 {
346356 // This interface is typically not injected into services and, more specifically, record types auto-implement it.
347- if ( implementedInterface . ConstructedFrom . ToString ( ) == "System.IEquatable<T>" ) continue ;
348- var interfaceName = ToNamedTypeWithoutPlaceholders ( implementedInterface ) . ToDisplayString ( _fullyQualifiedNullableFormat ) ;
357+ if ( implementedInterface . ConstructedFrom . ToString ( ) == "System.IEquatable<T>" )
358+ continue ;
359+
360+ var unboundInterface = ToUnboundGenericType ( implementedInterface ) ;
361+ isOpenGeneric = isOpenGeneric || IsOpenGeneric ( unboundInterface ) ;
362+
363+ var interfaceName = unboundInterface . ToDisplayString ( _fullyQualifiedNullableFormat ) ;
349364 serviceTypes . Add ( interfaceName ) ;
350365 }
351366 }
@@ -356,31 +371,27 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
356371 serviceTypes . Add ( implementationType ! ) ;
357372
358373 return new ServiceRegistration (
359- serviceLifetime ,
360- implementationType ! ,
361- serviceTypes . ToArray ( ) ,
362- serviceKey ,
363- implementationFactory ,
364- duplicateStrategy ?? KnownTypes . DuplicateStrategySkipShortName ,
365- registrationStrategy ?? KnownTypes . RegistrationStrategySelfWithInterfacesShortName ,
366- tags . ToArray ( ) ) ;
374+ Lifetime : serviceLifetime ,
375+ ImplementationType : implementationType ! ,
376+ ServiceTypes : serviceTypes . ToArray ( ) ,
377+ ServiceKey : serviceKey ,
378+ Factory : implementationFactory ,
379+ Duplicate : duplicateStrategy ?? KnownTypes . DuplicateStrategySkipShortName ,
380+ Registration : registrationStrategy ?? KnownTypes . RegistrationStrategySelfWithInterfacesShortName ,
381+ Tags : tags . ToArray ( ) ,
382+ IsOpenGeneric : isOpenGeneric ) ;
367383 }
368384
369- private static INamedTypeSymbol ToNamedTypeWithoutPlaceholders ( INamedTypeSymbol typeSymbol )
385+ private static INamedTypeSymbol ToUnboundGenericType ( INamedTypeSymbol typeSymbol )
370386 {
371- if ( ! typeSymbol . IsGenericType
372- || typeSymbol . IsUnboundGenericType )
373- {
387+ if ( ! typeSymbol . IsGenericType || typeSymbol . IsUnboundGenericType )
374388 return typeSymbol ;
375- }
376389
377390 foreach ( var typeArgument in typeSymbol . TypeArguments )
378391 {
379392 // If TypeKind is TypeParameter, it's actually the name of a locally declared type-parameter -> placeholder
380393 if ( typeArgument . TypeKind != TypeKind . TypeParameter )
381- {
382394 return typeSymbol ;
383- }
384395 }
385396
386397 return typeSymbol . ConstructUnboundGenericType ( ) ;
@@ -390,23 +401,23 @@ private static bool IsKnownAttribute(AttributeData attribute, out string service
390401 {
391402 if ( IsSingletonAttribute ( attribute ) )
392403 {
393- serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton" ;
404+ serviceLifetime = KnownTypes . ServiceLifetimeSingletonFullName ;
394405 return true ;
395406 }
396407
397408 if ( IsScopedAttribute ( attribute ) )
398409 {
399- serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped" ;
410+ serviceLifetime = KnownTypes . ServiceLifetimeScopedFullName ;
400411 return true ;
401412 }
402413
403414 if ( IsTransientAttribute ( attribute ) )
404415 {
405- serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient" ;
416+ serviceLifetime = KnownTypes . ServiceLifetimeTransientFullName ;
406417 return true ;
407418 }
408419
409- serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient" ;
420+ serviceLifetime = KnownTypes . ServiceLifetimeTransientFullName ;
410421 return false ;
411422 }
412423
@@ -501,6 +512,17 @@ private static bool IsStringCollection(IParameterSymbol parameterSymbol)
501512 } ;
502513 }
503514
515+ private static bool IsOpenGeneric ( INamedTypeSymbol ? typeSymbol )
516+ {
517+ if ( typeSymbol is null )
518+ return false ;
519+
520+ if ( ! typeSymbol . IsGenericType )
521+ return false ;
522+
523+ return typeSymbol . IsUnboundGenericType ;
524+ }
525+
504526 private static string ResolveDuplicateStrategy ( object ? value )
505527 {
506528 return value switch
0 commit comments