Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Trimming] Enable trimming and AOT analyzers in Core #21076

Merged
merged 9 commits into from
Mar 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ static DependencyProperty GetForegroundProperty(FrameworkElement element)
DependencyProperty foregroundProperty;
if (!ForegroundProperties.Value.TryGetValue(type, out foregroundProperty))
{
FieldInfo field = ReflectionExtensions.GetFields(type).FirstOrDefault(f => f.Name == "ForegroundProperty");
#pragma warning disable IL2075 // The Compatibility assembly is not trimmable
FieldInfo field = type.GetField("ForegroundProperty", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
#pragma warning restore IL2075
if (field == null)
throw new ArgumentException("type is not a Foregroundable type");

Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/BindablePropertyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ static Type GetControlType(string typeName)
BindableProperty ConvertFrom(Type type, string propertyName, IXmlLineInfo lineinfo)
{
string name = propertyName + "Property";
FieldInfo bpinfo = type.GetField(fi => fi.Name == name && fi.IsStatic && fi.IsPublic && fi.FieldType == typeof(BindableProperty));
if (bpinfo == null)
FieldInfo bpinfo = type.GetField(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (bpinfo == null || bpinfo.FieldType != typeof(BindableProperty))
throw new XamlParseException($"Can't resolve {name} on {type.Name}", lineinfo);
var bp = bpinfo.GetValue(null) as BindableProperty;
var isObsolete = bpinfo.GetCustomAttribute<ObsoleteAttribute>() != null;
Expand Down
8 changes: 8 additions & 0 deletions src/Core/src/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<NoWarn>$(NoWarn);CS1591;RS0041;RS0026;RS0027</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(TargetFramework)' != 'netstandard2.1'">
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
<!-- TODO: Remove once XamlTypeInfo.g.cs generates trimming-friendly code -->
<NoWarn Condition="$(TargetFramework.Contains('-windows'))">$(NoWarn);IL2059</NoWarn>
</PropertyGroup>

<PropertyGroup>
<!-- NuGet package information -->
<IsPackable>true</IsPackable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Maui
{
Expand All @@ -12,15 +13,20 @@ public interface IImageSourceServiceProvider : IServiceProvider

#if !NETSTANDARD
[Obsolete("Use GetImageSourceService instead.")]
[RequiresDynamicCode("The GetImageSourceServiceType method is not AOT compatible. Use GetImageSourceService instead.")]
[RequiresUnreferencedCode("The GetImageSourceServiceType method is not trimming compatible. Use GetImageSourceService instead.")]
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for future reference:
We've been burned in other projects by adding RUC (and other annotations) onto interface members. The problem is that doing so effectively marks all implementations of the interface with RUC. But RUC is a statement about the implementation, not about the interface.

For example Type.GetMembers - this has some annotation on it, but it's an abstract class. The reflection based implementation needs that annotation, but we also have MetadataLoadContext which also implements Type and that one doesn't need the annotation. But there's no way to tell that to the system and it will produce warnings and so on. Not that we will change Type.GetMembers, but it shows how the statement applies to all implementations, regardless if they are affected or not. (and also, removing the annotation from the interface member is a breaking change!)

I guess in this specific case, we basically don't care, since the methods are obsolete anyway. But it's a good rule to remember for the future: "Think hard if the annotation should be on the interface, really hard!"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggled with this one. It feels wrong adding those attributes to the interface. I just couldn't figure out how else to apply the annotation in this case. I arrived at the same conclusion that we don't care too much since we already marked it obsolte and we don't know about any customer who would have their custom implementation of IImageSourceProvider at the moment.

Type GetImageSourceServiceType(Type imageSource) => throw new NotImplementedException();

[Obsolete("Use GetImageSourceService instead.")]
[RequiresUnreferencedCode("The GetImageSourceType method is not trimming compatible. Use GetImageSourceService instead.")]
Type GetImageSourceType(Type imageSource) => throw new NotImplementedException();
#else
[Obsolete("Use GetImageSourceService instead.")]
[RequiresUnreferencedCode("The GetImageSourceType method is not trimming compatible. Use GetImageSourceService instead.")]
Type GetImageSourceServiceType(Type imageSource);

[Obsolete("Use GetImageSourceService instead.")]
[RequiresUnreferencedCode("The GetImageSourceType method is not trimming compatible. Use GetImageSourceService instead.")]
Type GetImageSourceType(Type imageSource);
#endif
}
Expand Down
54 changes: 36 additions & 18 deletions src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Hosting.Internal;

namespace Microsoft.Maui.Hosting
Expand Down Expand Up @@ -31,37 +32,54 @@ public ImageSourceServiceProvider(IImageSourceServiceCollection collection, ISer
return (IImageSourceService?)GetService(imageSourceService);
}

public Type GetImageSourceServiceType(Type imageSource) =>
_serviceCache.GetOrAdd(imageSource, type =>
#if !NETSTANDARD
[RequiresDynamicCode("The GetImageSourceServiceType method is not AOT compatible. Use GetImageSourceService instead.")]
#endif
[RequiresUnreferencedCode("The GetImageSourceServiceType method is not trimming compatible. Use GetImageSourceService instead.")]
public Type GetImageSourceServiceType(Type imageSource)
{
return _serviceCache.GetOrAdd(imageSource, CreateImageSourceServiceTypeCacheEntry);

Type CreateImageSourceServiceTypeCacheEntry(Type type)
{
var genericConcreteType = ImageSourceServiceType.MakeGenericType(type);

if (genericConcreteType != null && InternalCollection.TryGetService(genericConcreteType, out _))
{
return genericConcreteType;
}

return ImageSourceServiceType.MakeGenericType(GetImageSourceType(type));
});

public Type GetImageSourceType(Type imageSource) =>
_imageSourceCache.GetOrAdd(imageSource, CreateImageSourceTypeCacheEntry);
}
}

Type CreateImageSourceTypeCacheEntry(Type type)
[RequiresUnreferencedCode("The GetImageSourceType method is not trimming compatible. Use GetImageSourceService instead.")]
public Type GetImageSourceType(Type imageSource)
{
if (type.IsInterface)
{
if (type.GetInterface(ImageSourceInterface) != null)
return type;
}
else
return _imageSourceCache.GetOrAdd(imageSource, CreateImageSourceTypeCacheEntry);

Type CreateImageSourceTypeCacheEntry(Type type)
{
foreach (var directInterface in type.GetInterfaces())
if (type.IsInterface)
{
if (directInterface.GetInterface(ImageSourceInterface) != null)
return directInterface;
if (type.GetInterface(ImageSourceInterface) != null)
{
return type;
}
}
else
{
foreach (var directInterface in type.GetInterfaces())
{
if (directInterface.GetInterface(ImageSourceInterface) != null)
{
return directInterface;
}
}
}
}

throw new InvalidOperationException($"Unable to find the image source type because none of the interfaces on {type.Name} were derived from {nameof(IImageSource)}.");
throw new InvalidOperationException($"Unable to find the image source type because none of the interfaces on {type.Name} were derived from {nameof(IImageSource)}.");
}
}
}
}
29 changes: 17 additions & 12 deletions src/Core/src/HotReload/HotReloadExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Microsoft.Maui.Hosting;
Expand Down Expand Up @@ -34,20 +35,24 @@ public static void CheckHandlers(this IView? view)
}
}

public static List<MethodInfo> GetOnHotReloadMethods(this Type type) => getOnHotReloadMethods(type).Distinct(new ReflectionMethodComparer()).ToList();

static IEnumerable<MethodInfo> getOnHotReloadMethods(Type type, bool isSubclass = false)
[RequiresUnreferencedCode("Hot Reload is not trim compatible")]
public static List<MethodInfo> GetOnHotReloadMethods(this Type type)
{
var flags = BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic;
if (isSubclass)
flags = BindingFlags.Static | BindingFlags.NonPublic;
var foos = type.GetMethods(flags).Where(x => x.GetCustomAttributes(typeof(OnHotReloadAttribute), true).Length > 0).ToList();
foreach (var foo in foos)
yield return foo;

if (type.BaseType != null)
foreach (var foo in getOnHotReloadMethods(type.BaseType, true))
return getOnHotReloadMethods(type).Distinct(new ReflectionMethodComparer()).ToList();

static IEnumerable<MethodInfo> getOnHotReloadMethods(Type type, bool isSubclass = false)
{
var flags = BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic;
if (isSubclass)
flags = BindingFlags.Static | BindingFlags.NonPublic;
var foos = type.GetMethods(flags).Where(x => x.GetCustomAttributes(typeof(OnHotReloadAttribute), true).Length > 0).ToList();
foreach (var foo in foos)
yield return foo;

if (type.BaseType != null)
foreach (var foo in getOnHotReloadMethods(type.BaseType, true))
yield return foo;
}
}

class ReflectionMethodComparer : IEqualityComparer<MethodInfo>
Expand Down
30 changes: 19 additions & 11 deletions src/Core/src/HotReload/HotReloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -97,6 +98,11 @@ static void TransferState(IHotReloadableView oldView, IView newView)
static Dictionary<string, Type> replacedViews = new(StringComparer.Ordinal);
static Dictionary<IHotReloadableView, object[]> currentViews = new Dictionary<IHotReloadableView, object[]>();
static Dictionary<string, List<KeyValuePair<Type, Type>>> replacedHandlers = new(StringComparer.Ordinal);

[RequiresUnreferencedCode("Hot Reload is not trim compatible")]
#if !NETSTANDARD
[RequiresDynamicCode("Hot Reload is not AOT compatible")]
#endif
public static void RegisterReplacedView(string oldViewType, Type newViewType)
{
if (!IsSupported || !IsEnabled)
Expand Down Expand Up @@ -145,17 +151,15 @@ public static void RegisterReplacedView(string oldViewType, Type newViewType)
}
}

}


static void RegisterHandler(KeyValuePair<Type, Type> pair, Type newHandler)
{
_ = HandlerService ?? throw new ArgumentNullException(nameof(HandlerService));
var view = pair.Key;
var newType = newHandler;
if (pair.Value.IsGenericType)
newType = pair.Value.GetGenericTypeDefinition().MakeGenericType(newHandler);
HandlerService.AddHandler(view, newType);
static void RegisterHandler(KeyValuePair<Type, Type> pair, Type newHandler)
{
_ = HandlerService ?? throw new ArgumentNullException(nameof(HandlerService));
var view = pair.Key;
var newType = newHandler;
if (pair.Value.IsGenericType)
newType = pair.Value.GetGenericTypeDefinition().MakeGenericType(newHandler);
HandlerService.AddHandler(view, newType);
}
}

public static void TriggerReload()
Expand All @@ -179,6 +183,10 @@ public static void TriggerReload()
}
}
#region Metadata Update Handler
[RequiresUnreferencedCode("Hot Reload is not trim compatible")]
#if !NETSTANDARD
[RequiresDynamicCode("Hot Reload is not AOT compatible")]
#endif
public static void UpdateApplication(Type[] types)
{
IsEnabled = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static string GetPath(string res)
return res;
}

foreach (AppFW.ResourceManager.Category category in Enum.GetValues(typeof(AppFW.ResourceManager.Category)))
foreach (AppFW.ResourceManager.Category category in Enum.GetValues<AppFW.ResourceManager.Category>())
{
foreach (var file in new[] { res, res + ".jpg", res + ".png", res + ".gif" })
{
Expand Down
24 changes: 0 additions & 24 deletions src/Core/src/Platform/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Microsoft.Maui.Platform
{
internal static class ReflectionExtensions
{
public static FieldInfo? GetField(this Type type, Func<FieldInfo, bool> predicate)
{
return GetFields(type).FirstOrDefault(predicate);
}

public static IEnumerable<FieldInfo> GetFields(this Type type)
{
return GetParts(type, i => i.DeclaredFields);
}

internal static object[]? GetCustomAttributesSafe(this Assembly assembly, Type attrType)
{
try
Expand All @@ -38,17 +26,5 @@ public static bool IsInstanceOfType(this Type self, object o)
{
return self.IsAssignableFrom(o.GetType());
}

static IEnumerable<T> GetParts<T>(Type type, Func<TypeInfo, IEnumerable<T>> selector)
{
Type? t = type;
while (t != null)
{
TypeInfo ti = t.GetTypeInfo();
foreach (T f in selector(ti))
yield return f;
t = ti.BaseType;
}
}
}
}
Loading