<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Concurrent;
using Signum.Utilities.ExpressionTrees;
using System.Collections.ObjectModel;

namespace Signum.Utilities.Reflection
{
    public class GenericInvoker<T>
    {
        readonly ConcurrentDictionary<object, T> executor = new ConcurrentDictionary<object, T>();
        readonly Expression<T> expression;
        readonly int numParams;
        readonly Func<Type[], object> getKey;

        public GenericInvoker(Expression<T> expression)
        {
            this.expression = expression;
            this.numParams = GenericParametersVisitor.GenericParameters(expression);

            ParameterExpression tp = Expression.Parameter(typeof(Type[]));

            this.getKey = Expression.Lambda<Func<Type[], object>>(TupleReflection.TupleChainConstructor(0.To(numParams)
                                                         .Select(i => Expression.ArrayAccess(tp, Expression.Constant(i)))), tp).Compile();
        }

        public T GetInvoker(params Type[] types)
        {
            return executor.GetOrAdd(getKey(types), (object o) =>
            {
                if (types.Length != numParams)
                    throw new InvalidOperationException("Invalid generic arguments ({0} instead of {1})".Formato(types.Length, numParams));

                return GeneratorVisitor.GetGenerator<T>(expression, types).Compile();
            });
        }
    }

    internal class GenericParametersVisitor : SimpleExpressionVisitor
    {
        int? parameters;

        public static int GenericParameters(LambdaExpression expression)
        {
            var gpv = new GenericParametersVisitor();

            gpv.Visit(expression);

            if (gpv.parameters == null)
                throw new InvalidOperationException("No generic method or constructor found on expression:\r\n{0}".Formato(expression.NiceToString()));

            return gpv.parameters.Value;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if(!m.Method.IsGenericMethod)
                throw new InvalidOperationException("The method '{0}' should be generic".Formato(m.Method.MethodName()));

            parameters = m.Method.GetGenericMethodDefinition().GetGenericArguments().Length;

            return m;
        }

        protected override NewExpression VisitNew(NewExpression nex)
        {
            if (!nex.Type.IsGenericType)
                throw new InvalidOperationException("The constructor of {0} should be generic".Formato(nex.Type.TypeName()));

            parameters = nex.Type.GetGenericArguments().Length;

            return nex;
        }
    }

    class GeneratorVisitor : SimpleExpressionVisitor
    {
        Type[] types;

        public static Expression<T> GetGenerator<T>(Expression<T> expression, Type[] types)
        {
            return (Expression<T>)new GeneratorVisitor { types = types }.Visit(expression);
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            MethodInfo mi = m.Method.GetGenericMethodDefinition().MakeGenericMethod(types); 
            var result = Expression.Call(m.Object, mi, m.Arguments.Zip(mi.GetParameters(), (e,p)=>Convert(e, p.ParameterType)));
            return result; 
        }

        protected override NewExpression VisitNew(NewExpression nex)
        {
            ConstructorInfo ci = nex.Constructor.GetGenericConstructorDefinition().MakeGenericConstructor(types);
            var result = Expression.New(ci, nex.Arguments.Zip(ci.GetParameters(), (e, p) => Convert(e, p.ParameterType)));
            return result;
        }

        protected override Expression VisitLambda(LambdaExpression lambda)
        {
            var returnType = lambda.Type.GetMethod("Invoke").ReturnType;

            Expression body = Convert(this.Visit(lambda.Body), returnType);
            if (body != lambda.Body)
            {
                return Expression.Lambda(lambda.Type, body, lambda.Parameters);
            }
            return lambda;
        }

        private Expression Convert(Expression result, Type type)
        {
            if (result.Type == type)
                return result;

            if (result.NodeType == ExpressionType.Convert)
                result = ((UnaryExpression)result).Operand;

            return Expression.Convert(result, type);
        }
    }
}<commit_msg>Type[]<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Concurrent;
using Signum.Utilities.ExpressionTrees;
using System.Collections.ObjectModel;
using System.Reflection.Emit;

namespace Signum.Utilities.Reflection
{
    public class GenericInvoker<T>
    {
        readonly ConcurrentDictionary<Type[], T> executor = new ConcurrentDictionary<Type[], T>(TypeArrayEqualityComparer.Instance);
        readonly Expression<T> expression;
        readonly int numParams;

        public GenericInvoker(Expression<T> expression)
        {
            this.expression = expression;
            this.numParams = GenericParametersVisitor.GenericParameters(expression);

            ParameterExpression tp = Expression.Parameter(typeof(Type[]));
        }

        public T GetInvoker(params Type[] types)
        {
            if (types.Length != numParams)
                throw new InvalidOperationException("Invalid generic arguments ({0} instead of {1})".Formato(types.Length, numParams));

                return executor.GetOrAdd(types, (ts) =>
                     GeneratorVisitor.GetGenerator<T>(expression, ts).Compile());
        }
    }


    class TypeArrayEqualityComparer : IEqualityComparer<Type[]>
    {
        public static readonly TypeArrayEqualityComparer Instance = new TypeArrayEqualityComparer(); 
        public bool Equals(Type[] x, Type[] y)
        {
            for (int i = 0; i < x.Length; i++)
            {
                if (!x[i].Equals(y[i]))
                    return false;
            }

            return true;
        }

        public int GetHashCode(Type[] types)
        {
            int result = 0;
            for (int i = 0; i < types.Length; i++)
                result ^= types[i].GetHashCode() >> i;
            return result;
        }
    }


    internal class GenericParametersVisitor : SimpleExpressionVisitor
    {
        int? parameters;

        public static int GenericParameters(LambdaExpression expression)
        {
            var gpv = new GenericParametersVisitor();

            gpv.Visit(expression);

            if (gpv.parameters == null)
                throw new InvalidOperationException("No generic method or constructor found on expression:\r\n{0}".Formato(expression.NiceToString()));

            return gpv.parameters.Value;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if(!m.Method.IsGenericMethod)
                throw new InvalidOperationException("The method '{0}' should be generic".Formato(m.Method.MethodName()));

            parameters = m.Method.GetGenericMethodDefinition().GetGenericArguments().Length;

            return m;
        }

        protected override NewExpression VisitNew(NewExpression nex)
        {
            if (!nex.Type.IsGenericType)
                throw new InvalidOperationException("The constructor of {0} should be generic".Formato(nex.Type.TypeName()));

            parameters = nex.Type.GetGenericArguments().Length;

            return nex;
        }
    }

    class GeneratorVisitor : SimpleExpressionVisitor
    {
        Type[] types;

        public static Expression<T> GetGenerator<T>(Expression<T> expression, Type[] types)
        {
            return (Expression<T>)new GeneratorVisitor { types = types }.Visit(expression);
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            MethodInfo mi = m.Method.GetGenericMethodDefinition().MakeGenericMethod(types); 
            var result = Expression.Call(m.Object, mi, m.Arguments.Zip(mi.GetParameters(), (e,p)=>Convert(e, p.ParameterType)));
            return result; 
        }

        protected override NewExpression VisitNew(NewExpression nex)
        {
            ConstructorInfo ci = nex.Constructor.GetGenericConstructorDefinition().MakeGenericConstructor(types);
            var result = Expression.New(ci, nex.Arguments.Zip(ci.GetParameters(), (e, p) => Convert(e, p.ParameterType)));
            return result;
        }

        protected override Expression VisitLambda(LambdaExpression lambda)
        {
            var returnType = lambda.Type.GetMethod("Invoke").ReturnType;

            Expression body = Convert(this.Visit(lambda.Body), returnType);

            //if (returnType != typeof(void))
            //    body = Expression.Call(giInside.MakeGenericMethod(returnType),
            //        Expression.Lambda(body));

            if (body != lambda.Body)
            {
                return Expression.Lambda(lambda.Type, body, lambda.Parameters);
            }
            return lambda;
        }

        static MethodInfo giInside = ReflectionTools.GetMethodInfo(() => Inside<string>(null)).GetGenericMethodDefinition();

        static T Inside<T>(Func<T> lambda)
        {
            return HeavyProfiler.LogNoStackTrace("inside").Using(_ => lambda()); 
        }

        private Expression Convert(Expression result, Type type)
        {
            if (result.Type == type)
                return result;

            if (result.NodeType == ExpressionType.Convert)
                result = ((UnaryExpression)result).Operand;

            return Expression.Convert(result, type);
        }
    }
}
{'fraction_non_alphanumeric': 0.09901388609378145, 'fraction_numerical': 0.001408734151740793, 'mean_word_length': 2.330764075067024, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 41, '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': '5927101', 'n_tokens_mistral': 2452, 'n_tokens_neox': 2351, 'n_words': 543}