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