BindingPathFromExpressionBuilder.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. using System.Text;
  7. using Avalonia.Data.Core;
  8. #nullable enable
  9. namespace Avalonia.Base.UnitTests.Data.Core;
  10. internal class BindingPathFromExpressionBuilder : ExpressionVisitor
  11. {
  12. private static readonly PropertyInfo AvaloniaObjectIndexer;
  13. private static readonly string IndexerGetterName = "get_Item";
  14. private const string MultiDimensionalArrayGetterMethodName = "Get";
  15. private readonly LambdaExpression _rootExpression;
  16. private readonly StringBuilder _path = new();
  17. private Expression? _head;
  18. private int _negationCount;
  19. private TypeResolver? _resolver;
  20. public BindingPathFromExpressionBuilder(LambdaExpression expression)
  21. {
  22. _rootExpression = expression;
  23. }
  24. static BindingPathFromExpressionBuilder()
  25. {
  26. AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty("Item", new[] { typeof(AvaloniaProperty) })!;
  27. }
  28. public static (string, Func<string?, string, Type>?) Build<TIn, TOut>(Expression<Func<TIn, TOut>> expression)
  29. {
  30. var visitor = new BindingPathFromExpressionBuilder(expression);
  31. visitor.Visit(expression);
  32. visitor._path.Insert(0, new string('!', visitor._negationCount));
  33. Func<string?, string, Type>? resolver = null;
  34. if (visitor._resolver is not null)
  35. resolver = visitor._resolver.Resolve;
  36. return (visitor._path.ToString(), resolver);
  37. }
  38. protected override Expression VisitBinary(BinaryExpression node)
  39. {
  40. // Indexers require more work since the compiler doesn't generate IndexExpressions:
  41. // they weren't in System.Linq.Expressions v1 and so must be generated manually.
  42. if (node.NodeType == ExpressionType.ArrayIndex)
  43. return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right }));
  44. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  45. }
  46. protected override Expression VisitIndex(IndexExpression node)
  47. {
  48. if (node.Indexer == AvaloniaObjectIndexer)
  49. {
  50. var property = GetValue<AvaloniaProperty>(node.Arguments[0]);
  51. var name = property.Name;
  52. if (property.IsAttached)
  53. {
  54. _resolver ??= new();
  55. _resolver.Add(property.OwnerType.Name, property.OwnerType);
  56. name = $"({property.OwnerType.Name}.{property.Name})";
  57. }
  58. return Add(node.Object, node, name, ".");
  59. }
  60. else
  61. {
  62. var indexes = string.Join(',', node.Arguments.Select(GetValue<object>));
  63. return Add(node.Object, node, $"[{indexes}]");
  64. }
  65. }
  66. protected override Expression VisitMember(MemberExpression node)
  67. {
  68. if (node.Member.MemberType != MemberTypes.Property)
  69. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  70. var property = (PropertyInfo)node.Member;
  71. return Add(node.Expression, node, property.Name, ".");
  72. }
  73. protected override Expression VisitMethodCall(MethodCallExpression node)
  74. {
  75. var method = node.Method;
  76. if (method.Name == IndexerGetterName && node.Object is not null)
  77. {
  78. var property = TryGetPropertyFromMethod(method);
  79. return Visit(Expression.MakeIndex(node.Object, property, node.Arguments));
  80. }
  81. else if (method.Name == MultiDimensionalArrayGetterMethodName &&
  82. node.Object is not null)
  83. {
  84. var indexes = string.Join(',', node.Arguments.Select(GetValue<int>));
  85. return Add(node.Object, node, $"[{indexes}]");
  86. }
  87. else if (method.Name.StartsWith(StreamBindingExtensions.StreamBindingName) &&
  88. method.DeclaringType == typeof(StreamBindingExtensions) &&
  89. method.GetGenericArguments() is [Type genericArg])
  90. {
  91. var instance = node.Method.IsStatic ? node.Arguments[0] : node.Object;
  92. return Add(instance, node, "^");
  93. }
  94. throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType}.{node.Method.Name}'.");
  95. }
  96. protected override Expression VisitParameter(ParameterExpression node)
  97. {
  98. if (node == _rootExpression.Parameters[0] && _head is null)
  99. _head = node;
  100. return base.VisitParameter(node);
  101. }
  102. protected override Expression VisitUnary(UnaryExpression node)
  103. {
  104. if (node.NodeType == ExpressionType.Not && node.Type == typeof(bool))
  105. {
  106. ++_negationCount;
  107. return _head = base.VisitUnary(node);
  108. }
  109. else if (node.NodeType == ExpressionType.Convert)
  110. {
  111. if (node.Operand.Type.IsAssignableFrom(node.Type))
  112. {
  113. // Ignore inheritance casts
  114. return _head = base.VisitUnary(node);
  115. }
  116. }
  117. else if (node.NodeType == ExpressionType.TypeAs)
  118. {
  119. // Ignore as operator.
  120. return _head = base.VisitUnary(node);
  121. }
  122. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  123. }
  124. protected override Expression VisitBlock(BlockExpression node)
  125. {
  126. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  127. }
  128. protected override CatchBlock VisitCatchBlock(CatchBlock node)
  129. {
  130. throw new ExpressionParseException(0, $"Catch blocks are not allowed in binding expressions.");
  131. }
  132. protected override Expression VisitConditional(ConditionalExpression node)
  133. {
  134. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  135. }
  136. protected override Expression VisitDynamic(DynamicExpression node)
  137. {
  138. throw new ExpressionParseException(0, $"Dynamic expressions are not allowed in binding expressions.");
  139. }
  140. protected override ElementInit VisitElementInit(ElementInit node)
  141. {
  142. throw new ExpressionParseException(0, $"Element init expressions are not valid in a binding expression.");
  143. }
  144. protected override Expression VisitGoto(GotoExpression node)
  145. {
  146. throw new ExpressionParseException(0, $"Goto expressions not supported in binding expressions.");
  147. }
  148. protected override Expression VisitInvocation(InvocationExpression node)
  149. {
  150. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  151. }
  152. protected override Expression VisitLabel(LabelExpression node)
  153. {
  154. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  155. }
  156. protected override Expression VisitListInit(ListInitExpression node)
  157. {
  158. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  159. }
  160. protected override Expression VisitLoop(LoopExpression node)
  161. {
  162. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  163. }
  164. protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
  165. {
  166. throw new ExpressionParseException(0, $"Member assignments not supported in binding expressions.");
  167. }
  168. protected override Expression VisitSwitch(SwitchExpression node)
  169. {
  170. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  171. }
  172. protected override Expression VisitTry(TryExpression node)
  173. {
  174. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  175. }
  176. protected override Expression VisitTypeBinary(TypeBinaryExpression node)
  177. {
  178. throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
  179. }
  180. private Expression Add(Expression? instance, Expression expression, string pathSegment, string? separator = null)
  181. {
  182. var visited = Visit(instance);
  183. if (visited != _head)
  184. throw new ExpressionParseException(
  185. 0,
  186. $"Unable to parse '{expression}': expected an instance of '{_head}' but got '{visited}'.");
  187. if (_path.Length > 0 && !string.IsNullOrEmpty(separator))
  188. _path.Append(separator);
  189. _path.Append(pathSegment);
  190. return _head = expression;
  191. }
  192. private static T GetValue<T>(Expression expr)
  193. {
  194. if (expr is ConstantExpression constant)
  195. return (T)constant.Value!;
  196. return Expression.Lambda<Func<T>>(expr).Compile(preferInterpretation: true)();
  197. }
  198. private static PropertyInfo? TryGetPropertyFromMethod(MethodInfo method)
  199. {
  200. var type = method.DeclaringType;
  201. return type?.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method);
  202. }
  203. private class TypeResolver
  204. {
  205. private Dictionary<string, Type> _registered = new();
  206. public void Add(string name, Type type) => _registered.Add(name, type);
  207. public Type Resolve(string? ns, string name)
  208. {
  209. if (_registered.TryGetValue(name, out var type))
  210. return type;
  211. throw new Exception($"Unable to resolve type '{name}'.");
  212. }
  213. }
  214. }