Patch 8.2.3179
Problem:    Vim9: cannot assign to an imported variable at script level.
Solution:   Lookup imported items when assigning.
Files:      src/evalvars.c, src/errors.h, src/eval.c,
            src/testdir/test_vim9_script.vim


*** ../vim-8.2.3178/src/evalvars.c      2021-07-11 20:58:54.788028521 +0200
--- src/evalvars.c      2021-07-18 20:15:55.536726437 +0200
***************
*** 3201,3206 ****
--- 3201,3207 ----
      typval_T  *tv = tv_arg;
      typval_T  bool_tv;
      dictitem_T        *di;
+     typval_T  *dest_tv = NULL;
      char_u    *varname;
      hashtab_T *ht;
      int               is_script_local;
***************
*** 3241,3422 ****
  
      di = find_var_in_ht(ht, 0, varname, TRUE);
  
!     // Search in parent scope which is possible to reference from lambda
!     if (di == NULL)
!       di = find_var_in_scoped_ht(name, TRUE);
! 
!     if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
!                                     && var_wrong_func_name(name, di == NULL))
!       goto failed;
! 
!     if (need_convert_to_bool(type, tv))
      {
!       // Destination is a bool and the value is not, but it can be converted.
!       CLEAR_FIELD(bool_tv);
!       bool_tv.v_type = VAR_BOOL;
!       bool_tv.vval.v_number = tv2bool(tv) ? VVAL_TRUE : VVAL_FALSE;
!       tv = &bool_tv;
!     }
  
!     if (di != NULL)
!     {
!       // Item already exists.  Allowed to replace when reloading.
!       if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
        {
!           if ((flags & (ASSIGN_CONST | ASSIGN_FINAL))
!                                            && (flags & ASSIGN_FOR_LOOP) == 0)
!           {
!               emsg(_(e_cannot_mod));
!               goto failed;
!           }
  
!           if (is_script_local && vim9script
!                             && (flags & (ASSIGN_NO_DECL | ASSIGN_DECL)) == 0)
            {
!               semsg(_(e_redefining_script_item_str), name);
                goto failed;
            }
  
!           if (var_in_vim9script)
!           {
!               where_T where;
  
!               // check the type and adjust to bool if needed
!               where.wt_index = var_idx;
!               where.wt_variable = TRUE;
!               if (check_script_var_type(&di->di_tv, tv, name, where) == FAIL)
!                   goto failed;
!           }
  
!           if (var_check_permission(di, name) == FAIL)
!               goto failed;
!       }
!       else
        {
!           // can only redefine once
!           di->di_flags &= ~DI_FLAGS_RELOAD;
! 
!           // A Vim9 script-local variable is also present in sn_all_vars and
!           // sn_var_vals.  It may set "type" from "tv".
!           if (var_in_vim9script)
!               update_vim9_script_var(FALSE, di, flags, tv, &type,
!                                        (flags & ASSIGN_NO_MEMBER_TYPE) == 0);
        }
  
!       // existing variable, need to clear the value
! 
!       // Handle setting internal di: variables separately where needed to
!       // prevent changing the type.
!       if (ht == &vimvarht)
        {
!           if (di->di_tv.v_type == VAR_STRING)
            {
!               VIM_CLEAR(di->di_tv.vval.v_string);
!               if (copy || tv->v_type != VAR_STRING)
                {
!                   char_u *val = tv_get_string(tv);
  
!                   // Careful: when assigning to v:errmsg and tv_get_string()
!                   // causes an error message the variable will already be set.
!                   if (di->di_tv.vval.v_string == NULL)
!                       di->di_tv.vval.v_string = vim_strsave(val);
                }
!               else
                {
!                   // Take over the string to avoid an extra alloc/free.
!                   di->di_tv.vval.v_string = tv->vval.v_string;
!                   tv->vval.v_string = NULL;
                }
!               goto failed;
            }
!           else if (di->di_tv.v_type == VAR_NUMBER)
            {
!               di->di_tv.vval.v_number = tv_get_number(tv);
!               if (STRCMP(varname, "searchforward") == 0)
!                   set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
! #ifdef FEAT_SEARCH_EXTRA
!               else if (STRCMP(varname, "hlsearch") == 0)
                {
!                   no_hlsearch = !di->di_tv.vval.v_number;
!                   redraw_all_later(SOME_VALID);
                }
  #endif
!               goto failed;
            }
!           else if (di->di_tv.v_type != tv->v_type)
            {
!               semsg(_("E963: setting %s to value with wrong type"), name);
                goto failed;
            }
-       }
  
!       clear_tv(&di->di_tv);
!     }
!     else
!     {
!       // Item not found, check if a function already exists.
!       if (is_script_local && (flags & (ASSIGN_NO_DECL | ASSIGN_DECL)) == 0
!                  && lookup_scriptitem(name, STRLEN(name), FALSE, NULL) == OK)
!       {
!           semsg(_(e_redefining_script_item_str), name);
!           goto failed;
!       }
  
!       // add a new variable
!       if (var_in_vim9script && (flags & ASSIGN_NO_DECL))
!       {
!           semsg(_(e_unknown_variable_str), name);
!           goto failed;
!       }
  
!       // Can't add "v:" or "a:" variable.
!       if (ht == &vimvarht || ht == get_funccal_args_ht())
!       {
!           semsg(_(e_illvar), name);
!           goto failed;
!       }
  
!       // Make sure the variable name is valid.  In Vim9 script an autoload
!       // variable must be prefixed with "g:".
!       if (!valid_varname(varname, !vim9script
!                                              || STRNCMP(name, "g:", 2) == 0))
!           goto failed;
  
!       di = alloc(sizeof(dictitem_T) + STRLEN(varname));
!       if (di == NULL)
!           goto failed;
!       STRCPY(di->di_key, varname);
!       if (hash_add(ht, DI2HIKEY(di)) == FAIL)
!       {
!           vim_free(di);
!           goto failed;
        }
!       di->di_flags = DI_FLAGS_ALLOC;
!       if (flags & (ASSIGN_CONST | ASSIGN_FINAL))
!           di->di_flags |= DI_FLAGS_LOCK;
! 
!       // A Vim9 script-local variable is also added to sn_all_vars and
!       // sn_var_vals. It may set "type" from "tv".
!       if (var_in_vim9script)
!           update_vim9_script_var(TRUE, di, flags, tv, &type,
!                                        (flags & ASSIGN_NO_MEMBER_TYPE) == 0);
      }
  
      if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
!       copy_tv(tv, &di->di_tv);
      else
      {
!       di->di_tv = *tv;
!       di->di_tv.v_lock = 0;
        init_tv(tv);
      }
  
      if (vim9script && type != NULL)
      {
!       if (type->tt_type == VAR_DICT && di->di_tv.vval.v_dict != NULL)
!           di->di_tv.vval.v_dict->dv_type = alloc_type(type);
!       else if (type->tt_type == VAR_LIST && di->di_tv.vval.v_list != NULL)
!           di->di_tv.vval.v_list->lv_type = alloc_type(type);
      }
  
      // ":const var = value" locks the value
--- 3242,3451 ----
  
      di = find_var_in_ht(ht, 0, varname, TRUE);
  
!     if (di == NULL && var_in_vim9script)
      {
!       imported_T  *import = find_imported(varname, 0, NULL);
  
!       if (import != NULL)
        {
!           scriptitem_T    *si = SCRIPT_ITEM(import->imp_sid);
!           svar_T          *sv;
  
!           // imported variable from another script
!           if ((flags & ASSIGN_NO_DECL) == 0)
            {
!               semsg(_(e_redefining_imported_item_str), name);
                goto failed;
            }
+           sv = ((svar_T *)si->sn_var_vals.ga_data)
+                                                   + import->imp_var_vals_idx;
+           // TODO: check the type
+           // TODO: check for const and locked
+           dest_tv = sv->sv_tv;
+       }
+     }
  
!     if (dest_tv == NULL)
!     {
!       // Search in parent scope which is possible to reference from lambda
!       if (di == NULL)
!           di = find_var_in_scoped_ht(name, TRUE);
  
!       if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
!                                         && var_wrong_func_name(name, di == 
NULL))
!           goto failed;
  
!       if (need_convert_to_bool(type, tv))
        {
!           // Destination is a bool and the value is not, but it can be 
converted.
!           CLEAR_FIELD(bool_tv);
!           bool_tv.v_type = VAR_BOOL;
!           bool_tv.vval.v_number = tv2bool(tv) ? VVAL_TRUE : VVAL_FALSE;
!           tv = &bool_tv;
        }
  
!       if (di != NULL)
        {
!           // Item already exists.  Allowed to replace when reloading.
!           if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
            {
!               if ((flags & (ASSIGN_CONST | ASSIGN_FINAL))
!                                                && (flags & ASSIGN_FOR_LOOP) 
== 0)
                {
!                   emsg(_(e_cannot_mod));
!                   goto failed;
!               }
  
!               if (is_script_local && vim9script
!                                 && (flags & (ASSIGN_NO_DECL | ASSIGN_DECL)) 
== 0)
!               {
!                   semsg(_(e_redefining_script_item_str), name);
!                   goto failed;
                }
! 
!               if (var_in_vim9script)
                {
!                   where_T where;
! 
!                   // check the type and adjust to bool if needed
!                   where.wt_index = var_idx;
!                   where.wt_variable = TRUE;
!                   if (check_script_var_type(&di->di_tv, tv, name, where) == 
FAIL)
!                       goto failed;
                }
! 
!               if (var_check_permission(di, name) == FAIL)
!                   goto failed;
            }
!           else
            {
!               // can only redefine once
!               di->di_flags &= ~DI_FLAGS_RELOAD;
! 
!               // A Vim9 script-local variable is also present in sn_all_vars 
and
!               // sn_var_vals.  It may set "type" from "tv".
!               if (var_in_vim9script)
!                   update_vim9_script_var(FALSE, di, flags, tv, &type,
!                                            (flags & ASSIGN_NO_MEMBER_TYPE) == 
0);
!           }
! 
!           // existing variable, need to clear the value
! 
!           // Handle setting internal di: variables separately where needed to
!           // prevent changing the type.
!           if (ht == &vimvarht)
!           {
!               if (di->di_tv.v_type == VAR_STRING)
                {
!                   VIM_CLEAR(di->di_tv.vval.v_string);
!                   if (copy || tv->v_type != VAR_STRING)
!                   {
!                       char_u *val = tv_get_string(tv);
! 
!                       // Careful: when assigning to v:errmsg and 
tv_get_string()
!                       // causes an error message the variable will already be 
set.
!                       if (di->di_tv.vval.v_string == NULL)
!                           di->di_tv.vval.v_string = vim_strsave(val);
!                   }
!                   else
!                   {
!                       // Take over the string to avoid an extra alloc/free.
!                       di->di_tv.vval.v_string = tv->vval.v_string;
!                       tv->vval.v_string = NULL;
!                   }
!                   goto failed;
                }
+               else if (di->di_tv.v_type == VAR_NUMBER)
+               {
+                   di->di_tv.vval.v_number = tv_get_number(tv);
+                   if (STRCMP(varname, "searchforward") == 0)
+                       set_search_direction(di->di_tv.vval.v_number ? '/' : 
'?');
+ #ifdef FEAT_SEARCH_EXTRA
+                   else if (STRCMP(varname, "hlsearch") == 0)
+                   {
+                       no_hlsearch = !di->di_tv.vval.v_number;
+                       redraw_all_later(SOME_VALID);
+                   }
  #endif
!                   goto failed;
!               }
!               else if (di->di_tv.v_type != tv->v_type)
!               {
!                   semsg(_("E963: setting %s to value with wrong type"), name);
!                   goto failed;
!               }
            }
! 
!           clear_tv(&di->di_tv);
!       }
!       else
!       {
!           // Item not found, check if a function already exists.
!           if (is_script_local && (flags & (ASSIGN_NO_DECL | ASSIGN_DECL)) == 0
!                      && lookup_scriptitem(name, STRLEN(name), FALSE, NULL) == 
OK)
            {
!               semsg(_(e_redefining_script_item_str), name);
                goto failed;
            }
  
!           // add a new variable
!           if (var_in_vim9script && (flags & ASSIGN_NO_DECL))
!           {
!               semsg(_(e_unknown_variable_str), name);
!               goto failed;
!           }
  
!           // Can't add "v:" or "a:" variable.
!           if (ht == &vimvarht || ht == get_funccal_args_ht())
!           {
!               semsg(_(e_illvar), name);
!               goto failed;
!           }
  
!           // Make sure the variable name is valid.  In Vim9 script an autoload
!           // variable must be prefixed with "g:".
!           if (!valid_varname(varname, !vim9script
!                                                  || STRNCMP(name, "g:", 2) == 
0))
!               goto failed;
  
!           di = alloc(sizeof(dictitem_T) + STRLEN(varname));
!           if (di == NULL)
!               goto failed;
!           STRCPY(di->di_key, varname);
!           if (hash_add(ht, DI2HIKEY(di)) == FAIL)
!           {
!               vim_free(di);
!               goto failed;
!           }
!           di->di_flags = DI_FLAGS_ALLOC;
!           if (flags & (ASSIGN_CONST | ASSIGN_FINAL))
!               di->di_flags |= DI_FLAGS_LOCK;
  
!           // A Vim9 script-local variable is also added to sn_all_vars and
!           // sn_var_vals. It may set "type" from "tv".
!           if (var_in_vim9script)
!               update_vim9_script_var(TRUE, di, flags, tv, &type,
!                                            (flags & ASSIGN_NO_MEMBER_TYPE) == 
0);
        }
! 
!       dest_tv = &di->di_tv;
      }
  
      if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
!       copy_tv(tv, dest_tv);
      else
      {
!       *dest_tv = *tv;
!       dest_tv->v_lock = 0;
        init_tv(tv);
      }
  
      if (vim9script && type != NULL)
      {
!       if (type->tt_type == VAR_DICT && dest_tv->vval.v_dict != NULL)
!           dest_tv->vval.v_dict->dv_type = alloc_type(type);
!       else if (type->tt_type == VAR_LIST && dest_tv->vval.v_list != NULL)
!           dest_tv->vval.v_list->lv_type = alloc_type(type);
      }
  
      // ":const var = value" locks the value
***************
*** 3425,3432 ****
        // Like :lockvar! name: lock the value and what it contains, but only
        // if the reference count is up to one.  That locks only literal
        // values.
!       item_lock(&di->di_tv, DICT_MAXNEST, TRUE, TRUE);
      return;
  failed:
      if (!copy)
        clear_tv(tv_arg);
--- 3454,3462 ----
        // Like :lockvar! name: lock the value and what it contains, but only
        // if the reference count is up to one.  That locks only literal
        // values.
!       item_lock(dest_tv, DICT_MAXNEST, TRUE, TRUE);
      return;
+ 
  failed:
      if (!copy)
        clear_tv(tv_arg);
*** ../vim-8.2.3178/src/errors.h        2021-07-18 14:43:39.791940898 +0200
--- src/errors.h        2021-07-18 20:00:02.658429341 +0200
***************
*** 506,508 ****
--- 506,510 ----
        INIT(= N_("E1211: List required for argument %d"));
  EXTERN char e_bool_required_for_argument_nr[]
        INIT(= N_("E1211: Bool required for argument %d"));
+ EXTERN char e_redefining_imported_item_str[]
+       INIT(= N_("E1212: Redefining imported item %s"));
*** ../vim-8.2.3178/src/eval.c  2021-07-18 17:08:47.028125894 +0200
--- src/eval.c  2021-07-18 20:22:41.640075959 +0200
***************
*** 1358,1364 ****
                         || (!var_check_ro(di->di_flags, lp->ll_name, FALSE)
                           && !tv_check_lock(&di->di_tv, lp->ll_name, FALSE)))
                        && tv_op(&tv, rettv, op) == OK)
!                   set_var(lp->ll_name, &tv, FALSE);
                clear_tv(&tv);
            }
        }
--- 1358,1365 ----
                         || (!var_check_ro(di->di_flags, lp->ll_name, FALSE)
                           && !tv_check_lock(&di->di_tv, lp->ll_name, FALSE)))
                        && tv_op(&tv, rettv, op) == OK)
!                   set_var_const(lp->ll_name, NULL, &tv, FALSE,
!                                                           ASSIGN_NO_DECL, 0);
                clear_tv(&tv);
            }
        }
*** ../vim-8.2.3178/src/testdir/test_vim9_script.vim    2021-07-18 
18:21:34.180266943 +0200
--- src/testdir/test_vim9_script.vim    2021-07-18 20:33:33.250982839 +0200
***************
*** 1062,1067 ****
--- 1062,1073 ----
    export def Exported(): string
      return 'Exported'
    enddef
+   export def ExportedValue(): number
+     return exported
+   enddef
+   export def ExportedInc()
+     exported += 5
+   enddef
    export final theList = [1]
  END
  
***************
*** 1073,1082 ****
  def Test_vim9_import_export()
    var import_script_lines =<< trim END
      vim9script
!     import {exported, Exported} from './Xexport.vim'
!     g:imported = exported
      exported += 3
!     g:imported_added = exported
      g:imported_func = Exported()
  
      def GetExported(): string
--- 1079,1099 ----
  def Test_vim9_import_export()
    var import_script_lines =<< trim END
      vim9script
!     import {exported, Exported, ExportedValue} from './Xexport.vim'
!     g:exported1 = exported
      exported += 3
!     g:exported2 = exported
!     g:exported3 = ExportedValue()
! 
!     import ExportedInc from './Xexport.vim'
!     ExportedInc()
!     g:exported_i1 = exported
!     g:exported_i2 = ExportedValue()
! 
!     exported = 11
!     g:exported_s1 = exported
!     g:exported_s2 = ExportedValue()
! 
      g:imported_func = Exported()
  
      def GetExported(): string
***************
*** 1091,1097 ****
      g:imported_name = exp_name
      exp_name ..= ' Doe'
      g:imported_name_appended = exp_name
!     g:imported_later = exported
  
      import theList from './Xexport.vim'
      theList->add(2)
--- 1108,1114 ----
      g:imported_name = exp_name
      exp_name ..= ' Doe'
      g:imported_name_appended = exp_name
!     g:exported_later = exported
  
      import theList from './Xexport.vim'
      theList->add(2)
***************
*** 1105,1113 ****
  
    assert_equal('bobbie', g:result)
    assert_equal('bob', g:localname)
!   assert_equal(9876, g:imported)
!   assert_equal(9879, g:imported_added)
!   assert_equal(9879, g:imported_later)
    assert_equal('Exported', g:imported_func)
    assert_equal('Exported', g:funcref_result)
    assert_equal('John', g:imported_name)
--- 1122,1138 ----
  
    assert_equal('bobbie', g:result)
    assert_equal('bob', g:localname)
!   assert_equal(9876, g:exported1)
!   assert_equal(9879, g:exported2)
!   assert_equal(9879, g:exported3)
! 
!   assert_equal(9884, g:exported_i1)
!   assert_equal(9884, g:exported_i2)
! 
!   assert_equal(11, g:exported_s1)
!   assert_equal(11, g:exported_s2)
!   assert_equal(11, g:exported_later)
! 
    assert_equal('Exported', g:imported_func)
    assert_equal('Exported', g:funcref_result)
    assert_equal('John', g:imported_name)
***************
*** 1115,1123 ****
    assert_false(exists('g:name'))
  
    Undo_export_script_lines()
!   unlet g:imported
!   unlet g:imported_added
!   unlet g:imported_later
    unlet g:imported_func
    unlet g:imported_name g:imported_name_appended
    delete('Ximport.vim')
--- 1140,1151 ----
    assert_false(exists('g:name'))
  
    Undo_export_script_lines()
!   unlet g:exported1
!   unlet g:exported2
!   unlet g:exported3
!   unlet g:exported_i1
!   unlet g:exported_i2
!   unlet g:exported_later
    unlet g:imported_func
    unlet g:imported_name g:imported_name_appended
    delete('Ximport.vim')
***************
*** 1131,1152 ****
          }
          from
          './Xexport.vim'
!     g:imported = exported
!     exported += 5
!     g:imported_added = exported
      g:imported_func = Exported()
    END
    writefile(import_line_break_script_lines, 'Ximport_lbr.vim')
    source Ximport_lbr.vim
  
!   assert_equal(9876, g:imported)
!   assert_equal(9881, g:imported_added)
    assert_equal('Exported', g:imported_func)
  
    # exported script not sourced again
    assert_false(exists('g:result'))
!   unlet g:imported
!   unlet g:imported_added
    unlet g:imported_func
    delete('Ximport_lbr.vim')
  
--- 1159,1180 ----
          }
          from
          './Xexport.vim'
!     g:exported = exported
!     exported += 7
!     g:exported_added = exported
      g:imported_func = Exported()
    END
    writefile(import_line_break_script_lines, 'Ximport_lbr.vim')
    source Ximport_lbr.vim
  
!   assert_equal(11, g:exported)
!   assert_equal(18, g:exported_added)
    assert_equal('Exported', g:imported_func)
  
    # exported script not sourced again
    assert_false(exists('g:result'))
!   unlet g:exported
!   unlet g:exported_added
    unlet g:imported_func
    delete('Ximport_lbr.vim')
  
***************
*** 1154,1171 ****
      vim9script
      import * as Export from './Xexport.vim'
      def UseExport()
!       g:imported_def = Export.exported
      enddef
!     g:imported_script = Export.exported
      assert_equal(1, exists('Export.exported'))
      assert_equal(0, exists('Export.notexported'))
      UseExport()
    END
    writefile(import_star_as_lines, 'Ximport.vim')
    source Ximport.vim
!   # FIXME: this should be 9881
!   assert_equal(9876, g:imported_def)
!   assert_equal(9876, g:imported_script)
  
    var import_star_as_lines_no_dot =<< trim END
      vim9script
--- 1182,1201 ----
      vim9script
      import * as Export from './Xexport.vim'
      def UseExport()
!       g:exported_def = Export.exported
      enddef
!     g:exported_script = Export.exported
      assert_equal(1, exists('Export.exported'))
      assert_equal(0, exists('Export.notexported'))
      UseExport()
    END
    writefile(import_star_as_lines, 'Ximport.vim')
    source Ximport.vim
! 
!   assert_equal(18, g:exported_def)
!   assert_equal(18, g:exported_script)
!   unlet g:exported_def
!   unlet g:exported_script
  
    var import_star_as_lines_no_dot =<< trim END
      vim9script
***************
*** 1234,1246 ****
          from
          './Xexport.vim'
      def UseExport()
!       g:imported = Export.exported
      enddef
      UseExport()
    END
    writefile(import_star_as_lbr_lines, 'Ximport.vim')
    source Ximport.vim
!   assert_equal(9876, g:imported)
  
    var import_star_lines =<< trim END
      vim9script
--- 1264,1277 ----
          from
          './Xexport.vim'
      def UseExport()
!       g:exported = Export.exported
      enddef
      UseExport()
    END
    writefile(import_star_as_lbr_lines, 'Ximport.vim')
    source Ximport.vim
!   assert_equal(18, g:exported)
!   unlet g:exported
  
    var import_star_lines =<< trim END
      vim9script
*** ../vim-8.2.3178/src/version.c       2021-07-18 18:21:34.180266943 +0200
--- src/version.c       2021-07-18 20:01:01.230319645 +0200
***************
*** 757,758 ****
--- 757,760 ----
  {   /* Add new patch number below this line */
+ /**/
+     3179,
  /**/

-- 
The CIA drives around in cars with the "Intel inside" logo.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202107181841.16IIf2WQ2813721%40masaka.moolenaar.net.

Raspunde prin e-mail lui