# HG changeset patch # User Bram Moolenaar # Date 1618340405 -7200 # Node ID 5c6ccab68d1ec096021613f050bdf6697293c6d2 # Parent c63e5ce43b69261c07d7d8a6677cdd8d85eac886 patch 8.2.2759: Vim9: for loop infers type of loop variable Commit: https://github.com/vim/vim/commit/f2253963c28e4791092620df6a6bb238c33168df Author: Bram Moolenaar Date: Tue Apr 13 20:53:13 2021 +0200 patch 8.2.2759: Vim9: for loop infers type of loop variable Problem: Vim9: for loop infers type of loop variable. Solution: Do not get the member type. (closes https://github.com/vim/vim/issues/8102) diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -2059,7 +2059,7 @@ filter_map(typval_T *argvars, typval_T * { // 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); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); } if (argvars[0].v_type == VAR_BLOB) @@ -2565,7 +2565,7 @@ extend(typval_T *argvars, typval_T *rett { // 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); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); } if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -12,7 +12,7 @@ void ex_import(exarg_T *eap); int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx, int verbose); char_u *handle_import(char_u *arg_start, garray_T *gap, int import_sid, evalarg_T *evalarg, void *cctx); char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg); -void update_vim9_script_var(int create, dictitem_T *di, int flags, typval_T *tv, type_T **type); +void update_vim9_script_var(int create, dictitem_T *di, int flags, typval_T *tv, type_T **type, int do_member); void hide_script_var(scriptitem_T *si, int idx, int func_defined); void free_all_script_vars(scriptitem_T *si); svar_T *find_typval_in_script(typval_T *dest); diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -9,7 +9,7 @@ type_T *alloc_func_type(type_T *ret_type type_T *get_func_type(type_T *ret_type, int argcount, garray_T *type_gap); int func_type_add_arg_types(type_T *functype, int argcount, garray_T *type_gap); int need_convert_to_bool(type_T *type, typval_T *tv); -type_T *typval2type(typval_T *tv, int copyID, garray_T *type_gap); +type_T *typval2type(typval_T *tv, int copyID, garray_T *type_gap, int do_member); type_T *typval2type_vimvar(typval_T *tv, garray_T *type_gap); int check_typval_arg_type(type_T *expected, typval_T *actual_tv, int arg_idx); int check_typval_type(type_T *expected, typval_T *actual_tv, where_T where); diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2295,70 +2295,82 @@ def Test_for_outside_of_function() enddef def Test_for_loop() - var result = '' - for cnt in range(7) - if cnt == 4 - break - endif - if cnt == 2 - continue - endif - result ..= cnt .. '_' - endfor - assert_equal('0_1_3_', result) - - var concat = '' - for str in eval('["one", "two"]') - concat ..= str - endfor - assert_equal('onetwo', concat) - - var total = 0 - for nr in - [1, 2, 3] - total += nr - endfor - assert_equal(6, total) - - total = 0 - for nr - in [1, 2, 3] - total += nr - endfor - assert_equal(6, total) - - total = 0 - for nr - in - [1, 2, 3] - total += nr - endfor - assert_equal(6, total) - + var lines =<< trim END + var result = '' + for cnt in range(7) + if cnt == 4 + break + endif + if cnt == 2 + continue + endif + result ..= cnt .. '_' + endfor + assert_equal('0_1_3_', result) + + var concat = '' + for str in eval('["one", "two"]') + concat ..= str + endfor + assert_equal('onetwo', concat) + + var total = 0 + for nr in + [1, 2, 3] + total += nr + endfor + assert_equal(6, total) + + total = 0 + for nr + in [1, 2, 3] + total += nr + endfor + assert_equal(6, total) + + total = 0 + for nr + in + [1, 2, 3] + total += nr + endfor + assert_equal(6, total) + + # loop over string + var res = '' + for c in 'aéc̀d' + res ..= c .. '-' + endfor + assert_equal('a-é-c̀-d-', res) + + res = '' + for c in '' + res ..= c .. '-' + endfor + assert_equal('', res) + + res = '' + for c in test_null_string() + res ..= c .. '-' + endfor + assert_equal('', res) + + var foo: list> = [ + {a: 'Cat'} + ] + for dd in foo + dd.counter = 12 + endfor + assert_equal([{a: 'Cat', counter: 12}], foo) + END + CheckDefAndScriptSuccess(lines) + + # TODO: should also work at script level var res = "" for [n: number, s: string] in [[1, 'a'], [2, 'b']] res ..= n .. s endfor assert_equal('1a2b', res) - - # loop over string - res = '' - for c in 'aéc̀d' - res ..= c .. '-' - endfor - assert_equal('a-é-c̀-d-', res) - - res = '' - for c in '' - res ..= c .. '-' - endfor - assert_equal('', res) - - res = '' - for c in test_null_string() - res ..= c .. '-' - endfor - assert_equal('', res) enddef def Test_for_loop_fails() @@ -2471,20 +2483,23 @@ def Test_for_loop_unpack() enddef def Test_for_loop_with_try_continue() - var looped = 0 - var cleanup = 0 - for i in range(3) - looped += 1 - try - eval [][0] - catch - continue - finally - cleanup += 1 - endtry - endfor - assert_equal(3, looped) - assert_equal(3, cleanup) + var lines =<< trim END + var looped = 0 + var cleanup = 0 + for i in range(3) + looped += 1 + try + eval [][0] + catch + continue + finally + cleanup += 1 + endtry + endfor + assert_equal(3, looped) + assert_equal(3, cleanup) + END + CheckDefAndScriptSuccess(lines) enddef def Test_while_loop() diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 2759, +/**/ 2758, /**/ 2757, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2157,6 +2157,7 @@ typedef enum { #define ASSIGN_NO_DECL 0x04 // "name = expr" without ":let"/":const"/":final" #define ASSIGN_DECL 0x08 // may declare variable if it does not exist #define ASSIGN_UNPACK 0x10 // using [a, b] = list +#define ASSIGN_NO_MEMBER_TYPE 0x20 // use "any" for list and dict member type #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -713,7 +713,8 @@ vim9_declare_scriptvar(exarg_T *eap, cha * When "create" is TRUE this is a new variable, otherwise find and update an * existing variable. * "flags" can have ASSIGN_FINAL or ASSIGN_CONST. - * When "*type" is NULL use "tv" for the type and update "*type". + * When "*type" is NULL use "tv" for the type and update "*type". If + * "do_member" is TRUE also use the member type, otherwise use "any". */ void update_vim9_script_var( @@ -721,7 +722,8 @@ update_vim9_script_var( dictitem_T *di, int flags, typval_T *tv, - type_T **type) + type_T **type, + int do_member) { scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); hashitem_T *hi; @@ -774,7 +776,8 @@ update_vim9_script_var( if (sv != NULL) { if (*type == NULL) - *type = typval2type(tv, get_copyID(), &si->sn_type_list); + *type = typval2type(tv, get_copyID(), &si->sn_type_list, + do_member); sv->sv_type = *type; } diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -252,9 +252,10 @@ func_type_add_arg_types( /* * Get a type_T for a typval_T. * "type_gap" is used to temporarily create types in. + * When "do_member" is TRUE also get the member type, otherwise use "any". */ static type_T * -typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) +typval2type_int(typval_T *tv, int copyID, garray_T *type_gap, int do_member) { type_T *type; type_T *member_type = &t_any; @@ -274,6 +275,8 @@ typval2type_int(typval_T *tv, int copyID if (l == NULL || l->lv_first == NULL) return &t_list_empty; + if (!do_member) + return &t_list_any; if (l->lv_first == &range_list_item) return &t_list_number; if (l->lv_copyID == copyID) @@ -282,9 +285,9 @@ typval2type_int(typval_T *tv, int copyID l->lv_copyID = copyID; // Use the common type of all members. - member_type = typval2type(&l->lv_first->li_tv, copyID, type_gap); + member_type = typval2type(&l->lv_first->li_tv, copyID, type_gap, TRUE); for (li = l->lv_first->li_next; li != NULL; li = li->li_next) - common_type(typval2type(&li->li_tv, copyID, type_gap), + common_type(typval2type(&li->li_tv, copyID, type_gap, TRUE), member_type, &member_type, type_gap); return get_list_type(member_type, type_gap); } @@ -297,6 +300,8 @@ typval2type_int(typval_T *tv, int copyID if (d == NULL || d->dv_hashtab.ht_used == 0) return &t_dict_empty; + if (!do_member) + return &t_dict_any; if (d->dv_copyID == copyID) // avoid recursion return &t_dict_any; @@ -305,9 +310,9 @@ typval2type_int(typval_T *tv, int copyID // Use the common type of all values. dict_iterate_start(tv, &iter); dict_iterate_next(&iter, &value); - member_type = typval2type(value, copyID, type_gap); + member_type = typval2type(value, copyID, type_gap, TRUE); while (dict_iterate_next(&iter, &value) != NULL) - common_type(typval2type(value, copyID, type_gap), + common_type(typval2type(value, copyID, type_gap, TRUE), member_type, &member_type, type_gap); return get_dict_type(member_type, type_gap); } @@ -378,11 +383,12 @@ need_convert_to_bool(type_T *type, typva /* * Get a type_T for a typval_T. * "type_list" is used to temporarily create types in. + * When "do_member" is TRUE also get the member type, otherwise use "any". */ type_T * -typval2type(typval_T *tv, int copyID, garray_T *type_gap) +typval2type(typval_T *tv, int copyID, garray_T *type_gap, int do_member) { - type_T *type = typval2type_int(tv, copyID, type_gap); + type_T *type = typval2type_int(tv, copyID, type_gap, do_member); if (type != NULL && type != &t_bool && (tv->v_type == VAR_NUMBER @@ -404,7 +410,7 @@ typval2type_vimvar(typval_T *tv, garray_ return &t_list_string; if (tv->v_type == VAR_DICT) // e.g. for v:completed_item return &t_dict_any; - return typval2type(tv, get_copyID(), type_gap); + return typval2type(tv, get_copyID(), type_gap, TRUE); } int @@ -429,7 +435,7 @@ check_typval_type(type_T *expected, typv int res = FAIL; ga_init2(&type_list, sizeof(type_T *), 10); - actual_type = typval2type(actual_tv, get_copyID(), &type_list); + actual_type = typval2type(actual_tv, get_copyID(), &type_list, TRUE); if (actual_type != NULL) res = check_type(expected, actual_type, TRUE, where); clear_type_list(&type_list); @@ -1210,7 +1216,7 @@ f_typename(typval_T *argvars, typval_T * rettv->v_type = VAR_STRING; ga_init2(&type_list, sizeof(type_T *), 10); - type = typval2type(argvars, get_copyID(), &type_list); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); name = type_name(type, &tofree); if (tofree != NULL) rettv->vval.v_string = (char_u *)tofree;