<commit_before>﻿using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Compilation.Binding
{
    public static class ExpressionHelper
    {
        public static Expression GetMember(Expression target, string name, Type[] typeArguments = null, bool throwExceptions = true)
        {
            if (target is MethodGroupExpression)
                throw new Exception("can't access member on method group");

            var type = target.Type;
            var isStatic = target is StaticClassIdentifierExpression;
            var members = type.GetMembers(BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance))
                .Where(m => m.Name == name)
                .ToArray();
            if (members.Length == 0)
            {
                if (throwExceptions) throw new Exception($"couldn't find { (isStatic ? "static" : "instance") } member { name } on type { type.FullName }");
                else return null;
            }
            if (members.Length == 1)
            {
                var instance = isStatic ? null : target;
                if (members[0] is PropertyInfo)
                {
                    var property = members[0] as PropertyInfo;
                    return Expression.Property(instance, property);
                }
                else if (members[0] is FieldInfo)
                {
                    var field = members[0] as FieldInfo;
                    return Expression.Field(instance, field);
                }
                else if (members[0] is Type)
                {
                    return Expression.Constant(null, (Type)members[0]);
                }
            }
            return new MethodGroupExpression() { MethodName = name, Target = target, TypeArgs = typeArguments };
        }

        public static Expression Call(Expression target, Expression[] arguments)
        {
            if (target is MethodGroupExpression)
            {
                return ((MethodGroupExpression)target).CreateMethodCall(arguments);
            }
            return Expression.Invoke(target, arguments);
        }

        public static Expression CallMethod(Expression target, BindingFlags flags, string name, Type[] typeArguments, Expression[] arguments, IDictionary<string, Expression> namedArgs = null)
        {
            // the following piece of code is nicer and more readable than method recognition done in roslyn, c# dynamic and also expression evaluator :)
            var methods = from m in target.Type.GetMethods(flags)
                          where m.Name == name
                          let r = TryCallMethod(target, m, typeArguments, arguments, namedArgs)
                          orderby r.CastCount descending
                          select r;
            var method = methods.First();
            return Expression.Call(target, method.Method, method.Arguments);
        }

        class MethodRecognitionResult
        {
            public int CastCount { get; set; }
            public Expression[] Arguments { get; set; }
            public MethodInfo Method { get; set; }
        }

        private static MethodRecognitionResult TryCallMethod(Expression target, MethodInfo method, Type[] typeArguments, Expression[] positionalArguments, IDictionary<string, Expression> namedArguments)
        {
            var parameters = method.GetParameters();
            // method must have all named arguments
            if (namedArguments != null && !namedArguments.All(n => parameters.Any(p => p.Name == n.Key))) return null;

            int weight = 0;
            var args = new Expression[parameters.Length];
            for (int i = 0; i < args.Length; i++)
            {
                if (namedArguments?.ContainsKey(parameters[i].Name) == true)
                {
                    args[i] = namedArguments[parameters[i].Name];
                }
                else if (i < positionalArguments.Length)
                {
                    args[i] = positionalArguments[i];
                }
                else if (parameters[i].HasDefaultValue)
                {
                    weight++;
                    args[i] = Expression.Constant(parameters[i].DefaultValue, parameters[i].ParameterType);
                }
                else return null;
            }

            // resolve generic parameters
            if (method.ContainsGenericParameters)
            {
                var typeArgs = new Type[method.GetGenericArguments().Length];
                if (typeArguments != null) Array.Copy(typeArguments, typeArgs, typeArgs.Length);
                for (int i = 0; i < typeArgs.Length; i++)
                {
                    if (typeArgs[i] == null)
                    {
                        // try to resolve from arguments
                        var arg = Array.FindIndex(parameters, p => p.ParameterType.IsGenericParameter && p.ParameterType.GenericParameterPosition == i);
                        if (arg >= 0) typeArgs[i] = args[arg].Type;
                        else return null;
                    }
                }
                method = method.MakeGenericMethod(typeArgs);
                parameters = method.GetParameters();
            }

            // cast arguments
            for (int i = 0; i < args.Length; i++)
            {
                var casted = TypeConversion.ImplicitConversion(args[i], parameters[i].ParameterType);
                if (casted == null) return null;
                if(casted != args[i])
                {
                    weight++;
                    args[i] = casted;
                }
            }

            return new MethodRecognitionResult
            {
                CastCount = weight,
                Method = method,
                Arguments = args
            };
        }


        public static Expression EqualsMethod(Expression left, Expression right)
        {
            Expression equatable = null;
            Expression theOther = null;
            if (typeof(IEquatable<>).IsAssignableFrom(left.Type))
            {
                equatable = left;
                theOther = right;
            }
            else if (typeof(IEquatable<>).IsAssignableFrom(right.Type))
            {
                equatable = right;
                theOther = left;
            }

            if (equatable != null)
            {
                var m = CallMethod(equatable, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy, "Equals", null, new[] { theOther });
                if (m != null) return m;
            }

            if (left.Type.IsValueType)
            {<commit_msg>fixed bugs in method overload finding<commit_after>﻿using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DotVVM.Framework.Runtime.Compilation.Binding
{
    public static class ExpressionHelper
    {
        public static Expression GetMember(Expression target, string name, Type[] typeArguments = null, bool throwExceptions = true)
        {
            if (target is MethodGroupExpression)
                throw new Exception("can't access member on method group");

            var type = target.Type;
            var isStatic = target is StaticClassIdentifierExpression;
            var members = type.GetMembers(BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance))
                .Where(m => m.Name == name)
                .ToArray();
            if (members.Length == 0)
            {
                if (throwExceptions) throw new Exception($"couldn't find { (isStatic ? "static" : "instance") } member { name } on type { type.FullName }");
                else return null;
            }
            if (members.Length == 1)
            {
                var instance = isStatic ? null : target;
                if (members[0] is PropertyInfo)
                {
                    var property = members[0] as PropertyInfo;
                    return Expression.Property(instance, property);
                }
                else if (members[0] is FieldInfo)
                {
                    var field = members[0] as FieldInfo;
                    return Expression.Field(instance, field);
                }
                else if (members[0] is Type)
                {
                    return Expression.Constant(null, (Type)members[0]);
                }
            }
            return new MethodGroupExpression() { MethodName = name, Target = target, TypeArgs = typeArguments };
        }

        public static Expression Call(Expression target, Expression[] arguments)
        {
            if (target is MethodGroupExpression)
            {
                return ((MethodGroupExpression)target).CreateMethodCall(arguments);
            }
            return Expression.Invoke(target, arguments);
        }

        public static Expression CallMethod(Expression target, BindingFlags flags, string name, Type[] typeArguments, Expression[] arguments, IDictionary<string, Expression> namedArgs = null)
        {
            // the following piece of code is nicer and more readable than method recognition done in roslyn, c# dynamic and also expression evaluator :)
            var methods = from m in target.Type.GetMethods(flags)
                          where m.Name == name
                          let r = TryCallMethod(target, m, typeArguments, arguments, namedArgs)
                          where r != null
                          orderby r.CastCount descending, r.AutomaticTypeArgCount
                          select r;
            var method = methods.First();
            return Expression.Call(target, method.Method, method.Arguments);
        }

        class MethodRecognitionResult
        {
            public int AutomaticTypeArgCount { get; set; }
            public int CastCount { get; set; }
            public Expression[] Arguments { get; set; }
            public MethodInfo Method { get; set; }
        }

        private static MethodRecognitionResult TryCallMethod(Expression target, MethodInfo method, Type[] typeArguments, Expression[] positionalArguments, IDictionary<string, Expression> namedArguments)
        {
            var parameters = method.GetParameters();
            // method must have all named arguments
            if (namedArguments != null && !namedArguments.All(n => parameters.Any(p => p.Name == n.Key))) return null;

            int castCount = 0;
            var args = new Expression[parameters.Length];
            for (int i = 0; i < args.Length; i++)
            {
                if (namedArguments?.ContainsKey(parameters[i].Name) == true)
                {
                    args[i] = namedArguments[parameters[i].Name];
                }
                else if (i < positionalArguments.Length)
                {
                    args[i] = positionalArguments[i];
                }
                else if (parameters[i].HasDefaultValue)
                {
                    castCount++;
                    args[i] = Expression.Constant(parameters[i].DefaultValue, parameters[i].ParameterType);
                }
                else return null;
            }

            int automaticTypeArgs = 0;
            // resolve generic parameters
            if (method.ContainsGenericParameters)
            {
                var typeArgs = new Type[method.GetGenericArguments().Length];
                if (typeArguments != null)
                {
                    if (typeArguments.Length > typeArgs.Length) return null;
                    Array.Copy(typeArguments, typeArgs, typeArgs.Length);
                }
                for (int i = 0; i < typeArgs.Length; i++)
                {
                    if (typeArgs[i] == null)
                    {
                        // try to resolve from arguments
                        var arg = Array.FindIndex(parameters, p => p.ParameterType.IsGenericParameter && p.ParameterType.GenericParameterPosition == i);
                        automaticTypeArgs++;
                        if (arg >= 0) typeArgs[i] = args[arg].Type;
                        else return null;
                    }
                }
                method = method.MakeGenericMethod(typeArgs);
                parameters = method.GetParameters();
            }
            else if (typeArguments != null) return null;

            // cast arguments
            for (int i = 0; i < args.Length; i++)
            {
                var casted = TypeConversion.ImplicitConversion(args[i], parameters[i].ParameterType);
                if (casted == null) return null;
                if(casted != args[i])
                {
                    castCount++;
                    args[i] = casted;
                }
            }

            return new MethodRecognitionResult
            {
                CastCount = castCount,
                AutomaticTypeArgCount = automaticTypeArgs,
                Method = method,
                Arguments = args
            };
        }


        public static Expression EqualsMethod(Expression left, Expression right)
        {
            Expression equatable = null;
            Expression theOther = null;
            if (typeof(IEquatable<>).IsAssignableFrom(left.Type))
            {
                equatable = left;
                theOther = right;
            }
            else if (typeof(IEquatable<>).IsAssignableFrom(right.Type))
            {
                equatable = right;
                theOther = left;
            }

            if (equatable != null)
            {
                var m = CallMethod(equatable, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy, "Equals", null, new[] { theOther });
                if (m != null) return m;
            }

            if (left.Type.IsValueType)
            {
{'fraction_non_alphanumeric': 0.08693209746506565, 'fraction_numerical': 0.0018959342742784916, 'mean_word_length': 1.525177304964539, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6079455', 'n_tokens_mistral': 3395, 'n_tokens_neox': 3177, 'n_words': 895}