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 analyzers in Controls.Xaml #21927

Merged
merged 14 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions docs/design/FeatureSwitches.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Certain features of MAUI can be enabled or disabled using feature switches. The
| MauiShellSearchResultsRendererDisplayMemberNameSupported | Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported | When disabled, it is necessary to always set `ItemTemplate` of any `SearchHandler`. Displaying search results through `DisplayMemberName` will not work. |
| MauiQueryPropertyAttributeSupport | Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported | When disabled, the `[QueryProperty(...)]` attributes won't be used to set values to properties when navigating. |
| MauiImplicitCastOperatorsUsageViaReflectionSupport | Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported | When disabled, MAUI won't look for implicit cast operators when converting values from one type to another. This feature is not trim-compatible. |
| MauiNonCompiledBindingsInXamlSupport | Microsoft.Maui.RuntimeFeature.AreNonCompiledBindingsInXamlSupported | When disabled, it is not possible to use string-based bindings in XAML. All bindings are required to be annotated with x:DataType so they can be compiled. |

## MauiXamlRuntimeParsingSupport

Expand Down Expand Up @@ -37,3 +38,8 @@ When disabled, MAUI won't look for implicit cast operators when converting value
If your library or your app defines an implicit operator on a type that can be used in one of the previous scenarios, you should define a custom `TypeConverter` for your type and attach it to the type using the `[TypeConverter(typeof(MyTypeConverter))]` attribute.

_Note: Prefer using the `TypeConverterAttribute` as it can help the trimmer achieve better binary size in certain scenarios._

## MauiNonCompiledBindingsInXamlSupport

When disabled, XAML compiler will require that all bindings are compiled. All bindings that cannot be compiled due to missing x:DataType annotations will throw runtime exceptions.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<PublishAot>true</PublishAot>
<_IsPublishing>true</_IsPublishing>
<IlcTreatWarningsAsErrors>false</IlcTreatWarningsAsErrors>
<WarningsNotAsErrors>IL3050;XC0022</WarningsNotAsErrors>
<DefineConstants>$(DefineConstants);NATIVE_AOT</DefineConstants>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ static bool IsBindingContextBinding(ElementNode node)
{
// looking for BindingContext="{Binding ...}"
return GetParent(node) is IElementNode parentNode
&& ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out var propertyName)
&& node.TryGetPropertyName(parentNode, out var propertyName)
&& propertyName.NamespaceURI == ""
&& propertyName.LocalName == nameof(BindableObject.BindingContext);
}
Expand Down
27 changes: 8 additions & 19 deletions src/Controls/src/Xaml/ApplyPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Xml;
Expand All @@ -14,6 +15,10 @@

namespace Microsoft.Maui.Controls.Xaml
{
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#if !NETSTANDARD
[RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#endif
class ApplyPropertiesVisitor : IXamlNodeVisitor
{
public static readonly IList<XmlName> Skips = new List<XmlName> {
Expand Down Expand Up @@ -49,7 +54,7 @@ public void Visit(ValueNode node, INode parentNode)
return;


if (TryGetPropertyName(node, parentNode, out XmlName propertyName))
if (node.TryGetPropertyName(parentNode, out XmlName propertyName))
{
if (TrySetRuntimeName(propertyName, source, value, node))
return;
Expand Down Expand Up @@ -83,7 +88,7 @@ public void Visit(MarkupNode node, INode parentNode)

public void Visit(ElementNode node, INode parentNode)
{
if (TryGetPropertyName(node, parentNode, out XmlName propertyName) && propertyName == XmlName._CreateContent)
if (node.TryGetPropertyName(parentNode, out XmlName propertyName) && propertyName == XmlName._CreateContent)
{
var s0 = Values[parentNode];
if (s0 is ElementTemplate)
Expand All @@ -107,7 +112,7 @@ public void Visit(ElementNode node, INode parentNode)
if (!Values.TryGetValue(node, out var value) && Context.ExceptionHandler != null)
return;

if (propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName))
if (propertyName != XmlName.Empty || node.TryGetPropertyName(parentNode, out propertyName))
{
if (Skips.Contains(propertyName))
return;
Expand Down Expand Up @@ -229,22 +234,6 @@ public void Visit(ListNode node, INode parentNode)
{
}

public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name)
{
name = default(XmlName);
var parentElement = parentNode as IElementNode;
if (parentElement == null)
return false;
foreach (var kvp in parentElement.Properties)
{
if (kvp.Value != node)
continue;
name = kvp.Key;
return true;
}
return false;
}

internal static bool IsCollectionItem(INode node, INode parentNode)
{
var parentList = parentNode as IListNode;
Expand Down
6 changes: 6 additions & 0 deletions src/Controls/src/Xaml/Controls.Xaml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<Description>.NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML. This package only contains the XAML tooling. Please install the Microsoft.Maui.Controls package to start using .NET MAUI.</Description>
</PropertyGroup>

<PropertyGroup Condition="!$(TargetFramework.StartsWith('netstandard'))">
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Controls\src\Core\Controls.Core.csproj" />
<ProjectReference Include="..\..\..\Core\src\Core.csproj" />
Expand Down
7 changes: 6 additions & 1 deletion src/Controls/src/Xaml/CreateValuesVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
Expand All @@ -9,6 +10,10 @@

namespace Microsoft.Maui.Controls.Xaml
{
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#if !NETSTANDARD
[RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#endif
class CreateValuesVisitor : IXamlNodeVisitor
{
public CreateValuesVisitor(HydrationContext context)
Expand Down Expand Up @@ -185,7 +190,7 @@ public void Visit(RootNode node, INode parentNode)
public void Visit(ListNode node, INode parentNode)
{
//this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties
if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out XmlName name))
if (node.TryGetPropertyName(parentNode, out XmlName name))
node.XmlName = name;
}

Expand Down
8 changes: 7 additions & 1 deletion src/Controls/src/Xaml/ExpandMarkupsVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using Microsoft.Maui.Controls.Xaml.Internals;

namespace Microsoft.Maui.Controls.Xaml
{
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#if !NETSTANDARD
[RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#endif
class ExpandMarkupsVisitor : IXamlNodeVisitor
{
public ExpandMarkupsVisitor(HydrationContext context) => Context = context;
Expand Down Expand Up @@ -40,7 +45,7 @@ public void Visit(MarkupNode markupnode, INode parentNode)
{
var parentElement = parentNode as IElementNode;
XmlName propertyName;
if (!ApplyPropertiesVisitor.TryGetPropertyName(markupnode, parentNode, out propertyName))
if (!markupnode.TryGetPropertyName(parentNode, out propertyName))
return;
if (Skips.Contains(propertyName))
return;
Expand Down Expand Up @@ -106,6 +111,7 @@ INode ParseExpression(ref string expression, IXmlNamespaceResolver nsResolver, I
return new MarkupExpansionParser { ExceptionHandler = Context.ExceptionHandler }.Parse(match, ref expression, serviceProvider);
}

[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
public class MarkupExpansionParser : MarkupExpressionParser, IExpressionParser<INode>
{
IElementNode _node;
Expand Down
7 changes: 6 additions & 1 deletion src/Controls/src/Xaml/FillResourceDictionariesVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Xaml.Internals;

namespace Microsoft.Maui.Controls.Xaml
{
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#if !NETSTANDARD
[RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#endif
class FillResourceDictionariesVisitor : IXamlNodeVisitor
{
public FillResourceDictionariesVisitor(HydrationContext context) => Context = context;
Expand Down Expand Up @@ -38,7 +43,7 @@ public void Visit(ElementNode node, INode parentNode)
return;

//Set RD to VE
if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]) && ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out XmlName propertyName))
if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]) && node.TryGetPropertyName(parentNode, out XmlName propertyName))
{
if ((propertyName.LocalName == "Resources" ||
propertyName.LocalName.EndsWith(".Resources", StringComparison.Ordinal)) && value is ResourceDictionary)
Expand Down
4 changes: 4 additions & 0 deletions src/Controls/src/Xaml/MarkupExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
//

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace Microsoft.Maui.Controls.Xaml
Expand All @@ -46,6 +47,7 @@ protected struct Property
public object value;
}

[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
public object ParseExpression(ref string expression, IServiceProvider serviceProvider)
{
if (serviceProvider == null)
Expand Down Expand Up @@ -120,6 +122,7 @@ internal static bool MatchMarkup(out string match, string expression, out int en
return true;
}

[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
protected Property ParseProperty(IServiceProvider serviceProvider, ref string remaining)
{
object value = null;
Expand Down Expand Up @@ -148,6 +151,7 @@ protected Property ParseProperty(IServiceProvider serviceProvider, ref string re
return new Property { last = next == '}', name = name, strValue = str_value, value = value };
}

[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
Property ParsePropertyExpression(string prop, IServiceProvider serviceProvider, ref string remaining)
{
bool last;
Expand Down
5 changes: 5 additions & 0 deletions src/Controls/src/Xaml/MarkupExtensionParser.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Microsoft.Maui.Controls.Xaml
{
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#if !NETSTANDARD
[RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
#endif
internal sealed class MarkupExtensionParser : MarkupExpressionParser, IExpressionParser<object>
{
IMarkupExtension markupExtension;
Expand Down
4 changes: 4 additions & 0 deletions src/Controls/src/Xaml/MarkupExtensions/ArrayExtension.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Maui.Controls.Xaml
{
[ContentProperty(nameof(Items))]
[AcceptEmptyServiceProvider]
#if !NETSTANDARD
[RequiresDynamicCode("ArrayExtension is not AOT safe.")]
#endif
public class ArrayExtension : IMarkupExtension<Array>
jonathanpeppers marked this conversation as resolved.
Show resolved Hide resolved
{
public ArrayExtension()
Expand Down
21 changes: 16 additions & 5 deletions src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@ public sealed class BindingExtension : IMarkupExtension<BindingBase>
BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
if (TypedBinding == null)
return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source)
{
if (RuntimeFeature.AreNonCompiledBindingsInXamlSupported)
{
UpdateSourceEventName = UpdateSourceEventName,
FallbackValue = FallbackValue,
TargetNullValue = TargetNullValue,
};
return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source)
{
UpdateSourceEventName = UpdateSourceEventName,
FallbackValue = FallbackValue,
TargetNullValue = TargetNullValue,
};
}
else
{
throw new InvalidOperationException(
$"It was not possible to compile binding with path '{Path}'. Bindings with string paths are not supported. " +
"Make sure all bindings in XAML are correctly annotated with x:DataType.");
}
}

TypedBinding.Mode = Mode;
TypedBinding.Converter = Converter;
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Xml;
Expand All @@ -13,6 +14,7 @@ namespace Microsoft.Maui.Controls.Xaml
typeof(IValueConverterProvider),
typeof(IXmlLineInfoProvider),
typeof(IConverterOptions)])]
[RequiresUnreferencedCode("The OnIdiomExtension is not trim safe. Use OnIdiom<T> instead.")]
Copy link
Member Author

Choose a reason for hiding this comment

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

@jonathanpeppers this is the case I'm most worried about of these extensions. The {OnIdiom ...} cannot be simplified at compile time and I don't think we can really make it trimming-friendly because of Activator.CreateInstance(propertyType) which we call for value types.

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 wonder if XamlC could transform OnIdiomExtension into OnIdiom<T> when it can infer T from the values passed to it 🤔 It might be worth trying.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think we could file an issue to fix {OnIdiom} in the future.

This PR makes good progress, so doesn't have to fix it here yet.

Copy link
Member Author

Choose a reason for hiding this comment

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

Issue: #22142

public class OnIdiomExtension : IMarkupExtension
{
// See Device.Idiom
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -12,6 +13,7 @@ namespace Microsoft.Maui.Controls.Xaml
typeof(IValueConverterProvider),
typeof(IXmlLineInfoProvider),
typeof(IConverterOptions)])]
[RequiresUnreferencedCode("The OnPlatformExtension is not trim safe. Use OnPlatform<T> instead.")]
jonathanpeppers marked this conversation as resolved.
Show resolved Hide resolved
public class OnPlatformExtension : IMarkupExtension
{
static object s_notset = new object();
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/MarkupExtensions/StaticExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Xml;
Expand All @@ -7,6 +8,7 @@ namespace Microsoft.Maui.Controls.Xaml
{
[ContentProperty(nameof(Member))]
[ProvideCompiled("Microsoft.Maui.Controls.Build.Tasks.StaticExtension")]
[RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)]
public class StaticExtension : IMarkupExtension
jonathanpeppers marked this conversation as resolved.
Show resolved Hide resolved
{
public string Member { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,24 @@ public TemplateBindingExtension()

BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
return new Binding
if (RuntimeFeature.AreNonCompiledBindingsInXamlSupported)
{
Source = RelativeBindingSource.TemplatedParent,
Path = Path,
Mode = Mode,
Converter = Converter,
ConverterParameter = ConverterParameter,
StringFormat = StringFormat
};
return new Binding
{
Source = RelativeBindingSource.TemplatedParent,
Path = Path,
Mode = Mode,
Converter = Converter,
ConverterParameter = ConverterParameter,
StringFormat = StringFormat
};
}
else
{
throw new InvalidOperationException(
$"It was not possible to compile binding with path '{Path}'. Bindings with string paths are not supported. " +
"Use {Binding RelativeSource={RelativeSource TemplatedParent}} instead of {TemplateBinding} Make sure all bindings in XAML are correctly annotated with x:DataType.");
}
}

object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
Expand Down
21 changes: 21 additions & 0 deletions src/Controls/src/Xaml/NodeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Microsoft.Maui.Controls.Xaml
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
{
internal static class NodeExtensions
{
public static bool TryGetPropertyName(this INode node, INode parentNode, out XmlName name)
{
name = default;
var parentElement = parentNode as IElementNode;
if (parentElement == null)
return false;
foreach (var kvp in parentElement.Properties)
{
if (kvp.Value != node)
continue;
name = kvp.Key;
return true;
}
return false;
}
}
}
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading