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] Use typed bindings internally #20567

Merged
merged 12 commits into from
Feb 29, 2024

Conversation

simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Feb 14, 2024

Description of Change

This is a follow-up to #20415. Since all bindings now can be typed bindings, I started replacing some bindings declared in code with typed bindings to fix trimming warnings in dotnet new maui on iOS. I haven't even tried to replace any bindings in Tizen, Windows, or Android specific code.

ShellSearchResultRenderer changes

The default template of ShellSearchResultRenderer uses SearchHandler.DisplayMemberName as the path for the binding. I don't see a good way to reimplement this in a way which would work well with typed bindings (would we need Func<object, string> DisplayMember in addition to DisplayMemberName? would that be a good API?). Instead, I decided to propose a new feature switch that will disable the default template and required to always define custom ItemTemplate.

Issues Fixed

Contributes to #19397 - fixes 9 trimming warnings.

@simonrozsival simonrozsival force-pushed the use-typed-bindings-internally branch from 1e6bc87 to d046eef Compare February 14, 2024 08:31
@simonrozsival simonrozsival marked this pull request as ready for review February 14, 2024 15:53
@simonrozsival simonrozsival requested a review from a team as a code owner February 14, 2024 15:53
@jsuarezruiz jsuarezruiz added the area-xaml XAML, CSS, Triggers, Behaviors label Feb 19, 2024
Copy link
Contributor

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

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

I know we've merged that API already, but TypedBinding.ForSingleNestingLevel looks odd. couldn't we go with the original plan of exposing, but internally, a generic SetBinding overload that takes an Expression, so we're future proof ? Even if we fail on multi-level ?

Or is it a performance issue ? can we address that ?

@simonrozsival
Copy link
Member Author

@StephaneDelcroix Yes, the problem with the expressions-based bindings (#19995) was performance. I agree that the name isn't the best and there aren't any checks that the delegates access just the single level so it can be misused. I proposed a different way to approach this via source generators (#20574) and if that is approved in some form, we could use it instead of this internal API and drop it.

Comment on lines 92 to 95
if (!RuntimeFeature.IsShellSearchResultsRendererDefaultTemplateSupported)
{
throw new ArgumentNullException(nameof(SearchHandler.ItemTemplate), "ItemTemplate must be set on SearchHandler");
}
Copy link
Member

@jonathanpeppers jonathanpeppers Feb 20, 2024

Choose a reason for hiding this comment

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

It looks like we should make the feature flag around DisplayMemberName and the default template can still work.

TODO: how many DisplayMemberName-like features exist out there? Telerik, third party, etc.?

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 updated the feature switch. It will now always just call ToString() on the item (the default behavior of bindings that set string properties) + added a warning if somebody uses DisplayMemberName.

image

{
if (SearchHandler.DisplayMemberName is not null)
{
Application.Current?.FindMauiContext()?.CreateLogger<ShellSearchResultsRenderer>().LogWarning(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning);
Copy link
Member

Choose a reason for hiding this comment

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

Should this throw? and then we put [RequiresUnreferencedCode] on the setter of the DisplayMemberName property?

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 wasn't sure if this should throw or not. When I'm running the app from the terminal, I don't see any stack trace or the exception message when the app crashes. Maybe do both?

The problem with [RequiresUnreferencedCode] on DisplayMemberName is that it causes ~50 trimming warnings similar to this one:

/.../maui/src/Controls/src/Core/Shell/SearchHandler.cs(398): Trim analysis warning IL2026: Microsoft.Maui.Controls.SearchHandler..cctor(): Using member 'Microsoft.Maui.Controls.SearchHandler.DisplayMemberName.set' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. DisplayMemberName is not supported. Consider implementing custom ItemTemplate instead. Alternatively, enable DisplayMemberName by setting the $(MauiShellSearchResultsRendererDisplayMemberNameSupported) MSBuild property to true. Note: DisplayMemberName is not trimming-safe and it might not work as expected in NativeAOT or fully trimmed apps. [/.../MyMauiApp/MyMauiApp.csproj::TargetFramework=net9.0-maccatalyst]

I am not sure why the static constructor would cause such a warning, but unfortunately, I don't know what to do about it.

Copy link
Member

Choose a reason for hiding this comment

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

So if we have:

public static readonly BindableProperty DisplayMemberNameProperty =
	BindableProperty.Create(nameof(DisplayMemberName), typeof(string), typeof(SearchHandler), null, BindingMode.OneTime);

//...

public string DisplayMemberName
{
	get { return (string)GetValue(DisplayMemberNameProperty); }
	[RequiresUnreferencedCode]
	set { SetValue(DisplayMemberNameProperty, value); }
}

Was it warning about nameof(DisplayMemberName)? I think we can probably leave the BindableProperty alone and only add the attribute to the setter of DisplayMemberName , if that is possible?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is exactly the code that I had locally, and it produced the flood of trimming warnings (we can't apply the attribute to fields BTW). Maybe the warnings are a bug in ILC? @vitek-karas might know what's going on when he's back from vacation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, it's probably not a bug, but it's still strange. We get 2 exactly same warnings on each line where we use typeof(SearchHandler), for example:

// /.../maui/src/Controls/src/Core/Shell/SearchHandler.cs(145): Trim analysis warning IL2026: Microsoft.Maui.Controls.SearchHandler..cctor(): Using member 'Microsoft.Maui.Controls.SearchHandler.DisplayMemberName.set' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Use ItemTemplate instead. [/.../MyMauiApp/MyMauiApp.csproj::TargetFramework=net9.0-maccatalyst]
// /.../maui/src/Controls/src/Core/Shell/SearchHandler.cs(145): Trim analysis warning IL2026: Microsoft.Maui.Controls.SearchHandler..cctor(): Using member 'Microsoft.Maui.Controls.SearchHandler.DisplayMemberName.set' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Use ItemTemplate instead. [/.../MyMauiApp/MyMauiApp.csproj::TargetFramework=net9.0-maccatalyst]
145: public static readonly BindableProperty CancelButtonColorProperty = BindableProperty.Create(nameof(CancelButtonColor), typeof(Color), typeof(SearchHandler), default(Color));

At the same time, the compiled XAML doesn't produce any warnings. That is because XamlC doesn't use the setter method, but it sets the value using BindableObject.SetValue:

<Shell.SearchHandler>
    <local:AnimalSearchHandler Placeholder="Enter search term"
                               ShowsResults="true"
                               DisplayMemberName="Name" />
</Shell.SearchHandler>
// decompiled IL
animalSearchHandler.SetValue(SearchHandler.DisplayMemberNameProperty, "Name");

Copy link
Member

Choose a reason for hiding this comment

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

This is very likely a problem in the trimmer - and possibly even in NativeAOT, but that I'm not sure of. The root cause is that the parameter to which we're passing typeof(SearchHandler) has DAM(PublicProperties | PublicMethods).

The PublicProperties will mark the DisplayMemberName property as reflection accessible, which in turn marks the getter of it as reflection accessible -> warning.
The PublicMethods will mark the getter direcly as reflection accessible -> warning.

@sbomer as FYI.

@simonrozsival simonrozsival force-pushed the use-typed-bindings-internally branch from a9b60fc to 53c26ae Compare February 28, 2024 08:31
@simonrozsival
Copy link
Member Author

@jonathanpeppers I think this PR is now ready to be merged

@rmarinho rmarinho merged commit 4c3a09c into dotnet:net9.0 Feb 29, 2024
47 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Mar 31, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

7 participants