maui-linux/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs

1467 строки
66 KiB
C#

using System;
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;
using static Mono.Cecil.Cil.Instruction;
using static Mono.Cecil.Cil.OpCodes;
namespace Xamarin.Forms.Build.Tasks
{
class SetPropertiesVisitor : IXamlNodeVisitor
{
static int dtcount;
static int typedBindingCount;
static readonly IList<XmlName> skips = new List<XmlName>
{
XmlName.xKey,
XmlName.xTypeArguments,
XmlName.xArguments,
XmlName.xFactoryMethod,
XmlName.xName,
XmlName.xDataType
};
public SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = false)
{
Context = context;
Module = context.Body.Method.Module;
StopOnResourceDictionary = stopOnResourceDictionary;
}
public ILContext Context { get; }
public bool StopOnResourceDictionary { get; }
public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
public bool StopOnDataTemplate => true;
public bool VisitNodeOnDataTemplate => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
{
var parentVar = Context.Variables[(IElementNode)node];
return parentVar.VariableType.FullName == "Xamarin.Forms.ResourceDictionary"
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Xamarin.Forms.ResourceDictionary";
}
ModuleDefinition Module { get; }
public void Visit(ValueNode node, INode parentNode)
{
//TODO support Label text as element
XmlName propertyName;
if (!TryGetPropertyName(node, parentNode, out propertyName))
{
if (!IsCollectionItem(node, parentNode))
return;
string contentProperty;
if (!Context.Variables.ContainsKey((IElementNode)parentNode))
return;
var parentVar = Context.Variables[(IElementNode)parentNode];
if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null)
propertyName = new XmlName(((IElementNode)parentNode).NamespaceURI, contentProperty);
else
return;
}
if (TrySetRuntimeName(propertyName, Context.Variables[(IElementNode)parentNode], node))
return;
if (skips.Contains(propertyName))
return;
if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
return;
if (propertyName.Equals(XamlParser.McUri, "Ignorable"))
return;
Context.IL.Append(SetPropertyValue(Context.Variables [(IElementNode)parentNode], propertyName, node, Context, node));
}
public void Visit(MarkupNode node, INode parentNode)
{
}
public void Visit(ElementNode node, INode parentNode)
{
XmlName propertyName = XmlName.Empty;
//Simplify ListNodes with single elements
var pList = parentNode as ListNode;
if (pList != null && pList.CollectionItems.Count == 1) {
propertyName = pList.XmlName;
parentNode = parentNode.Parent;
}
if ((propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName)) && skips.Contains(propertyName))
return;
if (propertyName == XmlName._CreateContent) {
SetDataTemplate((IElementNode)parentNode, node, Context, node);
return;
}
//if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable
var vardef = Context.Variables[node];
var vardefref = new VariableDefinitionReference(vardef);
var localName = propertyName.LocalName;
TypeReference declaringTypeReference = null;
FieldReference bpRef = null;
var _ = false;
PropertyDefinition propertyRef = null;
if (parentNode is IElementNode && propertyName != XmlName.Empty) {
bpRef = GetBindablePropertyReference(Context.Variables [(IElementNode)parentNode], propertyName.NamespaceURI, ref localName, out _, 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;
Context.Body.Variables.Add(vardef);
Context.Variables[node] = vardef;
}
if (propertyName != XmlName.Empty) {
if (skips.Contains(propertyName))
return;
if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
return;
Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
}
else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) {
var parentVar = Context.Variables[(IElementNode)parentNode];
string contentProperty;
if (CanAddToResourceDictionary(parentVar, parentVar.VariableType, node, node, Context)) {
Context.IL.Emit(Ldloc, parentVar);
Context.IL.Append(AddToResourceDictionary(node, node, Context));
}
// Collection element, implicit content, or implicit collection element.
else if ( parentVar.VariableType.ImplementsInterface(Module.ImportReference(("mscorlib", "System.Collections", "IEnumerable")))
&& parentVar.VariableType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).Any()) {
var elementType = parentVar.VariableType;
var adderTuple = elementType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First();
var adderRef = Module.ImportReference(adderTuple.Item1);
adderRef = Module.ImportReference(adderRef.ResolveGenericParameters(adderTuple.Item2, Module));
Context.IL.Emit(Ldloc, parentVar);
Context.IL.Emit(Ldloc, vardef);
Context.IL.Emit(Callvirt, adderRef);
if (adderRef.ReturnType.FullName != "System.Void")
Context.IL.Emit(Pop);
}
else if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null) {
var name = new XmlName(node.NamespaceURI, contentProperty);
if (skips.Contains(name))
return;
if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
return;
Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], name, node, Context, node));
}
else
throw new XamlParseException($"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute", node);
}
else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
{
// IL_000d: ldloc.2
// IL_000e: callvirt instance class [mscorlib]System.Collections.Generic.IList`1<!0> class [Xamarin.Forms.Core]Xamarin.Forms.Layout`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::get_Children()
// IL_0013: ldloc.0
// IL_0014: callvirt instance void class [mscorlib]System.Collections.Generic.ICollection`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::Add(!0)
var parentList = (ListNode)parentNode;
var parent = Context.Variables[((IElementNode)parentNode.Parent)];
if (skips.Contains(parentList.XmlName))
return;
if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
return;
var elementType = parent.VariableType;
var localname = parentList.XmlName.LocalName;
TypeReference propertyType;
Context.IL.Append(GetPropertyValue(parent, parentList.XmlName, Context, node, out propertyType));
if (CanAddToResourceDictionary(parent, propertyType, node, node, Context)) {
Context.IL.Append(AddToResourceDictionary(node, node, Context));
return;
}
var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).FirstOrDefault();
if (adderTuple == null)
throw new XamlParseException($"Can not Add() elements to {parent.VariableType}.{localname}", node);
var adderRef = Module.ImportReference(adderTuple.Item1);
adderRef = Module.ImportReference(adderRef.ResolveGenericParameters(adderTuple.Item2, Module));
Context.IL.Emit(OpCodes.Ldloc, vardef);
Context.IL.Emit(OpCodes.Callvirt, adderRef);
if (adderRef.ReturnType.FullName != "System.Void")
Context.IL.Emit(OpCodes.Pop);
}
}
public void Visit(RootNode node, INode parentNode)
{
}
public void Visit(ListNode node, INode parentNode)
{
}
public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name)
{
name = default(XmlName);
var parentElement = parentNode as IElementNode;
if (parentElement == null)
return false;
foreach (var kvp in parentElement.Properties)
{
if (kvp.Value != node)
continue;
name = kvp.Key;
return true;
}
return false;
}
static bool IsCollectionItem(INode node, INode parentNode)
{
var parentList = parentNode as IListNode;
if (parentList == null)
return false;
return parentList.CollectionItems.Contains(node);
}
internal static string GetContentProperty(TypeReference typeRef)
{
var typeDef = typeRef.ResolveCached();
var attributes = typeDef.CustomAttributes;
var attr =
attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName));
if (attr != null)
return attr.ConstructorArguments[0].Value as string;
if (typeDef.BaseType == null)
return null;
return GetContentProperty(typeDef.BaseType);
}
public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ILContext context,
ModuleDefinition module, ElementNode node, FieldReference bpRef = null,
PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null)
{
GenericInstanceType markupExtension;
IList<TypeReference> genericArguments;
if (vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.ArrayExtension" &&
vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1",
out markupExtension, out genericArguments))
{
var markExt = markupExtension.ResolveCached();
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
var provideValue = module.ImportReference(provideValueInfo);
provideValue =
module.ImportReference(provideValue.ResolveGenericParameters(markupExtension, module));
var typeNode = node.Properties[new XmlName("", "Type")];
TypeReference arrayTypeRef;
if (context.TypeExtensions.TryGetValue(typeNode, out arrayTypeRef))
vardefref.VariableDefinition = new VariableDefinition(module.ImportReference(arrayTypeRef.MakeArrayType()));
else
vardefref.VariableDefinition = new VariableDefinition(module.ImportReference(genericArguments.First()));
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
if (arrayTypeRef != null)
yield return Instruction.Create(OpCodes.Castclass, module.ImportReference(arrayTypeRef.MakeArrayType()));
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
}
else if (vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1",
out markupExtension, out genericArguments))
{
var acceptEmptyServiceProvider = vardefref.VariableDefinition.VariableType.GetCustomAttribute(module, ("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
if ( vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.BindingExtension"
&& ( node.Properties == null
|| !node.Properties.ContainsKey(new XmlName("", "Source"))))
foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition))
yield return instruction;
var markExt = markupExtension.ResolveCached();
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
var provideValue = module.ImportReference(provideValueInfo);
provideValue =
module.ImportReference(provideValue.ResolveGenericParameters(markupExtension, module));
vardefref.VariableDefinition = new VariableDefinition(module.ImportReference(genericArguments.First()));
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
if (acceptEmptyServiceProvider)
yield return Instruction.Create(OpCodes.Ldnull);
else
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);
}
else if (context.Variables[node].VariableType.ImplementsInterface(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "IMarkupExtension"))))
{
var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(module, ("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
yield return Create(Ldloc, context.Variables[node]);
if (acceptEmptyServiceProvider)
yield return Create(Ldnull);
else
foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "IMarkupExtension"),
methodName: "ProvideValue",
parameterTypes: new[] { ("System.ComponentModel", "System", "IServiceProvider") }));
yield return Create(Stloc, vardefref.VariableDefinition);
}
else if (context.Variables[node].VariableType.ImplementsInterface(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "IValueProvider"))))
{
var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(module, ("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
var valueProviderType = context.Variables[node].VariableType;
//If the IValueProvider has a ProvideCompiledAttribute that can be resolved, shortcut this
var compiledValueProviderName = valueProviderType?.GetCustomAttribute(module, ("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "ProvideCompiledAttribute"))?.ConstructorArguments?[0].Value as string;
Type compiledValueProviderType;
if (compiledValueProviderName != null && (compiledValueProviderType = Type.GetType(compiledValueProviderName)) != null) {
var compiledValueProvider = Activator.CreateInstance(compiledValueProviderType);
var cProvideValue = typeof(ICompiledValueProvider).GetMethods().FirstOrDefault(md => md.Name == "ProvideValue");
var instructions = (IEnumerable<Instruction>)cProvideValue.Invoke(compiledValueProvider, new object[] {
vardefref,
context.Body.Method.Module,
node as BaseNode,
context});
foreach (var i in instructions)
yield return i;
yield break;
}
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
yield return Create(Ldloc, context.Variables[node]);
if (acceptEmptyServiceProvider)
yield return Create(Ldnull);
else
foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
yield return instruction;
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms.Xaml", "IValueProvider"),
methodName: "ProvideValue",
parameterTypes: new[] { ("System.ComponentModel", "System", "IServiceProvider") }));
yield return Create(Stloc, vardefref.VariableDefinition);
}
}
//Once we get compiled IValueProvider, this will move to the BindingExpression
static IEnumerable<Instruction> CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt)
{
//TODO support casting operators
var module = context.Module;
INode pathNode;
if (!node.Properties.TryGetValue(new XmlName("", "Path"), out pathNode) && node.CollectionItems.Any())
pathNode = node.CollectionItems [0];
var path = (pathNode as ValueNode)?.Value as string;
BindingMode declaredmode;
if ( !node.Properties.TryGetValue(new XmlName("", "Mode"), out INode modeNode)
|| !Enum.TryParse((modeNode as ValueNode)?.Value as string, true, out declaredmode))
declaredmode = BindingMode.TwoWay; //meaning the mode isn't specified in the Binding extension. generate getters, setters, handlers
INode dataTypeNode = null;
IElementNode n = node;
while (n != null) {
if (n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode))
break;
n = n.Parent as IElementNode;
}
var dataType = (dataTypeNode as ValueNode)?.Value as string;
if (dataType == null)
yield break; //throw
var prefix = dataType.Contains(":") ? dataType.Substring(0, dataType.IndexOf(":", StringComparison.Ordinal)) : "";
var namespaceuri = node.NamespaceResolver.LookupNamespace(prefix) ?? "";
if (!string.IsNullOrEmpty(prefix) && string.IsNullOrEmpty(namespaceuri))
throw new XamlParseException($"Undeclared xmlns prefix '{prefix}'", dataTypeNode as IXmlLineInfo);
var dtXType = new XmlType(namespaceuri, dataType, null);
var tSourceRef = dtXType.GetTypeReference(module, (IXmlLineInfo)node);
if (tSourceRef == null)
yield break; //throw
var properties = ParsePath(path, tSourceRef, node as IXmlLineInfo, module);
var tPropertyRef = properties != null && properties.Any() ? properties.Last().Item1.PropertyType : tSourceRef;
tPropertyRef = module.ImportReference(tPropertyRef);
var funcRef = module.ImportReference(module.ImportReference(("mscorlib", "System", "Func`2")).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef }));
var actionRef = module.ImportReference(module.ImportReference(("mscorlib", "System", "Action`2")).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef }));
var funcObjRef = module.ImportReference(module.ImportReference(("mscorlib", "System", "Func`2")).MakeGenericInstanceType(new [] { tSourceRef, module.TypeSystem.Object }));
var tupleRef = module.ImportReference(module.ImportReference(("mscorlib", "System", "Tuple`2")).MakeGenericInstanceType(new [] { funcObjRef, module.TypeSystem.String}));
var typedBindingRef = module.ImportReference(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "TypedBinding`2")).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef}));
var ctorInfo = module.ImportReference(typedBindingRef.ResolveCached().Methods.FirstOrDefault(md => md.IsConstructor && !md.IsStatic && md.Parameters.Count == 3 ));
var ctorinforef = ctorInfo.MakeGeneric(typedBindingRef, funcRef, actionRef, tupleRef);
yield return Instruction.Create(OpCodes.Ldloc, bindingExt);
foreach (var instruction in CompiledBindingGetGetter(tSourceRef, tPropertyRef, properties, node, context))
yield return instruction;
if (declaredmode != BindingMode.OneTime && declaredmode != BindingMode.OneWay) { //if the mode is explicitly 1w, or 1t, no need for setters
foreach (var instruction in CompiledBindingGetSetter(tSourceRef, tPropertyRef, properties, node, context))
yield return instruction;
} else
yield return Create(Ldnull);
if (declaredmode != BindingMode.OneTime) { //if the mode is explicitly 1t, no need for handlers
foreach (var instruction in CompiledBindingGetHandlers(tSourceRef, tPropertyRef, properties, node, context))
yield return instruction;
} else
yield return Create(Ldnull);
yield return Instruction.Create(OpCodes.Newobj, module.ImportReference(ctorinforef));
yield return Instruction.Create(OpCodes.Callvirt, module.ImportPropertySetterReference(("Xamarin.Forms.Xaml", "Xamarin.Forms.Xaml", "BindingExtension"), propertyName: "TypedBinding"));
}
static IList<Tuple<PropertyDefinition, string>> ParsePath(string path, TypeReference tSourceRef, IXmlLineInfo lineInfo, ModuleDefinition module)
{
if (string.IsNullOrWhiteSpace(path))
return null;
path = path.Trim(' ', '.'); //trim leading or trailing dots
var parts = path.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries);
var properties = new List<Tuple<PropertyDefinition, string>>();
var previousPartTypeRef = tSourceRef;
TypeReference _;
foreach (var part in parts) {
var p = part;
string indexArg = null;
var lbIndex = p.IndexOf('[');
if (lbIndex != -1) {
var rbIndex = p.LastIndexOf(']');
if (rbIndex == -1)
throw new XamlParseException("Binding: Indexer did not contain closing bracket", lineInfo);
var argLength = rbIndex - lbIndex - 1;
if (argLength == 0)
throw new XamlParseException("Binding: Indexer did not contain arguments", lineInfo);
indexArg = p.Substring(lbIndex + 1, argLength).Trim();
if (indexArg.Length == 0)
throw new XamlParseException("Binding: Indexer did not contain arguments", lineInfo);
p = p.Substring(0, lbIndex);
p = p.Trim();
}
if (p.Length > 0) {
var property = previousPartTypeRef.GetProperty(pd => pd.Name == p && pd.GetMethod != null && pd.GetMethod.IsPublic, out _)
?? throw new XamlParseException($"Binding: Property '{p}' not found on '{previousPartTypeRef}'", lineInfo);
properties.Add(new Tuple<PropertyDefinition, string>(property,null));
previousPartTypeRef = property.PropertyType;
}
if (indexArg != null) {
var defaultMemberAttribute = previousPartTypeRef.GetCustomAttribute(module, ("mscorlib", "System.Reflection", "DefaultMemberAttribute"));
var indexerName = defaultMemberAttribute?.ConstructorArguments?.FirstOrDefault().Value as string ?? "Item";
var indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName && pd.GetMethod != null && pd.GetMethod.IsPublic, out _);
properties.Add(new Tuple<PropertyDefinition, string>(indexer, indexArg));
if (indexer.PropertyType != module.TypeSystem.String && indexer.PropertyType != module.TypeSystem.Int32)
throw new XamlParseException($"Binding: Unsupported indexer index type: {indexer.PropertyType.FullName}", lineInfo);
previousPartTypeRef = indexer.PropertyType;
}
}
return properties;
}
static IEnumerable<Instruction> CompiledBindingGetGetter(TypeReference tSourceRef, TypeReference tPropertyRef, IList<Tuple<PropertyDefinition, string>> properties, ElementNode node, ILContext context)
{
// .method private static hidebysig default string '<Main>m__0' (class ViewModel vm) cil managed
// {
// .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // ...
//
// IL_0000: ldarg.0
// IL_0001: callvirt instance class ViewModel class ViewModel::get_Model()
// IL_0006: callvirt instance string class ViewModel::get_Text()
// IL_0006: ret
// }
var module = context.Module;
var getter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}",
MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static,
tPropertyRef) {
Parameters = {
new ParameterDefinition(tSourceRef)
},
CustomAttributes = {
new CustomAttribute (module.ImportCtorReference(("mscorlib", "System.Runtime.CompilerServices", "CompilerGeneratedAttribute"), parameterTypes: null))
}
};
getter.Body.InitLocals = true;
var il = getter.Body.GetILProcessor();
if (properties == null || properties.Count == 0) { //return self
il.Emit(Ldarg_0);
il.Emit(Ret);
}
else {
if (tSourceRef.IsValueType)
il.Emit(Ldarga_S, (byte)0);
else
il.Emit(Ldarg_0);
foreach (var propTuple in properties) {
var property = propTuple.Item1;
var indexerArg = propTuple.Item2;
if (indexerArg != null) {
if (property.GetMethod.Parameters[0].ParameterType == module.TypeSystem.String)
il.Emit(Ldstr, indexerArg);
else if (property.GetMethod.Parameters[0].ParameterType == module.TypeSystem.Int32) {
int index;
if (!int.TryParse(indexerArg, out index))
throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo);
il.Emit(Ldc_I4, index);
}
}
if (property.GetMethod.IsVirtual)
il.Emit(Callvirt, module.ImportReference(property.GetMethod));
else
il.Emit(Call, module.ImportReference(property.GetMethod));
}
il.Emit(Ret);
}
context.Body.Method.DeclaringType.Methods.Add(getter);
// IL_0007: ldnull
// IL_0008: ldftn string class Test::'<Main>m__0'(class ViewModel)
// IL_000e: newobj instance void class [mscorlib]System.Func`2<class ViewModel, string>::'.ctor'(object, native int)
yield return Create(Ldnull);
yield return Create(Ldftn, getter);
yield return Create(Newobj, module.ImportCtorReference(("mscorlib", "System", "Func`2"), paramCount: 2, classArguments: new[] { tSourceRef, tPropertyRef }));
}
static IEnumerable<Instruction> CompiledBindingGetSetter(TypeReference tSourceRef, TypeReference tPropertyRef, IList<Tuple<PropertyDefinition, string>> properties, ElementNode node, ILContext context)
{
if (properties == null || properties.Count == 0) {
yield return Create(Ldnull);
yield break;
}
// .method private static hidebysig default void '<Main>m__1' (class ViewModel vm, string s) cil managed
// {
// .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // ....
//
// IL_0000: ldarg.0
// IL_0001: callvirt instance class ViewModel class ViewModel::get_Model()
// IL_0006: ldarg.1
// IL_0007: callvirt instance void class ViewModel::set_Text(string)
// IL_000c: ret
// }
var module = context.Module;
var setter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}",
MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static,
module.TypeSystem.Void) {
Parameters = {
new ParameterDefinition(tSourceRef),
new ParameterDefinition(tPropertyRef)
},
CustomAttributes = {
new CustomAttribute (module.ImportCtorReference(("mscorlib", "System.Runtime.CompilerServices", "CompilerGeneratedAttribute"), parameterTypes: null))
}
};
setter.Body.InitLocals = true;
var il = setter.Body.GetILProcessor();
var lastProperty = properties.LastOrDefault();
var setterRef = lastProperty?.Item1.SetMethod;
if (setterRef == null) {
yield return Create(Ldnull); //throw or not ?
yield break;
}
if (tSourceRef.IsValueType)
il.Emit(Ldarga_S, (byte)0);
else
il.Emit(Ldarg_0);
for (int i = 0; i < properties.Count - 1; i++) {
var property = properties[i].Item1;
var indexerArg = properties[i].Item2;
if (indexerArg != null) {
if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String)
il.Emit(Ldstr, indexerArg);
else if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) {
int index;
if (!int.TryParse(indexerArg, out index))
throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo);
il.Emit(Ldc_I4, index);
}
}
if (property.GetMethod.IsVirtual)
il.Emit(Callvirt, module.ImportReference(property.GetMethod));
else
il.Emit(Call, module.ImportReference(property.GetMethod));
}
var indexer = properties.Last().Item2;
if (indexer != null) {
if (lastProperty.Item1.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String)
il.Emit(Ldstr, indexer);
else if (lastProperty.Item1.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) {
int index;
if (!int.TryParse(indexer, out index))
throw new XamlParseException($"Binding: {indexer} could not be parsed as an index for a {lastProperty.Item1.Name}", node as IXmlLineInfo);
il.Emit(Ldc_I4, index);
}
}
il.Emit(Ldarg_1);
if (setterRef.IsVirtual)
il.Emit(Callvirt, module.ImportReference(setterRef));
else
il.Emit(Call, module.ImportReference(setterRef));
il.Emit(Ret);
context.Body.Method.DeclaringType.Methods.Add(setter);
// IL_0024: ldnull
// IL_0025: ldftn void class Test::'<Main>m__1'(class ViewModel, string)
// IL_002b: newobj instance void class [mscorlib]System.Action`2<class ViewModel, string>::'.ctor'(object, native int)
yield return Create(Ldnull);
yield return Create(Ldftn, setter);
yield return Create(Newobj, module.ImportCtorReference(("mscorlib", "System", "Action`2"),
paramCount: 2,
classArguments:
new[] { tSourceRef, tPropertyRef }));
}
static IEnumerable<Instruction> CompiledBindingGetHandlers(TypeReference tSourceRef, TypeReference tPropertyRef, IList<Tuple<PropertyDefinition, string>> properties, ElementNode node, ILContext context)
{
// .method private static hidebysig default object '<Main>m__2'(class ViewModel vm) cil managed {
// .custom instance void class [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // ....
// IL_0000: ldarg.0
// IL_0001: ret
// } // end of method Test::<Main>m__2
// .method private static hidebysig default object '<Main>m__3' (class ViewModel vm) cil managed {
// .custom instance void class [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // ....
// IL_0000: ldarg.0
// IL_0001: callvirt instance class ViewModel class ViewModel::get_Model()
// IL_0006: ret
// }
var module = context.Module;
var partGetters = new List<MethodDefinition>();
if (properties == null || properties.Count == 0) {
yield return Instruction.Create(OpCodes.Ldnull);
yield break;
}
for (int i = 0; i < properties.Count; i++) {
var tuple = properties [i];
var partGetter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, module.TypeSystem.Object) {
Parameters = {
new ParameterDefinition(tSourceRef)
},
CustomAttributes = {
new CustomAttribute (module.ImportCtorReference(("mscorlib", "System.Runtime.CompilerServices", "CompilerGeneratedAttribute"), parameterTypes: null))
}
};
partGetter.Body.InitLocals = true;
var il = partGetter.Body.GetILProcessor();
if (i == 0) { //return self
il.Emit(Ldarg_0);
if (tSourceRef.IsValueType)
il.Emit(Box, module.ImportReference(tSourceRef));
il.Emit(Ret);
context.Body.Method.DeclaringType.Methods.Add(partGetter);
partGetters.Add(partGetter);
continue;
}
if (tSourceRef.IsValueType)
il.Emit(Ldarga_S, (byte)0);
else
il.Emit(Ldarg_0);
var lastGetterTypeRef = tSourceRef;
for (int j = 0; j < i; j++) {
var propTuple = properties [j];
var property = propTuple.Item1;
var indexerArg = propTuple.Item2;
if (indexerArg != null) {
if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String)
il.Emit(OpCodes.Ldstr, indexerArg);
else if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) {
int index;
if (!int.TryParse(indexerArg, out index))
throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo);
il.Emit(OpCodes.Ldc_I4, index);
}
}
if (property.GetMethod.IsVirtual)
il.Emit(Callvirt, module.ImportReference(property.GetMethod));
else
il.Emit(Call, module.ImportReference(property.GetMethod));
lastGetterTypeRef = property.PropertyType;
}
if (lastGetterTypeRef.IsValueType)
il.Emit(Box, module.ImportReference(lastGetterTypeRef));
il.Emit(OpCodes.Ret);
context.Body.Method.DeclaringType.Methods.Add(partGetter);
partGetters.Add(partGetter);
}
var funcObjRef = context.Module.ImportReference(module.ImportReference(("mscorlib", "System", "Func`2")).MakeGenericInstanceType(new [] { tSourceRef, module.TypeSystem.Object }));
var tupleRef = context.Module.ImportReference(module.ImportReference(("mscorlib", "System", "Tuple`2")).MakeGenericInstanceType(new [] { funcObjRef, module.TypeSystem.String }));
var funcCtor = module.ImportReference(funcObjRef.ResolveCached().GetConstructors().First());
funcCtor = funcCtor.MakeGeneric(funcObjRef, new [] { tSourceRef, module.TypeSystem.Object });
var tupleCtor = module.ImportReference(tupleRef.ResolveCached().GetConstructors().First());
tupleCtor = tupleCtor.MakeGeneric(tupleRef, new [] { funcObjRef, module.TypeSystem.String});
// IL_003a: ldc.i4.2
// IL_003b: newarr class [mscorlib] System.Tuple`2<class [mscorlib]System.Func`2<class ViewModel,object>,string>
// IL_0040: dup
// IL_0041: ldc.i4.0
// IL_0049: ldnull
// IL_004a: ldftn object class Test::'<Main>m__2'(class ViewModel)
// IL_0050: newobj instance void class [mscorlib]System.Func`2<class ViewModel, object>::'.ctor'(object, native int)
// IL_005f: ldstr "Model"
// IL_0064: newobj instance void class [mscorlib]System.Tuple`2<class [mscorlib]System.Func`2<class ViewModel, object>, string>::'.ctor'(!0, !1)
// IL_0069: stelem.ref
// IL_006a: dup
// IL_006b: ldc.i4.1
// IL_0073: ldnull
// IL_0074: ldftn object class Test::'<Main>m__3'(class ViewModel)
// IL_007a: newobj instance void class [mscorlib]System.Func`2<class ViewModel, object>::'.ctor'(object, native int)
// IL_0089: ldstr "Text"
// IL_008e: newobj instance void class [mscorlib]System.Tuple`2<class [mscorlib]System.Func`2<class ViewModel, object>, string>::'.ctor'(!0, !1)
// IL_0093: stelem.ref
yield return Instruction.Create(OpCodes.Ldc_I4, properties.Count);
yield return Instruction.Create(OpCodes.Newarr, tupleRef);
for (var i = 0; i < properties.Count; i++) {
yield return Instruction.Create(OpCodes.Dup);
yield return Instruction.Create(OpCodes.Ldc_I4, i);
yield return Instruction.Create(OpCodes.Ldnull);
yield return Instruction.Create(OpCodes.Ldftn, partGetters [i]);
yield return Instruction.Create(OpCodes.Newobj, module.ImportReference(funcCtor));
yield return Instruction.Create(OpCodes.Ldstr, properties [i].Item1.Name);
yield return Instruction.Create(OpCodes.Newobj, module.ImportReference(tupleCtor));
yield return Instruction.Create(OpCodes.Stelem_Ref);
}
}
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;
bool attached;
var bpRef = GetBindablePropertyReference(parent, propertyName.NamespaceURI, ref localName, out attached, context, iXmlLineInfo);
//If the target is an event, connect
if (CanConnectEvent(parent, localName, attached))
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, attached, 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, propertyName, valueNode, iXmlLineInfo, context))
return Add(parent, propertyName, valueNode, iXmlLineInfo, context);
throw new XamlParseException($"No property, bindable property, or event found for '{localName}', or mismatching type between value and property.", iXmlLineInfo);
}
public static IEnumerable<Instruction> GetPropertyValue(VariableDefinition parent, XmlName propertyName, ILContext context, IXmlLineInfo lineInfo, out TypeReference propertyType)
{
var module = context.Body.Method.Module;
var localName = propertyName.LocalName;
bool attached;
var bpRef = GetBindablePropertyReference(parent, propertyName.NamespaceURI, ref localName, out attached, context, lineInfo);
//If it's a BP, GetValue ()
if (CanGetValue(parent, bpRef, attached, lineInfo, context, out _))
return GetValue(parent, bpRef, lineInfo, context, out propertyType);
//If it's a property, set it
if (CanGet(parent, localName, context, out _))
return Get(parent, localName, lineInfo, context, out propertyType);
throw new XamlParseException($"Property {localName} is not found or does not have an accessible getter", lineInfo);
}
static FieldReference GetBindablePropertyReference(VariableDefinition parent, string namespaceURI, ref string localName, out bool attached, 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;
attached = GetNameAndTypeRef(ref bpOwnerType, namespaceURI, ref localName, context, iXmlLineInfo);
var name = $"{localName}Property";
FieldReference bpRef = bpOwnerType.GetField(fd => fd.Name == name &&
fd.IsStatic &&
(fd.IsPublic || fd.IsAssembly), out declaringTypeReference);
if (bpRef != null) {
bpRef = module.ImportReference(bpRef.ResolveGenericParameters(declaringTypeReference));
bpRef.FieldType = module.ImportReference(bpRef.FieldType);
}
return bpRef;
}
static bool CanConnectEvent(VariableDefinition parent, string localName, bool attached)
{
return !attached && parent.VariableType.GetEvent(ed => ed.Name == localName, out _) != 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;
TypeReference eventDeclaringTypeRef;
var eventinfo = elementType.GetEvent(ed => ed.Name == localName, out eventDeclaringTypeRef);
// 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
// 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 value = ((ValueNode)valueNode).Value;
yield return Create(Ldloc, parent);
if (context.Root is VariableDefinition)
yield return Create(Ldloc, context.Root as VariableDefinition);
else if (context.Root is FieldDefinition) {
yield return Create(Ldarg_0);
yield return Create(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);
//check if the handler signature matches the Invoke signature;
var invoke = module.ImportReference(eventinfo.EventType.ResolveCached().GetMethods().First(md => md.Name == "Invoke"));
invoke = invoke.ResolveGenericParameters(eventinfo.EventType, module);
if (!handler.ReturnType.InheritsFromOrImplements(invoke.ReturnType))
throw new XamlParseException($"Signature (return type) of EventHandler \"{context.Body.Method.DeclaringType.FullName}.{value}\" doesn't match the event type", iXmlLineInfo);
if (invoke.Parameters.Count != handler.Parameters.Count)
throw new XamlParseException($"Signature (number of arguments) of EventHandler \"{context.Body.Method.DeclaringType.FullName}.{value}\" doesn't match the event type", iXmlLineInfo);
if (!invoke.ContainsGenericParameter)
for (var i = 0; i < invoke.Parameters.Count;i++)
if (!handler.Parameters[i].ParameterType.InheritsFromOrImplements(invoke.Parameters[i].ParameterType))
throw new XamlParseException($"Signature (parameter {i}) of EventHandler \"{context.Body.Method.DeclaringType.FullName}.{value}\" doesn't match the event type", iXmlLineInfo);
if (handler.IsVirtual) {
yield return Create(Ldarg_0);
yield return Create(Ldvirtftn, handler);
} else
yield return Create(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.ImportReference(eventinfo.EventType.ResolveCached().GetConstructors().First());
ctor = ctor.ResolveGenericParameters(eventinfo.EventType, module);
yield return Create(Newobj, module.ImportReference(ctor));
//Check if the handler has the same signature as the ctor (it should)
var adder = module.ImportReference(eventinfo.AddMethod);
adder = adder.ResolveGenericParameters(eventDeclaringTypeRef, module);
yield return Create(Callvirt, module.ImportReference(adder));
}
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;
}
static IEnumerable<Instruction> SetDynamicResource(VariableDefinition parent, FieldReference bpRef, IElementNode elementNode, IXmlLineInfo iXmlLineInfo, ILContext context)
{
var module = context.Body.Method.Module;
yield return Create(Ldloc, parent);
yield return Create(Ldsfld, bpRef);
yield return Create(Ldloc, context.Variables[elementNode]);
yield return Create(Callvirt, module.ImportPropertyGetterReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "DynamicResource"), propertyName: "Key"));
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "IDynamicResourceHandler"),
methodName: "SetDynamicResource",
parameterTypes: new[] {
("Xamarin.Forms.Core", "Xamarin.Forms", "BindableProperty"),
("mscorlib", "System", "String"),
}));
}
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;
VariableDefinition varValue;
if (!context.Variables.TryGetValue(valueNode as IElementNode, out varValue))
return false;
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(module.ImportReference(("Xamarin.Forms.Core","Xamarin.Forms","BindingBase")), module);
if (implicitOperator != null)
return true;
return varValue.VariableType.InheritsFromOrImplements(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindingBase")));
}
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];
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindingBase")), module);
//TODO: check if parent is a BP
yield return Create(Ldloc, parent);
yield return Create(Ldsfld, bpRef);
yield return Create(Ldloc, varValue);
if (implicitOperator != null)
// IL_000f: call !0 class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<BindingBase>::op_Implicit(class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<!0>)
yield return Create(Call, module.ImportReference(implicitOperator));
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject"),
methodName: "SetBinding",
parameterTypes: new[] {
("Xamarin.Forms.Core", "Xamarin.Forms", "BindableProperty"),
("Xamarin.Forms.Core", "Xamarin.Forms", "BindingBase"),
}));
}
static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
{
var module = context.Body.Method.Module;
if (bpRef == null)
return false;
var valueNode = node as ValueNode;
if (valueNode != null && valueNode.CanConvertValue(context, bpRef))
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 = bpRef.GetBindablePropertyType(iXmlLineInfo, module);
// If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here.
// Worst case scenario ? InvalidCastException at runtime
if (attached && varValue.VariableType.FullName == "System.Object")
return true;
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(bpTypeRef, module);
if (implicitOperator != null)
return true;
//as we're in the SetValue Scenario, we can accept value types, they'll be boxed
if (varValue.VariableType.IsValueType && bpTypeRef.FullName == "System.Object")
return true;
return varValue.VariableType.InheritsFromOrImplements(bpTypeRef);
}
static bool CanGetValue(VariableDefinition parent, FieldReference bpRef, bool attached, IXmlLineInfo iXmlLineInfo, ILContext context, out TypeReference propertyType)
{
var module = context.Body.Method.Module;
propertyType = null;
if (bpRef == null)
return false;
if (!parent.VariableType.InheritsFromOrImplements(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject"))))
return false;
propertyType = bpRef.GetBindablePropertyType(iXmlLineInfo, module);
return true;
}
static IEnumerable<Instruction> SetValue(VariableDefinition parent, FieldReference bpRef, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
{
var valueNode = node as ValueNode;
var elementNode = node as IElementNode;
var module = context.Body.Method.Module;
// 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)
yield return Create(Ldloc, parent);
yield return Create(Ldsfld, bpRef);
if (valueNode != null) {
foreach (var instruction in valueNode.PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef:bpRef), true, false))
yield return instruction;
} else if (elementNode != null) {
var bpTypeRef = bpRef.GetBindablePropertyType(iXmlLineInfo, module);
var varDef = context.Variables[elementNode];
var varType = varDef.VariableType;
var implicitOperator = varDef.VariableType.GetImplicitOperatorTo(bpTypeRef, module);
yield return Create(Ldloc, varDef);
if (implicitOperator != null) {
yield return Create(Call, module.ImportReference(implicitOperator));
varType = module.ImportReference(bpTypeRef);
}
if (varType.IsValueType)
yield return Create(Box, varType);
}
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject"),
methodName: "SetValue",
parameterTypes: new[] {
("Xamarin.Forms.Core", "Xamarin.Forms", "BindableProperty"),
("mscorlib", "System", "Object"),
}));
}
static IEnumerable<Instruction> GetValue(VariableDefinition parent, FieldReference bpRef, IXmlLineInfo iXmlLineInfo, ILContext context, out TypeReference propertyType)
{
var module = context.Body.Method.Module;
propertyType = bpRef.GetBindablePropertyType(iXmlLineInfo, module);
return new[] {
Create(Ldloc, parent),
Create(Ldsfld, bpRef),
Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject"),
methodName: "GetValue",
parameterTypes: new[] { ("Xamarin.Forms.Core", "Xamarin.Forms", "BindableProperty")})),
};
}
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 propertyType = property.ResolveGenericPropertyType(declaringTypeReference, module);
var propertySetter = property.SetMethod;
if (propertySetter == null || !propertySetter.IsPublic || propertySetter.IsStatic)
return false;
var valueNode = node as ValueNode;
if (valueNode != null && valueNode.CanConvertValue(context, propertyType, new ICustomAttributeProvider[] { property, propertyType.ResolveCached()}))
return true;
var elementNode = node as IElementNode;
if (elementNode == null)
return false;
var vardef = context.Variables [elementNode];
var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(propertyType, module);
if (vardef.VariableType.InheritsFromOrImplements(propertyType))
return true;
if (implicitOperator != null)
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 bool CanGet(VariableDefinition parent, string localName, ILContext context, out TypeReference propertyType)
{
var module = context.Body.Method.Module;
propertyType = null;
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;
module.ImportReference(parent.VariableType.ResolveCached());
var propertyGetterRef = module.ImportReference(module.ImportReference(propertyGetter).ResolveGenericParameters(declaringTypeReference, module));
propertyGetterRef.ImportTypes(module);
propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
return true;
}
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)
module.ImportReference(parent.VariableType.ResolveCached());
var propertySetterRef = module.ImportReference(module.ImportReference(propertySetter).ResolveGenericParameters(declaringTypeReference, module));
propertySetterRef.ImportTypes(module);
var propertyType = property.ResolveGenericPropertyType(declaringTypeReference, module);
var valueNode = node as ValueNode;
var elementNode = node as IElementNode;
//if it's a value type, load the address so we can invoke methods on it
if (parent.VariableType.IsValueType)
yield return Instruction.Create(OpCodes.Ldloca, parent);
else
yield return Instruction.Create(OpCodes.Ldloc, parent);
if (valueNode != null) {
foreach (var instruction in valueNode.PushConvertedValue(context, propertyType, new ICustomAttributeProvider [] { property, propertyType.ResolveCached() }, valueNode.PushServiceProvider(context, propertyRef:property), false, true))
yield return instruction;
if (parent.VariableType.IsValueType)
yield return Instruction.Create(OpCodes.Call, propertySetterRef);
else
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 (!vardef.VariableType.InheritsFromOrImplements(propertyType) && 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.ImportReference(implicitOperator));
} else if (!vardef.VariableType.IsValueType && propertyType.IsValueType)
yield return Instruction.Create(OpCodes.Unbox_Any, module.ImportReference(propertyType));
else if (vardef.VariableType.IsValueType && propertyType.FullName == "System.Object")
yield return Instruction.Create(OpCodes.Box, vardef.VariableType);
if (parent.VariableType.IsValueType)
yield return Instruction.Create(OpCodes.Call, propertySetterRef);
else
yield return Instruction.Create(OpCodes.Callvirt, propertySetterRef);
}
}
static IEnumerable<Instruction> Get(VariableDefinition parent, string localName, IXmlLineInfo iXmlLineInfo, ILContext context, out TypeReference propertyType)
{
var module = context.Body.Method.Module;
var property = parent.VariableType.GetProperty(pd => pd.Name == localName, out var declaringTypeReference);
var propertyGetter = property.GetMethod;
module.ImportReference(parent.VariableType.ResolveCached());
var propertyGetterRef = module.ImportReference(module.ImportReference(propertyGetter).ResolveGenericParameters(declaringTypeReference, module));
propertyGetterRef.ImportTypes(module);
propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
if (parent.VariableType.IsValueType)
return new[] {
Instruction.Create(OpCodes.Ldloca, parent),
Instruction.Create(OpCodes.Call, propertyGetterRef),
};
else
return new[] {
Instruction.Create(OpCodes.Ldloc, parent),
Instruction.Create(OpCodes.Callvirt, propertyGetterRef),
};
}
static bool CanAdd(VariableDefinition parent, XmlName propertyName, INode node, IXmlLineInfo lineInfo, ILContext context)
{
var module = context.Body.Method.Module;
var localName = propertyName.LocalName;
bool attached;
var bpRef = GetBindablePropertyReference(parent, propertyName.NamespaceURI, ref localName, out attached, context, lineInfo);
TypeReference propertyType;
if ( !CanGetValue(parent, bpRef, attached, null, context, out propertyType)
&& !CanGet(parent, localName, context, out propertyType))
return false;
//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 Dictionary<VariableDefinition, IList<string>> resourceNamesInUse = new Dictionary<VariableDefinition, IList<string>>();
static bool CanAddToResourceDictionary(VariableDefinition parent, TypeReference collectionType, IElementNode node, IXmlLineInfo lineInfo, ILContext context)
{
if ( collectionType.FullName != "Xamarin.Forms.ResourceDictionary"
&& collectionType.ResolveCached().BaseType?.FullName != "Xamarin.Forms.ResourceDictionary")
return false;
if (node.Properties.ContainsKey(XmlName.xKey)) {
var key = (node.Properties[XmlName.xKey] as ValueNode).Value as string;
if (!resourceNamesInUse.TryGetValue(parent, out var names))
resourceNamesInUse[parent] = (names = new List<string>());
if (names.Contains(key))
throw new XamlParseException($"A resource with the key '{key}' is already present in the ResourceDictionary.", lineInfo);
names.Add(key);
return true;
}
//is there a RD.Add() overrides that accepts this ?
var nodeTypeRef = context.Variables[node].VariableType;
var module = context.Body.Method.Module;
if (module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "ResourceDictionary"),
methodName: "Add",
parameterTypes: new[] { (nodeTypeRef.Scope.Name, nodeTypeRef.Namespace, nodeTypeRef.Name) }) != null)
return true;
throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", lineInfo);
}
static IEnumerable<Instruction> Add(VariableDefinition parent, XmlName propertyName, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
{
var module = context.Body.Method.Module;
var elementNode = node as IElementNode;
var vardef = context.Variables [elementNode];
TypeReference propertyType;
foreach (var instruction in GetPropertyValue(parent, propertyName, context, iXmlLineInfo, out propertyType))
yield return instruction;
if (CanAddToResourceDictionary(parent, propertyType, elementNode, iXmlLineInfo, context)) {
foreach (var instruction in AddToResourceDictionary(elementNode, iXmlLineInfo, context))
yield return instruction;
yield break;
}
var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
var adderRef = module.ImportReference(adderTuple.Item1);
adderRef = module.ImportReference(adderRef.ResolveGenericParameters(adderTuple.Item2, module));
var childType = GetParameterType(adderRef.Parameters[0]);
var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(childType, module);
yield return Instruction.Create(OpCodes.Ldloc, vardef);
if (implicitOperator != null)
yield return Instruction.Create(OpCodes.Call, module.ImportReference(implicitOperator));
if (implicitOperator == null && vardef.VariableType.IsValueType && !childType.IsValueType)
yield return Instruction.Create(OpCodes.Box, vardef.VariableType);
yield return Instruction.Create(OpCodes.Callvirt, adderRef);
if (adderRef.ReturnType.FullName != "System.Void")
yield return Instruction.Create(OpCodes.Pop);
}
static IEnumerable<Instruction> AddToResourceDictionary(IElementNode node, IXmlLineInfo lineInfo, ILContext context)
{
var module = context.Body.Method.Module;
if (node.Properties.ContainsKey(XmlName.xKey)) {
// IL_0014: ldstr "key"
// IL_0019: ldstr "foo"
// IL_001e: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object)
yield return Create(Ldstr, (node.Properties[XmlName.xKey] as ValueNode).Value as string);
var varDef = context.Variables[node];
yield return Create(Ldloc, varDef);
if (varDef.VariableType.IsValueType)
yield return Create(Box, module.ImportReference(varDef.VariableType));
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "ResourceDictionary"),
methodName: "Add",
parameterTypes: new[] {
("mscorlib", "System", "String"),
("mscorlib", "System", "Object"),
}));
yield break;
}
var nodeTypeRef = context.Variables[node].VariableType;
yield return Create(Ldloc, context.Variables[node]);
yield return Create(Callvirt, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms", "ResourceDictionary"),
methodName: "Add",
parameterTypes: new[] { (nodeTypeRef.Scope.Name, nodeTypeRef.Namespace, nodeTypeRef.Name) }));
yield break;
}
public static TypeReference GetParameterType(ParameterDefinition param)
{
if (!param.ParameterType.IsGenericParameter)
return param.ParameterType;
var type = (param.Method as MethodReference).DeclaringType as GenericInstanceType;
return type.GenericArguments [0];
}
static bool GetNameAndTypeRef(ref TypeReference elementType, string namespaceURI, ref string localname,
ILContext context, IXmlLineInfo lineInfo)
{
var dotIdx = localname.IndexOf('.');
if (dotIdx > 0)
{
var typename = localname.Substring(0, dotIdx);
localname = localname.Substring(dotIdx + 1);
elementType = new XmlType(namespaceURI, typename, null).GetTypeReference(context.Body.Method.Module, lineInfo);
return true;
}
return false;
}
static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext parentContext,
IXmlLineInfo xmlLineInfo)
{
var parentVar = parentContext.Variables[parentNode];
//Push the DataTemplate to the stack, for setting the template
parentContext.IL.Emit(OpCodes.Ldloc, parentVar);
//Create nested class
// .class nested private auto ansi sealed beforefieldinit '<Main>c__AnonStorey0'
// extends [mscorlib]System.Object
var module = parentContext.Module;
var anonType = new TypeDefinition(
null,
"<" + parentContext.Body.Method.Name + ">_anonXamlCDataTemplate_" + dtcount++,
TypeAttributes.BeforeFieldInit |
TypeAttributes.Sealed |
TypeAttributes.NestedPrivate) {
BaseType = module.TypeSystem.Object,
CustomAttributes = {
new CustomAttribute (module.ImportCtorReference(("mscorlib", "System.Runtime.CompilerServices", "CompilerGeneratedAttribute"), parameterTypes: null)),
}
};
parentContext.Body.Method.DeclaringType.NestedTypes.Add(anonType);
var ctor = anonType.AddDefaultConstructor();
var loadTemplate = new MethodDefinition("LoadDataTemplate",
MethodAttributes.Assembly | MethodAttributes.HideBySig,
module.TypeSystem.Object);
loadTemplate.Body.InitLocals = true;
anonType.Methods.Add(loadTemplate);
var parentValues = new FieldDefinition("parentValues", FieldAttributes.Assembly, module.ImportArrayReference(("mscorlib", "System", "Object")));
anonType.Fields.Add(parentValues);
TypeReference rootType = null;
var vdefRoot = parentContext.Root as VariableDefinition;
if (vdefRoot != null)
rootType = vdefRoot.VariableType;
var fdefRoot = parentContext.Root as FieldDefinition;
if (fdefRoot != null)
rootType = fdefRoot.FieldType;
var root = new FieldDefinition("root", FieldAttributes.Assembly, rootType);
anonType.Fields.Add(root);
//Fill the loadTemplate Body
var templateIl = loadTemplate.Body.GetILProcessor();
templateIl.Emit(OpCodes.Nop);
var templateContext = new ILContext(templateIl, loadTemplate.Body, module, parentValues)
{
Root = root
};
node.Accept(new CreateObjectVisitor(templateContext), null);
node.Accept(new SetNamescopesAndRegisterNamesVisitor(templateContext), null);
node.Accept(new SetFieldVisitor(templateContext), null);
node.Accept(new SetResourcesVisitor(templateContext), null);
node.Accept(new SetPropertiesVisitor(templateContext, stopOnResourceDictionary: true), null);
templateIl.Emit(OpCodes.Ldloc, templateContext.Variables[node]);
templateIl.Emit(OpCodes.Ret);
//Instanciate nested class
var parentIl = parentContext.IL;
parentIl.Emit(OpCodes.Newobj, ctor);
//Copy required local vars
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
parentIl.Append(node.PushParentObjectsArray(parentContext));
parentIl.Emit(OpCodes.Stfld, parentValues);
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
if (parentContext.Root is VariableDefinition)
parentIl.Emit(OpCodes.Ldloc, parentContext.Root as VariableDefinition);
else if (parentContext.Root is FieldDefinition)
{
parentIl.Emit(OpCodes.Ldarg_0);
parentIl.Emit(OpCodes.Ldfld, parentContext.Root as FieldDefinition);
}
else
throw new InvalidProgramException();
parentIl.Emit(OpCodes.Stfld, root);
//SetDataTemplate
parentIl.Emit(Ldftn, loadTemplate);
parentIl.Emit(Newobj, module.ImportCtorReference(("mscorlib", "System", "Func`1"),
classArguments: new[] { ("mscorlib", "System", "Object") },
paramCount: 2));
parentContext.IL.Emit(OpCodes.Callvirt, module.ImportPropertySetterReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "IDataTemplate"), propertyName: "LoadTemplate"));
loadTemplate.Body.Optimize();
}
bool TrySetRuntimeName(XmlName propertyName, VariableDefinition variableDefinition, ValueNode node)
{
if (propertyName != XmlName.xName)
return false;
var attributes = variableDefinition.VariableType.ResolveCached()
.CustomAttributes.Where(attribute => attribute.AttributeType.FullName == "Xamarin.Forms.Xaml.RuntimeNamePropertyAttribute").ToList();
if (!attributes.Any())
return false;
var runTimeName = attributes[0].ConstructorArguments[0].Value as string;
if (string.IsNullOrEmpty(runTimeName))
return false;
Context.IL.Append(SetPropertyValue(variableDefinition, new XmlName("", runTimeName), node, Context, node));
return true;
}
}
class VariableDefinitionReference
{
public VariableDefinitionReference(VariableDefinition vardef)
{
VariableDefinition = vardef;
}
public VariableDefinition VariableDefinition { get; set; }
public static implicit operator VariableDefinition(VariableDefinitionReference vardefref)
{
return vardefref.VariableDefinition;
}
}
}