changeset 27406:4c1bdee75bed v8.2.4231

patch 8.2.4231: Vim9: map() gives type error when type was not declared Commit: https://github.com/vim/vim/commit/35c807df1f5774f09612d756ddc3cd5c44eacaca Author: Bram Moolenaar <Bram@vim.org> Date: Thu Jan 27 16:36:29 2022 +0000 patch 8.2.4231: Vim9: map() gives type error when type was not declared Problem: Vim9: map() gives type error when type was not declared. Solution: Only check the type when it was declared, like extend() does. (closes #9635)
author Bram Moolenaar <Bram@vim.org>
date Thu, 27 Jan 2022 17:45:03 +0100
parents 9265a363d1ab
children 7ad1f361ffd6
files src/evalfunc.c src/list.c src/testdir/test_vim9_assign.vim src/testdir/test_vim9_builtin.vim src/version.c src/vim9instr.c
diffstat 6 files changed, 35 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -530,21 +530,30 @@ arg_map_func(type_T *type, type_T *decl_
 
     if (type->tt_type == VAR_FUNC)
     {
-	if (type->tt_member != &t_any
-	    && type->tt_member != &t_unknown)
+	if (type->tt_member != &t_any && type->tt_member != &t_unknown)
 	{
 	    type_T *expected = NULL;
 
 	    if (context->arg_types[0].type_curr->tt_type == VAR_LIST
 		    || context->arg_types[0].type_curr->tt_type == VAR_DICT)
-		expected = context->arg_types[0].type_curr->tt_member;
+	    {
+		// Use the declared type, so that an error is given if a
+		// declared list changes type, but not if a constant list
+		// changes type.
+		if (context->arg_types[0].type_decl->tt_type == VAR_LIST
+			|| context->arg_types[0].type_decl->tt_type == VAR_DICT)
+		    expected = context->arg_types[0].type_decl->tt_member;
+		else
+		    expected = context->arg_types[0].type_curr->tt_member;
+	    }
 	    else if (context->arg_types[0].type_curr->tt_type == VAR_STRING)
 		expected = &t_string;
 	    else if (context->arg_types[0].type_curr->tt_type == VAR_BLOB)
 		expected = &t_number;
 	    if (expected != NULL)
 	    {
-		type_T t_func_exp = {VAR_FUNC, -1, 0, TTFLAG_STATIC, NULL, NULL};
+		type_T t_func_exp = {VAR_FUNC, -1, 0, TTFLAG_STATIC,
+								   NULL, NULL};
 
 		t_func_exp.tt_member = expected;
 		return check_arg_type(&t_func_exp, type, context);
--- a/src/list.c
+++ b/src/list.c
@@ -580,15 +580,14 @@ list_append(list_T *l, listitem_T *item)
     {
 	// empty list
 	l->lv_first = item;
-	l->lv_u.mat.lv_last = item;
 	item->li_prev = NULL;
     }
     else
     {
 	l->lv_u.mat.lv_last->li_next = item;
 	item->li_prev = l->lv_u.mat.lv_last;
-	l->lv_u.mat.lv_last = item;
     }
+    l->lv_u.mat.lv_last = item;
     ++l->lv_len;
     item->li_next = NULL;
 }
@@ -2362,8 +2361,7 @@ list_filter_map(
 	    tv.v_lock = 0;
 	    tv.vval.v_number = val;
 	    set_vim_var_nr(VV_KEY, idx);
-	    if (filter_map_one(&tv, expr, filtermap, &newtv, &rem)
-		    == FAIL)
+	    if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL)
 		break;
 	    if (did_emsg)
 	    {
@@ -2461,7 +2459,6 @@ filter_map(typval_T *argvars, typval_T *
 						    : N_("filter() argument"));
     int		save_did_emsg;
     type_T	*type = NULL;
-    garray_T	type_list;
 
     // map() and filter() return the first argument, also on failure.
     if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
@@ -2474,9 +2471,13 @@ filter_map(typval_T *argvars, typval_T *
 
     if (filtermap == FILTERMAP_MAP && in_vim9script())
     {
-	// Check that map() does not change the type of the dict.
-	ga_init2(&type_list, sizeof(type_T *), 10);
-	type = typval2type(argvars, get_copyID(), &type_list, TVTT_DO_MEMBER);
+	// Check that map() does not change the declared type of the list or
+	// dict.
+	if (argvars[0].v_type == VAR_DICT && argvars[0].vval.v_dict != NULL)
+	    type = argvars[0].vval.v_dict->dv_type;
+	else if (argvars[0].v_type == VAR_LIST
+					     && argvars[0].vval.v_list != NULL)
+	    type = argvars[0].vval.v_list->lv_type;
     }
 
     if (argvars[0].v_type != VAR_BLOB
@@ -2489,10 +2490,10 @@ filter_map(typval_T *argvars, typval_T *
 	goto theend;
     }
 
-    expr = &argvars[1];
     // On type errors, the preceding call has already displayed an error
     // message.  Avoid a misleading error message for an empty string that
     // was not passed as argument.
+    expr = &argvars[1];
     if (expr->v_type != VAR_UNKNOWN)
     {
 	typval_T	save_val;
@@ -2525,8 +2526,6 @@ filter_map(typval_T *argvars, typval_T *
     }
 
 theend:
-    if (type != NULL)
-	clear_type_list(&type_list);
 }
 
 /*
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -1963,7 +1963,7 @@ def Test_var_list_dict_type()
       var ll: list<number>
       ll = [1, 2, 3]->map('"one"')
   END
-  CheckDefExecFailure(lines, 'E1012: Type mismatch; expected number but got string')
+  CheckDefExecFailure(lines, 'E1012: Type mismatch; expected list<number> but got list<string>')
 enddef
 
 def Test_cannot_use_let()
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -2230,13 +2230,18 @@ def Test_map_function_arg()
   END
   CheckDefExecAndScriptFailure(lines, 'E1190: 2 arguments too few')
 
+  # declared list cannot change type
   lines =<< trim END
     def Map(i: number, v: number): string
       return 'bad'
     enddef
-    echo map([1, 2, 3], Map)
+    var ll: list<number> = [1, 2, 3]
+    echo map(ll, Map)
   END
   CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(number, number): string', 'E1012: Type mismatch; expected number but got string in map()'])
+
+  # not declared list can change type
+  echo [1, 2, 3]->map((..._) => 'x')
 enddef
 
 def Test_map_item_type()
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4231,
+/**/
     4230,
 /**/
     4229,
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1331,10 +1331,10 @@ generate_BCALL(cctx_T *cctx, int func_id
     if (push_type_stack(cctx, type) == FAIL)
 	return FAIL;
 
-    if (maptype != NULL && maptype[0].type_curr->tt_member != NULL
-				  && maptype[0].type_curr->tt_member != &t_any)
+    if (maptype != NULL && maptype[0].type_decl->tt_member != NULL
+				  && maptype[0].type_decl->tt_member != &t_any)
 	// Check that map() didn't change the item types.
-	generate_TYPECHECK(cctx, maptype[0].type_curr, -1, 1);
+	generate_TYPECHECK(cctx, maptype[0].type_decl, -1, 1);
 
     return OK;
 }