Skip to content

Commit

Permalink
[XC] Compile TemplateBindingExtension (#22008)
Browse files Browse the repository at this point in the history
* Compile template binding

* Add missing bpRef check

* Add test for template bindings compilation
  • Loading branch information
simonrozsival authored May 29, 2024
1 parent 1c08d05 commit 58b0da6
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 16 deletions.
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 @@ public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference
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
{
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 @@ public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference
}

//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
34 changes: 26 additions & 8 deletions src/Controls/src/Xaml/MarkupExtensions/TemplateBindingExtension.cs
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
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/PublicAPI/net-ios/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
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>
61 changes: 61 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml.cs
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);
}
}
}

0 comments on commit 58b0da6

Please sign in to comment.