changeset 27611:e311a80f8cbe v8.2.4332

patch 8.2.4332: Vim9: incomplete test for existing script variable in block Commit: https://github.com/vim/vim/commit/dce2441a603f2c9343a4a46091283a32420d80a2 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Feb 8 20:35:30 2022 +0000 patch 8.2.4332: Vim9: incomplete test for existing script variable in block Problem: Vim9: incomplete test for existing script variable in block. Solution: Add a couple more tests. Fix uncovered problem.
author Bram Moolenaar <Bram@vim.org>
date Tue, 08 Feb 2022 21:45:03 +0100
parents 81772f743738
children 8f106547bf9d
files src/proto/vim9compile.pro src/testdir/test_vim9_func.vim src/userfunc.c src/version.c src/vim9compile.c src/vim9expr.c src/vim9script.c
diffstat 7 files changed, 86 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -2,8 +2,8 @@
 int lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx);
 int arg_exists(char_u *name, size_t len, int *idxp, type_T **type, int *gen_load_outer, cctx_T *cctx);
 int script_is_vim9(void);
-int script_var_exists(char_u *name, size_t len, cctx_T *cctx);
-int check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg);
+int script_var_exists(char_u *name, size_t len, cctx_T *cctx, cstack_T *cstack);
+int check_defined(char_u *p, size_t len, cctx_T *cctx, cstack_T *cstack, int is_arg);
 int need_type_where(type_T *actual, type_T *expected, int offset, where_T where, cctx_T *cctx, int silent, int actual_is_const);
 int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const);
 lvar_T *reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type);
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1057,6 +1057,43 @@ def Test_call_wrong_args()
   END
   v9.CheckScriptSuccess(lines)
 
+  # with another variable in another block
+  lines =<< trim END
+    vim9script
+    if true
+      var name = 'piet'
+      # define a function so that the variable isn't cleared
+      def GetItem(): string
+        return item
+      enddef
+    endif
+    if true
+      var name = 'peter'
+      def FuncOne(name: string)
+        echo name
+      enddef
+    endif
+  END
+  v9.CheckScriptFailure(lines, 'E1168:')
+
+  # only variable in another block is OK
+  lines =<< trim END
+    vim9script
+    if true
+      var name = 'piet'
+      # define a function so that the variable isn't cleared
+      def GetItem(): string
+        return item
+      enddef
+    endif
+    if true
+      def FuncOne(name: string)
+        echo name
+      enddef
+    endif
+  END
+  v9.CheckScriptSuccess(lines)
+
   # argument name declared later is only found when compiling
   lines =<< trim END
     vim9script
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -55,6 +55,7 @@ func_tbl_get(void)
  * If "argtypes" is not NULL also get the type: "arg: type" (:def function).
  * If "types_optional" is TRUE a missing type is OK, use "any".
  * If "evalarg" is not NULL use it to check for an already declared name.
+ * If "eap" is not NULL use it to check for an already declared name.
  * Return a pointer to after the type.
  * When something is wrong return "arg".
  */
@@ -65,6 +66,7 @@ one_function_arg(
 	garray_T    *argtypes,
 	int	    types_optional,
 	evalarg_T   *evalarg,
+	exarg_T	    *eap,
 	int	    is_vararg,
 	int	    skip)
 {
@@ -87,7 +89,8 @@ one_function_arg(
     // Vim9 script: cannot use script var name for argument. In function: also
     // check local vars and arguments.
     if (!skip && argtypes != NULL && check_defined(arg, p - arg,
-		    evalarg == NULL ? NULL : evalarg->eval_cctx, TRUE) == FAIL)
+			       evalarg == NULL ? NULL : evalarg->eval_cctx,
+			       eap == NULL ? NULL : eap->cstack, TRUE) == FAIL)
 	return arg;
 
     if (newargs != NULL && ga_grow(newargs, 1) == FAIL)
@@ -210,7 +213,7 @@ get_function_args(
     int		*varargs,
     garray_T	*default_args,
     int		skip,
-    exarg_T	*eap,
+    exarg_T	*eap,		// can be NULL
     garray_T	*lines_to_free)
 {
     int		mustend = FALSE;
@@ -279,7 +282,7 @@ get_function_args(
 
 		arg = p;
 		p = one_function_arg(p, newargs, argtypes, types_optional,
-							  evalarg, TRUE, skip);
+						     evalarg, eap, TRUE, skip);
 		if (p == arg)
 		    break;
 		if (*skipwhite(p) == '=')
@@ -295,7 +298,7 @@ get_function_args(
 
 	    arg = p;
 	    p = one_function_arg(p, newargs, argtypes, types_optional,
-							 evalarg, FALSE, skip);
+						    evalarg, eap, FALSE, skip);
 	    if (p == arg)
 		break;
 
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4332,
+/**/
     4331,
 /**/
     4330,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -152,11 +152,12 @@ arg_exists(
  * Lookup a script-local variable in the current script, possibly defined in a
  * block that contains the function "cctx->ctx_ufunc".
  * "cctx" is NULL at the script level.
+ * "cstack_T" is NULL in a function.
  * If "len" is <= 0 "name" must be NUL terminated.
  * Return NULL when not found.
  */
     static sallvar_T *
-find_script_var(char_u *name, size_t len, cctx_T *cctx)
+find_script_var(char_u *name, size_t len, cctx_T *cctx, cstack_T *cstack)
 {
     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
     hashitem_T	    *hi;
@@ -183,11 +184,22 @@ find_script_var(char_u *name, size_t len
 
     if (cctx == NULL)
     {
-	// Not in a function scope, find variable with block id equal to or
-	// smaller than the current block id.
+	// Not in a function scope, find variable with block ID equal to or
+	// smaller than the current block id.  If "cstack" is not NULL go up
+	// the block scopes (more accurate).
 	while (sav != NULL)
 	{
-	    if (sav->sav_block_id <= si->sn_current_block_id)
+	    if (cstack != NULL)
+	    {
+		int idx;
+
+		for (idx = cstack->cs_idx; idx >= 0; --idx)
+		    if (cstack->cs_block_id[idx] == sav->sav_block_id)
+			break;
+		if (idx >= 0)
+		    break;
+	    }
+	    else if (sav->sav_block_id <= si->sn_current_block_id)
 		break;
 	    sav = sav->sav_next;
 	}
@@ -225,10 +237,11 @@ script_is_vim9()
 /*
  * Lookup a variable (without s: prefix) in the current script.
  * "cctx" is NULL at the script level.
+ * "cstack" is NULL in a function.
  * Returns OK or FAIL.
  */
     int
-script_var_exists(char_u *name, size_t len, cctx_T *cctx)
+script_var_exists(char_u *name, size_t len, cctx_T *cctx, cstack_T *cstack)
 {
     if (current_sctx.sc_sid <= 0)
 	return FAIL;
@@ -236,7 +249,7 @@ script_var_exists(char_u *name, size_t l
     {
 	// Check script variables that were visible where the function was
 	// defined.
-	if (find_script_var(name, len, cctx) != NULL)
+	if (find_script_var(name, len, cctx, cstack) != NULL)
 	    return OK;
     }
     else
@@ -267,7 +280,7 @@ variable_exists(char_u *name, size_t len
     return (cctx != NULL
 		&& (lookup_local(name, len, NULL, cctx) == OK
 		    || arg_exists(name, len, NULL, NULL, NULL, cctx) == OK))
-	    || script_var_exists(name, len, cctx) == OK
+	    || script_var_exists(name, len, cctx, NULL) == OK
 	    || find_imported(name, len, FALSE, cctx) != NULL;
 }
 
@@ -309,7 +322,12 @@ item_exists(char_u *name, size_t len, in
  * Return FAIL and give an error if it defined.
  */
     int
-check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg)
+check_defined(
+	char_u	    *p,
+	size_t	    len,
+	cctx_T	    *cctx,
+	cstack_T    *cstack,
+	int	    is_arg)
 {
     int		c = p[len];
     ufunc_T	*ufunc = NULL;
@@ -318,7 +336,7 @@ check_defined(char_u *p, size_t len, cct
     if (len == 1 && *p == '_')
 	return OK;
 
-    if (script_var_exists(p, len, cctx) == OK)
+    if (script_var_exists(p, len, cctx, cstack) == OK)
     {
 	if (is_arg)
 	    semsg(_(e_argument_already_declared_in_script_str), p);
@@ -526,7 +544,7 @@ get_script_item_idx(int sid, char_u *nam
 	return -1;
     if (sid == current_sctx.sc_sid)
     {
-	sallvar_T *sav = find_script_var(name, 0, cctx);
+	sallvar_T *sav = find_script_var(name, 0, cctx, NULL);
 
 	if (sav == NULL)
 	    return -2;
@@ -884,7 +902,8 @@ compile_nested_function(exarg_T *eap, cc
 	semsg(_(e_namespace_not_supported_str), name_start);
 	return NULL;
     }
-    if (check_defined(name_start, name_end - name_start, cctx, FALSE) == FAIL)
+    if (check_defined(name_start, name_end - name_start, cctx,
+							  NULL, FALSE) == FAIL)
 	return NULL;
     if (!ASCII_ISUPPER(is_global ? name_start[2] : name_start[0]))
     {
@@ -1356,9 +1375,9 @@ compile_lhs(
 				       && STRNCMP(var_start, "s:", 2) == 0;
 		int script_var = (script_namespace
 			? script_var_exists(var_start + 2, lhs->lhs_varlen - 2,
-									  cctx)
+								    cctx, NULL)
 			  : script_var_exists(var_start, lhs->lhs_varlen,
-								  cctx)) == OK;
+							    cctx, NULL)) == OK;
 		imported_T  *import =
 			find_imported(var_start, lhs->lhs_varlen, FALSE, cctx);
 
@@ -1442,8 +1461,8 @@ compile_lhs(
 			}
 		    }
 		}
-		else if (check_defined(var_start, lhs->lhs_varlen, cctx, FALSE)
-								       == FAIL)
+		else if (check_defined(var_start, lhs->lhs_varlen, cctx,
+							  NULL, FALSE) == FAIL)
 		    return FAIL;
 	    }
 	}
@@ -2470,7 +2489,7 @@ check_args_shadowing(ufunc_T *ufunc, cct
     for (i = 0; i < ufunc->uf_args.ga_len; ++i)
     {
 	arg = ((char_u **)(ufunc->uf_args.ga_data))[i];
-	if (check_defined(arg, STRLEN(arg), cctx, TRUE) == FAIL)
+	if (check_defined(arg, STRLEN(arg), cctx, NULL, TRUE) == FAIL)
 	{
 	    r = FAIL;
 	    break;
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -501,7 +501,7 @@ compile_load(
 	    {
 		// "var" can be script-local even without using "s:" if it
 		// already exists in a Vim9 script or when it's imported.
-		if (script_var_exists(*arg, len, cctx) == OK
+		if (script_var_exists(*arg, len, cctx, NULL) == OK
 			|| find_imported(name, 0, FALSE, cctx) != NULL)
 		   res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE);
 
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -600,7 +600,8 @@ handle_import(
 	    goto erret;
 	}
 	else if (imported == NULL
-		&& check_defined(as_name, STRLEN(as_name), cctx, FALSE) == FAIL)
+		&& check_defined(as_name, STRLEN(as_name), cctx, NULL,
+								FALSE) == FAIL)
 	    goto erret;
 
 	if (imported == NULL)