Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>