diff --git a/src/Controls/src/Build.Tasks/BuildException.cs b/src/Controls/src/Build.Tasks/BuildException.cs index 912b9917d308..e774bfff074d 100644 --- a/src/Controls/src/Build.Tasks/BuildException.cs +++ b/src/Controls/src/Build.Tasks/BuildException.cs @@ -80,6 +80,7 @@ class BuildExceptionCode public static BuildExceptionCode XStaticSyntax = new BuildExceptionCode("XFC", 0100, nameof(XStaticSyntax), ""); public static BuildExceptionCode XStaticResolution = new BuildExceptionCode("XFC", 0101, nameof(XStaticResolution), ""); public static BuildExceptionCode XDataTypeSyntax = new BuildExceptionCode("XFC", 0102, nameof(XDataTypeSyntax), ""); + public static BuildExceptionCode UnattributedMarkupType = new BuildExceptionCode("XC", 0103, nameof(UnattributedMarkupType), ""); //warning //Style, StyleSheets, Resources public static BuildExceptionCode StyleSheetSourceOrContent = new BuildExceptionCode("XFC", 0120, nameof(StyleSheetSourceOrContent), ""); diff --git a/src/Controls/src/Build.Tasks/CompiledValueProviders/SetterValueProvider.cs b/src/Controls/src/Build.Tasks/CompiledValueProviders/SetterValueProvider.cs index e4560ccd967d..ba31459f2d19 100644 --- a/src/Controls/src/Build.Tasks/CompiledValueProviders/SetterValueProvider.cs +++ b/src/Controls/src/Build.Tasks/CompiledValueProviders/SetterValueProvider.cs @@ -38,7 +38,7 @@ public IEnumerable ProvideValue(VariableDefinitionReference vardefr yield return instruction; //push the value - foreach (var instruction in ((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef: bpRef), boxValueTypes: true, unboxValueTypes: false)) + foreach (var instruction in ((ValueNode)valueNode).PushConvertedValue(context, bpRef, (requiredServices) => valueNode.PushServiceProvider(context, requiredServices, bpRef: bpRef), boxValueTypes: true, unboxValueTypes: false)) yield return instruction; //set the value diff --git a/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs b/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs index 543a8f4bbe80..b0ab313cd380 100644 --- a/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs +++ b/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs @@ -172,7 +172,8 @@ public void Visit(ElementNode node, INode parentNode) { //Purple Context.IL.Append(vnode.PushConvertedValue(Context, typeref, new ICustomAttributeProvider[] { typedef }, - node.PushServiceProvider(Context), false, true)); + (requiredServices) => node.PushServiceProvider(Context, requiredServices), + false, true)); Context.IL.Emit(OpCodes.Stloc, vardef); } else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && @@ -309,7 +310,8 @@ IEnumerable PushCtorArguments(MethodReference ctorinfo, ElementNode foreach (var instruction in vnode.PushConvertedValue(Context, parameter.ParameterType, new ICustomAttributeProvider[] { parameter, parameter.ParameterType.ResolveCached(Context.Cache) }, - enode.PushServiceProvider(Context), false, true)) + (requiredServices) => enode.PushServiceProvider(Context, requiredServices), + false, true)) yield return instruction; } } @@ -346,7 +348,8 @@ IEnumerable PushCtorXArguments(MethodReference factoryCtorInfo, Ele foreach (var instruction in vnode.PushConvertedValue(Context, parameter.ParameterType, new ICustomAttributeProvider[] { parameter, parameter.ParameterType.ResolveCached(Context.Cache) }, - enode.PushServiceProvider(Context), false, true)) + (requiredServices) => enode.PushServiceProvider(Context, requiredServices), + false, true)) yield return instruction; } } diff --git a/src/Controls/src/Build.Tasks/ErrorMessages.Designer.cs b/src/Controls/src/Build.Tasks/ErrorMessages.Designer.cs index e3316e659b3f..300f793996d6 100644 --- a/src/Controls/src/Build.Tasks/ErrorMessages.Designer.cs +++ b/src/Controls/src/Build.Tasks/ErrorMessages.Designer.cs @@ -382,6 +382,15 @@ internal class ErrorMessages { get { return ResourceManager.GetString("XStaticSyntax", resourceCulture); } + } + + /// + /// Looks up a localized string similar to Consider attributing the markup extension with [AcceptEmptyServiceProvider] or [RequireService].. + /// + internal static string UnattributedMarkupType { + get { + return ResourceManager.GetString("UnattributedMarkupType", resourceCulture); + } } /// diff --git a/src/Controls/src/Build.Tasks/ErrorMessages.resx b/src/Controls/src/Build.Tasks/ErrorMessages.resx index 92b100af60eb..255ce7fcac3e 100644 --- a/src/Controls/src/Build.Tasks/ErrorMessages.resx +++ b/src/Controls/src/Build.Tasks/ErrorMessages.resx @@ -243,6 +243,11 @@ x:DataType expects a string literal, an {{x:Type}} markup or {{x:Nul}l}. + + Consider attributing the markup extension "{0}" with [RequireService] or [AcceptEmptyServiceProvider] if it doesn't require any. + 0 is type name + + Undeclared xmlns prefix "{0}". 0 is the xmlns prefix diff --git a/src/Controls/src/Build.Tasks/NodeILExtensions.cs b/src/Controls/src/Build.Tasks/NodeILExtensions.cs index ec2a873b1fc1..4150f349bd47 100644 --- a/src/Controls/src/Build.Tasks/NodeILExtensions.cs +++ b/src/Controls/src/Build.Tasks/NodeILExtensions.cs @@ -98,7 +98,7 @@ public static bool CanConvertValue(this ValueNode node, ILContext context, TypeR public static IEnumerable PushConvertedValue(this ValueNode node, ILContext context, TypeReference targetTypeRef, IEnumerable attributeProviders, - IEnumerable pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) + Func> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) { TypeReference typeConverter = null; foreach (var attributeProvider in attributeProviders) @@ -119,7 +119,7 @@ public static bool CanConvertValue(this ValueNode node, ILContext context, TypeR } public static IEnumerable PushConvertedValue(this ValueNode node, ILContext context, FieldReference bpRef, - IEnumerable pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) + Func> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) { var module = context.Body.Method.Module; var targetTypeRef = bpRef.GetBindablePropertyType(context.Cache, node, module); @@ -142,7 +142,7 @@ static T TryFormat(Func func, IXmlLineInfo lineInfo, string str) } public static IEnumerable PushConvertedValue(this ValueNode node, ILContext context, - TypeReference targetTypeRef, TypeReference typeConverter, IEnumerable pushServiceProvider, + TypeReference targetTypeRef, TypeReference typeConverter, Func> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) { var module = context.Body.Method.Module; @@ -197,7 +197,10 @@ static T TryFormat(Func func, IXmlLineInfo lineInfo, string str) if (isExtendedConverter) { - foreach (var instruction in pushServiceProvider) + var requireServiceAttribute = typeConverter.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute")); + var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray(); + + foreach (var instruction in pushServiceProvider(requiredServiceType)) yield return instruction; } @@ -583,14 +586,12 @@ static IEnumerable PushNamescopes(INode node, ILContext context, Mo } } - public static IEnumerable PushServiceProvider(this INode node, ILContext context, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference declaringTypeReference = null) + public static IEnumerable PushServiceProvider(this INode node, ILContext context, TypeReference[] requiredServices, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference declaringTypeReference = null) { var module = context.Body.Method.Module; -#if NOSERVICEPROVIDER - yield return Instruction.Create (OpCodes.Ldnull); - yield break; -#endif + var createAllServices = requiredServices is null; + var alreadyContainsProvideValueTarget = false; var addService = module.ImportMethodReference(context.Cache, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "XamlServiceProvider"), methodName: "Add", @@ -601,43 +602,69 @@ public static IEnumerable PushServiceProvider(this INode node, ILCo yield return Create(Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "XamlServiceProvider"), parameterTypes: null)); - //Add a SimpleValueTargetProvider and register it as IProvideValueTarget and IReferenceProvider - var pushParentIl = node.PushParentObjectsArray(context).ToList(); - if (pushParentIl[pushParentIl.Count - 1].OpCode != Ldnull) + //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)) { - 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", "IProvideValueTarget"))); - yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true)); - - foreach (var instruction in pushParentIl) - yield return instruction; + alreadyContainsProvideValueTarget = true; + var pushParentIl = node.PushParentObjectsArray(context).ToList(); + if (pushParentIl[pushParentIl.Count - 1].OpCode != Ldnull) + { + 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", "IProvideValueTarget"))); + yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true)); - foreach (var instruction in PushTargetProperty(context, bpRef, propertyRef, declaringTypeReference, module)) - yield return instruction; + foreach (var instruction in pushParentIl) + yield return instruction; - foreach (var instruction in PushNamescopes(node, context, module)) - yield return instruction; + foreach (var instruction in PushTargetProperty(context, bpRef, propertyRef, declaringTypeReference, module)) + yield return instruction; - yield return Create(Ldc_I4_0); //don't ask - yield return Create(Newobj, module.ImportCtorReference(context.Cache, - ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "SimpleValueTargetProvider"), paramCount: 4)); + foreach (var instruction in PushNamescopes(node, context, module)) + yield return instruction; - //store the provider so we can register it again with a different key - yield return Create(Dup); - var refProvider = new VariableDefinition(module.ImportReference(context.Cache, ("mscorlib", "System", "Object"))); - context.Body.Variables.Add(refProvider); - yield return Create(Stloc, refProvider); - yield return Create(Callvirt, addService); + yield return Create(Ldc_I4_0); //don't ask + yield return Create(Newobj, module.ImportCtorReference(context.Cache, + ("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(context.Cache, ("mscorlib", "System", "Object"))); + context.Body.Variables.Add(refProvider); + yield return Create(Stloc, 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", "IReferenceProvider"))); + 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); + } + } + //Add an even simpler ValueTargetProvider and register it as IProvideValueTarget + if (!alreadyContainsProvideValueTarget && requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IProvideValueTarget")), TypeRefComparer.Default)) + { 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", "IReferenceProvider"))); + yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IProvideValueTarget"))); 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); + + if (node.Parent is IElementNode elementNode) + foreach (var instruction in context.Variables[elementNode].LoadAs(context.Cache, module.TypeSystem.Object, module)) + yield return instruction; + else + yield return Create(Ldnull); + + foreach (var instruction in PushTargetProperty(context, bpRef, propertyRef, declaringTypeReference, module)) + yield return instruction; + + yield return Create(Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "ValueTargetProvider"), paramCount: 2)); yield return Create(Callvirt, addService); } - //Add a XamlTypeResolver - if (node.NamespaceResolver != null) + //Add a IXamlTypeResolver + if (node.NamespaceResolver != null && createAllServices || requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IXamlTypeResolver")), TypeRefComparer.Default)) { yield return Create(Dup); //Duplicate the serviceProvider yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IXamlTypeResolver"))); @@ -662,7 +689,7 @@ public static IEnumerable PushServiceProvider(this INode node, ILCo yield return Create(Callvirt, addService); } - if (node is IXmlLineInfo) + if (node is IXmlLineInfo && createAllServices || requiredServices.Contains(module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IXmlLineInfoProvider")), TypeRefComparer.Default)) { yield return Create(Dup); //Duplicate the serviceProvider yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IXmlLineInfoProvider"))); @@ -672,6 +699,8 @@ public static IEnumerable PushServiceProvider(this INode node, ILCo yield return Create(Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml.Internals", "XmlLineInfoProvider"), parameterTypes: new[] { ("System.Xml.ReaderWriter", "System.Xml", "IXmlLineInfo") })); yield return Create(Callvirt, addService); } + + //and... we end up with the serviceProvider on the stack, as expected } } -} +} \ No newline at end of file diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs index 26e183912c10..bdea4f370800 100644 --- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs @@ -15,15 +15,15 @@ namespace Microsoft.Maui.Controls.Build.Tasks { class SetPropertiesVisitor : IXamlNodeVisitor { - static readonly IList skips = new List - { + static readonly IList skips = + [ XmlName.xKey, XmlName.xTypeArguments, XmlName.xArguments, XmlName.xFactoryMethod, XmlName.xName, XmlName.xDataType - }; + ]; public SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = false) { @@ -51,8 +51,7 @@ public bool IsResourceDictionary(ElementNode node) public void Visit(ValueNode node, INode parentNode) { //TODO support Label text as element - XmlName propertyName; - if (!TryGetPropertyName(node, parentNode, out propertyName)) + if (!TryGetPropertyName(node, parentNode, out XmlName propertyName)) { if (!IsCollectionItem(node, parentNode)) return; @@ -275,8 +274,7 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef vardefref.VariableDefinition = new VariableDefinition(module.ImportReference(genericArguments.First())); foreach (var instruction in context.Variables[node].LoadAs(context.Cache, markupExtension, module)) yield return instruction; - foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef)) - yield return instruction; + yield return Instruction.Create(OpCodes.Ldnull); //ArrayExtension does not require ServiceProvider yield return Instruction.Create(OpCodes.Callvirt, provideValue); if (arrayTypeRef != null) @@ -287,6 +285,12 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef out markupExtension, out genericArguments)) { var acceptEmptyServiceProvider = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null; + var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute")); + var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray(); + + if (!acceptEmptyServiceProvider && requireServiceAttribute == null) + context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType); + if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.BindingExtension" && (node.Properties == null || !node.Properties.ContainsKey(new XmlName("", "Source"))) //do not compile bindings if Source is set && bpRef != null //do not compile bindings if we're not gonna SetBinding @@ -304,9 +308,9 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef foreach (var instruction in context.Variables[node].LoadAs(context.Cache, markupExtension, module)) yield return instruction; if (acceptEmptyServiceProvider) - yield return Instruction.Create(OpCodes.Ldnull); + yield return Create(Ldnull); else - foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef)) + foreach (var instruction in node.PushServiceProvider(context, requiredServiceType, bpRef, propertyRef, propertyDeclaringTypeRef)) yield return instruction; yield return Instruction.Create(OpCodes.Callvirt, provideValue); yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition); @@ -314,6 +318,12 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef else if (context.Variables[node].VariableType.ImplementsInterface(context.Cache, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IMarkupExtension")))) { var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null; + var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute")); + var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray(); + + if (!acceptEmptyServiceProvider && requireServiceAttribute == null) + context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType); + var markupExtensionType = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IMarkupExtension"); vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object); foreach (var instruction in context.Variables[node].LoadAs(context.Cache, module.GetTypeDefinition(context.Cache, markupExtensionType), module)) @@ -321,7 +331,7 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef if (acceptEmptyServiceProvider) yield return Create(Ldnull); else - foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef)) + foreach (var instruction in node.PushServiceProvider(context, requiredServiceType, bpRef, propertyRef, propertyDeclaringTypeRef)) yield return instruction; yield return Create(Callvirt, module.ImportMethodReference(context.Cache, markupExtensionType, @@ -332,6 +342,12 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef else if (context.Variables[node].VariableType.ImplementsInterface(context.Cache, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IValueProvider")))) { var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null; + var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute")); + var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray(); + + if (!acceptEmptyServiceProvider && requireServiceAttribute == null) + context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType); + var valueProviderType = context.Variables[node].VariableType; //If the IValueProvider has a ProvideCompiledAttribute that can be resolved, shortcut this var compiledValueProviderName = valueProviderType?.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "ProvideCompiledAttribute"))?.ConstructorArguments?[0].Value as string; @@ -357,7 +373,7 @@ internal static string GetContentProperty(XamlCache cache, TypeReference typeRef if (acceptEmptyServiceProvider) yield return Create(Ldnull); else - foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef)) + foreach (var instruction in node.PushServiceProvider(context, requiredServiceType, bpRef, propertyRef, propertyDeclaringTypeRef)) yield return instruction; yield return Create(Callvirt, module.ImportMethodReference(context.Cache, valueProviderInterface, @@ -1253,7 +1269,8 @@ static IEnumerable SetValue(VariableDefinition parent, FieldReferen if (valueNode != null) { - foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef: bpRef), true, false)) + //FIXME which services are required here ? + foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, (requiredServices) => valueNode.PushServiceProvider(context, requiredServices, bpRef: bpRef), true, false)) yield return instruction; } else if (elementNode != null) @@ -1384,7 +1401,8 @@ static IEnumerable Set(VariableDefinition parent, string localName, if (valueNode != null) { - foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider[] { property, propertyType.ResolveCached(context.Cache) }, valueNode.PushServiceProvider(context, propertyRef: property), false, true)) + //FIXME which services are required here ? + foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider[] { property, propertyType.ResolveCached(context.Cache) }, (requiredServices) => valueNode.PushServiceProvider(context, requiredServices, propertyRef: property), false, true)) yield return instruction; if (parent.VariableType.IsValueType) yield return Instruction.Create(OpCodes.Call, propertySetterRef); diff --git a/src/Controls/src/Core/IMarkupExtension.cs b/src/Controls/src/Core/IMarkupExtension.cs index 6e9d12300398..1a2e9cab2742 100644 --- a/src/Controls/src/Core/IMarkupExtension.cs +++ b/src/Controls/src/Core/IMarkupExtension.cs @@ -18,4 +18,14 @@ public interface IMarkupExtension public sealed class AcceptEmptyServiceProviderAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class RequireServiceAttribute : Attribute + { + public RequireServiceAttribute(Type[] serviceTypes) + { + ServiceTypes = serviceTypes; + } + public Type[] ServiceTypes { get; } + } } \ No newline at end of file diff --git a/src/Controls/src/Core/LegacyLayouts/ConstraintExpression.cs b/src/Controls/src/Core/LegacyLayouts/ConstraintExpression.cs index a05780e21e70..a47c4307c2a9 100644 --- a/src/Controls/src/Core/LegacyLayouts/ConstraintExpression.cs +++ b/src/Controls/src/Core/LegacyLayouts/ConstraintExpression.cs @@ -8,6 +8,7 @@ namespace Microsoft.Maui.Controls.Compatibility { + [RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget)])] public class ConstraintExpression : IMarkupExtension { public ConstraintExpression() diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index c2084bb89b88..7739404c06fe 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -236,3 +236,6 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index a489446f682e..27033d726b52 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -249,3 +249,6 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 8a57f5667bfb..6c4af2037363 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -253,3 +253,6 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index a318e94a88a2..01f1df0df55f 100644 --- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -213,3 +213,6 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index fa1ca57b7941..72f990a1fb38 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -245,3 +245,6 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index da04ee53eaeb..59be19e184c3 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt @@ -207,3 +207,6 @@ Microsoft.Maui.Controls.PlatformPointerEventArgs *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 0e8dc0530411..96ad7b67248e 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -128,6 +128,9 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.WebView.UserAgent.get -> string ~Microsoft.Maui.Controls.WebView.UserAgent.set -> void +Microsoft.Maui.Controls.Xaml.RequireServiceAttribute +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] ~override Microsoft.Maui.Controls.ImageButton.OnPropertyChanged(string propertyName = null) -> void ~override Microsoft.Maui.Controls.LayoutOptions.Equals(object obj) -> bool ~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool diff --git a/src/Controls/src/Core/ReferenceTypeConverter.cs b/src/Controls/src/Core/ReferenceTypeConverter.cs index 5dad8b6ecd35..e42452b36797 100644 --- a/src/Controls/src/Core/ReferenceTypeConverter.cs +++ b/src/Controls/src/Core/ReferenceTypeConverter.cs @@ -10,6 +10,7 @@ namespace Microsoft.Maui.Controls { /// + [RequireService([typeof(IReferenceProvider), typeof(IProvideParentValues)])] public sealed class ReferenceTypeConverter : TypeConverter, IExtendedTypeConverter { diff --git a/src/Controls/src/Core/Setter.cs b/src/Controls/src/Core/Setter.cs index f21b1f8b863e..6634cb006288 100644 --- a/src/Controls/src/Core/Setter.cs +++ b/src/Controls/src/Core/Setter.cs @@ -11,6 +11,9 @@ namespace Microsoft.Maui.Controls /// [ContentProperty(nameof(Value))] [ProvideCompiled("Microsoft.Maui.Controls.XamlC.SetterValueProvider")] + [RequireService( + [typeof(IValueConverterProvider), + typeof(IXmlLineInfoProvider)])] public sealed class Setter : IValueProvider { /// diff --git a/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs index 3a37e23a734f..41f78cab7cc4 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs @@ -6,6 +6,11 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Default))] + [RequireService( + [typeof(IProvideValueTarget), + typeof(IValueConverterProvider), + typeof(IXmlLineInfoProvider), + typeof(IConverterOptions)])] public class AppThemeBindingExtension : IMarkupExtension { object _default; diff --git a/src/Controls/src/Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/DynamicResourceExtension.cs index bd703c1c0813..aa73f39ab195 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -4,6 +4,7 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Key))] + [RequireService([typeof(IXmlLineInfoProvider)])] public sealed class DynamicResourceExtension : IMarkupExtension { public string Key { get; set; } diff --git a/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs index a70921a84e7d..3fa9bcac4545 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs @@ -8,6 +8,11 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Default))] + [RequireService( + [typeof(IProvideValueTarget), + typeof(IValueConverterProvider), + typeof(IXmlLineInfoProvider), + typeof(IConverterOptions)])] public class OnIdiomExtension : IMarkupExtension { // See Device.Idiom diff --git a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs index a01e80d67a5a..608c178f91a3 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -7,6 +7,11 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Default))] + [RequireService( + [typeof(IProvideValueTarget), + typeof(IValueConverterProvider), + typeof(IXmlLineInfoProvider), + typeof(IConverterOptions)])] public class OnPlatformExtension : IMarkupExtension { static object s_notset = new object(); diff --git a/src/Controls/src/Xaml/MarkupExtensions/ReferenceExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/ReferenceExtension.cs index 4832a38f2213..2ef63ff043e1 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/ReferenceExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/ReferenceExtension.cs @@ -6,6 +6,7 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Name))] + [RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget)])] public class ReferenceExtension : IMarkupExtension { public string Name { get; set; } diff --git a/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs index fb6ae5e59300..b8d5a38d6b57 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -6,6 +6,7 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Key))] + [RequireService([typeof(IXmlLineInfoProvider), typeof(IProvideParentValues)])] public sealed class StaticResourceExtension : IMarkupExtension { public string Key { get; set; } diff --git a/src/Controls/src/Xaml/MarkupExtensions/StyleSheetExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/StyleSheetExtension.cs index 0c33b80ba3e0..ca81a17af4b6 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/StyleSheetExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/StyleSheetExtension.cs @@ -8,6 +8,7 @@ namespace Microsoft.Maui.Controls.Xaml { [ContentProperty(nameof(Style))] [ProvideCompiled("Microsoft.Maui.Controls.XamlC.StyleSheetProvider")] + [RequireService([typeof(IXmlLineInfoProvider), typeof(IRootObjectProvider)])] public sealed class StyleSheetExtension : IValueProvider { public string Style { get; set; } diff --git a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt index 0e88c4f7acfe..56eafad6e1b2 100644 --- a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable ~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.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void diff --git a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 0e88c4f7acfe..d2d847adc1fe 100644 --- a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider +Microsoft.Maui.Controls.Xaml.Internals.ValueTargetProvider.ValueTargetProvider(object! targetObject, object! targetProperty) -> void ~Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider.SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, Microsoft.Maui.Controls.Internals.INameScope[] scopes, bool notused) -> void diff --git a/src/Controls/src/Xaml/XamlServiceProvider.cs b/src/Controls/src/Xaml/XamlServiceProvider.cs index 23bf38738b32..2876d349f770 100644 --- a/src/Controls/src/Xaml/XamlServiceProvider.cs +++ b/src/Controls/src/Xaml/XamlServiceProvider.cs @@ -11,6 +11,8 @@ public class XamlServiceProvider : IServiceProvider { readonly Dictionary services = new Dictionary(); + static IValueConverterProvider defaultValueConverterProvider = new ValueConverterProvider(); + internal XamlServiceProvider(INode node, HydrationContext context) { if (context != null && node != null && node.Parent != null && context.Values.TryGetValue(node.Parent, out object targetObject)) @@ -26,10 +28,10 @@ internal XamlServiceProvider(INode node, HydrationContext context) if (node is IXmlLineInfo xmlLineInfo) IXmlLineInfoProvider = new XmlLineInfoProvider(xmlLineInfo); - IValueConverterProvider = new ValueConverterProvider(); + IValueConverterProvider = defaultValueConverterProvider; } - public XamlServiceProvider() => IValueConverterProvider = new ValueConverterProvider(); + public XamlServiceProvider() => IValueConverterProvider = defaultValueConverterProvider; internal IProvideValueTarget IProvideValueTarget { @@ -108,6 +110,22 @@ IEnumerable IProvideParentValues.ParentObjects } } +#nullable enable + public class ValueTargetProvider : IProvideValueTarget + { + private object targetObject; + private object targetProperty; + + public ValueTargetProvider(object targetObject, object targetProperty) + { + this.targetObject = targetObject; + this.targetProperty = targetProperty; + } + object IProvideValueTarget.TargetObject => targetObject; + object IProvideValueTarget.TargetProperty => targetProperty; + } +#nullable restore + public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget, IReferenceProvider { readonly object[] objectAndParents; diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Bz34037.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Bz34037.xaml.cs index 8a3272ea5f0a..3a93822f0594 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Bz34037.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Bz34037.xaml.cs @@ -6,6 +6,7 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests { + [AcceptEmptyServiceProvider] public class Bz34037Converter0 : IValueConverter, IMarkupExtension { public static int Invoked { get; set; } @@ -27,7 +28,7 @@ public object ProvideValue(IServiceProvider serviceProvider) return new Bz34037Converter0(); } } - + [AcceptEmptyServiceProvider] public class Bz34037Converter1 : IValueConverter, IMarkupExtension { public static int Invoked { get; set; } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Bz53275.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Bz53275.xaml.cs index a96539a29b35..2492b4d4447b 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Bz53275.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Bz53275.xaml.cs @@ -5,6 +5,7 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests { + [RequireService([typeof(IProvideValueTarget)])] public class TargetPropertyExtension : IMarkupExtension { public object ProvideValue(IServiceProvider serviceProvider) diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Gh2171.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Gh2171.xaml.cs index bd72e9bcff51..40bb4cb75d39 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Gh2171.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Gh2171.xaml.cs @@ -32,6 +32,7 @@ public void ParsingNestedMarkups(bool useCompiledXaml) } } + [AcceptEmptyServiceProvider] public class Gh2171Extension : IMarkupExtension { public string Foo { get; set; } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Gh4760.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Gh4760.xaml.cs index 9973e140e8ca..070fc8ba43b2 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Gh4760.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Gh4760.xaml.cs @@ -12,6 +12,7 @@ public abstract class Gh4760Base : IMarkupExtension ProvideValue(serviceProvider); } + [AcceptEmptyServiceProvider] public class Gh4760MultiplyExtension : Gh4760Base { public double Base { get; set; } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Gh9212.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Gh9212.xaml.cs index f4e2e386bc96..f5cca1cdde1c 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Gh9212.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Gh9212.xaml.cs @@ -9,6 +9,7 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests { [ContentProperty(nameof(Text))] + [AcceptEmptyServiceProvider] public class Gh9212MarkupExtension : IMarkupExtension { public string Text { get; set; } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported005.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported005.xaml.cs index 27a6ecdf27d5..0f0d57fc0642 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported005.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported005.xaml.cs @@ -5,7 +5,8 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests { using Constraint = Microsoft.Maui.Controls.Compatibility.Constraint; using RelativeLayout = Microsoft.Maui.Controls.Compatibility.RelativeLayout; - + + [RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget)])] public abstract class Unreported005RelativeToView : IMarkupExtension { protected Unreported005RelativeToView() diff --git a/src/Controls/tests/Xaml.UnitTests/MarkupExtensionTests.cs b/src/Controls/tests/Xaml.UnitTests/MarkupExtensionTests.cs index f0ddb09f805e..a5dfb6c8d224 100644 --- a/src/Controls/tests/Xaml.UnitTests/MarkupExtensionTests.cs +++ b/src/Controls/tests/Xaml.UnitTests/MarkupExtensionTests.cs @@ -15,6 +15,7 @@ public object ProvideValue(IServiceProvider serviceProvider) } } + [AcceptEmptyServiceProvider] public class AppendMarkupExtension : IMarkupExtension { public object Value0 { get; set; } diff --git a/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml b/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml new file mode 100644 index 000000000000..124215588d40 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml.cs b/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml.cs new file mode 100644 index 000000000000..0ceb9c389aa0 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/ServiceProviderTests.xaml.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Dispatching; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.UnitTests; +using NUnit.Framework; +using AbsoluteLayoutFlags = Microsoft.Maui.Layouts.AbsoluteLayoutFlags; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public class MarkupExtensionBase : IMarkupExtension +{ + public object ProvideValue(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + return null; + var services = new List (); + if (serviceProvider.GetService(typeof(IProvideValueTarget)) != null) + services.Add("IProvideValueTarget"); + if (serviceProvider.GetService(typeof(IXamlTypeResolver)) != null) + services.Add("IXamlTypeResolver"); + if (serviceProvider.GetService(typeof(IRootObjectProvider)) != null) + services.Add("IRootObjectProvider"); + if (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) != null) + services.Add("IXmlLineInfoProvider"); + if (serviceProvider.GetService(typeof(IValueConverterProvider)) != null) + services.Add("IValueConverterProvider"); + if (serviceProvider.GetService(typeof(IProvideParentValues)) != null) + services.Add("IProvideParentValues"); + if (serviceProvider.GetService(typeof(IReferenceProvider)) != null) + services.Add("IReferenceProvider"); + + return string.Join(",", services); + } +} + +[AcceptEmptyServiceProvider] +public class SPMarkup0 : MarkupExtensionBase { } + +[RequireService([typeof(IProvideValueTarget)])] +public class SPMarkup1 : MarkupExtensionBase { } + +[RequireService([typeof(IProvideParentValues)])] +public class SPMarkup2 : MarkupExtensionBase { } + +[RequireService([typeof(IXmlLineInfoProvider)])] +public class SPMarkup3 : MarkupExtensionBase { } + + + +public partial class ServiceProviderTests : ContentPage +{ + public ServiceProviderTests() => InitializeComponent(); + + public ServiceProviderTests(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + 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) + { + 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); + } + } +} diff --git a/src/Controls/tests/Xaml.UnitTests/TypeExtension.xaml.cs b/src/Controls/tests/Xaml.UnitTests/TypeExtension.xaml.cs index c1a01e626f5e..b2da2165920c 100644 --- a/src/Controls/tests/Xaml.UnitTests/TypeExtension.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/TypeExtension.xaml.cs @@ -17,6 +17,7 @@ public enum NavigationOperation } [ContentProperty(nameof(Operation))] + [AcceptEmptyServiceProvider] public class NavigateExtension : IMarkupExtension { public NavigationOperation Operation { get; set; } diff --git a/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj b/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj index cdc562972f47..97de84ad961d 100644 --- a/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj +++ b/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj @@ -7,7 +7,7 @@ Microsoft.Maui.TestUtils.DeviceTests.Runners $(NoWarn);CA1416 - $(WarningsNotAsErrors);XC0022;XC0023 + $(WarningsNotAsErrors);XC0022;XC0023;XC0103