Browse Source

Do not defer resources with name registration on them (#14703)

* Do not defer resources with name registration on them

* Fix transformers order

* Make NameScopeRegistrationVisitor usage more clear

* Reuse NameScopeRegistrationVisitor

* Make NameScopeRegistrationVisitor usage more intuitive
pull/14793/head
Max Katz 2 years ago
committed by GitHub
parent
commit
aeafbf9d3c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  2. 39
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs
  3. 28
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
  4. 51
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NameScopeRegistrationVisitor.cs
  5. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

12
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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
void InsertBefore<T>(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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());
InsertBefore<DeferredContentTransformer>(
new AvaloniaXamlIlDeferredResourceTransformer()
);
// After everything else
InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(),
@ -82,6 +83,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlCompiledBindingsMetadataRemover()
);
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
new AvaloniaXamlIlDeferredResourceTransformer());
Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());

39
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 @@ internal class AvaloniaXamlIlControlTemplatePartsChecker : IXamlAstTransformer
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 @@ internal class AvaloniaXamlIlControlTemplatePartsChecker : IXamlAstTransformer
return dictionary;
}
private class TemplatePartVisitor : Dictionary<string, (IXamlType type, IXamlLineInfo line)>, IXamlAstVisitor
{
private int _metadataScopeLevel = 0;
private Stack<IXamlAstNode> _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--;
}
}
}
}

28
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 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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<IXamlPropertySetter>
@ -29,7 +28,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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<IXamlPropertySetter>
@ -45,7 +45,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
// 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<AdderSetter>

51
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<string, (IXamlType type, IXamlLineInfo line)>, IXamlAstVisitor
{
private readonly int _targetMetadataScopeLevel;
private readonly Stack<IXamlAstNode> _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--;
}
}
}

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -125,6 +125,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<Panel x:Name='MyPanel' x:Key='MyPanel' />
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)window.Resources;
Assert.False(resources.ContainsDeferredKey("MyPanel"));
Assert.True(resources.ContainsKey("MyPanel"));
Assert.IsType<Panel>(window.Find<Panel>("MyPanel"));
}
}
[Fact]
public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred()
{

Loading…
Cancel
Save