changeset 27503:4cea92e99a5a v8.2.4279

patch 8.2.4279: Vim9: cannot change item type with map() after range() Commit: https://github.com/vim/vim/commit/8133018f50bc447570825801e93d5ed67e8dac90 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Feb 1 12:11:58 2022 +0000 patch 8.2.4279: Vim9: cannot change item type with map() after range() Problem: Vim9: cannot change item type with map() after range(). Solution: Split the return type in current type and declared type. (closes #9665)
author Bram Moolenaar <Bram@vim.org>
date Tue, 01 Feb 2022 13:15:03 +0100
parents aad2a09123be
children 93bc77ed725f
files src/evalfunc.c src/proto/evalfunc.pro src/proto/vim9type.pro src/testdir/test_vim9_builtin.vim src/version.c src/vim9instr.c src/vim9type.c
diffstat 7 files changed, 203 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -992,127 +992,209 @@ static argcheck_T arg24_match_func[] = {
  * Note that "argtypes" is NULL if "argcount" is zero.
  */
     static type_T *
-ret_void(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_void(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_void;
 }
     static type_T *
-ret_any(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_any(int	argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_any;
 }
     static type_T *
-ret_bool(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_bool(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_bool;
 }
     static type_T *
-ret_number_bool(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_number_bool(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_number_bool;
 }
     static type_T *
-ret_number(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_number(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_number;
 }
     static type_T *
-ret_float(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_float(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_float;
 }
     static type_T *
-ret_string(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_string(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_string;
 }
     static type_T *
-ret_list_any(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_list_any(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_any;
 }
     static type_T *
-ret_list_number(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_list_number(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_number;
 }
     static type_T *
-ret_list_string(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_range(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type)
+{
+    // returning a list<number>, but it's not declared as such
+    *decl_type = &t_list_any;
+    return &t_list_number;
+}
+    static type_T *
+ret_list_string(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_string;
 }
     static type_T *
-ret_list_dict_any(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_list_dict_any(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_dict_any;
 }
     static type_T *
-ret_list_items(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_list_items(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_list_any;
 }
 
     static type_T *
-ret_list_string_items(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_list_string_items(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_list_list_string;
 }
     static type_T *
-ret_dict_any(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_dict_any(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_dict_any;
 }
     static type_T *
-ret_job_info(int argcount, type2_T *argtypes UNUSED)
+ret_job_info(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     if (argcount == 0)
 	return &t_list_job;
     return &t_dict_any;
 }
     static type_T *
-ret_dict_number(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_dict_number(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_dict_number;
 }
     static type_T *
-ret_dict_string(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_dict_string(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_dict_string;
 }
     static type_T *
-ret_blob(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_blob(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_blob;
 }
     static type_T *
-ret_func_any(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_func_any(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_func_any;
 }
     static type_T *
-ret_func_unknown(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_func_unknown(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_func_unknown;
 }
     static type_T *
-ret_channel(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_channel(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_channel;
 }
     static type_T *
-ret_job(int argcount UNUSED, type2_T *argtypes UNUSED)
+ret_job(int argcount UNUSED,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return &t_job;
 }
     static type_T *
-ret_first_arg(int argcount, type2_T *argtypes)
+ret_first_arg(int argcount,
+	type2_T *argtypes,
+	type_T	**decl_type)
 {
     if (argcount > 0)
+    {
+	*decl_type = argtypes[0].type_decl;
 	return argtypes[0].type_curr;
+    }
     return &t_void;
 }
     static type_T *
-ret_repeat(int argcount, type2_T *argtypes)
+ret_extend(int argcount,
+	type2_T *argtypes,
+	type_T	**decl_type)
+{
+    if (argcount > 0)
+    {
+	*decl_type = argtypes[0].type_decl;
+	// if the second argument has a different current type then the current
+	// type is "any"
+	if (argcount > 1 && !equal_type(argtypes[0].type_curr,
+						     argtypes[1].type_curr, 0))
+	{
+	    if (argtypes[0].type_curr->tt_type == VAR_LIST)
+		return &t_list_any;
+	    if (argtypes[0].type_curr->tt_type == VAR_DICT)
+		return &t_dict_any;
+	}
+	return argtypes[0].type_curr;
+    }
+    return &t_void;
+}
+    static type_T *
+ret_repeat(int argcount,
+	type2_T *argtypes,
+	type_T	**decl_type UNUSED)
 {
     if (argcount == 0)
 	return &t_any;
@@ -1122,7 +1204,9 @@ ret_repeat(int argcount, type2_T *argtyp
 }
 // for map(): returns first argument but item type may differ
     static type_T *
-ret_first_cont(int argcount, type2_T *argtypes)
+ret_first_cont(int argcount,
+	type2_T *argtypes,
+	type_T	**decl_type UNUSED)
 {
     if (argcount > 0)
     {
@@ -1137,13 +1221,17 @@ ret_first_cont(int argcount, type2_T *ar
 }
 // for getline()
     static type_T *
-ret_getline(int argcount, type2_T *argtypes UNUSED)
+ret_getline(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     return argcount == 1 ? &t_string : &t_list_string;
 }
 // for finddir()
     static type_T *
-ret_finddir(int argcount, type2_T *argtypes UNUSED)
+ret_finddir(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     if (argcount < 3)
 	return &t_string;
@@ -1156,7 +1244,9 @@ ret_finddir(int argcount, type2_T *argty
  * one.
  */
     static type_T *
-ret_list_or_dict_0(int argcount, type2_T *argtypes UNUSED)
+ret_list_or_dict_0(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     if (argcount > 0)
 	return &t_dict_any;
@@ -1168,7 +1258,9 @@ ret_list_or_dict_0(int argcount, type2_T
  * are two.
  */
     static type_T *
-ret_list_or_dict_1(int argcount, type2_T *argtypes UNUSED)
+ret_list_or_dict_1(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     if (argcount > 1)
 	return &t_dict_any;
@@ -1176,7 +1268,9 @@ ret_list_or_dict_1(int argcount, type2_T
 }
 
     static type_T *
-ret_argv(int argcount, type2_T *argtypes UNUSED)
+ret_argv(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     // argv() returns list of strings
     if (argcount == 0)
@@ -1187,13 +1281,20 @@ ret_argv(int argcount, type2_T *argtypes
 }
 
     static type_T *
-ret_remove(int argcount, type2_T *argtypes)
+ret_remove(int argcount,
+	type2_T *argtypes,
+	type_T	**decl_type UNUSED)
 {
     if (argcount > 0)
     {
 	if (argtypes[0].type_curr->tt_type == VAR_LIST
 		|| argtypes[0].type_curr->tt_type == VAR_DICT)
+	{
+	    if (argtypes[0].type_curr->tt_type
+					     == argtypes[0].type_decl->tt_type)
+		*decl_type = argtypes[0].type_decl->tt_member;
 	    return argtypes[0].type_curr->tt_member;
+	}
 	if (argtypes[0].type_curr->tt_type == VAR_BLOB)
 	    return &t_number;
     }
@@ -1201,7 +1302,9 @@ ret_remove(int argcount, type2_T *argtyp
 }
 
     static type_T *
-ret_getreg(int argcount, type2_T *argtypes UNUSED)
+ret_getreg(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     // Assume that if the third argument is passed it's non-zero
     if (argcount == 3)
@@ -1210,7 +1313,9 @@ ret_getreg(int argcount, type2_T *argtyp
 }
 
     static type_T *
-ret_maparg(int argcount, type2_T *argtypes UNUSED)
+ret_maparg(int argcount,
+	type2_T *argtypes UNUSED,
+	type_T	**decl_type UNUSED)
 {
     // Assume that if the fourth argument is passed it's non-zero
     if (argcount == 4)
@@ -1229,7 +1334,8 @@ typedef struct
     char	f_max_argc;	// maximal number of arguments
     char	f_argtype;	// for method: FEARG_ values
     argcheck_T	*f_argcheck;	// list of functions to check argument types
-    type_T	*(*f_retfunc)(int argcount, type2_T *argtypes);
+    type_T	*(*f_retfunc)(int argcount, type2_T *argtypes,
+							   type_T **decl_type);
 				// return type function
     void	(*f_func)(typval_T *args, typval_T *rvar);
 				// implementation of function
@@ -1533,7 +1639,7 @@ static funcentry_T global_functions[] =
     {"expandcmd",	1, 1, FEARG_1,	    arg1_string,
 			ret_string,	    f_expandcmd},
     {"extend",		2, 3, FEARG_1,	    arg23_extend,
-			ret_first_arg,	    f_extend},
+			ret_extend,	    f_extend},
     {"extendnew",	2, 3, FEARG_1,	    arg23_extendnew,
 			ret_first_cont,	    f_extendnew},
     {"feedkeys",	1, 2, FEARG_1,	    arg2_string,
@@ -1991,7 +2097,7 @@ static funcentry_T global_functions[] =
     {"rand",		0, 1, FEARG_1,	    arg1_list_number,
 			ret_number,	    f_rand},
     {"range",		1, 3, FEARG_1,	    arg3_number,
-			ret_list_number,    f_range},
+			ret_range,	    f_range},
     {"readblob",	1, 1, FEARG_1,	    arg1_string,
 			ret_blob,	    f_readblob},
     {"readdir",		1, 3, FEARG_1,	    arg3_string_any_dict,
@@ -2622,14 +2728,25 @@ internal_func_get_argcount(int idx, int 
 
 /*
  * Call the "f_retfunc" function to obtain the return type of function "idx".
+ * "decl_type" is set to the declared type.
  * "argtypes" is the list of argument types or NULL when there are no
  * arguments.
  * "argcount" may be less than the actual count when only getting the type.
  */
     type_T *
-internal_func_ret_type(int idx, int argcount, type2_T *argtypes)
-{
-    return global_functions[idx].f_retfunc(argcount, argtypes);
+internal_func_ret_type(
+	int	    idx,
+	int	    argcount,
+	type2_T	    *argtypes,
+	type_T	    **decl_type)
+{
+    type_T *ret;
+
+    *decl_type = NULL;
+    ret = global_functions[idx].f_retfunc(argcount, argtypes, decl_type);
+    if (*decl_type == NULL)
+	*decl_type = ret;
+    return ret;
 }
 
 /*
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -6,7 +6,7 @@ int has_internal_func(char_u *name);
 char *internal_func_name(int idx);
 int internal_func_check_arg_types(type2_T *types, int idx, int argcount, cctx_T *cctx);
 void internal_func_get_argcount(int idx, int *argcount, int *min_argcount);
-type_T *internal_func_ret_type(int idx, int argcount, type2_T *argtypes);
+type_T *internal_func_ret_type(int idx, int argcount, type2_T *argtypes, type_T **decl_type);
 int internal_func_is_map(int idx);
 int check_internal_func(int idx, int argcount);
 int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
--- a/src/proto/vim9type.pro
+++ b/src/proto/vim9type.pro
@@ -25,6 +25,7 @@ int push_type_stack(cctx_T *cctx, type_T
 int push_type_stack2(cctx_T *cctx, type_T *type, type_T *decl_type);
 void set_type_on_stack(cctx_T *cctx, type_T *type, int offset);
 type_T *get_type_on_stack(cctx_T *cctx, int offset);
+type_T *get_decl_type_on_stack(cctx_T *cctx, int offset);
 type_T *get_member_type_from_stack(int count, int skip, type_T **decl_type, cctx_T *cctx);
 char *vartype_name(vartype_T type);
 char *type_name(type_T *type, char **tofree);
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -77,7 +77,12 @@ enddef
 
 def Test_add()
   v9.CheckDefAndScriptFailure(['add({}, 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1226: List or Blob required for argument 1'])
-  v9.CheckDefFailure(['add([1], "a")'], 'E1012: Type mismatch; expected number but got string')
+  v9.CheckDefExecFailure([
+        'var ln: list<number> = [1]',
+        'add(ln, "a")'],
+        'E1012: Type mismatch; expected number but got string')
+  assert_equal([1, 'a'], add([1], 'a'))
+  assert_equal(0z1234, add(0z12, 0x34))
 
   var lines =<< trim END
     vim9script
@@ -2804,6 +2809,9 @@ def Test_range()
   v9.CheckDefAndScriptFailure(['range("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
   v9.CheckDefAndScriptFailure(['range(10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
   v9.CheckDefAndScriptFailure(['range(10, 20, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+
+  # returns a list<number> but it's not declared as such
+  assert_equal(['x', 'x'], range(2)->map((i, v) => 'x'))
 enddef
 
 def Test_readdir()
@@ -2980,6 +2988,10 @@ def Test_remove()
   var d2: any = {1: 'a', 2: 'b', 3: 'c'}
   remove(d2, 2)
   assert_equal({1: 'a', 3: 'c'}, d2)
+
+  # using declared type
+  var x: string = range(2)->extend(['x'])->remove(2)
+  assert_equal('x', x)
 enddef
 
 def Test_remove_return_type()
--- 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 */
 /**/
+    4279,
+/**/
     4278,
 /**/
     4277,
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1078,7 +1078,8 @@ generate_NEWLIST(cctx_T *cctx, int count
 
     // Get the member type and the declared member type from all the items on
     // the stack.
-    member_type = get_member_type_from_stack(count, 1, &decl_member_type, cctx);
+    member_type = get_member_type_from_stack(count, 1,
+						      &decl_member_type, cctx);
     type = get_list_type(member_type, cctx->ctx_type_list);
     decl_type = get_list_type(decl_member_type, cctx->ctx_type_list);
 
@@ -1277,6 +1278,7 @@ generate_BCALL(cctx_T *cctx, int func_id
     type2_T	shuffled_argtypes[MAX_FUNC_ARGS];
     type2_T	*maptype = NULL;
     type_T	*type;
+    type_T	*decl_type;
 
     RETURN_OK_IF_SKIP(cctx);
     argoff = check_internal_func(func_idx, argcount);
@@ -1327,8 +1329,8 @@ generate_BCALL(cctx_T *cctx, int func_id
 
     // Drop the argument types and push the return type.
     stack->ga_len -= argcount;
-    type = internal_func_ret_type(func_idx, argcount, argtypes);
-    if (push_type_stack(cctx, type) == FAIL)
+    type = internal_func_ret_type(func_idx, argcount, argtypes, &decl_type);
+    if (push_type_stack2(cctx, type, decl_type) == FAIL)
 	return FAIL;
 
     if (maptype != NULL && maptype[0].type_decl->tt_member != NULL
@@ -1351,7 +1353,9 @@ generate_LISTAPPEND(cctx_T *cctx)
     type_T	*expected;
 
     // Caller already checked that list_type is a list.
-    list_type = get_type_on_stack(cctx, 1);
+    // For checking the item type we use the declared type of the list and the
+    // current type of the added item, adding a string to [1, 2] is OK.
+    list_type = get_decl_type_on_stack(cctx, 1);
     item_type = get_type_on_stack(cctx, 0);
     expected = list_type->tt_member;
     if (need_type(item_type, expected, -1, 0, cctx, FALSE, FALSE) == FAIL)
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -357,8 +357,10 @@ typval2type_int(typval_T *tv, int copyID
 
 	    if (idx >= 0)
 	    {
+		type_T *decl_type;  // unused
+
 		internal_func_get_argcount(idx, &argcount, &min_argcount);
-		member_type = internal_func_ret_type(idx, 0, NULL);
+		member_type = internal_func_ret_type(idx, 0, NULL, &decl_type);
 	    }
 	    else
 		ufunc = find_func(name, FALSE);
@@ -1244,7 +1246,8 @@ set_type_on_stack(cctx_T *cctx, type_T *
 }
 
 /*
- * Get the type from the type stack.  If "offset" is zero the one at the top,
+ * Get the current type from the type stack.  If "offset" is zero the one at
+ * the top,
  * if "offset" is one the type above that, etc.
  * Returns &t_unknown if there is no such stack entry.
  */
@@ -1260,6 +1263,23 @@ get_type_on_stack(cctx_T *cctx, int offs
 }
 
 /*
+ * Get the declared type from the type stack.  If "offset" is zero the one at
+ * the top,
+ * if "offset" is one the type above that, etc.
+ * Returns &t_unknown if there is no such stack entry.
+ */
+    type_T *
+get_decl_type_on_stack(cctx_T *cctx, int offset)
+{
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if (offset + 1 > stack->ga_len)
+	return &t_unknown;
+    return (((type2_T *)stack->ga_data) + stack->ga_len - offset - 1)
+								   ->type_decl;
+}
+
+/*
  * Get the member type of a dict or list from the items on the stack of "cctx".
  * The declared type is stored in "decl_type".
  * For a list "skip" is 1, for a dict "skip" is 2, keys are skipped.