Skip to content

Commit

Permalink
[Trimming] Use new feature switch definition attribute and enable ana…
Browse files Browse the repository at this point in the history
…lyzers in Controls.Core.csproj (#21621)

Contributes to #18658

We can use `[FeatureSwitchDefinition("...")]` attributes instead of `ILLink.Substitutions.xml` since dotnet/runtime#99338 has been merged and has flown into MAUI.

* Use new FeatureSwitchDefinition attributes instead of ILLink substitutions
* Update comment
* Update Controls
* Update Compatibility
* Disable analyzers in Controls.Core on Tizen
* Skip warning on Windows
* Add RUC attributes to NativeBindingExtensions
* Add RUC attributes to NativeBindingService
* Add comment explaining why DoNothing moved from Binding to MultiBinding
  • Loading branch information
simonrozsival authored Apr 17, 2024
1 parent f118f45 commit ac6cc71
Show file tree
Hide file tree
Showing 37 changed files with 293 additions and 207 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Android.App;
Expand Down Expand Up @@ -104,7 +105,7 @@ public void SetStatusBarColor(AColor color)
Window.SetStatusBarColor(color);
}

static void RegisterHandler(Type target, Type handler, Type filter)
static void RegisterHandler(Type target, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type handler, Type filter)
{
Profile.FrameBegin();

Expand Down Expand Up @@ -459,7 +460,7 @@ void OnStateChanged()
}

// This is currently being used by the previewer please do not change or remove this
void RegisterHandlerForDefaultRenderer(Type target, Type handler, Type filter)
void RegisterHandlerForDefaultRenderer(Type target, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type handler, Type filter)
{
RegisterHandler(target, handler, filter);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
public static class NativeBindingExtensions
{
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public static void SetBinding(this global::Android.Views.View view, string propertyName, BindingBase binding, string updateSourceEventName = null)
{
PlatformBindingHelpers.SetBinding(view, propertyName, binding, updateSourceEventName);
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/Android/NativeBindingservice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
class NativeBindingService : INativeBindingService
{
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)]
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public bool TrySetBinding(object target, string propertyName, BindingBase binding)
{
var view = target as AView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,9 @@ Cell GetNewGroupHeaderCell(ITemplatedItemsList<Cell> group)
else
{
groupHeaderCell = new TextCell();
groupHeaderCell.SetBinding(TextCell.TextProperty, nameof(group.Name));
groupHeaderCell.SetBinding(
TextCell.TextProperty,
TypedBinding.ForSingleNestingLevel(nameof(group.Name), static (ITemplatedItemsList<Cell> g) => g.Name));
groupHeaderCell.BindingContext = group;
}

Expand Down
9 changes: 5 additions & 4 deletions src/Compatibility/Core/src/ExportRendererAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Maui.Controls.Compatibility
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ExportRendererAttribute : HandlerAttribute
{
public ExportRendererAttribute(Type handler, Type target) : this(handler, target, null)
public ExportRendererAttribute(Type handler, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type target) : this(handler, target, null)
{
}

public ExportRendererAttribute(Type handler, Type target, Type[] supportedVisuals) : base(handler, target, supportedVisuals)
public ExportRendererAttribute(Type handler, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type target, Type[] supportedVisuals) : base(handler, target, supportedVisuals)
{
}
}

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ExportCellAttribute : HandlerAttribute
{
public ExportCellAttribute(Type handler, Type target) : base(handler, target)
public ExportCellAttribute(Type handler, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type target) : base(handler, target)
{
}
}

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
{
public ExportImageSourceHandlerAttribute(Type handler, Type target) : base(handler, target)
public ExportImageSourceHandlerAttribute(Type handler, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type target) : base(handler, target)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;

namespace Microsoft.Maui.Controls.Compatibility.Hosting
{
public static class MauiHandlersCollectionExtensions
{
public static IMauiHandlersCollection TryAddCompatibilityRenderer(this IMauiHandlersCollection handlersCollection, Type controlType, Type rendererType)
public static IMauiHandlersCollection TryAddCompatibilityRenderer(this IMauiHandlersCollection handlersCollection, Type controlType, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type rendererType)
{
Internals.Registrar.CheckIfRendererIsCompatibilityRenderer(rendererType);
Hosting.MauiAppBuilderExtensions.CheckForCompatibility();
Expand All @@ -21,7 +22,7 @@ public static IMauiHandlersCollection TryAddCompatibilityRenderer(this IMauiHand
return handlersCollection;
}

public static IMauiHandlersCollection AddCompatibilityRenderer(this IMauiHandlersCollection handlersCollection, Type controlType, Type rendererType)
public static IMauiHandlersCollection AddCompatibilityRenderer(this IMauiHandlersCollection handlersCollection, Type controlType, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] Type rendererType)
{
Internals.Registrar.CheckIfRendererIsCompatibilityRenderer(rendererType);
Hosting.MauiAppBuilderExtensions.CheckForCompatibility();
Expand All @@ -36,7 +37,7 @@ public static IMauiHandlersCollection AddCompatibilityRenderer(this IMauiHandler
return handlersCollection;
}

public static IMauiHandlersCollection AddCompatibilityRenderer<TControlType, TMauiType, TRenderer>(this IMauiHandlersCollection handlersCollection)
public static IMauiHandlersCollection AddCompatibilityRenderer<TControlType, TMauiType, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] TRenderer>(this IMauiHandlersCollection handlersCollection)
where TMauiType : IView
{
Internals.Registrar.CheckIfRendererIsCompatibilityRenderer(typeof(TRenderer));
Expand All @@ -51,7 +52,7 @@ public static IMauiHandlersCollection AddCompatibilityRenderer<TControlType, TMa
return handlersCollection;
}

public static IMauiHandlersCollection AddCompatibilityRenderer<TControlType, TRenderer>(this IMauiHandlersCollection handlersCollection)
public static IMauiHandlersCollection AddCompatibilityRenderer<TControlType, [DynamicallyAccessedMembers(Internals.HandlerType.TargetMembers)] TRenderer>(this IMauiHandlersCollection handlersCollection)
where TControlType : IView
{
Internals.Registrar.CheckIfRendererIsCompatibilityRenderer(typeof(TRenderer));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Internals;
using NView = Tizen.NUI.BaseComponents.View;

namespace Microsoft.Maui.Controls.Compatibility.Platform.Tizen
{
public static class NativeBindingExtensions
{
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public static void SetBinding(this NView view, string propertyName, BindingBase binding, string updateSourceEventName = null)
{
PlatformBindingHelpers.SetBinding(view, propertyName, binding, updateSourceEventName);
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/Tizen/NativeBindingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Tizen
{
class NativeBindingService : INativeBindingService
{
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)]
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public bool TrySetBinding(object target, string propertyName, BindingBase binding)
{
Hosting.MauiAppBuilderExtensions.CheckForCompatibility();
Expand Down
2 changes: 2 additions & 0 deletions src/Compatibility/Core/src/Windows/NativeBindingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Internals;
using Microsoft.UI.Xaml;
using static System.String;
Expand All @@ -9,6 +10,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.UWP
{
public static class NativeBindingExtensions
{
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public static void SetBinding(this FrameworkElement view, string propertyName, BindingBase bindingBase, string updateSourceEventName = null)
{
var binding = bindingBase as Binding;
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/Windows/NativeBindingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.UWP
{
public class NativeBindingService : INativeBindingService
{
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)]
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public bool TrySetBinding(object target, string propertyName, BindingBase binding)
{
var view = target as FrameworkElement;
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/iOS/NativeBindingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
[Preserve(AllMembers = true)]
class NativeBindingService : INativeBindingService
{
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)]
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public bool TrySetBinding(object target, string propertyName, BindingBase binding)
{
Hosting.MauiAppBuilderExtensions.CheckForCompatibility();
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/AppThemeBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ void Set()
target.SetDynamicResource(_targetProperty, dynamicResource.Key, specificity);
else
{
if (!BindingExpression.TryConvert(ref value, _targetProperty, _targetProperty.ReturnType, true))
if (!BindingExpressionHelper.TryConvert(ref value, _targetProperty, _targetProperty.ReturnType, true))
{
BindingDiagnostics.SendBindingFailure(this, null, target, _targetProperty, "AppThemeBinding", BindingExpression.CannotConvertTypeErrorMessage, value, _targetProperty.ReturnType);
return;
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/BindableObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Maui.Graphics;

Expand Down Expand Up @@ -48,6 +49,7 @@ internal static void PropagateBindingContext<T>(this BindableObject self, IEnume
}

/// <include file="../../docs/Microsoft.Maui.Controls/BindableObjectExtensions.xml" path="//Member[@MemberName='SetBinding']/Docs/*" />
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public static void SetBinding(this BindableObject self, BindableProperty targetProperty, string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null,
string stringFormat = null)
{
Expand Down
5 changes: 2 additions & 3 deletions src/Controls/src/Core/Binding.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;

namespace Microsoft.Maui.Controls
{
/// <include file="../../docs/Microsoft.Maui.Controls/Binding.xml" path="Type[@FullName='Microsoft.Maui.Controls.Binding']/Docs/*" />
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
public sealed class Binding : BindingBase
{
public const string SelfPath = ".";
Expand Down Expand Up @@ -92,7 +91,7 @@ public object Source
}

/// <include file="../../docs/Microsoft.Maui.Controls/Binding.xml" path="//Member[@MemberName='DoNothing']/Docs/*" />
public static readonly object DoNothing = new object();
public static readonly object DoNothing = MultiBinding.DoNothing; // the instance was moved to MultiBinding because the Binding class is annotated with [RequiresUnreferencedCode]

/// <include file="../../docs/Microsoft.Maui.Controls/Binding.xml" path="//Member[@MemberName='UpdateSourceEventName']/Docs/*" />
[EditorBrowsable(EditorBrowsableState.Never)]
Expand Down
54 changes: 4 additions & 50 deletions src/Controls/src/Core/BindingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
Expand All @@ -12,6 +13,7 @@

namespace Microsoft.Maui.Controls
{
[RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)]
internal sealed class BindingExpression
{
internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'";
Expand Down Expand Up @@ -154,7 +156,7 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop
else
value = Binding.FallbackValue ?? property.GetDefaultValue(target);

if (!TryConvert(ref value, property, property.ReturnType, true))
if (!BindingExpressionHelper.TryConvert(ref value, property, property.ReturnType, true))
{
BindingDiagnostics.SendBindingFailure(Binding, current, target, property, "Binding", CannotConvertTypeErrorMessage, value, property.ReturnType);
return;
Expand All @@ -166,7 +168,7 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop
{
object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);

if (!TryConvert(ref value, property, part.SetterType, false))
if (!BindingExpressionHelper.TryConvert(ref value, property, part.SetterType, false))
{
BindingDiagnostics.SendBindingFailure(Binding, current, target, property, "Binding", CannotConvertTypeErrorMessage, value, part.SetterType);
return;
Expand Down Expand Up @@ -428,54 +430,6 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part)
}
}

static readonly Type[] DecimalTypes = { typeof(float), typeof(decimal), typeof(double) };

internal static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
{
if (value == null)
return !convertTo.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(convertTo) != null;
try
{
if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
return true;
}
catch (InvalidOperationException)
{ //that's what TypeConverters ususally throw
return false;
}

object original = value;
try
{
convertTo = Nullable.GetUnderlyingType(convertTo) ?? convertTo;

var stringValue = value as string ?? string.Empty;
// see: https://bugzilla.xamarin.com/show_bug.cgi?id=32871
// do not canonicalize "*.[.]"; "1." should not update bound BindableProperty
if (stringValue.EndsWith(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, StringComparison.Ordinal) && DecimalTypes.Contains(convertTo))
{
value = original;
return false;
}

// do not canonicalize "-0"; user will likely enter a period after "-0"
if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
{
value = original;
return false;
}

value = Convert.ChangeType(value, convertTo, CultureInfo.CurrentCulture);

return true;
}
catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is InvalidOperationException || ex is OverflowException)
{
value = original;
return false;
}
}

// SubscribeToAncestryChanges, ClearAncestryChangeSubscriptions, FindAncestryIndex, and
// OnElementParentSet are used with RelativeSource ancestor-type bindings, to detect when
// there has been an ancestry change requiring re-applying the binding, and to minimize
Expand Down
58 changes: 58 additions & 0 deletions src/Controls/src/Core/BindingExpressionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace Microsoft.Maui.Controls
{
internal static class BindingExpressionHelper
{
static readonly Type[] DecimalTypes = { typeof(float), typeof(decimal), typeof(double) };

internal static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
{
if (value == null)
return !convertTo.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(convertTo) != null;
try
{
if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
return true;
}
catch (InvalidOperationException)
{ //that's what TypeConverters ususally throw
return false;
}

object original = value;
try
{
convertTo = Nullable.GetUnderlyingType(convertTo) ?? convertTo;

var stringValue = value as string ?? string.Empty;
// see: https://bugzilla.xamarin.com/show_bug.cgi?id=32871
// do not canonicalize "*.[.]"; "1." should not update bound BindableProperty
if (stringValue.EndsWith(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, StringComparison.Ordinal) && DecimalTypes.Contains(convertTo))
{
value = original;
return false;
}

// do not canonicalize "-0"; user will likely enter a period after "-0"
if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
{
value = original;
return false;
}

value = Convert.ChangeType(value, convertTo, CultureInfo.CurrentCulture);

return true;
}
catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is InvalidOperationException || ex is OverflowException)
{
value = original;
return false;
}
}
}
}
Loading

0 comments on commit ac6cc71

Please sign in to comment.