changeset 32013:ec05de98b0f7 v9.0.1338

patch 9.0.1338: :defcompile and :disassemble can't find class method Commit: https://github.com/vim/vim/commit/99a7c0d89cf77c0a908b60191e63f41f04f9e793 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Feb 21 19:55:14 2023 +0000 patch 9.0.1338: :defcompile and :disassemble can't find class method Problem: :defcompile and :disassemble can't find class method. (Ernie Rael) Solution: Make a class name and class.method name work. (closes https://github.com/vim/vim/issues/11984)
author Bram Moolenaar <Bram@vim.org>
date Tue, 21 Feb 2023 21:00:05 +0100
parents 2c4d6d25953c
children caf134e4ff5f
files src/eval.c src/proto/userfunc.pro src/structs.h src/testdir/test_vim9_class.vim src/userfunc.c src/version.c src/vim.h
diffstat 7 files changed, 147 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -1529,45 +1529,81 @@ get_lval(
 	    if (cl != NULL)
 	    {
 		lp->ll_valtype = NULL;
-		int count = v_type == VAR_OBJECT ? cl->class_obj_member_count
-						: cl->class_class_member_count;
-		ocmember_T *members = v_type == VAR_OBJECT
-						     ? cl->class_obj_members
-						     : cl->class_class_members;
-		for (int i = 0; i < count; ++i)
+
+		if (flags & GLV_PREFER_FUNC)
 		{
-		    ocmember_T *om = members + i;
-		    if (STRNCMP(om->ocm_name, key, p - key) == 0
-					       && om->ocm_name[p - key] == NUL)
+		    // First look for a function with this name.
+		    // round 1: class functions (skipped for an object)
+		    // round 2: object methods
+		    for (int round = v_type == VAR_OBJECT ? 2 : 1;
+							   round <= 2; ++round)
 		    {
-			switch (om->ocm_access)
+			int count = round == 1
+					    ? cl->class_class_function_count
+					    : cl->class_obj_method_count;
+			ufunc_T **funcs = round == 1
+					    ? cl->class_class_functions
+					    : cl->class_obj_methods;
+			for (int i = 0; i < count; ++i)
 			{
-			    case ACCESS_PRIVATE:
-				    semsg(_(e_cannot_access_private_member_str),
-								 om->ocm_name);
-				    return NULL;
-			    case ACCESS_READ:
-				    if (!(flags & GLV_READ_ONLY))
-				    {
-					semsg(_(e_member_is_not_writable_str),
+			    ufunc_T *fp = funcs[i];
+			    char_u *ufname = (char_u *)fp->uf_name;
+			    if (STRNCMP(ufname, key, p - key) == 0
+						     && ufname[p - key] == NUL)
+			    {
+				lp->ll_ufunc = fp;
+				lp->ll_valtype = fp->uf_func_type;
+				round = 3;
+				break;
+			    }
+			}
+		    }
+		}
+
+		if (lp->ll_valtype == NULL)
+		{
+		    int count = v_type == VAR_OBJECT
+					    ? cl->class_obj_member_count
+					    : cl->class_class_member_count;
+		    ocmember_T *members = v_type == VAR_OBJECT
+					    ? cl->class_obj_members
+					    : cl->class_class_members;
+		    for (int i = 0; i < count; ++i)
+		    {
+			ocmember_T *om = members + i;
+			if (STRNCMP(om->ocm_name, key, p - key) == 0
+					       && om->ocm_name[p - key] == NUL)
+			{
+			    switch (om->ocm_access)
+			    {
+				case ACCESS_PRIVATE:
+					semsg(_(e_cannot_access_private_member_str),
 								 om->ocm_name);
 					return NULL;
-				    }
-				    break;
-			    case ACCESS_ALL:
-				    break;
+				case ACCESS_READ:
+					if ((flags & GLV_READ_ONLY) == 0)
+					{
+					    semsg(_(e_member_is_not_writable_str),
+								 om->ocm_name);
+					    return NULL;
+					}
+					break;
+				case ACCESS_ALL:
+					break;
+			    }
+
+			    lp->ll_valtype = om->ocm_type;
+
+			    if (v_type == VAR_OBJECT)
+				lp->ll_tv = ((typval_T *)(
+					    lp->ll_tv->vval.v_object + 1)) + i;
+			    else
+				lp->ll_tv = &cl->class_members_tv[i];
+			    break;
 			}
-
-			lp->ll_valtype = om->ocm_type;
-
-			if (v_type == VAR_OBJECT)
-			    lp->ll_tv = ((typval_T *)(
-					    lp->ll_tv->vval.v_object + 1)) + i;
-			else
-			    lp->ll_tv = &cl->class_members_tv[i];
-			break;
 		    }
 		}
+
 		if (lp->ll_valtype == NULL)
 		{
 		    if (v_type == VAR_OBJECT)
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -41,7 +41,8 @@ void user_func_error(funcerror_T error, 
 int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
 int call_simple_func(char_u *funcname, int len, typval_T *rettv);
 char_u *printable_func_name(ufunc_T *fp);
-char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type);
+char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags);
+char_u *trans_function_name_ext(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type, ufunc_T **ufunc);
 char_u *get_scriptlocal_funcname(char_u *funcname);
 char_u *alloc_printable_func_name(char_u *fname);
 char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi);
--- a/src/structs.h
+++ b/src/structs.h
@@ -4521,6 +4521,7 @@ typedef struct lval_S
     char_u	*ll_newkey;	// New key for Dict in alloc. mem or NULL.
     type_T	*ll_valtype;	// type expected for the value or NULL
     blob_T	*ll_blob;	// The Blob or NULL
+    ufunc_T	*ll_ufunc;	// The function or NULL
 } lval_T;
 
 // Structure used to save the current state.  Used when executing Normal mode
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -842,6 +842,34 @@ def Test_class_function()
   v9.CheckScriptSuccess(lines)
 enddef
 
+def Test_class_defcompile()
+  var lines =<< trim END
+      vim9script
+
+      class C
+          def Fo(i: number): string
+              return i
+          enddef
+      endclass
+
+      defcompile C.Fo
+  END
+  v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number')
+
+  lines =<< trim END
+      vim9script
+
+      class C
+          static def Fc(): number
+            return 'x'
+          enddef
+      endclass
+
+      defcompile C.Fc
+  END
+  v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string')
+enddef
+
 def Test_class_object_to_string()
   var lines =<< trim END
       vim9script
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1037,8 +1037,7 @@ get_function_body(
 		if (*p == '!')
 		    p = skipwhite(p + 1);
 		p += eval_fname_script(p);
-		vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
-								  NULL, NULL));
+		vim_free(trans_function_name(&p, NULL, TRUE, 0));
 		if (*skipwhite(p) == '(')
 		{
 		    if (nesting == MAX_FUNC_NESTING - 1)
@@ -4041,10 +4040,26 @@ trans_function_name(
     char_u	**pp,
     int		*is_global,
     int		skip,		// only find the end, don't evaluate
+    int		flags)
+{
+    return trans_function_name_ext(pp, is_global, skip, flags,
+	    NULL, NULL, NULL, NULL);
+}
+
+/*
+ * trans_function_name() with extra arguments.
+ * "fdp", "partial", "type" and "ufunc" can be NULL.
+ */
+    char_u *
+trans_function_name_ext(
+    char_u	**pp,
+    int		*is_global,
+    int		skip,		// only find the end, don't evaluate
     int		flags,
     funcdict_T	*fdp,		// return: info about dictionary used
     partial_T	**partial,	// return: partial of a FuncRef
-    type_T	**type)		// return: type of funcref if not NULL
+    type_T	**type,		// return: type of funcref
+    ufunc_T	**ufunc)	// return: function
 {
     char_u	*name = NULL;
     char_u	*start;
@@ -4079,7 +4094,8 @@ trans_function_name(
 	start += lead;
 
     // Note that TFN_ flags use the same values as GLV_ flags.
-    end = get_lval(start, NULL, &lv, FALSE, skip, flags | GLV_READ_ONLY,
+    end = get_lval(start, NULL, &lv, FALSE, skip,
+			flags | GLV_READ_ONLY | GLV_PREFER_FUNC,
 					      lead > 2 ? 0 : FNE_CHECK_START);
     if (end == start || (vim9script && end != NULL
 				   && end[-1] == AUTOLOAD_CHAR && *end == '('))
@@ -4105,6 +4121,13 @@ trans_function_name(
 	goto theend;
     }
 
+    if (lv.ll_ufunc != NULL)
+    {
+	*ufunc = lv.ll_ufunc;
+	name = vim_strsave(lv.ll_ufunc->uf_name);
+	goto theend;
+    }
+
     if (lv.ll_tv != NULL)
     {
 	if (fdp != NULL)
@@ -4455,8 +4478,8 @@ save_function_name(
 	    CLEAR_POINTER(fudi);
     }
     else
-	saved = trans_function_name(&p, is_global, skip,
-						      flags, fudi, NULL, NULL);
+	saved = trans_function_name_ext(&p, is_global, skip,
+						flags, fudi, NULL, NULL, NULL);
     *name = p;
     return saved;
 }
@@ -5330,15 +5353,20 @@ find_func_by_name(char_u *name, compilet
     }
     else
     {
-	// First try finding a method in a class, find_func_by_name() will give
-	// an error if the function is not found.
+	// First try finding a method in a class, trans_function_name() will
+	// give an error if the function is not found.
 	ufunc = find_class_func(&arg);
 	if (ufunc != NULL)
 	    return ufunc;
 
-	fname = trans_function_name(&arg, &is_global, FALSE,
+	fname = trans_function_name_ext(&arg, &is_global, FALSE,
 		      TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DECL,
-		      NULL, NULL, NULL);
+		      NULL, NULL, NULL, &ufunc);
+	if (ufunc != NULL)
+	{
+	    vim_free(fname);
+	    return ufunc;
+	}
     }
     if (fname == NULL)
     {
@@ -5375,13 +5403,10 @@ find_func_by_name(char_u *name, compilet
     void
 ex_defcompile(exarg_T *eap)
 {
-    ufunc_T	*ufunc;
-
     if (*eap->arg != NUL)
     {
 	compiletype_T compile_type = CT_NONE;
-
-	ufunc = find_func_by_name(eap->arg, &compile_type);
+	ufunc_T *ufunc = find_func_by_name(eap->arg, &compile_type);
 	if (ufunc != NULL)
 	{
 	    if (func_needs_compiling(ufunc, compile_type))
@@ -5401,7 +5426,7 @@ ex_defcompile(exarg_T *eap)
 	    if (!HASHITEM_EMPTY(hi))
 	    {
 		--todo;
-		ufunc = HI2UF(hi);
+		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)
@@ -5475,7 +5500,7 @@ function_exists(char_u *name, int no_der
     flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
     if (no_deref)
 	flag |= TFN_NO_DEREF;
-    p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL, NULL);
+    p = trans_function_name(&nm, &is_global, FALSE, flag);
     nm = skipwhite(nm);
 
     // Only accept "funcname", "funcname ", "funcname (..." and
@@ -5494,8 +5519,7 @@ get_expanded_name(char_u *name, int chec
     char_u	*p;
     int		is_global = FALSE;
 
-    p = trans_function_name(&nm, &is_global, FALSE,
-					  TFN_INT|TFN_QUIET, NULL, NULL, NULL);
+    p = trans_function_name(&nm, &is_global, FALSE, TFN_INT|TFN_QUIET);
 
     if (p != NULL && *nm == NUL
 		       && (!check || translated_function_exists(p, is_global)))
@@ -5631,8 +5655,8 @@ ex_delfunction(exarg_T *eap)
     int		is_global = FALSE;
 
     p = eap->arg;
-    name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi,
-								   NULL, NULL);
+    name = trans_function_name_ext(&p, &is_global, eap->skip, 0, &fudi,
+							     NULL, NULL, NULL);
     vim_free(fudi.fd_newkey);
     if (name == NULL)
     {
@@ -6166,8 +6190,8 @@ ex_call(exarg_T *eap)
 	return;
     }
 
-    tofree = trans_function_name(&arg, NULL, eap->skip, TFN_INT,
-			      &fudi, &partial, vim9script ? &type : NULL);
+    tofree = trans_function_name_ext(&arg, NULL, eap->skip, TFN_INT,
+			     &fudi, &partial, vim9script ? &type : NULL, NULL);
     if (fudi.fd_newkey != NULL)
     {
 	// Still need to give an error message for missing key.
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1338,
+/**/
     1337,
 /**/
     1336,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2722,6 +2722,7 @@ typedef char *(*opt_did_set_cb_T)(optset
 #define GLV_NO_DECL	TFN_NO_DECL	// assignment without :var or :let
 #define GLV_COMPILING	TFN_COMPILING	// variable may be defined later
 #define GLV_ASSIGN_WITH_OP TFN_ASSIGN_WITH_OP // assignment with operator
+#define GLV_PREFER_FUNC	0x10000		// prefer function above variable
 
 #define DO_NOT_FREE_CNT 99999	// refcount for dict or list that should not
 				// be freed.