summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-09-23 09:01:22 +0200
committerJason Smith <jason.smith@xamarin.com>2016-09-23 00:04:54 -0700
commit349ae5314df967236c2676f3439d69147a5b6e49 (patch)
treeac2dce6b2f37237813f0d1c908563d03513c467f
parent1cf99945f8b7ab1ceea27cd90af4e076f1dd1f82 (diff)
downloadxamarin-forms-349ae5314df967236c2676f3439d69147a5b6e49.tar.gz
xamarin-forms-349ae5314df967236c2676f3439d69147a5b6e49.tar.bz2
xamarin-forms-349ae5314df967236c2676f3439d69147a5b6e49.zip
[XamlC] Implement IValueProvider.PropertyType (#345)
-rw-r--r--Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs61
-rw-r--r--Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs1
-rw-r--r--Xamarin.Forms.Build.Tasks/NodeILExtensions.cs131
-rw-r--r--Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs24
-rw-r--r--Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs87
-rw-r--r--Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj1
-rw-r--r--Xamarin.Forms.Core/IProvideValueTarget.cs1
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs6
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml18
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml.cs72
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs47
-rw-r--r--Xamarin.Forms.Xaml/XamlServiceProvider.cs22
12 files changed, 249 insertions, 222 deletions
diff --git a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs
new file mode 100644
index 00000000..159bbf5f
--- /dev/null
+++ b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs
@@ -0,0 +1,61 @@
+´╗┐using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml;
+
+using Mono.Cecil;
+
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Build.Tasks
+{
+ static class BindablePropertyReferenceExtensions
+ {
+ public static TypeReference GetBindablePropertyType(this FieldReference bpRef, IXmlLineInfo iXmlLineInfo, ModuleDefinition 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 &&
+ md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.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;
+ }
+
+ public static TypeReference GetBindablePropertyTypeConverter(this FieldReference bpRef, ModuleDefinition module)
+ {
+ TypeReference _;
+ var owner = bpRef.DeclaringType;
+ var bpName = bpRef.Name.EndsWith("Property", StringComparison.Ordinal) ? bpRef.Name.Substring(0, bpRef.Name.Length - 8) : bpRef.Name;
+ var property = owner.GetProperty(pd => pd.Name == bpName, out _);
+ var staticGetter = owner.GetMethods(md => md.Name == $"Get{bpName}" &&
+ md.IsStatic &&
+ md.IsPublic &&
+ md.Parameters.Count == 1 &&
+ md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.Item1;
+
+ var attributes = new List<CustomAttribute>();
+ if (property != null && property.HasCustomAttributes)
+ attributes.AddRange(property.CustomAttributes);
+ if (property != null && property.PropertyType.Resolve().HasCustomAttributes)
+ attributes.AddRange(property.PropertyType.Resolve().CustomAttributes);
+ if (staticGetter != null && staticGetter.HasCustomAttributes)
+ attributes.AddRange(staticGetter.CustomAttributes);
+ if (staticGetter != null && staticGetter.ReturnType.Resolve().HasCustomAttributes)
+ attributes.AddRange(staticGetter.ReturnType.Resolve().CustomAttributes);
+
+ return attributes.FirstOrDefault(cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName))?.ConstructorArguments [0].Value as TypeReference;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs
index 6240a201..3baa230c 100644
--- a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs
+++ b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs
@@ -1,6 +1,5 @@
using System;
using Mono.Cecil;
-using Mono.Cecil.Rocks;
namespace Xamarin.Forms.Build.Tasks
{
diff --git a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
index 3902c71b..084151ba 100644
--- a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
+++ b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
@@ -37,10 +37,8 @@ namespace Xamarin.Forms.Build.Tasks
IEnumerable<Instruction> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes)
{
var module = context.Body.Method.Module;
- var targetTypeRef = GetBPReturnType(context, bpRef, node);
-
- TypeReference typeConverter;
- bpRef.HasTypeConverter(module, out typeConverter);
+ var targetTypeRef = bpRef.GetBindablePropertyType(node, module);
+ var typeConverter = bpRef.GetBindablePropertyTypeConverter(module);
return node.PushConvertedValue(context, targetTypeRef, typeConverter, pushServiceProvider, boxValueTypes,
unboxValueTypes);
@@ -186,101 +184,6 @@ namespace Xamarin.Forms.Build.Tasks
throw new XamlParseException(string.Format("Enum value not found for {0}", value), lineInfo);
}
- static bool HasTypeConverter(this FieldReference bpRef, ModuleDefinition module,
- out TypeReference typeConverterReference)
- {
- typeConverterReference = null;
-
- var declaringType = bpRef.DeclaringType;
- var bpName = bpRef.Name;
- var pName = bpName.EndsWith("Property", StringComparison.Ordinal) ? bpName.Substring(0, bpName.Length - 8) : bpName;
- var property = declaringType.Resolve().Properties.FirstOrDefault(p => p.Name == pName);
- CustomAttribute attr = null;
-
- if (property != null)
- {
- if (property.HasCustomAttributes)
- {
- attr =
- property.CustomAttributes.FirstOrDefault(
- cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
- }
- if (attr == null && property.PropertyType.Resolve().HasCustomAttributes)
- {
- attr =
- property.PropertyType.Resolve()
- .CustomAttributes.FirstOrDefault(
- cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
- }
-
- if (attr == null)
- return false;
-
- typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference;
- return true;
- }
-
- var getters = bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + pName &&
- md.IsStatic &&
- md.Parameters.Count() == 1 &&
- md.Parameters[0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault();
- if (getters != null)
- {
- if (getters.Item1.HasCustomAttributes)
- {
- attr =
- getters.Item1.CustomAttributes.FirstOrDefault(
- cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
- }
- else if (getters.Item1.ReturnType.Resolve().HasCustomAttributes)
- {
- attr =
- getters.Item1.ReturnType.Resolve()
- .CustomAttributes.FirstOrDefault(
- cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
- }
-
- if (attr == null)
- return false;
-
- typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference;
- return true;
- }
-
- return false;
- }
-
- static TypeReference GetBPReturnType(ILContext context, FieldReference bpRef, IXmlLineInfo lineInfo)
- {
- //Find a property with a matching name
- var name = bpRef.Name;
- if (!name.EndsWith("Property", StringComparison.Ordinal))
- return context.Body.Method.Module.TypeSystem.Object;
- name = name.Substring(0, name.Length - 8);
-
- //First, check for a property
- TypeReference declaringTypeRef;
- var property = bpRef.DeclaringType.GetProperty(pd => pd.Name == name, out declaringTypeRef);
- if (property != null)
- return property.PropertyType;
-
- //Then check for getter or setter (attached BPs)
- var getters =
- bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + name &&
- md.IsStatic &&
- md.Parameters.Count() == 1 &&
- md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", context.Body.Method.Module)
- .SingleOrDefault();
- if (getters != null)
- return getters.Item1.ReturnType;
-
- //throws
- throw new XamlParseException(
- string.Format(
- "Can not find a Property named \"{0}\" or a static method named \"Get{0}\" for BindableProperty \"{1}\"", name,
- bpRef.Name), lineInfo);
- }
-
public static IEnumerable<Instruction> PushXmlLineInfo(this INode node, ILContext context)
{
var module = context.Body.Method.Module;
@@ -389,7 +292,30 @@ namespace Xamarin.Forms.Build.Tasks
}
}
- public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILContext context)
+ static IEnumerable<Instruction> PushTargetProperty(FieldReference bpRef, PropertyReference propertyRef, TypeReference declaringTypeReference, ModuleDefinition module)
+ {
+ if (bpRef != null) {
+ yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
+ yield break;
+ }
+ if (propertyRef != null) {
+// IL_0000: ldtoken [mscorlib]System.String
+// IL_0005: call class [mscorlib]System.Type class [mscorlib] System.Type::GetTypeFromHandle(valuetype [mscorlib] System.RuntimeTypeHandle)
+// IL_000a: ldstr "Foo"
+// IL_000f: callvirt instance class [mscorlib] System.Reflection.PropertyInfo class [mscorlib] System.Type::GetProperty(string)
+ var getTypeFromHandle = module.Import(typeof(Type).GetMethod("GetTypeFromHandle", new [] { typeof(RuntimeTypeHandle) }));
+ var getPropertyInfo = module.Import(typeof(Type).GetMethod("GetProperty", new [] { typeof(string) }));
+ yield return Instruction.Create(OpCodes.Ldtoken, module.Import(declaringTypeReference ?? propertyRef.DeclaringType));
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+ yield return Instruction.Create(OpCodes.Ldstr, propertyRef.Name);
+ yield return Instruction.Create(OpCodes.Callvirt, module.Import(getPropertyInfo));
+ yield break;
+ }
+ yield return Instruction.Create(OpCodes.Ldnull);
+ yield break;
+ }
+
+ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILContext context, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference declaringTypeReference = null)
{
var module = context.Body.Method.Module;
@@ -421,8 +347,11 @@ namespace Xamarin.Forms.Build.Tasks
foreach (var instruction in pushParentIl)
yield return instruction;
+ foreach (var instruction in PushTargetProperty(bpRef, propertyRef, declaringTypeReference, module))
+ yield return instruction;
+
var targetProviderCtor =
- module.Import(typeof (SimpleValueTargetProvider).GetConstructor(new[] { typeof (object[]) }));
+ module.Import(typeof (SimpleValueTargetProvider).GetConstructor(new[] { typeof (object[]), typeof(object) }));
yield return Instruction.Create(OpCodes.Newobj, targetProviderCtor);
yield return Instruction.Create(OpCodes.Callvirt, addService);
}
diff --git a/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs
index 472a5658..7a56c9d1 100644
--- a/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs
+++ b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs
@@ -1,32 +1,14 @@
+using System;
using Mono.Cecil;
namespace Xamarin.Forms.Build.Tasks
{
static class PropertyDefinitionExtensions
{
- // public static PropertyDefinition MakeGeneric (this PropertyDefinition self, GenericInstanceType declaringTypeReference)
- // {
- // if (declaringTypeReference == null)
- // throw new ArgumentNullException ("declaringTypeReference");
- // if (self == null)
- // throw new ArgumentNullException ("self");
- //
- // var propertyType = declaringTypeReference.GenericArguments[((GenericParameter)self.PropertyType).Position];
- // self.PropertyType = propertyType;
- // self.SetMethod = self.SetMethod.MakeGeneric (propertyType).Resolve ();
- // self.GetMethod.ReturnType = propertyType;
- //
- // return self;
- // }
-
- public static TypeReference ResolveGenericPropertyType(this PropertyDefinition self,
- TypeReference declaringTypeReference)
+ public static TypeReference ResolveGenericPropertyType(this PropertyDefinition self, TypeReference declaringTypeReference)
{
if (self.PropertyType.IsGenericParameter)
- {
- return
- ((GenericInstanceType)declaringTypeReference).GenericArguments[((GenericParameter)self.PropertyType).Position];
- }
+ return ((GenericInstanceType)declaringTypeReference).GenericArguments [((GenericParameter)self.PropertyType).Position];
return self.PropertyType;
}
}
diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
index 9bc09ce9..734a34c2 100644
--- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
+++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
@@ -81,10 +81,21 @@ namespace Xamarin.Forms.Build.Tasks
parentNode = parentNode.Parent;
}
+ if ((propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName)) && skips.Contains(propertyName))
+ return;
+
//if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable
var vardef = Context.Variables[node];
var vardefref = new VariableDefinitionReference(vardef);
- Context.IL.Append(ProvideValue(vardefref, Context, Module, node));
+ var localName = propertyName.LocalName;
+ TypeReference declaringTypeReference = null;
+ FieldReference bpRef = null;
+ PropertyDefinition propertyRef = null;
+ if (parentNode is IElementNode && propertyName != XmlName.Empty) {
+ bpRef = GetBindablePropertyReference(Context.Variables [(IElementNode)parentNode], propertyName.NamespaceURI, ref localName, Context, node);
+ propertyRef = Context.Variables [(IElementNode)parentNode].VariableType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
+ }
+ Context.IL.Append(ProvideValue(vardefref, Context, Module, node, bpRef:bpRef, propertyRef:propertyRef, propertyDeclaringTypeRef: declaringTypeReference));
if (vardef != vardefref.VariableDefinition)
{
vardef = vardefref.VariableDefinition;
@@ -92,11 +103,8 @@ namespace Xamarin.Forms.Build.Tasks
Context.Variables[node] = vardef;
}
- if (propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName))
+ if (propertyName != XmlName.Empty)
{
- if (skips.Contains(propertyName))
- return;
-
if (propertyName == XmlName._CreateContent)
SetDataTemplate((IElementNode)parentNode, node, Context, node);
else
@@ -221,7 +229,7 @@ namespace Xamarin.Forms.Build.Tasks
}
public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ILContext context,
- ModuleDefinition module, ElementNode node)
+ ModuleDefinition module, ElementNode node, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null)
{
GenericInstanceType markupExtension;
IList<TypeReference> genericArguments;
@@ -242,7 +250,7 @@ namespace Xamarin.Forms.Build.Tasks
else
vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First()));
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
- foreach (var instruction in node.PushServiceProvider(context))
+ foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
@@ -261,7 +269,7 @@ namespace Xamarin.Forms.Build.Tasks
vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First()));
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
- foreach (var instruction in node.PushServiceProvider(context))
+ foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
@@ -274,7 +282,7 @@ namespace Xamarin.Forms.Build.Tasks
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
- foreach (var instruction in node.PushServiceProvider(context))
+ foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
@@ -287,7 +295,7 @@ namespace Xamarin.Forms.Build.Tasks
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
- foreach (var instruction in node.PushServiceProvider(context))
+ foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
@@ -297,20 +305,8 @@ namespace Xamarin.Forms.Build.Tasks
public static IEnumerable<Instruction> SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode, ILContext context, IXmlLineInfo iXmlLineInfo)
{
var module = context.Body.Method.Module;
-
var localName = propertyName.LocalName;
- TypeReference declaringTypeReference;
-
- //If it's an attached BP, update elementType and propertyName
- 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);
- }
+ var bpRef = GetBindablePropertyReference(parent, propertyName.NamespaceURI, ref localName, context, iXmlLineInfo);
//If the target is an event, connect
if (CanConnectEvent(parent, localName))
@@ -339,6 +335,25 @@ namespace Xamarin.Forms.Build.Tasks
throw new XamlParseException($"No property, bindable property, or event found for '{localName}'", iXmlLineInfo);
}
+ static FieldReference GetBindablePropertyReference(VariableDefinition parent, string namespaceURI, ref string localName, ILContext context, IXmlLineInfo iXmlLineInfo)
+ {
+ var module = context.Body.Method.Module;
+ TypeReference declaringTypeReference;
+
+ //If it's an attached BP, update elementType and propertyName
+ var bpOwnerType = parent.VariableType;
+ GetNameAndTypeRef(ref bpOwnerType, namespaceURI, ref localName, context, iXmlLineInfo);
+ var name = $"{localName}Property";
+ FieldReference bpRef = bpOwnerType.GetField(fd => fd.Name == name &&
+ fd.IsStatic &&
+ fd.IsPublic, out declaringTypeReference);
+ if (bpRef != null) {
+ bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference));
+ bpRef.FieldType = module.Import(bpRef.FieldType);
+ }
+ return bpRef;
+ }
+
static bool CanConnectEvent(VariableDefinition parent, string localName)
{
return parent.VariableType.GetEvent(ed => ed.Name == localName) != null;
@@ -448,28 +463,10 @@ namespace Xamarin.Forms.Build.Tasks
yield return Instruction.Create(OpCodes.Callvirt, module.Import(setBinding));
}
- static TypeReference GetBindablePropertyType(FieldReference bpRef, IXmlLineInfo iXmlLineInfo, ILContext context)
+ static bool CanSetValue(FieldReference bpRef, INode node, 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;
@@ -484,7 +481,7 @@ namespace Xamarin.Forms.Build.Tasks
if (!context.Variables.TryGetValue(elementNode, out varValue))
return false;
- var bpTypeRef = GetBindablePropertyType(bpRef, iXmlLineInfo, context);
+ var bpTypeRef = bpRef.GetBindablePropertyType(iXmlLineInfo, module);
return varValue.VariableType.InheritsFromOrImplements(bpTypeRef);
}
@@ -504,7 +501,7 @@ namespace Xamarin.Forms.Build.Tasks
yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
if (valueNode != null) {
- foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context), true, false))
+ foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef:bpRef), true, false))
yield return instruction;
} else if (elementNode != null) {
yield return Instruction.Create(OpCodes.Ldloc, context.Variables [elementNode]);
@@ -572,7 +569,7 @@ namespace Xamarin.Forms.Build.Tasks
yield return Instruction.Create(OpCodes.Ldloc, parent);
if (valueNode != null) {
- foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider [] { property, propertyType.Resolve() }, valueNode.PushServiceProvider(context), false, true))
+ foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider [] { property, propertyType.Resolve() }, valueNode.PushServiceProvider(context, propertyRef:property), false, true))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, propertySetterRef);
} else if (elementNode != null) {
diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj
index 0b26e277..fd19f586 100644
--- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj
+++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj
@@ -90,6 +90,7 @@
<Compile Include="MethodDefinitionExtensions.cs" />
<Compile Include="CompiledMarkupExtensions\StaticExtension.cs" />
<Compile Include="CompiledMarkupExtensions\ICompiledMarkupExtension.cs" />
+ <Compile Include="BindablePropertyReferenceExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">
diff --git a/Xamarin.Forms.Core/IProvideValueTarget.cs b/Xamarin.Forms.Core/IProvideValueTarget.cs
index b8324b5e..13e4cb08 100644
--- a/Xamarin.Forms.Core/IProvideValueTarget.cs
+++ b/Xamarin.Forms.Core/IProvideValueTarget.cs
@@ -3,7 +3,6 @@ namespace Xamarin.Forms.Xaml
public interface IProvideValueTarget
{
object TargetObject { get; }
-
object TargetProperty { get; }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs b/Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs
index 7361235c..a026eda2 100644
--- a/Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs
+++ b/Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs
@@ -177,11 +177,7 @@ namespace Xamarin.Forms.Xaml.UnitTests
}
}
- public object TargetProperty {
- get {
- throw new NotImplementedException ();
- }
- }
+ public object TargetProperty { get; } = null;
}
[Test]
diff --git a/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml b/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml
index bf44299b..7f470a4a 100644
--- a/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml
+++ b/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml
@@ -1,9 +1,23 @@
-´╗┐<?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.OnPlatform">
+ <ContentPage.Resources>
+ <ResourceDictionary>
+ <OnPlatform x:TypeArguments="FontAttributes" x:Key="fontAttributes">
+ <OnPlatform.iOS>Bold</OnPlatform.iOS>
+ <OnPlatform.Android>Italic</OnPlatform.Android>
+ </OnPlatform>
+ <OnPlatform x:Key="phone" x:TypeArguments="x:Double" iOS="20" Android="20" WinPhone="30"/>
+ <OnPlatform x:Key="tablet" x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="60"/>
+ <OnIdiom x:Key="fontSize" x:TypeArguments="x:Double"
+ Phone="{StaticResource phone}"
+ Tablet="{StaticResource tablet}"/>
+
+ </ResourceDictionary>
+ </ContentPage.Resources>
<StackLayout>
- <Label x:Name="label0">
+ <Label x:Name="label0" FontAttributes="{StaticResource fontAttributes}" FontSize="{StaticResource fontSize}">
<Label.IsVisible>
<OnPlatform x:TypeArguments="x:Boolean">
<OnPlatform.iOS>true</OnPlatform.iOS>
diff --git a/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml.cs
index 2c2c6482..6d9dad6e 100644
--- a/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml.cs
+++ b/Xamarin.Forms.Xaml.UnitTests/OnPlatform.xaml.cs
@@ -22,6 +22,18 @@ namespace Xamarin.Forms.Xaml.UnitTests
[TestFixture]
public class Tests
{
+ [SetUp]
+ public void Setup()
+ {
+ Device.PlatformServices = new MockPlatformServices();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Device.PlatformServices = null;
+ }
+
[TestCase (false)]
[TestCase (true)]
public void BoolToVisibility (bool useCompiledXaml)
@@ -34,13 +46,61 @@ namespace Xamarin.Forms.Xaml.UnitTests
layout = new OnPlatform (useCompiledXaml);
Assert.AreEqual (false, layout.label0.IsVisible);
}
- }
- public void T ()
- {
- var onplat = new OnPlatform<bool> ();
- var label = new Label ();
- label.IsVisible = onplat;
+ [TestCase(false)]
+ [TestCase(true)]
+ public void DoubleToWidth(bool useCompiledXaml)
+ {
+ Device.OS = TargetPlatform.iOS;
+ var layout = new OnPlatform(useCompiledXaml);
+ Assert.AreEqual(20, layout.label0.WidthRequest);
+
+ Device.OS = TargetPlatform.Android;
+ layout = new OnPlatform(useCompiledXaml);
+ Assert.AreEqual(30, layout.label0.WidthRequest);
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void StringToText(bool useCompiledXaml)
+ {
+ Device.OS = TargetPlatform.iOS;
+ var layout = new OnPlatform(useCompiledXaml);
+ Assert.AreEqual("Foo", layout.label0.Text);
+
+ Device.OS = TargetPlatform.Android;
+ layout = new OnPlatform(useCompiledXaml);
+ Assert.AreEqual("Bar", layout.label0.Text);
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void OnPlatformAsResource(bool useCompiledXaml)
+ {
+ var layout = new OnPlatform(useCompiledXaml);
+ var onplat = layout.Resources ["fontAttributes"] as OnPlatform<FontAttributes>;
+ Assert.NotNull(onplat);
+ Assert.AreEqual(FontAttributes.Bold, onplat.iOS);
+
+ Assert.AreEqual(FontAttributes.Italic, onplat.Android);
+ }
+
+ [TestCase(false)]
+ [TestCase(true)]
+ public void OnPlatformAsResourceAreApplied(bool useCompiledXaml)
+ {
+ Device.OS = TargetPlatform.iOS;
+ var layout = new OnPlatform(useCompiledXaml);
+ var onidiom = layout.Resources ["fontSize"] as OnIdiom<double>;
+ Assert.NotNull(onidiom);
+ Assert.That(onidiom.Phone, Is.TypeOf<double>());
+ Assert.AreEqual(20, onidiom.Phone);
+ Assert.AreEqual(FontAttributes.Bold, layout.label0.FontAttributes);
+
+ Device.OS = TargetPlatform.Android;
+ layout = new OnPlatform(useCompiledXaml);
+ Assert.AreEqual(FontAttributes.Italic, layout.label0.FontAttributes);
+ }
}
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
index cf672a3d..a7f59be8 100644
--- a/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
@@ -11,7 +11,7 @@ namespace Xamarin.Forms.Xaml
public object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
- throw new ArgumentNullException("serviceProvider");
+ throw new ArgumentNullException(nameof(serviceProvider));
if (Key == null)
{
var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
@@ -23,6 +23,7 @@ namespace Xamarin.Forms.Xaml
throw new ArgumentException();
var xmlLineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
var xmlLineInfo = xmlLineInfoProvider != null ? xmlLineInfoProvider.XmlLineInfo : null;
+ object resource = null;
foreach (var p in valueProvider.ParentObjects)
{
@@ -30,35 +31,27 @@ namespace Xamarin.Forms.Xaml
var resDict = ve?.Resources ?? p as ResourceDictionary;
if (resDict == null)
continue;
- object res;
- if (resDict.TryGetValue(Key, out res))
- {
- return ConvertCompiledOnPlatform(res);
- }
- }
- if (Application.Current != null && Application.Current.Resources != null &&
- Application.Current.Resources.ContainsKey(Key))
- {
- var resource = Application.Current.Resources[Key];
-
- return ConvertCompiledOnPlatform(resource);
+ if (resDict.TryGetValue(Key, out resource))
+ break;
}
+ if (resource == null && Application.Current != null && Application.Current.Resources != null &&
+ Application.Current.Resources.ContainsKey(Key))
+ resource = Application.Current.Resources [Key];
- throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
- }
+ if (resource == null)
+ throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
- static object ConvertCompiledOnPlatform(object resource)
- {
- var actualType = resource.GetType();
- if (actualType.GetTypeInfo().IsGenericType && actualType.GetGenericTypeDefinition() == typeof(OnPlatform<>))
- {
- // If we're accessing OnPlatform via a StaticResource in compiled XAML
- // we'll have to handle the cast to the target type manually
- // (Normally the compiled XAML handles this by calling `implicit` explicitly,
- // but it doesn't know to do that when it's using a static resource)
- var method = actualType.GetRuntimeMethod("op_Implicit", new[] { actualType });
- resource = method.Invoke(resource, new[] { resource });
- }
+ var bp = valueProvider.TargetProperty as BindableProperty;
+ var pi = valueProvider.TargetProperty as PropertyInfo;
+ var propertyType = bp?.ReturnType ?? pi?.PropertyType;
+ if (propertyType == null)
+ return resource;
+ if (propertyType.IsAssignableFrom(resource.GetType()))
+ return resource;
+ var implicit_op = resource.GetType().GetRuntimeMethod("op_Implicit", new [] { resource.GetType() });
+ //This will invoke the op_implicit on OnPlatform<>
+ if (implicit_op != null && propertyType.IsAssignableFrom(implicit_op.ReturnType))
+ return implicit_op.Invoke(resource, new [] { resource });
return resource;
}
diff --git a/Xamarin.Forms.Xaml/XamlServiceProvider.cs b/Xamarin.Forms.Xaml/XamlServiceProvider.cs
index 77ed6345..5599cbbc 100644
--- a/Xamarin.Forms.Xaml/XamlServiceProvider.cs
+++ b/Xamarin.Forms.Xaml/XamlServiceProvider.cs
@@ -89,7 +89,7 @@ namespace Xamarin.Forms.Xaml.Internals
}
}
- internal class XamlValueTargetProvider : IProvideParentValues, IProvideValueTarget
+ class XamlValueTargetProvider : IProvideParentValues, IProvideValueTarget
{
public XamlValueTargetProvider(object targetObject, INode node, HydratationContext context, object targetProperty)
{
@@ -102,14 +102,8 @@ namespace Xamarin.Forms.Xaml.Internals
INode Node { get; }
HydratationContext Context { get; }
-
public object TargetObject { get; }
-
- public object TargetProperty
- {
- get { throw new NotImplementedException(); }
- private set { }
- }
+ public object TargetProperty { get; } = null;
IEnumerable<object> IProvideParentValues.ParentObjects
{
@@ -141,15 +135,17 @@ namespace Xamarin.Forms.Xaml.Internals
public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget
{
readonly object[] objectAndParents;
+ readonly object targetProperty;
- public SimpleValueTargetProvider(object[] objectAndParents)
+ public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty)
{
if (objectAndParents == null)
- throw new ArgumentNullException("objectAndParents");
+ throw new ArgumentNullException(nameof(objectAndParents));
if (objectAndParents.Length == 0)
throw new ArgumentException();
this.objectAndParents = objectAndParents;
+ this.targetProperty = targetProperty;
}
IEnumerable<object> IProvideParentValues.ParentObjects
@@ -164,7 +160,7 @@ namespace Xamarin.Forms.Xaml.Internals
object IProvideValueTarget.TargetProperty
{
- get { throw new NotImplementedException(); }
+ get { return targetProperty; }
}
}
@@ -249,7 +245,7 @@ namespace Xamarin.Forms.Xaml.Internals
XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly, out XamlParseException exception);
}
- internal class XamlRootObjectProvider : IRootObjectProvider
+ class XamlRootObjectProvider : IRootObjectProvider
{
public XamlRootObjectProvider(object rootObject)
{
@@ -269,7 +265,7 @@ namespace Xamarin.Forms.Xaml.Internals
public IXmlLineInfo XmlLineInfo { get; }
}
- internal interface INameScopeProvider
+ interface INameScopeProvider
{
INameScope NameScope { get; }
}