patch 9.1.0020: Vim9: cannot compile all methods in a class

Problem:  Vim9: cannot compile all methods in a class
Solution: Support compiling all the methods in a class using :defcompile
          (Yegappan Lakshmanan)

closes: #13844

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index e393c04..9bb4616 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -50,6 +50,7 @@
 ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags, ocmember_T *obj_members, int obj_member_count);
 void ex_function(exarg_T *eap);
 ufunc_T *find_func_by_name(char_u *name, compiletype_T *compile_type);
+void defcompile_function(ufunc_T *ufunc, class_T *cl);
 void ex_defcompile(exarg_T *eap);
 int eval_fname_script(char_u *p);
 int translated_function_exists(char_u *name, int is_global);
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index f1b6360..a746eb7 100644
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -31,6 +31,9 @@
 void emsg_var_cl_define(char *msg, char_u *name, size_t len, class_T *cl);
 void method_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
 void member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
+void defcompile_class(class_T *cl);
+void defcompile_classes_in_script(void);
+int is_class_name(char_u *name, typval_T *rettv);
 int class_instance_of(class_T *cl, class_T *other_cl);
 void f_instanceof(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index b34d2ad..62a6d04 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -9686,4 +9686,87 @@
   v9.CheckSourceFailure(lines, 'E1034: Cannot use reserved name __foo()', 3)
 enddef
 
+" Test for compiling class/object methods using :defcompile
+def Test_defcompile_class()
+  # defcompile all the classes in the current script
+  var lines =<< trim END
+    vim9script
+    class A
+      def Foo()
+        var i = 10
+      enddef
+    endclass
+    class B
+      def Bar()
+        var i = 20
+        xxx
+      enddef
+    endclass
+    defcompile
+  END
+  v9.CheckSourceFailure(lines, 'E476: Invalid command: xxx', 2)
+
+  # defcompile a specific class
+  lines =<< trim END
+    vim9script
+    class A
+      def Foo()
+        xxx
+      enddef
+    endclass
+    class B
+      def Bar()
+        yyy
+      enddef
+    endclass
+    defcompile B
+  END
+  v9.CheckSourceFailure(lines, 'E476: Invalid command: yyy', 1)
+
+  # defcompile a non-class
+  lines =<< trim END
+    vim9script
+    class A
+      def Foo()
+      enddef
+    endclass
+    var X: list<number> = []
+    defcompile X
+  END
+  v9.CheckSourceFailure(lines, 'E1061: Cannot find function X', 7)
+
+  # defcompile a class twice
+  lines =<< trim END
+    vim9script
+    class A
+      def new()
+      enddef
+    endclass
+    defcompile A
+    defcompile A
+    assert_equal('Function A.new does not need compiling', v:statusmsg)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # defcompile should not compile an imported class
+  lines =<< trim END
+    vim9script
+    export class A
+      def Foo()
+        xxx
+      enddef
+    endclass
+  END
+  writefile(lines, 'Xdefcompileimport.vim', 'D')
+  lines =<< trim END
+    vim9script
+
+    import './Xdefcompileimport.vim'
+    class B
+    endclass
+    defcompile
+  END
+  v9.CheckScriptSuccess(lines)
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/userfunc.c b/src/userfunc.c
index 64761ec..e39ce6e 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5546,6 +5546,60 @@
 }
 
 /*
+ * Compile the :def function "ufunc".  If "cl" is not NULL, then compile the
+ * class or object method "ufunc" in "cl".
+ */
+    void
+defcompile_function(ufunc_T *ufunc, class_T *cl)
+{
+    compiletype_T compile_type = CT_NONE;
+
+    if (func_needs_compiling(ufunc, compile_type))
+	(void)compile_def_function(ufunc, FALSE, compile_type, NULL);
+    else
+	smsg(_("Function %s%s%s does not need compiling"),
+				cl != NULL ? cl->class_name : (char_u *)"",
+				cl != NULL ? (char_u *)"." : (char_u *)"",
+				ufunc->uf_name);
+}
+
+/*
+ * Compile all the :def functions defined in the current script
+ */
+    static void
+defcompile_funcs_in_script(void)
+{
+    long	todo = (long)func_hashtab.ht_used;
+    int		changed = func_hashtab.ht_changed;
+    hashitem_T	*hi;
+
+    for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi)
+    {
+	if (!HASHITEM_EMPTY(hi))
+	{
+	    --todo;
+	    ufunc_T *ufunc = HI2UF(hi);
+	    if (ufunc->uf_script_ctx.sc_sid == current_sctx.sc_sid
+		    && ufunc->uf_def_status == UF_TO_BE_COMPILED
+		    && (ufunc->uf_flags & FC_DEAD) == 0)
+	    {
+		(void)compile_def_function(ufunc, FALSE, CT_NONE, NULL);
+
+		if (func_hashtab.ht_changed != changed)
+		{
+		    // a function has been added or removed, need to start
+		    // over
+		    todo = (long)func_hashtab.ht_used;
+		    changed = func_hashtab.ht_changed;
+		    hi = func_hashtab.ht_array;
+		    --hi;
+		}
+	    }
+	}
+    }
+}
+
+/*
  * :defcompile - compile all :def functions in the current script that need to
  * be compiled or the one specified by the argument.
  * Skips dead functions.  Doesn't do profiling.
@@ -5555,46 +5609,29 @@
 {
     if (*eap->arg != NUL)
     {
-	compiletype_T compile_type = CT_NONE;
-	ufunc_T *ufunc = find_func_by_name(eap->arg, &compile_type);
-	if (ufunc != NULL)
+	typval_T tv;
+
+	if (is_class_name(eap->arg, &tv))
 	{
-	    if (func_needs_compiling(ufunc, compile_type))
-		(void)compile_def_function(ufunc, FALSE, compile_type, NULL);
-	    else
-		smsg(_("Function %s does not need compiling"), eap->arg);
+	    class_T *cl = tv.vval.v_class;
+
+	    if (cl != NULL)
+		defcompile_class(cl);
+	}
+	else
+	{
+	    compiletype_T compile_type = CT_NONE;
+	    ufunc_T *ufunc = find_func_by_name(eap->arg, &compile_type);
+	    if (ufunc != NULL)
+		defcompile_function(ufunc, NULL);
 	}
     }
     else
     {
-	long	todo = (long)func_hashtab.ht_used;
-	int		changed = func_hashtab.ht_changed;
-	hashitem_T	*hi;
+	defcompile_funcs_in_script();
 
-	for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi)
-	{
-	    if (!HASHITEM_EMPTY(hi))
-	    {
-		--todo;
-		ufunc_T *ufunc = HI2UF(hi);
-		if (ufunc->uf_script_ctx.sc_sid == current_sctx.sc_sid
-			&& ufunc->uf_def_status == UF_TO_BE_COMPILED
-			&& (ufunc->uf_flags & FC_DEAD) == 0)
-		{
-		    (void)compile_def_function(ufunc, FALSE, CT_NONE, NULL);
-
-		    if (func_hashtab.ht_changed != changed)
-		    {
-			// a function has been added or removed, need to start
-			// over
-			todo = (long)func_hashtab.ht_used;
-			changed = func_hashtab.ht_changed;
-			hi = func_hashtab.ht_array;
-			--hi;
-		    }
-		}
-	    }
-	}
+	// compile all the class defined in the current script
+	defcompile_classes_in_script();
     }
 }
 
diff --git a/src/version.c b/src/version.c
index 627acd2..76a7668 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    20,
+/**/
     19,
 /**/
     18,
diff --git a/src/vim9class.c b/src/vim9class.c
index e5d9aeb..525f8d0 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -3225,6 +3225,54 @@
 }
 
 /*
+ * Compile all the class and object methods in "cl".
+ */
+    void
+defcompile_class(class_T *cl)
+{
+    for (int loop = 1; loop <= 2; ++loop)
+    {
+	int func_count = loop == 1 ? cl->class_class_function_count
+						: cl->class_obj_method_count;
+	for (int i = 0; i < func_count; i++)
+	{
+	    ufunc_T *ufunc = loop == 1 ? cl->class_class_functions[i]
+						: cl->class_obj_methods[i];
+	    defcompile_function(ufunc, cl);
+	}
+    }
+}
+
+/*
+ * Compile all the classes defined in the current script
+ */
+    void
+defcompile_classes_in_script(void)
+{
+    for (class_T *cl = first_class; cl != NULL; cl = cl->class_next_used)
+    {
+	if (eval_variable(cl->class_name, 0, 0, NULL, NULL,
+			EVAL_VAR_NOAUTOLOAD | EVAL_VAR_NO_FUNC) != FAIL)
+	    defcompile_class(cl);
+    }
+}
+
+/*
+ * Returns TRUE if "name" is the name of a class.  The typval for the class is
+ * returned in "rettv".
+ */
+    int
+is_class_name(char_u *name, typval_T *rettv)
+{
+    rettv->v_type = VAR_UNKNOWN;
+
+    if (eval_variable(name, 0, 0, rettv, NULL, EVAL_VAR_NOAUTOLOAD |
+						EVAL_VAR_NO_FUNC) != FAIL)
+	return rettv->v_type == VAR_CLASS;
+    return FALSE;
+}
+
+/*
  * Return TRUE when the class "cl", its base class or one of the implemented
  * interfaces matches the class "other_cl".
  */