diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2017-06-21 10:32:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-21 10:32:58 +0200 |
commit | c09603732916be56d27f21cea4ef1370057414bc (patch) | |
tree | 08e63c73ef6b04120e9a9b034a99d9f3d9c469d0 | |
parent | 28b343b2788fa073a3d3a87610275d8f3e756b61 (diff) | |
download | xamarin-forms-c09603732916be56d27f21cea4ef1370057414bc.tar.gz xamarin-forms-c09603732916be56d27f21cea4ef1370057414bc.tar.bz2 xamarin-forms-c09603732916be56d27f21cea4ef1370057414bc.zip |
[XamlC] Allow Properties and BP of generic types (#995)
* [XamlC] Allow Properties and BP of generic types
* try hard to convince the compiler that the variable is initialized
* Update MethodDefinitionExtensions.cs
9 files changed, 201 insertions, 57 deletions
diff --git a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs index 0edc5f59..014f9d62 100644 --- a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs @@ -17,9 +17,9 @@ namespace Xamarin.Forms.Build.Tasks throw new XamlParseException($"The name of the bindable property {bpRef.Name} does not ends with \"Property\". This is the kind of convention the world is build upon, a bit like Planck's constant.", iXmlLineInfo); var bpName = bpRef.Name.Substring(0, bpRef.Name.Length - 8); var owner = bpRef.DeclaringType; - TypeReference _; + TypeReference declaringTypeRef = null; - var getter = owner.GetProperty(pd => pd.Name == bpName, out _)?.GetMethod; + var getter = owner.GetProperty(pd => pd.Name == bpName, out declaringTypeRef)?.GetMethod; if (getter == null || getter.IsStatic || !getter.IsPublic) getter = null; getter = getter ?? owner.GetMethods(md => md.Name == $"Get{bpName}" && @@ -27,18 +27,18 @@ namespace Xamarin.Forms.Build.Tasks md.IsPublic && md.Parameters.Count == 1 && md.Parameters[0].ParameterType.InheritsFromOrImplements(module.ImportReference(typeof(BindableObject))), module).SingleOrDefault()?.Item1; - if (getter == null) throw new XamlParseException($"Missing a public static Get{bpName} or a public instance property getter for the attached property \"{bpRef.DeclaringType}.{bpRef.Name}\"", iXmlLineInfo); - return getter.ReturnType; + return getter.ResolveGenericReturnType(declaringTypeRef, module); } public static TypeReference GetBindablePropertyTypeConverter(this FieldReference bpRef, ModuleDefinition module) { - TypeReference _; + TypeReference propertyDeclaringType; var owner = bpRef.DeclaringType; var bpName = bpRef.Name.EndsWith("Property", StringComparison.Ordinal) ? bpRef.Name.Substring(0, bpRef.Name.Length - 8) : bpRef.Name; - var property = owner.GetProperty(pd => pd.Name == bpName, out _); + var property = owner.GetProperty(pd => pd.Name == bpName, out propertyDeclaringType); + var propertyType = property?.ResolveGenericPropertyType(propertyDeclaringType, module); var staticGetter = owner.GetMethods(md => md.Name == $"Get{bpName}" && md.IsStatic && md.IsPublic && @@ -48,8 +48,8 @@ namespace Xamarin.Forms.Build.Tasks var attributes = new List<CustomAttribute>(); if (property != null && property.HasCustomAttributes) attributes.AddRange(property.CustomAttributes); - if (property != null && property.PropertyType.Resolve().HasCustomAttributes) - attributes.AddRange(property.PropertyType.Resolve().CustomAttributes); + if (propertyType != null && propertyType.Resolve().HasCustomAttributes) + attributes.AddRange(propertyType.Resolve().CustomAttributes); if (staticGetter != null && staticGetter.HasCustomAttributes) attributes.AddRange(staticGetter.CustomAttributes); if (staticGetter != null && staticGetter.ReturnType.Resolve().HasCustomAttributes) diff --git a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs index 4b50b64d..6c6c7d88 100644 --- a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs @@ -38,5 +38,18 @@ namespace Xamarin.Forms.Build.Tasks } return true; } + + public static TypeReference ResolveGenericReturnType(this MethodDefinition self, TypeReference declaringTypeRef, ModuleDefinition module) + { + if (self == null) + throw new System.ArgumentNullException(nameof(self)); + if (declaringTypeRef == null) + throw new System.ArgumentNullException(nameof(declaringTypeRef)); + if (!self.ReturnType.IsGenericParameter) + return self.ReturnType; + + var t = ((GenericInstanceType)declaringTypeRef).GenericArguments[((GenericParameter)self.ReturnType).Position]; + return t; + } } -}
\ No newline at end of file +} diff --git a/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs index 7a56c9d1..8cbbbe4a 100644 --- a/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs @@ -5,11 +5,17 @@ namespace Xamarin.Forms.Build.Tasks { static class PropertyDefinitionExtensions { - public static TypeReference ResolveGenericPropertyType(this PropertyDefinition self, TypeReference declaringTypeReference) + public static TypeReference ResolveGenericPropertyType(this PropertyDefinition self, TypeReference declaringTypeRef, + ModuleDefinition module) { - if (self.PropertyType.IsGenericParameter) - return ((GenericInstanceType)declaringTypeReference).GenericArguments [((GenericParameter)self.PropertyType).Position]; - return self.PropertyType; + if (self == null) + throw new ArgumentNullException(nameof(self)); + if (declaringTypeRef == null) + throw new ArgumentNullException(nameof(declaringTypeRef)); + if (!self.PropertyType.IsGenericParameter) + return self.PropertyType; + + return ((GenericInstanceType)declaringTypeRef).GenericArguments [((GenericParameter)self.PropertyType).Position]; } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs index 5cbf075d..f7da7689 100644 --- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -967,7 +967,7 @@ namespace Xamarin.Forms.Build.Tasks return false; var vardef = context.Variables [elementNode]; - var propertyType = property.ResolveGenericPropertyType(declaringTypeReference); + var propertyType = property.ResolveGenericPropertyType(declaringTypeReference, module); var implicitOperator = vardef.VariableType.GetImplicitOperatorTo(propertyType, module); if (implicitOperator != null) @@ -998,7 +998,7 @@ namespace Xamarin.Forms.Build.Tasks module.ImportReference(parent.VariableType.Resolve()); var propertySetterRef = module.ImportReference(module.ImportReference(propertySetter).ResolveGenericParameters(declaringTypeReference, module)); propertySetterRef.ImportTypes(module); - var propertyType = property.ResolveGenericPropertyType(declaringTypeReference); + var propertyType = property.ResolveGenericPropertyType(declaringTypeReference, module); var valueNode = node as ValueNode; var elementNode = node as IElementNode; diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml new file mode 100644 index 00000000..ad664ba0 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml @@ -0,0 +1,7 @@ +<?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:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests" + x:Class="Xamarin.Forms.Xaml.UnitTests.Bz53350"> + <local:Bz53350String x:Name="content" SomeBP="Foo" SomeProperty="Bar" /> +</ContentPage>
\ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml.cs new file mode 100644 index 00000000..f25c018a --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53350.xaml.cs @@ -0,0 +1,63 @@ +using System; +using NUnit.Framework; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public class Bz53350Generic<T> : ContentView + { + public static readonly BindableProperty SomeBPProperty = + BindableProperty.Create("SomeBP", typeof(T), typeof(Bz53350Generic<T>), default(T)); + + public T SomeBP + { + get { return (T)GetValue(SomeBPProperty); } + set { SetValue(SomeBPProperty, value); } + } + + public T SomeProperty { get; set; } + } + + public class Bz53350String : Bz53350Generic<string> + { + + } + + public partial class Bz53350 + { + public Bz53350() + { + } + + public Bz53350(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] + public void Setup() + { + Device.PlatformServices = new MockPlatformServices(); + } + + [TearDown] + public void TearDown() + { + Application.Current = null; + Device.PlatformServices = null; + } + + [TestCase(true)] + [TestCase(false)] + public void PropertiesWithGenericType(bool useCompiledXaml) + { + var layout = new Bz53350(useCompiledXaml); + Assert.That(layout.content.SomeBP, Is.EqualTo("Foo")); + Assert.That(layout.content.SomeProperty, Is.EqualTo("Bar")); + } + } + } +} diff --git a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj index d528379e..ba470e60 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj +++ b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj @@ -491,6 +491,10 @@ <Compile Include="Issues\Bz56852.xaml.cs"> <DependentUpon>Bz56852.xaml</DependentUpon> </Compile> + <Compile Include="Issues\Bz53350.xaml.cs"> + <DependentUpon>Bz53350.xaml</DependentUpon> + </Compile> + <Compile Include="XamlC\MethodDefinitionExtensionsTests.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" /> @@ -899,6 +903,9 @@ <EmbeddedResource Include="Issues\Bz56852.xaml"> <Generator>MSBuild:UpdateDesignTimeXaml</Generator> </EmbeddedResource> + <EmbeddedResource Include="Issues\Bz53350.xaml"> + <Generator>MSBuild:UpdateDesignTimeXaml</Generator> + </EmbeddedResource> </ItemGroup> <ItemGroup> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlC/MethodDefinitionExtensionsTests.cs b/Xamarin.Forms.Xaml.UnitTests/XamlC/MethodDefinitionExtensionsTests.cs new file mode 100644 index 00000000..6133d727 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/XamlC/MethodDefinitionExtensionsTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; + +using Mono.Cecil; + +using Xamarin.Forms.Build.Tasks; + +using NUnit.Framework; +using System.Collections.Generic; + +namespace Xamarin.Forms.Xaml.XamlcUnitTests +{ + [TestFixture] + public class MethodDefinitionExtensionsTests + { + public class NonGenericClass + { + public object Property { get; set; } + } + + public class GenericClass<T, U, V> + { + public object NonGeneric() => default(object); + public T GenericT() => default(T); + public U GenericU() => default(U); + public V GenericV() => default(V); + public IEnumerable<T> EnumerableT() => default(IEnumerable<T>); + public KeyValuePair<V, U> KvpVU() => default(KeyValuePair<V,U>); + } + + ModuleDefinition module; + + [SetUp] + public void SetUp() + { + module = ModuleDefinition.CreateModule("foo", ModuleKind.Dll); + } + + [Test] + public void ResolveGenericReturnType() + { + var type = module.ImportReference(typeof(GenericClass<bool, string, int>)); + + var getter = type.GetMethods(md => md.Name == "NonGeneric", module).Single(); + var returnType = getter.Item1.ResolveGenericReturnType(getter.Item2, module); + Assert.AreEqual("System.Object", returnType.FullName); + + getter = type.GetMethods(md => md.Name == "GenericT", module).Single(); + returnType = getter.Item1.ResolveGenericReturnType(getter.Item2, module); + Assert.AreEqual("System.Boolean", returnType.FullName); + + getter = type.GetMethods(md => md.Name == "GenericU", module).Single(); + returnType = getter.Item1.ResolveGenericReturnType(getter.Item2, module); + Assert.AreEqual("System.String", returnType.FullName); + + getter = type.GetMethods(md => md.Name == "GenericV", module).Single(); + returnType = getter.Item1.ResolveGenericReturnType(getter.Item2, module); + Assert.AreEqual("System.Int32", returnType.FullName); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlC/PropertyDefinitionExtensionsTests.cs b/Xamarin.Forms.Xaml.UnitTests/XamlC/PropertyDefinitionExtensionsTests.cs index b43e4c9d..e8e56afa 100644 --- a/Xamarin.Forms.Xaml.UnitTests/XamlC/PropertyDefinitionExtensionsTests.cs +++ b/Xamarin.Forms.Xaml.UnitTests/XamlC/PropertyDefinitionExtensionsTests.cs @@ -6,6 +6,7 @@ using Mono.Cecil; using Xamarin.Forms.Build.Tasks; using NUnit.Framework; +using System.Collections.Generic; namespace Xamarin.Forms.Xaml.XamlcUnitTests { @@ -17,10 +18,14 @@ namespace Xamarin.Forms.Xaml.XamlcUnitTests public object Property { get; set; } } - public class GenericClass<T> + public class GenericClass<T, U, V> { public object Property { get; set; } - public T GenericProperty { get; set; } + public T GenericT { get; set; } + public U GenericU { get; set; } + public V GenericV { get; set; } + public IEnumerable<T> EnumerableT { get; set; } + public KeyValuePair<V, U> KvpVU { get; set; } } ModuleDefinition module; @@ -31,45 +36,27 @@ namespace Xamarin.Forms.Xaml.XamlcUnitTests module = ModuleDefinition.CreateModule ("foo", ModuleKind.Dll); } -// [Test] -// public void ResolveGenericsOnNonGenericPreserveAccessors () -// { -// var type = module.Import (typeof (NonGenericClass)); -// TypeReference declaringTypeReference; -// PropertyDefinition prop = type.GetProperty (fd => fd.Name == "Property", out declaringTypeReference); -// Assert.AreEqual ("System.Object", prop.PropertyType.FullName); -// Assert.AreEqual ("System.Void Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass::set_Property(System.Object)", prop.SetMethod.FullName); -// Assert.AreEqual ("System.Object Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass::get_Property()", prop.GetMethod.FullName); -// Assert.AreEqual ("Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass", prop.DeclaringType.FullName); -// -// prop.ResolveGenericParameters (declaringTypeReference); -// -// Assert.AreEqual ("System.Object", prop.PropertyType.FullName); -// Assert.AreEqual ("System.Void Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass::set_Property(System.Object)", prop.SetMethod.FullName); -// Assert.AreEqual ("System.Object Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass::get_Property()", prop.GetMethod.FullName); -// Assert.AreEqual ("Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/NonGenericClass", prop.DeclaringType.FullName); -// -// } -// -// [Test] -// public void NonGenericPropertyOnGenericType () -// { -// var type = module.Import (typeof (GenericClass<bool>)); -// TypeReference declaringTypeReference; -// PropertyDefinition prop = type.GetProperty (fd => fd.Name == "Property", out declaringTypeReference); -// Assert.AreEqual ("System.Object", prop.PropertyType.FullName); -// Assert.AreEqual ("System.Void Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1::set_Property(System.Object)", prop.SetMethod.FullName); -// Assert.AreEqual ("System.Object Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1::get_Property()", prop.GetMethod.FullName); -// Assert.AreEqual ("Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1", prop.DeclaringType.FullName); -// Assert.False (prop.DeclaringType.IsGenericInstance); -// -// prop.ResolveGenericParameters (declaringTypeReference); -// Assert.AreEqual ("System.Object", prop.PropertyType.FullName); -// Assert.AreEqual ("System.Void Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1::set_Property(System.Object)", prop.SetMethod.FullName); -// Assert.AreEqual ("System.Object Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1::get_Property()", prop.GetMethod.FullName); -// Assert.AreEqual ("Xamarin.Forms.Xaml.XamlcUnitTests.PropertyDefinitionExtensionsTests/GenericClass`1", prop.DeclaringType.FullName); -// Assert.True (prop.DeclaringType.IsGenericInstance); -// -// } + [Test] + public void ResolveGenericPropertyType () + { + var type = module.ImportReference (typeof (GenericClass<bool, string, int>)); + TypeReference declaringTypeReference; + var prop = type.GetProperty (fd => fd.Name == "Property", out declaringTypeReference); + var propertyType = prop.ResolveGenericPropertyType (declaringTypeReference, module); + Assert.AreEqual ("System.Object", propertyType.FullName); + + prop = type.GetProperty(fd => fd.Name == "GenericT", out declaringTypeReference); + propertyType = prop.ResolveGenericPropertyType(declaringTypeReference, module); + Assert.AreEqual("System.Boolean", propertyType.FullName); + + prop = type.GetProperty(fd => fd.Name == "GenericU", out declaringTypeReference); + propertyType = prop.ResolveGenericPropertyType(declaringTypeReference, module); + Assert.AreEqual("System.String", propertyType.FullName); + + prop = type.GetProperty(fd => fd.Name == "GenericV", out declaringTypeReference); + propertyType = prop.ResolveGenericPropertyType(declaringTypeReference, module); + Assert.AreEqual("System.Int32", propertyType.FullName); + + } } }
\ No newline at end of file |