Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/Controls/src/Build.Tasks/NodeILExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILCo
//Add a SimpleValueTargetProvider and register it as IProvideValueTarget, IReferenceProvider and IProvideParentValues
if (createAllServices
|| requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IProvideParentValues")), TypeRefComparer.Default)
|| requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IReferenceProvider")), TypeRefComparer.Default))
|| requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IReferenceProvider")), TypeRefComparer.Default)
|| requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IRootObjectProvider")), TypeRefComparer.Default))
{
alreadyContainsProvideValueTarget = true;
var pushParentIl = node.PushParentObjectsArray(context).ToList();
Expand All @@ -644,9 +645,25 @@ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILCo
foreach (var instruction in PushNamescopes(node, context, module))
yield return instruction;

yield return Create(Ldc_I4_0); //don't ask
//rootObject
if (context.Root is VariableDefinition rootVariable)
yield return Create(Ldloc, rootVariable);
else if (context.Root is FieldReference rootField)
{
yield return Create(Ldarg_0);
yield return Create(Ldfld, rootField);
}
else
yield return Create(Ldnull);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the runtime cost of passing an extra parameter that doesn't need to be evaluated is negligible

yield return Create(Newobj, module.ImportCtorReference(context.Cache,
("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "SimpleValueTargetProvider"), paramCount: 4));
type: ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "SimpleValueTargetProvider"),
parameterTypes: [
("mscorlib", "System", "Object[]"),
("mscorlib", "System", "Object"),
("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "INameScope[]"),
("mscorlib", "System", "Object"),
]));

//store the provider so we can register it again with a different key
yield return Create(Dup);
Expand All @@ -660,6 +677,12 @@ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILCo
yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
yield return Create(Ldloc, refProvider);
yield return Create(Callvirt, addService);

yield return Create(Dup); //Keep the serviceProvider on the stack
yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IRootObjectProvider")));
yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
yield return Create(Ldloc, refProvider);
yield return Create(Callvirt, addService);
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/Controls/src/Build.Tasks/XamlCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Maui.Controls.XamlC;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

namespace Microsoft.Maui.Controls.Build.Tasks;

Expand Down Expand Up @@ -38,8 +39,13 @@ public TypeDefinition Resolve(TypeReference typeReference) =>
public FieldReference GetOrAddFieldReference((ModuleDefinition module, string fieldRefKey) key, Func<(ModuleDefinition module, string fieldRefKey), FieldReference> valueFactory) =>
GetOrAdd(_fieldReferenceCache, key, valueFactory);

public TypeReference GetOrAddTypeReference(ModuleDefinition module, (string assemblyName, string clrNamespace, string typeName) type) =>
GetOrAdd(_typeReferenceCache, (module, type.ToString()), x => x.module.ImportReference(x.module.GetTypeDefinition(this, type)));
public TypeReference GetOrAddTypeReference(ModuleDefinition module, (string assemblyName, string clrNamespace, string typeName) type) => GetOrAdd(_typeReferenceCache, (module, type.ToString()), x =>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this allows specifying [] in the string tuple for type, so we can pick a methodreference with the right parameters if some of them are arrays

{
if (type.typeName.EndsWith("[]", StringComparison.InvariantCultureIgnoreCase))
return x.module.GetTypeDefinition(this, (type.assemblyName, type.clrNamespace, type.typeName.Substring(0, type.typeName.Length-2))).MakeArrayType();
else
return x.module.ImportReference(x.module.GetTypeDefinition(this, type));
});

public TypeReference GetOrAddTypeReference(ModuleDefinition module, string typeKey, Func<(ModuleDefinition module, string typeKey), TypeReference> valueFactory) =>
GetOrAdd(_typeReferenceCache, (module, typeKey), valueFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not modifying the publicAPI shipped files, so, is fine to include it in a SR?

~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
2 changes: 2 additions & 0 deletions src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase
~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void
~static Microsoft.Maui.Controls.Embedding.EmbeddingExtensions.UseMauiEmbeddedApp<TApp>(this Microsoft.Maui.Hosting.MauiAppBuilder builder, System.Func<System.IServiceProvider, TApp> implementationFactory) -> Microsoft.Maui.Hosting.MauiAppBuilder
Expand All @@ -8,3 +9,4 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Microsoft.Maui.Controls.Embedding.EmbeddingExtensions
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider
Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void
Microsoft.Maui.Controls.Xaml.ResourceDictionaryHelpers
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.RootObject.get -> object
~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, object rootObject) -> void
11 changes: 10 additions & 1 deletion src/Controls/src/Xaml/XamlServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,11 @@ public ValueTargetProvider(object targetObject, object targetProperty)
}
#nullable restore

public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget, IReferenceProvider
public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget, IReferenceProvider, IRootObjectProvider
Copy link

Copilot AI Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding IRootObjectProvider to a public class constitutes a breaking change in the public API; please ensure that this change is well-documented and validated in downstream usage.

Copilot uses AI. Check for mistakes.
{
readonly object[] objectAndParents;
readonly object targetProperty;
readonly object rootObject;
readonly INameScope[] scopes;

[Obsolete("Use the other ctor")]
Expand All @@ -147,7 +148,13 @@ public SimpleValueTargetProvider(object[] objectAndParents, object targetPropert
{
}

[Obsolete("Use the other ctor")]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to keep this around, like the other one, to make code compiled with a previous version of XamlC to keep working

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just try to add more details about the suggested alternative:
[Obsolete("This constructor is deprecated. Use the SimpleValueTargetProvider constructor with INameScope[] scopes and object rootObject instead.")]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is only used by generated code, so the obsolete message should never show up

public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, INameScope[] scopes, bool notused)
: this(objectAndParents, targetProperty, scopes, null)
{
}

public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, INameScope[] scopes, object rootObject)
{
if (objectAndParents == null)
throw new ArgumentNullException(nameof(objectAndParents));
Expand All @@ -157,8 +164,10 @@ public SimpleValueTargetProvider(object[] objectAndParents, object targetPropert
this.objectAndParents = objectAndParents;
this.targetProperty = targetProperty;
this.scopes = scopes;
this.rootObject = rootObject ?? objectAndParents[objectAndParents.Length - 1];
}

public object RootObject => rootObject;
IEnumerable<object> IProvideParentValues.ParentObjects => objectAndParents;
object IProvideValueTarget.TargetObject => objectAndParents[0];
object IProvideValueTarget.TargetProperty => targetProperty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
<Label x:Name="label1" Text="{local:SPMarkup1}"/>
<Label x:Name="label2" Text="{local:SPMarkup2}"/>
<Label x:Name="label3" Text="{local:SPMarkup3}"/>
<Label x:Name="label4" Text="{local:SPMarkup4}"/>
</StackLayout>
</ContentPage>
18 changes: 9 additions & 9 deletions src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public object ProvideValue(IServiceProvider serviceProvider)
if (serviceProvider.GetService(typeof(IXamlTypeResolver)) != null)
services.Add("IXamlTypeResolver");
if (serviceProvider.GetService(typeof(IRootObjectProvider)) != null)
services.Add("IRootObjectProvider");
services.Add($"IRootObjectProvider({((IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider))).RootObject.GetType().Name})");
if (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) != null)
services.Add("IXmlLineInfoProvider");
if (serviceProvider.GetService(typeof(IValueConverterProvider)) != null)
Expand All @@ -32,7 +32,7 @@ public object ProvideValue(IServiceProvider serviceProvider)
if (serviceProvider.GetService(typeof(IReferenceProvider)) != null)
services.Add("IReferenceProvider");

return string.Join(",", services);
return string.Join(", ", services);
}
}

Expand All @@ -48,6 +48,8 @@ public class SPMarkup2 : MarkupExtensionBase { }
[RequireService([typeof(IXmlLineInfoProvider)])]
public class SPMarkup3 : MarkupExtensionBase { }

[RequireService([typeof(IRootObjectProvider)])]
public class SPMarkup4 : MarkupExtensionBase { }


public partial class ServiceProviderTests : ContentPage
Expand All @@ -65,17 +67,15 @@ public class Tests
[SetUp] public void Setup() => DispatcherProvider.SetCurrent(new DispatcherProviderStub());
[TearDown] public void TearDown() => DispatcherProvider.SetCurrent(null);

[TestCase(true)]
public void TestServiceProviders(bool useCompiledXaml)
[Test]
public void TestServiceProviders([Values]bool useCompiledXaml)
{
var page = new ServiceProviderTests(useCompiledXaml);
MockCompiler.Compile(typeof(ServiceProviderTests));

//IValueConverterProvider is builtin for free
Assert.AreEqual(null, page.label0.Text);
Assert.AreEqual("IProvideValueTarget,IValueConverterProvider", page.label1.Text);
Assert.AreEqual("IProvideValueTarget,IValueConverterProvider,IReferenceProvider", page.label2.Text);
Assert.AreEqual("IXmlLineInfoProvider,IValueConverterProvider", page.label3.Text);
Assert.That(page.label1.Text, Does.Contain("IProvideValueTarget"));
Assert.That(page.label3.Text, Does.Contain("IXmlLineInfoProvider"));
Assert.That(page.label4.Text, Does.Contain("IRootObjectProvider(ServiceProviderTests)")); //https://github.com/dotnet/maui/issues/16881
}
}
}
Loading