Skip to content

Commit

Permalink
[X] Allow using x:Type and type literals interchangeably in certain s…
Browse files Browse the repository at this point in the history
…cenarios (#20915)

* Add tests

* Add new visitor to simplify type extension

* Use the new visitor before SetPropertiesVisitor and ApplyPropertiesVisitor

* Remove unnecessary code
  • Loading branch information
simonrozsival authored Mar 18, 2024
1 parent 2317119 commit 4306566
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,7 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
|| parent.XmlType.Name == nameof(MultiTrigger)
|| parent.XmlType.Name == nameof(Style)))
{
var ttnode = (parent as ElementNode).Properties[new XmlName("", "TargetType")];
if (ttnode is ValueNode)
typeName = (ttnode as ValueNode).Value as string;
else if (ttnode is IElementNode)
typeName = ((ttnode as IElementNode).CollectionItems.FirstOrDefault() as ValueNode)?.Value as string ?? ((ttnode as IElementNode).Properties[new XmlName("", "TypeName")] as ValueNode)?.Value as string;
typeName = GetTargetTypeName(parent);
}
else if (parent.XmlType.NamespaceUri == XamlParser.MauiUri && parent.XmlType.Name == nameof(VisualState))
{
Expand All @@ -57,12 +53,7 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
}
else if ((node.Parent as ElementNode)?.XmlType.NamespaceUri == XamlParser.MauiUri && (node.Parent as ElementNode)?.XmlType.Name == nameof(Trigger))
{
var targetTypeNode = (node.Parent as ElementNode).Properties[new XmlName("", "TargetType")];
if (targetTypeNode is ValueNode valueNode)
typeName = valueNode.Value as string;
else if (targetTypeNode is ElementNode elementNode && elementNode.XmlType.Name == "TypeExtension")
typeName = (elementNode.Properties[new XmlName("", "TypeName")] as ValueNode).Value as string;

typeName = GetTargetTypeName(node.Parent);
}
propertyName = parts[0];
}
Expand All @@ -85,6 +76,9 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
if (bpRef == null)
throw new BuildException(PropertyResolution, node, null, propertyName, typeRef.Name);
return bpRef;

static string GetTargetTypeName(INode node)
=> ((node as ElementNode).Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string;
}

static string FindTypeNameForVisualState(IElementNode parent, IXmlLineInfo lineInfo)
Expand Down
13 changes: 1 addition & 12 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,18 +407,7 @@ static IEnumerable<Instruction> CompileBindingPath(ElementNode node, ILContext c
yield break;
}

string dataType = null;

if (dataTypeNode is ElementNode elementNode
&& elementNode.XmlType.NamespaceUri == XamlParser.X2009Uri
&& elementNode.XmlType.Name == nameof(Microsoft.Maui.Controls.Xaml.TypeExtension)
&& elementNode.Properties.ContainsKey(new XmlName("", nameof(Microsoft.Maui.Controls.Xaml.TypeExtension.TypeName)))
&& (elementNode.Properties[new XmlName("", nameof(Microsoft.Maui.Controls.Xaml.TypeExtension.TypeName))] as ValueNode)?.Value is string stringtype)
dataType = stringtype;

if ((dataTypeNode as ValueNode)?.Value is string sType)
dataType = sType;

string dataType = (dataTypeNode as ValueNode)?.Value as string;
if (dataType is null)
throw new BuildException(XDataTypeSyntax, dataTypeNode as IXmlLineInfo, null);

Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Build.Tasks/XamlCTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ bool TryCoreCompile(MethodDefinition initComp, ILRootNode rootnode, string xamlF
rootnode.Accept(new SetNamescopesAndRegisterNamesVisitor(visitorContext), null);
rootnode.Accept(new SetFieldVisitor(visitorContext), null);
rootnode.Accept(new SetResourcesVisitor(visitorContext), null);
rootnode.Accept(new SimplifyTypeExtensionVisitor(), null);
rootnode.Accept(new SetPropertiesVisitor(visitorContext, true), null);

il.Emit(Ret);
Expand Down
84 changes: 84 additions & 0 deletions src/Controls/src/Xaml/SimplifyTypeExtensionVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Linq;

#nullable disable

namespace Microsoft.Maui.Controls.Xaml
{
class SimplifyTypeExtensionVisitor : IXamlNodeVisitor
{
public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
public bool StopOnDataTemplate => false;
public bool VisitNodeOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool IsResourceDictionary(ElementNode node) => false;
public bool SkipChildren(INode node, INode parentNode) => false;

public void Visit(ValueNode node, INode parentNode)
{
}

public void Visit(MarkupNode node, INode parentNode)
{
//markup was already expanded to element
}

public void Visit(ElementNode node, INode parentNode)
{
// Only simplify property setter of TypeExtension and x:Type
// TargetType="{x:Type typeNameValue}" -> TargetType="typeNameValue"
// x:DataType="{x:Type typeNameValue}" -> x:DataType="typeNameValue"

if (IsValueOfXDataTypeOrTargetType(node, parentNode, out XmlName propertyName)
&& IsTypeExtension(node, out ValueNode typeNameValueNode))
{
(parentNode as IElementNode).Properties[propertyName] = typeNameValueNode;
}

static bool IsValueOfXDataTypeOrTargetType(ElementNode node, INode parentNode, out XmlName propertyName)
=> ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName)
&& (IsXDataType(propertyName) || IsTargetTypePropertyOfMauiType(parentNode, propertyName));

static bool IsTargetTypePropertyOfMauiType(INode parentNode, XmlName propertyName)
=> propertyName == new XmlName("", "TargetType")
&& parentNode is ElementNode { XmlType: var parentType }
&& (IsStyle(parentType)
|| IsTrigger(parentType)
|| IsDataTrigger(parentType)
|| IsMultiTrigger(parentType));

static bool IsXDataType(XmlName name) => name == XmlName.xDataType;
static bool IsStyle(XmlType type) => type.Name == nameof(Style) && type.NamespaceUri == XamlParser.MauiUri;
static bool IsTrigger(XmlType type) => type.Name == nameof(Trigger) && type.NamespaceUri == XamlParser.MauiUri;
static bool IsDataTrigger(XmlType type) => type.Name == nameof(DataTrigger) && type.NamespaceUri == XamlParser.MauiUri;
static bool IsMultiTrigger(XmlType type) => type.Name == nameof(MultiTrigger) && type.NamespaceUri == XamlParser.MauiUri;

static bool IsTypeExtension(ElementNode node, out ValueNode typeNameValueNode)
{
XmlName typeNameXmlName = new("", "TypeName");

if (node.XmlType.Name == nameof(TypeExtension)
&& node.XmlType.NamespaceUri == XamlParser.X2009Uri
&& node.Properties.ContainsKey(typeNameXmlName)
&& node.Properties[typeNameXmlName] is ValueNode valueNode
&& valueNode.Value is string)
{
typeNameValueNode = valueNode;
return true;
}

typeNameValueNode = null;
return false;
}

}

public void Visit(RootNode node, INode parentNode)
{
}

public void Visit(ListNode node, INode parentNode)
{
}
}
}
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/XamlLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ public static IResourceDictionary LoadResources(string xaml, IResourcesProvider
resources.Accept(new CreateValuesVisitor(visitorContext), null);
resources.Accept(new RegisterXNamesVisitor(visitorContext), null);
resources.Accept(new FillResourceDictionariesVisitor(visitorContext), null);
resources.Accept(new SimplifyTypeExtensionVisitor(), null);
resources.Accept(new ApplyPropertiesVisitor(visitorContext, true), null);

return visitorContext.Values[resources] as IResourceDictionary;
Expand All @@ -207,6 +208,7 @@ static void Visit(RootNode rootnode, HydrationContext visitorContext, bool useDe
rootnode.Accept(new CreateValuesVisitor(visitorContext), null);
rootnode.Accept(new RegisterXNamesVisitor(visitorContext), null);
rootnode.Accept(new FillResourceDictionariesVisitor(visitorContext), null);
rootnode.Accept(new SimplifyTypeExtensionVisitor(), null);
rootnode.Accept(new ApplyPropertiesVisitor(visitorContext, true), null);
}

Expand Down
58 changes: 58 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls.Core.UnitTests;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Dispatching;

using Microsoft.Maui.Graphics;
using Microsoft.Maui.UnitTests;
using NUnit.Framework;

namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui20818
{
public Maui20818()
{
InitializeComponent();
}

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

[TestFixture]
class Test
{
[SetUp]
public void Setup()
{
Application.SetCurrentApplication(new MockApplication());
DispatcherProvider.SetCurrent(new DispatcherProviderStub());
}

[TearDown] public void TearDown() => AppInfo.SetCurrent(null);

[Test]
public void TypeLiteralAndXTypeCanBeUsedInterchangeably([Values(false, true)] bool useCompiledXaml)
{
var page = new Maui20818(useCompiledXaml);

Assert.That((page.Resources["A"] as Style).TargetType, Is.EqualTo(typeof(Label)));
Assert.That((page.Resources["B"] as Style).TargetType, Is.EqualTo(typeof(Label)));

Assert.That(page.TriggerC.TargetType, Is.EqualTo(typeof(Label)));
Assert.That(page.TriggerD.TargetType, Is.EqualTo(typeof(Label)));
Assert.That(page.TriggerE.TargetType, Is.EqualTo(typeof(Label)));
Assert.That(page.TriggerF.TargetType, Is.EqualTo(typeof(Label)));
Assert.That(page.TriggerG.TargetType, Is.EqualTo(typeof(Label)));
Assert.That(page.TriggerH.TargetType, Is.EqualTo(typeof(Label)));
}
}
}
25 changes: 25 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui20818">
<ContentPage.Resources>
<Style x:Key="A" TargetType="Label" />
<Style x:Key="B" TargetType="{x:Type Label}" />
</ContentPage.Resources>

<Label>
<Label.Triggers>
<Trigger x:Name="TriggerC" TargetType="Label" Property="BackgroundColor" Value="Red" />
<DataTrigger x:Name="TriggerD" TargetType="Label" />
<MultiTrigger x:Name="TriggerE" TargetType="Label" />
</Label.Triggers>
</Label>

<Label>
<Label.Triggers>
<Trigger x:Name="TriggerF" TargetType="{x:Type Label}" Property="BackgroundColor" Value="Red" />
<DataTrigger x:Name="TriggerG" TargetType="{x:Type Label}" />
<MultiTrigger x:Name="TriggerH" TargetType="{x:Type Label}" />
</Label.Triggers>
</Label>
</ContentPage>

0 comments on commit 4306566

Please sign in to comment.