summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2017-04-06 23:37:59 +0200
committerJason Smith <jason.smith@xamarin.com>2017-04-06 14:37:59 -0700
commit1a7aea41ffa43a652e053a5b2e34b76a371a598d (patch)
tree0d17c794f88b178513904a93688d6ea859c33913
parentab0fabac610b5c24c086c1cbf5e88b1d48511f2a (diff)
downloadxamarin-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.cs6
-rw-r--r--Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs18
-rw-r--r--Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs10
-rw-r--r--Xamarin.Forms.Build.Tasks/XamlGTask.cs136
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml10
-rw-r--r--Xamarin.Forms.Xaml.UnitTests/GenericsTests.xaml.cs9
-rw-r--r--Xamarin.Forms.Xaml/TypeArgumentsParser.cs4
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);
}
}