<commit_before>﻿using IronVelocity.Compilation;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace IronVelocity.Reflection
{
    public class MethodResolver : IMethodResolver
    {

        private readonly IArgumentConverter _conversionHelper;
        public MethodResolver(IArgumentConverter conversionHelper)
        {
            _conversionHelper = conversionHelper;
        }

        public Expression ConvertMethodParameters(MethodInfo method, Expression target, DynamicMetaObject[] args)//, Type[] argTypeArray)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            if (args == null)
                throw new ArgumentNullException("args");

            var parameters = method.GetParameters();
            var lastParameter = parameters.LastOrDefault();
            bool hasParamsArray = IsParameterArrayArgument(lastParameter);

            int trivialParams = hasParamsArray
                ? parameters.Length - 1
                : parameters.Length;

            var argTypeArray = args
                .Select(x => x.Value == null ? null : x.LimitType)
                .ToArray();


            var argExpressions = new Expression[parameters.Length];
            for (int i = 0; i < trivialParams; i++)
            {
                var parameter = parameters[i];
                argExpressions[i] = VelocityExpressions.ConvertParameterIfNeeded(args[i], parameter);
            }
            if (hasParamsArray)
            {
                int lastIndex = argExpressions.Length - 1;
                //Check if the array has been explicitly passed, rather than as individual elements
                if (args.Length == parameters.Length
                    && _conversionHelper.CanBeConverted(argTypeArray.Last(), lastParameter.ParameterType)
                    && argTypeArray.Last() != null)
                {
                    argExpressions[lastIndex] = VelocityExpressions.ConvertParameterIfNeeded(args[lastIndex], lastParameter);
                }
                else
                {
                    var elementType = lastParameter.ParameterType.GetElementType();
                    argExpressions[lastIndex] = Expression.NewArrayInit(
                        elementType,
                        args.Skip(lastIndex)
                            .Select(x => VelocityExpressions.ConvertIfNeeded(x, elementType))
                        );
                }
            }

            Expression result = Expression.Call(
                VelocityExpressions.ConvertIfNeeded(target, method.DeclaringType),
                method,
                argExpressions
            );

            if (method.ReturnType == typeof(void))
            {
                result = Expression.Block(
                    result,
                    Constants.VoidReturnValue
                );
            }

            return VelocityExpressions.BoxIfNeeded(result);
        }


        public MethodInfo ResolveMethod(Type type, string name, params Type[] argTypes)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            // Loosely based on C# resolution algorithm
            // C# 1.0 resolution algorithm at http://msdn.microsoft.com/en-us/library/aa691336(v=vs.71).aspx
            // C# 5.0 algorithm in section 7.5.3 of spec - http://www.microsoft.com/en-gb/download/details.aspx?id=7029

            //Given the set of applicable candidate function members, the best function member in that set is located.
            var candidates = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                .Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                .Where(x => IsMethodApplicable(x, argTypes))
                .ToList();

            //If the set contains only one function member, then that function member is the best function member.
            if (candidates.Count == 1)
                return candidates.First();

            //Otherwise, the best function member is the one function member that is better than all other function
            //members with respect to the given argument list, provided that each function member is compared to
            //all other function members using the rules in §7.5.3.2.
            return GetBestFunctionMember(candidates);
        }

        public bool IsArgumentCompatible(Type runtimeType, ParameterInfo parameter)
        {
            if (parameter == null)
                throw new ArgumentNullException("parameter");

            if (_conversionHelper.CanBeConverted(runtimeType, parameter.ParameterType))
                return true;

            return IsParameterArrayArgument(parameter)
                && _conversionHelper.CanBeConverted(runtimeType, parameter.ParameterType.GetElementType());
        }

        public bool IsMethodApplicable(MethodInfo method, params Type[] argTypes)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            if (argTypes == null)
                argTypes = new Type[0];

            //Don't support generic method definitions
            if (method.IsGenericMethod)
                return false;

            var args = method.GetParameters();
            //Do we have a param array?
            var lastArg = args.LastOrDefault();
            ParameterInfo paramsArrayInfo = null;
            if (lastArg != null)
            {
                if (IsParameterArrayArgument(lastArg))
                    paramsArrayInfo = lastArg;
            }


            if (paramsArrayInfo == null && args.Length != argTypes.Length)
                return false;
            else if (argTypes.Length < args.Length - 1)
                return false;


            for (int i = 0; i < argTypes.Length; i++)
            {
                var paramToValidateAgainst = i >= args.Length
                    ? paramsArrayInfo
                    : args[i];

                if (!IsArgumentCompatible(argTypes[i], paramToValidateAgainst))
                    return false;
            }

            return true;
        }


        public MethodInfo GetBestFunctionMember(IEnumerable<MethodInfo> applicableFunctionMembers)
        {
            if (applicableFunctionMembers == null)
                throw new ArgumentNullException("applicableFunctionMembers");


            var best = new List<MethodInfo>();
            foreach (var candidate in applicableFunctionMembers)
            {
                bool lessSpecific = false;
                foreach (var better in best.ToArray())
                {
                    switch (IsBetterFunctionMember(candidate, better))
                    {
                        //If the current candidate is better than the 'better', remove 'better' from<commit_msg>Fixed a bug which caused Method Resolution to report an Ambigious Match when there were actually 0 matches.<commit_after>﻿using IronVelocity.Compilation;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace IronVelocity.Reflection
{
    public class MethodResolver : IMethodResolver
    {

        private readonly IArgumentConverter _conversionHelper;
        public MethodResolver(IArgumentConverter conversionHelper)
        {
            _conversionHelper = conversionHelper;
        }

        public Expression ConvertMethodParameters(MethodInfo method, Expression target, DynamicMetaObject[] args)//, Type[] argTypeArray)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            if (args == null)
                throw new ArgumentNullException("args");

            var parameters = method.GetParameters();
            var lastParameter = parameters.LastOrDefault();
            bool hasParamsArray = IsParameterArrayArgument(lastParameter);

            int trivialParams = hasParamsArray
                ? parameters.Length - 1
                : parameters.Length;

            var argTypeArray = args
                .Select(x => x.Value == null ? null : x.LimitType)
                .ToArray();


            var argExpressions = new Expression[parameters.Length];
            for (int i = 0; i < trivialParams; i++)
            {
                var parameter = parameters[i];
                argExpressions[i] = VelocityExpressions.ConvertParameterIfNeeded(args[i], parameter);
            }
            if (hasParamsArray)
            {
                int lastIndex = argExpressions.Length - 1;
                //Check if the array has been explicitly passed, rather than as individual elements
                if (args.Length == parameters.Length
                    && _conversionHelper.CanBeConverted(argTypeArray.Last(), lastParameter.ParameterType)
                    && argTypeArray.Last() != null)
                {
                    argExpressions[lastIndex] = VelocityExpressions.ConvertParameterIfNeeded(args[lastIndex], lastParameter);
                }
                else
                {
                    var elementType = lastParameter.ParameterType.GetElementType();
                    argExpressions[lastIndex] = Expression.NewArrayInit(
                        elementType,
                        args.Skip(lastIndex)
                            .Select(x => VelocityExpressions.ConvertIfNeeded(x, elementType))
                        );
                }
            }

            Expression result = Expression.Call(
                VelocityExpressions.ConvertIfNeeded(target, method.DeclaringType),
                method,
                argExpressions
            );

            if (method.ReturnType == typeof(void))
            {
                result = Expression.Block(
                    result,
                    Constants.VoidReturnValue
                );
            }

            return VelocityExpressions.BoxIfNeeded(result);
        }


        public MethodInfo ResolveMethod(Type type, string name, params Type[] argTypes)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            // Loosely based on C# resolution algorithm
            // C# 1.0 resolution algorithm at http://msdn.microsoft.com/en-us/library/aa691336(v=vs.71).aspx
            // C# 5.0 algorithm in section 7.5.3 of spec - http://www.microsoft.com/en-gb/download/details.aspx?id=7029

            //Given the set of applicable candidate function members, the best function member in that set is located.
            var candidates = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                .Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                .Where(x => IsMethodApplicable(x, argTypes))
                .ToList();

            //If the set contains only one function member, then that function member is the best function member.
            if (candidates.Count == 1)
                return candidates.First();

            //Otherwise, the best function member is the one function member that is better than all other function
            //members with respect to the given argument list, provided that each function member is compared to
            //all other function members using the rules in §7.5.3.2.
            return GetBestFunctionMember(candidates);
        }

        public bool IsArgumentCompatible(Type runtimeType, ParameterInfo parameter)
        {
            if (parameter == null)
                throw new ArgumentNullException("parameter");

            if (_conversionHelper.CanBeConverted(runtimeType, parameter.ParameterType))
                return true;

            return IsParameterArrayArgument(parameter)
                && _conversionHelper.CanBeConverted(runtimeType, parameter.ParameterType.GetElementType());
        }

        public bool IsMethodApplicable(MethodInfo method, params Type[] argTypes)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            if (argTypes == null)
                argTypes = new Type[0];

            //Don't support generic method definitions
            if (method.IsGenericMethod)
                return false;

            var args = method.GetParameters();
            //Do we have a param array?
            var lastArg = args.LastOrDefault();
            ParameterInfo paramsArrayInfo = null;
            if (lastArg != null)
            {
                if (IsParameterArrayArgument(lastArg))
                    paramsArrayInfo = lastArg;
            }


            if (paramsArrayInfo == null && args.Length != argTypes.Length)
                return false;
            else if (argTypes.Length < args.Length - 1)
                return false;


            for (int i = 0; i < argTypes.Length; i++)
            {
                var paramToValidateAgainst = i >= args.Length
                    ? paramsArrayInfo
                    : args[i];

                if (!IsArgumentCompatible(argTypes[i], paramToValidateAgainst))
                    return false;
            }

            return true;
        }


        public MethodInfo GetBestFunctionMember(IEnumerable<MethodInfo> applicableFunctionMembers)
        {
            if (applicableFunctionMembers == null)
                throw new ArgumentNullException("applicableFunctionMembers");

            if (!applicableFunctionMembers.Any())
                return null;

            var best = new List<MethodInfo>();
            foreach (var candidate in applicableFunctionMembers)
            {
                bool lessSpecific = false;
                foreach (var better in best.ToArray())
                {
                    switch (IsBetterFunctionMember(candidate, better))
                    {
                        //If the current candidate is better than the 'better', remove 'better' from
{'fraction_non_alphanumeric': 0.0772447027141946, 'fraction_numerical': 0.004322868684005386, 'mean_word_length': 1.7243243243243243, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, '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': '5519775', 'n_tokens_mistral': 3353, 'n_tokens_neox': 3087, 'n_words': 872}