Browse Source

Merge pull request #4235 from jkoritzinsky/compiled-bindings-inherited-interfaces

Fix Discovering properties and indexers on parent interfaces of an interface type.
danwalmsley 5 years ago
parent
commit
70c0e12c12

+ 37 - 4
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs

@@ -121,7 +121,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                         }
                         else
                         {
-                            var clrProperty = targetType.GetAllProperties().FirstOrDefault(p => p.Name == propName.PropertyName);
+                            var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName);
 
                             if (clrProperty is null)
                             {
@@ -139,15 +139,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                             }
 
                             IXamlProperty property = null;
-                            for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
+                            foreach (var currentType in TraverseTypeHierarchy(targetType))
                             {
                                 var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute");
                                 if (defaultMemberAttribute != null)
                                 {
-                                    property = targetType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
+                                    property = currentType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
                                     break;
                                 }
-                            };
+                            }
                             if (property is null)
                             {
                                 throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
@@ -252,6 +252,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
                     lineInfo, true).GetClrType();
             }
+
+            static IEnumerable<IXamlProperty> GetAllDefinedProperties(IXamlType type)
+            {
+                foreach (var t in TraverseTypeHierarchy(type))
+                {
+                    foreach (var p in t.Properties)
+                    {
+                        yield return p;
+                    }
+                }
+            }
+
+            static IEnumerable<IXamlType> TraverseTypeHierarchy(IXamlType type)
+            {
+                if (type.IsInterface)
+                {
+                    yield return type;
+                    foreach (var iface in type.Interfaces)
+                    {
+                        foreach (var h in TraverseTypeHierarchy(iface))
+                        {
+                            yield return h;
+                        }
+                    }
+                }
+                else
+                {
+                    for (var currentType = type; currentType != null; currentType = currentType.BaseType)
+                    {
+                        yield return currentType;
+                    }
+                }
+            }
         }
 
         class ScopeRegistrationFinder : IXamlAstVisitor

+ 77 - 2
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -43,6 +43,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             }
         }
 
+        [Fact]
+        public void ResolvesClrPropertyBasedOnDataContextType_InterfaceInheritance()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:IHasPropertyDerived'>
+    <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+
         [Fact]
         public void ResolvesPathPassedByProperty()
         {
@@ -274,6 +301,36 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             }
         }
 
+        [Fact]
+        public void ResolvesNonIntegerIndexerBindingFromParentInterfaceCorrectly()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'>
+    <TextBlock Text='{CompiledBinding NonIntegerIndexerInterfaceProperty[Test]}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext();
+
+                dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "Initial Value";
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text);
+
+                dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "New Value";
+
+                Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text);
+            }
+        }
+
         [Fact]
         public void InfersDataTemplateTypeFromDataTypeProperty()
         {
@@ -584,7 +641,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         }
     }
 
-    public class TestDataContext
+    public interface INonIntegerIndexer
+    {
+        string this[string key] {get; set;}
+    }
+
+    public interface INonIntegerIndexerDerived : INonIntegerIndexer
+    {}
+
+    public interface IHasProperty
+    {
+        string StringProperty {get; set; }
+    }
+
+    public interface IHasPropertyDerived : IHasProperty
+    {}
+
+    public class TestDataContext : IHasPropertyDerived
     {
         public string StringProperty { get; set; }
 
@@ -600,7 +673,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
 
         public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer();
 
-        public class NonIntegerIndexer : NotifyingBase
+        public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty;
+
+        public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived
         {
             private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();