diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2017-04-06 23:37:59 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2017-04-06 14:37:59 -0700 |
commit | 1a7aea41ffa43a652e053a5b2e34b76a371a598d (patch) | |
tree | 0d17c794f88b178513904a93688d6ea859c33913 | |
parent | ab0fabac610b5c24c086c1cbf5e88b1d48511f2a (diff) | |
download | xamarin-forms-1a7aea41ffa43a652e053a5b2e34b76a371a598d.tar.gz xamarin-forms-1a7aea41ffa43a652e053a5b2e34b76a371a598d.tar.bz2 xamarin-forms-1a7aea41ffa43a652e053a5b2e34b76a371a598d.zip |
More Xaml generic support (#857)
* [XamlG] allow x:Name on nested generic types
* [Xaml*] fail on undeclared xmlns
* [XamlC] allow x:Arguments on generic ctors
-rw-r--r-- | Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs | 6 | ||||
-rw-r--r-- | Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs | 18 | ||||
-rw-r--r-- | Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs | 10 | ||||
-rw-r--r-- | Xamarin.Forms.Build.Tasks/XamlGTask.cs | 136 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml | 10 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs | 9 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml/TypeArgumentsParser.cs | 4 |
7 files changed, 106 insertions, 87 deletions
diff --git a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs index d7b05ac4..36428e84 100644 --- a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs @@ -87,7 +87,7 @@ namespace Xamarin.Forms.Build.Tasks factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor && !md.IsStatic && md.HasParameters && - md.MatchXArguments(node, Module, Context)); + md.MatchXArguments(node, typeref, Module, Context)); if (factoryCtorInfo == null) { throw new XamlParseException( string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node); @@ -100,7 +100,7 @@ namespace Xamarin.Forms.Build.Tasks factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor && md.Name == factoryMethod && md.IsStatic && - md.MatchXArguments(node, Module, Context)); + md.MatchXArguments(node, typeref, Module, Context)); if (factoryMethodInfo == null) { throw new XamlParseException( String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node); @@ -162,7 +162,7 @@ namespace Xamarin.Forms.Build.Tasks 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)) { + !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, typeref, Module, Context)) { // IL_0008: ldloca.s 1 // IL_000a: ldc.i4.1 // IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool) diff --git a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs index 8c3dba22..4b50b64d 100644 --- a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs @@ -6,28 +6,32 @@ namespace Xamarin.Forms.Build.Tasks { static class MethodDefinitionExtensions { - public static bool MatchXArguments(this MethodDefinition methodDefinition, ElementNode enode, ModuleDefinition module, ILContext context) + public static bool MatchXArguments(this MethodDefinition methodDef, ElementNode enode, TypeReference declaringTypeRef, ModuleDefinition module, ILContext context) { if (!enode.Properties.ContainsKey(XmlName.xArguments)) - return !methodDefinition.HasParameters; + return !methodDef.HasParameters; var arguments = new List<INode>(); var node = enode.Properties[XmlName.xArguments] as ElementNode; if (node != null) arguments.Add(node); + var list = enode.Properties[XmlName.xArguments] as ListNode; if (list != null) - { foreach (var n in list.CollectionItems) arguments.Add(n); - } - if (methodDefinition.Parameters.Count != arguments.Count) + if (methodDef.Parameters.Count != arguments.Count) return false; - for (var i = 0; i < methodDefinition.Parameters.Count; i++) + for (var i = 0; i < methodDef.Parameters.Count; i++) { - var paramType = methodDefinition.Parameters[i].ParameterType; + var paramType = methodDef.Parameters[i].ParameterType; + var genParam = paramType as GenericParameter; + if (genParam != null) { + var index = genParam.DeclaringType.GenericParameters.IndexOf(genParam); + paramType = (declaringTypeRef as GenericInstanceType).GenericArguments[index]; + } var argType = context.Variables [arguments [i] as IElementNode].VariableType; if (!argType.InheritsFromOrImplements(paramType)) return false; diff --git a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs index 2c0d8ba9..51be1995 100644 --- a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs @@ -34,11 +34,11 @@ namespace Xamarin.Forms.Build.Tasks public static void ImportTypes(this MethodReference self, ModuleDefinition module) { - if (self.HasParameters) - { - for (var i = 0; i < self.Parameters.Count; i++) - self.Parameters[i].ParameterType = module.ImportReference(self.Parameters[i].ParameterType); - } + if (!self.HasParameters) + return; + + for (var i = 0; i < self.Parameters.Count; i++) + self.Parameters[i].ParameterType = module.ImportReference(self.Parameters[i].ParameterType); } public static MethodReference MakeGeneric(this MethodReference self, TypeReference declaringType, params TypeReference [] arguments) diff --git a/Xamarin.Forms.Build.Tasks/XamlGTask.cs b/Xamarin.Forms.Build.Tasks/XamlGTask.cs index 85a400a8..b13eec69 100644 --- a/Xamarin.Forms.Build.Tasks/XamlGTask.cs +++ b/Xamarin.Forms.Build.Tasks/XamlGTask.cs @@ -3,7 +3,6 @@ using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Xml; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -14,6 +13,9 @@ namespace Xamarin.Forms.Build.Tasks { public class XamlGTask : Task { + const string XAML2006 = "http://schemas.microsoft.com/winfx/2006/xaml"; + const string XAML2009 = "http://schemas.microsoft.com/winfx/2009/xaml"; + internal static CodeDomProvider Provider = new CSharpCodeProvider(); [Required] @@ -64,11 +66,10 @@ namespace Xamarin.Forms.Build.Tasks xmlDoc.Load(xaml); var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); - nsmgr.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml"); - nsmgr.AddNamespace("x2009", "http://schemas.microsoft.com/winfx/2009/xaml"); - nsmgr.AddNamespace("f", "http://xamarin.com/schemas/2014/forms"); + nsmgr.AddNamespace("__f__", "http://xamarin.com/schemas/2014/forms"); var root = xmlDoc.SelectSingleNode("/*", nsmgr); + if (root == null) { Console.Error.WriteLine("{0}: No root node found"); @@ -79,8 +80,17 @@ namespace Xamarin.Forms.Build.Tasks return; } - var rootClass = root.Attributes["Class", "http://schemas.microsoft.com/winfx/2006/xaml"] ?? - root.Attributes["Class", "http://schemas.microsoft.com/winfx/2009/xaml"]; + foreach (XmlAttribute attr in root.Attributes) { + if (attr.Name == "xmlns") { + nsmgr.AddNamespace("", attr.Value); //Add default xmlns + } + if (attr.Prefix != "xmlns") + continue; + nsmgr.AddNamespace(attr.LocalName, attr.Value); + } + + var rootClass = root.Attributes["Class", XAML2006] + ?? root.Attributes["Class", XAML2009]; if (rootClass == null) { rootType = null; @@ -94,12 +104,18 @@ namespace Xamarin.Forms.Build.Tasks XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform); namesAndTypes = GetNamesAndTypes(root, nsmgr); - var typeArguments = root.Attributes["TypeArguments", "http://schemas.microsoft.com/winfx/2009/xaml"]; + var typeArgsAttr = root.Attributes["TypeArguments", XAML2006] + ?? root.Attributes["TypeArguments", XAML2009]; - baseType = GetType(root.NamespaceURI, root.LocalName, typeArguments == null ? null : typeArguments.Value.Split(','), - root.GetNamespaceOfPrefix); + var xmlType = new XmlType(root.NamespaceURI, root.LocalName, typeArgsAttr != null ? TypeArgumentsParser.ParseExpression(typeArgsAttr.Value, nsmgr, null): null); + baseType = GetType(xmlType, root.GetNamespaceOfPrefix); } + static CodeAttributeDeclaration GeneratedCodeAttrDecl => + new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof(GeneratedCodeAttribute).FullName}"), + new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")), + new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0"))); + internal static void GenerateCode(string rootType, string rootNs, CodeTypeReference baseType, IDictionary<string, CodeTypeReference> namesAndTypes, string xamlFile, string outFile) { @@ -124,16 +140,11 @@ namespace Xamarin.Forms.Build.Tasks declNs.Types.Add(declType); - var initcomp = new CodeMemberMethod - { + var initcomp = new CodeMemberMethod { Name = "InitializeComponent", - CustomAttributes = - { - new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof (GeneratedCodeAttribute).FullName}"), - new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")), - new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0"))) - } + CustomAttributes = { GeneratedCodeAttrDecl } }; + declType.Members.Add(initcomp); initcomp.Statements.Add(new CodeMethodInvokeExpression( @@ -149,12 +160,7 @@ namespace Xamarin.Forms.Build.Tasks { Name = name, Type = type, - CustomAttributes = - { - new CodeAttributeDeclaration(new CodeTypeReference($"global::{typeof (GeneratedCodeAttribute).FullName}"), - new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")), - new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0"))) - } + CustomAttributes = { GeneratedCodeAttrDecl } }; declType.Members.Add(field); @@ -165,8 +171,6 @@ namespace Xamarin.Forms.Build.Tasks "FindByName", type), new CodeThisReferenceExpression(), new CodePrimitiveExpression(name)); - //CodeCastExpression cast = new CodeCastExpression (type, find_invoke); - CodeAssignStatement assign = new CodeAssignStatement( new CodeVariableReferenceExpression(name), find_invoke); @@ -185,79 +189,73 @@ namespace Xamarin.Forms.Build.Tasks using (StreamReader reader = File.OpenText(xamlFile)) ParseXaml(reader, out rootType, out rootNs, out baseType, out namesAndTypes); - GenerateCode(rootType, rootNs, baseType, namesAndTypes, System.IO.Path.GetFullPath(xamlFile), outFile); + + GenerateCode(rootType, rootNs, baseType, namesAndTypes, Path.GetFullPath(xamlFile), outFile); } static Dictionary<string, CodeTypeReference> GetNamesAndTypes(XmlNode root, XmlNamespaceManager nsmgr) { var res = new Dictionary<string, CodeTypeReference>(); - foreach (string attrib in new[] { "x:Name", "x2009:Name" }) - { - XmlNodeList names = - root.SelectNodes( - "//*[@" + attrib + - "][not(ancestor:: f:DataTemplate) and not(ancestor:: f:ControlTemplate) and not(ancestor:: f:Style)]", nsmgr); - foreach (XmlNode node in names) - { - // Don't take the root canvas - if (node == root) - continue; - - XmlAttribute attr = node.Attributes["Name", "http://schemas.microsoft.com/winfx/2006/xaml"] ?? - node.Attributes["Name", "http://schemas.microsoft.com/winfx/2009/xaml"]; - XmlAttribute typeArgsAttr = node.Attributes["x:TypeArguments"]; - var typeArgsList = typeArgsAttr == null ? null : typeArgsAttr.Value.Split(',').ToList(); - string name = attr.Value; + var xPrefix = nsmgr.LookupPrefix(XAML2006) ?? nsmgr.LookupPrefix(XAML2009); + if (xPrefix == null) + return null; - res[name] = GetType(node.NamespaceURI, node.LocalName, typeArgsList, node.GetNamespaceOfPrefix); - } + XmlNodeList names = + root.SelectNodes( + "//*[@" + xPrefix + ":Name" + + "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style)]", nsmgr); + foreach (XmlNode node in names) + { + // Don't take the root canvas + if (node == root) + continue; + + XmlAttribute attr = node.Attributes["Name", XAML2006] + ?? node.Attributes["Name", XAML2009]; + XmlAttribute typeArgsAttr = node.Attributes["TypeArguments", XAML2006] + ?? node.Attributes["TypeArguments", XAML2009]; + var xmlType = new XmlType(node.NamespaceURI, node.LocalName, + typeArgsAttr != null + ? TypeArgumentsParser.ParseExpression(typeArgsAttr.Value, nsmgr, null) + : null); + + res[attr.Value] = GetType(xmlType, node.GetNamespaceOfPrefix); } return res; } - static CodeTypeReference GetType(string nsuri, string type, IList<string> typeArguments = null, + static CodeTypeReference GetType(XmlType xmlType, Func<string, string> getNamespaceOfPrefix = null) { - var ns = GetNamespace(nsuri); + var type = xmlType.Name; + var ns = GetClrNamespace(xmlType.NamespaceUri); if (ns != null) - type = String.Concat(ns, ".", type); + type = $"{ns}.{type}"; - if (typeArguments != null) - type = String.Concat(type, "`", typeArguments.Count); + if (xmlType.TypeArguments != null) + type = $"{type}`{xmlType.TypeArguments.Count}"; var returnType = new CodeTypeReference(type); if (ns != null) returnType.Options |= CodeTypeReferenceOptions.GlobalReference; - if (typeArguments != null) - { - foreach (var typeArg in typeArguments) - { - var prefix = ""; - var _type = typeArg; - if (typeArg.Contains(":")) - { - prefix = typeArg.Split(':')[0].Trim(); - _type = typeArg.Split(':')[1].Trim(); - } - var ns_uri = getNamespaceOfPrefix(prefix); - returnType.TypeArguments.Add(GetType(ns_uri, _type, null, getNamespaceOfPrefix)); - } - } + if (xmlType.TypeArguments != null) + foreach (var typeArg in xmlType.TypeArguments) + returnType.TypeArguments.Add(GetType(typeArg, getNamespaceOfPrefix)); return returnType; } - static string GetNamespace(string namespaceuri) + static string GetClrNamespace(string namespaceuri) { if (namespaceuri == "http://xamarin.com/schemas/2014/forms") return "Xamarin.Forms"; - if (namespaceuri == "http://schemas.microsoft.com/winfx/2009/xaml") + if (namespaceuri == XAML2009) return "System"; - if (namespaceuri != "http://schemas.microsoft.com/winfx/2006/xaml" && !namespaceuri.Contains("clr-namespace")) - throw new Exception(String.Format("Can't load types from xmlns {0}", namespaceuri)); + if (namespaceuri != XAML2006 && !namespaceuri.Contains("clr-namespace")) + throw new Exception($"Can't load types from xmlns {namespaceuri}"); return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri); } } diff --git a/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml b/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml index da224282..0fe02287 100644 --- a/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml +++ b/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml @@ -1,4 +1,4 @@ -<?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" xmlns:sys="clr-namespace:System;assembly=mscorlib" @@ -16,6 +16,14 @@ <scg:Dictionary x:TypeArguments="sys:String, sys:String" x:Key="dict"/> <scgs:Queue x:TypeArguments="scg:KeyValuePair(sys:String,sys:String)" x:Key="queue"/> <scg:List x:TypeArguments="x:String" x:Key="stringList"/> + <scg:List x:TypeArguments="scg:KeyValuePair(x:String,x:String)" x:Key="TestList" x:Name="TestListMember" > + <scg:KeyValuePair x:TypeArguments="x:String,x:String" > + <x:Arguments> + <x:String>TheKey</x:String> + <x:String>TheValue</x:String> + </x:Arguments> + </scg:KeyValuePair> + </scg:List> </ResourceDictionary> </ContentPage.Resources> </ContentPage>
\ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs index 5a62b9fa..6171b327 100644 --- a/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs +++ b/Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs @@ -56,7 +56,14 @@ namespace Xamarin.Forms.Xaml.UnitTests { var layout = new GenericsTests (useCompiledXaml); var list = layout.FindByName<List<Button>> ("myList"); - Assert.NotNull (list); + Assert.That(list, Is.Not.Null); + Assert.That(list, Is.TypeOf<List<Button>>()); + + var nestedGenericList = layout.TestListMember; + Assert.That(nestedGenericList, Is.Not.Null); + Assert.That(nestedGenericList, Is.TypeOf<List<KeyValuePair<string, string>>>()); + + Assert.That(nestedGenericList.Count, Is.EqualTo(1)); } [TestCase (false)] diff --git a/Xamarin.Forms.Xaml/TypeArgumentsParser.cs b/Xamarin.Forms.Xaml/TypeArgumentsParser.cs index 525b4d74..30ed4da6 100644 --- a/Xamarin.Forms.Xaml/TypeArgumentsParser.cs +++ b/Xamarin.Forms.Xaml/TypeArgumentsParser.cs @@ -3,7 +3,7 @@ using System.Xml; namespace Xamarin.Forms.Xaml { - internal static class TypeArgumentsParser + static class TypeArgumentsParser { public static IList<XmlType> ParseExpression(string expression, IXmlNamespaceResolver resolver, IXmlLineInfo lineInfo) { @@ -62,6 +62,8 @@ namespace Xamarin.Forms.Xaml } var namespaceuri = resolver.LookupNamespace(prefix); + if (namespaceuri == null) + throw new XamlParseException($"No xmlns declaration for prefix '{prefix}'.", lineinfo, null); return new XmlType(namespaceuri, name, typeArguments); } } |