diff --git a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs index 0c74fe87da59..261d61e08789 100644 --- a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs +++ b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs @@ -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)) { @@ -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]; } @@ -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) diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs index eea32f2ba5c2..10071addee24 100644 --- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs @@ -407,18 +407,7 @@ static IEnumerable 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); diff --git a/src/Controls/src/Build.Tasks/XamlCTask.cs b/src/Controls/src/Build.Tasks/XamlCTask.cs index cb41e4bcf08f..d2001ebd7306 100644 --- a/src/Controls/src/Build.Tasks/XamlCTask.cs +++ b/src/Controls/src/Build.Tasks/XamlCTask.cs @@ -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); diff --git a/src/Controls/src/Xaml/SimplifyTypeExtensionVisitor.cs b/src/Controls/src/Xaml/SimplifyTypeExtensionVisitor.cs new file mode 100644 index 000000000000..02a5139de95b --- /dev/null +++ b/src/Controls/src/Xaml/SimplifyTypeExtensionVisitor.cs @@ -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) + { + } + } +} diff --git a/src/Controls/src/Xaml/XamlLoader.cs b/src/Controls/src/Xaml/XamlLoader.cs index 65bd708c7867..09f533cf2a62 100644 --- a/src/Controls/src/Xaml/XamlLoader.cs +++ b/src/Controls/src/Xaml/XamlLoader.cs @@ -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; @@ -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); } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.cs new file mode 100644 index 000000000000..e2e23f2673c1 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.cs @@ -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))); + } + } +} diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.xaml new file mode 100644 index 000000000000..b5dea32c6bbd --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20818.xaml @@ -0,0 +1,25 @@ + + + +