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

[XC] Compile TemplateBindingExtension #22008

Merged
merged 3 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,19 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef
if (!acceptEmptyServiceProvider && requireServiceAttribute == null)
context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType);

if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.BindingExtension"
&& bpRef != null //do not compile bindings if we're not gonna SetBinding
)
foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition))
yield return instruction;
if (bpRef is not null) // do not compile bindings if we're not gonna SetBinding
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 hope we can remove this condition in #22023

{
if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.BindingExtension")
{
foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "BindingExtension")))
yield return instruction;
}
else if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.TemplateBindingExtension")
{
foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "TemplateBindingExtension")))
yield return instruction;
}
}

var markExt = markupExtension.ResolveCached(context.Cache);
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
Expand Down Expand Up @@ -383,7 +391,7 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef
}

//Once we get compiled IValueProvider, this will move to the BindingExpression
static IEnumerable<Instruction> CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt)
static IEnumerable<Instruction> CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt, (string, string, string) bindingExtensionType)
{
//TODO support casting operators
var module = context.Module;
Expand Down Expand Up @@ -480,8 +488,6 @@ static IEnumerable<Instruction> CompileBindingPath(ElementNode node, ILContext c
&& !md.HasCustomAttributes(module.ImportReference(context.Cache, ("mscorlib", "System", "ObsoleteAttribute")))));
var ctorinforef = ctorInfo.MakeGeneric(typedBindingRef, funcRef, actionRef, tupleRef);

var bindingExtensionType = ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "BindingExtension");

foreach (var instruction in bindingExt.LoadAs(context.Cache, module.GetTypeDefinition(context.Cache, bindingExtensionType), module))
yield return instruction;
foreach (var instruction in CompiledBindingGetGetter(tSourceRef, tPropertyRef, properties, node, context))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls.Xaml
{
Expand All @@ -24,17 +26,33 @@ public TemplateBindingExtension()

public string StringFormat { get; set; }

[EditorBrowsable(EditorBrowsableState.Never)] public TypedBindingBase TypedBinding { get; set; }

BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
return new Binding
if (TypedBinding is null)
{
Source = RelativeBindingSource.TemplatedParent,
Path = Path,
Mode = Mode,
Converter = Converter,
ConverterParameter = ConverterParameter,
StringFormat = StringFormat
};
#pragma warning disable IL2026 // Using member 'Microsoft.Maui.Controls.Binding.Binding(String, BindingMode, IValueConverter, Object, String, Object)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Using bindings with string paths is not trim safe. Use expression-based binding instead.
// This code is only reachable in XamlC compiled code when there is a missing x:DataType and the binding could not be compiled.
// In that case, we produce a warning that the binding could not be compiled.
return new Binding
{
Source = RelativeBindingSource.TemplatedParent,
Path = Path,
Mode = Mode,
Converter = Converter,
ConverterParameter = ConverterParameter,
StringFormat = StringFormat
};
#pragma warning restore IL2026
}

TypedBinding.Mode = Mode;
TypedBinding.Converter = Converter;
TypedBinding.ConverterParameter = ConverterParameter;
TypedBinding.StringFormat = StringFormat;
TypedBinding.Source = RelativeBindingSource.TemplatedParent;
return TypedBinding;
}

object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string
~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void
~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate
Expand Down
19 changes: 19 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.TemplateBindingsCompiler" >
<ContentPage.Resources>
<ControlTemplate x:Key="MyCardTemplate">
<Label
x:Name="CardTitleLabel"
Text="{TemplateBinding CardTitle, x:DataType=local:TemplateBindingCompilerTestCardView}" />
</ControlTemplate>
</ContentPage.Resources>

<local:TemplateBindingCompilerTestCardView
x:Name="ContentView"
CardTitle="The title"
ControlTemplate="{StaticResource MyCardTemplate}" />
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Core.UnitTests;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.UnitTests;
using NUnit.Framework;

namespace Microsoft.Maui.Controls.Xaml.UnitTests
{
public partial class TemplateBindingsCompiler : ContentPage
{
public TemplateBindingsCompiler()
{
InitializeComponent();
}

public TemplateBindingsCompiler(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}

[TestFixture]
public class Tests
{
[SetUp] public void Setup() => DispatcherProvider.SetCurrent(new DispatcherProviderStub());
[TearDown] public void TearDown() => DispatcherProvider.SetCurrent(null);

[TestCase(false)]
[TestCase(true)]
public void Test(bool useCompiledXaml)
{
var page = new TemplateBindingsCompiler(useCompiledXaml);
var label = (Label)page.ContentView.GetTemplateChild("CardTitleLabel");
Assert.AreEqual("The title", label?.Text);

if (useCompiledXaml)
{
var binding = label.GetContext(Label.TextProperty).Bindings.GetValue();
Assert.That(binding, Is.TypeOf<TypedBinding<TemplateBindingCompilerTestCardView, string>>());
}
}
}
}

public class TemplateBindingCompilerTestCardView : ContentView
{
public static readonly BindableProperty CardTitleProperty =
BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(TemplateBindingCompilerTestCardView), string.Empty);

public string CardTitle
{
get => (string)GetValue(CardTitleProperty);
set => SetValue(CardTitleProperty, value);
}
}
}