Resolve fallback type before trying to make constructed generic type (#6511)
authorTim Barham <tim.barham@microsoft.com>
Thu, 13 Jun 2019 18:21:55 +0000 (04:21 +1000)
committerStephane Delcroix <stephane@delcroix.org>
Thu, 13 Jun 2019 18:21:55 +0000 (20:21 +0200)
* Resolve fallback type before trying to make constructed generic type

The FallbackTypeResolver hander in the Forms previewer does not have access to the x:TypeArguments attribute, so if it constructs a proxy generic type from metadata it can't construct it. So the previewer has to create an unconstructed generic type, and Forms will need to construct it.

So we need to move the code that constructs the generic type after the call to FallbackTypeResolver.

(cherry picked from commit 5acfea4f457453856a8905002f4d35c8e9aad106)

* Fix error building DesignTimeLoaderTests in 4.1

Xamarin.Forms.Xaml.UnitTests/DesignTimeLoaderTests.cs
Xamarin.Forms.Xaml/XamlParser.cs

index 075e3e7..dd0505b 100644 (file)
@@ -285,7 +285,28 @@ namespace Xamarin.Forms.Xaml.UnitTests
                [Test]
                public void UnknownGenericType()
                {
+                       XamlLoader.FallbackTypeResolver = (p, type) => type ?? 
+                               (p.Any(i => i.TypeName == "MyCustomButton`1") ? typeof(ProxyGenericButton<>) : typeof(MockView));
+
+                       var xaml = @"
+                               <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
+                                       xmlns:local=""clr-namespace:MissingNamespace;assembly=MissingAssembly""
+                                       xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml"">
+                                       <local:MyCustomButton x:TypeArguments=""local:MyCustomType"" />
+                                </ContentPage>";
+
+                       var page = (ContentPage)XamlLoader.Create(xaml, true);
+                       Assert.That(page.Content, Is.TypeOf<ProxyGenericButton<MockView>>());
+               }
+
+               [Test]
+               public void InvalidGenericType()
+               {
+                       int exceptionCount = 0;
+#pragma warning disable 0618 // Type or member is obsolete
+                       Forms.Internals.ResourceLoader.ExceptionHandler = _ => exceptionCount++;
                        XamlLoader.FallbackTypeResolver = (p, type) => type ?? typeof(MockView);
+#pragma warning restore 0618 // Type or member is obsolete
 
                        var xaml = @"
                                <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
@@ -295,7 +316,8 @@ namespace Xamarin.Forms.Xaml.UnitTests
                                 </ContentPage>";
 
                        var page = (ContentPage)XamlLoader.Create(xaml, true);
-                       Assert.That(page.Content, Is.TypeOf<MockView>());
+                       Assert.That(page.Content, Is.Null);
+                       Assert.That(exceptionCount, Is.EqualTo(1));
                }
 
                [Test]
@@ -707,14 +729,20 @@ namespace Xamarin.Forms.Xaml.UnitTests
 
                        XamlLoader.FallbackTypeResolver = (p, type) =>
                        {
+                               if (type != null)
+                                       return type;
                                Assert.That(p.Select(i => i.TypeName), Has.Some.EqualTo("GenericContentPage`1"));
-                               return typeof(ContentPage);
+                               return typeof(ProxyGenericContentPage<>);
                        };
 
                        Assert.DoesNotThrow(() => XamlLoader.Create(xaml, true));
                }
        }
 
+       public class ProxyGenericContentPage<T> : ContentPage { }
+
+       public class ProxyGenericButton<T> : Button { }
+
        public class InstantiateThrows
        {
                public InstantiateThrows() => throw new InvalidOperationException();
index f6918c9..19df0a1 100644 (file)
@@ -369,6 +369,24 @@ namespace Xamarin.Forms.Xaml
                        var typeArguments = xmlType.TypeArguments;
                        exception = null;
 
+#if NETSTANDARD2_0
+                       if (type == null)
+                       {
+                               // This covers the scenario where the AppDomain's loaded
+                               // assemblies might have changed since this method was first
+                               // called. This occurred during unit test runs and could
+                               // conceivably occur in the field. 
+                               if (!hasRetriedNsSearch) {
+                                       hasRetriedNsSearch = true;
+                                       s_xmlnsDefinitions = null;
+                                       goto retry;
+                               }
+                       }
+#endif
+                                                       
+                       if (XamlLoader.FallbackTypeResolver != null)
+                               type = XamlLoader.FallbackTypeResolver(potentialTypes, type);
+
                        if (type != null && typeArguments != null)
                        {
                                XamlParseException innerexception = null;
@@ -386,26 +404,13 @@ namespace Xamarin.Forms.Xaml
                                        exception = innerexception;
                                        return null;
                                }
-                               type = type.MakeGenericType(args);
-                       }
 
-#if NETSTANDARD2_0
-                       if (type == null)
-                       {
-                               // This covers the scenario where the AppDomain's loaded
-                               // assemblies might have changed since this method was first
-                               // called. This occurred during unit test runs and could
-                               // conceivably occur in the field. 
-                               if (!hasRetriedNsSearch) {
-                                       hasRetriedNsSearch = true;
-                                       s_xmlnsDefinitions = null;
-                                       goto retry;
+                               try {
+                                       type = type.MakeGenericType(args);
+                               } catch (InvalidOperationException) {
+                                       exception = new XamlParseException($"Type {type} is not a GenericTypeDefinition", xmlInfo);
                                }
                        }
-#endif
-                                                       
-                       if (XamlLoader.FallbackTypeResolver != null)
-                               type = XamlLoader.FallbackTypeResolver(potentialTypes, type);
 
                        if (type == null)
                                exception = new XamlParseException($"Type {xmlType.Name} not found in xmlns {xmlType.NamespaceUri}", xmlInfo);