private static XamlIlBindingPathNode TransformBindingPath()

in src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs [140:435]


        private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
        {
            List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
            List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
            foreach (var astNode in bindingExpression)
            {
                var targetTypeResolver = nodes.Count == 0 ? startTypeResolver : () => nodes[nodes.Count - 1].Type;
                switch (astNode)
                {
                    case BindingExpressionGrammar.EmptyExpressionNode _:
                        break;
                    case BindingExpressionGrammar.NotNode _:
                        transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
                        break;
                    case BindingExpressionGrammar.StreamNode _:
                        {
                            IXamlType targetType = targetTypeResolver();
                            IXamlType? observableType;
                            if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
                            {
                                observableType = targetType;
                            }
                            else
                            {
                                observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false);
                            }

                            if (observableType != null)
                            {
                                nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0]));
                                break;
                            }

                            bool foundTask = false;
                            var taskType = context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1");

                            for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
                            {
                                if (currentType.GenericTypeDefinition?.Equals(taskType) == true)
                                {
                                    foundTask = true;
                                    nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
                                    break;
                                }
                            }
                            if (foundTask)
                            {
                                break;
                            }
                            throw new XamlX.XamlTransformException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
                        }
                    case BindingExpressionGrammar.PropertyNameNode propName:
                        {
                            IXamlType targetType = targetTypeResolver();
                            var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
                            var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
                                f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);

                            if (avaloniaPropertyFieldMaybe != null)
                            {
                                var isDataContextProperty = avaloniaPropertyFieldMaybe.Name == "DataContextProperty" && Equals(avaloniaPropertyFieldMaybe.DeclaringType, context.GetAvaloniaTypes().StyledElement);
                                var propertyType = isDataContextProperty
                                    ? (nodes.LastOrDefault() as IXamlIlBindingPathNodeWithDataContextType)?.DataContextType
                                    : null;
                                propertyType ??= XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo);

                                nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, propertyType, propName.AcceptsNull));
                            }
                            else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty)
                            {
                                nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty, propName.AcceptsNull));
                            }
                            else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method)
                            {
                                nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate, propName.AcceptsNull));
                            }
                            else
                            {
                                throw new XamlX.XamlTransformException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
                            }
                            break;
                        }
                    case BindingExpressionGrammar.IndexerNode indexer:
                        {
                            IXamlType targetType = targetTypeResolver();
                            if (targetType.IsArray)
                            {
                                nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo));
                                break;
                            }

                            IXamlProperty? property = null;
                            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 = currentType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]!);
                                    break;
                                }
                            }
                            if (property is null)
                            {
                                throw new XamlX.XamlTransformException($"The type '${targetType}' does not have an indexer.", lineInfo);
                            }

                            IEnumerable<IXamlType> parameters = property.IndexerParameters;

                            List<IXamlAstValueNode> values = new List<IXamlAstValueNode>();
                            int currentParamIndex = 0;
                            foreach (var param in parameters)
                            {
                                var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
                                if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
                                        property.CustomAttributes, param, out var converted))
                                    throw new XamlX.XamlTransformException(
                                        $"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
                                        textNode);

                                values.Add(converted);
                                currentParamIndex++;
                            }

                            bool isNotifyingCollection = targetType.GetAllInterfaces().Any(i => i.FullName == "System.Collections.Specialized.INotifyCollectionChanged");

                            nodes.Add(new XamlIlClrIndexerPathElementNode(property, values, string.Join(",", indexer.Arguments), isNotifyingCollection));
                            break;
                        }
                    case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
                        var avaloniaPropertyFieldName = attachedProp.PropertyName + "Property";
                        var propertyOwnerType = GetType(attachedProp.Namespace, attachedProp.TypeName);
                        var avaloniaPropertyField = propertyOwnerType.GetAllFields().FirstOrDefault(f =>
                            f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);

                        if (avaloniaPropertyField is null)
                        {
                            throw new XamlTransformException(
                                $"Unable to find {avaloniaPropertyFieldName} field on type {propertyOwnerType.GetFullName()}",
                                lineInfo);
                        }

                        nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
                            XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo),
                            attachedProp.AcceptsNull));
                        break;
                    case BindingExpressionGrammar.SelfNode _:
                        nodes.Add(new SelfPathElementNode(selfType));
                        break;
                    case VisualAncestorBindingExpressionNode visualAncestor:
                        nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level));
                        break;
                    case TemplatedParentBindingExpressionNode templatedParent:
                        var templatedParentType = context
                            .ParentNodes()
                            .OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
                            .Where(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
                            .FirstOrDefault()?.TargetType;

                        if (templatedParentType is null)
                        {
                            throw new XamlTransformException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", lineInfo);
                        }

                        nodes.Add(new TemplatedParentPathElementNode(templatedParentType.GetClrType()));
                        break;
                    case BindingExpressionGrammar.AncestorNode ancestor:
                        var styledElement = context.GetAvaloniaTypes().StyledElement;
                        var ancestorTypeFilter = !(ancestor.Namespace is null && ancestor.TypeName is null) ? GetType(ancestor.Namespace, ancestor.TypeName) : null;

                        var ancestorNode = context
                            .ParentNodes()
                            .OfType<XamlAstConstructableObjectNode>()
                            .Where(x => styledElement.IsAssignableFrom(x.Type.GetClrType()))
                            .Skip(1)
                            .Where(x => ancestorTypeFilter is not null
                                ? ancestorTypeFilter.IsAssignableFrom(x.Type.GetClrType()) : true)
                            .ElementAtOrDefault(ancestor.Level);

                        IXamlType? dataContextType = null;
                        if (ancestorNode is not null)
                        {
                            var isSkipping = true;
                            foreach (var node in context.ParentNodes())
                            {
                                if (node == ancestorNode)
                                    isSkipping = false;
                                if (node is AvaloniaNameScopeRegistrationXamlIlNode)
                                    break;
                                if (!isSkipping && node is AvaloniaXamlIlDataContextTypeMetadataNode metadataNode)
                                {
                                    dataContextType = metadataNode.DataContextType;
                                    break;
                                }
                            }
                        }

                        // We need actual ancestor for a correct DataContextType,
                        // but since in current design bindings do a double-work by enumerating the tree,
                        // we want to keep original ancestor type filter, if it was present.
                        var bindingAncestorType = ancestorTypeFilter is not null
                            ? ancestorTypeFilter
                            : ancestorNode?.Type.GetClrType();

                        if (bindingAncestorType is null)
                        {
                            throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
                        }

                        nodes.Add(new FindAncestorPathElementNode(bindingAncestorType, ancestor.Level, dataContextType));
                        break;
                    case BindingExpressionGrammar.NameNode elementName:
                        IXamlType? elementType = null, dataType = null;
                        foreach (var deferredContent in context.ParentNodes().OfType<NestedScopeMetadataNode>())
                        {
                            (elementType, dataType) = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name) ?? default;
                            if (!(elementType is null))
                            {
                                break;
                            }
                        }
                        if (elementType is null)
                        {
                            (elementType, dataType) = ScopeRegistrationFinder.GetTargetType(context.ParentNodes().Last(), elementName.Name) ?? default;
                        }

                        if (elementType is null)
                        {
                            throw new XamlX.XamlTransformException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
                        }
                        nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType, dataType));
                        break;
                    case BindingExpressionGrammar.TypeCastNode typeCastNode:
                        var castType = GetType(typeCastNode.Namespace, typeCastNode.TypeName);

                        if (castType is null)
                        {
                            throw new XamlX.XamlTransformException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
                        }

                        nodes.Add(new TypeCastPathElementNode(castType));
                        break;
                }
            }

            return new XamlIlBindingPathNode(lineInfo, context.GetAvaloniaTypes().CompiledBindingPath, transformNodes, nodes);

            IXamlType GetType(string? ns, string? name)
            {
                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<IXamlMethod> GetAllDefinedMethods(IXamlType type)
            {
                foreach (var t in TraverseTypeHierarchy(type))
                {
                    foreach (var m in t.Methods)
                    {
                        yield return m;
                    }
                }
            }

            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;
                    }
                }
            }
        }