Skip to content

Commit

Permalink
[X] Pass the parent scopes to DataContext
Browse files Browse the repository at this point in the history
This allows correct x:Reference resolution in DataTemplates

- fixes #8149
  • Loading branch information
StephaneDelcroix committed Jun 27, 2022
1 parent 5fbfc26 commit aa770a6
Show file tree
Hide file tree
Showing 17 changed files with 204 additions and 31 deletions.
37 changes: 32 additions & 5 deletions src/Controls/src/Build.Tasks/NodeILExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,32 @@ static IEnumerable<Instruction> PushTargetProperty(FieldReference bpRef, Propert
yield break;
}

static IEnumerable<Instruction> PushNamescopes(INode node, ILContext context, ModuleDefinition module)
{
var scopes = new List<VariableDefinition>();
do
{

if (context.Scopes.TryGetValue(node, out var scope))
scopes.Add(scope.Item1);
node = node.Parent;
} while (node != null);


yield return Instruction.Create(OpCodes.Ldc_I4, scopes.Count);
yield return Instruction.Create(OpCodes.Newarr, module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")));

var i = 0;
foreach (var scope in scopes)
{
yield return Instruction.Create(OpCodes.Dup);
yield return Instruction.Create(OpCodes.Ldc_I4, i);
yield return Instruction.Create(OpCodes.Ldloc, scope);
yield return Instruction.Create(OpCodes.Stelem_Ref);
i++;
}
}

public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILContext context, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference declaringTypeReference = null)
{
var module = context.Body.Method.Module;
Expand Down Expand Up @@ -608,12 +634,13 @@ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILCo
foreach (var instruction in PushTargetProperty(bpRef, propertyRef, declaringTypeReference, module))
yield return instruction;

if (context.Scopes.TryGetValue(node, out var scope))
yield return Create(Ldloc, scope.Item1);
else
yield return Create(Ldnull);
foreach (var instruction in PushNamescopes(node, context, module))
yield return instruction;

yield return Create(Newobj, module.ImportCtorReference(("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "SimpleValueTargetProvider"), paramCount: 3));
yield return Create(Ldc_I4_0); //don't ask
yield return Create(Newobj, module.ImportCtorReference(
("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "SimpleValueTargetProvider"), paramCount: 4));

//store the provider so we can register it again with a different key
yield return Create(Dup);
var refProvider = new VariableDefinition(module.ImportReference(("mscorlib", "System", "Object")));
Expand Down
30 changes: 27 additions & 3 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,33 @@ static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext
Root = root,
XamlFilePath = parentContext.XamlFilePath,
};

//Instanciate nested class
var parentIl = parentContext.IL;
parentIl.Emit(OpCodes.Newobj, ctor);

//Copy the scopes over for x:Reference to work
//the scopes will be copied to fields of the anon type, the templateIL will copy them as VariableDef and populate context.Scopes
var i = 0;
foreach (var kvp in parentContext.Scopes)
{
//On the parentIL, copy the scope to a field
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
parentIl.Append(kvp.Value.Item1.LoadAs(module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")), module));
var fieldDefScope = new FieldDefinition($"_scope{i++}", FieldAttributes.Assembly, module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")));
anonType.Fields.Add(fieldDefScope);
parentIl.Emit(OpCodes.Stfld, fieldDefScope);

//On the templateIL, copy the field to a var, and populate the Scopes
templateIl.Emit(OpCodes.Ldarg_0);
var varDefScope = new VariableDefinition(module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")));
loadTemplate.Body.Variables.Add(varDefScope);
templateIl.Emit(OpCodes.Ldfld, fieldDefScope);
templateIl.Emit(OpCodes.Stloc, varDefScope);
templateContext.Scopes[kvp.Key] = new Tuple<VariableDefinition, IList<string>>(varDefScope, kvp.Value.Item2);
}

//inflate the template
node.Accept(new CreateObjectVisitor(templateContext), null);
node.Accept(new SetNamescopesAndRegisterNamesVisitor(templateContext), null);
node.Accept(new SetFieldVisitor(templateContext), null);
Expand All @@ -1588,9 +1615,6 @@ static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext
templateIl.Append(templateContext.Variables[node].LoadAs(module.TypeSystem.Object, module));
templateIl.Emit(OpCodes.Ret);

//Instanciate nested class
var parentIl = parentContext.IL;
parentIl.Emit(OpCodes.Newobj, ctor);

//Copy required local vars
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Microsoft.Maui.Controls.Xaml.XamlFilePathAttribute
~Microsoft.Maui.Controls.Xaml.FontImageExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.ImageSource
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.FindByName(string name) -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope scope) -> void
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.Add(System.Type type, object service) -> void
~Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider.GetService(System.Type serviceType) -> object
~Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver.XamlTypeResolver(System.Xml.IXmlNamespaceResolver namespaceResolver, System.Reflection.Assembly currentAssembly) -> void
Expand Down
16 changes: 12 additions & 4 deletions src/Controls/src/Xaml/XamlServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,15 @@ public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarg
{
readonly object[] objectAndParents;
readonly object targetProperty;
readonly INameScope scope;
readonly INameScope[] scopes;

[Obsolete("Use the other ctor")]
public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, INameScope scope)
: this(objectAndParents, targetProperty, new INameScope[] { scope }, false)
{
}

public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, INameScope[] scopes, bool notused)
{
if (objectAndParents == null)
throw new ArgumentNullException(nameof(objectAndParents));
Expand All @@ -123,7 +129,7 @@ public SimpleValueTargetProvider(object[] objectAndParents, object targetPropert

this.objectAndParents = objectAndParents;
this.targetProperty = targetProperty;
this.scope = scope;
this.scopes = scopes;
}

IEnumerable<object> IProvideParentValues.ParentObjects => objectAndParents;
Expand All @@ -133,8 +139,10 @@ public SimpleValueTargetProvider(object[] objectAndParents, object targetPropert
public object FindByName(string name)
{
object value;
if ((value = scope?.FindByName(name)) != null)
return value;
if (scopes!=null)
foreach(var scope in scopes)
if ((value = scope?.FindByName(name)) != null)
return value;

for (var i = 0; i < objectAndParents.Length; i++)
{
Expand Down
37 changes: 18 additions & 19 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui7744.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@
using Microsoft.Maui.Devices;
using NUnit.Framework;

namespace Microsoft.Maui.Controls.Xaml.UnitTests
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui7744 : ContentPage
{
public partial class Maui7744 : ContentPage
public Maui7744() => InitializeComponent();
public Maui7744(bool useCompiledXaml)
{
public Maui7744() => InitializeComponent();
public Maui7744(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}
//this stub will be replaced at compile time
}

[TestFixture]
class Test
{
[SetUp] public void Setup() => AppInfo.SetCurrent(new MockAppInfo());
[TearDown] public void TearDown() => AppInfo.SetCurrent(null);
[TestFixture]
class Test
{
[SetUp] public void Setup() => AppInfo.SetCurrent(new MockAppInfo());
[TearDown] public void TearDown() => AppInfo.SetCurrent(null);

[Test]
public void ConvertersAreExecutedWhileApplyingSetter([Values(false, true)] bool useCompiledXaml)
{
var page = new Maui7744(useCompiledXaml);
Assert.That(page.border0.StrokeShape, Is.TypeOf<RoundRectangle>());
Assert.That(page.border1.StrokeShape, Is.TypeOf<RoundRectangle>());
}
[Test]
public void ConvertersAreExecutedWhileApplyingSetter([Values(false, true)] bool useCompiledXaml)
{
var page = new Maui7744(useCompiledXaml);
Assert.That(page.border0.StrokeShape, Is.TypeOf<RoundRectangle>());
Assert.That(page.border1.StrokeShape, Is.TypeOf<RoundRectangle>());
}
}
}
18 changes: 18 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui8149"
x:Name="Self">

<local:Maui8149View>
<local:Maui8149View.ItemTemplate>
<DataTemplate>
<local:Maui8149Item>
<Label x:Name="label" Text="{Binding Source={x:Reference Self}}"/>
</local:Maui8149Item>
</DataTemplate>
</local:Maui8149View.ItemTemplate>
</local:Maui8149View>

</ContentView>
35 changes: 35 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls.Core.UnitTests;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Devices;
using NUnit.Framework;

namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui8149 : ContentView
{

public Maui8149() => InitializeComponent();

public Maui8149(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}

[TestFixture]
class Test
{
[SetUp] public void Setup() => AppInfo.SetCurrent(new MockAppInfo());
[TearDown] public void TearDown() => AppInfo.SetCurrent(null);

[Test]
public void NamescopeWithXamlC([Values(false, true)] bool useCompiledXaml)
{
if (useCompiledXaml)
MockCompiler.Compile(typeof(Maui8149));

var page = new Maui8149(useCompiledXaml);
Assert.That((page.Content as Maui8149View).Text, Is.EqualTo("Microsoft.Maui.Controls.Xaml.UnitTests.Maui8149"));
}
}
}
16 changes: 16 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149Item.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui8149Item"
x:Name="Self">

<!--
Note the 'x:Name="Self"
In this *minimal* repro, all Content is provided by the DataTemplate
-->

</ContentView>
9 changes: 9 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149Item.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui8149Item : ContentView
{
public Maui8149Item()
{
InitializeComponent();
}
}
10 changes: 10 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149View.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui8149View"
x:Name="Self">



</ContentView>
20 changes: 20 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui8149View.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui8149View : ContentView
{
private Controls.DataTemplate _itemTemplate;
public string Text { get; set; }
public Microsoft.Maui.Controls.DataTemplate ItemTemplate {
get => _itemTemplate;
set {
_itemTemplate = value;
Content = _itemTemplate.CreateContent() as View;
Text = ((Content as Maui8149Item).Content as Label).Text;
}
}

public Maui8149View()
{
InitializeComponent();
}
}

0 comments on commit aa770a6

Please sign in to comment.