Skip to content

Commit aeafbf9

Browse files
authored
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
1 parent d1ecf5c commit aeafbf9

File tree

5 files changed

+105
-47
lines changed

5 files changed

+105
-47
lines changed

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
@@ -29,6 +30,10 @@ void InsertAfter<T>(params IXamlAstTransformer[] t)
2930
void InsertBefore<T>(params IXamlAstTransformer[] t)
3031
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
3132

33+
void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t)
34+
=> Transformers.InsertRange(types
35+
.Select(type => Transformers.FindIndex(x => x.GetType() == type))
36+
.Min(), t);
3237

3338
// Before everything else
3439

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

72-
InsertBefore<DeferredContentTransformer>(
73-
new AvaloniaXamlIlDeferredResourceTransformer()
74-
);
75-
7677
// After everything else
7778
InsertBefore<NewObjectTransformer>(
7879
new AddNameScopeRegistration(),
@@ -82,6 +83,9 @@ void InsertBefore<T>(params IXamlAstTransformer[] t)
8283
new AvaloniaXamlIlCompiledBindingsMetadataRemover()
8384
);
8485

86+
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
87+
new AvaloniaXamlIlDeferredResourceTransformer());
88+
8589
Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
8690
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
8791
Transformers.Add(new AvaloniaXamlIlRootObjectScope());

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System.Collections.Generic;
44
using System.Linq;
5+
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
56
using XamlX;
67
using XamlX.Ast;
78
using XamlX.Transform;
@@ -25,7 +26,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod
2526
if (templateParts.Count == 0)
2627
return node;
2728

28-
var visitor = new TemplatePartVisitor();
29+
var visitor = new NameScopeRegistrationVisitor();
2930
node.VisitChildren(visitor);
3031

3132
foreach (var pair in templateParts)
@@ -99,40 +100,4 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod
99100

100101
return dictionary;
101102
}
102-
103-
private class TemplatePartVisitor : Dictionary<string, (IXamlType type, IXamlLineInfo line)>, IXamlAstVisitor
104-
{
105-
private int _metadataScopeLevel = 0;
106-
private Stack<IXamlAstNode> _parents = new();
107-
108-
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
109-
{
110-
if (_metadataScopeLevel == 1
111-
&& node is AvaloniaNameScopeRegistrationXamlIlNode nameScopeRegistration
112-
&& nameScopeRegistration.Name is XamlAstTextNode textNode)
113-
{
114-
this[textNode.Text] = (nameScopeRegistration.TargetType, textNode);
115-
}
116-
117-
return node;
118-
}
119-
120-
void IXamlAstVisitor.Push(IXamlAstNode node)
121-
{
122-
_parents.Push(node);
123-
if (node is NestedScopeMetadataNode)
124-
{
125-
_metadataScopeLevel++;
126-
}
127-
}
128-
129-
void IXamlAstVisitor.Pop()
130-
{
131-
var oldParent = _parents.Pop();
132-
if (oldParent is NestedScopeMetadataNode)
133-
{
134-
_metadataScopeLevel--;
135-
}
136-
}
137-
}
138103
}

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
45
using XamlX.Ast;
56
using XamlX.Emit;
67
using XamlX.IL;
@@ -16,20 +17,19 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod
1617
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
1718
return node;
1819

19-
if (!ShouldBeDeferred(pa.Values[1]))
20-
return node;
21-
2220
var types = context.GetAvaloniaTypes();
2321

24-
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
22+
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content"
23+
&& ShouldBeDeferred(pa.Values[1]))
2524
{
2625
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
2726
pa.PossibleSetters = new List<IXamlPropertySetter>
2827
{
2928
new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd),
3029
};
3130
}
32-
else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary))
31+
else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary)
32+
&& ShouldBeDeferred(pa.Values[1]))
3333
{
3434
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
3535
pa.PossibleSetters = new List<IXamlPropertySetter>
@@ -45,7 +45,23 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node)
4545
{
4646
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
4747
// At the moment it should be safe to not defer structs.
48-
return !node.Type.GetClrType().IsValueType;
48+
if (node.Type.GetClrType().IsValueType)
49+
{
50+
return false;
51+
}
52+
53+
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
54+
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
55+
// We set target scope level to 0, assuming that this resource node is a scope of itself.
56+
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
57+
targetMetadataScopeLevel: 0);
58+
node.Visit(nameRegistrationsVisitor);
59+
if (nameRegistrationsVisitor.Count > 0)
60+
{
61+
return false;
62+
}
63+
64+
return true;
4965
}
5066

5167
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Collections.Generic;
2+
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
3+
using XamlX.Ast;
4+
using XamlX.TypeSystem;
5+
6+
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
7+
8+
internal class NameScopeRegistrationVisitor : Dictionary<string, (IXamlType type, IXamlLineInfo line)>, IXamlAstVisitor
9+
{
10+
private readonly int _targetMetadataScopeLevel;
11+
private readonly Stack<IXamlAstNode> _parents = new();
12+
private int _metadataScopeLevel;
13+
14+
public NameScopeRegistrationVisitor(
15+
int initialMetadataScopeLevel = 0,
16+
int targetMetadataScopeLevel = 1)
17+
{
18+
_metadataScopeLevel = initialMetadataScopeLevel;
19+
_targetMetadataScopeLevel = targetMetadataScopeLevel;
20+
}
21+
22+
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
23+
{
24+
if (_metadataScopeLevel == _targetMetadataScopeLevel
25+
&& node is AvaloniaNameScopeRegistrationXamlIlNode nameScopeRegistration
26+
&& nameScopeRegistration.Name is XamlAstTextNode textNode)
27+
{
28+
this[textNode.Text] = (nameScopeRegistration.TargetType, textNode);
29+
}
30+
31+
return node;
32+
}
33+
34+
void IXamlAstVisitor.Push(IXamlAstNode node)
35+
{
36+
_parents.Push(node);
37+
if (node is NestedScopeMetadataNode)
38+
{
39+
_metadataScopeLevel++;
40+
}
41+
}
42+
43+
void IXamlAstVisitor.Pop()
44+
{
45+
var oldParent = _parents.Pop();
46+
if (oldParent is NestedScopeMetadataNode)
47+
{
48+
_metadataScopeLevel--;
49+
}
50+
}
51+
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,28 @@ public void Item_Is_Added_To_Window_Resources_As_Deferred()
125125
}
126126
}
127127

128+
[Fact]
129+
public void Named_Item_Is_Added_To_Resources_Should_Not_Be_Deferred()
130+
{
131+
// Since Named items can be accessed through the NameScope, we cannot delay their initialization.
132+
using (StyledWindow())
133+
{
134+
var xaml = @"
135+
<Window xmlns='https://github.com/avaloniaui'
136+
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
137+
<Window.Resources>
138+
<Panel x:Name='MyPanel' x:Key='MyPanel' />
139+
</Window.Resources>
140+
</Window>";
141+
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
142+
var resources = (ResourceDictionary)window.Resources;
143+
144+
Assert.False(resources.ContainsDeferredKey("MyPanel"));
145+
Assert.True(resources.ContainsKey("MyPanel"));
146+
Assert.IsType<Panel>(window.Find<Panel>("MyPanel"));
147+
}
148+
}
149+
128150
[Fact]
129151
public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred()
130152
{

0 commit comments

Comments
 (0)