Compare commits

...

13 Commits

Author SHA1 Message Date
Bram Moolenaar
2c79e9d14d patch 8.2.1344: Vim9: No test for trying to redefine global function
Problem:    Vim9: No test for trying to redefine global function.
Solution:   Add a test.
2020-08-01 18:57:52 +02:00
Bram Moolenaar
333894b195 patch 8.2.1343: Vim9: cannot find global function when using g:
Problem:    Vim9: cannot find global function when using g: when local
            function with the same name exists.
Solution:   Find global function when using g:.
2020-08-01 18:53:07 +02:00
Bram Moolenaar
f5a48010ef patch 8.2.1342: Vim9: accidentally using "t" gives a confusing error
Problem:    Vim9: accidentally using "x" gives a confusing error.
Solution:   Disallow using ":t" in Vim9 script. (issue #6399)
2020-08-01 17:00:03 +02:00
Bram Moolenaar
2ec208172c patch 8.2.1341: build failures
Problem:    Build failures.
Solution:   Add missing error message.
2020-08-01 16:35:08 +02:00
Bram Moolenaar
b86abadf87 patch 8.2.1340: some tests fail on Cirrus CI and/or with FreeBSD
Problem:    Some tests fail on Cirrus CI and/or with FreeBSD.
Solution:   Make 'backupskip' empty. Do not run tests as root. Check for
            directory when using viminfo. (Ozaki Kiichi, closes #6596)
2020-08-01 16:08:19 +02:00
Bram Moolenaar
2caa1594e7 patch 8.2.1339: Vim9: assigning to global dict variable doesn't work
Problem:    Vim9: assigning to global dict variable doesn't work.
Solution:   Guess variable type based in index type. (issue #6591)
2020-08-01 15:53:19 +02:00
Bram Moolenaar
8e4c8c853e patch 8.2.1338: Vim9: assigning to script-local variable doesn't check type
Problem:    Vim9: assigning to script-local variable doesn't check type.
Solution:   Use the type. (issue #6591)
2020-08-01 15:38:38 +02:00
Bram Moolenaar
586268721d patch 8.2.1337: Vim9: cannot use empty key in dict assignment
Problem:    Vim9: cannot use empty key in dict assignment.
Solution:   Allow empty key. (closes #6591)
2020-08-01 14:06:38 +02:00
Bram Moolenaar
af50e899e7 patch 8.2.1336: build failure on non-Unix systems
Problem:    Build failure on non-Unix systems.
Solution:   Add #ifdef.
2020-08-01 13:22:10 +02:00
Bram Moolenaar
4e1d8bd79b patch 8.2.1335: CTRL-C in the GUI doesn't interrupt
Problem:    CTRL-C in the GUI doesn't interrupt. (Sergey Vlasov)
Solution:   Recognize "C" with CTRL modifier as CTRL-C. (issue #6565)
2020-08-01 13:10:14 +02:00
Bram Moolenaar
b53da7918c patch 8.2.1334: Github workflow timeout needs tuning
Problem:    Github workflow timeout needs tuning
Solution:   Use a 10 minute timeout. Fail when timing out. (Ken Takata,
            closes #6590)
2020-08-01 12:26:04 +02:00
Bram Moolenaar
af8edbb8dc patch 8.2.1333: Vim9: memory leak when using nested global function
Problem:    Vim9: memory leak when using nested global function.
Solution:   Swap from and to when copying the lines.
2020-08-01 00:03:09 +02:00
Bram Moolenaar
ce6583568f patch 8.2.1332: Vim9: memory leak when using nested global function
Problem:    Vim9: memory leak when using nested global function.
Solution:   Delete the function when deleting the instruction.  Disable test
            that still causes a leak.
2020-07-31 23:47:12 +02:00
23 changed files with 349 additions and 143 deletions

View File

@@ -11,6 +11,9 @@ freebsd_12_task:
- NPROC=$(getconf _NPROCESSORS_ONLN) - NPROC=$(getconf _NPROCESSORS_ONLN)
- ./configure --with-features=${FEATURES} - ./configure --with-features=${FEATURES}
- make -j${NPROC} - make -j${NPROC}
- src/vim --version
test_script: test_script:
- make test - src/vim --version
# run tests as user "cirrus" instead of root
- pw useradd cirrus -m
- chown -R cirrus:cirrus .
- sudo -u cirrus make test

View File

@@ -210,14 +210,18 @@ jobs:
echo %COL_GREEN%Wait for vim tests to finish.%COL_RESET% echo %COL_GREEN%Wait for vim tests to finish.%COL_RESET%
cd ..\src2\testdir cd ..\src2\testdir
:: Wait about 5 minutes. :: Wait about 10 minutes.
for /L %%i in (1,1,300) do ( for /L %%i in (1,1,600) do (
if exist done.txt goto exitloop if exist done.txt goto exitloop
ping -n 2 localhost > nul ping -n 2 localhost > nul
) )
echo %COL_RED%Timed out.%COL_RESET% set timeout=1
:exitloop :exitloop
echo %COL_GREEN%Test results of vim:%COL_RESET% echo %COL_GREEN%Test results of vim:%COL_RESET%
if exist messages type messages if exist messages type messages
nmake -nologo -f Make_dos.mak report VIMPROG=..\..\src\vim || exit 1 nmake -nologo -f Make_dos.mak report VIMPROG=..\..\src\vim || exit 1
if "%timeout%"=="1" (
echo %COL_RED%Timed out.%COL_RESET%
exit 1
)

View File

@@ -190,8 +190,8 @@ To intentionally avoid a variable being available later, a block can be used:
An existing variable cannot be assigned to with `:let`, since that implies a An existing variable cannot be assigned to with `:let`, since that implies a
declaration. Global, window, tab, buffer and Vim variables can only be used declaration. Global, window, tab, buffer and Vim variables can only be used
without `:let`, because they are are not really declared, they can also be without `:let`, because they are not really declared, they can also be deleted
deleted with `:unlet`. with `:unlet`.
Variables cannot shadow previously defined variables. Variables cannot shadow previously defined variables.
Variables may shadow Ex commands, rename the variable if needed. Variables may shadow Ex commands, rename the variable if needed.
@@ -352,10 +352,11 @@ No curly braces expansion ~
|curly-braces-names| cannot be used. |curly-braces-names| cannot be used.
No :xit, :append, :change or :insert ~ No :xit, :t, :append, :change or :insert ~
These commands are too easily confused with local variable names. Instead of These commands are too easily confused with local variable names.
`:x` or `:xit` you can use `:exit`. Instead of `:x` or `:xit` you can use `:exit`.
Instead of `:t` you can use `:copy`.
Comparators ~ Comparators ~

View File

@@ -7276,6 +7276,9 @@ ex_copymove(exarg_T *eap)
{ {
long n; long n;
if (not_in_vim9(eap) == FAIL)
return;
n = get_address(eap, &eap->arg, eap->addr_type, FALSE, FALSE, FALSE, 1); n = get_address(eap, &eap->arg, eap->addr_type, FALSE, FALSE, FALSE, 1);
if (eap->arg == NULL) // error detected if (eap->arg == NULL) // error detected
{ {

View File

@@ -1684,6 +1684,7 @@ EXTERN char e_readonlysbx[] INIT(= N_("E794: Cannot set variable in the sandbox:
EXTERN char e_stringreq[] INIT(= N_("E928: String required")); EXTERN char e_stringreq[] INIT(= N_("E928: String required"));
EXTERN char e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary")); EXTERN char e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary"));
EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required")); EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required"));
EXTERN char e_dictnull[] INIT(= N_("E1103: Dictionary not set"));
EXTERN char e_listidx[] INIT(= N_("E684: list index out of range: %ld")); EXTERN char e_listidx[] INIT(= N_("E684: list index out of range: %ld"));
EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %ld")); EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %ld"));
EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob")); EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));

View File

@@ -5575,3 +5575,27 @@ gui_handle_drop(
entered = FALSE; entered = FALSE;
} }
#endif #endif
/*
* Check if "key" is to interrupt us. Handles a key that has not had modifiers
* applied yet.
* Return the key with modifiers applied if so, NUL if not.
*/
int
check_for_interrupt(int key, int modifiers_arg)
{
int modifiers = modifiers_arg;
int c = merge_modifyOtherKeys(key, &modifiers);
if ((c == Ctrl_C && ctrl_c_interrupts)
#ifdef UNIX
|| (intr_char != Ctrl_C && c == intr_char)
#endif
)
{
got_int = TRUE;
return c;
}
return NUL;
}

View File

@@ -1254,11 +1254,16 @@ key_press_event(GtkWidget *widget UNUSED,
add_to_input_buf(string2, 3); add_to_input_buf(string2, 3);
} }
if (len == 1 && ((string[0] == Ctrl_C && ctrl_c_interrupts) // Check if the key interrupts.
|| (string[0] == intr_char && intr_char != Ctrl_C)))
{ {
trash_input_buf(); int int_ch = check_for_interrupt(key, modifiers);
got_int = TRUE;
if (int_ch != NUL)
{
trash_input_buf();
string[0] = int_ch;
len = 1;
}
} }
add_to_input_buf(string, len); add_to_input_buf(string, len);

View File

@@ -596,11 +596,17 @@ gui_ph_handle_keyboard(PtWidget_t *widget, void *data, PtCallbackInfo_t *info)
string[ len++ ] = ch; string[ len++ ] = ch;
} }
if (len == 1 && ((ch == Ctrl_C && ctrl_c_interrupts) // Check if the key interrupts.
|| ch == intr_char))
{ {
trash_input_buf(); int int_ch = check_for_interrupt(ch, modifiers);
got_int = TRUE;
if (int_ch != NUL)
{
ch = int_ch;
string[0] = ch;
len = 1;
trash_input_buf();
}
} }
if (len == 1 && string[0] == CSI) if (len == 1 && string[0] == CSI)

View File

@@ -970,14 +970,16 @@ gui_x11_key_hit_cb(
add_to_input_buf(string2, 3); add_to_input_buf(string2, 3);
} }
if (len == 1 && ((string[0] == Ctrl_C && ctrl_c_interrupts) // Check if the key interrupts.
#ifdef UNIX
|| (intr_char != 0 && string[0] == intr_char)
#endif
))
{ {
trash_input_buf(); int int_ch = check_for_interrupt(key, modifiers);
got_int = TRUE;
if (int_ch != NUL)
{
trash_input_buf();
string[0] = int_ch;
len = 1;
}
} }
add_to_input_buf(string, len); add_to_input_buf(string, len);

View File

@@ -65,4 +65,5 @@ void gui_update_screen(void);
char_u *get_find_dialog_text(char_u *arg, int *wwordp, int *mcasep); char_u *get_find_dialog_text(char_u *arg, int *wwordp, int *mcasep);
int gui_do_findrepl(int flags, char_u *find_text, char_u *repl_text, int down); int gui_do_findrepl(int flags, char_u *find_text, char_u *repl_text, int down);
void gui_handle_drop(int x, int y, int_u modifiers, char_u **fnames, int count); void gui_handle_drop(int x, int y, int_u modifiers, char_u **fnames, int count);
int check_for_interrupt(int key, int modifiers_arg);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -5,12 +5,13 @@ int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T
char_u *get_lambda_name(void); char_u *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg); int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
void copy_func(char_u *lambda, char_u *global);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
void emsg_funcname(char *ermsg, char_u *name); void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe);
char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
ufunc_T *find_func_even_dead(char_u *name, int is_global, cctx_T *cctx);
ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx); ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx);
void copy_func(char_u *lambda, char_u *global);
int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict); int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict);
void save_funccal(funccal_entry_T *entry); void save_funccal(funccal_entry_T *entry);
void restore_funccal(void); void restore_funccal(void);

View File

@@ -19,6 +19,22 @@ func Test_backup()
call delete('Xbackup.txt~') call delete('Xbackup.txt~')
endfunc endfunc
func Test_backup_backupskip()
set backup backupdir=. backupskip=*.txt
new
call setline(1, ['line1', 'line2'])
:f Xbackup.txt
:w! Xbackup.txt
" backup file is only created after
" writing a second time (before overwriting)
:w! Xbackup.txt
call assert_false(filereadable('Xbackup.txt~'))
bw!
set backup&vim backupdir&vim backupskip&vim
call delete('Xbackup.txt')
call delete('Xbackup.txt~')
endfunc
func Test_backup2() func Test_backup2()
set backup backupdir=.// backupskip= set backup backupdir=.// backupskip=
new new
@@ -30,7 +46,7 @@ func Test_backup2()
:w! Xbackup.txt :w! Xbackup.txt
sp *Xbackup.txt~ sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
let f=expand('%') let f = expand('%')
call assert_match('%testdir%Xbackup.txt\~', f) call assert_match('%testdir%Xbackup.txt\~', f)
bw! bw!
bw! bw!
@@ -50,7 +66,7 @@ func Test_backup2_backupcopy()
:w! Xbackup.txt :w! Xbackup.txt
sp *Xbackup.txt~ sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
let f=expand('%') let f = expand('%')
call assert_match('%testdir%Xbackup.txt\~', f) call assert_match('%testdir%Xbackup.txt\~', f)
bw! bw!
bw! bw!
@@ -61,14 +77,11 @@ endfunc
" Test for using a non-existing directory as a backup directory " Test for using a non-existing directory as a backup directory
func Test_non_existing_backupdir() func Test_non_existing_backupdir()
CheckNotBSD set backupdir=./non_existing_dir backupskip=
let save_backup = &backupdir
set backupdir=./non_existing_dir
call writefile(['line1'], 'Xfile') call writefile(['line1'], 'Xfile')
new Xfile new Xfile
" TODO: write doesn't fail in Cirrus FreeBSD CI test
call assert_fails('write', 'E510:') call assert_fails('write', 'E510:')
let &backupdir = save_backup set backupdir&vim backupskip&vim
call delete('Xfile') call delete('Xfile')
endfunc endfunc

View File

@@ -1682,7 +1682,6 @@ endfunc
" Test for editing a file without read permission " Test for editing a file without read permission
func Test_edit_file_no_read_perm() func Test_edit_file_no_read_perm()
CheckUnix CheckUnix
CheckNotBSD
call writefile(['one', 'two'], 'Xfile') call writefile(['one', 'two'], 'Xfile')
call setfperm('Xfile', '-w-------') call setfperm('Xfile', '-w-------')
new new

View File

@@ -133,6 +133,12 @@ def Test_nested_function()
CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:') CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
enddef enddef
func Test_call_default_args_from_func()
call assert_equal('string', MyDefaultArgs())
call assert_equal('one', MyDefaultArgs('one'))
call assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
endfunc
def Test_nested_global_function() def Test_nested_global_function()
let lines =<< trim END let lines =<< trim END
vim9script vim9script
@@ -141,7 +147,7 @@ def Test_nested_global_function()
return 'inner' return 'inner'
enddef enddef
enddef enddef
disass Outer defcompile
Outer() Outer()
assert_equal('inner', g:Inner()) assert_equal('inner', g:Inner())
delfunc g:Inner delfunc g:Inner
@@ -153,13 +159,35 @@ def Test_nested_global_function()
delfunc g:Inner delfunc g:Inner
END END
CheckScriptSuccess(lines) CheckScriptSuccess(lines)
lines =<< trim END
vim9script
def Outer()
def g:Inner(): string
return 'inner'
enddef
enddef
defcompile
Outer()
Outer()
END
CheckScriptFailure(lines, "E122:")
enddef enddef
func Test_call_default_args_from_func() def Test_global_local_function()
call assert_equal('string', MyDefaultArgs()) let lines =<< trim END
call assert_equal('one', MyDefaultArgs('one')) vim9script
call assert_fails('call MyDefaultArgs("one", "two")', 'E118:') def g:Func(): string
endfunc return 'global'
enddef
def Func(): string
return 'local'
enddef
assert_equal('global', g:Func())
assert_equal('local', Func())
END
CheckScriptSuccess(lines)
enddef
func TakesOneArg(arg) func TakesOneArg(arg)
echo a:arg echo a:arg

View File

@@ -244,10 +244,60 @@ def Test_assignment_dict()
# overwrite # overwrite
dict3['key'] = 'another' dict3['key'] = 'another'
call CheckDefExecFailure(['let dd = {}', 'dd[""] = 6'], 'E713:') # empty key can be used
let dd = {}
dd[""] = 6
assert_equal({'': 6}, dd)
# type becomes dict<any> # type becomes dict<any>
let somedict = rand() > 0 ? #{a: 1, b: 2} : #{a: 'a', b: 'b'} let somedict = rand() > 0 ? #{a: 1, b: 2} : #{a: 'a', b: 'b'}
# assignment to script-local dict
let lines =<< trim END
vim9script
let test: dict<any> = {}
def FillDict(): dict<any>
test['a'] = 43
return test
enddef
assert_equal(#{a: 43}, FillDict())
END
call CheckScriptSuccess(lines)
lines =<< trim END
vim9script
let test: dict<any>
def FillDict(): dict<any>
test['a'] = 43
return test
enddef
FillDict()
END
call CheckScriptFailure(lines, 'E1103:')
# assignment to global dict
lines =<< trim END
vim9script
g:test = {}
def FillDict(): dict<any>
g:test['a'] = 43
return g:test
enddef
assert_equal(#{a: 43}, FillDict())
END
call CheckScriptSuccess(lines)
# assignment to buffer dict
lines =<< trim END
vim9script
b:test = {}
def FillDict(): dict<any>
b:test['a'] = 43
return b:test
enddef
assert_equal(#{a: 43}, FillDict())
END
call CheckScriptSuccess(lines)
enddef enddef
def Test_assignment_local() def Test_assignment_local()
@@ -783,13 +833,6 @@ def Test_try_catch()
endtry endtry
assert_equal(300, n) assert_equal(300, n)
try
d[''] = 3
catch /E713:/
n = 311
endtry
assert_equal(311, n)
try try
unlet g:does_not_exist unlet g:does_not_exist
catch /E108:/ catch /E108:/
@@ -1585,18 +1628,21 @@ def Test_fixed_size_list()
enddef enddef
def Test_no_insert_xit() def Test_no_insert_xit()
call CheckDefExecFailure(['x = 1'], 'E1100:')
call CheckDefExecFailure(['a = 1'], 'E1100:') call CheckDefExecFailure(['a = 1'], 'E1100:')
call CheckDefExecFailure(['i = 1'], 'E1100:')
call CheckDefExecFailure(['c = 1'], 'E1100:') call CheckDefExecFailure(['c = 1'], 'E1100:')
call CheckDefExecFailure(['i = 1'], 'E1100:')
call CheckDefExecFailure(['t = 1'], 'E1100:')
call CheckDefExecFailure(['x = 1'], 'E1100:')
CheckScriptFailure(['vim9script', 'x = 1'], 'E1100:')
CheckScriptFailure(['vim9script', 'a = 1'], 'E488:') CheckScriptFailure(['vim9script', 'a = 1'], 'E488:')
CheckScriptFailure(['vim9script', 'a'], 'E1100:') CheckScriptFailure(['vim9script', 'a'], 'E1100:')
CheckScriptFailure(['vim9script', 'i = 1'], 'E488:')
CheckScriptFailure(['vim9script', 'i'], 'E1100:')
CheckScriptFailure(['vim9script', 'c = 1'], 'E488:') CheckScriptFailure(['vim9script', 'c = 1'], 'E488:')
CheckScriptFailure(['vim9script', 'c'], 'E1100:') CheckScriptFailure(['vim9script', 'c'], 'E1100:')
CheckScriptFailure(['vim9script', 'i = 1'], 'E488:')
CheckScriptFailure(['vim9script', 'i'], 'E1100:')
CheckScriptFailure(['vim9script', 't'], 'E1100:')
CheckScriptFailure(['vim9script', 't = 1'], 'E1100:')
CheckScriptFailure(['vim9script', 'x = 1'], 'E1100:')
enddef enddef
def IfElse(what: number): string def IfElse(what: number): string

View File

@@ -807,7 +807,7 @@ func Test_viminfo_perm()
" Try to write the viminfo to a directory " Try to write the viminfo to a directory
call mkdir('Xdir') call mkdir('Xdir')
call assert_fails('wviminfo Xdir', 'E886:') call assert_fails('wviminfo Xdir', 'E137:')
call delete('Xdir', 'rf') call delete('Xdir', 'rf')
endfunc endfunc

View File

@@ -136,9 +136,7 @@ func Test_writefile_sync_arg()
endfunc endfunc
func Test_writefile_sync_dev_stdout() func Test_writefile_sync_dev_stdout()
if !has('unix') CheckUnix
return
endif
if filewritable('/dev/stdout') if filewritable('/dev/stdout')
" Just check that this doesn't cause an error. " Just check that this doesn't cause an error.
call writefile(['one'], '/dev/stdout') call writefile(['one'], '/dev/stdout')
@@ -371,13 +369,10 @@ endfunc
" Test for writing to a readonly file " Test for writing to a readonly file
func Test_write_readonly() func Test_write_readonly()
" In Cirrus-CI, the freebsd tests are run under a root account. So this test
" doesn't fail.
CheckNotBSD
call writefile([], 'Xfile') call writefile([], 'Xfile')
call setfperm('Xfile', "r--------") call setfperm('Xfile', "r--------")
edit Xfile edit Xfile
set noreadonly set noreadonly backupskip=
call assert_fails('write', 'E505:') call assert_fails('write', 'E505:')
let save_cpo = &cpo let save_cpo = &cpo
set cpo+=W set cpo+=W
@@ -386,37 +381,32 @@ func Test_write_readonly()
call setline(1, ['line1']) call setline(1, ['line1'])
write! write!
call assert_equal(['line1'], readfile('Xfile')) call assert_equal(['line1'], readfile('Xfile'))
set backupskip&
call delete('Xfile') call delete('Xfile')
endfunc endfunc
" Test for 'patchmode' " Test for 'patchmode'
func Test_patchmode() func Test_patchmode()
CheckNotBSD
call writefile(['one'], 'Xfile') call writefile(['one'], 'Xfile')
set patchmode=.orig nobackup writebackup set patchmode=.orig nobackup backupskip= writebackup
new Xfile new Xfile
call setline(1, 'two') call setline(1, 'two')
" first write should create the .orig file " first write should create the .orig file
write write
" TODO: Xfile.orig is not created in Cirrus FreeBSD CI test
call assert_equal(['one'], readfile('Xfile.orig')) call assert_equal(['one'], readfile('Xfile.orig'))
call setline(1, 'three') call setline(1, 'three')
" subsequent writes should not create/modify the .orig file " subsequent writes should not create/modify the .orig file
write write
call assert_equal(['one'], readfile('Xfile.orig')) call assert_equal(['one'], readfile('Xfile.orig'))
set patchmode& backup& writebackup& set patchmode& backup& backupskip& writebackup&
call delete('Xfile') call delete('Xfile')
call delete('Xfile.orig') call delete('Xfile.orig')
endfunc endfunc
" Test for writing to a file in a readonly directory " Test for writing to a file in a readonly directory
func Test_write_readonly_dir() func Test_write_readonly_dir()
if !has('unix') || has('bsd') " On MS-Windows, modifying files in a read-only directory is allowed.
" On MS-Windows, modifying files in a read-only directory is allowed. CheckUnix
" In Cirrus-CI for Freebsd, tests are run under a root account where
" modifying files in a read-only directory are allowed.
return
endif
call mkdir('Xdir') call mkdir('Xdir')
call writefile(['one'], 'Xdir/Xfile1') call writefile(['one'], 'Xdir/Xfile1')
call setfperm('Xdir', 'r-xr--r--') call setfperm('Xdir', 'r-xr--r--')
@@ -426,12 +416,12 @@ func Test_write_readonly_dir()
call assert_fails('write', 'E212:') call assert_fails('write', 'E212:')
" try to create a backup file in the directory " try to create a backup file in the directory
edit! Xdir/Xfile1 edit! Xdir/Xfile1
set backupdir=./Xdir set backupdir=./Xdir backupskip=
set patchmode=.orig set patchmode=.orig
call assert_fails('write', 'E509:') call assert_fails('write', 'E509:')
call setfperm('Xdir', 'rwxr--r--') call setfperm('Xdir', 'rwxr--r--')
call delete('Xdir', 'rf') call delete('Xdir', 'rf')
set backupdir& patchmode& set backupdir& backupskip& patchmode&
endfunc endfunc
" Test for writing a file using invalid file encoding " Test for writing a file using invalid file encoding

View File

@@ -780,7 +780,7 @@ find_func_with_sid(char_u *name, int sid)
* When "is_global" is true don't find script-local or imported functions. * When "is_global" is true don't find script-local or imported functions.
* Return NULL for unknown function. * Return NULL for unknown function.
*/ */
static ufunc_T * ufunc_T *
find_func_even_dead(char_u *name, int is_global, cctx_T *cctx) find_func_even_dead(char_u *name, int is_global, cctx_T *cctx)
{ {
hashitem_T *hi; hashitem_T *hi;
@@ -789,9 +789,10 @@ find_func_even_dead(char_u *name, int is_global, cctx_T *cctx)
if (!is_global) if (!is_global)
{ {
char_u *after_script = NULL; int vim9script = in_vim9script();
char_u *after_script = NULL;
if (in_vim9script()) if (vim9script)
{ {
// Find script-local function before global one. // Find script-local function before global one.
func = find_func_with_sid(name, current_sctx.sc_sid); func = find_func_with_sid(name, current_sctx.sc_sid);
@@ -799,7 +800,7 @@ find_func_even_dead(char_u *name, int is_global, cctx_T *cctx)
return func; return func;
} }
if (!in_vim9script() if (!vim9script
&& name[0] == K_SPECIAL && name[0] == K_SPECIAL
&& name[1] == KS_EXTRA && name[1] == KS_EXTRA
&& name[2] == KE_SNR) && name[2] == KE_SNR)
@@ -815,7 +816,7 @@ find_func_even_dead(char_u *name, int is_global, cctx_T *cctx)
else else
after_script = NULL; after_script = NULL;
} }
if (in_vim9script() || after_script != NULL) if (vim9script || after_script != NULL)
{ {
// Find imported function before global one. // Find imported function before global one.
imported = find_imported( imported = find_imported(
@@ -1188,10 +1189,10 @@ copy_func(char_u *lambda, char_u *global)
fp->uf_flags = (ufunc->uf_flags & ~FC_VIM9) | FC_COPY; fp->uf_flags = (ufunc->uf_flags & ~FC_VIM9) | FC_COPY;
fp->uf_def_status = ufunc->uf_def_status; fp->uf_def_status = ufunc->uf_def_status;
fp->uf_dfunc_idx = ufunc->uf_dfunc_idx; fp->uf_dfunc_idx = ufunc->uf_dfunc_idx;
if (ga_copy_strings(&fp->uf_args, &ufunc->uf_args) == FAIL if (ga_copy_strings(&ufunc->uf_args, &fp->uf_args) == FAIL
|| ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args) || ga_copy_strings(&ufunc->uf_def_args, &fp->uf_def_args)
== FAIL == FAIL
|| ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines) == FAIL) || ga_copy_strings(&ufunc->uf_lines, &fp->uf_lines) == FAIL)
goto failed; goto failed;
fp->uf_name_exp = ufunc->uf_name_exp == NULL ? NULL fp->uf_name_exp = ufunc->uf_name_exp == NULL ? NULL
@@ -1759,7 +1760,7 @@ delete_script_functions(int sid)
{ {
hashitem_T *hi; hashitem_T *hi;
ufunc_T *fp; ufunc_T *fp;
long_u todo; long_u todo = 1;
char_u buf[30]; char_u buf[30];
size_t len; size_t len;
@@ -1769,18 +1770,27 @@ delete_script_functions(int sid)
sprintf((char *)buf + 3, "%d_", sid); sprintf((char *)buf + 3, "%d_", sid);
len = STRLEN(buf); len = STRLEN(buf);
todo = func_hashtab.ht_used; while (todo > 0)
for (hi = func_hashtab.ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) todo = func_hashtab.ht_used;
{ for (hi = func_hashtab.ht_array; todo > 0; ++hi)
fp = HI2UF(hi); if (!HASHITEM_EMPTY(hi))
if (STRNCMP(fp->uf_name, buf, len) == 0)
{ {
fp->uf_flags |= FC_DEAD; fp = HI2UF(hi);
func_clear(fp, TRUE); if (STRNCMP(fp->uf_name, buf, len) == 0)
{
int changed = func_hashtab.ht_changed;
fp->uf_flags |= FC_DEAD;
func_clear(fp, TRUE);
// When clearing a function another function can be cleared
// as a side effect. When that happens start over.
if (changed != func_hashtab.ht_changed)
break;
}
--todo;
} }
--todo; }
}
} }
#if defined(EXITFREE) || defined(PROTO) #if defined(EXITFREE) || defined(PROTO)
@@ -2077,10 +2087,14 @@ call_func(
if (error == FCERR_NONE && funcexe->evaluate) if (error == FCERR_NONE && funcexe->evaluate)
{ {
char_u *rfname = fname; char_u *rfname = fname;
int is_global = FALSE;
// Ignore "g:" before a function name. // Skip "g:" before a function name.
if (fp == NULL && fname[0] == 'g' && fname[1] == ':') if (fp == NULL && fname[0] == 'g' && fname[1] == ':')
{
is_global = TRUE;
rfname = fname + 2; rfname = fname + 2;
}
rettv->v_type = VAR_NUMBER; // default rettv is number zero rettv->v_type = VAR_NUMBER; // default rettv is number zero
rettv->vval.v_number = 0; rettv->vval.v_number = 0;
@@ -2092,7 +2106,7 @@ call_func(
* User defined function. * User defined function.
*/ */
if (fp == NULL) if (fp == NULL)
fp = find_func(rfname, FALSE, NULL); fp = find_func(rfname, is_global, NULL);
// Trigger FuncUndefined event, may load the function. // Trigger FuncUndefined event, may load the function.
if (fp == NULL if (fp == NULL
@@ -2101,13 +2115,13 @@ call_func(
&& !aborting()) && !aborting())
{ {
// executed an autocommand, search for the function again // executed an autocommand, search for the function again
fp = find_func(rfname, FALSE, NULL); fp = find_func(rfname, is_global, NULL);
} }
// Try loading a package. // Try loading a package.
if (fp == NULL && script_autoload(rfname, TRUE) && !aborting()) if (fp == NULL && script_autoload(rfname, TRUE) && !aborting())
{ {
// loaded a package, search for the function again // loaded a package, search for the function again
fp = find_func(rfname, FALSE, NULL); fp = find_func(rfname, is_global, NULL);
} }
if (fp == NULL) if (fp == NULL)
{ {
@@ -2116,7 +2130,7 @@ call_func(
// If using Vim9 script try not local to the script. // If using Vim9 script try not local to the script.
// TODO: should not do this if the name started with "s:". // TODO: should not do this if the name started with "s:".
if (p != NULL) if (p != NULL)
fp = find_func(p, FALSE, NULL); fp = find_func(p, is_global, NULL);
} }
if (fp != NULL && (fp->uf_flags & FC_DELETED)) if (fp != NULL && (fp->uf_flags & FC_DELETED))
@@ -2166,6 +2180,7 @@ call_func(
*/ */
error = call_internal_func(fname, argcount, argvars, rettv); error = call_internal_func(fname, argcount, argvars, rettv);
} }
/* /*
* The function call (or "FuncUndefined" autocommand sequence) might * The function call (or "FuncUndefined" autocommand sequence) might
* have been aborted by an error, an interrupt, or an explicitly thrown * have been aborted by an error, an interrupt, or an explicitly thrown

View File

@@ -754,6 +754,32 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1344,
/**/
1343,
/**/
1342,
/**/
1341,
/**/
1340,
/**/
1339,
/**/
1338,
/**/
1337,
/**/
1336,
/**/
1335,
/**/
1334,
/**/
1333,
/**/
1332,
/**/ /**/
1331, 1331,
/**/ /**/

View File

@@ -5070,6 +5070,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
char_u *ret = NULL; char_u *ret = NULL;
int var_count = 0; int var_count = 0;
int var_idx; int var_idx;
int scriptvar_sid = 0;
int scriptvar_idx = -1;
int semicolon = 0; int semicolon = 0;
garray_T *instr = &cctx->ctx_instr; garray_T *instr = &cctx->ctx_instr;
garray_T *stack = &cctx->ctx_type_stack; garray_T *stack = &cctx->ctx_type_stack;
@@ -5333,7 +5335,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
} }
else else
{ {
int idx; int idx;
imported_T *import = NULL;
for (idx = 0; reserved[idx] != NULL; ++idx) for (idx = 0; reserved[idx] != NULL; ++idx)
if (STRCMP(reserved[idx], name) == 0) if (STRCMP(reserved[idx], name) == 0)
@@ -5374,9 +5377,11 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
} }
else if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0) else if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0)
|| lookup_script(var_start, varlen) == OK || lookup_script(var_start, varlen) == OK
|| find_imported(var_start, varlen, cctx) != NULL) || (import = find_imported(var_start, varlen, cctx))
!= NULL)
{ {
dest = dest_script; char_u *rawname = name + (name[1] == ':' ? 2 : 0);
if (is_decl) if (is_decl)
{ {
if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0)) if ((varlen > 1 && STRNCMP(var_start, "s:", 2) == 0))
@@ -5387,6 +5392,21 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
name); name);
goto theend; goto theend;
} }
dest = dest_script;
// existing script-local variables should have a type
scriptvar_sid = current_sctx.sc_sid;
if (import != NULL)
scriptvar_sid = import->imp_sid;
scriptvar_idx = get_script_item_idx(scriptvar_sid,
rawname, TRUE);
if (scriptvar_idx >= 0)
{
scriptitem_T *si = SCRIPT_ITEM(scriptvar_sid);
svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+ scriptvar_idx;
type = sv->sv_type;
}
} }
else if (name[1] == ':' && name[2] != NUL) else if (name[1] == ':' && name[2] != NUL)
{ {
@@ -5466,11 +5486,9 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
{ {
has_index = TRUE; has_index = TRUE;
if (type->tt_member == NULL) if (type->tt_member == NULL)
{ member_type = &t_any;
semsg(_("E1088: cannot use an index on %s"), name); else
goto theend; member_type = type->tt_member;
}
member_type = type->tt_member;
} }
else else
{ {
@@ -5699,6 +5717,18 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
emsg(_(e_missbrac)); emsg(_(e_missbrac));
goto theend; goto theend;
} }
if (type == &t_any)
{
type_T *idx_type = ((type_T **)stack->ga_data)[
stack->ga_len - 1];
// Index on variable of unknown type: guess the type from the
// index type: number is dict, otherwise dict.
// TODO: should do the assignment at runtime
if (idx_type->tt_type == VAR_NUMBER)
type = &t_list_any;
else
type = &t_dict_any;
}
if (type->tt_type == VAR_DICT if (type->tt_type == VAR_DICT
&& may_generate_2STRING(-1, cctx) == FAIL) && may_generate_2STRING(-1, cctx) == FAIL)
goto theend; goto theend;
@@ -5766,21 +5796,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
break; break;
case dest_script: case dest_script:
{ {
char_u *rawname = name + (name[1] == ':' ? 2 : 0); if (scriptvar_idx < 0)
imported_T *import = NULL;
int sid = current_sctx.sc_sid;
int idx;
if (name[1] != ':')
{
import = find_imported(name, 0, cctx);
if (import != NULL)
sid = import->imp_sid;
}
idx = get_script_item_idx(sid, rawname, TRUE);
// TODO: specific type
if (idx < 0)
{ {
char_u *name_s = name; char_u *name_s = name;
@@ -5796,14 +5812,14 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
vim_snprintf((char *)name_s, len, vim_snprintf((char *)name_s, len,
"s:%s", name); "s:%s", name);
} }
generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, generate_OLDSCRIPT(cctx, ISN_STORES, name_s,
&t_any); scriptvar_sid, type);
if (name_s != name) if (name_s != name)
vim_free(name_s); vim_free(name_s);
} }
else else
generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT,
sid, idx, &t_any); scriptvar_sid, scriptvar_idx, type);
} }
break; break;
case dest_local: case dest_local:
@@ -7451,6 +7467,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
case CMD_append: case CMD_append:
case CMD_change: case CMD_change:
case CMD_insert: case CMD_insert:
case CMD_t:
case CMD_xit: case CMD_xit:
not_in_vim9(&ea); not_in_vim9(&ea);
goto erret; goto erret;
@@ -7677,8 +7694,21 @@ delete_instr(isn_T *isn)
break; break;
case ISN_NEWFUNC: case ISN_NEWFUNC:
vim_free(isn->isn_arg.newfunc.nf_lambda); {
vim_free(isn->isn_arg.newfunc.nf_global); char_u *lambda = isn->isn_arg.newfunc.nf_lambda;
ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL);
if (ufunc != NULL)
{
// Clear uf_dfunc_idx so that the function is deleted.
clear_def_function(ufunc);
ufunc->uf_dfunc_idx = 0;
func_ptr_unref(ufunc);
}
vim_free(lambda);
vim_free(isn->isn_arg.newfunc.nf_global);
}
break; break;
case ISN_2BOOL: case ISN_2BOOL:

View File

@@ -1422,11 +1422,13 @@ call_def_function(
dict_T *dict = tv_dict->vval.v_dict; dict_T *dict = tv_dict->vval.v_dict;
dictitem_T *di; dictitem_T *di;
if (key == NULL || *key == NUL) if (dict == NULL)
{ {
emsg(_(e_emptykey)); emsg(_(e_dictnull));
goto on_error; goto on_error;
} }
if (key == NULL)
key = (char_u *)"";
tv = STACK_TV_BOT(-3); tv = STACK_TV_BOT(-3);
di = dict_find(dict, key, -1); di = dict_find(dict, key, -1);
if (di != NULL) if (di != NULL)

View File

@@ -67,9 +67,10 @@ not_in_vim9(exarg_T *eap)
if (in_vim9script()) if (in_vim9script())
switch (eap->cmdidx) switch (eap->cmdidx)
{ {
case CMD_insert:
case CMD_append: case CMD_append:
case CMD_change: case CMD_change:
case CMD_insert:
case CMD_t:
case CMD_xit: case CMD_xit:
semsg(_("E1100: Missing :let: %s"), eap->cmd); semsg(_("E1100: Missing :let: %s"), eap->cmd);
return FAIL; return FAIL;

View File

@@ -3007,6 +3007,7 @@ read_viminfo(
{ {
FILE *fp; FILE *fp;
char_u *fname; char_u *fname;
stat_T st; // mch_stat() of existing viminfo file
if (no_viminfo()) if (no_viminfo())
return FAIL; return FAIL;
@@ -3031,6 +3032,11 @@ read_viminfo(
vim_free(fname); vim_free(fname);
if (fp == NULL) if (fp == NULL)
return FAIL; return FAIL;
if (mch_fstat(fileno(fp), &st) < 0 || S_ISDIR(st.st_mode))
{
fclose(fp);
return FAIL;
}
viminfo_errcnt = 0; viminfo_errcnt = 0;
do_viminfo(fp, NULL, flags); do_viminfo(fp, NULL, flags);
@@ -3054,12 +3060,12 @@ write_viminfo(char_u *file, int forceit)
FILE *fp_out = NULL; // output viminfo file FILE *fp_out = NULL; // output viminfo file
char_u *tempname = NULL; // name of temp viminfo file char_u *tempname = NULL; // name of temp viminfo file
stat_T st_new; // mch_stat() of potential new file stat_T st_new; // mch_stat() of potential new file
stat_T st_old; // mch_stat() of existing viminfo file
#if defined(UNIX) || defined(VMS) #if defined(UNIX) || defined(VMS)
mode_t umask_save; mode_t umask_save;
#endif #endif
#ifdef UNIX #ifdef UNIX
int shortname = FALSE; // use 8.3 file name int shortname = FALSE; // use 8.3 file name
stat_T st_old; // mch_stat() of existing viminfo file
#endif #endif
#ifdef MSWIN #ifdef MSWIN
int hidden = FALSE; int hidden = FALSE;
@@ -3097,20 +3103,20 @@ write_viminfo(char_u *file, int forceit)
// write the new viminfo into, in the same directory as the // write the new viminfo into, in the same directory as the
// existing viminfo file, which will be renamed once all writing is // existing viminfo file, which will be renamed once all writing is
// successful. // successful.
if (mch_fstat(fileno(fp_in), &st_old) < 0
|| S_ISDIR(st_old.st_mode)
#ifdef UNIX #ifdef UNIX
// For Unix we check the owner of the file. It's not very nice to // For Unix we check the owner of the file. It's not very nice
// overwrite a user's viminfo file after a "su root", with a // to overwrite a user's viminfo file after a "su root", with a
// viminfo file that the user can't read. // viminfo file that the user can't read.
st_old.st_dev = (dev_t)0; || (getuid() != ROOT_UID
st_old.st_ino = 0; && !(st_old.st_uid == getuid()
st_old.st_mode = 0600; ? (st_old.st_mode & 0200)
if (mch_stat((char *)fname, &st_old) == 0 : (st_old.st_gid == getgid()
&& getuid() != ROOT_UID ? (st_old.st_mode & 0020)
&& !(st_old.st_uid == getuid() : (st_old.st_mode & 0002))))
? (st_old.st_mode & 0200) #endif
: (st_old.st_gid == getgid() )
? (st_old.st_mode & 0020)
: (st_old.st_mode & 0002))))
{ {
int tt = msg_didany; int tt = msg_didany;
@@ -3120,7 +3126,6 @@ write_viminfo(char_u *file, int forceit)
fclose(fp_in); fclose(fp_in);
goto end; goto end;
} }
#endif
#ifdef MSWIN #ifdef MSWIN
// Get the file attributes of the existing viminfo file. // Get the file attributes of the existing viminfo file.
hidden = mch_ishidden(fname); hidden = mch_ishidden(fname);