From d29d554f257984f206b2bb584e0bdac0ec6af9ad Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 24 Jan 2025 10:07:04 +0100 Subject: [PATCH] Fix style without selector not finding target type (#18026) * Add failing style test without selector * Fix XAML target type of style without selector * Address review * Throw for style without selector in ControlTheme #Conflicts: # tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs --- .../AvaloniaXamlIlSelectorTransformer.cs | 55 +++++++++++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../Xaml/StyleTests.cs | 1 + 3 files changed, 58 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index cfec681558..e0d234e6df 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -32,8 +32,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var pn = on.Children.OfType() .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); + // Missing selector, use the object's target type if available if (pn == null) + { + // We already went through this node + if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode metadataNode + && metadataNode.Value == on) + { + return node; + } + + if (FindStyleParentObject(on, context) is { } parentObjectNode) + { + return new AvaloniaXamlIlTargetTypeMetadataNode( + on, + new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + return node; + } if (pn.Values.Count != 1) throw new XamlSelectorsTransformException("Selector property should have exactly one value", @@ -195,6 +213,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers pn.Values[0] = selector; var templateType = GetLastTemplateTypeFromSelector(selector); + + // Empty selector, use the object's target type if available + if (selector == initialNode) + { + if (FindStyleParentObject(on, context) is { } parentObjectNode) + { + return new AvaloniaXamlIlTargetTypeMetadataNode( + on, + new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + + return node; + } var styleNode = new AvaloniaXamlIlTargetTypeMetadataNode(on, new XamlAstClrTypeReference(selector, selector.TargetType!, false), @@ -209,6 +241,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers }; } + private static XamlAstObjectNode? FindStyleParentObject(XamlAstNode styleNode, AstTransformationContext context) + { + var avaloniaTypes = context.GetAvaloniaTypes(); + + var parentNode = context + .ParentNodes() + .OfType() + .FirstOrDefault(n => !avaloniaTypes.Styles.IsAssignableFrom(n.Type.GetClrType())); + + if (parentNode is not null) + { + var parentType = parentNode.Type.GetClrType(); + + if (avaloniaTypes.StyledElement.IsAssignableFrom(parentType)) + return parentNode; + + if (avaloniaTypes.ControlTheme.IsAssignableFrom(parentType)) + throw new XamlTransformException("Cannot add a Style without selector to a ControlTheme.", styleNode); + } + + return null; + } + private static IXamlType? GetLastTemplateTypeFromSelector(XamlIlSelectorNode? node) { while (node is not null) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 09c9acc9c4..673d2b8b8d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -126,6 +126,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType UriKind { get; } public IXamlConstructor UriConstructor { get; } public IXamlType Style { get; } + public IXamlType Styles { get; } public IXamlType ControlTheme { get; } public IXamlType WindowTransparencyLevel { get; } public IXamlType IReadOnlyListOfT { get; } @@ -324,6 +325,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers UriKind = cfg.TypeSystem.GetType("System.UriKind"); UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind }); Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); + Styles = cfg.TypeSystem.GetType("Avalonia.Styling.Styles"); ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate"); IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 11f264762d..e3ac752504 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -15,6 +15,7 @@ using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { + [InvariantCulture] public class StyleTests : XamlTestBase { [Fact]