diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index bcec4862543..71d77b12ed0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -29,6 +30,10 @@ void InsertAfter(params IXamlAstTransformer[] t) void InsertBefore(params IXamlAstTransformer[] t) => Transformers.InsertRange(Transformers.FindIndex(x => x is T), t); + void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t) + => Transformers.InsertRange(types + .Select(type => Transformers.FindIndex(x => x.GetType() == type)) + .Min(), t); // Before everything else @@ -69,10 +74,6 @@ void InsertBefore(params IXamlAstTransformer[] t) InsertAfter( new XDataTypeTransformer()); - InsertBefore( - new AvaloniaXamlIlDeferredResourceTransformer() - ); - // After everything else InsertBefore( new AddNameScopeRegistration(), @@ -82,6 +83,9 @@ void InsertBefore(params IXamlAstTransformer[] t) new AvaloniaXamlIlCompiledBindingsMetadataRemover() ); + InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) }, + new AvaloniaXamlIlDeferredResourceTransformer()); + Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer()); Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlRootObjectScope()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs index 3bfc5881d03..51029730f7c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; using XamlX; using XamlX.Ast; using XamlX.Transform; @@ -25,7 +26,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod if (templateParts.Count == 0) return node; - var visitor = new TemplatePartVisitor(); + var visitor = new NameScopeRegistrationVisitor(); node.VisitChildren(visitor); foreach (var pair in templateParts) @@ -99,40 +100,4 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod return dictionary; } - - private class TemplatePartVisitor : Dictionary, IXamlAstVisitor - { - private int _metadataScopeLevel = 0; - private Stack _parents = new(); - - IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node) - { - if (_metadataScopeLevel == 1 - && node is AvaloniaNameScopeRegistrationXamlIlNode nameScopeRegistration - && nameScopeRegistration.Name is XamlAstTextNode textNode) - { - this[textNode.Text] = (nameScopeRegistration.TargetType, textNode); - } - - return node; - } - - void IXamlAstVisitor.Push(IXamlAstNode node) - { - _parents.Push(node); - if (node is NestedScopeMetadataNode) - { - _metadataScopeLevel++; - } - } - - void IXamlAstVisitor.Pop() - { - var oldParent = _parents.Pop(); - if (oldParent is NestedScopeMetadataNode) - { - _metadataScopeLevel--; - } - } - } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 15fa8897abc..59bb016140c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; @@ -16,12 +17,10 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) return node; - if (!ShouldBeDeferred(pa.Values[1])) - return node; - var types = context.GetAvaloniaTypes(); - if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") + if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content" + && ShouldBeDeferred(pa.Values[1])) { pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List @@ -29,7 +28,8 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), }; } - else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary)) + else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary) + && ShouldBeDeferred(pa.Values[1])) { pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List @@ -45,7 +45,23 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node) { // XAML compiler is currently strict about value types, allowing them to be created only through converters. // At the moment it should be safe to not defer structs. - return !node.Type.GetClrType().IsValueType; + if (node.Type.GetClrType().IsValueType) + { + return false; + } + + // Do not defer resources, if it has any x:Name registration, as it cannot be delayed. + // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes. + // We set target scope level to 0, assuming that this resource node is a scope of itself. + var nameRegistrationsVisitor = new NameScopeRegistrationVisitor( + targetMetadataScopeLevel: 0); + node.Visit(nameRegistrationsVisitor); + if (nameRegistrationsVisitor.Count > 0) + { + return false; + } + + return true; } class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NameScopeRegistrationVisitor.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NameScopeRegistrationVisitor.cs new file mode 100644 index 00000000000..4acc43fdabc --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NameScopeRegistrationVisitor.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; + +internal class NameScopeRegistrationVisitor : Dictionary, IXamlAstVisitor +{ + private readonly int _targetMetadataScopeLevel; + private readonly Stack _parents = new(); + private int _metadataScopeLevel; + + public NameScopeRegistrationVisitor( + int initialMetadataScopeLevel = 0, + int targetMetadataScopeLevel = 1) + { + _metadataScopeLevel = initialMetadataScopeLevel; + _targetMetadataScopeLevel = targetMetadataScopeLevel; + } + + IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node) + { + if (_metadataScopeLevel == _targetMetadataScopeLevel + && node is AvaloniaNameScopeRegistrationXamlIlNode nameScopeRegistration + && nameScopeRegistration.Name is XamlAstTextNode textNode) + { + this[textNode.Text] = (nameScopeRegistration.TargetType, textNode); + } + + return node; + } + + void IXamlAstVisitor.Push(IXamlAstNode node) + { + _parents.Push(node); + if (node is NestedScopeMetadataNode) + { + _metadataScopeLevel++; + } + } + + void IXamlAstVisitor.Pop() + { + var oldParent = _parents.Pop(); + if (oldParent is NestedScopeMetadataNode) + { + _metadataScopeLevel--; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index b1b7a74dd33..75458ca2a1b 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -125,6 +125,28 @@ public void Item_Is_Added_To_Window_Resources_As_Deferred() } } + [Fact] + public void Named_Item_Is_Added_To_Resources_Should_Not_Be_Deferred() + { + // Since Named items can be accessed through the NameScope, we cannot delay their initialization. + using (StyledWindow()) + { + var xaml = @" + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)window.Resources; + + Assert.False(resources.ContainsDeferredKey("MyPanel")); + Assert.True(resources.ContainsKey("MyPanel")); + Assert.IsType(window.Find("MyPanel")); + } + } + [Fact] public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred() {