using System; using System.Threading; using System.Reflection; using System.Reflection.Emit; using System.Collections.Generic; using NLua.Method; namespace NLua { class CodeGeneration { private readonly Dictionary _classCollection = new Dictionary(); private readonly Dictionary _delegateCollection = new Dictionary(); #if !NETSTANDARD && !WINDOWS_UWP private Dictionary eventHandlerCollection = new Dictionary(); private Type eventHandlerParent = typeof(LuaEventHandler); private Type delegateParent = typeof(LuaDelegate); private Type classHelper = typeof(LuaClassHelper); private AssemblyBuilder newAssembly; private ModuleBuilder newModule; private int luaClassNumber = 1; #endif static CodeGeneration() { } private CodeGeneration() { // Create an assembly name var assemblyName = new AssemblyName(); assemblyName.Name = "NLua_generatedcode"; // Create a new assembly with one module. #if NETCOREAPP newAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); newModule = newAssembly.DefineDynamicModule("NLua_generatedcode"); #elif !NETSTANDARD && !WINDOWS_UWP newAssembly = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); newModule = newAssembly.DefineDynamicModule("NLua_generatedcode"); #endif } /* * Singleton instance of the class */ public static CodeGeneration Instance { get; } = new CodeGeneration(); /* * Generates an event handler that calls a Lua function */ private Type GenerateEvent(Type eventHandlerType) { #if NETSTANDARD || WINDOWS_UWP throw new NotImplementedException(" Emit not available on .NET Standard "); #else string typeName; lock (this) { typeName = "LuaGeneratedClass" + luaClassNumber; luaClassNumber++; } // Define a public class in the assembly, called typeName var myType = newModule.DefineType(typeName, TypeAttributes.Public, eventHandlerParent); // Defines the handler method. Its signature is void(object, ) var paramTypes = new Type[2]; paramTypes[0] = typeof(object); paramTypes[1] = eventHandlerType; var returnType = typeof(void); var handleMethod = myType.DefineMethod("HandleEvent", MethodAttributes.Public | MethodAttributes.HideBySig, returnType, paramTypes); // Emits the IL for the method. It loads the arguments // and calls the handleEvent method of the base class ILGenerator generator = handleMethod.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Ldarg_2); var miGenericEventHandler = eventHandlerParent.GetMethod("HandleEvent"); generator.Emit(OpCodes.Call, miGenericEventHandler); // returns generator.Emit(OpCodes.Ret); // creates the new type return myType.CreateType(); #endif } /* * Generates a type that can be used for instantiating a delegate * of the provided type, given a Lua function. */ private Type GenerateDelegate(Type delegateType) { #if NETSTANDARD || WINDOWS_UWP throw new NotImplementedException("GenerateDelegate is not available on Windows Store, please register your LuaDelegate type with Lua.RegisterLuaDelegateType( yourDelegate, theLuaDelegateHandler) "); #else string typeName; lock (this) { typeName = "LuaGeneratedClass" + luaClassNumber; luaClassNumber++; } // Define a public class in the assembly, called typeName var myType = newModule.DefineType(typeName, TypeAttributes.Public, delegateParent); // Defines the delegate method with the same signature as the // Invoke method of delegateType var invokeMethod = delegateType.GetMethod("Invoke"); var paramInfo = invokeMethod.GetParameters(); var paramTypes = new Type[paramInfo.Length]; var returnType = invokeMethod.ReturnType; // Counts out and ref params, for use later int nOutParams = 0; int nOutAndRefParams = 0; for (int i = 0; i < paramTypes.Length; i++) { paramTypes[i] = paramInfo[i].ParameterType; if ((!paramInfo[i].IsIn) && paramInfo[i].IsOut) nOutParams++; if (paramTypes[i].IsByRef) nOutAndRefParams++; } int[] refArgs = new int[nOutAndRefParams]; var delegateMethod = myType.DefineMethod("CallFunction", invokeMethod.Attributes, returnType, paramTypes); // Generates the IL for the method ILGenerator generator = delegateMethod.GetILGenerator(); generator.DeclareLocal(typeof(object[])); // original arguments generator.DeclareLocal(typeof(object[])); // with out-only arguments removed generator.DeclareLocal(typeof(int[])); // indexes of out and ref arguments if (!(returnType == typeof(void))) // return value generator.DeclareLocal(returnType); else generator.DeclareLocal(typeof(object)); // Initializes local variables generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); generator.Emit(OpCodes.Newarr, typeof(object)); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldc_I4, paramTypes.Length - nOutParams); generator.Emit(OpCodes.Newarr, typeof(object)); generator.Emit(OpCodes.Stloc_1); generator.Emit(OpCodes.Ldc_I4, nOutAndRefParams); generator.Emit(OpCodes.Newarr, typeof(int)); generator.Emit(OpCodes.Stloc_2); // Stores the arguments in the local variables for (int iArgs = 0, iInArgs = 0, iOutArgs = 0; iArgs < paramTypes.Length; iArgs++) { generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4, iArgs); generator.Emit(OpCodes.Ldarg, iArgs + 1); if (paramTypes[iArgs].IsByRef) { if (paramTypes[iArgs].GetElementType().IsValueType) { generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); } else generator.Emit(OpCodes.Ldind_Ref); } else { if (paramTypes[iArgs].IsValueType) generator.Emit(OpCodes.Box, paramTypes[iArgs]); } generator.Emit(OpCodes.Stelem_Ref); if (paramTypes[iArgs].IsByRef) { generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Ldc_I4, iOutArgs); generator.Emit(OpCodes.Ldc_I4, iArgs); generator.Emit(OpCodes.Stelem_I4); refArgs[iOutArgs] = iArgs; iOutArgs++; } if (paramInfo[iArgs].IsIn || (!paramInfo[iArgs].IsOut)) { generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldc_I4, iInArgs); generator.Emit(OpCodes.Ldarg, iArgs + 1); if (paramTypes[iArgs].IsByRef) { if (paramTypes[iArgs].GetElementType().IsValueType) { generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); } else generator.Emit(OpCodes.Ldind_Ref); } else { if (paramTypes[iArgs].IsValueType) generator.Emit(OpCodes.Box, paramTypes[iArgs]); } generator.Emit(OpCodes.Stelem_Ref); iInArgs++; } } // Calls the callFunction method of the base class generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldloc_2); var miGenericEventHandler = delegateParent.GetMethod("CallFunction"); generator.Emit(OpCodes.Call, miGenericEventHandler); // Stores return value if (returnType == typeof(void)) { generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ldnull); } else if (returnType.IsValueType) { generator.Emit(OpCodes.Unbox, returnType); generator.Emit(OpCodes.Ldobj, returnType); } else generator.Emit(OpCodes.Castclass, returnType); generator.Emit(OpCodes.Stloc_3); // Stores new value of out and ref params for (int i = 0; i < refArgs.Length; i++) { generator.Emit(OpCodes.Ldarg, refArgs[i] + 1); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4, refArgs[i]); generator.Emit(OpCodes.Ldelem_Ref); if (paramTypes[refArgs[i]].GetElementType().IsValueType) { generator.Emit(OpCodes.Unbox, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Ldobj, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Stobj, paramTypes[refArgs[i]].GetElementType()); } else { generator.Emit(OpCodes.Castclass, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Stind_Ref); } } // Returns if (!(returnType == typeof(void))) generator.Emit(OpCodes.Ldloc_3); generator.Emit(OpCodes.Ret); return myType.CreateType(); // creates the new type #endif } void GetReturnTypesFromClass(Type klass, out Type[][] returnTypes) { var classMethods = klass.GetMethods(); returnTypes = new Type[classMethods.Length][]; int i = 0; foreach (var method in classMethods) { if (klass.IsInterface) { GetReturnTypesFromMethod(method, out returnTypes[i]); i++; } else if (!method.IsPrivate && !method.IsFinal && method.IsVirtual) { GetReturnTypesFromMethod(method, out returnTypes[i]); i++; } } } /* * Generates an implementation of klass, if it is an interface, or * a subclass of klass that delegates its virtual methods to a Lua table. */ public void GenerateClass(Type klass, out Type newType, out Type[][] returnTypes) { #if NETSTANDARD || WINDOWS_UWP throw new NotImplementedException (" Emit not available on .NET Standard "); #else string typeName; lock (this) { typeName = "LuaGeneratedClass" + luaClassNumber; luaClassNumber++; } TypeBuilder myType; // Define a public class in the assembly, called typeName if (klass.IsInterface) myType = newModule.DefineType(typeName, TypeAttributes.Public, typeof(object), new Type[] { klass, typeof(ILuaGeneratedType) }); else myType = newModule.DefineType(typeName, TypeAttributes.Public, klass, new Type[] { typeof(ILuaGeneratedType) }); // Field that stores the Lua table var luaTableField = myType.DefineField("__luaInterface_luaTable", typeof(LuaTable), FieldAttributes.Public); // Field that stores the return types array var returnTypesField = myType.DefineField("__luaInterface_returnTypes", typeof(Type[][]), FieldAttributes.Public); // Generates the constructor for the new type, it takes a Lua table and an array // of return types and stores them in the respective fields var constructor = myType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(LuaTable), typeof(Type[][]) }); ILGenerator generator = constructor.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); if (klass.IsInterface) generator.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); else generator.Emit(OpCodes.Call, klass.GetConstructor(Type.EmptyTypes)); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Stfld, luaTableField); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_2); generator.Emit(OpCodes.Stfld, returnTypesField); generator.Emit(OpCodes.Ret); // Generates overriden versions of the klass' public virtual methods var classMethods = klass.GetMethods(); returnTypes = new Type[classMethods.Length][]; int i = 0; foreach (var method in classMethods) { if (klass.IsInterface) { GenerateMethod(myType, method, MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, i, luaTableField, returnTypesField, false, out returnTypes[i]); i++; } else { if (!method.IsPrivate && !method.IsFinal && method.IsVirtual) { GenerateMethod(myType, method, (method.Attributes | MethodAttributes.NewSlot) ^ MethodAttributes.NewSlot, i, luaTableField, returnTypesField, true, out returnTypes[i]); i++; } } } // Generates an implementation of the luaInterfaceGetLuaTable method var returnTableMethod = myType.DefineMethod("LuaInterfaceGetLuaTable", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(LuaTable), new Type[0]); myType.DefineMethodOverride(returnTableMethod, typeof(ILuaGeneratedType).GetMethod("LuaInterfaceGetLuaTable")); generator = returnTableMethod.GetILGenerator(); generator.Emit(OpCodes.Ldfld, luaTableField); generator.Emit(OpCodes.Ret); newType = myType.CreateType(); // Creates the type #endif } void GetReturnTypesFromMethod(MethodInfo method, out Type[] returnTypes) { var paramInfo = method.GetParameters(); var paramTypes = new Type[paramInfo.Length]; var returnTypesList = new List(); // Counts out and ref parameters, for later use, // and creates the list of return types int nOutParams = 0; int nOutAndRefParams = 0; var returnType = method.ReturnType; returnTypesList.Add(returnType); for (int i = 0; i < paramTypes.Length; i++) { paramTypes[i] = paramInfo[i].ParameterType; if (!paramInfo[i].IsIn && paramInfo[i].IsOut) { nOutParams++; } if (paramTypes[i].IsByRef) { returnTypesList.Add(paramTypes[i].GetElementType()); nOutAndRefParams++; } } returnTypes = returnTypesList.ToArray(); } #if !NETSTANDARD && !WINDOWS_UWP /* * Generates an overriden implementation of method inside myType that delegates * to a function in a Lua table with the same name, if the function exists. If it * doesn't the method calls the base method (or does nothing, in case of interface * implementations). */ private void GenerateMethod(TypeBuilder myType, MethodInfo method, MethodAttributes attributes, int methodIndex, FieldInfo luaTableField, FieldInfo returnTypesField, bool generateBase, out Type[] returnTypes) { var paramInfo = method.GetParameters(); var paramTypes = new Type[paramInfo.Length]; var returnTypesList = new List(); // Counts out and ref parameters, for later use, // and creates the list of return types int nOutParams = 0; int nOutAndRefParams = 0; var returnType = method.ReturnType; returnTypesList.Add(returnType); for (int i = 0; i < paramTypes.Length; i++) { paramTypes[i] = paramInfo[i].ParameterType; if (!paramInfo[i].IsIn && paramInfo[i].IsOut) nOutParams++; if (paramTypes[i].IsByRef) { returnTypesList.Add(paramTypes[i].GetElementType()); nOutAndRefParams++; } } int[] refArgs = new int[nOutAndRefParams]; returnTypes = returnTypesList.ToArray(); // Generates a version of the method that calls the base implementation // directly, for use by the base field of the table if (generateBase) { var baseMethod = myType.DefineMethod("__luaInterface_base_" + method.Name, MethodAttributes.Private | MethodAttributes.NewSlot | MethodAttributes.HideBySig, returnType, paramTypes); ILGenerator generatorBase = baseMethod.GetILGenerator(); generatorBase.Emit(OpCodes.Ldarg_0); for (int i = 0; i < paramTypes.Length; i++) generatorBase.Emit(OpCodes.Ldarg, i + 1); generatorBase.Emit(OpCodes.Call, method); if (returnType == typeof(void)) generatorBase.Emit(OpCodes.Pop); generatorBase.Emit(OpCodes.Ret); } // Defines the method var methodImpl = myType.DefineMethod(method.Name, attributes, returnType, paramTypes); // If it's an implementation of an interface tells what method it // is overriding if (myType.BaseType.Equals(typeof(object))) myType.DefineMethodOverride(methodImpl, method); ILGenerator generator = methodImpl.GetILGenerator(); generator.DeclareLocal(typeof(object[])); // original arguments generator.DeclareLocal(typeof(object[])); // with out-only arguments removed generator.DeclareLocal(typeof(int[])); // indexes of out and ref arguments if (!(returnType == typeof(void))) // return value generator.DeclareLocal(returnType); else generator.DeclareLocal(typeof(object)); // Initializes local variables generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); generator.Emit(OpCodes.Newarr, typeof(object)); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldc_I4, paramTypes.Length - nOutParams + 1); generator.Emit(OpCodes.Newarr, typeof(object)); generator.Emit(OpCodes.Stloc_1); generator.Emit(OpCodes.Ldc_I4, nOutAndRefParams); generator.Emit(OpCodes.Newarr, typeof(int)); generator.Emit(OpCodes.Stloc_2); generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, luaTableField); generator.Emit(OpCodes.Stelem_Ref); // Stores the arguments into the local variables, as needed for (int iArgs = 0, iInArgs = 1, iOutArgs = 0; iArgs < paramTypes.Length; iArgs++) { generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4, iArgs); generator.Emit(OpCodes.Ldarg, iArgs + 1); if (paramTypes[iArgs].IsByRef) { if (paramTypes[iArgs].GetElementType().IsValueType) { generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); } else generator.Emit(OpCodes.Ldind_Ref); } else { if (paramTypes[iArgs].IsValueType) generator.Emit(OpCodes.Box, paramTypes[iArgs]); } generator.Emit(OpCodes.Stelem_Ref); if (paramTypes[iArgs].IsByRef) { generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Ldc_I4, iOutArgs); generator.Emit(OpCodes.Ldc_I4, iArgs); generator.Emit(OpCodes.Stelem_I4); refArgs[iOutArgs] = iArgs; iOutArgs++; } if (paramInfo[iArgs].IsIn || (!paramInfo[iArgs].IsOut)) { generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldc_I4, iInArgs); generator.Emit(OpCodes.Ldarg, iArgs + 1); if (paramTypes[iArgs].IsByRef) { if (paramTypes[iArgs].GetElementType().IsValueType) { generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); } else generator.Emit(OpCodes.Ldind_Ref); } else { if (paramTypes[iArgs].IsValueType) generator.Emit(OpCodes.Box, paramTypes[iArgs]); } generator.Emit(OpCodes.Stelem_Ref); iInArgs++; } } // Gets the function the method will delegate to by calling // the getTableFunction method of class LuaClassHelper generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, luaTableField); generator.Emit(OpCodes.Ldstr, method.Name); generator.Emit(OpCodes.Call, classHelper.GetMethod("GetTableFunction")); var lab1 = generator.DefineLabel(); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Brtrue_S, lab1); // Function does not exist, call base method generator.Emit(OpCodes.Pop); if (!method.IsAbstract) { generator.Emit(OpCodes.Ldarg_0); for (int i = 0; i < paramTypes.Length; i++) generator.Emit(OpCodes.Ldarg, i + 1); generator.Emit(OpCodes.Call, method); if (returnType == typeof(void)) generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ret); generator.Emit(OpCodes.Ldnull); } else generator.Emit(OpCodes.Ldnull); var lab2 = generator.DefineLabel(); generator.Emit(OpCodes.Br_S, lab2); generator.MarkLabel(lab1); // Function exists, call using method callFunction of LuaClassHelper generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, returnTypesField); generator.Emit(OpCodes.Ldc_I4, methodIndex); generator.Emit(OpCodes.Ldelem_Ref); generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Call, classHelper.GetMethod("CallFunction")); generator.MarkLabel(lab2); // Stores the function return value if (returnType == typeof(void)) { generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ldnull); } else if (returnType.IsValueType) { generator.Emit(OpCodes.Unbox, returnType); generator.Emit(OpCodes.Ldobj, returnType); } else generator.Emit(OpCodes.Castclass, returnType); generator.Emit(OpCodes.Stloc_3); // Sets return values of out and ref parameters for (int i = 0; i < refArgs.Length; i++) { generator.Emit(OpCodes.Ldarg, refArgs[i] + 1); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4, refArgs[i]); generator.Emit(OpCodes.Ldelem_Ref); if (paramTypes[refArgs[i]].GetElementType().IsValueType) { generator.Emit(OpCodes.Unbox, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Ldobj, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Stobj, paramTypes[refArgs[i]].GetElementType()); } else { generator.Emit(OpCodes.Castclass, paramTypes[refArgs[i]].GetElementType()); generator.Emit(OpCodes.Stind_Ref); } } // Returns if (!(returnType == typeof(void))) generator.Emit(OpCodes.Ldloc_3); generator.Emit(OpCodes.Ret); } #endif /* * Gets an event handler for the event type that delegates to the eventHandler Lua function. * Caches the generated type. */ public LuaEventHandler GetEvent(Type eventHandlerType, LuaFunction eventHandler) { #if NETSTANDARD || WINDOWS_UWP throw new NotImplementedException (" Emit not available on .NET Standard "); #else Type eventConsumerType; if (eventHandlerCollection.ContainsKey(eventHandlerType)) eventConsumerType = eventHandlerCollection[eventHandlerType]; else { eventConsumerType = GenerateEvent(eventHandlerType); eventHandlerCollection[eventHandlerType] = eventConsumerType; } var luaEventHandler = (LuaEventHandler)Activator.CreateInstance(eventConsumerType); luaEventHandler.Handler = eventHandler; return luaEventHandler; #endif } public void RegisterLuaDelegateType(Type delegateType, Type luaDelegateType) { _delegateCollection[delegateType] = luaDelegateType; } public void RegisterLuaClassType(Type klass, Type luaClass) { var luaClassType = new LuaClassType(); luaClassType.klass = luaClass; GetReturnTypesFromClass(klass, out luaClassType.returnTypes); _classCollection[klass] = luaClassType; } /* * Gets a delegate with delegateType that calls the luaFunc Lua function * Caches the generated type. */ public Delegate GetDelegate(Type delegateType, LuaFunction luaFunc) { var returnTypes = new List(); Type luaDelegateType; if (_delegateCollection.ContainsKey(delegateType)) { luaDelegateType = _delegateCollection[delegateType]; } else { luaDelegateType = GenerateDelegate(delegateType); _delegateCollection[delegateType] = luaDelegateType; } var methodInfo = delegateType.GetMethod("Invoke"); returnTypes.Add(methodInfo.ReturnType); foreach (ParameterInfo paramInfo in methodInfo.GetParameters()) { if (paramInfo.ParameterType.IsByRef) returnTypes.Add(paramInfo.ParameterType); } var luaDelegate = (LuaDelegate)Activator.CreateInstance(luaDelegateType); luaDelegate.Function = luaFunc; luaDelegate.ReturnTypes = returnTypes.ToArray(); return Delegate.CreateDelegate(delegateType, luaDelegate, "CallFunction"); } /* * Gets an instance of an implementation of the klass interface or * subclass of klass that delegates public virtual methods to the * luaTable table. * Caches the generated type. */ public object GetClassInstance(Type klass, LuaTable luaTable) { LuaClassType luaClassType; if (_classCollection.ContainsKey(klass)) luaClassType = _classCollection[klass]; else { luaClassType = new LuaClassType(); GenerateClass(klass, out luaClassType.klass, out luaClassType.returnTypes); _classCollection[klass] = luaClassType; } return Activator.CreateInstance(luaClassType.klass, new object[] { luaTable, luaClassType.returnTypes }); } } }