<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using Parsley;
using Rook.Compiling.Types;
using Rook.Core.Collections;

namespace Rook.Compiling.Syntax
{
    public class TypeChecker
    {
        public readonly Func<TypeVariable> CreateTypeVariable;
        private readonly TypeUnifier unifier;
        private readonly TypeRegistry typeRegistry;
        private readonly List<CompilerError> errorLog;

        public TypeChecker()
        {
            unifier = new TypeUnifier();
            typeRegistry = new TypeRegistry();
            errorLog = new List<CompilerError>();

            int next = 0;
            CreateTypeVariable = () => new TypeVariable(next++);
        }

        public Vector<CompilerError> Errors { get { return errorLog.ToVector(); } }
        public bool HasErrors { get { return errorLog.Any(); } }

        //TODO: This property is deprecated.  Once TypeRegistry can discover .NET types via reflection,
        //rephrase unit test usages of this so they don't have to manually prepare the TypeRegistry.
        public TypeRegistry TypeRegistry { get { return typeRegistry; } }

        public CompilationUnit TypeCheck(CompilationUnit compilationUnit)
        {
            var Position = compilationUnit.Position;
            var Classes = compilationUnit.Classes;
            var Functions = compilationUnit.Functions;

            foreach (var @class in Classes)//TODO: Test coverage.
                typeRegistry.Register(@class);

            var scope = Scope.CreateRoot(this);

            foreach (var @class in Classes)
                if (!scope.TryIncludeUniqueBinding(@class))
                {
                    LogError(CompilerError.DuplicateIdentifier(@class));
                    return null;
                }

            foreach (var function in Functions)
                if (!scope.TryIncludeUniqueBinding(function))
                {
                    LogError(CompilerError.DuplicateIdentifier(function));
                    return null;
                }

            var typeCheckedClasses = TypeCheckAll(Classes, scope);
            var typeCheckedFunctions = TypeCheckAll(Functions, scope);

            if (typeCheckedClasses.Any(x => x == null) || typeCheckedFunctions.Any(x => x == null))
                return null;

            return new CompilationUnit(Position, typeCheckedClasses, typeCheckedFunctions);
        }

        public Class TypeCheck(Class @class, Scope scope)
        {
            var Position = @class.Position;
            var Name = @class.Name;
            var Methods = @class.Methods;

            var localScope = scope.CreateLocalScope();

            foreach (var method in Methods)
                if (!localScope.TryIncludeUniqueBinding(method))
                {
                    LogError(CompilerError.DuplicateIdentifier(method));
                    return null;
                }

            var typeCheckedMethods = TypeCheckAll(Methods, localScope);

            if (typeCheckedMethods.Any(x => x == null))
                return null;

            return new Class(Position, Name, typeCheckedMethods);
        }

        public Function TypeCheck(Function function, Scope scope)
        {
            var Position = function.Position;
            var ReturnType = function.ReturnType;
            var Name = function.Name;
            var Parameters = function.Parameters;
            var Body = function.Body;
            var DeclaredType = function.DeclaredType;

            var localScope = scope.CreateLocalScope();

            foreach (var parameter in Parameters)
                if (!localScope.TryIncludeUniqueBinding(parameter))
                {
                    LogError(CompilerError.DuplicateIdentifier(parameter));
                    return null;
                }

            var typeCheckedBody = TypeCheck(Body, localScope);
            if (typeCheckedBody == null)
                return null;

            if (!Unify(typeCheckedBody.Position, ReturnType, typeCheckedBody.Type))
                return null;

            return new Function(Position, ReturnType, Name, Parameters, typeCheckedBody, DeclaredType);
        }

        public Expression TypeCheck(Expression expression, Scope scope)
        {
            return expression.WithTypes(this, scope);
        }

        public Expression TypeCheck(Name name, Scope scope)
        {
            var Position = name.Position;
            var Identifier = name.Identifier;

            DataType type;

            //TODO: We should probably normalize 'type' before freshening its variables.

            if (scope.TryGet(Identifier, out type))
                return new Name(Position, Identifier, FreshenGenericTypeVariables(scope, type));

            LogError(CompilerError.UndefinedIdentifier(name));
            return null;
        }

        private DataType FreshenGenericTypeVariables(Scope scope, DataType type)
        {
            var substitutions = new Dictionary<TypeVariable, DataType>();
            var genericTypeVariables = type.FindTypeVariables().Where(scope.IsGeneric);
            foreach (var genericTypeVariable in genericTypeVariables)
                substitutions[genericTypeVariable] = CreateTypeVariable();

            return type.ReplaceTypeVariables(substitutions);
        }

        public Expression TypeCheck(Block block, Scope scope)
        {
            var Position = block.Position;
            var VariableDeclarations = block.VariableDeclarations;
            var InnerExpressions = block.InnerExpressions;

            var localScope = scope.CreateLocalScope();

            var typedVariableDeclarations = new List<VariableDeclaration>();
            foreach (var variable in VariableDeclarations)
            {
                var typeCheckedValue = TypeCheck(variable.Value, localScope);

                if (typeCheckedValue == null)
                    return null;

                Binding binding = variable;
                if (variable.IsImplicitlyTyped())
                    binding = new VariableDeclaration(variable.Position, /*Replaces implicit type.*/ typeCheckedValue.Type, variable.Identifier, variable.Value);

                if (!localScope.TryIncludeUniqueBinding(binding))
                {
                    LogError(CompilerError.DuplicateIdentifier(binding));
                    return null;
                }

                typedVariableDeclarations.Add(new VariableDeclaration(variable.Position,
                                                                      binding.Type,
                                                                      variable.Identifier,
                                                                      typeCheckedValue));

                if (!Unify(typeCheckedValue.Position, binding.Type, typeCheckedValue.Type))
                    return null;
            }

            var typeCheckedInnerExpressions = TypeCheckAll(InnerExpressions, localScope);

            if (typeCheckedInnerExpressions.Any(x => x == null))
                return null;

            var blockType = typeCheckedInnerExpressions.Last().Type;

            return new Block(Position, typedVariableDeclarations.ToVector(), typeCheckedInnerExpressions, blockType);
        }

        public Expression TypeCheck(Lambda lambda, Scope scope)
        {
            var Position = lambda.Position;
            var Parameters = lambda.Parameters;
            var Body = lambda.Body;

            var localScope = scope.CreateLocalScope();

            var typedParameters = ReplaceImplicitTypesWithNewNonGenericTypeVariables(Parameters, localScope);

            foreach (var parameter in typedParameters)
            {
                if (!localScope.TryIncludeUniqueBinding(parameter))
                {
                    LogError(CompilerError.DuplicateIdentifier(parameter));
                    return null;
                }
            }

            var typeCheckedBody = TypeCheck(Body, localScope);
            if (typeCheckedBody == null)
                return null;

            var normalizedParameters = NormalizeTypes(typedParameters);
            //TODO: Determine whether I should also normalize typedBody.Type for the return below.

            var parameterTypes = normalizedParameters.Select(p => p.Type).ToArray();

            return new Lambda(Position, normalizedParameters, typeCheckedBody, NamedType.Function(parameterTypes, typeCheckedBody.Type));
        }

        private Parameter[] ReplaceImplicitTypesWithNewNonGenericTypeVariables(IEnumerable<Parameter> parameters, Scope localScope)
        {
            var decoratedParameters = new List<Parameter>();
            var typeVariables = new List<TypeVariable>();

            foreach (var parameter in parameters)
            {
                if (parameter.IsImplicitlyTyped())
                {
                    var typeVariable = CreateTypeVariable();
                    typeVariables.Add(typeVariable);
                    decoratedParameters.Add(new Parameter(parameter.Position, typeVariable, parameter.Identifier));
                }
                else
                {
                    decoratedParameters.Add(parameter);
                }
            }

            localScope.TreatAsNonGeneric(typeVariables);

            return decoratedParameters.ToArray();
        }

        private Vector<Parameter> NormalizeTypes(IEnumerable<Parameter> typedParameters)
        {
            return typedParameters.Select(p => new Parameter(p.Position, unifier.Normalize(p.Type), p.Identifier)).ToVector();
        }

        public Expression TypeCheck(If conditional, Scope scope)
        {
            var Position = conditional.Position;
            var Condition = conditional.Condition;
            var BodyWhenTrue = conditional.BodyWhenTrue;
            var BodyWhenFalse = conditional.BodyWhenFalse;

            var typeCheckedCondition = TypeCheck(Condition, scope);
            var typeCheckedWhenTrue = TypeCheck(BodyWhenTrue, scope);
            var typeCheckedWhenFalse = TypeCheck(BodyWhenFalse, scope);

            if (typeCheckedCondition == null || typeCheckedWhenTrue == null || typeCheckedWhenFalse == null)
                return null;

            var unifiedA = Unify(typeCheckedCondition.Position, NamedType.Boolean, typeCheckedCondition.Type);
            var unifiedB = Unify(typeCheckedWhenFalse.Position, typeCheckedWhenTrue.Type, typeCheckedWhenFalse.Type);

            if (!unifiedA || !unifiedB)
                return null;

            return new If(Position, typeCheckedCondition, typeCheckedWhenTrue, typeCheckedWhenFalse, typeCheckedWhenTrue.Type);
        }

        public Expression TypeCheck(Call call, Scope scope)
        {
            var Position = call.Position;
            var Callable = call.Callable;
            var Arguments = call.Arguments;
            var IsOperator = call.IsOperator;

            var typeCheckedCallable = TypeCheck(Callable, scope);
            var typeCheckedArguments = TypeCheckAll(Arguments, scope);

            if (typeCheckedCallable == null || typeCheckedArguments.Any(x => x == null))
                return null;

            var calleeType = typeCheckedCallable.Type;
            var calleeFunctionType = calleeType as NamedType;

            if (calleeFunctionType == null || calleeFunctionType.Name != "System.Func")
            {
                LogError(CompilerError.ObjectNotCallable(Position));
                return null;
            }

            var returnType = calleeType.InnerTypes.Last();
            var argumentTypes = typeCheckedArguments.Select(x => x.Type).ToVector();

            if (!Unify(Position, calleeType, NamedType.Function(argumentTypes, returnType)))
                return null;

            var callType = unifier.Normalize(returnType);

            return new Call(Position, typeCheckedCallable, typeCheckedArguments, IsOperator, callType);
        }

        public Expression TypeCheck(MethodInvocation methodInvocation, Scope scope)
        {
            var Position = methodInvocation.Position;
            var Instance = methodInvocation.Instance;
            var MethodName = methodInvocation.MethodName;
            var Arguments = methodInvocation.Arguments;

            var typeCheckedInstance = TypeCheck(Instance, scope);

            if (typeCheckedInstance == null)
                return null;

            var instanceType = typeCheckedInstance.Type;
            var instanceNamedType = instanceType as NamedType;

            if (instanceNamedType == null)
            {
                LogError(CompilerError.AmbiguousMethodInvocation(Position));
                return null;
            }

            Scope typeMemberScope;
            if (scope.TryGetMemberScope(typeRegistry, instanceNamedType, out typeMemberScope))
            {
                //This block is SUSPICIOUSLY like all of TypeCheck(Call, Scope), but Callable/MethodName is evaluated in a special scope and the successful return is different.

                var Callable = MethodName;

                //EXPERIMENTAL - TRY EXTENSION METHOD WHEN WE FAILED TO FIND THE METHOD IN THE TYPE MEMBER SCOPE
                DataType type;
                if (!typeMemberScope.TryGet(Callable.Identifier, out type))
                {
                    var extensionMethodCall = TypeCheck(new Call(Position, MethodName, new[] { Instance }.Concat(Arguments)), scope);

                    if (extensionMethodCall != null)
                        return extensionMethodCall;
                }

                var typeCheckedCallable = TypeCheck(Callable, typeMemberScope);
                
                //END EXPERIMENT

                var typeCheckedArguments = TypeCheckAll(Arguments, scope);

                if (typeCheckedCallable == null || typeCheckedArguments.Any(x => x == null))
                    return null;

                var calleeType = typeCheckedCallable.Type;
                var calleeFunctionType = calleeType as NamedType;

                if (calleeFunctionType == null || calleeFunctionType.Name != "System.Func")
                {
                    LogError(CompilerError.ObjectNotCallable(Position));
                    return null;
                }

                var returnType = calleeType.InnerTypes.Last();
                var argumentTypes = typeCheckedArguments.Select(x => x.Type).ToVector();

                if (!Unify(Position, calleeType, NamedType.Function(argumentTypes, returnType)))
                    return null;

                var callType = unifier.Normalize(returnType);
                //End of suspiciously duplicated code.

                var typedMethodName = (Name)typeCheckedCallable;
                return new MethodInvocation(Position, typeCheckedInstance, typedMethodName, typeCheckedArguments, callType);
            }
            else
            {
                //HACK: Because TypeRegistry cannot yet look up members for concretions of generic types like int*,
                //  we have to double-check whether this is an extension method call for a regular built-in generic function.
                //  Once TypeRegistry lets you look up the members for a type like int*, this block should be removed.
                DataType type;
                if (scope.TryGet(MethodName.Identifier, out type))
                {
                    var typeCheckedCallable = TypeCheck(MethodName, scope);
                    if (typeCheckedCallable != null)
                    {
                        var extensionMethodCall = TypeCheck(new Call(Position, MethodName, new[] { Instance }.Concat(Arguments)), scope);

                        if (extensionMethodCall != null)
                            return extensionMethodCall;<commit_msg>Replace calls to scope.TryGet(...) with calls to scope.Contains(...) where applicable.<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using Parsley;
using Rook.Compiling.Types;
using Rook.Core.Collections;

namespace Rook.Compiling.Syntax
{
    public class TypeChecker
    {
        public readonly Func<TypeVariable> CreateTypeVariable;
        private readonly TypeUnifier unifier;
        private readonly TypeRegistry typeRegistry;
        private readonly List<CompilerError> errorLog;

        public TypeChecker()
        {
            unifier = new TypeUnifier();
            typeRegistry = new TypeRegistry();
            errorLog = new List<CompilerError>();

            int next = 0;
            CreateTypeVariable = () => new TypeVariable(next++);
        }

        public Vector<CompilerError> Errors { get { return errorLog.ToVector(); } }
        public bool HasErrors { get { return errorLog.Any(); } }

        //TODO: This property is deprecated.  Once TypeRegistry can discover .NET types via reflection,
        //rephrase unit test usages of this so they don't have to manually prepare the TypeRegistry.
        public TypeRegistry TypeRegistry { get { return typeRegistry; } }

        public CompilationUnit TypeCheck(CompilationUnit compilationUnit)
        {
            var Position = compilationUnit.Position;
            var Classes = compilationUnit.Classes;
            var Functions = compilationUnit.Functions;

            foreach (var @class in Classes)//TODO: Test coverage.
                typeRegistry.Register(@class);

            var scope = Scope.CreateRoot(this);

            foreach (var @class in Classes)
                if (!scope.TryIncludeUniqueBinding(@class))
                {
                    LogError(CompilerError.DuplicateIdentifier(@class));
                    return null;
                }

            foreach (var function in Functions)
                if (!scope.TryIncludeUniqueBinding(function))
                {
                    LogError(CompilerError.DuplicateIdentifier(function));
                    return null;
                }

            var typeCheckedClasses = TypeCheckAll(Classes, scope);
            var typeCheckedFunctions = TypeCheckAll(Functions, scope);

            if (typeCheckedClasses.Any(x => x == null) || typeCheckedFunctions.Any(x => x == null))
                return null;

            return new CompilationUnit(Position, typeCheckedClasses, typeCheckedFunctions);
        }

        public Class TypeCheck(Class @class, Scope scope)
        {
            var Position = @class.Position;
            var Name = @class.Name;
            var Methods = @class.Methods;

            var localScope = scope.CreateLocalScope();

            foreach (var method in Methods)
                if (!localScope.TryIncludeUniqueBinding(method))
                {
                    LogError(CompilerError.DuplicateIdentifier(method));
                    return null;
                }

            var typeCheckedMethods = TypeCheckAll(Methods, localScope);

            if (typeCheckedMethods.Any(x => x == null))
                return null;

            return new Class(Position, Name, typeCheckedMethods);
        }

        public Function TypeCheck(Function function, Scope scope)
        {
            var Position = function.Position;
            var ReturnType = function.ReturnType;
            var Name = function.Name;
            var Parameters = function.Parameters;
            var Body = function.Body;
            var DeclaredType = function.DeclaredType;

            var localScope = scope.CreateLocalScope();

            foreach (var parameter in Parameters)
                if (!localScope.TryIncludeUniqueBinding(parameter))
                {
                    LogError(CompilerError.DuplicateIdentifier(parameter));
                    return null;
                }

            var typeCheckedBody = TypeCheck(Body, localScope);
            if (typeCheckedBody == null)
                return null;

            if (!Unify(typeCheckedBody.Position, ReturnType, typeCheckedBody.Type))
                return null;

            return new Function(Position, ReturnType, Name, Parameters, typeCheckedBody, DeclaredType);
        }

        public Expression TypeCheck(Expression expression, Scope scope)
        {
            return expression.WithTypes(this, scope);
        }

        public Expression TypeCheck(Name name, Scope scope)
        {
            var Position = name.Position;
            var Identifier = name.Identifier;

            DataType type;

            if (scope.TryGet(Identifier, out type))
                return new Name(Position, Identifier, FreshenGenericTypeVariables(scope, type));

            LogError(CompilerError.UndefinedIdentifier(name));
            return null;
        }

        private DataType FreshenGenericTypeVariables(Scope scope, DataType type)
        {
            var substitutions = new Dictionary<TypeVariable, DataType>();
            var genericTypeVariables = type.FindTypeVariables().Where(scope.IsGeneric);
            foreach (var genericTypeVariable in genericTypeVariables)
                substitutions[genericTypeVariable] = CreateTypeVariable();

            return type.ReplaceTypeVariables(substitutions);
        }

        public Expression TypeCheck(Block block, Scope scope)
        {
            var Position = block.Position;
            var VariableDeclarations = block.VariableDeclarations;
            var InnerExpressions = block.InnerExpressions;

            var localScope = scope.CreateLocalScope();

            var typedVariableDeclarations = new List<VariableDeclaration>();
            foreach (var variable in VariableDeclarations)
            {
                var typeCheckedValue = TypeCheck(variable.Value, localScope);

                if (typeCheckedValue == null)
                    return null;

                Binding binding = variable;
                if (variable.IsImplicitlyTyped())
                    binding = new VariableDeclaration(variable.Position, /*Replaces implicit type.*/ typeCheckedValue.Type, variable.Identifier, variable.Value);

                if (!localScope.TryIncludeUniqueBinding(binding))
                {
                    LogError(CompilerError.DuplicateIdentifier(binding));
                    return null;
                }

                typedVariableDeclarations.Add(new VariableDeclaration(variable.Position,
                                                                      binding.Type,
                                                                      variable.Identifier,
                                                                      typeCheckedValue));

                if (!Unify(typeCheckedValue.Position, binding.Type, typeCheckedValue.Type))
                    return null;
            }

            var typeCheckedInnerExpressions = TypeCheckAll(InnerExpressions, localScope);

            if (typeCheckedInnerExpressions.Any(x => x == null))
                return null;

            var blockType = typeCheckedInnerExpressions.Last().Type;

            return new Block(Position, typedVariableDeclarations.ToVector(), typeCheckedInnerExpressions, blockType);
        }

        public Expression TypeCheck(Lambda lambda, Scope scope)
        {
            var Position = lambda.Position;
            var Parameters = lambda.Parameters;
            var Body = lambda.Body;

            var localScope = scope.CreateLocalScope();

            var typedParameters = ReplaceImplicitTypesWithNewNonGenericTypeVariables(Parameters, localScope);

            foreach (var parameter in typedParameters)
            {
                if (!localScope.TryIncludeUniqueBinding(parameter))
                {
                    LogError(CompilerError.DuplicateIdentifier(parameter));
                    return null;
                }
            }

            var typeCheckedBody = TypeCheck(Body, localScope);
            if (typeCheckedBody == null)
                return null;

            var normalizedParameters = NormalizeTypes(typedParameters);

            var parameterTypes = normalizedParameters.Select(p => p.Type).ToArray();

            return new Lambda(Position, normalizedParameters, typeCheckedBody, NamedType.Function(parameterTypes, typeCheckedBody.Type));
        }

        private Parameter[] ReplaceImplicitTypesWithNewNonGenericTypeVariables(IEnumerable<Parameter> parameters, Scope localScope)
        {
            var decoratedParameters = new List<Parameter>();
            var typeVariables = new List<TypeVariable>();

            foreach (var parameter in parameters)
            {
                if (parameter.IsImplicitlyTyped())
                {
                    var typeVariable = CreateTypeVariable();
                    typeVariables.Add(typeVariable);
                    decoratedParameters.Add(new Parameter(parameter.Position, typeVariable, parameter.Identifier));
                }
                else
                {
                    decoratedParameters.Add(parameter);
                }
            }

            localScope.TreatAsNonGeneric(typeVariables);

            return decoratedParameters.ToArray();
        }

        private Vector<Parameter> NormalizeTypes(IEnumerable<Parameter> typedParameters)
        {
            return typedParameters.Select(p => new Parameter(p.Position, unifier.Normalize(p.Type), p.Identifier)).ToVector();
        }

        public Expression TypeCheck(If conditional, Scope scope)
        {
            var Position = conditional.Position;
            var Condition = conditional.Condition;
            var BodyWhenTrue = conditional.BodyWhenTrue;
            var BodyWhenFalse = conditional.BodyWhenFalse;

            var typeCheckedCondition = TypeCheck(Condition, scope);
            var typeCheckedWhenTrue = TypeCheck(BodyWhenTrue, scope);
            var typeCheckedWhenFalse = TypeCheck(BodyWhenFalse, scope);

            if (typeCheckedCondition == null || typeCheckedWhenTrue == null || typeCheckedWhenFalse == null)
                return null;

            var unifiedA = Unify(typeCheckedCondition.Position, NamedType.Boolean, typeCheckedCondition.Type);
            var unifiedB = Unify(typeCheckedWhenFalse.Position, typeCheckedWhenTrue.Type, typeCheckedWhenFalse.Type);

            if (!unifiedA || !unifiedB)
                return null;

            return new If(Position, typeCheckedCondition, typeCheckedWhenTrue, typeCheckedWhenFalse, typeCheckedWhenTrue.Type);
        }

        public Expression TypeCheck(Call call, Scope scope)
        {
            var Position = call.Position;
            var Callable = call.Callable;
            var Arguments = call.Arguments;
            var IsOperator = call.IsOperator;

            var typeCheckedCallable = TypeCheck(Callable, scope);
            var typeCheckedArguments = TypeCheckAll(Arguments, scope);

            if (typeCheckedCallable == null || typeCheckedArguments.Any(x => x == null))
                return null;

            var calleeType = typeCheckedCallable.Type;
            var calleeFunctionType = calleeType as NamedType;

            if (calleeFunctionType == null || calleeFunctionType.Name != "System.Func")
            {
                LogError(CompilerError.ObjectNotCallable(Position));
                return null;
            }

            var returnType = calleeType.InnerTypes.Last();
            var argumentTypes = typeCheckedArguments.Select(x => x.Type).ToVector();

            if (!Unify(Position, calleeType, NamedType.Function(argumentTypes, returnType)))
                return null;

            var callType = unifier.Normalize(returnType);

            return new Call(Position, typeCheckedCallable, typeCheckedArguments, IsOperator, callType);
        }

        public Expression TypeCheck(MethodInvocation methodInvocation, Scope scope)
        {
            var Position = methodInvocation.Position;
            var Instance = methodInvocation.Instance;
            var MethodName = methodInvocation.MethodName;
            var Arguments = methodInvocation.Arguments;

            var typeCheckedInstance = TypeCheck(Instance, scope);

            if (typeCheckedInstance == null)
                return null;

            var instanceType = typeCheckedInstance.Type;
            var instanceNamedType = instanceType as NamedType;

            if (instanceNamedType == null)
            {
                LogError(CompilerError.AmbiguousMethodInvocation(Position));
                return null;
            }

            Scope typeMemberScope;
            if (scope.TryGetMemberScope(typeRegistry, instanceNamedType, out typeMemberScope))
            {
                //This block is SUSPICIOUSLY like all of TypeCheck(Call, Scope), but Callable/MethodName is evaluated in a special scope and the successful return is different.

                var Callable = MethodName;

                //EXPERIMENTAL - TRY EXTENSION METHOD WHEN WE FAILED TO FIND THE METHOD IN THE TYPE MEMBER SCOPE
                if (!typeMemberScope.Contains(Callable.Identifier))
                {
                    var extensionMethodCall = TypeCheck(new Call(Position, MethodName, new[] { Instance }.Concat(Arguments)), scope);

                    if (extensionMethodCall != null)
                        return extensionMethodCall;
                }

                var typeCheckedCallable = TypeCheck(Callable, typeMemberScope);
                
                //END EXPERIMENT

                var typeCheckedArguments = TypeCheckAll(Arguments, scope);

                if (typeCheckedCallable == null || typeCheckedArguments.Any(x => x == null))
                    return null;

                var calleeType = typeCheckedCallable.Type;
                var calleeFunctionType = calleeType as NamedType;

                if (calleeFunctionType == null || calleeFunctionType.Name != "System.Func")
                {
                    LogError(CompilerError.ObjectNotCallable(Position));
                    return null;
                }

                var returnType = calleeType.InnerTypes.Last();
                var argumentTypes = typeCheckedArguments.Select(x => x.Type).ToVector();

                if (!Unify(Position, calleeType, NamedType.Function(argumentTypes, returnType)))
                    return null;

                var callType = unifier.Normalize(returnType);
                //End of suspiciously duplicated code.

                var typedMethodName = (Name)typeCheckedCallable;
                return new MethodInvocation(Position, typeCheckedInstance, typedMethodName, typeCheckedArguments, callType);
            }
            else
            {
                //HACK: Because TypeRegistry cannot yet look up members for concretions of generic types like int*,
                //  we have to double-check whether this is an extension method call for a regular built-in generic function.
                //  Once TypeRegistry lets you look up the members for a type like int*, this block should be removed.
                if (scope.Contains(MethodName.Identifier))
                {
                    var typeCheckedCallable = TypeCheck(MethodName, scope);
                    if (typeCheckedCallable != null)
                    {
                        var extensionMethodCall = TypeCheck(new Call(Position, MethodName, new[] { Instance }.Concat(Arguments)), scope);

                        if (extensionMethodCall != null)
                            return extensionMethodCall;
{'fraction_non_alphanumeric': 0.07324820532367703, 'fraction_numerical': 6.352836541515787e-05, 'mean_word_length': 1.8923288929719797, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1445914', 'n_tokens_mistral': 7252, 'n_tokens_neox': 6792, 'n_words': 1818}