From 05740285f41030b1510ff38df6c6f43989c044a0 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 20 Nov 2020 08:55:18 -0800 Subject: [PATCH 1/4] Add vtable entry for IEnumerable for winrt types too. --- UnitTest/TestComponentCSharp_Tests.cs | 17 ++++++++++++++++- WinRT.Runtime/Projections.cs | 15 ++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/UnitTest/TestComponentCSharp_Tests.cs b/UnitTest/TestComponentCSharp_Tests.cs index 69cd6b8d0..017a16a29 100644 --- a/UnitTest/TestComponentCSharp_Tests.cs +++ b/UnitTest/TestComponentCSharp_Tests.cs @@ -687,7 +687,22 @@ public void TestObjectCasting() var objects = new List() { new ManagedType(), new ManagedType() }; var query = from item in objects select item; - TestObject.ObjectIterableProperty = query; + TestObject.ObjectIterableProperty = query; + + TestObject.ObjectProperty = "test"; + Assert.Equal("test", TestObject.ObjectProperty); + + var objectArray = new ManagedType[] { new ManagedType(), new ManagedType() }; + TestObject.ObjectIterableProperty = objectArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectArray)); + + var strArray = new string[] { "str1", "str2", "str3" }; + TestObject.ObjectIterableProperty = strArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(strArray)); + + var uriArray = new Uri[] { new Uri("http://aka.ms/cswinrt"), new Uri("http://github.com") }; + TestObject.ObjectIterableProperty = uriArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(uriArray)); } [Fact] diff --git a/WinRT.Runtime/Projections.cs b/WinRT.Runtime/Projections.cs index 06b06c9ca..920fe61e8 100644 --- a/WinRT.Runtime/Projections.cs +++ b/WinRT.Runtime/Projections.cs @@ -226,21 +226,14 @@ public static bool TryGetCompatibleWindowsRuntimeTypeForVariantType(Type type, o var newArguments = new Type[genericArguments.Length]; for (int i = 0; i < genericArguments.Length; i++) { - if (!IsTypeWindowsRuntimeTypeNoArray(genericArguments[i])) + bool argumentCovariant = (genericConstraints[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) == GenericParameterAttributes.Covariant; + if (argumentCovariant && !genericArguments[i].IsValueType) { - bool argumentCovariant = (genericConstraints[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) == GenericParameterAttributes.Covariant; - if (argumentCovariant && !genericArguments[i].IsValueType) - { - newArguments[i] = typeof(object); - } - else - { - return false; - } + newArguments[i] = typeof(object); } else { - newArguments[i] = genericArguments[i]; + return false; } } compatibleType = definition.MakeGenericType(newArguments); From 4c5a3842b70763623d98959639e6a872c321d0d6 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 23 Nov 2020 01:35:01 -0800 Subject: [PATCH 2/4] Adding more complete covariance support. --- TestComponentCSharp/Class.cpp | 28 ++++++ TestComponentCSharp/Class.h | 6 ++ TestComponentCSharp/TestComponentCSharp.idl | 2 + UnitTest/TestComponentCSharp_Tests.cs | 26 ++++- WinRT.Runtime/ComWrappersSupport.cs | 15 +-- WinRT.Runtime/Projections.cs | 103 +++++++++++++++++++- 6 files changed, 169 insertions(+), 11 deletions(-) diff --git a/TestComponentCSharp/Class.cpp b/TestComponentCSharp/Class.cpp index ac6248a24..94c825697 100644 --- a/TestComponentCSharp/Class.cpp +++ b/TestComponentCSharp/Class.cpp @@ -481,6 +481,34 @@ namespace winrt::TestComponentCSharp::implementation { _objectIterableChanged.remove(token); } + IIterable> Class::IterableOfPointIterablesProperty() + { + return _pointIterableIterable; + } + void Class::IterableOfPointIterablesProperty(IIterable> const& value) + { + for (auto pointList : value) + { + for (auto point : pointList) + { + } + } + _pointIterableIterable = value; + } + IIterable> Class::IterableOfObjectIterablesProperty() + { + return _objectIterableIterable; + } + void Class::IterableOfObjectIterablesProperty(IIterable> const& value) + { + for (auto objectList : value) + { + for (auto object : objectList) + { + } + } + _objectIterableIterable = value; + } Uri Class::UriProperty() { return _uri; diff --git a/TestComponentCSharp/Class.h b/TestComponentCSharp/Class.h index 7eae0b447..c2039f632 100644 --- a/TestComponentCSharp/Class.h +++ b/TestComponentCSharp/Class.h @@ -37,6 +37,8 @@ namespace winrt::TestComponentCSharp::implementation Windows::Foundation::IInspectable _object; winrt::event> _objectChanged; Windows::Foundation::Collections::IIterable _objectIterable; + Windows::Foundation::Collections::IIterable> _pointIterableIterable; + Windows::Foundation::Collections::IIterable> _objectIterableIterable; winrt::event>> _objectIterableChanged; Windows::Foundation::Uri _uri; winrt::event> _uriChanged; @@ -153,6 +155,10 @@ namespace winrt::TestComponentCSharp::implementation void CallForObjectIterable(TestComponentCSharp::ProvideObjectIterable const& provideObjectIterable); winrt::event_token ObjectIterablePropertyChanged(Windows::Foundation::EventHandler> const& handler); void ObjectIterablePropertyChanged(winrt::event_token const& token) noexcept; + Windows::Foundation::Collections::IIterable> IterableOfPointIterablesProperty(); + void IterableOfPointIterablesProperty(Windows::Foundation::Collections::IIterable> const& value); + Windows::Foundation::Collections::IIterable> IterableOfObjectIterablesProperty(); + void IterableOfObjectIterablesProperty(Windows::Foundation::Collections::IIterable> const& value); Windows::Foundation::Uri UriProperty(); void UriProperty(Windows::Foundation::Uri const& value); void RaiseUriChanged(); diff --git a/TestComponentCSharp/TestComponentCSharp.idl b/TestComponentCSharp/TestComponentCSharp.idl index 55815cdae..4d1148cdc 100644 --- a/TestComponentCSharp/TestComponentCSharp.idl +++ b/TestComponentCSharp/TestComponentCSharp.idl @@ -176,6 +176,8 @@ namespace TestComponentCSharp void RaiseObjectIterableChanged(); void CallForObjectIterable(ProvideObjectIterable provideObjectIterable); event Windows.Foundation.EventHandler > ObjectIterablePropertyChanged; + Windows.Foundation.Collections.IIterable > IterableOfPointIterablesProperty; + Windows.Foundation.Collections.IIterable > IterableOfObjectIterablesProperty; Windows.Foundation.Uri UriProperty; void RaiseUriChanged(); diff --git a/UnitTest/TestComponentCSharp_Tests.cs b/UnitTest/TestComponentCSharp_Tests.cs index 017a16a29..25c23376d 100644 --- a/UnitTest/TestComponentCSharp_Tests.cs +++ b/UnitTest/TestComponentCSharp_Tests.cs @@ -694,7 +694,7 @@ public void TestObjectCasting() var objectArray = new ManagedType[] { new ManagedType(), new ManagedType() }; TestObject.ObjectIterableProperty = objectArray; - Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectArray)); + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectArray)); var strArray = new string[] { "str1", "str2", "str3" }; TestObject.ObjectIterableProperty = strArray; @@ -703,6 +703,18 @@ public void TestObjectCasting() var uriArray = new Uri[] { new Uri("http://aka.ms/cswinrt"), new Uri("http://github.com") }; TestObject.ObjectIterableProperty = uriArray; Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(uriArray)); + + var objectUriArray = new object[] { new Uri("http://github.com") }; + TestObject.ObjectIterableProperty = objectUriArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectUriArray)); + + var listOfListOfUris = new List>() { + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://github.com") }, + new List{ new Uri("http://aka.ms/cswinrt") }, + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://microsoft.com") } + }; + TestObject.IterableOfObjectIterablesProperty = listOfListOfUris; + Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris)); } [Fact] @@ -2265,6 +2277,18 @@ public void TestIBindableVector() { CustomBindableVectorTest vector = new CustomBindableVectorTest(); Assert.NotNull(vector); + } + + [Fact] + public void TestCovariance() + { + var listOfListOfPoints = new List>() { + new List{ new Point(1, 1), new Point(1, 2), new Point(1, 3) }, + new List{ new Point(2, 1), new Point(2, 2), new Point(2, 3) }, + new List{ new Point(3, 1), new Point(3, 2), new Point(3, 3) } + }; + TestObject.IterableOfPointIterablesProperty = listOfListOfPoints; + Assert.True(TestObject.IterableOfPointIterablesProperty.SequenceEqual(listOfListOfPoints)); } } } \ No newline at end of file diff --git a/WinRT.Runtime/ComWrappersSupport.cs b/WinRT.Runtime/ComWrappersSupport.cs index 347a09247..0f5e8f329 100644 --- a/WinRT.Runtime/ComWrappersSupport.cs +++ b/WinRT.Runtime/ComWrappersSupport.cs @@ -107,14 +107,17 @@ internal static List GetInterfaceTableEntries(object obj) } if (iface.IsConstructedGenericType - && Projections.TryGetCompatibleWindowsRuntimeTypeForVariantType(iface, out var compatibleIface)) + && Projections.TryGetCompatibleWindowsRuntimeTypesForVariantType(iface, out var compatibleIfaces)) { - var compatibleIfaceAbiType = compatibleIface.FindHelperType(); - entries.Add(new ComInterfaceEntry + foreach (var compatibleIface in compatibleIfaces) { - IID = GuidGenerator.GetIID(compatibleIfaceAbiType), - Vtable = (IntPtr)compatibleIfaceAbiType.GetAbiToProjectionVftblPtr() - }); + var compatibleIfaceAbiType = compatibleIface.FindHelperType(); + entries.Add(new ComInterfaceEntry + { + IID = GuidGenerator.GetIID(compatibleIfaceAbiType), + Vtable = (IntPtr)compatibleIfaceAbiType.GetAbiToProjectionVftblPtr() + }); + } } } diff --git a/WinRT.Runtime/Projections.cs b/WinRT.Runtime/Projections.cs index 920fe61e8..402b0b73e 100644 --- a/WinRT.Runtime/Projections.cs +++ b/WinRT.Runtime/Projections.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; using System.Numerics; using System.Reflection; using System.Threading; @@ -206,6 +207,7 @@ private static bool IsTypeWindowsRuntimeTypeNoArray(Type type) || type.GetCustomAttribute() is object; } + // Use TryGetCompatibleWindowsRuntimeTypesForVariantType instead. public static bool TryGetCompatibleWindowsRuntimeTypeForVariantType(Type type, out Type compatibleType) { compatibleType = null; @@ -226,20 +228,113 @@ public static bool TryGetCompatibleWindowsRuntimeTypeForVariantType(Type type, o var newArguments = new Type[genericArguments.Length]; for (int i = 0; i < genericArguments.Length; i++) { - bool argumentCovariant = (genericConstraints[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) == GenericParameterAttributes.Covariant; - if (argumentCovariant && !genericArguments[i].IsValueType) + if (!IsTypeWindowsRuntimeTypeNoArray(genericArguments[i])) { - newArguments[i] = typeof(object); + bool argumentCovariant = (genericConstraints[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) == GenericParameterAttributes.Covariant; + if (argumentCovariant && !genericArguments[i].IsValueType) + { + newArguments[i] = typeof(object); + } + else + { + return false; + } } else { - return false; + newArguments[i] = genericArguments[i]; } } compatibleType = definition.MakeGenericType(newArguments); return true; } + private static HashSet GetCompatibleTypes(Type type) + { + HashSet compatibleTypes = new HashSet(); + + foreach (var iface in type.GetInterfaces()) + { + if (IsTypeWindowsRuntimeTypeNoArray(iface)) + { + compatibleTypes.Add(iface); + } + + if (iface.IsConstructedGenericType + && TryGetCompatibleWindowsRuntimeTypesForVariantType(iface, out var compatibleIfaces)) + { + compatibleTypes.UnionWith(compatibleIfaces); + } + } + + Type baseType = type.BaseType; + while (baseType != null) + { + if (IsTypeWindowsRuntimeTypeNoArray(baseType)) + { + compatibleTypes.Add(baseType); + } + baseType = baseType.BaseType; + } + + return compatibleTypes; + } + + internal static bool TryGetCompatibleWindowsRuntimeTypesForVariantType(Type type, out IEnumerable compatibleTypes) + { + compatibleTypes = null; + if (!type.IsConstructedGenericType) + { + throw new ArgumentException(nameof(type)); + } + + var definition = type.GetGenericTypeDefinition(); + + if (!IsTypeWindowsRuntimeTypeNoArray(definition)) + { + return false; + } + + var genericConstraints = definition.GetGenericArguments(); + var genericArguments = type.GetGenericArguments(); + List> compatibleTypesPerGeneric = new List>(); + for (int i = 0; i < genericArguments.Length; i++) + { + List compatibleTypesForGeneric = new List(); + bool argumentCovariantObject = (genericConstraints[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) == GenericParameterAttributes.Covariant + && !genericArguments[i].IsValueType; + + if (IsTypeWindowsRuntimeTypeNoArray(genericArguments[i])) + { + compatibleTypesForGeneric.Add(genericArguments[i]); + } + else if (!argumentCovariantObject) + { + return false; + } + + if (argumentCovariantObject) + { + compatibleTypesForGeneric.AddRange(GetCompatibleTypes(genericArguments[i])); + } + + compatibleTypesPerGeneric.Add(compatibleTypesForGeneric); + } + + // https://docs.microsoft.com/en-us/archive/blogs/ericlippert/computing-a-cartesian-product-with-linq + IEnumerable> newArgumentsList = new[] { Enumerable.Empty() }; + foreach (var compatibleTypesForGeneric in compatibleTypesPerGeneric) + { + newArgumentsList = + from seq in newArgumentsList + from item in compatibleTypesForGeneric + select seq.Concat(new[] { item }); + } + + compatibleTypes = newArgumentsList.Select(newArguments => definition.MakeGenericType(newArguments.ToArray())); + return true; + } + internal static bool TryGetDefaultInterfaceTypeForRuntimeClassType(Type runtimeClass, out Type defaultInterface) { runtimeClass = runtimeClass.GetRuntimeClassCCWType() ?? runtimeClass; From 7a1c04e94834661ee2d59c9638007c49b8a066dc Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 23 Nov 2020 11:09:43 -0800 Subject: [PATCH 3/4] PR feedback. --- TestComponentCSharp/Class.cpp | 8 ++++---- UnitTest/TestComponentCSharp_Tests.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/TestComponentCSharp/Class.cpp b/TestComponentCSharp/Class.cpp index 94c825697..b14a02f30 100644 --- a/TestComponentCSharp/Class.cpp +++ b/TestComponentCSharp/Class.cpp @@ -487,9 +487,9 @@ namespace winrt::TestComponentCSharp::implementation } void Class::IterableOfPointIterablesProperty(IIterable> const& value) { - for (auto pointList : value) + for (auto points : value) { - for (auto point : pointList) + for (auto point : points) { } } @@ -501,9 +501,9 @@ namespace winrt::TestComponentCSharp::implementation } void Class::IterableOfObjectIterablesProperty(IIterable> const& value) { - for (auto objectList : value) + for (auto objects : value) { - for (auto object : objectList) + for (auto object : objects) { } } diff --git a/UnitTest/TestComponentCSharp_Tests.cs b/UnitTest/TestComponentCSharp_Tests.cs index 25c23376d..474e44bf6 100644 --- a/UnitTest/TestComponentCSharp_Tests.cs +++ b/UnitTest/TestComponentCSharp_Tests.cs @@ -707,14 +707,6 @@ public void TestObjectCasting() var objectUriArray = new object[] { new Uri("http://github.com") }; TestObject.ObjectIterableProperty = objectUriArray; Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectUriArray)); - - var listOfListOfUris = new List>() { - new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://github.com") }, - new List{ new Uri("http://aka.ms/cswinrt") }, - new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://microsoft.com") } - }; - TestObject.IterableOfObjectIterablesProperty = listOfListOfUris; - Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris)); } [Fact] @@ -2289,6 +2281,14 @@ public void TestCovariance() }; TestObject.IterableOfPointIterablesProperty = listOfListOfPoints; Assert.True(TestObject.IterableOfPointIterablesProperty.SequenceEqual(listOfListOfPoints)); + + var listOfListOfUris = new List>() { + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://github.com") }, + new List{ new Uri("http://aka.ms/cswinrt") }, + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://microsoft.com") } + }; + TestObject.IterableOfObjectIterablesProperty = listOfListOfUris; + Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris)); } } } \ No newline at end of file From de3955eeca35bf60b06544dcd28e68a56993e7a5 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 23 Nov 2020 17:48:28 -0800 Subject: [PATCH 4/4] PR feedback. --- WinRT.Runtime/Projections.cs | 48 +++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/WinRT.Runtime/Projections.cs b/WinRT.Runtime/Projections.cs index 402b0b73e..6c345c53d 100644 --- a/WinRT.Runtime/Projections.cs +++ b/WinRT.Runtime/Projections.cs @@ -280,6 +280,42 @@ private static HashSet GetCompatibleTypes(Type type) return compatibleTypes; } + internal static IEnumerable GetAllPossibleTypeCombinations(IEnumerable> compatibleTypesPerGeneric, Type definition) + { + // Implementation adapted from https://stackoverflow.com/a/4424005 + var accum = new List(); + var compatibleTypesPerGenericArray = compatibleTypesPerGeneric.ToArray(); + if (compatibleTypesPerGenericArray.Length > 0) + { + GetAllPossibleTypeCombinationsCore( + accum, + new Stack(), + compatibleTypesPerGenericArray, + compatibleTypesPerGenericArray.Length - 1); + } + return accum; + + void GetAllPossibleTypeCombinationsCore(List accum, Stack stack, IEnumerable[] compatibleTypes, int index) + { + foreach (var type in compatibleTypes[index]) + { + stack.Push(type); + if (index == 0) + { + // IEnumerable on a System.Collections.Generic.Stack + // enumerates in order of removal (last to first). + // As a result, we get the correct ordering here. + accum.Add(definition.MakeGenericType(stack.ToArray())); + } + else + { + GetAllPossibleTypeCombinationsCore(accum, stack, compatibleTypes, index - 1); + } + stack.Pop(); + } + } + } + internal static bool TryGetCompatibleWindowsRuntimeTypesForVariantType(Type type, out IEnumerable compatibleTypes) { compatibleTypes = null; @@ -321,17 +357,7 @@ internal static bool TryGetCompatibleWindowsRuntimeTypesForVariantType(Type type compatibleTypesPerGeneric.Add(compatibleTypesForGeneric); } - // https://docs.microsoft.com/en-us/archive/blogs/ericlippert/computing-a-cartesian-product-with-linq - IEnumerable> newArgumentsList = new[] { Enumerable.Empty() }; - foreach (var compatibleTypesForGeneric in compatibleTypesPerGeneric) - { - newArgumentsList = - from seq in newArgumentsList - from item in compatibleTypesForGeneric - select seq.Concat(new[] { item }); - } - - compatibleTypes = newArgumentsList.Select(newArguments => definition.MakeGenericType(newArguments.ToArray())); + compatibleTypes = GetAllPossibleTypeCombinations(compatibleTypesPerGeneric, definition); return true; }