Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/JasperFx/Core/IoC/AssemblyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ public void Start()
TypeFinder = TypeRepository.FindTypes(_assemblies, type => _filter.Matches(type));
}

[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Dispatches each IRegistrationConvention.ScanTypes call, which scans discovered types reflectively and constructs ServiceDescriptors.")]
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Open-generic conventions close types via MakeGenericType.")]
public void ApplyRegistrations(IServiceCollection services)
{
foreach (var convention in Conventions) convention.ScanTypes(TypeFinder, services);
Expand Down
5 changes: 5 additions & 0 deletions src/JasperFx/Core/IoC/DefaultConventionScanner.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -15,6 +16,8 @@ public DefaultConventionScanner(ServiceLifetime lifetime = ServiceLifetime.Trans

public OverwriteBehavior Overwrites { get; set; } = OverwriteBehavior.NewType;

[RequiresUnreferencedCode("Convention scans types reflectively and constructs ServiceDescriptors; discovered types and their constructors must survive trimming.")]
[RequiresDynamicCode("Inherits the contract of IRegistrationConvention.ScanTypes.")]
public void ScanTypes(TypeSet types, IServiceCollection services)
{
foreach (var type in types.FindTypes(TypeClassification.Concretes)
Expand Down Expand Up @@ -51,6 +54,8 @@ public bool ShouldAdd(IServiceCollection services, Type serviceType, Type implem
return !hasMatch;
}

[UnconditionalSuppressMessage("Trimming", "IL2070:DynamicallyAccessedMembers",
Justification = "Reads concreteType's interface list to find the matching I{Name} interface. Reachable only from ScanTypes which is annotated [RequiresUnreferencedCode]; trim-conscious callers either avoid convention scanning entirely or accept that registered types' interfaces survive trimming.")]
public virtual Type? FindServiceType(Type concreteType)
{
var interfaceName = "I" + concreteType.Name;
Expand Down
9 changes: 9 additions & 0 deletions src/JasperFx/Core/IoC/FindAllTypesFilter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.Reflection;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -15,6 +16,8 @@ public FindAllTypesFilter(Type serviceType, ServiceLifetime lifetime = ServiceLi
_lifetime = lifetime;
}

[RequiresUnreferencedCode("Convention scans types reflectively for any implementor of _serviceType; discovered types and their constructors must survive trimming.")]
[RequiresDynamicCode("Open-generic _serviceType uses GenericConnectionScanner which closes types via MakeGenericType.")]
void IRegistrationConvention.ScanTypes(TypeSet types, IServiceCollection services)
{
if (_serviceType.IsOpenGeneric())
Expand All @@ -32,11 +35,17 @@ void IRegistrationConvention.ScanTypes(TypeSet types, IServiceCollection service
}
}

[UnconditionalSuppressMessage("Trimming", "IL2070:DynamicallyAccessedMembers",
Justification = "type comes from convention-scan discovery; reachable only from ScanTypes which is annotated [RequiresUnreferencedCode]. The user invariant for convention-based registration is that discovered types' constructors survive trimming.")]
[UnconditionalSuppressMessage("Trimming", "IL2067:DynamicallyAccessedMembers",
Justification = "Same as IL2070 — TypeExtensions.CanBeCreated has a [DAM(PublicConstructors)] constraint that propagates from the (un-annotated) convention-scan discovery surface.")]
private bool Matches(Type type)
{
return type.CanBeCastTo(_serviceType) && type.GetConstructors().Any() && type.CanBeCreated();
}

[UnconditionalSuppressMessage("Trimming", "IL2067:DynamicallyAccessedMembers",
Justification = "type.FindFirstInterfaceThatCloses needs [DAM(Interfaces)]; type comes from convention-scan discovery. See Matches for the matching convention-scan justification.")]
private static Type determineLeastSpecificButValidType(Type pluginType, Type type)
{
if (pluginType.IsGenericTypeDefinition && !type.IsOpenGeneric())
Expand Down
3 changes: 3 additions & 0 deletions src/JasperFx/Core/IoC/FirstInterfaceConvention.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.Reflection;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -13,6 +14,8 @@ public FirstInterfaceConvention(ServiceLifetime lifetime = ServiceLifetime.Trans
_lifetime = lifetime;
}

[RequiresUnreferencedCode("Convention scans concrete types reflectively and registers each against its first non-IDisposable interface; discovered types and their constructors must survive trimming.")]
[RequiresDynamicCode("Inherits the contract of IRegistrationConvention.ScanTypes.")]
public void ScanTypes(TypeSet types, IServiceCollection services)
{
foreach (var type in types.FindTypes(TypeClassification.Concretes).Where(x => x.GetConstructors().Any()))
Expand Down
9 changes: 9 additions & 0 deletions src/JasperFx/Core/IoC/GenericConnectionScanner.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.Reflection;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -23,6 +24,8 @@ public GenericConnectionScanner(Type openType, Func<Type, ServiceLifetime>? life
}
}

[RequiresUnreferencedCode("Convention scans types reflectively, closes open generics via MakeGenericType, and constructs ServiceDescriptors. Open-generic implementations and their public constructors must survive trimming.")]
[RequiresDynamicCode("addConcretionsThatCouldBeClosed calls Type.MakeGenericType.")]
public void ScanTypes(TypeSet types, IServiceCollection services)
{
foreach (var type in types.AllTypes())
Expand Down Expand Up @@ -63,6 +66,12 @@ public override string ToString()
return "Connect all implementations of open generic type " + _openType.FullNameInCode();
}

[UnconditionalSuppressMessage("Trimming", "IL2067:DynamicallyAccessedMembers",
Justification = "ServiceDescriptor ctor with the closed concreteType; reachable only from ScanTypes which is annotated [RequiresUnreferencedCode]. The open-generic concretion's public constructors must survive trimming for convention-based registration to work.")]
[UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers",
Justification = "Closes an open generic via MakeGenericType. The caller is annotated [RequiresDynamicCode] at ScanTypes.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
Justification = "Closes an open generic via MakeGenericType. The caller is annotated [RequiresDynamicCode] at ScanTypes.")]
private void addConcretionsThatCouldBeClosed(Type @interface, IServiceCollection services)
{
_concretions.Where(x => x.IsOpenGeneric())
Expand Down
14 changes: 13 additions & 1 deletion src/JasperFx/Core/IoC/IRegistrationConvention.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -6,10 +7,21 @@ namespace JasperFx.Core.IoC;
#region sample_IRegistrationConvention

/// <summary>
/// Used to create custom type scanning conventions
/// Used to create custom type scanning conventions.
/// </summary>
/// <remarks>
/// Convention-based registration walks types discovered from loaded assemblies
/// and constructs <see cref="ServiceDescriptor"/>s from them. The trimmer cannot
/// reason statically about which types survive, and any <c>MakeGenericType</c>
/// used by an open-generic convention needs runtime code generation. AOT-publishing
/// apps should either avoid convention-based registration entirely (use explicit
/// <c>services.AddSingleton&lt;TService, TImpl&gt;()</c> calls) or substitute a
/// source-generated registration manifest.
/// </remarks>
public interface IRegistrationConvention
{
[RequiresUnreferencedCode("Scans TypeSet for types matching the convention and constructs ServiceDescriptors reflectively. Discovered types and their constructors must survive trimming.")]
[RequiresDynamicCode("Open-generic conventions close types via MakeGenericType, which requires runtime code generation.")]
void ScanTypes(TypeSet types, IServiceCollection services);
}

Expand Down
5 changes: 4 additions & 1 deletion src/JasperFx/Core/IoC/ImplementationMap.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JasperFx.Core.Reflection;
using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.Reflection;
using JasperFx.Core.TypeScanning;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -13,6 +14,8 @@ public ImplementationMap(ServiceLifetime lifetime = ServiceLifetime.Transient)
_lifetime = lifetime;
}

[RequiresUnreferencedCode("Convention scans types reflectively and constructs ServiceDescriptors for any single-implementation interface; discovered types and their constructors must survive trimming.")]
[RequiresDynamicCode("Inherits the contract of IRegistrationConvention.ScanTypes.")]
public void ScanTypes(TypeSet types, IServiceCollection services)
{
var interfaces = types.FindTypes(TypeClassification.Interfaces | TypeClassification.Closed)
Expand Down
7 changes: 6 additions & 1 deletion src/JasperFx/Core/IoC/ScanningExploder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;

namespace JasperFx.Core.IoC;

internal static class ScanningExploder
{
[RequiresUnreferencedCode("Drives AssemblyScanner.ApplyRegistrations, which dispatches IRegistrationConvention.ScanTypes for each registered convention.")]
[RequiresDynamicCode("Open-generic conventions close types via MakeGenericType.")]
internal static (IServiceCollection, AssemblyScanner[]) ExplodeSynchronously(IServiceCollection services)
{
var scanners = new AssemblyScanner[0];
Expand Down Expand Up @@ -42,6 +45,8 @@ internal static (IServiceCollection, AssemblyScanner[]) ExplodeSynchronously(ISe
return (registry, scanners);
}

[RequiresUnreferencedCode("Drives AssemblyScanner.ApplyRegistrations, which dispatches IRegistrationConvention.ScanTypes for each registered convention.")]
[RequiresDynamicCode("Open-generic conventions close types via MakeGenericType.")]
internal static Task<(IServiceCollection, AssemblyScanner[])> Explode(IServiceCollection services)
{
var scanners = Array.Empty<AssemblyScanner>();
Expand Down
7 changes: 5 additions & 2 deletions src/JasperFx/Core/IoC/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;

namespace JasperFx.Core.IoC;

Expand All @@ -18,6 +19,8 @@ public static class ServiceCollectionExtensions
/// Create an isolated type scanning registration policy
/// </summary>
/// <param name="scan"></param>
[RequiresUnreferencedCode("Scans assemblies for types matching the registered IRegistrationConventions and constructs ServiceDescriptors reflectively. AOT-publishing apps should use explicit registrations or a source-generated registration manifest instead of convention scanning.")]
[RequiresDynamicCode("Open-generic conventions close types via MakeGenericType.")]
public static IServiceCollection Scan(this IServiceCollection services, Action<IAssemblyScanner> scan)
{
var finder = new AssemblyScanner(services);
Expand Down Expand Up @@ -73,7 +76,7 @@ public static bool Matches(this ServiceDescriptor descriptor, Type serviceType,
}

public static ServiceDescriptor? AddType(this IServiceCollection services, Type serviceType,
Type implementationType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
ServiceLifetime lifetime = ServiceLifetime.Transient)
{
var hasAlready = services.Any(x => x.Matches(serviceType, implementationType));
Expand Down
14 changes: 11 additions & 3 deletions src/JasperFx/Core/IoC/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.Core.Reflection;

namespace JasperFx.Core.IoC;

internal static class TypeExtensions
{
public static bool CanBeCreated(this Type type)
public static bool CanBeCreated(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type)
{
return type.IsConcrete() && type.GetConstructors().Any();
}


public static Type FindFirstInterfaceThatCloses(this Type implementationType, Type templateType)
public static Type FindFirstInterfaceThatCloses(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type implementationType,
Type templateType)
{
return implementationType.FindInterfacesThatClose(templateType).FirstOrDefault()!;
}

public static IEnumerable<Type> FindInterfacesThatClose(this Type pluggedType, Type templateType)
public static IEnumerable<Type> FindInterfacesThatClose(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type pluggedType,
Type templateType)
{
return rawFindInterfacesThatCloses(pluggedType, templateType).Distinct();
}

[UnconditionalSuppressMessage("Trimming", "IL2070:DynamicallyAccessedMembers",
Justification = "Recurses up BaseType chain to find matching interface closures. The caller chain enters from FindInterfacesThatClose which carries [DAM(Interfaces)] on the first parameter; base types beyond the entry point are walked best-effort and the trim impact is bounded by the entry-point annotation.")]
private static IEnumerable<Type> rawFindInterfacesThatCloses(Type TPluggedType, Type templateType)
{
if (!TPluggedType.IsConcrete())
Expand Down
Loading