// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Management.Automation.Internal;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

using Microsoft.PowerShell;

namespace System.Management.Automation.Language
{
    internal class TypeDefiner
    {
        internal const string DynamicClassAssemblyName = "PowerShell Class Assembly";
        internal const string DynamicClassAssemblyFullNamePrefix = "PowerShell Class Assembly,";

        private static int s_globalCounter = 0;

        private static readonly CustomAttributeBuilder s_hiddenCustomAttributeBuilder =
            new CustomAttributeBuilder(typeof(HiddenAttribute).GetConstructor(Type.EmptyTypes), Array.Empty<object>());

        private static readonly string s_sessionStateKeeperFieldName = "__sessionStateKeeper";
        internal static readonly string SessionStateFieldName = "__sessionState";

        private static readonly MethodInfo s_sessionStateKeeper_GetSessionState =
            typeof(SessionStateKeeper).GetMethod("GetSessionState", BindingFlags.Instance | BindingFlags.Public);

        private static bool TryConvertArg(object arg, Type type, out object result, Parser parser, IScriptExtent errorExtent)
        {
            // This code could be added to LanguagePrimitives.ConvertTo
            if (arg != null && arg.GetType() == type)
            {
                result = arg;
                return true;
            }

            if (!LanguagePrimitives.TryConvertTo(arg, type, out result))
            {
                parser.ReportError(errorExtent,
                    nameof(ParserStrings.CannotConvertValue),
                    ParserStrings.CannotConvertValue,
                    ToStringCodeMethods.Type(type));
                return false;
            }

            return true;
        }

        private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, AttributeAst attributeAst, AttributeTargets attributeTargets)
        {
            var attributeType = attributeAst.TypeName.GetReflectionAttributeType();
            Diagnostics.Assert(attributeType != null, "Semantic checks should have verified attribute type exists");

            Diagnostics.Assert(
                attributeType.GetCustomAttribute<AttributeUsageAttribute>(true) == null ||
                (attributeType.GetCustomAttribute<AttributeUsageAttribute>(true).ValidOn & attributeTargets) != 0, "Semantic checks should have verified attribute usage");

            var positionalArgs = new object[attributeAst.PositionalArguments.Count];
            var cvv = new ConstantValueVisitor { AttributeArgument = false };
            for (var i = 0; i < attributeAst.PositionalArguments.Count; i++)
            {
                var posArg = attributeAst.PositionalArguments[i];
                positionalArgs[i] = posArg.Accept(cvv);
            }

            var ctorInfos = attributeType.GetConstructors();
            var newConstructors = DotNetAdapter.GetMethodInformationArray(ctorInfos);

            string errorId = null;
            string errorMsg = null;
            bool expandParamsOnBest;
            bool callNonVirtually;
            var positionalArgCount = positionalArgs.Length;

            var bestMethod = Adapter.FindBestMethod(
                newConstructors,
                invocationConstraints: null,
                allowCastingToByRefLikeType: false,
                positionalArgs,
                ref errorId,
                ref errorMsg,
                out expandParamsOnBest,
                out callNonVirtually);

            if (bestMethod == null)
            {
                parser.ReportError(new ParseError(attributeAst.Extent, errorId,
                    string.Format(CultureInfo.InvariantCulture, errorMsg, attributeType.Name, attributeAst.PositionalArguments.Count)));
                return null;
            }

            var constructorInfo = (ConstructorInfo)bestMethod.method;

            var parameterInfo = constructorInfo.GetParameters();
            var ctorArgs = new object[parameterInfo.Length];
            object arg;
            for (var argIndex = 0; argIndex < parameterInfo.Length; ++argIndex)
            {
                var resultType = parameterInfo[argIndex].ParameterType;

                // The extension method 'CustomAttributeExtensions.GetCustomAttributes(ParameterInfo, Type, Boolean)' has inconsistent
                // behavior on its return value in both FullCLR and CoreCLR. According to MSDN, if the attribute cannot be found, it
                // should return an empty collection. However, it returns null in some rare cases [when the parameter isn't backed by
                // actual metadata].
                // This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling
                // against CoreCLR. So we need to add a null check until this is fixed in CLR.
                var paramArrayAttrs = parameterInfo[argIndex].GetCustomAttributes(typeof(ParamArrayAttribute), true);
                if (paramArrayAttrs != null && paramArrayAttrs.Length > 0 && expandParamsOnBest)
                {
                    var elementType = parameterInfo[argIndex].ParameterType.GetElementType();
                    var paramsArray = Array.CreateInstance(elementType, positionalArgCount - argIndex);
                    ctorArgs[argIndex] = paramsArray;

                    for (var i = 0; i < paramsArray.Length; ++i, ++argIndex)
                    {
                        if (!TryConvertArg(positionalArgs[argIndex], elementType, out arg,
                            parser, attributeAst.PositionalArguments[argIndex].Extent))
                        {
                            return null;
                        }

                        paramsArray.SetValue(arg, i);
                    }

                    break;
                }

                if (!TryConvertArg(positionalArgs[argIndex], resultType, out arg,
                    parser, attributeAst.PositionalArguments[argIndex].Extent))
                {
                    return null;
                }

                ctorArgs[argIndex] = arg;
            }

            if (attributeAst.NamedArguments.Count == 0)
            {
                return new CustomAttributeBuilder(constructorInfo, ctorArgs);
            }

            var propertyInfoList = new List<PropertyInfo>();
            var propertyArgs = new List<object>();
            var fieldInfoList = new List<FieldInfo>();
            var fieldArgs = new List<object>();
            foreach (var namedArg in attributeAst.NamedArguments)
            {
                var name = namedArg.ArgumentName;
                var members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property,
                    BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                Diagnostics.Assert(members.Length == 1 && (members[0] is PropertyInfo || members[0] is FieldInfo),
                    "Semantic checks should have ensured names attribute argument exists");

                arg = namedArg.Argument.Accept(cvv);

                var propertyInfo = members[0] as PropertyInfo;
                if (propertyInfo != null)
                {
                    Diagnostics.Assert(propertyInfo.GetSetMethod() != null, "Semantic checks ensures property is settable");

                    if (!TryConvertArg(arg, propertyInfo.PropertyType, out arg, parser, namedArg.Argument.Extent))
                    {
                        return null;
                    }

                    propertyInfoList.Add(propertyInfo);
                    propertyArgs.Add(arg);
                    continue;
                }

                var fieldInfo = (FieldInfo)members[0];
                Diagnostics.Assert(!fieldInfo.IsInitOnly && !fieldInfo.IsLiteral, "Semantic checks ensures field is settable");

                if (!TryConvertArg(arg, fieldInfo.FieldType, out arg, parser, namedArg.Argument.Extent))
                {
                    return null;
                }

                fieldInfoList.Add(fieldInfo);
                fieldArgs.Add(arg);
            }

            return new CustomAttributeBuilder(constructorInfo, ctorArgs,
                propertyInfoList.ToArray(), propertyArgs.ToArray(),
                fieldInfoList.ToArray(), fieldArgs.ToArray());
        }

        internal static void DefineCustomAttributes(TypeBuilder member, ReadOnlyCollection<AttributeAst> attributes, Parser parser, AttributeTargets attributeTargets)
        {
            if (attributes != null)
            {
                foreach (var attr in attributes)
                {
                    var cabuilder = GetAttributeBuilder(parser, attr, attributeTargets);
                    if (cabuilder != null)
                    {
                        member.SetCustomAttribute(cabuilder);
                    }
                }
            }
        }

        internal static void DefineCustomAttributes(PropertyBuilder member, ReadOnlyCollection<AttributeAst> attributes, Parser parser, AttributeTargets attributeTargets)
        {
            if (attributes != null)
            {
                foreach (var attr in attributes)
                {
                    var cabuilder = GetAttributeBuilder(parser, attr, attributeTargets);
                    if (cabuilder != null)
                    {
                        member.SetCustomAttribute(cabuilder);
                    }
                }
            }
        }

        internal static void DefineCustomAttributes(ConstructorBuilder member, ReadOnlyCollection<AttributeAst> attributes, Parser parser, AttributeTargets attributeTargets)
        {
            if (attributes != null)
            {
                foreach (var attr in attributes)
                {
                    var cabuilder = GetAttributeBuilder(parser, attr, attributeTargets);
                    if (cabuilder != null)
                    {
                        member.SetCustomAttribute(cabuilder);
                    }
                }
            }
        }

        internal static void DefineCustomAttributes(MethodBuilder member, ReadOnlyCollection<AttributeAst> attributes, Parser parser, AttributeTargets attributeTargets)
        {
            if (attributes != null)
            {
                foreach (var attr in attributes)
                {
                    var cabuilder = GetAttributeBuilder(parser, attr, attributeTargets);
                    if (cabuilder != null)
                    {
                        member.SetCustomAttribute(cabuilder);
                    }
                }
            }
        }

        internal static void DefineCustomAttributes(EnumBuilder member, ReadOnlyCollection<AttributeAst> attributes, Parser parser, AttributeTargets attributeTargets)
        {
            if (attributes != null)
            {
                foreach (var attr in attributes)
                {
                    var cabuilder = GetAttributeBuilder(parser, attr, attributeTargets);
                    if (cabuilder != null)
                    {
                        member.SetCustomAttribute(cabuilder);
                    }
                }
            }
        }

        private class DefineTypeHelper
        {
            private readonly Parser _parser;
            internal readonly TypeDefinitionAst _typeDefinitionAst;
            internal readonly TypeBuilder _typeBuilder;
            internal readonly FieldBuilder _sessionStateField;
            internal readonly FieldBuilder _sessionStateKeeperField;
            internal readonly ModuleBuilder _moduleBuilder;
            internal readonly TypeBuilder _staticHelpersTypeBuilder;
            private readonly Dictionary<string, PropertyMemberAst> _definedProperties;
            private readonly Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>> _definedMethods;
            private HashSet<Tuple<string, Type>> _interfaceProperties;
            internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions;
            private bool _baseClassHasDefaultCtor;

            /// <summary>
            /// If type has fatal errors we cannot construct .NET type from it.
            /// TypeBuilder.CreateTypeInfo() would throw exception.
            /// </summary>
            public bool HasFatalErrors { get; private set; }

            public DefineTypeHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst typeDefinitionAst, string typeName)
            {
                _moduleBuilder = module;
                _parser = parser;
                _typeDefinitionAst = typeDefinitionAst;

                List<Type> interfaces;
                var baseClass = this.GetBaseTypes(parser, typeDefinitionAst, out interfaces);

                _typeBuilder = module.DefineType(typeName, Reflection.TypeAttributes.Class | Reflection.TypeAttributes.Public, baseClass, interfaces.ToArray());
                _staticHelpersTypeBuilder = module.DefineType(string.Format(CultureInfo.InvariantCulture, "{0}_<staticHelpers>", typeName), Reflection.TypeAttributes.Class);
                DefineCustomAttributes(_typeBuilder, typeDefinitionAst.Attributes, _parser, AttributeTargets.Class);
                _typeDefinitionAst.Type = _typeBuilder;

                _fieldsToInitForMemberFunctions = new List<(string, IParameterMetadataProvider, bool)>();
                _definedMethods = new Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>>(StringComparer.OrdinalIgnoreCase);
                _definedProperties = new Dictionary<string, PropertyMemberAst>(StringComparer.OrdinalIgnoreCase);

                _sessionStateField = _typeBuilder.DefineField(SessionStateFieldName, typeof(SessionStateInternal), FieldAttributes.Private);
                _sessionStateKeeperField = _staticHelpersTypeBuilder.DefineField(s_sessionStateKeeperFieldName, typeof(SessionStateKeeper), FieldAttributes.Assembly | FieldAttributes.Static);
            }

            /// <summary>
            /// Return base class type, never return null.
            /// </summary>
            /// <param name="parser"></param>
            /// <param name="typeDefinitionAst"></param>
            /// <param name="interfaces">Return declared interfaces.</param>
            /// <returns></returns>
            private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, out List<Type> interfaces)
            {
                // Define base types and report errors.
                Type baseClass = null;
                interfaces = new List<Type>();

                // Default base class is System.Object and it has a default ctor.
                _baseClassHasDefaultCtor = true;
                if (typeDefinitionAst.BaseTypes.Count > 0)
                {
                    // base class
                    var baseTypeAsts = typeDefinitionAst.BaseTypes;
                    var firstBaseTypeAst = baseTypeAsts[0];

                    if (firstBaseTypeAst.TypeName.IsArray)
                    {
                        parser.ReportError(firstBaseTypeAst.Extent,
                            nameof(ParserStrings.SubtypeArray),
                            ParserStrings.SubtypeArray,
                            firstBaseTypeAst.TypeName.FullName);
                        // fall to the default base type
                    }
                    else
                    {
                        baseClass = firstBaseTypeAst.TypeName.GetReflectionType();
                        if (baseClass == null)
                        {
                            parser.ReportError(firstBaseTypeAst.Extent,
                                nameof(ParserStrings.TypeNotFound),
                                ParserStrings.TypeNotFound,
                                firstBaseTypeAst.TypeName.FullName);
                            // fall to the default base type
                        }
                        else
                        {
                            if (baseClass.IsSealed)
                            {
                                parser.ReportError(firstBaseTypeAst.Extent,
                                    nameof(ParserStrings.SealedBaseClass),
                                    ParserStrings.SealedBaseClass,
                                    baseClass.Name);
                                // ignore base type if it's sealed.
                                baseClass = null;
                            }
                            else if (baseClass.IsGenericType && !baseClass.IsConstructedGenericType)
                            {
                                parser.ReportError(firstBaseTypeAst.Extent,
                                    nameof(ParserStrings.SubtypeUnclosedGeneric),
                                    ParserStrings.SubtypeUnclosedGeneric,
                                    baseClass.Name);
                                // ignore base type, we cannot inherit from unclosed generic.
                                baseClass = null;
                            }
                            else if (baseClass.IsInterface)
                            {
                                // First Ast can represent interface as well as BaseClass.
                                interfaces.Add(baseClass);
                                baseClass = null;
                            }
                        }
                    }

                    if (baseClass != null)
                    {
                        // All PS classes are TypeName instances.
                        // For PS classes we cannot use reflection API, because type is not created yet.
                        var baseTypeName = firstBaseTypeAst.TypeName as TypeName;
                        if (baseTypeName != null)
                        {
                            _baseClassHasDefaultCtor = baseTypeName.HasDefaultCtor();
                        }
                        else
                        {
                            _baseClassHasDefaultCtor = baseClass.HasDefaultCtor();
                        }
                    }

                    for (int i = 0; i < baseTypeAsts.Count; i++)
                    {
                        if (baseTypeAsts[i].TypeName.IsArray)
                        {
                            parser.ReportError(baseTypeAsts[i].Extent,
                                nameof(ParserStrings.SubtypeArray),
                                ParserStrings.SubtypeArray,
                                baseTypeAsts[i].TypeName.FullName);
                            this.HasFatalErrors = true;
                        }
                    }

                    for (int i = 1; i < baseTypeAsts.Count; i++)
                    {
                        if (baseTypeAsts[i].TypeName.IsArray)
                        {
                            parser.ReportError(baseTypeAsts[i].Extent,
                                nameof(ParserStrings.SubtypeArray),
                                ParserStrings.SubtypeArray,
                                baseTypeAsts[i].TypeName.FullName);
                        }
                        else
                        {
                            Type interfaceType = baseTypeAsts[i].TypeName.GetReflectionType();
                            if (interfaceType == null)
                            {
                                parser.ReportError(baseTypeAsts[i].Extent,
                                    nameof(ParserStrings.TypeNotFound),
                                    ParserStrings.TypeNotFound,
                                    baseTypeAsts[i].TypeName.FullName);
                            }
                            else
                            {
                                if (interfaceType.IsInterface)
                                {
                                    interfaces.Add(interfaceType);
                                }
                                else
                                {
                                    parser.ReportError(baseTypeAsts[i].Extent,
                                        nameof(ParserStrings.InterfaceNameExpected),
                                        ParserStrings.InterfaceNameExpected,
                                        interfaceType.Name);
                                }
                            }
                        }
                    }
                }

                return baseClass ?? typeof(object);
            }

            private bool ShouldImplementProperty(string name, Type type)
            {
                if (_interfaceProperties == null)
                {
                    _interfaceProperties = new HashSet<Tuple<string, Type>>();
                    var allInterfaces = new HashSet<Type>();

                    // TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor.
                    // During compilation the interface hierarchy is flattened, so we only need to resolve one level of ancestral interfaces.
                    foreach (var interfaceType in _typeBuilder.GetInterfaces())
                    {
                        foreach (var parentInterface in interfaceType.GetInterfaces())
                        {
                            allInterfaces.Add(parentInterface);
                        }

                        allInterfaces.Add(interfaceType);
                    }

                    foreach (var interfaceType in allInterfaces)
                    {
                        foreach (var property in interfaceType.GetProperties())
                        {
                            _interfaceProperties.Add(Tuple.Create(property.Name, property.PropertyType));
                        }
                    }
                }

                return _interfaceProperties.Contains(Tuple.Create(name, type));
            }

            public void DefineMembers()
            {
                // If user didn't provide any instance ctors or static ctor we will generate default ctor or static ctor respectively.
                // We can avoid explicit default ctor and static ctor, if we don't have any properties to initialize.
                bool needStaticCtor = false;
                bool needDefaultCtor = false;
                bool hasAnyMethods = false;
                List<FunctionMemberAst> staticCtors = new List<FunctionMemberAst>();
                List<FunctionMemberAst> instanceCtors = new List<FunctionMemberAst>();

                foreach (var member in _typeDefinitionAst.Members)
                {
                    var propertyMemberAst = member as PropertyMemberAst;
                    if (propertyMemberAst != null)
                    {
                        DefineProperty(propertyMemberAst);
                        if (propertyMemberAst.InitialValue != null)
                        {
                            if (propertyMemberAst.IsStatic)
                            {
                                needStaticCtor = true;
                            }
                            else
                            {
                                needDefaultCtor = true;
                            }
                        }
                    }
                    else
                    {
                        FunctionMemberAst method = member as FunctionMemberAst;
                        Diagnostics.Assert(method != null, StringUtil.Format("Unexpected subtype of MemberAst '{0}'. Expect `{1}`",
                            member.GetType().Name, typeof(FunctionMemberAst).GetType().Name));
                        if (method.IsConstructor)
                        {
                            if (method.IsStatic)
                            {
                                staticCtors.Add(method);
                            }
                            else
                            {
                                instanceCtors.Add(method);
                            }
                        }

                        hasAnyMethods = true;

                        DefineMethod(method);
                    }
                }

                // inside ctor we put logic to capture session state from execution context,
                // we cannot delegate default ctor creation to _typeBuilder, if we have any methods.
                // If there are only static methods, we still want to capture context to allow static method calls on instances in the right context.
                if (hasAnyMethods)
                {
                    needDefaultCtor = true;
                }

                if (needStaticCtor)
                {
                    foreach (var ctor in staticCtors)
                    {
                        var parameters = ((IParameterMetadataProvider)ctor.Body).Parameters;
                        // We report error for static ctors with parameters, even with default values.
                        // We don't take them into account.
                        if (parameters == null || parameters.Count == 0)
                        {
                            needStaticCtor = false;
                        }
                    }
                }

                if (needDefaultCtor)
                {
                    needDefaultCtor = instanceCtors.Count == 0;
                }

                //// Now we can decide to create explicit default ctors or report error.

                if (needStaticCtor)
                {
                    var staticCtorAst = new CompilerGeneratedMemberFunctionAst(PositionUtilities.EmptyExtent, _typeDefinitionAst, SpecialMemberFunctionType.StaticConstructor);
                    DefineConstructor(staticCtorAst, null, true, Reflection.MethodAttributes.Private | Reflection.MethodAttributes.Static, Type.EmptyTypes);
                }

                if (_baseClassHasDefaultCtor)
                {
                    if (needDefaultCtor)
                    {
                        var defaultCtorAst = new CompilerGeneratedMemberFunctionAst(PositionUtilities.EmptyExtent, _typeDefinitionAst, SpecialMemberFunctionType.DefaultConstructor);
                        DefineConstructor(defaultCtorAst, null, true, Reflection.MethodAttributes.Public, Type.EmptyTypes);
                    }
                }
                else
                {
                    if (instanceCtors.Count == 0)
                    {
                        _parser.ReportError(_typeDefinitionAst.Extent,
                            nameof(ParserStrings.BaseClassNoDefaultCtor),
                            ParserStrings.BaseClassNoDefaultCtor,
                            _typeBuilder.BaseType.Name);
                        this.HasFatalErrors = true;
                    }
                }
            }

            private void DefineProperty(PropertyMemberAst propertyMemberAst)
            {
                if (_definedProperties.ContainsKey(propertyMemberAst.Name))
                {
                    _parser.ReportError(propertyMemberAst.Extent,
                        nameof(ParserStrings.MemberAlreadyDefined),
                        ParserStrings.MemberAlreadyDefined,
                        propertyMemberAst.Name);
                    return;
                }

                _definedProperties.Add(propertyMemberAst.Name, propertyMemberAst);

                Type type;
                if (propertyMemberAst.PropertyType == null)
                {
                    type = typeof(object);
                }
                else
                {
                    type = propertyMemberAst.PropertyType.TypeName.GetReflectionType();
                    Diagnostics.Assert(type != null, "Semantic checks should have ensure type can't be null");
                }

                PropertyBuilder property = this.EmitPropertyIl(propertyMemberAst, type);
                // Define custom attributes on the property, not on the backingField
                DefineCustomAttributes(property, propertyMemberAst.Attributes, _parser, AttributeTargets.Field | AttributeTargets.Property);
            }

            private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type type)
            {
                // backing field is always private.
                var backingFieldAttributes = FieldAttributes.Private;
                // The property set and property get methods require a special set of attributes.
                var getSetAttributes = Reflection.MethodAttributes.SpecialName | Reflection.MethodAttributes.HideBySig;
                getSetAttributes |= propertyMemberAst.IsPublic ? Reflection.MethodAttributes.Public : Reflection.MethodAttributes.Private;
                if (ShouldImplementProperty(propertyMemberAst.Name, type))
                {
                    getSetAttributes |= Reflection.MethodAttributes.Virtual;
                }

                if (propertyMemberAst.IsStatic)
                {
                    backingFieldAttributes |= FieldAttributes.Static;
                    getSetAttributes |= Reflection.MethodAttributes.Static;
                }
                // C# naming convention for backing fields.
                string backingFieldName = string.Format(CultureInfo.InvariantCulture, "<{0}>k__BackingField", propertyMemberAst.Name);
                var backingField = _typeBuilder.DefineField(backingFieldName, type, backingFieldAttributes);

                bool hasValidateAttributes = false;
                if (propertyMemberAst.Attributes != null)
                {
                    for (int i = 0; i < propertyMemberAst.Attributes.Count; i++)
                    {
                        Type attributeType = propertyMemberAst.Attributes[i].TypeName.GetReflectionAttributeType();
                        if (attributeType != null && attributeType.IsSubclassOf(typeof(ValidateArgumentsAttribute)))
                        {
                            hasValidateAttributes = true;
                            break;
                        }
                    }
                }

                // The last argument of DefineProperty is null, because the property has no parameters.
                PropertyBuilder property = _typeBuilder.DefineProperty(propertyMemberAst.Name, Reflection.PropertyAttributes.None, type, null);

                // Define the "get" accessor method.
                MethodBuilder getMethod = _typeBuilder.DefineMethod(string.Concat("get_", propertyMemberAst.Name), getSetAttributes, type, Type.EmptyTypes);
                ILGenerator getIlGen = getMethod.GetILGenerator();
                if (propertyMemberAst.IsStatic)
                {
                    // static
                    getIlGen.Emit(OpCodes.Ldsfld, backingField);
                    getIlGen.Emit(OpCodes.Ret);
                }
                else
                {
                    // instance
                    getIlGen.Emit(OpCodes.Ldarg_0);
                    getIlGen.Emit(OpCodes.Ldfld, backingField);
                    getIlGen.Emit(OpCodes.Ret);
                }

                // Define the "set" accessor method.
                MethodBuilder setMethod = _typeBuilder.DefineMethod(string.Concat("set_", propertyMemberAst.Name), getSetAttributes, null, new Type[] { type });
                ILGenerator setIlGen = setMethod.GetILGenerator();

                if (hasValidateAttributes)
                {
                    Type typeToLoad = _typeBuilder;
                    setIlGen.Emit(OpCodes.Ldtoken, typeToLoad);
                    setIlGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); // load current Type on stack
                    setIlGen.Emit(OpCodes.Ldstr, propertyMemberAst.Name); // load name of Property
                    setIlGen.Emit(propertyMemberAst.IsStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1); // load set value
                    if (type.IsValueType)
                    {
                        setIlGen.Emit(OpCodes.Box, type);
                    }

                    setIlGen.Emit(OpCodes.Call, CachedReflectionInfo.ClassOps_ValidateSetProperty);
                }

                if (propertyMemberAst.IsStatic)
                {
                    setIlGen.Emit(OpCodes.Ldarg_0);
                    setIlGen.Emit(OpCodes.Stsfld, backingField);
                }
                else
                {
                    setIlGen.Emit(OpCodes.Ldarg_0);
                    setIlGen.Emit(OpCodes.Ldarg_1);
                    setIlGen.Emit(OpCodes.Stfld, backingField);
                }

                setIlGen.Emit(OpCodes.Ret);

                // Map the two methods created above to our PropertyBuilder to
                // their corresponding behaviors, "get" and "set" respectively.
                property.SetGetMethod(getMethod);
                property.SetSetMethod(setMethod);

                if (propertyMemberAst.IsHidden)
                {
                    property.SetCustomAttribute(s_hiddenCustomAttributeBuilder);
                }

                return property;
            }

            private bool CheckForDuplicateOverload(FunctionMemberAst functionMemberAst, Type[] newParameters)
            {
                List<Tuple<FunctionMemberAst, Type[]>> overloads;
                if (!_definedMethods.TryGetValue(functionMemberAst.Name, out overloads))
                {
                    overloads = new List<Tuple<FunctionMemberAst, Type[]>>();
                    _definedMethods.Add(functionMemberAst.Name, overloads);
                }
                else
                {
                    foreach (var overload in overloads)
                    {
                        var overloadParameters = overload.Item2;

                        // This test won't be correct when defaults are supported
                        if (newParameters.Length != overloadParameters.Length)
                        {
                            continue;
                        }

                        var sameSignature = true;
                        for (int i = 0; i < newParameters.Length; i++)
                        {
                            if (newParameters[i] != overloadParameters[i])
                            {
                                sameSignature = false;
                                break;
                            }
                        }

                        if (sameSignature)
                        {
                            // If both are both static/instance, it's an error.
                            // Otherwise, signatures can match only for the constructor.
                            if (overload.Item1.IsStatic == functionMemberAst.IsStatic ||
                                !functionMemberAst.IsConstructor)
                            {
                                _parser.ReportError(functionMemberAst.NameExtent ?? functionMemberAst.Extent,
                                    nameof(ParserStrings.MemberAlreadyDefined),
                                    ParserStrings.MemberAlreadyDefined,
                                    functionMemberAst.Name);
                                return true;
                            }
                        }
                    }
                }

                overloads.Add(Tuple.Create(functionMemberAst, newParameters));
                return false;
            }

            private Type[] GetParameterTypes(FunctionMemberAst functionMemberAst)
            {
                var parameters = ((IParameterMetadataProvider)functionMemberAst).Parameters;
                if (parameters == null)
                {
                    return Type.EmptyTypes;
                }

                bool anyErrors = false;
                var result = new Type[parameters.Count];
                for (var i = 0; i < parameters.Count; i++)
                {
                    var typeConstraint = parameters[i].Attributes.OfType<TypeConstraintAst>().FirstOrDefault();
                    var paramType = (typeConstraint != null)
                                        ? typeConstraint.TypeName.GetReflectionType()
                                        : typeof(object);
                    if (paramType == null)
                    {
                        _parser.ReportError(typeConstraint.Extent,
                            nameof(ParserStrings.TypeNotFound),
                            ParserStrings.TypeNotFound,
                            typeConstraint.TypeName.FullName);
                        anyErrors = true;
                    }
                    else if (paramType == typeof(void) || paramType.IsGenericTypeDefinition)
                    {
                        _parser.ReportError(typeConstraint.Extent,
                            nameof(ParserStrings.TypeNotAllowedForParameter),
                            ParserStrings.TypeNotAllowedForParameter,
                            typeConstraint.TypeName.FullName);
                        anyErrors = true;
                    }

                    result[i] = paramType;
                }

                return anyErrors ? null : result;
            }

            private bool MethodExistsOnBaseClassAndFinal(string methodName, Type[] parameterTypes)
            {
                Type baseType = _typeBuilder.BaseType;

                // If baseType is PS class, then method will be virtual, once we define it.
                if (baseType is TypeBuilder)
                {
                    return false;
                }

                var mi = baseType.GetMethod(methodName, parameterTypes);
                return mi != null && mi.IsFinal;
            }

            private void DefineMethod(FunctionMemberAst functionMemberAst)
            {
                var parameterTypes = GetParameterTypes(functionMemberAst);
                if (parameterTypes == null)
                {
                    // There must have been an error, just return
                    return;
                }

                if (CheckForDuplicateOverload(functionMemberAst, parameterTypes))
                {
                    return;
                }

                if (functionMemberAst.IsConstructor)
                {
                    var methodAttributes = Reflection.MethodAttributes.Public;
                    if (functionMemberAst.IsStatic)
                    {
                        var parameters = functionMemberAst.Parameters;
                        if (parameters.Count > 0)
                        {
                            IScriptExtent errorExtent = Parser.ExtentOf(parameters[0], parameters.Last());
                            _parser.ReportError(errorExtent,
                                nameof(ParserStrings.StaticConstructorCantHaveParameters),
                                ParserStrings.StaticConstructorCantHaveParameters);
                            return;
                        }

                        methodAttributes |= Reflection.MethodAttributes.Static;
                    }

                    DefineConstructor(functionMemberAst, functionMemberAst.Attributes, functionMemberAst.IsHidden, methodAttributes, parameterTypes);
                    return;
                }

                var attributes = functionMemberAst.IsPublic
                                     ? Reflection.MethodAttributes.Public
                                     : Reflection.MethodAttributes.Private;
                if (functionMemberAst.IsStatic)
                {
                    attributes |= Reflection.MethodAttributes.Static;
                }
                else
                {
                    if (this.MethodExistsOnBaseClassAndFinal(functionMemberAst.Name, parameterTypes))
                    {
                        attributes |= Reflection.MethodAttributes.HideBySig;
                        attributes |= Reflection.MethodAttributes.NewSlot;
                    }

                    attributes |= Reflection.MethodAttributes.Virtual;
                }

                var returnType = functionMemberAst.GetReturnType();
                if (returnType == null)
                {
                    _parser.ReportError(functionMemberAst.ReturnType.Extent,
                        nameof(ParserStrings.TypeNotFound),
                        ParserStrings.TypeNotFound,
                        functionMemberAst.ReturnType.TypeName.FullName);
                    return;
                }

                var method = _typeBuilder.DefineMethod(functionMemberAst.Name, attributes, returnType, parameterTypes);
                DefineCustomAttributes(method, functionMemberAst.Attributes, _parser, AttributeTargets.Method);
                if (functionMemberAst.IsHidden)
                {
                    method.SetCustomAttribute(s_hiddenCustomAttributeBuilder);
                }

                var ilGenerator = method.GetILGenerator();
                DefineMethodBody(functionMemberAst, ilGenerator, GetMetaDataName(method.Name, parameterTypes.Length), functionMemberAst.IsStatic, parameterTypes, returnType,
                    (i, n) => method.DefineParameter(i, ParameterAttributes.None, n));
            }

            private void DefineConstructor(IParameterMetadataProvider ipmp, ReadOnlyCollection<AttributeAst> attributeAsts, bool isHidden, Reflection.MethodAttributes methodAttributes, Type[] parameterTypes)
            {
                bool isStatic = (methodAttributes & Reflection.MethodAttributes.Static) != 0;
                var ctor = isStatic
                    ? _typeBuilder.DefineTypeInitializer()
                    : _typeBuilder.DefineConstructor(methodAttributes, CallingConventions.Standard, parameterTypes);
                DefineCustomAttributes(ctor, attributeAsts, _parser, AttributeTargets.Constructor);
                if (isHidden)
                {
                    ctor.SetCustomAttribute(s_hiddenCustomAttributeBuilder);
                }

                var ilGenerator = ctor.GetILGenerator();

                if (!isStatic)
                {
                    ilGenerator.Emit(OpCodes.Ldarg_0); // load 'this' on stack for Stfld call

                    ilGenerator.Emit(OpCodes.Ldnull);
                    ilGenerator.Emit(OpCodes.Ldfld, _sessionStateKeeperField);
                    ilGenerator.EmitCall(OpCodes.Call, s_sessionStateKeeper_GetSessionState, null); // load 'sessionState' on stack for Stfld call

                    ilGenerator.Emit(OpCodes.Stfld, _sessionStateField);
                }

                DefineMethodBody(ipmp, ilGenerator, GetMetaDataName(ctor.Name, parameterTypes.Length), isStatic, parameterTypes, typeof(void),
                    (i, n) => ctor.DefineParameter(i, ParameterAttributes.None, n));
            }

            private string GetMetaDataName(string name, int numberOfParameters)
            {
                int currentId = Interlocked.Increment(ref s_globalCounter);
                string metaDataName = name + "_" + numberOfParameters + "_" + currentId;
                return metaDataName;
            }

            private void DefineMethodBody(
                IParameterMetadataProvider ipmp,
                ILGenerator ilGenerator,
                string metadataToken,
                bool isStatic,
                Type[] parameterTypes,
                Type returnType,
                Action<int, string> parameterNameSetter)
            {
                var wrapperFieldName = string.Format(CultureInfo.InvariantCulture, "<{0}>", metadataToken);
                var scriptBlockWrapperField = _staticHelpersTypeBuilder.DefineField(wrapperFieldName,
                                                                       typeof(ScriptBlockMemberMethodWrapper),
                                                                       FieldAttributes.Assembly | FieldAttributes.Static);

                ilGenerator.Emit(OpCodes.Ldsfld, scriptBlockWrapperField);
                if (isStatic)
                {
                    ilGenerator.Emit(OpCodes.Ldnull);                   // pass null (no this)
                    ilGenerator.Emit(OpCodes.Ldnull);                   // pass null (no sessionStateInternal)
                }
                else
                {
                    EmitLdarg(ilGenerator, 0);                            // pass this
                    ilGenerator.Emit(OpCodes.Ldarg_0);                    // pass 'this' for Ldfld call
                    ilGenerator.Emit(OpCodes.Ldfld, _sessionStateField);  // pass sessionStateInternal
                }

                int parameterCount = parameterTypes.Length;
                if (parameterCount > 0)
                {
                    var parameters = ipmp.Parameters;
                    var local = ilGenerator.DeclareLocal(typeof(object[]));

                    EmitLdc(ilGenerator, parameterCount);               // Create an array to hold all
                    ilGenerator.Emit(OpCodes.Newarr, typeof(object));  //     of the parameters
                    ilGenerator.Emit(OpCodes.Stloc, local);             // Save array for repeated use
                    int j = isStatic ? 0 : 1;
                    for (int i = 0; i < parameterCount; i++, j++)
                    {
                        ilGenerator.Emit(OpCodes.Ldloc, local);           // load array
                        EmitLdc(ilGenerator, i);                          // index to save at
                        EmitLdarg(ilGenerator, j);                        // load argument (skipping this)
                        if (parameterTypes[i].IsValueType)  // value types must be boxed
                        {
                            ilGenerator.Emit(OpCodes.Box, parameterTypes[i]);
                        }

                        ilGenerator.Emit(OpCodes.Stelem_Ref);           // save the argument in the array

                        // Set the parameter name, mostly for Get-Member
                        // Parameters are indexed beginning with the number 1 for the first parameter
                        parameterNameSetter(i + 1, parameters[i].Name.VariablePath.UserPath);
                    }

                    ilGenerator.Emit(OpCodes.Ldloc, local);         // load array
                }
                else
                {
                    ilGenerator.Emit(OpCodes.Ldsfld, typeof(ScriptBlockMemberMethodWrapper).GetField("_emptyArgumentArray", BindingFlags.Static | BindingFlags.Public));
                }

                MethodInfo invokeHelper;
                if (returnType == typeof(void))
                {
                    invokeHelper = typeof(ScriptBlockMemberMethodWrapper).GetMethod("InvokeHelper", BindingFlags.Instance | BindingFlags.Public);
                }
                else
                {
                    invokeHelper = typeof(ScriptBlockMemberMethodWrapper).GetMethod("InvokeHelperT", BindingFlags.Instance | BindingFlags.Public).MakeGenericMethod(returnType);
                }

                ilGenerator.Emit(OpCodes.Tailcall);
                ilGenerator.EmitCall(OpCodes.Call, invokeHelper, null);
                ilGenerator.Emit(OpCodes.Ret);

                _fieldsToInitForMemberFunctions.Add((wrapperFieldName, ipmp, isStatic));
            }
        }

        private class DefineEnumHelper
        {
            private readonly Parser _parser;
            private readonly TypeDefinitionAst _enumDefinitionAst;
            private readonly ModuleBuilder _moduleBuilder;
            private readonly string _typeName;

            internal DefineEnumHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst enumDefinitionAst, string typeName)
            {
                _parser = parser;
                _enumDefinitionAst = enumDefinitionAst;
                _moduleBuilder = module;
                _typeName = typeName;
            }

            internal static List<DefineEnumHelper> Sort(List<DefineEnumHelper> defineEnumHelpers, Parser parser)
            {
                // This function does a topological sort of the enums to be defined.  This is needed so we
                // can allow one enum member to use the value of another w/o needing to worry about the order
                // they are declared in.  For example:
                //
                //     enum E1 { e1 = [E2]::e2 }
                //     enum E2 { e2 = 42 }
                //
                // We also want to report an error for recursive expressions, e.g.
                //
                //     enum E1 { e1 = [E2]::e2 }
                //     enum E2 { e2 = [E1]::e1 }
                //
                // Note that this code is not as permissive as it could be, e.g. we could (but do not) allow:
                //
                //     enum E1 { e1 = [E2]::e2 }
                //     enum E2 {
                //         e2 = 42
                //         e2a = [E1]::e1
                //     }
                //
                // In this case, there is no cycle in the constant values despite E1 referencing E2 and vice versa.
                //
                // The basic algorithm is to create a graph where the edges represent a dependency, using this example:
                //
                //     enum E1 { e1 = [E2]::e2 }
                //     enum E2 { e2 = 42 }
                //
                // We have an edge E1->E2.  E2 has no dependencies.

                if (defineEnumHelpers.Count == 1)
                {
                    return defineEnumHelpers;
                }

                // There won't be many nodes in our graph, so we just use a dictionary with a list of edges instead
                // of something cleaner.
                var graph = new Dictionary<TypeDefinitionAst, Tuple<DefineEnumHelper, List<TypeDefinitionAst>>>();

                // Add all of our nodes to the graph
                foreach (var helper in defineEnumHelpers)
                {
                    graph.Add(helper._enumDefinitionAst, Tuple.Create(helper, new List<TypeDefinitionAst>()));
                }

                // Now find any edges.
                foreach (var helper in defineEnumHelpers)
                {
                    foreach (var enumerator in helper._enumDefinitionAst.Members)
                    {
                        var initExpr = ((PropertyMemberAst)enumerator).InitialValue;
                        if (initExpr == null)
                        {
                            // No initializer, so no dependency (this is incorrect assumption if
                            // we wanted to be more general like C#.)
                            continue;
                        }

                        // The expression may have multiple member expressions, e.g. [E]::e1 + [E]::e2
                        foreach (var memberExpr in initExpr.FindAll(ast => ast is MemberExpressionAst, false))
                        {
                            var typeExpr = ((MemberExpressionAst)memberExpr).Expression as TypeExpressionAst;
                            if (typeExpr != null)
                            {
                                // We only want to add edges for enums being defined in the current scope.
                                // We detect this by seeing if the ast is in our graph or not.
                                var typeName = typeExpr.TypeName as TypeName;
                                if (typeName != null
                                    && typeName._typeDefinitionAst != null
                                    && typeName._typeDefinitionAst != helper._enumDefinitionAst  // Don't add self edges
                                    && graph.ContainsKey(typeName._typeDefinitionAst))
                                {
                                    var edgeList = graph[helper._enumDefinitionAst].Item2;
                                    if (!edgeList.Contains(typeName._typeDefinitionAst))  // Only add 1 edge per enum
                                    {
                                        edgeList.Add(typeName._typeDefinitionAst);
                                    }
                                }
                            }
                        }
                    }
                }

                // Our graph is built.  The ready list will hold nodes that don't depend on anything not already
                // in the result list.  We start with a list of nodes with no edges (no dependencies).
                var result = new List<DefineEnumHelper>(defineEnumHelpers.Count);
                var readyList = new List<DefineEnumHelper>(defineEnumHelpers.Count);
                readyList.AddRange(from value in graph.Values where value.Item2.Count == 0 select value.Item1);
                while (readyList.Count > 0)
                {
                    var node = readyList[readyList.Count - 1];
                    readyList.RemoveAt(readyList.Count - 1);
                    result.Add(node);

                    // Remove all edges to this node as it is in our result list now.
                    foreach (var value in graph.Values)
                    {
                        value.Item2.Remove(node._enumDefinitionAst);

                        // If we removed the last edge, we can put this node on the ready list (assuming it
                        // wasn't already there or in our result list.)
                        if (value.Item2.Count == 0 && !result.Contains(value.Item1) && !readyList.Contains(value.Item1))
                        {
                            readyList.Add(value.Item1);
                        }
                    }
                }

                if (result.Count < defineEnumHelpers.Count)
                {
                    // There was a cycle, report an error on each enum.
                    foreach (var helper in defineEnumHelpers)
                    {
                        if (!result.Contains(helper))
                        {
                            parser.ReportError(helper._enumDefinitionAst.Extent,
                                nameof(ParserStrings.CycleInEnumInitializers),
                                ParserStrings.CycleInEnumInitializers);
                        }
                    }
                }
                else
                {
                    Diagnostics.Assert(result.Count == defineEnumHelpers.Count, "Logic error if we have more outgoing results than incoming");
                }

                return result;
            }

            internal void DefineEnum()
            {
                var typeConstraintAst = _enumDefinitionAst.BaseTypes.FirstOrDefault();
                var underlyingType = typeConstraintAst == null ? typeof(int) : typeConstraintAst.TypeName.GetReflectionType();

                var definedEnumerators = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                var enumBuilder = _moduleBuilder.DefineEnum(_typeName, Reflection.TypeAttributes.Public, underlyingType);
                DefineCustomAttributes(enumBuilder, _enumDefinitionAst.Attributes, _parser, AttributeTargets.Enum);

                dynamic value = 0;
                dynamic maxValue = 0;
                switch (Type.GetTypeCode(underlyingType))
                {
                    case TypeCode.Byte:
                        maxValue = byte.MaxValue;
                        break;
                    case TypeCode.Int16:
                        maxValue = short.MaxValue;
                        break;
                    case TypeCode.Int32:
                        maxValue = int.MaxValue;
                        break;
                    case TypeCode.Int64:
                        maxValue = long.MaxValue;
                        break;
                    case TypeCode.SByte:
                        maxValue = sbyte.MaxValue;
                        break;
                    case TypeCode.UInt16:
                        maxValue = ushort.MaxValue;
                        break;
                    case TypeCode.UInt32:
                        maxValue = uint.MaxValue;
                        break;
                    case TypeCode.UInt64:
                        maxValue = ulong.MaxValue;
                        break;
                    default:
                        _parser.ReportError(
                            typeConstraintAst.Extent,
                            nameof(ParserStrings.InvalidUnderlyingType),
                            ParserStrings.InvalidUnderlyingType,
                            underlyingType);
                        break;
                }

                bool valueTooBig = false;

                foreach (var member in _enumDefinitionAst.Members)
                {
                    var enumerator = (PropertyMemberAst)member;
                    if (enumerator.InitialValue != null)
                    {
                        object constValue;
                        if (IsConstantValueVisitor.IsConstant(enumerator.InitialValue, out constValue, false, false))
                        {
                            if (!LanguagePrimitives.TryConvertTo(constValue, underlyingType, out value))
                            {
                                if (constValue != null &&
                                    LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constValue.GetType())))
                                {
                                    _parser.ReportError(
                                        enumerator.InitialValue.Extent,
                                        nameof(ParserStrings.EnumeratorValueOutOfBounds),
                                        ParserStrings.EnumeratorValueOutOfBounds,
                                        ToStringCodeMethods.Type(underlyingType));
                                }
                                else
                                {
                                    _parser.ReportError(
                                        enumerator.InitialValue.Extent,
                                        nameof(ParserStrings.CannotConvertValue),
                                        ParserStrings.CannotConvertValue,
                                        ToStringCodeMethods.Type(underlyingType));
                                }
                            }
                        }
                        else
                        {
                            _parser.ReportError(
                                enumerator.InitialValue.Extent,
                                nameof(ParserStrings.EnumeratorValueMustBeConstant),
                                ParserStrings.EnumeratorValueMustBeConstant);
                        }

                        valueTooBig = value > maxValue;
                    }

                    if (valueTooBig)
                    {
                        _parser.ReportError(
                            enumerator.Extent,
                            nameof(ParserStrings.EnumeratorValueOutOfBounds),
                            ParserStrings.EnumeratorValueOutOfBounds,
                            ToStringCodeMethods.Type(underlyingType));
                    }

                    if (definedEnumerators.Contains(enumerator.Name))
                    {
                        _parser.ReportError(
                            enumerator.Extent,
                            nameof(ParserStrings.MemberAlreadyDefined),
                            ParserStrings.MemberAlreadyDefined,
                            enumerator.Name);
                    }
                    else if (value != null)
                    {
                        value = Convert.ChangeType(value, underlyingType);
                        definedEnumerators.Add(enumerator.Name);
                        enumBuilder.DefineLiteral(enumerator.Name, value);
                    }

                    if (value < maxValue)
                    {
                        value += 1;
                        valueTooBig = false;
                    }
                    else
                    {
                        valueTooBig = true;
                    }
                }

                _enumDefinitionAst.Type = enumBuilder.CreateTypeInfo().AsType();
            }
        }

        private static IEnumerable<CustomAttributeBuilder> GetAssemblyAttributeBuilders(string scriptFile)
        {
            var ctor = typeof(DynamicClassImplementationAssemblyAttribute).GetConstructor(Type.EmptyTypes);
            var emptyArgs = Array.Empty<object>();

            if (string.IsNullOrEmpty(scriptFile))
            {
                yield return new CustomAttributeBuilder(ctor, emptyArgs);
                yield break;
            }

            var propertyInfo = new PropertyInfo[] {
                typeof(DynamicClassImplementationAssemblyAttribute).GetProperty(nameof(DynamicClassImplementationAssemblyAttribute.ScriptFile)) };
            var propertyArgs = new object[] { scriptFile };

            yield return new CustomAttributeBuilder(ctor, emptyArgs,
                propertyInfo, propertyArgs, Array.Empty<FieldInfo>(), emptyArgs);
        }

        private static int counter = 0;

        internal static Assembly DefineTypes(Parser parser, Ast rootAst, TypeDefinitionAst[] typeDefinitions)
        {
            Diagnostics.Assert(rootAst.Parent == null, "Caller should only define types from the root ast");

            var definedTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            var assemblyName = new AssemblyName(DynamicClassAssemblyName)
            {
                // We could generate a unique name, but a unique version works too.
                Version = new Version(1, 0, 0, Interlocked.Increment(ref counter))
            };
            var assembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.RunAndCollect, GetAssemblyAttributeBuilders(rootAst.Extent.File));
            var module = assembly.DefineDynamicModule(DynamicClassAssemblyName);

            var defineTypeHelpers = new List<DefineTypeHelper>();
            var defineEnumHelpers = new List<DefineEnumHelper>();

            foreach (var typeDefinitionAst in typeDefinitions)
            {
                var typeName = GetClassNameInAssembly(typeDefinitionAst);
                if (!definedTypes.Contains(typeName))
                {
                    definedTypes.Add(typeName);
                    if ((typeDefinitionAst.TypeAttributes & TypeAttributes.Class) == TypeAttributes.Class)
                    {
                        defineTypeHelpers.Add(new DefineTypeHelper(parser, module, typeDefinitionAst, typeName));
                    }
                    else if ((typeDefinitionAst.TypeAttributes & TypeAttributes.Enum) == TypeAttributes.Enum)
                    {
                        defineEnumHelpers.Add(new DefineEnumHelper(parser, module, typeDefinitionAst, typeName));
                    }
                }
            }

            // Define enums before classes so members of classes can use these enum types.
            defineEnumHelpers = DefineEnumHelper.Sort(defineEnumHelpers, parser);
            foreach (var helper in defineEnumHelpers)
            {
                helper.DefineEnum();
            }

            foreach (var helper in defineTypeHelpers)
            {
                helper.DefineMembers();
            }

            foreach (var helper in defineTypeHelpers)
            {
                Diagnostics.Assert(helper._typeDefinitionAst.Type is TypeBuilder, "Type should be the TypeBuilder");
                bool runtimeTypeAssigned = false;
                if (!helper.HasFatalErrors)
                {
                    try
                    {
                        var type = helper._typeBuilder.CreateType();
                        helper._typeDefinitionAst.Type = type;
                        runtimeTypeAssigned = true;
                        var helperType = helper._staticHelpersTypeBuilder.CreateType();

                        SessionStateKeeper sessionStateKeeper = new SessionStateKeeper();
                        helperType.GetField(s_sessionStateKeeperFieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, sessionStateKeeper);

                        if (helper._fieldsToInitForMemberFunctions != null)
                        {
                            foreach (var tuple in helper._fieldsToInitForMemberFunctions)
                            {
                                // If the wrapper is for a static method, we need the sessionStateKeeper to determine the right SessionState to run.
                                // If the wrapper is for an instance method, we use the SessionState that the instance is bound to, and thus don't need sessionStateKeeper.
                                var methodWrapper = tuple.isStatic
                                    ? new ScriptBlockMemberMethodWrapper(tuple.bodyAst, sessionStateKeeper)
                                    : new ScriptBlockMemberMethodWrapper(tuple.bodyAst);
                                helperType.GetField(tuple.fieldName, BindingFlags.NonPublic | BindingFlags.Static)
                                    .SetValue(null, methodWrapper);
                            }
                        }
                    }
                    catch (TypeLoadException e)
                    {
                        // This is a cheap way to get error messages about non-implemented abstract/interface methods (and maybe some other errors).
                        // We use .NET API to perform this check during type creation.
                        //
                        // Presumably this catch could go away when we will not create Type at parse time.
                        // Error checking should be moved/added to semantic checks.
                        parser.ReportError(helper._typeDefinitionAst.Extent,
                            nameof(ParserStrings.TypeCreationError),
                            ParserStrings.TypeCreationError,
                            helper._typeBuilder.Name,
                            e.Message);
                    }
                }

                if (!runtimeTypeAssigned)
                {
                    // Clean up ast
                    helper._typeDefinitionAst.Type = null;
                }
            }

            return assembly;
        }

        private static string GetClassNameInAssembly(TypeDefinitionAst typeDefinitionAst)
        {
            // Only allocate a list if necessary - in the most common case, we don't need it.
            List<string> nameParts = null;
            var parent = typeDefinitionAst.Parent;
            while (parent.Parent != null)
            {
                if (parent is IParameterMetadataProvider)
                {
                    nameParts = nameParts ?? new List<string>();
                    var fnDefn = parent.Parent as FunctionDefinitionAst;
                    if (fnDefn != null)
                    {
                        parent = fnDefn;
                        nameParts.Add(fnDefn.Name);
                    }
                    else
                    {
                        nameParts.Add("<" + parent.Extent.Text.GetHashCode().ToString("x", CultureInfo.InvariantCulture) + ">");
                    }
                }

                parent = parent.Parent;
            }

            if (nameParts == null)
            {
                return typeDefinitionAst.Name;
            }

            nameParts.Reverse();
            nameParts.Add(typeDefinitionAst.Name);
            return string.Join(".", nameParts);
        }

        private static OpCode[] s_ldc =
        {
            OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, OpCodes.Ldc_I4_4,
            OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8
        };

        private static void EmitLdc(ILGenerator emitter, int c)
        {
            if (c < s_ldc.Length)
            {
                emitter.Emit(s_ldc[c]);
            }
            else
            {
                emitter.Emit(OpCodes.Ldc_I4, c);
            }
        }

        private static OpCode[] s_ldarg =
        {
            OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3
        };

        private static void EmitLdarg(ILGenerator emitter, int c)
        {
            if (c < s_ldarg.Length)
            {
                emitter.Emit(s_ldarg[c]);
            }
            else
            {
                emitter.Emit(OpCodes.Ldarg, c);
            }
        }
    }
}
