Skip to content

Commit ee24545

Browse files
authored
Trimming and Aot annotations (#5271)
1 parent aa32e4f commit ee24545

26 files changed

+215
-100
lines changed

src/Directory.Build.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
<PropertyGroup>
55
<GenerateDocumentationFile>true</GenerateDocumentationFile>
6+
<!-- Compatible from net6.0 onwards (we get warnings otherwise). This implies IsTrimmable as well -->
7+
<IsAotCompatible Condition=" $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0')) ">true</IsAotCompatible>
68
</PropertyGroup>
79

810
<ItemGroup>

src/Npgsql.DependencyInjection/NpgsqlServiceCollectionExtensions.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Data.Common;
3+
using System.Diagnostics.CodeAnalysis;
34
using Microsoft.Extensions.DependencyInjection.Extensions;
45
using Microsoft.Extensions.Logging;
56
using Npgsql;
@@ -29,6 +30,8 @@ public static class NpgsqlServiceCollectionExtensions
2930
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
3031
/// </param>
3132
/// <returns>The same service collection so that multiple calls can be chained.</returns>
33+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
34+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
3235
public static IServiceCollection AddNpgsqlDataSource(
3336
this IServiceCollection serviceCollection,
3437
string connectionString,
@@ -51,6 +54,8 @@ public static IServiceCollection AddNpgsqlDataSource(
5154
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
5255
/// </param>
5356
/// <returns>The same service collection so that multiple calls can be chained.</returns>
57+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
58+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
5459
public static IServiceCollection AddNpgsqlDataSource(
5560
this IServiceCollection serviceCollection,
5661
string connectionString,
@@ -76,6 +81,8 @@ public static IServiceCollection AddNpgsqlDataSource(
7681
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
7782
/// </param>
7883
/// <returns>The same service collection so that multiple calls can be chained.</returns>
84+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
85+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
7986
public static IServiceCollection AddMultiHostNpgsqlDataSource(
8087
this IServiceCollection serviceCollection,
8188
string connectionString,
@@ -100,6 +107,8 @@ public static IServiceCollection AddMultiHostNpgsqlDataSource(
100107
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
101108
/// </param>
102109
/// <returns>The same service collection so that multiple calls can be chained.</returns>
110+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
111+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
103112
public static IServiceCollection AddMultiHostNpgsqlDataSource(
104113
this IServiceCollection serviceCollection,
105114
string connectionString,
@@ -108,6 +117,8 @@ public static IServiceCollection AddMultiHostNpgsqlDataSource(
108117
=> AddNpgsqlMultiHostDataSourceCore(
109118
serviceCollection, connectionString, dataSourceBuilderAction: null, connectionLifetime, dataSourceLifetime);
110119

120+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
121+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
111122
static IServiceCollection AddNpgsqlDataSourceCore(
112123
this IServiceCollection serviceCollection,
113124
string connectionString,
@@ -132,6 +143,8 @@ static IServiceCollection AddNpgsqlDataSourceCore(
132143
return serviceCollection;
133144
}
134145

146+
[RequiresUnreferencedCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums etc. Use NpgsqlSlimDataSourceBuilder to start with a reduced - reflection free - set and opt into what your app specifically requires.")]
147+
[RequiresDynamicCode("NpgsqlDataSource uses reflection to handle various PostgreSQL types like records, unmapped enums. This can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
135148
static IServiceCollection AddNpgsqlMultiHostDataSourceCore(
136149
this IServiceCollection serviceCollection,
137150
string connectionString,
@@ -185,4 +198,4 @@ static void AddCommonServices(
185198
sp => sp.GetRequiredService<NpgsqlConnection>(),
186199
connectionLifetime));
187200
}
188-
}
201+
}

src/Npgsql.Json.NET/NpgsqlJsonNetExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using Npgsql.TypeMapping;
34
using NpgsqlTypes;
45
using Newtonsoft.Json;
@@ -23,6 +24,8 @@ public static class NpgsqlJsonNetExtensions
2324
/// <param name="jsonClrTypes">
2425
/// A list of CLR types to map to PostgreSQL <c>json</c> (no need to specify <see cref="NpgsqlDbType.Json" />).
2526
/// </param>
27+
[RequiresUnreferencedCode("Json serializer may perform reflection on trimmed types.")]
28+
[RequiresDynamicCode("Serializing arbitary types to json can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
2629
public static INpgsqlTypeMapper UseJsonNet(
2730
this INpgsqlTypeMapper mapper,
2831
JsonSerializerSettings? settings = null,

src/Npgsql/Internal/Composites/Metadata/CompositeInfo.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics;
43
using Npgsql.Util;
54

65
namespace Npgsql.Internal.Composites;
@@ -10,7 +9,7 @@ sealed class CompositeInfo<T>
109
readonly int _lastConstructorFieldIndex;
1110
readonly CompositeFieldInfo[] _fields;
1211

13-
public CompositeInfo(CompositeFieldInfo[] fields, int? constructorParameters, Func<StrongBox[], T>? constructor)
12+
public CompositeInfo(CompositeFieldInfo[] fields, int constructorParameters, Func<StrongBox[], T> constructor)
1413
{
1514
_lastConstructorFieldIndex = -1;
1615
for (var i = fields.Length - 1; i >= 0; i--)
@@ -21,7 +20,7 @@ public CompositeInfo(CompositeFieldInfo[] fields, int? constructorParameters, Fu
2120
}
2221

2322
var parameterSum = 0;
24-
for(var i = constructorParameters - 1 ?? 0; i > 0; i--)
23+
for (var i = constructorParameters - 1; i > 0; i--)
2524
parameterSum += i;
2625

2726
var argumentsSum = 0;
@@ -36,20 +35,14 @@ public CompositeInfo(CompositeFieldInfo[] fields, int? constructorParameters, Fu
3635
throw new InvalidOperationException($"Missing composite fields to map to the required {constructorParameters} constructor parameters.");
3736

3837
_fields = fields;
39-
if (constructor is null)
40-
Constructor = _ => Activator.CreateInstance<T>();
41-
else
38+
var arguments = constructorParameters is 0 ? Array.Empty<CompositeFieldInfo>() : new CompositeFieldInfo[constructorParameters];
39+
foreach (var field in fields)
4240
{
43-
var arguments = new CompositeFieldInfo[constructorParameters.GetValueOrDefault()];
44-
foreach (var field in fields)
45-
{
46-
if (field.ConstructorParameterIndex is { } index)
47-
arguments[index] = field;
48-
}
49-
Constructor = constructor;
41+
if (field.ConstructorParameterIndex is { } index)
42+
arguments[index] = field;
5043
}
51-
52-
ConstructorParameters = constructorParameters ?? 0;
44+
Constructor = constructor;
45+
ConstructorParameters = constructorParameters;
5346
}
5447

5548
public IReadOnlyList<CompositeFieldInfo> Fields => _fields;

src/Npgsql/Internal/Composites/ReflectionCompositeInfoFactory.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Linq;
56
using System.Linq.Expressions;
67
using System.Reflection;
@@ -10,9 +11,11 @@
1011

1112
namespace Npgsql.Internal.Composites;
1213

14+
[RequiresDynamicCode("Serializing arbitary types can require creating new generic types or methods. This may not work when AOT compiling.")]
1315
static class ReflectionCompositeInfoFactory
1416
{
15-
public static CompositeInfo<T> CreateCompositeInfo<T>(PostgresCompositeType pgType, INpgsqlNameTranslator nameTranslator, PgSerializerOptions options)
17+
public static CompositeInfo<T> CreateCompositeInfo<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] T>(
18+
PostgresCompositeType pgType, INpgsqlNameTranslator nameTranslator, PgSerializerOptions options)
1619
{
1720
var pgFields = pgType.Fields;
1821
var propertyMap = MapProperties<T>(pgFields, nameTranslator);
@@ -86,8 +89,8 @@ public static CompositeInfo<T> CreateCompositeInfo<T>(PostgresCompositeType pgTy
8689

8790
Debug.Assert(compositeFields.All(x => x is not null));
8891

89-
var constructor = constructorInfo is null ? null : CreateStrongBoxConstructor<T>(constructorInfo);
90-
return new CompositeInfo<T>(compositeFields!, constructorInfo is null ? null : constructorParameters.Length, constructor);
92+
var constructor = constructorInfo is null ? _ => Activator.CreateInstance<T>() : CreateStrongBoxConstructor<T>(constructorInfo);
93+
return new CompositeInfo<T>(compositeFields!, constructorInfo is null ? 0 : constructorParameters.Length, constructor);
9194

9295
// We have to map the pg type back to the composite field type, as we've resolved based on the representational pg type.
9396
PgConverterResolution MapResolution(PostgresCompositeType.Field field, PgConverterResolution resolution)
@@ -151,6 +154,11 @@ static Delegate CreateSetter<T>(PropertyInfo info)
151154
static Expression UnboxAny(Expression expression, Type type)
152155
=> type.IsValueType ? Expression.Unbox(expression, type) : Expression.Convert(expression, type, null);
153156

157+
#if !NETSTANDARD
158+
[DynamicDependency("TypedValue", typeof(StrongBox<>))]
159+
[DynamicDependency("Length", typeof(StrongBox[]))]
160+
#endif
161+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "DynamicDependencies in place for the System.Linq.Expression.Property calls")]
154162
static Func<StrongBox[], T> CreateStrongBoxConstructor<T>(ConstructorInfo constructorInfo)
155163
{
156164
var values = Expression.Parameter(typeof(StrongBox[]), "values");
@@ -187,7 +195,7 @@ static CompositeFieldInfo CreateCompositeFieldInfo(string name, Type type, PgCon
187195
=> (CompositeFieldInfo)Activator.CreateInstance(
188196
typeof(CompositeFieldInfo<>).MakeGenericType(type), name, converterResolution, getter, setter)!;
189197

190-
static Dictionary<int, PropertyInfo> MapProperties<T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
198+
static Dictionary<int, PropertyInfo> MapProperties<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
191199
{
192200
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
193201
var propertiesAndNames = properties.Select(x =>
@@ -215,7 +223,7 @@ static Dictionary<int, PropertyInfo> MapProperties<T>(IReadOnlyList<PostgresComp
215223
return result;
216224
}
217225

218-
static Dictionary<int, FieldInfo> MapFields<T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
226+
static Dictionary<int, FieldInfo> MapFields<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
219227
{
220228
var clrFields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
221229
var clrFieldsAndNames = clrFields.Select(x =>
@@ -243,7 +251,7 @@ static Dictionary<int, FieldInfo> MapFields<T>(IReadOnlyList<PostgresCompositeTy
243251
return result;
244252
}
245253

246-
static (ConstructorInfo? ConstructorInfo, int[] ParameterFieldMap) MapBestMatchingConstructor<T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
254+
static (ConstructorInfo? ConstructorInfo, int[] ParameterFieldMap) MapBestMatchingConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(IReadOnlyList<PostgresCompositeType.Field> fields, INpgsqlNameTranslator nameTranslator)
247255
{
248256
ConstructorInfo? clrDefaultConstructor = null;
249257
foreach (var constructor in typeof(T).GetConstructors().OrderByDescending(x => x.GetParameters().Length))

src/Npgsql/Internal/Converters/CastingConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using Npgsql.Internal.Postgres;
@@ -65,6 +66,7 @@ protected override PgConverter<T> CreateConverter(PgConverterResolution effectiv
6566

6667
static class CastingTypeInfoExtensions
6768
{
69+
[RequiresDynamicCode("Changing boxing converters to their non-boxing counterpart can require creating new generic types or methods, which requires creating code at runtime. This may not be AOT when AOT compiling")]
6870
internal static PgTypeInfo ToNonBoxing(this PgTypeInfo typeInfo)
6971
{
7072
if (!typeInfo.IsBoxing)

src/Npgsql/Internal/Converters/SystemTextJsonConverter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ sealed class SystemTextJsonConverter<T, TBase> : PgStreamingConverter<T?> where
2020

2121
public SystemTextJsonConverter(bool jsonb, Encoding textEncoding, JsonSerializerOptions serializerOptions)
2222
{
23-
// We do GetTypeInfo calls directly so we need a resolver.
2423
if (serializerOptions.TypeInfoResolver is null)
25-
serializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
24+
throw new InvalidOperationException("System.Text.Json serialization requires a type info resolver, make sure to set-it up beforehand.");
2625

2726
_jsonb = jsonb;
2827
_textEncoding = textEncoding;

src/Npgsql/Internal/DynamicTypeInfoResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
namespace Npgsql.Internal;
88

9-
[RequiresUnreferencedCode("A dynamic type info resolver may perform reflection on types that were trimmed if not referenced directly.")]
109
[RequiresDynamicCode("A dynamic type info resolver may need to construct a generic converter for a statically unknown type.")]
1110
public abstract class DynamicTypeInfoResolver : IPgTypeInfoResolver
1211
{
@@ -43,6 +42,7 @@ protected static bool IsArrayDataTypeName(DataTypeName dataTypeName, PgSerialize
4342

4443
protected abstract DynamicMappingCollection? GetMappings(Type? type, DataTypeName dataTypeName, PgSerializerOptions options);
4544

45+
[RequiresDynamicCode("A dynamic type info resolver may need to construct a generic converter for a statically unknown type.")]
4646
protected class DynamicMappingCollection
4747
{
4848
TypeInfoMappingCollection? _mappings;

src/Npgsql/Internal/Resolvers/NetworkTypeInfoResolver.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,26 @@ static void AddInfos(TypeInfoMappingCollection mappings)
3939
// We do so by wrapping our converter in a casting converter constructed over the derived type.
4040
// Finally we add a custom predicate to be able to match any type which values are assignable to IPAddress.
4141
mappings.AddType<IPAddress>(DataTypeNames.Inet,
42-
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because the target will only ever be a reference type.")]
43-
static (options, resolvedMapping, _) =>
44-
{
45-
var derivedType = resolvedMapping.Type != typeof(IPAddress);
46-
PgConverter converter = new IPAddressConverter();
47-
if (derivedType)
48-
// There is not much more we can do, the deriving type IPAddress+ReadOnlyIPAddress isn't public.
49-
converter = (PgConverter)Activator.CreateInstance(typeof(CastingConverter<>).MakeGenericType(resolvedMapping.Type), converter)!;
50-
51-
return resolvedMapping.CreateInfo(options, converter);
52-
}, mapping => mapping with { MatchRequirement = MatchRequirement.Single, TypeMatchPredicate = type => type is null || typeof(IPAddress).IsAssignableFrom(type) });
42+
CreateInfo, mapping => mapping with { MatchRequirement = MatchRequirement.Single, TypeMatchPredicate = type => type is null || typeof(IPAddress).IsAssignableFrom(type) });
5343
mappings.AddStructType<NpgsqlInet>(DataTypeNames.Inet,
5444
static (options, mapping, _) => mapping.CreateInfo(options, new NpgsqlInetConverter()));
5545

5646
// cidr
5747
mappings.AddStructType<NpgsqlCidr>(DataTypeNames.Cidr,
5848
static (options, mapping, _) => mapping.CreateInfo(options, new NpgsqlCidrConverter()), isDefault: true);
49+
50+
// Code is split out to a local method as suppression attributes on lambdas aren't properly handled by the ILLink analyzer yet.
51+
[UnconditionalSuppressMessage("AotAnalysis", "IL3050", Justification = "MakeGenericType is safe because the target will only ever be a reference type.")]
52+
static PgTypeInfo CreateInfo(PgSerializerOptions options, TypeInfoMapping resolvedMapping, bool _)
53+
{
54+
var derivedType = resolvedMapping.Type != typeof(IPAddress);
55+
PgConverter converter = new IPAddressConverter();
56+
if (derivedType)
57+
// There is not much more we can do, the deriving type IPAddress+ReadOnlyIPAddress isn't public.
58+
converter = (PgConverter)Activator.CreateInstance(typeof(CastingConverter<>).MakeGenericType(resolvedMapping.Type), converter)!;
59+
60+
return resolvedMapping.CreateInfo(options, converter);
61+
}
5962
}
6063

6164
static void AddArrayInfos(TypeInfoMappingCollection mappings)

0 commit comments

Comments
 (0)