From 39f2deb5e04886374e720013782c751a4bb7675d Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Sun, 4 Dec 2016 22:08:11 +0100 Subject: [PATCH] [Xaml] support arrays as x:Arguments (#545) * [Xaml] port some FactoryMethod tests to XamlC * [Xaml] support array parameters in factory ctors * [XamlC] support arrays as x:Arguments * fix build --- .../CompiledMarkupExtensions/ArrayExtension.cs | 31 +++++++++++++ .../ICompiledMarkupExtension.cs | 2 +- .../CompiledMarkupExtensions/StaticExtension.cs | 7 ++- Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs | 33 ++++++++++++-- .../TypeReferenceExtensions.cs | 20 ++++---- .../Xamarin.Forms.Build.Tasks.csproj | 1 + .../FactoryMethodMissingCtor.xaml | 15 ++++++ .../FactoryMethodMissingCtor.xaml.cs | 35 ++++++++++++++ .../FactoryMethodMissingMethod.xaml | 16 +++++++ .../FactoryMethodMissingMethod.xaml.cs | 35 ++++++++++++++ Xamarin.Forms.Xaml.UnitTests/FactoryMethodTests.cs | 53 ---------------------- Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml | 16 ++++++- .../FactoryMethods.xaml.cs | 13 ++++++ Xamarin.Forms.Xaml.UnitTests/SetValue.xaml | 2 +- .../Xamarin.Forms.Xaml.UnitTests.csproj | 13 +++++- Xamarin.Forms.Xaml/CreateValuesVisitor.cs | 9 +++- 16 files changed, 226 insertions(+), 75 deletions(-) create mode 100644 Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ArrayExtension.cs create mode 100644 Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs create mode 100644 Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml.cs delete mode 100644 Xamarin.Forms.Xaml.UnitTests/FactoryMethodTests.cs diff --git a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ArrayExtension.cs b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ArrayExtension.cs new file mode 100644 index 0000000..0626114 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ArrayExtension.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class ArrayExtension : ICompiledMarkupExtension + { + public IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, ILContext context, out TypeReference memberRef) + { + var typeNode = node.Properties[new XmlName("", "Type")] as IElementNode; + var typeTypeRef = context.TypeExtensions[typeNode]; + var n = node.CollectionItems.Count; + + var instructions = new List(); + instructions.Add(Instruction.Create(OpCodes.Ldc_I4, n)); + instructions.Add(Instruction.Create(OpCodes.Newarr, typeTypeRef)); + + memberRef = typeTypeRef.MakeArrayType(); + for (var i = 0; i < n; i++) { + instructions.Add(Instruction.Create(OpCodes.Dup)); + instructions.Add(Instruction.Create(OpCodes.Ldc_I4, i)); + instructions.Add(Instruction.Create(OpCodes.Ldloc, context.Variables[node.CollectionItems[i] as IElementNode])); + instructions.Add(Instruction.Create(OpCodes.Stelem_Ref)); + } + return instructions; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs index b18d956..285a536 100644 --- a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs +++ b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/ICompiledMarkupExtension.cs @@ -7,6 +7,6 @@ namespace Xamarin.Forms.Build.Tasks { interface ICompiledMarkupExtension { - IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, out TypeReference typeRef); + IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, ILContext context, 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 index 14797c5..014fb82 100644 --- a/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs +++ b/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; using Xamarin.Forms.Xaml; @@ -11,11 +10,11 @@ namespace Xamarin.Forms.Build.Tasks { class StaticExtension : ICompiledMarkupExtension { - public IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, out TypeReference memberRef) + public IEnumerable ProvideValue(IElementNode node, ModuleDefinition module, ILContext context, out TypeReference memberRef) { INode ntype; if (!node.Properties.TryGetValue(new XmlName("", "Member"), out ntype)) - ntype = node.CollectionItems [0]; + ntype = node.CollectionItems[0]; var member = ((ValueNode)ntype).Value as string; if (IsNullOrEmpty(member) || !member.Contains(".")) { diff --git a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs index 4e1372c..ac53021 100644 --- a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs @@ -64,7 +64,7 @@ namespace Xamarin.Forms.Build.Tasks if (typeref.FullName == "Xamarin.Forms.Xaml.StaticExtension") { var markupProvider = new StaticExtension(); - var il = markupProvider.ProvideValue(node, Module, out typeref); + var il = markupProvider.ProvideValue(node, Module, Context, out typeref); var vardef = new VariableDefinition(typeref); Context.Variables [node] = vardef; @@ -180,7 +180,6 @@ namespace Xamarin.Forms.Build.Tasks 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()) @@ -212,6 +211,30 @@ namespace Xamarin.Forms.Build.Tasks Context.Body.Variables.Add(vardefref.VariableDefinition); } } + + if (typeref.FullName == "Xamarin.Forms.Xaml.ArrayExtension") + { + 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); + + var markupProvider = new ArrayExtension(); + + var il = markupProvider.ProvideValue(node, Module, Context, out typeref); + + vardef = new VariableDefinition(typeref); + Context.Variables[node] = vardef; + Context.Body.Variables.Add(vardef); + + Context.IL.Append(il); + Context.IL.Emit(OpCodes.Stloc, vardef); + + //clean the node as it has been fully exhausted + node.Properties.Remove(new XmlName("","Type")); + node.CollectionItems.Clear(); + } } } @@ -316,8 +339,10 @@ namespace Xamarin.Forms.Build.Tasks static bool IsXaml2009LanguagePrimitive(IElementNode node) { - if (node.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml") - return true; + if (node.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml") { + var n = node.XmlType.Name.Split(':') [1]; + return n != "Array"; + } if (node.NamespaceURI != "clr-namespace:System;assembly=mscorlib") return false; var name = node.XmlType.Name.Split(':')[1]; diff --git a/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs index d0ccbc5..613b0cc 100644 --- a/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs @@ -65,12 +65,9 @@ namespace Xamarin.Forms.Build.Tasks genericArguments = null; var typeDef = typeRef.Resolve(); TypeReference iface; - if ( - (iface = - typeDef.Interfaces.FirstOrDefault( - tr => - tr.FullName.StartsWith(@interface) && tr.IsGenericInstance && (tr as GenericInstanceType).HasGenericArguments)) != - null) + if ((iface = typeDef.Interfaces.FirstOrDefault(tr => + tr.FullName.StartsWith(@interface, StringComparison.Ordinal) && + tr.IsGenericInstance && (tr as GenericInstanceType).HasGenericArguments)) != null) { interfaceReference = iface as GenericInstanceType; genericArguments = (iface as GenericInstanceType).GenericArguments; @@ -89,20 +86,25 @@ namespace Xamarin.Forms.Build.Tasks var arrayInterfaces = new[] { - "System.IEnumerable", + "System.Collections.IEnumerable", "System.Collections.IList", "System.Collections.Collection" }; var arrayGenericInterfaces = new[] { - "System.IEnumerable`1", + "System.Collections.IEnumerable`1", "System.Collections.Generic.IList`1", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.Collection" }; + if (typeRef.IsArray && baseClass.IsArray) { + typeRef = typeRef.Resolve(); + baseClass = baseClass.Resolve(); + } + if (typeRef.IsArray) { var arrayType = typeRef.Resolve(); @@ -112,7 +114,9 @@ namespace Xamarin.Forms.Build.Tasks baseClass.IsGenericInstance && (baseClass as GenericInstanceType).GenericArguments[0].FullName == arrayType.FullName) return true; + return false; } + var typeDef = typeRef.Resolve(); if (typeDef.FullName == baseClass.FullName) return true; diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj index 515319e..46d4f23 100644 --- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -100,6 +100,7 @@ + diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml new file mode 100644 index 0000000..f49108c --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml @@ -0,0 +1,15 @@ + + + + + + + bar + 42 + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs new file mode 100644 index 0000000..6771edf --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingCtor.xaml.cs @@ -0,0 +1,35 @@ +using System; +using NUnit.Framework; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + [XamlCompilation(XamlCompilationOptions.Skip)] + public partial class FactoryMethodMissingCtor : MockView + { + public FactoryMethodMissingCtor() + { + InitializeComponent(); + } + + [TestFixture] + public class Tests + { + [SetUp] + public void SetUp() + { + Device.PlatformServices = new MockPlatformServices(); + } + + [TestCase(false)] + [TestCase(true)] + public void Throw(bool useCompiledXaml) + { + if (useCompiledXaml) + Assert.Throws(new XamlParseExceptionConstraint(7, 4), () => MockCompiler.Compile(typeof(FactoryMethodMissingCtor))); + else + Assert.Throws(() => new FactoryMethodMissingCtor()); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml new file mode 100644 index 0000000..85e279d --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml @@ -0,0 +1,16 @@ + + + + + + + bar + 42 + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml.cs new file mode 100644 index 0000000..ecc4048 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodMissingMethod.xaml.cs @@ -0,0 +1,35 @@ +using System; +using NUnit.Framework; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + [XamlCompilation(XamlCompilationOptions.Skip)] + public partial class FactoryMethodMissingMethod : MockView + { + public FactoryMethodMissingMethod() + { + InitializeComponent(); + } + + [TestFixture] + public class Tests + { + [SetUp] + public void SetUp() + { + Device.PlatformServices = new MockPlatformServices(); + } + + [TestCase(false)] + [TestCase(true)] + public void Throw(bool useCompiledXaml) + { + if (useCompiledXaml) + Assert.Throws(new XamlParseExceptionConstraint(8, 4), () => MockCompiler.Compile(typeof(FactoryMethodMissingMethod))); + else + Assert.Throws(() => new FactoryMethodMissingMethod()); + } + } + } +} diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodTests.cs b/Xamarin.Forms.Xaml.UnitTests/FactoryMethodTests.cs deleted file mode 100644 index d6ee188..0000000 --- a/Xamarin.Forms.Xaml.UnitTests/FactoryMethodTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using NUnit.Framework; - -using Xamarin.Forms.Core.UnitTests; - -namespace Xamarin.Forms.Xaml.UnitTests -{ - [TestFixture] - public class FactoryMethodTests : BaseTestFixture - { - [Test] - public void ThrowOnMissingCtor () - { - var xaml = @" - - - - - - bar - 42 - - - - "; - Assert.Throws (()=>new MockView ().LoadFromXaml (xaml)); - } - - [Test] - public void ThrowOnMissingMethod () - { - var xaml = @" - - - - - - bar - 42 - - - - "; - Assert.Throws (()=>new MockView ().LoadFromXaml (xaml)); - } - } -} diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml b/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml index 5225c7d..9ff91de 100644 --- a/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml @@ -1,4 +1,4 @@ - + - + @@ -68,5 +68,17 @@ + + + + + + Foo + Bar + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml.cs index 9fa4e92..f579038 100644 --- a/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml.cs +++ b/Xamarin.Forms.Xaml.UnitTests/FactoryMethods.xaml.cs @@ -32,6 +32,11 @@ namespace Xamarin.Forms.Xaml.UnitTests Content = "int ctor " + arg.ToString (); } + public MockFactory(object [] args) + { + Content = string.Join(" ", args); + } + public static MockFactory ParameterlessFactory () { return new MockFactory { @@ -138,6 +143,14 @@ namespace Xamarin.Forms.Xaml.UnitTests var layout = new FactoryMethods(useCompiledXaml); Assert.AreEqual("alternate ctor Property", layout.v7.Content.Content); } + + [TestCase(false)] + [TestCase(true)] + public void TestCtorWithArrayParameter(bool useCompiledXaml) + { + var layout = new FactoryMethods(useCompiledXaml); + Assert.AreEqual("Foo Bar", layout.v8.Content.Content); + } } } } \ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/SetValue.xaml b/Xamarin.Forms.Xaml.UnitTests/SetValue.xaml index fd11bd5..5ebc4f4 100644 --- a/Xamarin.Forms.Xaml.UnitTests/SetValue.xaml +++ b/Xamarin.Forms.Xaml.UnitTests/SetValue.xaml @@ -1,4 +1,4 @@ - + - @@ -391,6 +390,12 @@ Bz47703.xaml + + FactoryMethodMissingCtor.xaml + + + FactoryMethodMissingMethod.xaml + @@ -703,6 +708,12 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + + + MSBuild:UpdateDesignTimeXaml + diff --git a/Xamarin.Forms.Xaml/CreateValuesVisitor.cs b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs index c2331bf..817b62c 100644 --- a/Xamarin.Forms.Xaml/CreateValuesVisitor.cs +++ b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs @@ -107,7 +107,7 @@ namespace Xamarin.Forms.Xaml Values[node] = value; var markup = value as IMarkupExtension; - if (markup != null && (value is TypeExtension || value is StaticExtension)) + if (markup != null && (value is TypeExtension || value is StaticExtension || value is ArrayExtension)) { var serviceProvider = new XamlServiceProvider(node, Context); @@ -119,9 +119,16 @@ namespace Xamarin.Forms.Xaml value = markup.ProvideValue(serviceProvider); + INode xKey; + if (!node.Properties.TryGetValue(XmlName.xKey, out xKey)) + xKey = null; + node.Properties.Clear(); node.CollectionItems.Clear(); + if (xKey != null) + node.Properties.Add(XmlName.xKey, xKey); + Values[node] = value; } -- 2.7.4