changeset 34112:0f2632b04cde v9.1.0020

patch 9.1.0020: Vim9: cannot compile all methods in a class Commit: https://github.com/vim/vim/commit/4f32c83a775a195ae7e1545b2840fb773f93414f Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Fri Jan 12 17:36:40 2024 +0100 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>
author Christian Brabandt <cb@256bit.org>
date Fri, 12 Jan 2024 17:45:08 +0100
parents 204b15645ef4
children 9390cf5cca86
files runtime/doc/tags runtime/doc/todo.txt runtime/doc/vim9.txt runtime/doc/vim9class.txt src/proto/userfunc.pro src/proto/vim9class.pro src/testdir/test_vim9_class.vim src/userfunc.c src/version.c src/vim9class.c
diffstat 10 files changed, 226 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -6428,6 +6428,7 @@ cino-{	indent.txt	/*cino-{*
 cino-}	indent.txt	/*cino-}*
 cinoptions-values	indent.txt	/*cinoptions-values*
 class	vim9class.txt	/*class*
+class-compile	vim9class.txt	/*class-compile*
 class-method	vim9class.txt	/*class-method*
 clear-undo	undo.txt	/*clear-undo*
 clearmatches()	builtin.txt	/*clearmatches()*
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -130,8 +130,6 @@ Further Vim9 improvements:
     Issue #11822: any.Func() can be a dict or an object call, need to handle
     this at runtime.  Also see #12198 for an example.
     Possibly issue #11981 can be fixed at the same time (has two examples).
-  - Make ":defcompile ClassName" compile all functions and methods in the
-    class.
   - Forward declaration of a class?  E.g. for Clone() function.
 	Email lifepillar 2023 Mar 26
   - object empty(), len() - can class define a method to be used for them?
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -1,4 +1,4 @@
-*vim9.txt*	For Vim version 9.1.  Last change: 2023 Dec 24
+*vim9.txt*	For Vim version 9.1.  Last change: 2024 Jan 12
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -1260,10 +1260,12 @@ Script-local variables in a |Vim9| scrip
 level.  They cannot be created in a function, also not in a legacy function.
 
 						*:defc* *:defcompile*
-:defc[ompile]		Compile functions defined in the current script that
-			were not compiled yet.
-			This will report any errors found during compilation.
-			This excludes functions defined inside a class.
+:defc[ompile]		Compile functions and classes (|class-compile|)
+			defined in the current script that were not compiled
+			yet.  This will report any errors found during
+			compilation.
+
+:defc[ompile] MyClass  Compile all methods in a class |class-compile|.
 
 :defc[ompile] {func}
 :defc[ompile] debug {func}
--- a/runtime/doc/vim9class.txt
+++ b/runtime/doc/vim9class.txt
@@ -1,4 +1,4 @@
-*vim9class.txt*	For Vim version 9.1.  Last change: 2024 Jan 06
+*vim9class.txt*	For Vim version 9.1.  Last change: 2024 Jan 12
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -830,7 +830,14 @@ Note that the method name must start wit
 "new()" then the default constructor is added, even though there are other
 constructor methods.
 
+Compiling methods in a Class ~
+							*class-compile*
+The |:defcompile| command can be used to compile all the class and object
+methods defined in a class: >
 
+	defcompile MyClass	# Compile class "MyClass"
+	defcompile		# Compile the classes in the current script
+<
 ==============================================================================
 
 7.  Type definition				*typealias* *Vim9-type* *:type*
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -50,6 +50,7 @@ void list_functions(regmatch_T *regmatch
 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);
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -31,6 +31,9 @@ void object_free_items(int copyID);
 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 : */
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -9686,4 +9686,87 @@ def Test_method_double_underscore_prefix
   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
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5546,6 +5546,60 @@ find_func_by_name(char_u *name, compilet
 }
 
 /*
+ * 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 @@ ex_defcompile(exarg_T *eap)
 {
     if (*eap->arg != NUL)
     {
-	compiletype_T compile_type = CT_NONE;
-	ufunc_T *ufunc = find_func_by_name(eap->arg, &compile_type);
-	if (ufunc != NULL)
-	{
-	    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);
+	typval_T tv;
+
+	if (is_class_name(eap->arg, &tv))
+	{
+	    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;
-
-	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_funcs_in_script();
+
+	// compile all the class defined in the current script
+	defcompile_classes_in_script();
     }
 }
 
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    20,
+/**/
     19,
 /**/
     18,
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -3225,6 +3225,54 @@ member_not_found_msg(class_T *cl, vartyp
 }
 
 /*
+ * 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".
  */