summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-09-08 20:51:01 +0200
committerJason Smith <jason.smith@xamarin.com>2016-09-08 11:51:01 -0700
commit1f84a4955c93544192f44ba6af5d3593554dc116 (patch)
treecb5006606a772b01fcc3b16cf33c9fd537833a19
parent3b7d798fdda51a669683ed7d5c3770ebf3adfa77 (diff)
downloadxamarin-forms-1f84a4955c93544192f44ba6af5d3593554dc116.tar.gz
xamarin-forms-1f84a4955c93544192f44ba6af5d3593554dc116.tar.bz2
xamarin-forms-1f84a4955c93544192f44ba6af5d3593554dc116.zip
[XamlC] replace the runtime type check by compiletime (#334)
-rw-r--r--Xamarin.Forms.Build.Tasks/NodeILExtensions.cs2
-rw-r--r--Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs551
-rw-r--r--Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs12
-rw-r--r--Xamarin.Forms.Core/ConstraintExpression.cs9
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/GenericCollections.xaml.cs5
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml12
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml.cs2
7 files changed, 348 insertions, 245 deletions
diff --git a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
index 35016694..3902c71b 100644
--- a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
+++ b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
@@ -396,7 +396,7 @@ namespace Xamarin.Forms.Build.Tasks
#if NOSERVICEPROVIDER
yield return Instruction.Create (OpCodes.Ldnull);
yield break;
- #endif
+#endif
var ctorinfo = typeof (XamlServiceProvider).GetConstructor(new Type[] { });
var ctor = module.Import(ctorinfo);
diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
index 5e11fe1d..462a8962 100644
--- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
+++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
@@ -3,9 +3,11 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
+
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
+
using Xamarin.Forms.Internals;
using Xamarin.Forms.Xaml;
@@ -15,7 +17,7 @@ namespace Xamarin.Forms.Build.Tasks
{
static int dtcount;
- readonly IList<XmlName> skips = new List<XmlName>
+ static readonly IList<XmlName> skips = new List<XmlName>
{
XmlName.xKey,
XmlName.xTypeArguments,
@@ -32,21 +34,12 @@ namespace Xamarin.Forms.Build.Tasks
}
public ILContext Context { get; }
+ public bool StopOnResourceDictionary { get; }
+ public bool VisitChildrenFirst { get; } = true;
+ public bool StopOnDataTemplate { get; } = true;
ModuleDefinition Module { get; }
- public bool VisitChildrenFirst
- {
- get { return true; }
- }
-
- public bool StopOnDataTemplate
- {
- get { return true; }
- }
-
- public bool StopOnResourceDictionary { get; }
-
public void Visit(ValueNode node, INode parentNode)
{
//TODO support Label text as element
@@ -70,7 +63,7 @@ namespace Xamarin.Forms.Build.Tasks
if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" &&
propertyName.LocalName == "Ignorable")
return;
- SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
+ Context.IL.Append(SetPropertyValue(Context.Variables [(IElementNode)parentNode], propertyName, node, Context, node));
}
public void Visit(MarkupNode node, INode parentNode)
@@ -107,7 +100,7 @@ namespace Xamarin.Forms.Build.Tasks
if (propertyName == XmlName._CreateContent)
SetDataTemplate((IElementNode)parentNode, node, Context, node);
else
- SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
+ Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
}
else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
{
@@ -135,7 +128,7 @@ namespace Xamarin.Forms.Build.Tasks
var name = new XmlName(node.NamespaceURI, contentProperty);
if (skips.Contains(name))
return;
- SetPropertyValue(Context.Variables[(IElementNode)parentNode], name, node, Context, node);
+ Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], name, node, Context, node));
}
}
else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
@@ -299,255 +292,355 @@ namespace Xamarin.Forms.Build.Tasks
}
}
- public static void SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode,
- ILContext context, IXmlLineInfo iXmlLineInfo)
+ public static IEnumerable<Instruction> SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode, ILContext context, IXmlLineInfo iXmlLineInfo)
{
- var elementType = parent.VariableType;
- var localName = propertyName.LocalName;
var module = context.Body.Method.Module;
- var br = Instruction.Create(OpCodes.Nop);
+
+ var localName = propertyName.LocalName;
TypeReference declaringTypeReference;
- var handled = false;
//If it's an attached BP, update elementType and propertyName
- GetNameAndTypeRef(ref elementType, propertyName.NamespaceURI, ref localName, context, iXmlLineInfo);
+ var bpOwnerType = parent.VariableType;
+ GetNameAndTypeRef(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, iXmlLineInfo);
+ FieldReference bpRef = bpOwnerType.GetField(fd => fd.Name == localName + "Property" &&
+ fd.IsStatic &&
+ fd.IsPublic, out declaringTypeReference);
+ if (bpRef != null) {
+ bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference));
+ bpRef.FieldType = module.Import(bpRef.FieldType);
+ }
//If the target is an event, connect
+ if (CanConnectEvent(parent, localName))
+ return ConnectEvent(parent, localName, valueNode, iXmlLineInfo, context);
+
+ //If Value is DynamicResource, SetDynamicResource
+ if (CanSetDynamicResource(bpRef, valueNode, context))
+ return SetDynamicResource(parent, bpRef, valueNode as IElementNode, iXmlLineInfo, context);
+
+ //If Value is a BindingBase and target is a BP, SetBinding
+ if (CanSetBinding(bpRef, valueNode, context))
+ return SetBinding(parent, bpRef, valueNode as IElementNode, iXmlLineInfo, context);
+
+ //If it's a BP, SetValue ()
+ if (CanSetValue(bpRef, valueNode, iXmlLineInfo, context))
+ return SetValue(parent, bpRef, valueNode, iXmlLineInfo, context);
+
+ //If it's a property, set it
+ if (CanSet(parent, localName, valueNode, context))
+ return Set(parent, localName, valueNode, iXmlLineInfo, context);
+
+ //If it's an already initialized property, add to it
+ if (CanAdd(parent, localName, valueNode, context))
+ return Add(parent, localName, valueNode, iXmlLineInfo, context);
+
+ throw new XamlParseException($"No property, bindable property, or event found for '{localName}'", iXmlLineInfo);
+ }
+
+ static bool CanConnectEvent(VariableDefinition parent, string localName)
+ {
+ return parent.VariableType.GetEvent(ed => ed.Name == localName) != null;
+ }
+
+ static IEnumerable<Instruction> ConnectEvent(VariableDefinition parent, string localName, INode valueNode, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var elementType = parent.VariableType;
+ var module = context.Body.Method.Module;
+ var eventinfo = elementType.GetEvent(ed => ed.Name == localName);
+
// IL_0007: ldloc.0
// IL_0008: ldarg.0
//
// IL_0009: ldftn instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
- //OR, if the handler is virtual
+//OR, if the handler is virtual
// IL_000x: ldarg.0
// IL_0009: ldvirtftn instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
//
// IL_000f: newobj instance void class [mscorlib]System.EventHandler::'.ctor'(object, native int)
// IL_0014: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::add_Clicked(class [mscorlib]System.EventHandler)
- var eventinfo = elementType.GetEvent(ed => ed.Name == localName);
- if (eventinfo != null)
- {
- var value = ((ValueNode)valueNode).Value;
+ var value = ((ValueNode)valueNode).Value;
- context.IL.Emit(OpCodes.Ldloc, parent);
- if (context.Root is VariableDefinition)
- context.IL.Emit(OpCodes.Ldloc, context.Root as VariableDefinition);
- else if (context.Root is FieldDefinition)
- {
- context.IL.Emit(OpCodes.Ldarg_0);
- context.IL.Emit(OpCodes.Ldfld, context.Root as FieldDefinition);
- }
- else
- throw new InvalidProgramException();
- var declaringType = context.Body.Method.DeclaringType;
- if (declaringType.IsNested)
- declaringType = declaringType.DeclaringType;
- var handler = declaringType.AllMethods().FirstOrDefault(md => md.Name == value as string);
- if (handler == null)
- {
- throw new XamlParseException(
- string.Format("EventHandler \"{0}\" not found in type \"{1}\"", value, context.Body.Method.DeclaringType.FullName),
- iXmlLineInfo);
- }
- if (handler.IsVirtual)
- {
- context.IL.Emit(OpCodes.Ldarg_0);
- context.IL.Emit(OpCodes.Ldvirtftn, handler);
- }
- else
- context.IL.Emit(OpCodes.Ldftn, handler);
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
+ if (context.Root is VariableDefinition)
+ yield return Instruction.Create(OpCodes.Ldloc, context.Root as VariableDefinition);
+ else if (context.Root is FieldDefinition) {
+ yield return Instruction.Create(OpCodes.Ldarg_0);
+ yield return Instruction.Create(OpCodes.Ldfld, context.Root as FieldDefinition);
+ } else
+ throw new InvalidProgramException();
+ var declaringType = context.Body.Method.DeclaringType;
+ while (declaringType.IsNested)
+ declaringType = declaringType.DeclaringType;
+ var handler = declaringType.AllMethods().FirstOrDefault(md => md.Name == value as string);
+ if (handler == null)
+ throw new XamlParseException($"EventHandler \"{value}\" not found in type \"{context.Body.Method.DeclaringType.FullName}\"", iXmlLineInfo);
+ if (handler.IsVirtual) {
+ yield return Instruction.Create(OpCodes.Ldarg_0);
+ yield return Instruction.Create(OpCodes.Ldvirtftn, handler);
+ } else
+ yield return Instruction.Create(OpCodes.Ldftn, handler);
+
+ //FIXME: eventually get the right ctor instead fo the First() one, just in case another one could exists (not even sure it's possible).
+ var ctor = module.Import(eventinfo.EventType.Resolve().GetConstructors().First());
+ ctor = ctor.ResolveGenericParameters(eventinfo.EventType, module);
+ yield return Instruction.Create(OpCodes.Newobj, module.Import(ctor));
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(eventinfo.AddMethod));
+ }
- //FIXME: eventually get the right ctor instead fo the First() one, just in case another one could exists (not even sure it's possible).
- var ctor = module.Import(eventinfo.EventType.Resolve().GetConstructors().First());
- ctor = ctor.ResolveGenericParameters(eventinfo.EventType, module);
- context.IL.Emit(OpCodes.Newobj, module.Import(ctor));
- context.IL.Emit(OpCodes.Callvirt, module.Import(eventinfo.AddMethod));
- return;
- }
+ static bool CanSetDynamicResource(FieldReference bpRef, INode valueNode, ILContext context)
+ {
+ if (bpRef == null)
+ return false;
+ var elementNode = valueNode as IElementNode;
+ if (elementNode == null)
+ return false;
+
+ VariableDefinition varValue;
+ if (!context.Variables.TryGetValue(valueNode as IElementNode, out varValue))
+ return false;
+ return varValue.VariableType.FullName == typeof(DynamicResource).FullName;
+ }
- FieldReference bpRef = elementType.GetField(fd => fd.Name == localName + "Property" && fd.IsStatic && fd.IsPublic,
- out declaringTypeReference);
- if (bpRef != null)
- {
- bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference));
- bpRef.FieldType = module.Import(bpRef.FieldType);
- }
+ static IEnumerable<Instruction> SetDynamicResource(VariableDefinition parent, FieldReference bpRef, IElementNode elementNode, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ var varValue = context.Variables [elementNode];
+ var setDynamicResource = module.Import(typeof(IDynamicResourceHandler)).Resolve().Methods.First(m => m.Name == "SetDynamicResource");
+ var getKey = typeof(DynamicResource).GetProperty("Key").GetMethod;
+
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
+ yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
+ yield return Instruction.Create(OpCodes.Ldloc, varValue);
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(getKey));
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(setDynamicResource));
+ }
+
+ static bool CanSetBinding(FieldReference bpRef, INode valueNode, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+
+ if (bpRef == null)
+ return false;
+ var elementNode = valueNode as IElementNode;
+ if (elementNode == null)
+ return false;
- //If Value is DynamicResource, SetDynamicResource
VariableDefinition varValue;
- if (bpRef != null && valueNode is IElementNode &&
- context.Variables.TryGetValue(valueNode as IElementNode, out varValue) &&
- varValue.VariableType.FullName == typeof (DynamicResource).FullName)
- {
- var setDynamicResource =
- module.Import(typeof (IDynamicResourceHandler)).Resolve().Methods.First(m => m.Name == "SetDynamicResource");
- var getKey = typeof (DynamicResource).GetProperty("Key").GetMethod;
-
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Ldsfld, bpRef);
- context.IL.Emit(OpCodes.Ldloc, varValue);
- context.IL.Emit(OpCodes.Callvirt, module.Import(getKey));
- context.IL.Emit(OpCodes.Callvirt, module.Import(setDynamicResource));
- return;
- }
+ if (!context.Variables.TryGetValue(valueNode as IElementNode, out varValue))
+ return false;
+ return varValue.VariableType.InheritsFromOrImplements(module.Import(typeof(BindingBase)));
+ }
- //If Value is a BindingBase and target is a BP, SetBinding
- if (bpRef != null && valueNode is IElementNode &&
- context.Variables.TryGetValue(valueNode as IElementNode, out varValue) &&
- varValue.VariableType.InheritsFromOrImplements(module.Import(typeof (BindingBase))))
- {
- //TODO: check if parent is a BP
- var setBinding = typeof (BindableObject).GetMethod("SetBinding",
- new[] { typeof (BindableProperty), typeof (BindingBase) });
-
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Ldsfld, bpRef);
- context.IL.Emit(OpCodes.Ldloc, varValue);
- context.IL.Emit(OpCodes.Callvirt, module.Import(setBinding));
- return;
- }
+ static IEnumerable<Instruction> SetBinding(VariableDefinition parent, FieldReference bpRef, IElementNode elementNode, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ var varValue = context.Variables [elementNode];
+
+ //TODO: check if parent is a BP
+ var setBinding = typeof(BindableObject).GetMethod("SetBinding", new [] { typeof(BindableProperty), typeof(BindingBase) });
+
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
+ yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
+ yield return Instruction.Create(OpCodes.Ldloc, varValue);
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(setBinding));
+ }
+
+ static TypeReference GetBindablePropertyType(FieldReference bpRef, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+
+ if (!bpRef.Name.EndsWith("Property", StringComparison.InvariantCulture))
+ throw new XamlParseException($"The name of the bindable property {bpRef.Name} does not ends with \"Property\". This is the kind of convention the world is build upon, a bit like Planck's constant.", iXmlLineInfo);
+ var bpName = bpRef.Name.Substring(0, bpRef.Name.Length - 8);
+ var owner = bpRef.DeclaringType;
+ TypeReference _;
+
+ var getter = owner.GetProperty(pd => pd.Name == bpName, out _)?.GetMethod;
+ if (getter == null || getter.IsStatic || !getter.IsPublic)
+ getter = null;
+ getter = getter ?? owner.GetMethods(md => md.Name == $"Get{bpName}" && md.IsStatic && md.IsPublic && md.Parameters.Count == 1, module).FirstOrDefault()?.Item1;
+
+ if (getter == null)
+ throw new XamlParseException($"Missing a public static Get{bpName} or a public instance property getter for the attached property \"{bpRef.DeclaringType}.{bpRef.Name}\"", iXmlLineInfo);
+ return getter.ReturnType;
+ }
+
+ static bool CanSetValue(FieldReference bpRef, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ if (bpRef == null)
+ return false;
+
+ if (node is ValueNode)
+ return true;
+
+ var elementNode = node as IElementNode;
+ if (elementNode == null)
+ return false;
+
+ VariableDefinition varValue;
+ if (!context.Variables.TryGetValue(elementNode, out varValue))
+ return false;
+
+ var bpTypeRef = GetBindablePropertyType(bpRef, iXmlLineInfo, context);
+ return varValue.VariableType.InheritsFromOrImplements(bpTypeRef);
+ }
+
+ static IEnumerable<Instruction> SetValue(VariableDefinition parent, FieldReference bpRef, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var setValue = typeof(BindableObject).GetMethod("SetValue", new [] { typeof(BindableProperty), typeof(object) });
+ var valueNode = node as ValueNode;
+ var elementNode = node as IElementNode;
+ var module = context.Body.Method.Module;
- //If it's a BP, SetValue ()
// IL_0007: ldloc.0
// IL_0008: ldsfld class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty [Xamarin.Forms.Core]Xamarin.Forms.Label::TextProperty
// IL_000d: ldstr "foo"
// IL_0012: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.BindableObject::SetValue(class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty, object)
- if (bpRef != null)
- {
- //TODO: check if parent is a BP
- var setValue = typeof (BindableObject).GetMethod("SetValue", new[] { typeof (BindableProperty), typeof (object) });
- if (valueNode is ValueNode)
- {
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Ldsfld, bpRef);
- context.IL.Append(((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context),
- true, false));
- context.IL.Emit(OpCodes.Callvirt, module.Import(setValue));
- return;
- }
- if (valueNode is IElementNode)
- {
- var getPropertyReturnType = module.Import(typeof (BindableProperty).GetProperty("ReturnType").GetGetMethod());
- //FIXME: this should check for inheritance too
- var isInstanceOfType = module.Import(typeof (Type).GetMethod("IsInstanceOfType", new[] { typeof (object) }));
- var brfalse = Instruction.Create(OpCodes.Nop);
-
- context.IL.Emit(OpCodes.Ldsfld, bpRef);
- context.IL.Emit(OpCodes.Call, getPropertyReturnType);
- context.IL.Emit(OpCodes.Ldloc, context.Variables[valueNode as IElementNode]);
- if (context.Variables[valueNode as IElementNode].VariableType.IsValueType)
- context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType);
- context.IL.Emit(OpCodes.Callvirt, isInstanceOfType);
- context.IL.Emit(OpCodes.Brfalse, brfalse);
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Ldsfld, bpRef);
- context.IL.Emit(OpCodes.Ldloc, context.Variables[(IElementNode)valueNode]);
- if (context.Variables[valueNode as IElementNode].VariableType.IsValueType)
- context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType);
- context.IL.Emit(OpCodes.Callvirt, module.Import(setValue));
- context.IL.Emit(OpCodes.Br, br);
- context.IL.Append(brfalse);
- handled = true;
- }
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
+ yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
+
+ if (valueNode != null) {
+ foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context), true, false))
+ yield return instruction;
+ } else if (elementNode != null) {
+ yield return Instruction.Create(OpCodes.Ldloc, context.Variables [elementNode]);
+ if (context.Variables [elementNode].VariableType.IsValueType)
+ yield return Instruction.Create(OpCodes.Box, context.Variables [elementNode].VariableType);
}
- //If it's a property, set it
-// IL_0007: ldloc.0
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(setValue));
+ }
+
+ static bool CanSet(VariableDefinition parent, string localName, INode node, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ TypeReference declaringTypeReference;
+ var property = parent.VariableType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
+ if (property == null)
+ return false;
+ var propertySetter = property.SetMethod;
+ if (propertySetter == null || !propertySetter.IsPublic || propertySetter.IsStatic)
+ return false;
+
+ if (node is ValueNode)
+ return true;
+
+ var elementNode = node as IElementNode;
+ if (elementNode == null)
+ return false;
+
+ var vardef = context.Variables [elementNode];
+ var propertyType = property.ResolveGenericPropertyType(declaringTypeReference);
+ var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(propertyType, module);
+
+ if (implicitOperator != null)
+ return true;
+ if (vardef.VariableType.InheritsFromOrImplements(propertyType))
+ return true;
+ if (propertyType.FullName == "System.Object")
+ return true;
+
+ //I'd like to get rid of this condition. This comment used to be //TODO replace latest check by a runtime type check
+ if (vardef.VariableType.FullName == "System.Object")
+ return true;
+
+ return false;
+ }
+
+ static IEnumerable<Instruction> Set(VariableDefinition parent, string localName, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ TypeReference declaringTypeReference;
+ var property = parent.VariableType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
+ var propertySetter = property.SetMethod;
+
+// IL_0007: ldloc.0
// IL_0008: ldstr "foo"
// IL_000d: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Label::set_Text(string)
- PropertyDefinition property = elementType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
- MethodDefinition propertySetter;
- if (property != null && (propertySetter = property.SetMethod) != null && propertySetter.IsPublic)
- {
- module.Import(elementType.Resolve());
- var propertySetterRef =
- module.Import(module.Import(propertySetter).ResolveGenericParameters(declaringTypeReference, module));
- propertySetterRef.ImportTypes(module);
- var propertyType = property.ResolveGenericPropertyType(declaringTypeReference);
- ValueNode vnode = null;
-
- if ((vnode = valueNode as ValueNode) != null)
- {
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Append(vnode.PushConvertedValue(context,
- propertyType,
- new ICustomAttributeProvider[] { property, propertyType.Resolve() },
- valueNode.PushServiceProvider(context), false, true));
- context.IL.Emit(OpCodes.Callvirt, propertySetterRef);
-
- context.IL.Append(br);
- return;
- }
- if (valueNode is IElementNode)
- {
- var vardef = context.Variables[(ElementNode)valueNode];
- var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(propertyType, module);
- //TODO replace latest check by a runtime type check
- if (implicitOperator != null || vardef.VariableType.InheritsFromOrImplements(propertyType) ||
- propertyType.FullName == "System.Object" || vardef.VariableType.FullName == "System.Object")
- {
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Ldloc, vardef);
- if (implicitOperator != null)
- {
-// IL_000f: call !0 class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<bool>::op_Implicit(class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<!0>)
- context.IL.Emit(OpCodes.Call, module.Import(implicitOperator));
- }
- else if (!vardef.VariableType.IsValueType && propertyType.IsValueType)
- context.IL.Emit(OpCodes.Unbox_Any, module.Import(propertyType));
- else if (vardef.VariableType.IsValueType && propertyType.FullName == "System.Object")
- context.IL.Emit(OpCodes.Box, vardef.VariableType);
- context.IL.Emit(OpCodes.Callvirt, propertySetterRef);
- context.IL.Append(br);
- return;
- }
- handled = true;
- }
- }
+ module.Import(parent.VariableType.Resolve());
+ var propertySetterRef = module.Import(module.Import(propertySetter).ResolveGenericParameters(declaringTypeReference, module));
+ propertySetterRef.ImportTypes(module);
+ var propertyType = property.ResolveGenericPropertyType(declaringTypeReference);
+ var valueNode = node as ValueNode;
+ var elementNode = node as IElementNode;
- //If it's an already initialized property, add to it
- MethodDefinition propertyGetter;
- //TODO: check if property is assigned
- if (property != null && (propertyGetter = property.GetMethod) != null && propertyGetter.IsPublic)
- {
- var propertyGetterRef = module.Import(propertyGetter);
- propertyGetterRef = module.Import(propertyGetterRef.ResolveGenericParameters(declaringTypeReference, module));
- var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
- var vardef = context.Variables [(ElementNode)valueNode];
-
- //TODO check md.Parameters[0] type
- var adderTuple =
- propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
- if (adderTuple != null)
- {
- var adderRef = module.Import(adderTuple.Item1);
- adderRef = module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, module));
- var childType = GetParameterType(adderRef.Parameters [0]);
- var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(childType, module);
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
- if (valueNode is IElementNode)
- {
- context.IL.Emit(OpCodes.Ldloc, parent);
- context.IL.Emit(OpCodes.Callvirt, propertyGetterRef);
- context.IL.Emit(OpCodes.Ldloc, vardef);
- if (implicitOperator != null)
- context.IL.Emit(OpCodes.Call, module.Import(implicitOperator));
- context.IL.Emit(OpCodes.Callvirt, adderRef);
- if (adderRef.ReturnType.FullName != "System.Void")
- context.IL.Emit(OpCodes.Pop);
- context.IL.Append(br);
- return;
- }
- }
- }
- if (!handled)
- {
- throw new XamlParseException(string.Format("No property, bindable property, or event found for '{0}'", localName),
- iXmlLineInfo);
+ if (valueNode != null) {
+ foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider [] { property, propertyType.Resolve() }, valueNode.PushServiceProvider(context), false, true))
+ yield return instruction;
+ yield return Instruction.Create(OpCodes.Callvirt, propertySetterRef);
+ } else if (elementNode != null) {
+ var vardef = context.Variables [elementNode];
+ var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(propertyType, module);
+ yield return Instruction.Create(OpCodes.Ldloc, vardef);
+ if (implicitOperator != null) {
+// IL_000f: call !0 class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<bool>::op_Implicit(class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<!0>)
+ yield return Instruction.Create(OpCodes.Call, module.Import(implicitOperator));
+ } else if (!vardef.VariableType.IsValueType && propertyType.IsValueType)
+ yield return Instruction.Create(OpCodes.Unbox_Any, module.Import(propertyType));
+ else if (vardef.VariableType.IsValueType && propertyType.FullName == "System.Object")
+ yield return Instruction.Create(OpCodes.Box, vardef.VariableType);
+ yield return Instruction.Create(OpCodes.Callvirt, propertySetterRef);
}
- context.IL.Append(br);
+ }
+
+ static bool CanAdd(VariableDefinition parent, string localName, INode node, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ TypeReference declaringTypeReference;
+ var property = parent.VariableType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
+ if (property == null)
+ return false;
+ var propertyGetter = property.GetMethod;
+ if (propertyGetter == null || !propertyGetter.IsPublic || propertyGetter.IsStatic)
+ return false;
+ var elementNode = node as IElementNode;
+ if (elementNode == null)
+ return false;
+
+ var vardef = context.Variables [elementNode];
+ var propertyGetterRef = module.Import(propertyGetter);
+ propertyGetterRef = module.Import(propertyGetterRef.ResolveGenericParameters(declaringTypeReference, module));
+ var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
+
+ //TODO check md.Parameters[0] type
+ var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
+ if (adderTuple == null)
+ return false;
+
+ return true;
+ }
+
+ static IEnumerable<Instruction> Add(VariableDefinition parent, string localName, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+ TypeReference declaringTypeReference;
+ var property = parent.VariableType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
+ var propertyGetter = property.GetMethod;
+ var elementNode = node as IElementNode;
+ var vardef = context.Variables [elementNode];
+ var propertyGetterRef = module.Import(propertyGetter);
+ propertyGetterRef = module.Import(propertyGetterRef.ResolveGenericParameters(declaringTypeReference, module));
+ var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
+ //TODO check md.Parameters[0] type
+ var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
+ var adderRef = module.Import(adderTuple.Item1);
+ adderRef = module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, module));
+ var childType = GetParameterType(adderRef.Parameters [0]);
+ var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(childType, module);
+
+ yield return Instruction.Create(OpCodes.Ldloc, parent);
+ yield return Instruction.Create(OpCodes.Callvirt, propertyGetterRef);
+ yield return Instruction.Create(OpCodes.Ldloc, vardef);
+ if (implicitOperator != null)
+ yield return Instruction.Create(OpCodes.Call, module.Import(implicitOperator));
+ yield return Instruction.Create(OpCodes.Callvirt, adderRef);
+ if (adderRef.ReturnType.FullName != "System.Void")
+ yield return Instruction.Create(OpCodes.Pop);
}
public static TypeReference GetParameterType(ParameterDefinition param)
@@ -683,4 +776,4 @@ namespace Xamarin.Forms.Build.Tasks
return vardefref.VariableDefinition;
}
}
-}
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs
index 25efa439..95a93393 100644
--- a/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs
+++ b/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs
@@ -57,7 +57,7 @@ namespace Xamarin.Forms.Build.Tasks
return;
if (propertyName.LocalName != "MergedWith")
return;
- SetPropertiesVisitor.SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
+ Context.IL.Append(SetPropertiesVisitor.SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
}
public void Visit(MarkupNode node, INode parentNode)
@@ -118,10 +118,10 @@ namespace Xamarin.Forms.Build.Tasks
Context.Variables[node] = vardef;
}
- // IL_0013: ldloc.0
- // IL_0014: ldstr "key"
- // IL_0019: ldstr "foo"
- // IL_001e: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object)
+// IL_0013: ldloc.0
+// IL_0014: ldstr "key"
+// IL_0019: ldstr "foo"
+// IL_001e: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object)
Context.IL.Emit(OpCodes.Ldloc, parentVar);
Context.IL.Emit(OpCodes.Ldstr, (node.Properties[XmlName.xKey] as ValueNode).Value as string);
var varDef = Context.Variables[node];
@@ -143,7 +143,7 @@ namespace Xamarin.Forms.Build.Tasks
(propertyName.LocalName == "Resources" || propertyName.LocalName.EndsWith(".Resources", StringComparison.Ordinal)) &&
(Context.Variables[node].VariableType.FullName == "Xamarin.Forms.ResourceDictionary" ||
Context.Variables[node].VariableType.Resolve().BaseType.FullName == "Xamarin.Forms.ResourceDictionary"))
- SetPropertiesVisitor.SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
+ Context.IL.Append(SetPropertiesVisitor.SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
}
public void Visit(RootNode node, INode parentNode)
diff --git a/Xamarin.Forms.Core/ConstraintExpression.cs b/Xamarin.Forms.Core/ConstraintExpression.cs
index b2ca4b8f..0e4f5e97 100644
--- a/Xamarin.Forms.Core/ConstraintExpression.cs
+++ b/Xamarin.Forms.Core/ConstraintExpression.cs
@@ -6,7 +6,7 @@ using Xamarin.Forms.Xaml;
namespace Xamarin.Forms
{
- public class ConstraintExpression : IMarkupExtension
+ public class ConstraintExpression : IMarkupExtension<Constraint>
{
public ConstraintExpression()
{
@@ -23,7 +23,12 @@ namespace Xamarin.Forms
public ConstraintType Type { get; set; }
- public object ProvideValue(IServiceProvider serviceProvider)
+ object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return (this as IMarkupExtension<Constraint>).ProvideValue(serviceProvider);
+ }
+
+ public Constraint ProvideValue(IServiceProvider serviceProvider)
{
MethodInfo minfo;
switch (Type)
diff --git a/Xamarin.Forms.Xaml.UnitTests/GenericCollections.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/GenericCollections.xaml.cs
index 95c057b0..c2891e06 100644
--- a/Xamarin.Forms.Xaml.UnitTests/GenericCollections.xaml.cs
+++ b/Xamarin.Forms.Xaml.UnitTests/GenericCollections.xaml.cs
@@ -14,6 +14,11 @@ namespace Xamarin.Forms.Xaml.UnitTests
typeof(GenericCollection),
typeof(AttachedBP),
null);
+
+ public static GenericCollection GetAttachedBP(BindableObject bindable)
+ {
+ throw new NotImplementedException();
+ }
}
public class GenericCollection : ObservableCollection<object>
diff --git a/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml b/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml
index 87252208..a40489cf 100644
--- a/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml
+++ b/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml
@@ -1,14 +1,14 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Forms.Xaml.UnitTests.SetterOnNonBP"
xmlns:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests;assembly=Xamarin.Forms.Xaml.UnitTests">
- <local:FakeVisualElement>
- <local:FakeVisualElement.Style>
- <Style TargetType="local:FakeVisualElement">
+ <local:FakeView>
+ <local:FakeView.Style>
+ <Style TargetType="local:FakeView">
<Setter Property="NonBindable" Value="Should Fail"/>
</Style>
- </local:FakeVisualElement.Style>
- </local:FakeVisualElement>
+ </local:FakeView.Style>
+ </local:FakeView>
</ContentPage> \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml.cs
index f6d8ffea..f39e5dd3 100644
--- a/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml.cs
+++ b/Xamarin.Forms.Xaml.UnitTests/Validation/SetterOnNonBP.xaml.cs
@@ -3,7 +3,7 @@
using Xamarin.Forms;
namespace Xamarin.Forms.Xaml.UnitTests
{
- public class FakeVisualElement : VisualElement
+ public class FakeView : View
{
public string NonBindable { get; set; }
}