From 775df09a3e45e90128f9047e8618461d4c4411a0 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Mon, 15 Aug 2016 22:06:11 +0200 Subject: [Xaml] x:Static in x:Arguments (#288) * [Xaml] Support x:Static as x:Arguments * [XamlC] allow x:Static in x:Arguments * fix typo, remove commented code --- .../ICompiledMarkupExtension.cs | 12 + .../CompiledMarkupExtensions/StaticExtension.cs | 88 +++++++ Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs | 293 ++++++++++----------- Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs | 5 +- .../MethodDefinitionExtensions.cs | 4 +- .../Xamarin.Forms.Build.Tasks.csproj | 5 + 6 files changed, 254 insertions(+), 153 deletions(-) create mode 100644 Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs create mode 100644 Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs (limited to 'Xamarin.Forms.Build.Tasks') diff --git a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs new file mode 100644 index 00000000..b18d9561 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs @@ -0,0 +1,12 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; +using System.Collections.Generic; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + interface ICompiledMarkupExtension + { + IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, out TypeReference typeRef); + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs new file mode 100644 index 00000000..82654969 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; +using System.Xml; + +using static System.String; + +namespace Xamarin.Forms.Build.Tasks +{ + class StaticExtension : ICompiledMarkupExtension + { + public IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, out TypeReference memberRef) + { + INode ntype; + if (!node.Properties.TryGetValue(new XmlName("", "Member"), out ntype)) + ntype = node.CollectionItems [0]; + var member = ((ValueNode)ntype).Value as string; + + if (IsNullOrEmpty(member) || !member.Contains(".")) { + var lineInfo = node as IXmlLineInfo; + throw new XamlParseException("Syntax for x:Static is [Member=][prefix:]typeName.staticMemberName", lineInfo); + } + + var dotIdx = member.LastIndexOf('.'); + var typename = member.Substring(0, dotIdx); + var membername = member.Substring(dotIdx + 1); + + var typeRef = GetTypeReference(typename, module, node); + var fieldRef = GetFieldReference(typeRef, membername, module); + var propertyDef = GetPropertyDefinition(typeRef, membername, module); + + if (fieldRef == null && propertyDef == null) + throw new XamlParseException(Format("x:Static: unable to find a public static field or property named {0} in {1}", membername, typename), node as IXmlLineInfo); + + if (fieldRef != null) { + memberRef = fieldRef.FieldType; + return new [] { Instruction.Create(OpCodes.Ldsfld, fieldRef) }; + } + + memberRef = propertyDef.PropertyType; + var getterDef = propertyDef.GetMethod; + return new [] { Instruction.Create(OpCodes.Call, getterDef)}; + } + + + public static TypeReference GetTypeReference(string xmlType, ModuleDefinition module, IElementNode node) + { + var split = xmlType.Split(':'); + if (split.Length > 2) + throw new Xaml.XamlParseException(string.Format("Type \"{0}\" is invalid", xmlType), node as IXmlLineInfo); + + string prefix, name; + if (split.Length == 2) { + prefix = split [0]; + name = split [1]; + } else { + prefix = ""; + name = split [0]; + } + var namespaceuri = node.NamespaceResolver.LookupNamespace(prefix) ?? ""; + return XmlTypeExtensions.GetTypeReference(new XmlType(namespaceuri, name, null), module, node as IXmlLineInfo); + } + + public static FieldReference GetFieldReference(TypeReference typeRef, string fieldName, ModuleDefinition module) + { + TypeReference declaringTypeReference; + FieldReference fRef = typeRef.GetField(fd => fd.Name == fieldName && + fd.IsStatic && + fd.IsPublic, out declaringTypeReference); + if (fRef != null) { + fRef = module.Import(fRef.ResolveGenericParameters(declaringTypeReference)); + fRef.FieldType = module.Import(fRef.FieldType); + } + return fRef; + } + + public static PropertyDefinition GetPropertyDefinition(TypeReference typeRef, string propertyName, ModuleDefinition module) + { + TypeReference declaringTypeReference; + PropertyDefinition pDef = typeRef.GetProperty(pd => pd.Name == propertyName && + pd.GetMethod.IsPublic && + pd.GetMethod.IsStatic, out declaringTypeReference); + return pDef; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs index 5f9adafd..e6453575 100644 --- a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs @@ -5,6 +5,7 @@ using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using Xamarin.Forms.Xaml; +using System.Xml; namespace Xamarin.Forms.Build.Tasks { @@ -67,165 +68,161 @@ namespace Xamarin.Forms.Build.Tasks var typeref = node.XmlType.GetTypeReference(Module, node); TypeDefinition typedef = typeref.Resolve(); - if (IsXaml2009LanguagePrimitive(node)) - { + if (IsXaml2009LanguagePrimitive(node)) { var vardef = new VariableDefinition(typeref); - Context.Variables[node] = vardef; + Context.Variables [node] = vardef; Context.Body.Variables.Add(vardef); Context.IL.Append(PushValueFromLanguagePrimitive(typedef, node)); Context.IL.Emit(OpCodes.Stloc, vardef); + return; } - else - { - MethodDefinition factoryCtorInfo = null; - MethodDefinition factoryMethodInfo = null; - MethodDefinition parameterizedCtorInfo = null; - MethodDefinition ctorInfo = null; - if (node.Properties.ContainsKey(XmlName.xArguments) && !node.Properties.ContainsKey(XmlName.xFactoryMethod)) - { - factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor && - !md.IsStatic && - md.HasParameters && - md.MatchXArguments(node, Module)); - if (factoryCtorInfo == null) - { - throw new XamlParseException( - string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node); - } - ctorInfo = factoryCtorInfo; - if (!typedef.IsValueType) //for ctor'ing typedefs, we first have to ldloca before the params - Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); - } - else if (node.Properties.ContainsKey(XmlName.xFactoryMethod)) - { - var factoryMethod = (string)(node.Properties[XmlName.xFactoryMethod] as ValueNode).Value; - factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor && - md.Name == factoryMethod && - md.IsStatic && - md.MatchXArguments(node, Module)); - if (factoryMethodInfo == null) - { - throw new XamlParseException( - String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node); - } - Context.IL.Append(PushCtorXArguments(factoryMethodInfo, node)); - } - if (ctorInfo == null && factoryMethodInfo == null) - { - parameterizedCtorInfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor && - !md.IsStatic && - md.HasParameters && - md.Parameters.All( - pd => - pd.CustomAttributes.Any( - ca => - ca.AttributeType.FullName == - "Xamarin.Forms.ParameterAttribute"))); - } - if (parameterizedCtorInfo != null && ValidateCtorArguments(parameterizedCtorInfo, node)) - { - ctorInfo = parameterizedCtorInfo; - // IL_0000: ldstr "foo" - Context.IL.Append(PushCtorArguments(parameterizedCtorInfo, node)); - } - ctorInfo = ctorInfo ?? typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters && !md.IsStatic); + if (typeref.FullName == "Xamarin.Forms.Xaml.StaticExtension") { + var markupProvider = new StaticExtension(); - var ctorinforef = ctorInfo?.ResolveGenericParameters(typeref, Module); - var factorymethodinforef = factoryMethodInfo?.ResolveGenericParameters(typeref, Module); - var implicitOperatorref = typedef.Methods.FirstOrDefault(md => - md.IsPublic && - md.IsStatic && - md.IsSpecialName && - md.Name == "op_Implicit" && md.Parameters[0].ParameterType.FullName == "System.String"); + var il = markupProvider.ProvideValue(node, Module, out typeref); - if (ctorinforef != null || factorymethodinforef != null || typedef.IsValueType) - { - VariableDefinition vardef = new VariableDefinition(typeref); - Context.Variables[node] = vardef; - Context.Body.Variables.Add(vardef); + var vardef = new VariableDefinition(typeref); + Context.Variables [node] = vardef; + Context.Body.Variables.Add(vardef); - ValueNode vnode = null; - if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && - vardef.VariableType.IsValueType) - { - //Purple - Context.IL.Append(vnode.PushConvertedValue(Context, typeref, new ICustomAttributeProvider[] { typedef }, - node.PushServiceProvider(Context), false, true)); - Context.IL.Emit(OpCodes.Stloc, vardef); - } - else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && - implicitOperatorref != null) - { - //path.png - var implicitOperator = Module.Import(implicitOperatorref); - Context.IL.Emit(OpCodes.Ldstr, ((ValueNode)(node.CollectionItems.First())).Value as string); - Context.IL.Emit(OpCodes.Call, implicitOperator); - Context.IL.Emit(OpCodes.Stloc, vardef); - } - else if (factorymethodinforef != null) - { - var factory = Module.Import(factorymethodinforef); - Context.IL.Emit(OpCodes.Call, factory); - Context.IL.Emit(OpCodes.Stloc, vardef); - } - else if (!typedef.IsValueType) - { - var ctor = Module.Import(ctorinforef); - // IL_0001: newobj instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::'.ctor'() - // IL_0006: stloc.0 - Context.IL.Emit(OpCodes.Newobj, ctor); - Context.IL.Emit(OpCodes.Stloc, vardef); - } - else if (ctorInfo != null && node.Properties.ContainsKey(XmlName.xArguments) && - !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, Module)) - { - // IL_0008: ldloca.s 1 - // IL_000a: ldc.i4.1 - // IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool) - - var ctor = Module.Import(ctorinforef); - Context.IL.Emit(OpCodes.Ldloca, vardef); - Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); - Context.IL.Emit(OpCodes.Call, ctor); - } - else - { - // IL_0000: ldloca.s 0 - // IL_0002: initobj Test/Foo - Context.IL.Emit(OpCodes.Ldloca, vardef); - Context.IL.Emit(OpCodes.Initobj, Module.Import(typedef)); - } + Context.IL.Append(il); + Context.IL.Emit(OpCodes.Stloc, vardef); - if (typeref.FullName == "Xamarin.Forms.Xaml.TypeExtension") - { - var visitor = new SetPropertiesVisitor(Context); - foreach (var cnode in node.Properties.Values.ToList()) - cnode.Accept(visitor, node); - foreach (var cnode in node.CollectionItems) - cnode.Accept(visitor, node); - - //As we're stripping the TypeExtension bare, keep the type if we need it later (hint: we do need it) - INode ntype; - if (!node.Properties.TryGetValue(new XmlName("", "TypeName"), out ntype)) - ntype = node.CollectionItems[0]; - - var type = ((ValueNode)ntype).Value as string; - var namespaceuri = type.Contains(":") ? node.NamespaceResolver.LookupNamespace(type.Split(':')[0].Trim()) : ""; - type = type.Contains(":") ? type.Split(':')[1].Trim() : type; - Context.TypeExtensions[node] = new XmlType(namespaceuri, type, null).GetTypeReference(Module, node); - - node.Properties.Clear(); - node.CollectionItems.Clear(); - - var vardefref = new VariableDefinitionReference(vardef); - Context.IL.Append(SetPropertiesVisitor.ProvideValue(vardefref, Context, Module, node)); - if (vardef != vardefref.VariableDefinition) - { - Context.Variables[node] = vardefref.VariableDefinition; - Context.Body.Variables.Add(vardefref.VariableDefinition); - } + //clean the node as it has been fully exhausted + node.Properties.Clear(); + node.CollectionItems.Clear(); + return; + } + + MethodDefinition factoryCtorInfo = null; + MethodDefinition factoryMethodInfo = null; + MethodDefinition parameterizedCtorInfo = null; + MethodDefinition ctorInfo = null; + + if (node.Properties.ContainsKey(XmlName.xArguments) && !node.Properties.ContainsKey(XmlName.xFactoryMethod)) { + factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor && + !md.IsStatic && + md.HasParameters && + md.MatchXArguments(node, Module, Context)); + if (factoryCtorInfo == null) { + throw new XamlParseException( + string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node); + } + ctorInfo = factoryCtorInfo; + if (!typedef.IsValueType) //for ctor'ing typedefs, we first have to ldloca before the params + Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); + } else if (node.Properties.ContainsKey(XmlName.xFactoryMethod)) { + var factoryMethod = (string)(node.Properties [XmlName.xFactoryMethod] as ValueNode).Value; + factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor && + md.Name == factoryMethod && + md.IsStatic && + md.MatchXArguments(node, Module, Context)); + if (factoryMethodInfo == null) { + throw new XamlParseException( + String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node); + } + Context.IL.Append(PushCtorXArguments(factoryMethodInfo, node)); + } + if (ctorInfo == null && factoryMethodInfo == null) { + parameterizedCtorInfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor && + !md.IsStatic && + md.HasParameters && + md.Parameters.All( + pd => + pd.CustomAttributes.Any( + ca => + ca.AttributeType.FullName == + "Xamarin.Forms.ParameterAttribute"))); + } + if (parameterizedCtorInfo != null && ValidateCtorArguments(parameterizedCtorInfo, node)) { + ctorInfo = parameterizedCtorInfo; + // IL_0000: ldstr "foo" + Context.IL.Append(PushCtorArguments(parameterizedCtorInfo, node)); + } + ctorInfo = ctorInfo ?? typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters && !md.IsStatic); + + var ctorinforef = ctorInfo?.ResolveGenericParameters(typeref, Module); + var factorymethodinforef = factoryMethodInfo?.ResolveGenericParameters(typeref, Module); + var implicitOperatorref = typedef.Methods.FirstOrDefault(md => + md.IsPublic && + md.IsStatic && + md.IsSpecialName && + md.Name == "op_Implicit" && md.Parameters [0].ParameterType.FullName == "System.String"); + + if (ctorinforef != null || factorymethodinforef != null || typedef.IsValueType) { + VariableDefinition vardef = new VariableDefinition(typeref); + Context.Variables [node] = vardef; + Context.Body.Variables.Add(vardef); + + ValueNode vnode = null; + if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && + vardef.VariableType.IsValueType) { + //Purple + Context.IL.Append(vnode.PushConvertedValue(Context, typeref, new ICustomAttributeProvider [] { typedef }, + node.PushServiceProvider(Context), false, true)); + Context.IL.Emit(OpCodes.Stloc, vardef); + } else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && + implicitOperatorref != null) { + //path.png + var implicitOperator = Module.Import(implicitOperatorref); + Context.IL.Emit(OpCodes.Ldstr, ((ValueNode)(node.CollectionItems.First())).Value as string); + Context.IL.Emit(OpCodes.Call, implicitOperator); + Context.IL.Emit(OpCodes.Stloc, vardef); + } else if (factorymethodinforef != null) { + var factory = Module.Import(factorymethodinforef); + Context.IL.Emit(OpCodes.Call, factory); + Context.IL.Emit(OpCodes.Stloc, vardef); + } else if (!typedef.IsValueType) { + var ctor = Module.Import(ctorinforef); +// IL_0001: newobj instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::'.ctor'() +// IL_0006: stloc.0 + Context.IL.Emit(OpCodes.Newobj, ctor); + Context.IL.Emit(OpCodes.Stloc, vardef); + } else if (ctorInfo != null && node.Properties.ContainsKey(XmlName.xArguments) && + !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, Module, Context)) { +// IL_0008: ldloca.s 1 +// IL_000a: ldc.i4.1 +// IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool) + + var ctor = Module.Import(ctorinforef); + Context.IL.Emit(OpCodes.Ldloca, vardef); + Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); + Context.IL.Emit(OpCodes.Call, ctor); + } else { +// IL_0000: ldloca.s 0 +// IL_0002: initobj Test/Foo + Context.IL.Emit(OpCodes.Ldloca, vardef); + Context.IL.Emit(OpCodes.Initobj, Module.Import(typedef)); + } + + //if/when we land the compiled converters, those 2 blocks could be greatly simplified + if (typeref.FullName == "Xamarin.Forms.Xaml.TypeExtension") { + var visitor = new SetPropertiesVisitor(Context); + foreach (var cnode in node.Properties.Values.ToList()) + cnode.Accept(visitor, node); + foreach (var cnode in node.CollectionItems) + cnode.Accept(visitor, node); + + //As we're stripping the TypeExtension bare, keep the type if we need it later (hint: we do need it) + INode ntype; + if (!node.Properties.TryGetValue(new XmlName("", "TypeName"), out ntype)) + ntype = node.CollectionItems [0]; + + var type = ((ValueNode)ntype).Value as string; + var namespaceuri = type.Contains(":") ? node.NamespaceResolver.LookupNamespace(type.Split(':') [0].Trim()) : ""; + type = type.Contains(":") ? type.Split(':') [1].Trim() : type; + Context.TypeExtensions [node] = new XmlType(namespaceuri, type, null).GetTypeReference(Module, node); + + node.Properties.Clear(); + node.CollectionItems.Clear(); + + var vardefref = new VariableDefinitionReference(vardef); + Context.IL.Append(SetPropertiesVisitor.ProvideValue(vardefref, Context, Module, node)); + if (vardef != vardefref.VariableDefinition) { + Context.Variables [node] = vardefref.VariableDefinition; + Context.Body.Variables.Add(vardefref.VariableDefinition); } } } diff --git a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs index bbdaa6c8..feafe214 100644 --- a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs @@ -12,7 +12,6 @@ namespace Xamarin.Forms.Build.Tasks { XmlName.xKey, XmlName.xTypeArguments, - XmlName.xArguments, XmlName.xFactoryMethod, XmlName.xName }; @@ -175,8 +174,8 @@ namespace Xamarin.Forms.Build.Tasks throw new NotSupportedException(); node = xmlLineInfo == null - ? new ElementNode(type, null, nsResolver) - : new ElementNode(type, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); + ? new ElementNode(type, "", nsResolver) + : new ElementNode(type, "", nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); if (remaining.StartsWith("}", StringComparison.Ordinal)) { diff --git a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs index 41d7cb20..8c3dba22 100644 --- a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs @@ -6,7 +6,7 @@ namespace Xamarin.Forms.Build.Tasks { static class MethodDefinitionExtensions { - public static bool MatchXArguments(this MethodDefinition methodDefinition, ElementNode enode, ModuleDefinition module) + public static bool MatchXArguments(this MethodDefinition methodDefinition, ElementNode enode, ModuleDefinition module, ILContext context) { if (!enode.Properties.ContainsKey(XmlName.xArguments)) return !methodDefinition.HasParameters; @@ -28,7 +28,7 @@ namespace Xamarin.Forms.Build.Tasks for (var i = 0; i < methodDefinition.Parameters.Count; i++) { var paramType = methodDefinition.Parameters[i].ParameterType; - var argType = ((IElementNode)arguments[i]).XmlType.GetTypeReference(module, null); + var argType = context.Variables [arguments [i] as IElementNode].VariableType; if (!argType.InheritsFromOrImplements(paramType)) return false; } diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj index 1bdd8bf1..0b26e277 100644 --- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -88,6 +88,8 @@ + + @@ -113,6 +115,9 @@ ICSharpCode.Decompiler + + + -- cgit v1.2.3