Skip to content

Commit

Permalink
Do not defer resources with name registration on them (#14703)
Browse files Browse the repository at this point in the history
* Do not defer resources with name registration on them

* Fix transformers order

* Make NameScopeRegistrationVisitor usage more clear

* Reuse NameScopeRegistrationVisitor

* Make NameScopeRegistrationVisitor usage more intuitive
  • Loading branch information
maxkatz6 authored Mar 1, 2024
1 parent d1ecf5c commit aeafbf9
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
Expand Down Expand Up @@ -29,6 +30,10 @@ void InsertAfter<T>(params IXamlAstTransformer[] t)
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

Expand Down Expand Up @@ -69,10 +74,6 @@ void InsertBefore<T>(params IXamlAstTransformer[] t)
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());

InsertBefore<DeferredContentTransformer>(
new AvaloniaXamlIlDeferredResourceTransformer()
);

// After everything else
InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(),
Expand All @@ -82,6 +83,9 @@ void InsertBefore<T>(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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -99,40 +100,4 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod

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--;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,20 +17,19 @@ 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<IXamlPropertySetter>
{
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>
Expand All @@ -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<AdderSetter>
Expand Down
Original file line number Diff line number Diff line change
@@ -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--;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
<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()
{
Expand Down

0 comments on commit aeafbf9

Please sign in to comment.