csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
20 KiB
459 lines
20 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using Avalonia.Markup.Parsers;
|
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
|
|
using XamlIl.Ast;
|
|
using XamlIl.Transform;
|
|
using XamlIl.Transform.Transformers;
|
|
using XamlIl.TypeSystem;
|
|
using XamlIl;
|
|
using Avalonia.Utilities;
|
|
|
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|
{
|
|
static class XamlIlBindingPathHelper
|
|
{
|
|
public static IXamlIlType UpdateCompiledBindingExtension(XamlIlAstTransformationContext context, XamlIlAstObjectNode binding, IXamlIlType startType)
|
|
{
|
|
IXamlIlType bindingResultType = null;
|
|
if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlIlAstTextNode bindingPathText)
|
|
{
|
|
var reader = new CharacterReader(bindingPathText.Text.AsSpan());
|
|
var grammar = BindingExpressionGrammar.Parse(ref reader);
|
|
|
|
var transformed = TransformBindingPath(
|
|
context,
|
|
bindingPathText,
|
|
startType,
|
|
grammar.Nodes);
|
|
|
|
bindingResultType = transformed.BindingResultType;
|
|
binding.Arguments[0] = transformed;
|
|
}
|
|
else
|
|
{
|
|
var bindingPathAssignment = binding.Children.OfType<XamlIlAstXamlPropertyValueNode>()
|
|
.FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path");
|
|
|
|
if (bindingPathAssignment is null)
|
|
{
|
|
return startType;
|
|
}
|
|
|
|
if (bindingPathAssignment.Values[0] is XamlIlAstTextNode pathValue)
|
|
{
|
|
var reader = new CharacterReader(pathValue.Text.AsSpan());
|
|
var grammar = BindingExpressionGrammar.Parse(ref reader);
|
|
|
|
var transformed = TransformBindingPath(
|
|
context,
|
|
pathValue,
|
|
startType,
|
|
grammar.Nodes);
|
|
|
|
bindingResultType = transformed.BindingResultType;
|
|
bindingPathAssignment.Values[0] = transformed;
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
|
|
return bindingResultType;
|
|
}
|
|
|
|
private static IXamlIlBindingPathNode TransformBindingPath(XamlIlAstTransformationContext context, IXamlIlLineInfo lineInfo, IXamlIlType startType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
|
|
{
|
|
bool appendNotNode = false;
|
|
List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
|
|
foreach (var astNode in bindingExpression)
|
|
{
|
|
var targetType = nodes.Count == 0 ? startType : nodes[nodes.Count - 1].Type;
|
|
switch (astNode)
|
|
{
|
|
case BindingExpressionGrammar.EmptyExpressionNode _:
|
|
break;
|
|
case BindingExpressionGrammar.NotNode _:
|
|
appendNotNode = !appendNotNode;
|
|
break;
|
|
case BindingExpressionGrammar.StreamNode _:
|
|
var 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;
|
|
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
|
|
{
|
|
if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1")))
|
|
{
|
|
foundTask = true;
|
|
nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
|
|
break;
|
|
}
|
|
}
|
|
if (foundTask)
|
|
{
|
|
break;
|
|
}
|
|
throw new XamlIlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
|
|
case BindingExpressionGrammar.PropertyNameNode propName:
|
|
var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
|
|
var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
|
|
f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);
|
|
|
|
if (avaloniaPropertyFieldMaybe != null)
|
|
{
|
|
nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
|
|
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
|
|
}
|
|
else
|
|
{
|
|
var clrProperty = targetType.GetAllProperties().FirstOrDefault(p => p.Name == propName.PropertyName);
|
|
nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
|
|
}
|
|
break;
|
|
case BindingExpressionGrammar.IndexerNode indexer:
|
|
{
|
|
IXamlIlProperty property = null;
|
|
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
|
|
{
|
|
var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.GetFullName() == "System.Reflection.DefaultMemberAttribute");
|
|
if (defaultMemberAttribute != null)
|
|
{
|
|
property = targetType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
|
|
break;
|
|
}
|
|
|
|
};
|
|
if (property is null)
|
|
{
|
|
throw new XamlIlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
|
|
}
|
|
|
|
IEnumerable<IXamlIlType> parameters = property.IndexerParameters;
|
|
|
|
List<IXamlIlAstValueNode> values = new List<IXamlIlAstValueNode>();
|
|
int currentParamIndex = 0;
|
|
foreach (var param in parameters)
|
|
{
|
|
var textNode = new XamlIlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex]);
|
|
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
|
|
param, out var converted))
|
|
throw new XamlIlParseException(
|
|
$"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, isNotifyingCollection));
|
|
break;
|
|
}
|
|
case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
|
|
var avaloniaPropertyFieldName = attachedProp.PropertyName + "Property";
|
|
var avaloniaPropertyField = GetType(attachedProp.Namespace, attachedProp.TypeName).GetAllFields().FirstOrDefault(f =>
|
|
f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);
|
|
nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
|
|
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo)));
|
|
break;
|
|
case BindingExpressionGrammar.SelfNode _:
|
|
nodes.Add(new SelfPathElementNode(targetType));
|
|
break;
|
|
case BindingExpressionGrammar.AncestorNode ancestor:
|
|
nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level));
|
|
break;
|
|
case BindingExpressionGrammar.NameNode elementName:
|
|
var elementType = ScopeRegistrationFinder.GetControlType(context, context.RootObject, elementName.Name);
|
|
if (elementType is null)
|
|
{
|
|
throw new XamlIlParseException($"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));
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (appendNotNode)
|
|
{
|
|
// TODO: Fix Not behavior
|
|
nodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
|
|
}
|
|
|
|
return new XamlIlBindingPathNode(lineInfo, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
|
|
|
|
IXamlIlType GetType(string ns, string name)
|
|
{
|
|
return XamlIlTypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
|
|
lineInfo, true).GetClrType();
|
|
}
|
|
}
|
|
|
|
class ScopeRegistrationFinder : IXamlIlAstTransformer
|
|
{
|
|
private ScopeRegistrationFinder(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
string Name { get; }
|
|
|
|
IXamlIlType ControlType { get; set; }
|
|
|
|
public static IXamlIlType GetControlType(XamlIlAstTransformationContext context, IXamlIlAstNode namescopeRoot, string name)
|
|
{
|
|
var finder = new ScopeRegistrationFinder(name);
|
|
context.Visit(namescopeRoot, finder);
|
|
return finder.ControlType;
|
|
}
|
|
|
|
IXamlIlAstNode IXamlIlAstTransformer.Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
|
|
{
|
|
if (node is ScopeRegistrationNode registration)
|
|
{
|
|
if (registration.Value is XamlIlAstTextNode text && text.Text == Name)
|
|
{
|
|
ControlType = registration.ControlType;
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
}
|
|
|
|
interface IXamlIlBindingPathElementNode
|
|
{
|
|
IXamlIlType Type { get; }
|
|
|
|
void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
|
|
}
|
|
|
|
class XamlIlNotPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
public XamlIlNotPathElementNode(IXamlIlType boolType)
|
|
{
|
|
Type = boolType;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Not"));
|
|
}
|
|
}
|
|
|
|
class XamlIlStreamObservablePathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
public XamlIlStreamObservablePathElementNode(IXamlIlType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamObservable").MakeGenericMethod(new[] { Type }));
|
|
}
|
|
}
|
|
|
|
class XamlIlStreamTaskPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
public XamlIlStreamTaskPathElementNode(IXamlIlType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamTask").MakeGenericMethod(new[] { Type }));
|
|
}
|
|
}
|
|
|
|
class SelfPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
public SelfPathElementNode(IXamlIlType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Self"));
|
|
}
|
|
}
|
|
|
|
class FindAncestorPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
private readonly int _level;
|
|
|
|
public FindAncestorPathElementNode(IXamlIlType ancestorType, int level)
|
|
{
|
|
Type = ancestorType;
|
|
_level = level;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.Ldtype(Type)
|
|
.Ldc_I4(_level)
|
|
.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "FindAncestor"));
|
|
}
|
|
}
|
|
|
|
class ElementNamePathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
private readonly string _name;
|
|
|
|
public ElementNamePathElementNode(string name, IXamlIlType elementType)
|
|
{
|
|
_name = name;
|
|
Type = elementType;
|
|
}
|
|
|
|
public IXamlIlType Type { get; }
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
codeGen.Ldstr(_name)
|
|
.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "ElementName"));
|
|
}
|
|
}
|
|
|
|
class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
private readonly IXamlIlField _field;
|
|
|
|
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlIlField field, IXamlIlType propertyType)
|
|
{
|
|
_field = field;
|
|
Type = propertyType;
|
|
}
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
=> codeGen
|
|
.Ldsfld(_field)
|
|
.EmitCall(context.GetAvaloniaTypes()
|
|
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateAvaloniaPropertyInfo"))
|
|
.EmitCall(context.GetAvaloniaTypes()
|
|
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
|
|
|
|
public IXamlIlType Type { get; }
|
|
}
|
|
|
|
class XamlIlClrPropertyPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
private readonly IXamlIlProperty _property;
|
|
|
|
public XamlIlClrPropertyPathElementNode(IXamlIlProperty property)
|
|
{
|
|
_property = property;
|
|
}
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
|
|
.Emit(context, codeGen, _property);
|
|
|
|
codeGen
|
|
.EmitCall(context.GetAvaloniaTypes()
|
|
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateINPCPropertyInfo"))
|
|
.EmitCall(context.GetAvaloniaTypes()
|
|
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
|
|
}
|
|
|
|
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
|
|
}
|
|
|
|
class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
|
|
{
|
|
private readonly IXamlIlProperty _property;
|
|
private readonly List<IXamlIlAstValueNode> _values;
|
|
private readonly bool _isNotifyingCollection;
|
|
|
|
public XamlIlClrIndexerPathElementNode(IXamlIlProperty property, List<IXamlIlAstValueNode> values, bool isNotifyingCollection)
|
|
{
|
|
_property = property;
|
|
_values = values;
|
|
_isNotifyingCollection = isNotifyingCollection;
|
|
}
|
|
|
|
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
var intType = context.Configuration.TypeSystem.GetType("System.Int32");
|
|
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
|
|
.Emit(context, codeGen, _property, _values);
|
|
|
|
if (_isNotifyingCollection
|
|
&&
|
|
_values.Count == 1
|
|
&& _values[0].Type.GetClrType().Equals(intType))
|
|
{
|
|
context.Emit(_values[0], codeGen, intType);
|
|
codeGen.EmitCall(context.GetAvaloniaTypes()
|
|
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateIndexerPropertyInfo"));
|
|
}
|
|
else
|
|
{
|
|
codeGen.EmitCall(context.GetAvaloniaTypes()
|
|
.NotifyingPropertyInfoHelpers.FindMethod(m => m.Name == "CreateINPCPropertyInfo"));
|
|
}
|
|
|
|
codeGen.EmitCall(context.GetAvaloniaTypes()
|
|
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
|
|
}
|
|
|
|
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
|
|
}
|
|
|
|
class XamlIlBindingPathNode : XamlIlAstNode, IXamlIlBindingPathNode, IXamlIlAstEmitableNode
|
|
{
|
|
private readonly List<IXamlIlBindingPathElementNode> _elements;
|
|
|
|
public XamlIlBindingPathNode(IXamlIlLineInfo lineInfo,
|
|
IXamlIlType bindingPathType,
|
|
List<IXamlIlBindingPathElementNode> elements) : base(lineInfo)
|
|
{
|
|
Type = new XamlIlAstClrTypeReference(lineInfo, bindingPathType, false);
|
|
_elements = elements;
|
|
}
|
|
|
|
public IXamlIlType BindingResultType => _elements[_elements.Count - 1].Type;
|
|
|
|
public IXamlIlAstTypeReference Type { get; }
|
|
|
|
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
|
|
{
|
|
var types = context.GetAvaloniaTypes();
|
|
codeGen.Newobj(types.CompiledBindingPathBuilder.FindConstructor());
|
|
|
|
foreach (var element in _elements)
|
|
{
|
|
element.Emit(context, codeGen);
|
|
}
|
|
|
|
codeGen.EmitCall(types.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Build"));
|
|
return XamlIlNodeEmitResult.Type(0, types.CompiledBindingPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface IXamlIlBindingPathNode : IXamlIlAstValueNode
|
|
{
|
|
IXamlIlType BindingResultType { get; }
|
|
}
|
|
}
|
|
|