From b4c167b56228b86eb459f0dd049d4d5c537f5724 Mon Sep 17 00:00:00 2001
From: Joao Matos <joao@tritao.eu>
Date: Sat, 20 Feb 2021 23:24:30 +0000
Subject: [PATCH] Rework core implementation of QuickJS generator.

This adds more accurate overloading, default arguments handling and
events.
---
 src/AST/Event.cs                              |   2 +
 .../Generators/C/CppDefaultValuePrinter.cs    |  58 ++
 .../Generators/QuickJS/QuickJSHeaders.cs      |   5 +
 .../Generators/QuickJS/QuickJSSources.cs      | 705 +++++++++++++++++-
 .../Generators/QuickJS/QuickJSTypeCheckGen.cs | 129 ++++
 5 files changed, 872 insertions(+), 27 deletions(-)
 create mode 100644 src/Generator/Generators/C/CppDefaultValuePrinter.cs
 create mode 100644 src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs

diff --git a/src/AST/Event.cs b/src/AST/Event.cs
index 03e32ea3..28b63832 100644
--- a/src/AST/Event.cs
+++ b/src/AST/Event.cs
@@ -15,5 +15,7 @@ namespace CppSharp.AST
         public List<Parameter> Parameters { get; } = new List<Parameter>();
 
         public Declaration OriginalDeclaration { get; set; }
+
+        public int GlobalId { get; set; }
     }
 }
diff --git a/src/Generator/Generators/C/CppDefaultValuePrinter.cs b/src/Generator/Generators/C/CppDefaultValuePrinter.cs
new file mode 100644
index 00000000..6987d434
--- /dev/null
+++ b/src/Generator/Generators/C/CppDefaultValuePrinter.cs
@@ -0,0 +1,58 @@
+
+using System;
+using CppSharp.AST;
+
+namespace CppSharp.Generators.C
+{
+    public class CppDefaultValuePrinter : CppTypePrinter
+    {
+        public CppDefaultValuePrinter(BindingContext context) : base(context)
+        {
+        }
+
+        public override TypePrinterResult VisitBuiltinType(BuiltinType builtin,
+            TypeQualifiers quals)
+        {
+            return VisitPrimitiveType(builtin.Type);
+        }
+
+        public override TypePrinterResult VisitPrimitiveType(PrimitiveType type)
+        {
+            switch (type)
+            {
+                case PrimitiveType.Bool:
+                    return PrintFlavorKind == CppTypePrintFlavorKind.Cpp ? "false" : "0";
+                case PrimitiveType.WideChar:
+                case PrimitiveType.Char:
+                case PrimitiveType.SChar:
+                case PrimitiveType.UChar:
+                case PrimitiveType.Char16:
+                case PrimitiveType.Char32:
+                case PrimitiveType.Short:
+                case PrimitiveType.UShort:
+                case PrimitiveType.Int:
+                case PrimitiveType.UInt:
+                case PrimitiveType.Long:
+                case PrimitiveType.ULong:
+                case PrimitiveType.LongLong:
+                case PrimitiveType.ULongLong:
+                case PrimitiveType.Int128:
+                case PrimitiveType.UInt128:
+                case PrimitiveType.Half:
+                case PrimitiveType.Float:
+                case PrimitiveType.Double:
+                case PrimitiveType.LongDouble:
+                case PrimitiveType.Float128:
+                case PrimitiveType.Decimal:
+                    return "0";
+                case PrimitiveType.Null:
+                case PrimitiveType.IntPtr:
+                case PrimitiveType.UIntPtr:
+                case PrimitiveType.String:
+                    return PrintFlavorKind == CppTypePrintFlavorKind.Cpp ? "nullptr" : "0";
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(type), type, null);
+            }
+        }
+    }
+}
diff --git a/src/Generator/Generators/QuickJS/QuickJSHeaders.cs b/src/Generator/Generators/QuickJS/QuickJSHeaders.cs
index e5038ab7..aab93000 100644
--- a/src/Generator/Generators/QuickJS/QuickJSHeaders.cs
+++ b/src/Generator/Generators/QuickJS/QuickJSHeaders.cs
@@ -52,5 +52,10 @@ namespace CppSharp.Generators.Cpp
         {
             return true;
         }
+
+        public override bool VisitFieldDecl(Field field)
+        {
+            return true;
+        }
     }
 }
diff --git a/src/Generator/Generators/QuickJS/QuickJSSources.cs b/src/Generator/Generators/QuickJS/QuickJSSources.cs
index 2b184795..9c10cf03 100644
--- a/src/Generator/Generators/QuickJS/QuickJSSources.cs
+++ b/src/Generator/Generators/QuickJS/QuickJSSources.cs
@@ -1,15 +1,33 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using CppSharp.AST;
 using CppSharp.AST.Extensions;
+using CppSharp.Generators.AST;
 using CppSharp.Generators.C;
 using static CppSharp.Generators.Cpp.NAPISources;
-using static CppSharp.Generators.Cpp.NAPIInvokes;
+using Type = CppSharp.AST.Type;
 
 namespace CppSharp.Generators.Cpp
 {
+    public class QuickJSCodeGenerator : NAPICodeGenerator
+    {
+        public QuickJSCodeGenerator(BindingContext context, IEnumerable<TranslationUnit> units)
+            : base(context, units)
+        {
+        }
+
+        public override MarshalPrinter<MarshalContext> GetMarshalManagedToNativePrinter(MarshalContext ctx)
+        {
+            return new QuickJSMarshalManagedToNativePrinter(ctx);
+        }
+
+        public override MarshalPrinter<MarshalContext> GetMarshalNativeToManagedPrinter(MarshalContext ctx)
+        {
+            return new QuickJSMarshalNativeToManagedPrinter(ctx);
+        }
+    }
+
     /// <summary>
     /// Generates QuickJS C/C++ source files.
     /// QuickJS documentation: https://bellard.org/quickjs/
@@ -42,6 +60,8 @@ namespace CppSharp.Generators.Cpp
             WriteLine("extern \"C\" {");
             NewLine();
 
+            GenerateExternClassIds();
+
             var registerGen = new QuickJSRegister(Context, TranslationUnits);
             registerGen.Process();
             WriteLine(registerGen.Generate());
@@ -49,16 +69,88 @@ namespace CppSharp.Generators.Cpp
             PushBlock();
             {
                 var name = GetTranslationUnitName(TranslationUnit);
-                WriteLine($"void register_{name}(JSContext *ctx, JSModuleDef *m, bool set)");
+                WriteLine($"void register_{name}(JSContext *ctx, JSModuleDef *m, bool set, int phase)");
                 WriteOpenBraceAndIndent();
 
-                TranslationUnit.Visit(this);
+                Block registerBlock;
+                PushBlock();
+                {
+                    WriteLine("if (phase == 0)");
+
+                    WriteOpenBraceAndIndent();
+                    {
+                        PushBlock();
+                        VisitOptions.ClearFlags(VisitFlags.NamespaceClasses);
+                        TranslationUnit.Visit(this);
+                        registerBlock = PopBlock();
+
+                    }
+                    UnindentAndWriteCloseBrace();
+                }
+                var phase0Block = PopBlock(NewLineKind.IfNotEmpty);
+                phase0Block.CheckGenerate = () => !registerBlock.IsEmpty;
+
+                PushBlock();
+                {
+                    VisitOptions.ResetFlags(VisitFlags.Default);
+                    VisitOptions.SetFlags(VisitFlags.NamespaceClasses);
+                    TranslationUnit.Visit(this);
+                }
+                PopBlock(NewLineKind.IfNotEmpty);
 
                 UnindentAndWriteCloseBrace();
             }
             PopBlock(NewLineKind.BeforeNextBlock);
+            NewLine();
+
+            WriteLine("} // extern \"C\"");
+        }
+
+        public static string SignalClassId = "classId__Signal";
+
+        private void GenerateExternClassIds()
+        {
+            PushBlock();
+            {
+                var classCollector = new RecordCollector(TranslationUnit);
+                TranslationUnit.Visit(classCollector);
+
+                var externClasses = new HashSet<Class>();
+                foreach (var record in classCollector.Declarations)
+                {
+                    var @event = record.Value as Event;
+                    Class @class;
+                    if (@event != null)
+                    {
+                        foreach (var param in @event.Parameters)
+                        {
+                            if (!param.Type.GetFinalPointee().TryGetClass(out @class))
+                                continue;
+
+                            externClasses.Add(@class);
+                        }
+                        continue;
+                    }
+
+                    @class = record.Value as Class;
+                    if (@class == null || !@class.IsGenerated)
+                        continue;
+
+                    externClasses.Add(@class);
+                }
+
+                foreach (var @class in externClasses)
+                {
+                    WriteLine($"extern JSClassID classId_{GetCIdentifier(Context, @class)};");
+                }
+            }
 
-            WriteLine("}");
+            // If any of the classes have events, then also generate an external for Signal.
+            // TODO: Only generate if necessary and fix possible id conflict.
+            WriteLine($"extern JSClassID {SignalClassId};");
+
+
+            PopBlock(NewLineKind.BeforeNextBlock);
         }
 
         public override bool VisitClassDecl(Class @class)
@@ -67,7 +159,7 @@ namespace CppSharp.Generators.Cpp
                 return true;
 
             PushBlock();
-            WriteLine($"register_class_{GetCIdentifier(Context, @class)}(ctx, m, set);");
+            WriteLine($"register_class_{GetCIdentifier(Context, @class)}(ctx, m, set, phase);");
             PopBlock(NewLineKind.BeforeNextBlock);
             return true;
         }
@@ -93,6 +185,59 @@ namespace CppSharp.Generators.Cpp
             PopBlock(NewLineKind.BeforeNextBlock);
             return true;
         }
+
+        public override bool VisitTypedefDecl(TypedefDecl typedef)
+        {
+            return true;
+        }
+    }
+
+    public class QuickJSClassFuncDef : NAPICodeGenerator
+    {
+        public QuickJSClassFuncDef(BindingContext context) : base(context, null)
+        {
+        }
+
+        public override void VisitClassConstructors(Class @class)
+        {
+        }
+
+        public override bool VisitEnumDecl(Enumeration @enum)
+        {
+            return true;
+        }
+
+        public override void GenerateMethodGroup(List<Method> @group)
+        {
+            GenerateFunctionGroup(@group.OfType<Function>().ToList());
+        }
+
+        public override void GenerateFunctionGroup(List<Function> @group)
+        {
+            var function = @group.FirstOrDefault();
+            var maxArgs = @group.Max(f => f.Parameters.Count);
+            var type = function is Method ? "method" : "function";
+            var callbackId = $"callback_{type}_{GetCIdentifier(Context, function)}";
+            WriteLine($"JS_CFUNC_DEF(\"{function.Name}\", {maxArgs}, {callbackId}),");
+        }
+
+        public override bool VisitEvent(Event @event)
+        {
+            var getterId = $"callback_event_getter_{GetCIdentifier(Context, @event)}";
+            WriteLine($"JS_CGETSET_DEF(\"{@event.Name}\", {getterId}, NULL),");
+            return true;
+        }
+
+        public override bool VisitFunctionTemplateDecl(FunctionTemplate template)
+        {
+            return true;
+        }
+
+        public void GenerateToString(Class @class)
+        {
+            var callbackId = $"callback_class_{GetCIdentifier(Context, @class)}_toString";
+            WriteLine($"JS_CFUNC_DEF(\"toString\", 0, {callbackId}),");
+        }
     }
 
     public class QuickJSRegister : NAPIRegister
@@ -102,33 +247,110 @@ namespace CppSharp.Generators.Cpp
         {
         }
 
+        public override MarshalPrinter<MarshalContext> GetMarshalManagedToNativePrinter(MarshalContext ctx)
+        {
+            return new QuickJSMarshalManagedToNativePrinter(ctx);
+        }
+
+        public override MarshalPrinter<MarshalContext> GetMarshalNativeToManagedPrinter(MarshalContext ctx)
+        {
+            return new QuickJSMarshalNativeToManagedPrinter(ctx);
+        }
+
         public override bool VisitClassDecl(Class @class)
         {
             if (@class.IsIncomplete)
                 return true;
 
-/*
+            if (!VisitDeclContext(@class))
+                return true;
+
             PushBlock(BlockKind.InternalsClass, @class);
             {
-                var callbacks = new QuickJSInvokes(Context);
-                @class.Visit(callbacks);
-                Write(callbacks.Generate());
+                WriteLine($"JSClassID classId_{GetCIdentifier(Context, @class)};");
+                NewLine();
+
+                GenerateClassExtraData(@class);
+
+                PushBlock();
+                {
+                    var callbacks = new QuickJSInvokes(Context);
+                    @class.Visit(callbacks);
+                    callbacks.GenerateToString(@class);
+                    Write(callbacks.Generate());
+                }
+                PopBlock(NewLineKind.BeforeNextBlock);
+
+                var finalizerId = $"finalizer_{GetCIdentifier(Context, @class)}";
+                PushBlock();
+                {
+                    WriteLine($"void {finalizerId}(JSRuntime *rt, JSValue val)");
+                    WriteOpenBraceAndIndent();
+
+                    //WriteLine($"printf(\"Calling finalizer for {@class.QualifiedOriginalName}\\n\");");
+
+                    if(ClassNeedsExtraData(@class))
+                    {
+                        // Remove the event connection from the delegate.
+                        // var invokeId = $"event_invoke_{@event.OriginalName}";
+                        // WriteLine($"data->instance->{@event.OriginalName}.bind(data, &{classDataId}::{invokeId});");
+                        // NewLine();
+
+                        var instanceKind = "JS_INTEROP_INSTANCE_SIGNAL_CONTEXT";
+                        WriteLine($"JS_Interop_CleanupObject(val, {instanceKind});");
+                    }
+                    else
+                    {
+                        Write($"{@class.QualifiedOriginalName}* instance = ");
+                        WriteLine($"({@class.QualifiedOriginalName}*) JS_GetOpaque(val, 0);");
+                    }
+
+                    UnindentAndWriteCloseBrace();
+                }
+                PopBlock(NewLineKind.BeforeNextBlock);
+
+                PushBlock();
+                {
+                    WriteLine($"static JSClassDef classDef_{GetCIdentifier(Context, @class)}");
+                    WriteOpenBraceAndIndent();
+
+                    WriteLine($"\"{@class.Name}\",");
+                    WriteLine($".finalizer = {finalizerId}");
+
+                    Unindent();
+                    WriteLine("};");
+                }
+                PopBlock(NewLineKind.BeforeNextBlock);
+
+                PushBlock();
+                {
+                    WriteLine($"static JSCFunctionListEntry funcDef_{GetCIdentifier(Context, @class)}[]");
+                    WriteOpenBraceAndIndent();
+
+                    var funcGen = new QuickJSClassFuncDef(Context);
+                    funcGen.Indent(CurrentIndentation);
+                    funcGen.VisitClassDeclContext(@class);
+                    funcGen.GenerateToString(@class);
+
+                    Write(funcGen.Generate());
+
+                    Unindent();
+                    WriteLine("};");
+                }
+                PopBlock(NewLineKind.BeforeNextBlock);
             }
             PopBlock(NewLineKind.BeforeNextBlock);
-*/
 
             PushBlock(BlockKind.Class, @class);
             {
                 Write($"static void register_class_{GetCIdentifier(Context, @class)}");
-                WriteLine("(JSContext *ctx, JSModuleDef *m, bool set)");
+                WriteLine("(JSContext *ctx, JSModuleDef *m, bool set, int phase)");
                 WriteOpenBraceAndIndent();
 
-/*
-                var sources = new NAPIRegisterImpl(Context);
+                var sources = new QuickJSRegisterImpl(Context);
                 sources.Indent(CurrentIndentation);
                 @class.Visit(sources);
                 Write(sources.Generate());
-*/
 
                 UnindentAndWriteCloseBrace();
             }
@@ -137,6 +359,128 @@ namespace CppSharp.Generators.Cpp
             return false;
         }
 
+        public void GenerateClassExtraData(Class @class)
+        {
+            if (!ClassNeedsExtraData(@class))
+                return;
+
+            PushBlock();
+            {
+                var classDataId = $"data_{GetCIdentifier(Context, @class)}";
+                WriteLine($"struct {classDataId} : public JS_Interop_ClassData");
+                WriteOpenBraceAndIndent();
+
+                //WriteLine($"{@class.QualifiedOriginalName}* instance;");
+
+                foreach (var @event in @class.Events)
+                {
+                    GenerateEventInvoke(@event);
+                }
+
+                Unindent();
+                WriteLine("};");
+            }
+            PopBlock(NewLineKind.BeforeNextBlock);
+        }
+
+        private void GenerateEventInvoke(Event @event)
+        {
+            var invokeId = $"event_invoke_{@event.OriginalName}";
+            PushBlock();
+            {
+                CTypePrinter.PushContext(TypePrinterContextKind.Native);
+                var functionType = @event.Type as FunctionType;
+                var retType = functionType.ReturnType.Visit(CTypePrinter);
+                CTypePrinter.PopContext();
+
+                Write($"{retType} {invokeId}(");
+
+                var @params = new List<string>();
+                foreach (var param in @event.Parameters)
+                {
+                    CTypePrinter.PushContext(TypePrinterContextKind.Native);
+                    var paramType = param.Type.Visit(CTypePrinter);
+                    CTypePrinter.PopContext();
+
+                    @params.Add($"{paramType} {param.Name}");
+                }
+
+                Write(string.Join(", ", @params));
+                WriteLine(")");
+                WriteOpenBraceAndIndent();
+
+                WriteLine($"JSValue event = JS_Interop_FindEvent(&events, {@event.GlobalId});");
+                WriteLine($"if (JS_IsUndefined(event))");
+
+                var isVoidReturn = functionType.ReturnType.Type.IsPrimitiveType(PrimitiveType.Void);
+                if (isVoidReturn)
+                {
+                    WriteLineIndent($"return;");
+                }
+                else
+                {
+                    var defaultValuePrinter = new CppDefaultValuePrinter(Context);
+                    var defaultValue = functionType.ReturnType.Visit(defaultValuePrinter);
+                    WriteLineIndent($"return {defaultValue};");
+                }
+                NewLine();
+
+                // Marshal the arguments.
+                var marshalers = new List<MarshalPrinter<MarshalContext>>();
+                foreach (var param in @event.Parameters)
+                {
+                    var ctx = new MarshalContext(Context, CurrentIndentation)
+                    {
+                        ArgName = param.Name,
+                        ReturnVarName = param.Name,
+                        ReturnType = param.QualifiedType,
+                        Parameter = param
+                    };
+
+                    var marshal = GetMarshalNativeToManagedPrinter(ctx);
+                    marshalers.Add(marshal);
+
+                    param.Visit(marshal);
+
+                    if (!string.IsNullOrWhiteSpace(marshal.Context.Before))
+                    {
+                        Write(marshal.Context.Before);
+                    }
+                }
+
+                var args = marshalers.Select(m => m.Context.Return.ToString());
+                WriteLine($"JSValueConst argv[] = {{ { string.Join(", ", args)} }};");
+                WriteLine($"auto data = (JS_SignalContext*) JS_GetOpaque(event, 0);");
+                WriteLine($"JSValue ret = JS_Call(ctx, data->function, JS_UNDEFINED, {@event.Parameters.Count}, argv);");
+                WriteLine($"JS_FreeValue(ctx, ret);");
+
+                //WriteLine($"{@class.QualifiedOriginalName}* instance = data->instance;");
+
+/*
+
+                if (!isVoidReturn)
+                {
+                    CTypePrinter.PushContext(TypePrinterContextKind.Native);
+                    var returnType = function.ReturnType.Visit(CTypePrinter);
+                    CTypePrinter.PopContext();
+
+                    Write($"{returnType} {Helpers.ReturnIdentifier} = ");
+                }
+
+                var @class = function.Namespace as Class;
+*/
+
+                UnindentAndWriteCloseBrace();
+            }
+            PopBlock(NewLineKind.BeforeNextBlock);
+        }
+
+        public static bool ClassNeedsExtraData(Class @class)
+        {
+            return @class.Events.Any() ||
+                   @class.Bases.Any(b => b.IsClass && ClassNeedsExtraData(b.Class));
+        }
+
         public override void GenerateFunctionGroup(List<Function> group)
         {
             var function = group.First();
@@ -213,6 +557,11 @@ namespace CppSharp.Generators.Cpp
 
             return true;
         }
+
+        public override bool VisitFunctionTemplateDecl(FunctionTemplate template)
+        {
+            return true;
+        }
     }
 
     public class QuickJSRegisterImpl : QuickJSInvokes
@@ -224,6 +573,70 @@ namespace CppSharp.Generators.Cpp
 
         public override bool VisitClassDecl(Class @class)
         {
+            var ctors = @class.Constructors.Where(c => c.IsGenerated).ToArray();
+            var ctor = ctors.FirstOrDefault();
+            if (ctor == null)
+                return true;
+
+            var funcDef = $"funcDef_{GetCIdentifier(Context, @class)}";
+            var classId = $"classId_{GetCIdentifier(Context, @class)}";
+            var classDef = $"classDef_{GetCIdentifier(Context, @class)}";
+
+            WriteLine("if (!set)");
+            WriteOpenBraceAndIndent();
+            {
+                WriteLine($"JS_AddModuleExport(ctx, m, \"{ctor.Name}\");");
+                WriteLine("return;");
+            }
+            UnindentAndWriteCloseBrace();
+            NewLine();
+
+            WriteLine("if (phase == 0)");
+            WriteOpenBraceAndIndent();
+            {
+                WriteLine($"JS_NewClassID(&{classId});");
+                NewLine();
+
+                WriteLine($"JS_NewClass(JS_GetRuntime(ctx), {classId}, &{classDef});");
+                NewLine();
+
+                WriteLine("JSValue proto = JS_NewObject(ctx);");
+                WriteLine(
+                    $"JS_SetPropertyFunctionList(ctx, proto, {funcDef}, sizeof({funcDef}) / sizeof({funcDef}[0]));");
+                WriteLine($"JS_SetClassProto(ctx, {classId}, proto);");
+                NewLine();
+
+                if (ctor.IsGenerated && !ctor.IsCopyConstructor)
+                {
+                    var maxArgs = ctors.Max(m => m.Parameters.Count);
+                    var callbackId = $"callback_method_{GetCIdentifier(Context, ctor)}";
+
+                    Write($"JSValue ctor = JS_NewCFunction2(ctx,");
+                    WriteLine($" {callbackId}, \"{ctor.Name}\", {maxArgs}, JS_CFUNC_constructor, 0);");
+                    WriteLine($"JS_SetConstructor(ctx, ctor, proto);");
+                    NewLine();
+
+                    WriteLine($"JS_SetModuleExport(ctx, m, \"{ctor.Name}\", ctor);");
+                }
+            }
+            UnindentAndWriteCloseBrace();
+
+            if (@class.HasBaseClass && @class.BaseClass.IsGenerated)
+            {
+                WriteLine("else if (phase == 1)");
+                WriteOpenBraceAndIndent();
+                {
+                    var baseClassId = $"classId_{GetCIdentifier(Context, @class.BaseClass)}";
+
+                    WriteLine($"JSValue proto = JS_GetClassProto(ctx, {classId});");
+                    WriteLine($"JSValue baseProto = JS_GetClassProto(ctx, {baseClassId});");
+                    WriteLine("int err = JS_SetPrototype(ctx, proto, baseProto);");
+                    WriteLine("assert(err != -1);");
+                    WriteLine("JS_FreeValue(ctx, baseProto);");
+                    WriteLine("JS_FreeValue(ctx, proto);");
+                }
+                UnindentAndWriteCloseBrace();
+            }
 
             return true;
         }
@@ -263,9 +676,10 @@ namespace CppSharp.Generators.Cpp
                 WriteLine("// " + item.Name);
                 WriteOpenBraceAndIndent();
                 {
+                    var @enum = item.Namespace as Enumeration;
                     var ctx = new MarshalContext(Context, CurrentIndentation)
                     {
-                        ArgName = item.Value.ToString(),
+                        ArgName = @enum.GetItemValueAsString(item),
                         ReturnVarName = "item"
                     };
 
@@ -287,6 +701,12 @@ namespace CppSharp.Generators.Cpp
         {
             return true;
         }
+
+        public override bool VisitEvent(Event @event)
+        {
+            Console.WriteLine(@event.QualifiedName);
+            return base.VisitEvent(@event);
+        }
     }
 
     public class QuickJSInvokes : NAPIInvokes
@@ -313,34 +733,265 @@ namespace CppSharp.Generators.Cpp
 
         public override void GenerateFunctionGroup(List<Function> @group)
         {
-            GenerateFunctionCallback(@group);
+            PushBlock(BlockKind.Function, @group);
+            {
+                GenerateFunctionCallback(@group);
+            }
+            PopBlock(NewLineKind.BeforeNextBlock);
         }
 
         public override void GenerateMethodGroup(List<Method> @group)
         {
-            GenerateFunctionCallback(@group.OfType<Function>().ToList());
+            PushBlock(BlockKind.Method, @group);
+            {
+                GenerateFunctionCallback(@group.OfType<Function>().ToList());
+            }
+            PopBlock(NewLineKind.BeforeNextBlock);
         }
-        public override void GenerateFunctionCallback(List<Function> @group)
-        {
-            var function = @group.First();
 
+        private void GenerateFunctionSignature(Function function)
+        {
             var type = function is Method ? "method" : "function";
             var callbackId = $"callback_{type}_{GetCIdentifier(Context, function)}";
 
-            PushBlock();
-            WriteLine($"static JSValue {callbackId}(JSContext* ctx, JSValueConst this_val,");
+            GenerateFunctionSignature(callbackId);
+        }
+
+        private void GenerateFunctionSignature(string id)
+        {
+            WriteLine($"static JSValue {id}(JSContext* ctx, JSValueConst this_val,");
             WriteLineIndent("int argc, JSValueConst* argv)");
+        }
+
+        public override void GenerateFunctionCallback(List<Function> @group)
+        {
+            var function = @group.First();
+            WriteLine($"// {function.QualifiedName}");
+
+            GenerateFunctionSignature(function);
             WriteOpenBraceAndIndent();
 
-            GenerateFunctionCall(function);
+            // Check if the arguments are in the expected range.
+            CheckArgumentsRange(@group);
+            NewLine();
+
+            var method = function as Method;
+            if (method != null && !method.IsStatic)
+            {
+                var @class = method.Namespace as Class;
+                if (method.IsConstructor)
+                {
+                    WriteLine($"{@class.QualifiedOriginalName}* instance;");
+                }
+                else if(QuickJSRegister.ClassNeedsExtraData(@class))
+                {
+                    var classDataId = $"data_{GetCIdentifier(Context, @class)}";
+                    WriteLine($"auto data = ({classDataId}*) JS_GetOpaque(this_val, 0);");
+                    WriteLine($"{@class.QualifiedOriginalName}* instance = ({@class.QualifiedOriginalName}*) data->instance;");
+                }
+                else
+                {
+                    Write($"{@class.QualifiedOriginalName}* instance = ");
+                    WriteLine($"({@class.QualifiedOriginalName}*) JS_GetOpaque(this_val, 0);");
+                }
+
+                NewLine();
+            }
+
+            // Handle the zero arguments case right away if one exists.
+            CheckZeroArguments(@group);
+            NewLineIfNeeded();
 
-            var needsReturn = !function.ReturnType.Type.IsPrimitiveType(PrimitiveType.Void);
-            if (!needsReturn)
+            var needsArguments = @group.Any(f => f.Parameters.Any(p => p.IsGenerated));
+            if (needsArguments)
             {
-                WriteLine("return JS_UNDEFINED;");
+                var stateMachine = CalculateOverloadStates(@group);
+                CheckArgumentsOverload(@group, stateMachine);
+
+                // Error state.
+                Unindent();
+                WriteLine($"error:");
+                Indent();
+
+                WriteLine("return JS_ThrowTypeError(ctx, \"Unsupported argument type\");");
+                NewLine();
+
+                GenerateOverloadCalls(@group, stateMachine);
+            }
+            else
+            {
+                GenerateFunctionCall(function);
+            }
+
+            if (method != null && method.IsConstructor)
+            {
+                NewLine();
+
+                Unindent();
+                {
+                    WriteLine("wrap:");
+                }
+                Indent();
+
+                var @class = method.Namespace as Class;
+                var classId = $"classId_{GetCIdentifier(Context, @class)}";
+
+                GenerateJSClassInstance(classId);
+                NewLine();
+
+                var instanceKind = QuickJSRegister.ClassNeedsExtraData(@class)
+                    ? "JS_INTEROP_INSTANCE_SIGNAL_CONTEXT" : "JS_INTEROP_INSTANCE_RAW_POINTER";
+                WriteLine($"JS_Interop_InitObject(ctx, __obj, {instanceKind}, instance);");
+
+                var hasExternalInstanceField = @class.Fields.Any(f => f.OriginalName == "__ExternalInstance");
+                @class.FindHierarchy(c =>
+                {
+                    hasExternalInstanceField |= c.Fields.Any(f => f.OriginalName == "__ExternalInstance");
+                    return hasExternalInstanceField;
+                });
+
+                if (hasExternalInstanceField)
+                {
+                    WriteLine("JSObject* __js_obj = JS_VALUE_GET_OBJ(__obj);");
+                    WriteLine($"instance->__ExternalInstance = (void*) __js_obj;");
+                    NewLine();
+                }
+
+                NewLine();
+                WriteLine($"return __obj;");
             }
 
             UnindentAndWriteCloseBrace();
+        }
+
+        public void GenerateJSClassInstance(string classId, bool lookupProtoFromInstance = true)
+        {
+            if (lookupProtoFromInstance)
+            {
+                WriteLine("JSValue proto;");
+                WriteLine("if (JS_IsUndefined(this_val))");
+                WriteLineIndent($"proto = JS_GetClassProto(ctx, {classId});");
+                WriteLine("else");
+                WriteLineIndent($"proto = JS_GetProperty(ctx, this_val, JS_ATOM_prototype);");
+            }
+            else
+            {
+                WriteLine($"JSValue proto = JS_GetClassProto(ctx, {classId});");
+            }
+
+            NewLine();
+
+            WriteLine("if (JS_IsException(proto))");
+            WriteLineIndent("return proto;");
+            NewLine();
+
+            WriteLine($"JSValue __obj = JS_NewObjectProtoClass(ctx, proto, {classId});");
+            WriteLine("JS_FreeValue(ctx, proto);");
+        }
+
+        public override void CheckArgumentsRange(IEnumerable<Function> @group)
+        {
+            var functions = @group as List<Function> ?? @group.ToList();
+
+            var (minArgs, maxArgs) = (functions.Min(m => m.Parameters.Count),
+                functions.Max(m => m.Parameters.Count));
+
+            var requiredArgs = functions.Min(f => f.Parameters.FindIndex(p => p.HasDefaultValue));
+            if (requiredArgs != -1)
+                minArgs = requiredArgs;
+
+            string rangeCheck;
+            if (minArgs > 0)
+                rangeCheck = minArgs == maxArgs ? $"argc != {minArgs}" : $"argc < {minArgs} || argc > {maxArgs}";
+            else
+                rangeCheck = $"argc > {maxArgs}";
+
+            WriteLine($"if ({rangeCheck})");
+            WriteLineIndent("return JS_ThrowRangeError(ctx, \"Unsupported number of arguments\");");
+        }
+
+        public override string GenerateTypeCheckForParameter(int paramIndex, Type type)
+        {
+            var typeChecker = new QuickJSTypeCheckGen(paramIndex);
+            type.Visit(typeChecker);
+
+            var condition = typeChecker.Generate();
+            if (string.IsNullOrWhiteSpace(condition))
+                throw new NotSupportedException();
+
+            return condition;
+        }
+
+        public override bool VisitEvent(Event @event)
+        {
+            var getterId = $"callback_event_getter_{GetCIdentifier(Context, @event)}";
+            PushBlock();
+            {
+                WriteLine($"JSValue {getterId}(JSContext *ctx, JSValueConst this_val)");
+                WriteOpenBraceAndIndent();
+
+                var @class = @event.Namespace as Class;
+                var classId = $"classId_{GetCIdentifier(Context, @class)}";
+                var classDataId = $"data_{GetCIdentifier(Context, @class)}";
+                WriteLine($"auto data = ({classDataId}*) JS_GetOpaque(this_val, 0);");
+
+                WriteLine($"if (data == nullptr)");
+                WriteLineIndent("return JS_ThrowTypeError(ctx, \"Could not find object instance\");");
+                NewLine();
+
+                WriteLine($"JSValue event = JS_Interop_FindEvent(&data->events, {@event.GlobalId});");
+                WriteLine($"if (!JS_IsUndefined(event))");
+                WriteLineIndent($"return JS_DupValue(ctx, event);");
+                NewLine();
+
+                //GenerateJSClassInstance(QuickJSSources.SignalClassId, lookupProtoFromInstance: false);
+
+                // Lookup Signal object constructor
+                WriteLine($"JSValue signalProto = JS_GetClassProto(ctx, {QuickJSSources.SignalClassId});");
+                WriteLine($"JSValue signalCtor = JS_GetProperty(ctx, signalProto, JS_ATOM_constructor);");
+
+                //WriteLine("JSValue argv[] = { JS_DupValue(ctx, this_val) };");
+                //WriteLine("JS_FreeValue(ctx, JS_CallConstructor2(ctx, signalCtor, __obj, 1, argv));");
+                WriteLine("JSValue argv[] = { this_val };");
+                WriteLine("JSValue __obj = JS_CallConstructor(ctx, signalCtor, 1, argv);");
+
+                WriteLine("JS_FreeValue(ctx, signalCtor);");
+                WriteLine("JS_FreeValue(ctx, signalProto);");
+                NewLine();
+
+                WriteLine($"JS_Interop_InsertEvent(&data->events, {@event.GlobalId}, JS_DupValue(ctx, __obj));");
+                //WriteLine($"data->events[{eventIndex}] = JS_DupValue(ctx, __obj);");
+                NewLine();
+
+                var invokeId = $"event_invoke_{@event.OriginalName}";
+                WriteLine($"(({@class.QualifiedOriginalName}*)data->instance)->{@event.OriginalName}.bind(data, &{classDataId}::{invokeId});");
+                NewLine();
+
+                WriteLine("return __obj;");
+                UnindentAndWriteCloseBrace();
+            }
+            PopBlock(NewLineKind.BeforeNextBlock);
+
+            return true;
+        }
+
+        public override bool VisitFunctionTemplateDecl(FunctionTemplate template)
+        {
+            return true;
+        }
+
+        public void GenerateToString(Class @class)
+        {
+            PushBlock();
+            {
+                var callbackId = $"callback_class_{GetCIdentifier(Context, @class)}_toString";
+                GenerateFunctionSignature(callbackId);
+                WriteOpenBraceAndIndent();
+
+                WriteLine($"return JS_NewString(ctx, \"{@class.Name}\");");
+
+                UnindentAndWriteCloseBrace();
+            }
             PopBlock(NewLineKind.BeforeNextBlock);
         }
     }
diff --git a/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs b/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs
new file mode 100644
index 00000000..c166c93e
--- /dev/null
+++ b/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs
@@ -0,0 +1,129 @@
+using System;
+using CppSharp.AST;
+using CppSharp.AST.Extensions;
+using CppSharp.Extensions;
+
+namespace CppSharp.Generators.Cpp
+{
+    public class QuickJSTypeCheckGen : CodeGenerator
+    {
+        private int ParameterIndex;
+
+        public override string FileExtension { get; }
+
+        public QuickJSTypeCheckGen(int parameterIndex) : base(null)
+        {
+            ParameterIndex = parameterIndex;
+        }
+
+        public override void Process()
+        {
+            throw new System.NotImplementedException();
+        }
+
+        public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals)
+        {
+            // TODO: Use TargetInfo to check the actual width of types for the target.
+
+            var condition = string.Empty;
+            var arg = $"argv[{ParameterIndex}]";
+            switch (primitive)
+            {
+            case PrimitiveType.Bool:
+                condition = $"JS_IsBool({arg})";
+                break;
+            case PrimitiveType.Char:
+            case PrimitiveType.SChar:
+                condition = $"JS_IsInt8({arg})";
+                break;
+            case PrimitiveType.UChar:
+                condition = $"JS_IsUInt8({arg})";
+                break;
+            case PrimitiveType.WideChar:
+                condition = $"JS_IsUInt16({arg})";
+                break;
+            case PrimitiveType.Short:
+                condition = $"JS_IsInt16({arg})";
+                break;
+            case PrimitiveType.UShort:
+                condition = $"JS_IsUInt16({arg})";
+                break;
+            case PrimitiveType.Int:
+            case PrimitiveType.Long:
+                condition = $"JS_IsInt32({arg})";
+                break;
+            case PrimitiveType.ULong:
+            case PrimitiveType.UInt:
+                condition = $"JS_IsUInt32({arg})";
+                break;
+            case PrimitiveType.LongLong:
+            case PrimitiveType.ULongLong:
+            case PrimitiveType.Int128:
+            case PrimitiveType.UInt128:
+                condition = $"JS_IsBigInt(ctx, {arg})";
+                break;
+            case PrimitiveType.Half:
+            case PrimitiveType.Float:
+            case PrimitiveType.Double:
+                condition = $"JS_IsFloat({arg})";
+                break;
+            case PrimitiveType.LongDouble:
+            case PrimitiveType.Float128:
+                condition = $"JS_IsBigFloat({arg})";
+                break;
+            case PrimitiveType.String:
+                condition = $"JS_IsString({arg}) || JS_IsNull({arg})";
+                break;
+            case PrimitiveType.Decimal:
+                condition = $"JS_IsBigDecimal({arg})";
+                break;
+            case PrimitiveType.Null:
+                condition = $"JS_IsNull({arg})";
+                break;
+            case PrimitiveType.Void:
+            case PrimitiveType.Char16:
+            case PrimitiveType.Char32:
+            case PrimitiveType.IntPtr:
+            case PrimitiveType.UIntPtr:
+            default:
+                throw new NotImplementedException();
+            }
+
+            Write(condition);
+            return true;
+        }
+
+        public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals)
+        {
+            if (pointer.IsConstCharString())
+                return VisitPrimitiveType(PrimitiveType.String, quals);
+
+            return base.VisitPointerType(pointer, quals);
+        }
+
+        public override bool VisitFunctionType(FunctionType function, TypeQualifiers quals)
+        {
+            Write($"JS_IsFunction(ctx, argv[{ParameterIndex}])");
+            return true;
+        }
+
+        public override bool VisitTagType(TagType tag, TypeQualifiers quals)
+        {
+            if (tag.Declaration is Enumeration)
+            {
+                VisitPrimitiveType(PrimitiveType.Int, quals);
+                return true;
+            }
+
+            var arg = $"argv[{ParameterIndex}]";
+            Write($"JS_IsObject({arg}) || JS_IsNull({arg})");
+            return true;
+        }
+
+        public override bool VisitArrayType(ArrayType array, TypeQualifiers quals)
+        {
+            Write($"JS_IsArray(ctx, argv[{ParameterIndex}])");
+            return true;
+        }
+    }
+}
\ No newline at end of file