Compare commits

...

19 Commits

Author SHA1 Message Date
Bram Moolenaar
2a1381c305 patch 8.2.0701: Vim9 test fails without job feature
Problem:    Vim9 test fails without job feature.
Solution:   Add feature check.
2020-05-05 23:32:58 +02:00
Bram Moolenaar
40ee466c36 patch 8.2.0700: Vim9: converting error message to exception not tested
Problem:    Vim9: converting error message to exception not tested.
Solution:   Test exception from error.  Do not continue after :echoerr.
2020-05-05 22:08:26 +02:00
Bram Moolenaar
015f4267f4 patch 8.2.0699: Vim9: not all errors tested
Problem:    Vim9: not all errors tested.
Solution:   Add test for deleted function.  Bail out on first error.
2020-05-05 21:25:22 +02:00
Bram Moolenaar
f9ab52e155 patch 8.2.0698: insert mode completion not fully tested
Problem:    Insert mode completion not fully tested.
Solution:   Add a few more tests. (Yegappan Lakshmanan, closes #6041)
2020-05-05 19:57:18 +02:00
Bram Moolenaar
221fcc741a patch 8.2.0697: Vim9: memory leak when using nested function
Problem:    Vim9: memory leak when using nested function.
Solution:   Unreference function when deleting instructions. Adjust reference
            count for local variables.
2020-05-05 19:46:20 +02:00
Bram Moolenaar
0e65d3de0a patch 8.2.0696: Vim9: nested function does not work properly
Problem:    Vim9: nested function does not work properly
Solution:   Create a function reference.  Check argument count.
2020-05-05 17:53:16 +02:00
Bram Moolenaar
04b1269783 patch 8.2.0695: Vim9: cannot define a function inside a function
Problem:    Vim9: cannot define a function inside a function.
Solution:   Initial support for :def inside :def.
2020-05-04 23:24:44 +02:00
Bram Moolenaar
80a8d3889b patch 8.2.0694: Haiku: channel and terminal do not work
Problem:    Haiku: channel and terminal do not work.
Solution:   Close files when the job has finished. (Ozaki Kiichi,
            closes #6039)
2020-05-03 22:57:32 +02:00
Bram Moolenaar
2fd4cd755c patch 8.2.0693: closure using argument not tested
Problem:    Closure using argument not tested.
Solution:   Add a test, make it work.
2020-05-03 22:30:49 +02:00
Bram Moolenaar
4515bcdec8 patch 8.2.0692: startup test fails on MS-Windows
Problem:    Startup test fails on MS-Windows.
Solution:   Allow for any path.
2020-05-03 18:21:04 +02:00
Bram Moolenaar
7779ee30d9 patch 8.2.0691: startup test fails
Problem:    Startup test fails.
Solution:   Adjust expected output from -V2 argument.
2020-05-03 17:55:32 +02:00
Bram Moolenaar
5125874951 patch 8.2.0690: line number of option set by modeline is wrong
Problem:    Line number of option set by modeline is wrong.
Solution:   Do not double the line number. (Ozaki Kiichi, closes #6035)
2020-05-03 17:19:33 +02:00
Bram Moolenaar
a38b2b737e patch 8.2.0689: when using getaddrinfo() the error message is unclear
Problem:    When using getaddrinfo() the error message is unclear.
Solution:   Use gai_strerror() to get the message. (Ozaki Kiichi,
            closes #6034)
2020-05-03 17:03:29 +02:00
Bram Moolenaar
647a530b33 patch 8.2.0688: output clobbered if setting 'verbose' to see shell commands
Problem:    Output clobbered if setting 'verbose' to see shell commands.
Solution:   Only output "Searching for" when 'verbose' is 11 or higher.
2020-05-03 17:01:24 +02:00
Bram Moolenaar
41d4299f26 patch 8.2.0687: some tests do not work on FreeBSD
Problem:    Some tests do not work on FreeBSD.
Solution:   Enable modeline.  Use WaitFor() in more cases. (Ozaki Kiichi,
            closes #6036)
2020-05-03 16:29:50 +02:00
Bram Moolenaar
2eaeaf3c31 patch 8.2.0686: formatoptions not sufficiently tested
Problem:    Formatoptions not sufficiently tested.
Solution:   Add a few more tests. (Yegappan Lakshmanan, closes #6031)
2020-05-03 16:04:43 +02:00
Bram Moolenaar
3df02f507f patch 8.2.0685: Build failure
Problem:    Build failure.
Solution:   Include missing changes.
2020-05-03 15:47:33 +02:00
Bram Moolenaar
f7779c63d4 patch 8.2.0684: Vim9: memory leak when using lambda
Problem:    Vim9: memory leak when using lambda.
Solution:   Move the funccal context to the partial. Free the function when
            exiting.
2020-05-03 15:38:16 +02:00
Bram Moolenaar
5adc55cb74 patch 8.2.0683: Vim9: parsing type does not always work
Problem:    Vim9: parsing type does not always work.
Solution:   Handle func type without return value.  Test more closures.
            Fix type check offset.  Fix garbage collection.
2020-05-02 23:12:58 +02:00
28 changed files with 1014 additions and 263 deletions

View File

@@ -8323,9 +8323,11 @@ A jump table for the options with a short description can be found at |Q_op|.
Currently, these messages are given:
>= 1 When the viminfo file is read or written.
>= 2 When a file is ":source"'ed.
>= 4 Shell commands.
>= 5 Every searched tags file and include file.
>= 8 Files for which a group of autocommands is executed.
>= 9 Every executed autocommand.
>= 11 Finding items in a path
>= 12 Every executed function.
>= 13 When an exception is thrown, caught, finished, or discarded.
>= 14 Anything pending in a ":finally" clause.

View File

@@ -955,6 +955,7 @@ channel_open(
int sd = -1;
channel_T *channel = NULL;
#ifdef FEAT_IPV6
int err;
struct addrinfo hints;
struct addrinfo *res = NULL;
struct addrinfo *addr = NULL;
@@ -986,10 +987,11 @@ channel_open(
// Set port number manually in order to prevent name resolution services
// from being invoked in the environment where AI_NUMERICSERV is not
// defined.
if (getaddrinfo(hostname, NULL, &hints, &res) != 0)
if ((err = getaddrinfo(hostname, NULL, &hints, &res)) != 0)
{
ch_error(channel, "in getaddrinfo() in channel_open()");
PERROR(_("E901: getaddrinfo() in channel_open()"));
semsg(_("E901: getaddrinfo() in channel_open(): %s"),
gai_strerror(err));
channel_free(channel);
return NULL;
}
@@ -3938,7 +3940,7 @@ theend:
free_job_options(&opt);
}
# if defined(MSWIN) || defined(FEAT_GUI) || defined(PROTO)
#if defined(MSWIN) || defined(__HAIKU__) || defined(FEAT_GUI) || defined(PROTO)
/*
* Check the channels for anything that is ready to be read.
* The data is put in the read queue.
@@ -3971,9 +3973,23 @@ channel_handle_events(int only_keep_open)
"channel_handle_events");
}
}
# ifdef __HAIKU__
// Workaround for Haiku: Since select/poll cannot detect EOF from tty,
// should close fds when the job has finished if 'channel' connects to
// the pty.
if (channel->ch_job != NULL)
{
job_T *job = channel->ch_job;
if (job->jv_tty_out != NULL && job->jv_status == JOB_FINISHED)
for (part = PART_SOCK; part < PART_COUNT; ++part)
ch_close_part(channel, part);
}
# endif
}
}
# endif
#endif
# if defined(FEAT_GUI) || defined(PROTO)
/*
@@ -4539,6 +4555,20 @@ channel_select_check(int ret_in, void *rfds_in, void *wfds_in)
channel_write_input(channel);
--ret;
}
# ifdef __HAIKU__
// Workaround for Haiku: Since select/poll cannot detect EOF from tty,
// should close fds when the job has finished if 'channel' connects to
// the pty.
if (channel->ch_job != NULL)
{
job_T *job = channel->ch_job;
if (job->jv_tty_out != NULL && job->jv_status == JOB_FINISHED)
for (part = PART_SOCK; part < PART_COUNT; ++part)
ch_close_part(channel, part);
}
# endif
}
return ret;

View File

@@ -3703,6 +3703,24 @@ partial_free(partial_T *pt)
}
else
func_ptr_unref(pt->pt_func);
if (pt->pt_funcstack != NULL)
{
// Decrease the reference count for the context of a closure. If down
// to zero free it and clear the variables on the stack.
if (--pt->pt_funcstack->fs_refcount == 0)
{
garray_T *gap = &pt->pt_funcstack->fs_ga;
typval_T *stack = gap->ga_data;
for (i = 0; i < gap->ga_len; ++i)
clear_tv(stack + i);
ga_clear(gap);
vim_free(pt->pt_funcstack);
}
pt->pt_funcstack = NULL;
}
vim_free(pt);
}
@@ -4336,6 +4354,15 @@ set_ref_in_item(
for (i = 0; i < pt->pt_argc; ++i)
abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID,
ht_stack, list_stack);
if (pt->pt_funcstack != NULL)
{
typval_T *stack = pt->pt_funcstack->fs_ga.ga_data;
for (i = 0; i < pt->pt_funcstack->fs_ga.ga_len; ++i)
abort = abort || set_ref_in_item(stack + i, copyID,
ht_stack, list_stack);
}
}
}
#ifdef FEAT_JOB_CHANNEL

View File

@@ -2147,7 +2147,7 @@ parse_queued_messages(void)
for (i = 0; i < MAX_REPEAT_PARSE; ++i)
{
// For Win32 mch_breakcheck() does not check for input, do it here.
# if defined(MSWIN) && defined(FEAT_JOB_CHANNEL)
# if (defined(MSWIN) || defined(__HAIKU__)) && defined(FEAT_JOB_CHANNEL)
channel_handle_events(FALSE);
# endif

View File

@@ -1145,10 +1145,11 @@ VimFormView::FrameResized(float new_width, float new_height)
VimTextAreaView::VimTextAreaView(BRect frame):
BView(frame, "VimTextAreaView", B_FOLLOW_ALL_SIDES,
#ifdef FEAT_MBYTE_IME
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_INPUT_METHOD_AWARE),
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_INPUT_METHOD_AWARE
#else
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
#endif
),
mouseDragEventCount(0)
{
#ifdef FEAT_MBYTE_IME
@@ -3093,7 +3094,7 @@ gui_haiku_process_event(bigtime_t timeout)
}
}
#else
add_to_input_buf_csi(string, len);
add_to_input_buf_csi(string, len);
#endif
else
add_to_input_buf(string, len);
@@ -4437,15 +4438,16 @@ gui_mch_wait_for_chars(
int wtime)
{
int focus;
bigtime_t until, timeout;
status_t st;
bigtime_t until, timeout;
status_t st;
if (wtime >= 0) {
if (wtime >= 0)
{
timeout = wtime * 1000;
until = system_time() + timeout;
} else {
timeout = B_INFINITE_TIMEOUT;
}
else
timeout = B_INFINITE_TIMEOUT;
focus = gui.in_focus;
for (;;)
@@ -4461,6 +4463,28 @@ gui_mch_wait_for_chars(
}
gui_mch_flush();
#ifdef MESSAGE_QUEUE
# ifdef FEAT_TIMERS
did_add_timer = FALSE;
# endif
parse_queued_messages();
# ifdef FEAT_TIMERS
if (did_add_timer)
// Need to recompute the waiting time.
break;
# endif
# ifdef FEAT_JOB_CHANNEL
if (has_any_channel())
{
if (wtime < 0 || timeout > 20000)
timeout = 20000;
}
else if (wtime < 0)
timeout = B_INFINITE_TIMEOUT;
# endif
#endif
/*
* Don't use gui_mch_update() because then we will spin-lock until a
* char arrives, instead we use gui_haiku_process_event() to hang until
@@ -4478,7 +4502,8 @@ gui_mch_wait_for_chars(
* Calculate how much longer we're willing to wait for the
* next event.
*/
if (wtime >= 0) {
if (wtime >= 0)
{
timeout = until - system_time();
if (timeout < 0)
break;

View File

@@ -1666,23 +1666,25 @@ vim_getenv(char_u *name, int *mustfree)
if (p != NULL)
return p;
# ifdef __HAIKU__
// special handling for user settings directory...
if (STRCMP(name, "BE_USER_SETTINGS") == 0)
{
static char userSettingsPath[MAXPATHL];
if (find_directory(B_USER_SETTINGS_DIRECTORY, 0, false,
userSettingsPath, MAXPATHL) == B_OK)
return (char_u *)userSettingsPath;
else
return NULL;
}
# endif
#endif
// handling $VIMRUNTIME and $VIM is below, bail out if it's another name.
vimruntime = (STRCMP(name, "VIMRUNTIME") == 0);
if (!vimruntime && STRCMP(name, "VIM") != 0)
#if defined(__HAIKU__)
// special handling for user settings directory...
if (STRCMP(name, "BE_USER_SETTINGS") == 0)
{
static char userSettingsPath[MAXPATHL] = {0};
if (B_OK == find_directory(B_USER_SETTINGS_DIRECTORY, 0,
false, userSettingsPath, MAXPATHL))
return userSettingsPath;
}
else
#endif
return NULL;
/*

View File

@@ -2460,7 +2460,9 @@ set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
int indir = (int)options[opt_idx].indir;
sctx_T new_script_ctx = script_ctx;
new_script_ctx.sc_lnum += SOURCING_LNUM;
// Modeline already has the line number set.
if (!(opt_flags & OPT_MODELINE))
new_script_ctx.sc_lnum += SOURCING_LNUM;
// Remember where the option was set. For local options need to do that
// in the buffer or window structure.

View File

@@ -2,6 +2,7 @@
void func_init(void);
hashtab_T *func_tbl_get(void);
int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free);
char_u *get_lambda_name(void);
int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
void emsg_funcname(char *ermsg, char_u *name);
@@ -22,6 +23,7 @@ void user_func_error(int error, char_u *name);
int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial);
char_u *untrans_function_name(char_u *name);
ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context);
void ex_function(exarg_T *eap);
int eval_fname_script(char_u *p);
int translated_function_exists(char_u *name, int is_global);

View File

@@ -1,6 +1,7 @@
/* vim9execute.c */
int call_def_function(ufunc_T *ufunc, int argc, typval_T *argv, typval_T *rettv);
int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, typval_T *rettv);
void ex_disassemble(exarg_T *eap);
int tv2bool(typval_T *tv);
int check_not_string(typval_T *tv);
int set_ref_in_dfunc(ufunc_T *ufunc, int copyID);
/* vim: set ft=c : */

View File

@@ -228,7 +228,7 @@ do_in_path(
buf = alloc(MAXPATHL);
if (buf != NULL && rtp_copy != NULL)
{
if (p_verbose > 1 && name != NULL)
if (p_verbose > 10 && name != NULL)
{
verbose_enter();
smsg(_("Searching for \"%s\" in \"%s\""),
@@ -276,7 +276,7 @@ do_in_path(
copy_option_part(&np, tail, (int)(MAXPATHL - (tail - buf)),
"\t ");
if (p_verbose > 2)
if (p_verbose > 10)
{
verbose_enter();
smsg(_("Searching for \"%s\""), buf);

View File

@@ -1777,6 +1777,21 @@ typedef struct {
typval_T *basetv; // base for base->method()
} funcexe_T;
/*
* Structure to hold the context of a compiled function, used by closures
* defined in that function.
*/
typedef struct funcstack_S
{
garray_T fs_ga; // contains the stack, with:
// - arguments
// - frame
// - local variables
int fs_refcount; // nr of closures referencing this funcstack
int fs_copyID; // for garray_T collection
} funcstack_T;
struct partial_S
{
int pt_refcount; // reference count
@@ -1786,8 +1801,16 @@ struct partial_S
// with pt_name
int pt_auto; // when TRUE the partial was created for using
// dict.member in handle_subscript()
// For a compiled closure: the arguments and local variables.
garray_T *pt_ectx_stack; // where to find local vars
int pt_ectx_frame; // index of function frame in uf_ectx_stack
funcstack_T *pt_funcstack; // copy of stack, used after context
// function returns
int pt_argc; // number of arguments
typval_T *pt_argv; // arguments in allocated array
dict_T *pt_dict; // dict for "self"
};

View File

@@ -328,8 +328,8 @@ func Test_edit_11_indentexpr()
bw!
endfunc
" Test changing indent in replace mode
func Test_edit_12()
" Test changing indent in replace mode
new
call setline(1, ["\tabc", "\tdef"])
call cursor(2, 4)
@@ -368,15 +368,15 @@ func Test_edit_12()
call feedkeys("R\<c-t>\<c-t>", 'tnix')
call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
call assert_equal([0, 2, 2, 0], getpos('.'))
set et
set sw& et&
set sw&
" In replace mode, after hitting enter in a line with tab characters,
" pressing backspace should restore the tab characters.
%d
call setline(1, ["\t/*"])
set formatoptions=croql
call cursor(1, 3)
call feedkeys("A\<cr>\<cr>/", 'tnix')
call assert_equal(["\t/*", " *", " */"], getline(1, '$'))
set formatoptions&
setlocal autoindent backspace=2
call setline(1, "\tone\t\ttwo")
exe "normal ggRred\<CR>six" .. repeat("\<BS>", 8)
call assert_equal(["\tone\t\ttwo"], getline(1, '$'))
bw!
endfunc

View File

@@ -493,12 +493,43 @@ func Test_completefunc_error()
call setline(1, ['', 'abcd', ''])
call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E578:')
" Jump to a different window from the complete function
" TODO: The following test causes an ASAN failure. Once this issue is
" addressed, enable the following test.
"func! CompleteFunc(findstart, base)
" if a:findstart == 1
" return col('.') - 1
" endif
" wincmd p
" return ['a', 'b']
"endfunc
"set completefunc=CompleteFunc
"new
"call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E839:')
"close!
set completefunc&
delfunc CompleteFunc
delfunc CompleteFunc2
close!
endfunc
" Test for returning non-string values from 'completefunc'
func Test_completefunc_invalid_data()
new
func! CompleteFunc(findstart, base)
if a:findstart == 1
return col('.') - 1
endif
return [{}, '', 'moon']
endfunc
set completefunc=CompleteFunc
exe "normal i\<C-X>\<C-U>"
call assert_equal('moon', getline(1))
set completefunc&
close!
endfunc
" Test for errors in using complete() function
func Test_complete_func_error()
call assert_fails('call complete(1, ["a"])', 'E785:')
@@ -513,6 +544,7 @@ func Test_complete_func_error()
delfunc ListColors
delfunc ListMonths
call assert_fails('call complete_info({})', 'E714:')
call assert_equal([], complete_info(['items']).items)
endfunc
" Test for completing words following a completed word in a line
@@ -535,4 +567,71 @@ func Test_complete_wrapscan()
close!
endfunc
" Test for completing special characters
func Test_complete_special_chars()
new
call setline(1, 'int .*[-\^$ func float')
call feedkeys("oin\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>", 'xt')
call assert_equal('int .*[-\^$ func float', getline(2))
close!
endfunc
" Test for completion when text is wrapped across lines.
func Test_complete_across_line()
new
call setline(1, ['red green blue', 'one two three'])
setlocal textwidth=20
exe "normal 2G$a re\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
call assert_equal(['one two three red', 'green blue one'], getline(2, '$'))
close!
endfunc
" Test for using CTRL-L to add one character when completing matching
func Test_complete_add_onechar()
new
call setline(1, ['wool', 'woodwork'])
call feedkeys("Gowoo\<C-P>\<C-P>\<C-P>\<C-L>f", 'xt')
call assert_equal('woof', getline(3))
" use 'ignorecase' and backspace to erase characters from the prefix string
" and then add letters using CTRL-L
%d
set ignorecase backspace=2
setlocal complete=.
call setline(1, ['workhorse', 'workload'])
normal Go
exe "normal aWOR\<C-P>\<bs>\<bs>\<bs>\<bs>\<bs>\<bs>\<C-L>r\<C-L>\<C-L>"
call assert_equal('workh', getline(3))
set ignorecase& backspace&
close!
endfunc
" Test insert completion with 'cindent' (adjust the indent)
func Test_complete_with_cindent()
new
setlocal cindent
call setline(1, ['if (i == 1)', " j = 2;"])
exe "normal Go{\<CR>i\<C-X>\<C-L>\<C-X>\<C-L>\<CR>}"
call assert_equal(['{', "\tif (i == 1)", "\t\tj = 2;", '}'], getline(3, '$'))
%d
call setline(1, ['when while', '{', ''])
setlocal cinkeys+==while
exe "normal Giwh\<C-P> "
call assert_equal("\twhile ", getline('$'))
close!
endfunc
" Test for <CTRL-X> <CTRL-V> completion. Complete commands and functions
func Test_complete_cmdline()
new
exe "normal icaddb\<C-X>\<C-V>"
call assert_equal('caddbuffer', getline(1))
exe "normal ocall getqf\<C-X>\<C-V>"
call assert_equal('call getqflist(', getline(2))
exe "normal oabcxyz(\<C-X>\<C-V>"
call assert_equal('abcxyz(', getline(3))
close!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -279,3 +279,59 @@ func Test_modeline_fails_modelineexpr()
call s:modeline_fails('tabline', 'tabline=Something()', 'E992:')
call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
endfunc
func Test_modeline_setoption_verbose()
let modeline = &modeline
set modeline
let lines =<< trim END
1 vim:ts=2
2 two
3 three
4 four
5 five
6 six
7 seven
8 eight
END
call writefile(lines, 'Xmodeline')
edit Xmodeline
let info = split(execute('verbose set tabstop?'), "\n")
call assert_match('^\s*Last set from modeline line 1$', info[-1])
bwipe!
let lines =<< trim END
1 one
2 two
3 three
4 vim:ts=4
5 five
6 six
7 seven
8 eight
END
call writefile(lines, 'Xmodeline')
edit Xmodeline
let info = split(execute('verbose set tabstop?'), "\n")
call assert_match('^\s*Last set from modeline line 4$', info[-1])
bwipe!
let lines =<< trim END
1 one
2 two
3 three
4 four
5 five
6 six
7 seven
8 vim:ts=8
END
call writefile(lines, 'Xmodeline')
edit Xmodeline
let info = split(execute('verbose set tabstop?'), "\n")
call assert_match('^\s*Last set from modeline line 8$', info[-1])
bwipe!
let &modeline = modeline
call delete('Xmodeline')
endfunc

View File

@@ -204,6 +204,21 @@ func Test_normal05_formatexpr_setopt()
set formatexpr=
endfunc
" When 'formatexpr' returns non-zero, internal formatting is used.
func Test_normal_formatexpr_returns_nonzero()
new
call setline(1, ['one', 'two'])
func! Format()
return 1
endfunc
setlocal formatexpr=Format()
normal VGgq
call assert_equal(['one two'], getline(1, '$'))
setlocal formatexpr=
delfunc Format
close!
endfunc
" basic test for formatprg
func Test_normal06_formatprg()
" only test on non windows platform

View File

@@ -1708,7 +1708,6 @@ func s:create_test_file(filename)
endfunc
func Test_switchbuf()
CheckNotBSD
call s:create_test_file('Xqftestfile1')
call s:create_test_file('Xqftestfile2')
call s:create_test_file('Xqftestfile3')
@@ -1834,6 +1833,7 @@ func Test_switchbuf()
" If opening a file changes 'switchbuf', then the new value should be
" retained.
set modeline&vim
call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
enew | only
set switchbuf&vim

View File

@@ -274,7 +274,7 @@ func Test_V_arg()
call assert_equal(" verbose=0\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V2 -c "set verbose?" -cq')
call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nSearching for \"filetype\.vim\".*\n", out)
call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline \\d\\+: sourcing \"[^\"]*runtime[\\/]filetype\.vim\".*\n", out)
call assert_match(" verbose=2\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V15 -c "set verbose?" -cq')

View File

@@ -894,7 +894,6 @@ func Test_terminal_wqall()
endfunc
func Test_terminal_composing_unicode()
CheckNotBSD
let save_enc = &encoding
set encoding=utf-8
@@ -909,7 +908,7 @@ func Test_terminal_composing_unicode()
enew
let buf = term_start(cmd, {'curwin': bufnr('')})
let g:job = term_getjob(buf)
call TermWait(buf, 25)
call WaitFor({-> term_getline(buf, 1) !=# ''}, 1000)
if has('win32')
call assert_equal('cmd', job_info(g:job).cmd[0])
@@ -919,10 +918,11 @@ func Test_terminal_composing_unicode()
" ascii + composing
let txt = "a\u0308bc"
call term_sendkeys(buf, "echo " . txt . "\r")
call term_sendkeys(buf, "echo " . txt)
call TermWait(buf, 25)
call assert_match("echo " . txt, term_getline(buf, lnum[0]))
call assert_equal(txt, term_getline(buf, lnum[0] + 1))
call term_sendkeys(buf, "\<cr>")
call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[0] + 1))}, 1000)
let l = term_scrape(buf, lnum[0] + 1)
call assert_equal("a\u0308", l[0].chars)
call assert_equal("b", l[1].chars)
@@ -930,10 +930,11 @@ func Test_terminal_composing_unicode()
" multibyte + composing
let txt = "\u304b\u3099\u304e\u304f\u3099\u3052\u3053\u3099"
call term_sendkeys(buf, "echo " . txt . "\r")
call term_sendkeys(buf, "echo " . txt)
call TermWait(buf, 25)
call assert_match("echo " . txt, term_getline(buf, lnum[1]))
call assert_equal(txt, term_getline(buf, lnum[1] + 1))
call term_sendkeys(buf, "\<cr>")
call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[1] + 1))}, 1000)
let l = term_scrape(buf, lnum[1] + 1)
call assert_equal("\u304b\u3099", l[0].chars)
call assert_equal("\u304e", l[1].chars)
@@ -943,10 +944,11 @@ func Test_terminal_composing_unicode()
" \u00a0 + composing
let txt = "abc\u00a0\u0308"
call term_sendkeys(buf, "echo " . txt . "\r")
call term_sendkeys(buf, "echo " . txt)
call TermWait(buf, 25)
call assert_match("echo " . txt, term_getline(buf, lnum[2]))
call assert_equal(txt, term_getline(buf, lnum[2] + 1))
call term_sendkeys(buf, "\<cr>")
call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[2] + 1))}, 1000)
let l = term_scrape(buf, lnum[2] + 1)
call assert_equal("\u00a0\u0308", l[3].chars)

View File

@@ -975,6 +975,30 @@ func Test_whichwrap_multi_byte()
bwipe!
endfunc
" Test for automatically adding comment leaders in insert mode
func Test_threepiece_comment()
new
setlocal expandtab
call setline(1, ["\t/*"])
setlocal formatoptions=croql
call cursor(1, 3)
call feedkeys("A\<cr>\<cr>/", 'tnix')
call assert_equal(["\t/*", " *", " */"], getline(1, '$'))
" If a comment ends in a single line, then don't add it in the next line
%d
call setline(1, '/* line1 */')
call feedkeys("A\<CR>next line", 'xt')
call assert_equal(['/* line1 */', 'next line'], getline(1, '$'))
%d
" Copy the trailing indentation from the leader comment to a new line
setlocal autoindent noexpandtab
call feedkeys("a\t/*\tone\ntwo\n/", 'xt')
call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$'))
close!
endfunc
" Test for the 'f' flag in 'comments' (only the first line has the comment
" string)
func Test_firstline_comment()
@@ -1091,6 +1115,17 @@ func Test_fo_a_w()
setlocal fo+=aw tw=10
call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt')
call assert_equal(['abc abcde ', 'a abc'], getline(1, '$'))
" Test for 'a', 'w' and '1' options.
setlocal textwidth=0
setlocal fo=1aw
%d
call setline(1, '. foo')
normal 72ig
call feedkeys('a uu uu uu', 'xt')
call assert_equal('g uu uu ', getline(1)[-8:])
call assert_equal(['uu. foo'], getline(2, '$'))
%bw!
endfunc
@@ -1113,4 +1148,185 @@ func Test_fo_j()
%bw!
endfunc
" Test for formatting lines using gq in visual mode
func Test_visual_gq_format()
new
call setline(1, ['one two three four', 'five six', 'one two'])
setl textwidth=10
call feedkeys('ggv$jj', 'xt')
redraw!
normal gq
%d
call setline(1, ['one two three four', 'five six', 'one two'])
normal G$
call feedkeys('v0kk', 'xt')
redraw!
normal gq
setl textwidth&
close!
endfunc
" Test for 'n' flag in 'formatoptions' to format numbered lists
func Test_fo_n()
new
setlocal autoindent
setlocal textwidth=12
setlocal fo=n
call setline(1, [' 1) one two three four', ' 2) two'])
normal gggqG
call assert_equal([' 1) one two', ' three', ' four', ' 2) two'],
\ getline(1, '$'))
close!
endfunc
" Test for 'formatlistpat' option
func Test_formatlistpat()
new
setlocal autoindent
setlocal textwidth=10
setlocal fo=n
setlocal formatlistpat=^\\s*-\\s*
call setline(1, [' - one two three', ' - two'])
normal gggqG
call assert_equal([' - one', ' two', ' three', ' - two'],
\ getline(1, '$'))
close!
endfunc
" Test for the 'b' and 'v' flags in 'formatoptions'
" Text should wrap only if a space character is inserted at or before
" 'textwidth'
func Test_fo_b()
new
setlocal textwidth=20
setlocal formatoptions=t
call setline(1, 'one two three four')
call feedkeys('Amore', 'xt')
call assert_equal(['one two three', 'fourmore'], getline(1, '$'))
setlocal formatoptions=bt
%d
call setline(1, 'one two three four')
call feedkeys('Amore five', 'xt')
call assert_equal(['one two three fourmore five'], getline(1, '$'))
setlocal formatoptions=bt
%d
call setline(1, 'one two three four')
call feedkeys('A five', 'xt')
call assert_equal(['one two three four', 'five'], getline(1, '$'))
setlocal formatoptions=vt
%d
call setline(1, 'one two three four')
call feedkeys('Amore five', 'xt')
call assert_equal(['one two three fourmore', 'five'], getline(1, '$'))
close!
endfunc
" Test for the '1' flag in 'formatoptions'. Don't wrap text after a one letter
" word.
func Test_fo_1()
new
setlocal textwidth=20
setlocal formatoptions=t
call setline(1, 'one two three four')
call feedkeys('A a bird', 'xt')
call assert_equal(['one two three four a', 'bird'], getline(1, '$'))
%d
setlocal formatoptions=t1
call setline(1, 'one two three four')
call feedkeys('A a bird', 'xt')
call assert_equal(['one two three four', 'a bird'], getline(1, '$'))
close!
endfunc
" Test for 'l' flag in 'formatoptions'. When starting insert mode, if a line
" is longer than 'textwidth', then it is not broken.
func Test_fo_l()
new
setlocal textwidth=20
setlocal formatoptions=t
call setline(1, 'one two three four five')
call feedkeys('A six', 'xt')
call assert_equal(['one two three four', 'five six'], getline(1, '$'))
%d
setlocal formatoptions=tl
call setline(1, 'one two three four five')
call feedkeys('A six', 'xt')
call assert_equal(['one two three four five six'], getline(1, '$'))
close!
endfunc
" Test for the '2' flag in 'formatoptions'
func Test_fo_2()
new
setlocal autoindent
setlocal formatoptions=t2
setlocal textwidth=30
call setline(1, ["\tfirst line of a paragraph.",
\ "second line of the same paragraph.",
\ "third line."])
normal gggqG
call assert_equal(["\tfirst line of a",
\ "paragraph. second line of the",
\ "same paragraph. third line."], getline(1, '$'))
close!
endfunc
" Test for formatting lines where only the first line has a comment.
func Test_fo_gq_with_firstline_comment()
new
setlocal formatoptions=tcq
call setline(1, ['- one two', 'three'])
normal gggqG
call assert_equal(['- one two three'], getline(1, '$'))
%d
call setline(1, ['- one', '- two'])
normal gggqG
call assert_equal(['- one', '- two'], getline(1, '$'))
close!
endfunc
" Test for trying to join a comment line with a non-comment line
func Test_join_comments()
new
call setline(1, ['one', '/* two */', 'three'])
normal gggqG
call assert_equal(['one', '/* two */', 'three'], getline(1, '$'))
close!
endfunc
" Test for using 'a' in 'formatoptions' with comments
func Test_autoformat_comments()
new
setlocal formatoptions+=a
call feedkeys("a- one\n- two\n", 'xt')
call assert_equal(['- one', '- two', ''], getline(1, '$'))
%d
call feedkeys("a\none\n", 'xt')
call assert_equal(['', 'one', ''], getline(1, '$'))
setlocal formatoptions+=aw
%d
call feedkeys("aone \ntwo\n", 'xt')
call assert_equal(['one two', ''], getline(1, '$'))
%d
call feedkeys("aone\ntwo\n", 'xt')
call assert_equal(['one', 'two', ''], getline(1, '$'))
close!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -759,11 +759,11 @@ def Test_expr7_list()
assert_equal(g:list_mixed, [1, 'b', false])
assert_equal('b', g:list_mixed[1])
call CheckDefExecFailure("let x = g:anint[3]", 'E714:')
call CheckDefExecFailure(["let x = g:anint[3]"], 'E714:')
call CheckDefFailure(["let x = g:list_mixed[xxx]"], 'E1001:')
call CheckDefExecFailure("let x = g:list_mixed['xx']", 'E39:')
call CheckDefExecFailure(["let x = g:list_mixed['xx']"], 'E39:')
call CheckDefFailure(["let x = g:list_mixed[0"], 'E111:')
call CheckDefExecFailure("let x = g:list_empty[3]", 'E684:')
call CheckDefExecFailure(["let x = g:list_empty[3]"], 'E684:')
enddef
def Test_expr7_lambda()
@@ -792,8 +792,8 @@ def Test_expr7_dict()
call CheckDefFailure(["let x = #"], 'E1015:')
call CheckDefFailure(["let x += 1"], 'E1020:')
call CheckDefFailure(["let x = x + 1"], 'E1001:')
call CheckDefExecFailure("let x = g:anint.member", 'E715:')
call CheckDefExecFailure("let x = g:dict_empty.member", 'E716:')
call CheckDefExecFailure(["let x = g:anint.member"], 'E715:')
call CheckDefExecFailure(["let x = g:dict_empty.member"], 'E716:')
enddef
def Test_expr_member()
@@ -899,8 +899,8 @@ func Test_expr7_fails()
call CheckDefFailure(["let x = -'xx'"], "E1030:")
call CheckDefFailure(["let x = +'xx'"], "E1030:")
call CheckDefFailure(["let x = -0z12"], "E974:")
call CheckDefExecFailure("let x = -[8]", "E39:")
call CheckDefExecFailure("let x = -{'a': 1}", "E39:")
call CheckDefExecFailure(["let x = -[8]"], "E39:")
call CheckDefExecFailure(["let x = -{'a': 1}"], "E39:")
call CheckDefFailure(["let x = @"], "E1002:")
call CheckDefFailure(["let x = @<"], "E354:")
@@ -914,23 +914,23 @@ func Test_expr7_fails()
call CheckDefFailure(["let x = &notexist"], 'E113:')
call CheckDefFailure(["&grepprg = [343]"], 'E1013:')
call CheckDefExecFailure("echo s:doesnt_exist", 'E121:')
call CheckDefExecFailure("echo g:doesnt_exist", 'E121:')
call CheckDefExecFailure(["echo s:doesnt_exist"], 'E121:')
call CheckDefExecFailure(["echo g:doesnt_exist"], 'E121:')
call CheckDefFailure(["echo a:somevar"], 'E1075:')
call CheckDefFailure(["echo l:somevar"], 'E1075:')
call CheckDefFailure(["echo x:somevar"], 'E1075:')
call CheckDefExecFailure("let x = +g:astring", 'E1030:')
call CheckDefExecFailure("let x = +g:ablob", 'E974:')
call CheckDefExecFailure("let x = +g:alist", 'E745:')
call CheckDefExecFailure("let x = +g:adict", 'E728:')
call CheckDefExecFailure(["let x = +g:astring"], 'E1030:')
call CheckDefExecFailure(["let x = +g:ablob"], 'E974:')
call CheckDefExecFailure(["let x = +g:alist"], 'E745:')
call CheckDefExecFailure(["let x = +g:adict"], 'E728:')
call CheckDefFailure(["let x = ''", "let y = x.memb"], 'E715:')
call CheckDefExecFailure("[1, 2->len()", 'E492:')
call CheckDefExecFailure("#{a: 1->len()", 'E488:')
call CheckDefExecFailure("{'a': 1->len()", 'E492:')
call CheckDefExecFailure(["[1, 2->len()"], 'E492:')
call CheckDefExecFailure(["#{a: 1->len()"], 'E488:')
call CheckDefExecFailure(["{'a': 1->len()"], 'E492:')
endfunc
let g:Funcrefs = [function('add')]
@@ -986,7 +986,7 @@ func Test_expr_fails()
call CheckDefFailure(["let x = '1'is2"], 'E488:')
call CheckDefFailure(["let x = '1'isnot2"], 'E488:')
call CheckDefExecFailure("CallMe ('yes')", 'E492:')
call CheckDefExecFailure(["CallMe ('yes')"], 'E492:')
call CheckDefFailure(["CallMe2('yes','no')"], 'E1069:')
call CheckDefFailure(["CallMe2('yes' , 'no')"], 'E1068:')

View File

@@ -2,19 +2,7 @@
source check.vim
source view_util.vim
" Check that "lines" inside ":def" results in an "error" message.
func CheckDefFailure(lines, error)
call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef')
call assert_fails('so Xdef', a:error, a:lines)
call delete('Xdef')
endfunc
func CheckScriptFailure(lines, error)
call writefile(a:lines, 'Xdef')
call assert_fails('so Xdef', a:error, a:lines)
call delete('Xdef')
endfunc
source vim9.vim
func Test_def_basic()
def SomeFunc(): string
@@ -95,8 +83,20 @@ def Test_call_default_args()
assert_equal('one', MyDefaultArgs('one'))
assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:')
call CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string')
CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:')
CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string')
enddef
def Test_nested_function()
def Nested(arg: string): string
return 'nested ' .. arg
enddef
assert_equal('nested function', Nested('function'))
CheckDefFailure(['def Nested()', 'enddef', 'Nested(66)'], 'E118:')
CheckDefFailure(['def Nested(arg: string)', 'enddef', 'Nested()'], 'E119:')
CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
enddef
func Test_call_default_args_from_func()
@@ -642,6 +642,23 @@ func Test_E1056_1059()
call assert_equal(1, caught_1059)
endfunc
func DelMe()
echo 'DelMe'
endfunc
def Test_deleted_function()
CheckDefExecFailure([
'let RefMe: func = function("g:DelMe")',
'delfunc g:DelMe',
'echo RefMe()'], 'E117:')
enddef
def Test_unknown_function()
CheckDefExecFailure([
'let Ref: func = function("NotExist")',
'delfunc g:NotExist'], 'E700:')
enddef
def RefFunc(Ref: func(string): string): string
return Ref('more')
enddef
@@ -662,5 +679,72 @@ def Test_closure_ref_after_return()
unlet g:Ref
enddef
def MakeTwoRefs()
let local = ['some']
g:Extend = {s -> local->add(s)}
g:Read = {-> local}
enddef
def Test_closure_two_refs()
MakeTwoRefs()
assert_equal('some', join(g:Read(), ' '))
g:Extend('more')
assert_equal('some more', join(g:Read(), ' '))
g:Extend('even')
assert_equal('some more even', join(g:Read(), ' '))
unlet g:Extend
unlet g:Read
enddef
def ReadRef(Ref: func(): list<string>): string
return join(Ref(), ' ')
enddef
def ExtendRef(Ref: func(string), add: string)
Ref(add)
enddef
def Test_closure_two_indirect_refs()
MakeTwoRefs()
assert_equal('some', ReadRef(g:Read))
ExtendRef(g:Extend, 'more')
assert_equal('some more', ReadRef(g:Read))
ExtendRef(g:Extend, 'even')
assert_equal('some more even', ReadRef(g:Read))
unlet g:Extend
unlet g:Read
enddef
def MakeArgRefs(theArg: string)
let local = 'loc_val'
g:UseArg = {s -> theArg .. '/' .. local .. '/' .. s}
enddef
def MakeArgRefsVarargs(theArg: string, ...rest: list<string>)
let local = 'the_loc'
g:UseVararg = {s -> theArg .. '/' .. local .. '/' .. s .. '/' .. join(rest)}
enddef
def Test_closure_using_argument()
MakeArgRefs('arg_val')
assert_equal('arg_val/loc_val/call_val', g:UseArg('call_val'))
MakeArgRefsVarargs('arg_val', 'one', 'two')
assert_equal('arg_val/the_loc/call_val/one two', g:UseVararg('call_val'))
unlet g:UseArg
unlet g:UseVararg
enddef
def Test_nested_closure()
let local = 'text'
def Closure(arg: string): string
return local .. arg
enddef
assert_equal('text!!!', Closure('!!!'))
enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -466,6 +466,22 @@ def Test_try_catch_fails()
call CheckDefFailure(['throw xxx'], 'E1001:')
enddef
if has('channel')
let someJob = test_null_job()
def FuncWithError()
echomsg g:someJob
enddef
func Test_convert_emsg_to_exception()
try
call FuncWithError()
catch
call assert_match('Vim:E908:', v:exception)
endtry
endfunc
endif
let s:export_script_lines =<< trim END
vim9script
let name: string = 'bob'
@@ -1058,7 +1074,11 @@ def Test_echomsg_cmd()
enddef
def Test_echoerr_cmd()
# TODO: write this test
try
echoerr 'something' 'wrong' # comment
catch
assert_match('something wrong', v:exception)
endtry
enddef
def Test_for_outside_of_function()

View File

@@ -7,6 +7,14 @@ func CheckDefFailure(lines, error)
call delete('Xdef')
endfunc
" Check that "lines" inside ":def" results in an "error" message when executed.
func CheckDefExecFailure(lines, error)
call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef')
so Xdef
call assert_fails('call Func()', a:error, a:lines)
call delete('Xdef')
endfunc
def CheckScriptFailure(lines: list<string>, error: string)
writefile(lines, 'Xdef')
assert_fails('so Xdef', error, lines)
@@ -18,11 +26,3 @@ def CheckScriptSuccess(lines: list<string>)
so Xdef
delete('Xdef')
enddef
" Check that "line" inside ":def" results in an "error" message when executed.
func CheckDefExecFailure(line, error)
call writefile(['def! Func()', a:line, 'enddef'], 'Xdef')
so Xdef
call assert_fails('call Func()', a:error, a:line)
call delete('Xdef')
endfunc

View File

@@ -328,6 +328,19 @@ set_ufunc_name(ufunc_T *fp, char_u *name)
}
}
/*
* Get a name for a lambda. Returned in static memory.
*/
char_u *
get_lambda_name(void)
{
static char_u name[30];
static int lambda_no = 0;
sprintf((char*)name, "<lambda>%d", ++lambda_no);
return name;
}
/*
* Parse a lambda expression and get a Funcref from "*arg".
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
@@ -344,7 +357,6 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
int ret;
char_u *start = skipwhite(*arg + 1);
char_u *s, *e;
static int lambda_no = 0;
int *old_eval_lavars = eval_lavars_used;
int eval_lavars = FALSE;
@@ -392,9 +404,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
{
int len, flags = 0;
char_u *p;
char_u name[20];
sprintf((char*)name, "<lambda>%d", ++lambda_no);
char_u *name = get_lambda_name();
fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
if (fp == NULL)
@@ -1016,16 +1026,17 @@ func_clear(ufunc_T *fp, int force)
/*
* Free a function and remove it from the list of functions. Does not free
* what a function contains, call func_clear() first.
* When "force" is TRUE we are exiting.
*/
static void
func_free(ufunc_T *fp)
func_free(ufunc_T *fp, int force)
{
// Only remove it when not done already, otherwise we would remove a newer
// version of the function with the same name.
if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
func_remove(fp);
if ((fp->uf_flags & FC_DEAD) == 0)
if ((fp->uf_flags & FC_DEAD) == 0 || force)
vim_free(fp);
}
@@ -1037,7 +1048,7 @@ func_free(ufunc_T *fp)
func_clear_free(ufunc_T *fp, int force)
{
func_clear(fp, force);
func_free(fp);
func_free(fp, force);
}
@@ -1664,7 +1675,7 @@ free_all_functions(void)
++skipped;
else
{
func_free(fp);
func_free(fp, FALSE);
skipped = 0;
break;
}
@@ -2363,10 +2374,11 @@ untrans_function_name(char_u *name)
}
/*
* ":function"
* ":function" also supporting nested ":def".
* Returns a pointer to the function or NULL if no function defined.
*/
void
ex_function(exarg_T *eap)
ufunc_T *
def_function(exarg_T *eap, char_u *name_arg, void *context)
{
char_u *theline;
char_u *line_to_free = NULL;
@@ -2374,7 +2386,7 @@ ex_function(exarg_T *eap)
int c;
int saved_did_emsg;
int saved_wait_return = need_wait_return;
char_u *name = NULL;
char_u *name = name_arg;
int is_global = FALSE;
char_u *p;
char_u *arg;
@@ -2386,7 +2398,7 @@ ex_function(exarg_T *eap)
int varargs = FALSE;
int flags = 0;
char_u *ret_type = NULL;
ufunc_T *fp;
ufunc_T *fp = NULL;
int overwrite = FALSE;
int indent;
int nesting;
@@ -2428,7 +2440,7 @@ ex_function(exarg_T *eap)
}
}
eap->nextcmd = check_nextcmd(eap->arg);
return;
return NULL;
}
/*
@@ -2468,7 +2480,7 @@ ex_function(exarg_T *eap)
if (*p == '/')
++p;
eap->nextcmd = check_nextcmd(p);
return;
return NULL;
}
ga_init(&newargs);
@@ -2492,25 +2504,34 @@ ex_function(exarg_T *eap)
* g:func global function name, same as "func"
*/
p = eap->arg;
name = trans_function_name(&p, &is_global, eap->skip,
TFN_NO_AUTOLOAD, &fudi, NULL);
paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
if (name_arg != NULL)
{
/*
* Return on an invalid expression in braces, unless the expression
* evaluation has been cancelled due to an aborting error, an
* interrupt, or an exception.
*/
if (!aborting())
// nested function, argument is (args).
paren = TRUE;
CLEAR_FIELD(fudi);
}
else
{
name = trans_function_name(&p, &is_global, eap->skip,
TFN_NO_AUTOLOAD, &fudi, NULL);
paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
{
if (!eap->skip && fudi.fd_newkey != NULL)
semsg(_(e_dictkey), fudi.fd_newkey);
vim_free(fudi.fd_newkey);
return;
/*
* Return on an invalid expression in braces, unless the expression
* evaluation has been cancelled due to an aborting error, an
* interrupt, or an exception.
*/
if (!aborting())
{
if (!eap->skip && fudi.fd_newkey != NULL)
semsg(_(e_dictkey), fudi.fd_newkey);
vim_free(fudi.fd_newkey);
return NULL;
}
else
eap->skip = TRUE;
}
else
eap->skip = TRUE;
}
// An error in a function call during evaluation of an expression in magic
@@ -2595,7 +2616,7 @@ ex_function(exarg_T *eap)
ga_init2(&newlines, (int)sizeof(char_u *), 3);
if (!eap->skip)
if (!eap->skip && name_arg == NULL)
{
// Check the name of the function. Unless it's a dictionary function
// (that we are overwriting).
@@ -3254,7 +3275,7 @@ ex_function(exarg_T *eap)
// ":def Func()" needs to be compiled
if (eap->cmdidx == CMD_def)
compile_def_function(fp, FALSE, NULL);
compile_def_function(fp, FALSE, context);
goto ret_free;
@@ -3268,10 +3289,22 @@ ret_free:
vim_free(skip_until);
vim_free(line_to_free);
vim_free(fudi.fd_newkey);
vim_free(name);
if (name != name_arg)
vim_free(name);
vim_free(ret_type);
did_emsg |= saved_did_emsg;
need_wait_return |= saved_wait_return;
return fp;
}
/*
* ":function"
*/
void
ex_function(exarg_T *eap)
{
def_function(eap, NULL, NULL);
}
/*
@@ -4440,6 +4473,7 @@ set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped)
abort = abort || set_ref_in_funccal(fc, copyID);
}
vim_free(tofree);
return abort;
}

View File

@@ -746,6 +746,44 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
701,
/**/
700,
/**/
699,
/**/
698,
/**/
697,
/**/
696,
/**/
695,
/**/
694,
/**/
693,
/**/
692,
/**/
691,
/**/
690,
/**/
689,
/**/
688,
/**/
687,
/**/
686,
/**/
685,
/**/
684,
/**/
683,
/**/
682,
/**/

View File

@@ -259,21 +259,6 @@ struct isn_S {
} isn_arg;
};
/*
* Structure to hold the context of a compiled function, used by closures
* defined in that function.
*/
typedef struct funcstack_S
{
garray_T fs_ga; // contains the stack, with:
// - arguments
// - frame
// - local variables
int fs_refcount; // nr of closures referencing this funcstack
int fs_copyID; // for garray_T collection
} funcstack_T;
/*
* Info about a function defined with :def. Used in "def_functions".
*/
@@ -286,11 +271,6 @@ struct dfunc_S {
isn_T *df_instr; // function body to be executed
int df_instr_count;
garray_T *df_ectx_stack; // where compiled closure finds local vars
int df_ectx_frame; // index of function frame in uf_ectx_stack
funcstack_T *df_funcstack; // copy of stack for closure, used after
// closure context function returns
int df_varcount; // number of local variables
int df_closure_count; // number of closures created
};

View File

@@ -186,37 +186,74 @@ lookup_local(char_u *name, size_t len, cctx_T *cctx)
}
/*
* Lookup an argument in the current function.
* Returns the argument index or -1 if not found.
* Lookup an argument in the current function and an enclosing function.
* Returns the argument index in "idxp"
* Returns the argument type in "type"
* Sets "gen_load_outer" to TRUE if found in outer scope.
* Returns OK when found, FAIL otherwise.
*/
static int
lookup_arg(char_u *name, size_t len, cctx_T *cctx)
lookup_arg(
char_u *name,
size_t len,
int *idxp,
type_T **type,
int *gen_load_outer,
cctx_T *cctx)
{
int idx;
char_u *va_name;
if (len == 0)
return -1;
return FAIL;
for (idx = 0; idx < cctx->ctx_ufunc->uf_args.ga_len; ++idx)
{
char_u *arg = FUNCARG(cctx->ctx_ufunc, idx);
if (STRNCMP(name, arg, len) == 0 && STRLEN(arg) == len)
return idx;
if (STRNCMP(name, arg, len) == 0 && arg[len] == NUL)
{
if (idxp != NULL)
{
// Arguments are located above the frame pointer. One further
// if there is a vararg argument
*idxp = idx - (cctx->ctx_ufunc->uf_args.ga_len
+ STACK_FRAME_SIZE)
+ (cctx->ctx_ufunc->uf_va_name != NULL ? -1 : 0);
if (cctx->ctx_ufunc->uf_arg_types != NULL)
*type = cctx->ctx_ufunc->uf_arg_types[idx];
else
*type = &t_any;
}
return OK;
}
}
return -1;
}
/*
* Lookup a vararg argument in the current function.
* Returns TRUE if there is a match.
*/
static int
lookup_vararg(char_u *name, size_t len, cctx_T *cctx)
{
char_u *va_name = cctx->ctx_ufunc->uf_va_name;
va_name = cctx->ctx_ufunc->uf_va_name;
if (va_name != NULL
&& STRNCMP(name, va_name, len) == 0 && va_name[len] == NUL)
{
if (idxp != NULL)
{
// varargs is always the last argument
*idxp = -STACK_FRAME_SIZE - 1;
*type = cctx->ctx_ufunc->uf_va_type;
}
return OK;
}
return len > 0 && va_name != NULL
&& STRNCMP(name, va_name, len) == 0 && STRLEN(va_name) == len;
if (cctx->ctx_outer != NULL)
{
// Lookup the name for an argument of the outer function.
if (lookup_arg(name, len, idxp, type, gen_load_outer, cctx->ctx_outer)
== OK)
{
*gen_load_outer = TRUE;
return OK;
}
}
return FAIL;
}
/*
@@ -824,7 +861,7 @@ generate_TYPECHECK(cctx_T *cctx, type_T *vartype, int offset)
isn->isn_arg.type.ct_off = offset;
// type becomes vartype
((type_T **)stack->ga_data)[stack->ga_len - 1] = vartype;
((type_T **)stack->ga_data)[stack->ga_len + offset] = vartype;
return OK;
}
@@ -1466,7 +1503,24 @@ generate_PCALL(
if (type->tt_type == VAR_ANY)
ret_type = &t_any;
else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
{
if (type->tt_argcount != -1)
{
int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
if (argcount < type->tt_min_argcount - varargs)
{
semsg(_(e_toofewarg), "[reference]");
return FAIL;
}
if (!varargs && argcount > type->tt_argcount)
{
semsg(_(e_toomanyarg), "[reference]");
return FAIL;
}
}
ret_type = type->tt_member;
}
else
{
semsg(_("E1085: Not a callable type: %s"), name);
@@ -1584,7 +1638,7 @@ reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type)
{
lvar_T *lvar;
if (lookup_arg(name, len, cctx) >= 0 || lookup_vararg(name, len, cctx))
if (lookup_arg(name, len, NULL, NULL, NULL, cctx) == OK)
{
emsg_namelen(_("E1006: %s is used as an argument"), name, (int)len);
return NULL;
@@ -1671,8 +1725,13 @@ skip_type(char_u *start)
if (*p == ',')
p = skipwhite(p + 1);
}
if (*p == ')' && p[1] == ':')
p = skip_type(skipwhite(p + 2));
if (*p == ')')
{
if (p[1] == ':')
p = skip_type(skipwhite(p + 2));
else
p = skipwhite(p + 1);
}
}
return p;
@@ -2447,26 +2506,10 @@ compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
if (name == NULL)
return FAIL;
idx = lookup_arg(*arg, len, cctx);
if (idx >= 0)
if (lookup_arg(*arg, len, &idx, &type, &gen_load_outer, cctx) == OK)
{
if (cctx->ctx_ufunc->uf_arg_types != NULL)
type = cctx->ctx_ufunc->uf_arg_types[idx];
else
type = &t_any;
// Arguments are located above the frame pointer.
idx -= cctx->ctx_ufunc->uf_args.ga_len + STACK_FRAME_SIZE;
if (cctx->ctx_ufunc->uf_va_name != NULL)
--idx;
gen_load = TRUE;
}
else if (lookup_vararg(*arg, len, cctx))
{
// varargs is always the last argument
idx = -STACK_FRAME_SIZE - 1;
type = cctx->ctx_ufunc->uf_va_type;
gen_load = TRUE;
if (!gen_load_outer)
gen_load = TRUE;
}
else
{
@@ -2769,7 +2812,6 @@ compile_list(char_u **arg, cctx_T *cctx)
static int
compile_lambda(char_u **arg, cctx_T *cctx)
{
garray_T *instr = &cctx->ctx_instr;
typval_T rettv;
ufunc_T *ufunc;
@@ -2787,12 +2829,7 @@ compile_lambda(char_u **arg, cctx_T *cctx)
compile_def_function(ufunc, TRUE, cctx);
if (ufunc->uf_dfunc_idx >= 0)
{
if (ga_grow(instr, 1) == FAIL)
return FAIL;
generate_FUNCREF(cctx, ufunc->uf_dfunc_idx);
return OK;
}
return generate_FUNCREF(cctx, ufunc->uf_dfunc_idx);
return FAIL;
}
@@ -3900,6 +3937,9 @@ compile_expr1(char_u **arg, cctx_T *cctx)
{
char_u *p;
// TODO: Try parsing as a constant. If that works just one PUSH
// instruction needs to be generated.
// evaluate the first expression
if (compile_expr2(arg, cctx) == FAIL)
return FAIL;
@@ -4019,6 +4059,64 @@ compile_return(char_u *arg, int set_return_type, cctx_T *cctx)
return skipwhite(p);
}
/*
* Get a line from the compilation context, compatible with exarg_T getline().
* Return a pointer to the line in allocated memory.
* Return NULL for end-of-file or some error.
*/
static char_u *
exarg_getline(
int c UNUSED,
void *cookie,
int indent UNUSED,
int do_concat UNUSED)
{
cctx_T *cctx = (cctx_T *)cookie;
if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
{
iemsg("Heredoc got to end");
return NULL;
}
++cctx->ctx_lnum;
return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
[cctx->ctx_lnum]);
}
/*
* Compile a nested :def command.
*/
static char_u *
compile_nested_function(exarg_T *eap, cctx_T *cctx)
{
char_u *name_start = eap->arg;
char_u *name_end = to_name_end(eap->arg, FALSE);
char_u *name = get_lambda_name();
lvar_T *lvar;
ufunc_T *ufunc;
eap->arg = name_end;
eap->getline = exarg_getline;
eap->cookie = cctx;
eap->skip = cctx->ctx_skip == TRUE;
eap->forceit = FALSE;
ufunc = def_function(eap, name, cctx);
if (ufunc == NULL || ufunc->uf_dfunc_idx < 0)
return NULL;
// Define a local variable for the function reference.
lvar = reserve_local(cctx, name_start, name_end - name_start,
TRUE, ufunc->uf_func_type);
if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL
|| generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL)
return NULL;
// TODO: warning for trailing?
return (char_u *)"";
}
/*
* Return the length of an assignment operator, or zero if there isn't one.
*/
@@ -4048,30 +4146,6 @@ static char *reserved[] = {
NULL
};
/*
* Get a line for "=<<".
* Return a pointer to the line in allocated memory.
* Return NULL for end-of-file or some error.
*/
static char_u *
heredoc_getline(
int c UNUSED,
void *cookie,
int indent UNUSED,
int do_concat UNUSED)
{
cctx_T *cctx = (cctx_T *)cookie;
if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
{
iemsg("Heredoc got to end");
return NULL;
}
++cctx->ctx_lnum;
return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
[cctx->ctx_lnum]);
}
typedef enum {
dest_local,
dest_option,
@@ -4365,7 +4439,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
listitem_T *li;
// [let] varname =<< [trim] {end}
eap->getline = heredoc_getline;
eap->getline = exarg_getline;
eap->cookie = cctx;
l = heredoc_get(eap, op + 3, FALSE);
@@ -6270,9 +6344,12 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
switch (ea.cmdidx)
{
case CMD_def:
ea.arg = p;
line = compile_nested_function(&ea, &cctx);
break;
case CMD_function:
// TODO: Nested function
emsg("Nested function not implemented yet");
emsg(_("E1086: Cannot use :function inside :def"));
goto erret;
case CMD_return:
@@ -6552,6 +6629,14 @@ delete_instr(isn_T *isn)
vim_free(isn->isn_arg.ufunc.cuf_name);
break;
case ISN_FUNCREF:
{
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ isn->isn_arg.funcref.fr_func;
func_ptr_unref(dfunc->df_ufunc);
}
break;
case ISN_2BOOL:
case ISN_2STRING:
case ISN_ADDBLOB:
@@ -6580,7 +6665,6 @@ delete_instr(isn_T *isn)
case ISN_EXECCONCAT:
case ISN_EXECUTE:
case ISN_FOR:
case ISN_FUNCREF:
case ISN_INDEX:
case ISN_JUMP:
case ISN_LOAD:
@@ -6629,23 +6713,6 @@ delete_def_function_contents(dfunc_T *dfunc)
delete_instr(dfunc->df_instr + idx);
VIM_CLEAR(dfunc->df_instr);
}
if (dfunc->df_funcstack != NULL)
{
// Decrease the reference count for the context of a closure. If down
// to zero free it and clear the variables on the stack.
if (--dfunc->df_funcstack->fs_refcount == 0)
{
garray_T *gap = &dfunc->df_funcstack->fs_ga;
typval_T *stack = gap->ga_data;
int i;
for (i = 0; i < gap->ga_len; ++i)
clear_tv(stack + i);
ga_clear(gap);
vim_free(dfunc->df_funcstack);
}
dfunc->df_funcstack = NULL;
}
dfunc->df_deleted = TRUE;
}

View File

@@ -232,10 +232,6 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx)
ectx->ec_instr = dfunc->df_instr;
estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1);
// used for closures
ectx->ec_outer_stack = dfunc->df_ectx_stack;
ectx->ec_outer_frame = dfunc->df_ectx_frame;
// Decide where to start execution, handles optional arguments.
init_instr_idx(ufunc, argcount, ectx);
@@ -268,8 +264,28 @@ handle_closure_in_use(ectx_T *ectx, int free_arguments)
{
tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE
+ dfunc->df_varcount + idx);
if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial->pt_refcount > 1)
closure_in_use = TRUE;
if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial != NULL
&& tv->vval.v_partial->pt_refcount > 1)
{
int refcount = tv->vval.v_partial->pt_refcount;
int i;
// A Reference in a local variables doesn't count, its get
// unreferenced on return.
for (i = 0; i < dfunc->df_varcount; ++i)
{
typval_T *stv = STACK_TV(ectx->ec_frame_idx
+ STACK_FRAME_SIZE + i);
if (stv->v_type == VAR_PARTIAL
&& tv->vval.v_partial == stv->vval.v_partial)
--refcount;
}
if (refcount > 1)
{
closure_in_use = TRUE;
break;
}
}
}
if (closure_in_use)
@@ -315,15 +331,17 @@ handle_closure_in_use(ectx_T *ectx, int free_arguments)
{
tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE
+ dfunc->df_varcount + idx);
if (tv->v_type == VAR_PARTIAL
&& tv->vval.v_partial->pt_refcount > 1)
if (tv->v_type == VAR_PARTIAL)
{
dfunc_T *pt_dfunc = ((dfunc_T *)def_functions.ga_data)
+ tv->vval.v_partial->pt_func->uf_dfunc_idx;
++funcstack->fs_refcount;
pt_dfunc->df_funcstack = funcstack;
pt_dfunc->df_ectx_stack = &funcstack->fs_ga;
pt_dfunc->df_ectx_frame = ectx->ec_frame_idx - top;
partial_T *partial = tv->vval.v_partial;
if (partial->pt_refcount > 1)
{
++funcstack->fs_refcount;
partial->pt_funcstack = funcstack;
partial->pt_ectx_stack = &funcstack->fs_ga;
partial->pt_ectx_frame = ectx->ec_frame_idx - top;
}
}
}
}
@@ -414,6 +432,7 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx)
{
typval_T argvars[MAX_FUNC_ARGS];
int idx;
int called_emsg_before = called_emsg;
if (call_prepare(argcount, argvars, ectx) == FAIL)
return FAIL;
@@ -424,6 +443,9 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx)
// Clear the arguments.
for (idx = 0; idx < argcount; ++idx)
clear_tv(&argvars[idx]);
if (called_emsg != called_emsg_before)
return FAIL;
return OK;
}
@@ -515,7 +537,15 @@ call_partial(typval_T *tv, int argcount, ectx_T *ectx)
partial_T *pt = tv->vval.v_partial;
if (pt->pt_func != NULL)
return call_ufunc(pt->pt_func, argcount, ectx, NULL);
{
int ret = call_ufunc(pt->pt_func, argcount, ectx, NULL);
// closure may need the function context where it was defined
ectx->ec_outer_stack = pt->pt_ectx_stack;
ectx->ec_outer_frame = pt->pt_ectx_frame;
return ret;
}
name = pt->pt_name;
}
else if (tv->v_type == VAR_FUNC)
@@ -523,7 +553,8 @@ call_partial(typval_T *tv, int argcount, ectx_T *ectx)
if (name == NULL || call_by_name(name, argcount, ectx, NULL) == FAIL)
{
if (called_emsg == called_emsg_before)
semsg(_(e_unknownfunc), name);
semsg(_(e_unknownfunc),
name == NULL ? (char_u *)"[unknown]" : name);
return FAIL;
}
return OK;
@@ -867,14 +898,8 @@ call_def_function(
}
else
{
int save_did_emsg = did_emsg;
SOURCING_LNUM = iptr->isn_lnum;
emsg(ga.ga_data);
if (!force_abort)
// We don't want to abort following
// commands, restore did_emsg.
did_emsg = save_did_emsg;
}
}
}
@@ -1434,8 +1459,8 @@ call_def_function(
// The closure needs to find arguments and local
// variables in the current stack.
pt_dfunc->df_ectx_stack = &ectx.ec_stack;
pt_dfunc->df_ectx_frame = ectx.ec_frame_idx;
pt->pt_ectx_stack = &ectx.ec_stack;
pt->pt_ectx_frame = ectx.ec_frame_idx;
// If this function returns and the closure is still
// used, we need to make a copy of the context
@@ -2437,11 +2462,12 @@ ex_disassemble(exarg_T *eap)
break;
case ISN_FUNCREF:
{
funcref_T *funcref = &iptr->isn_arg.funcref;
dfunc_T *df = ((dfunc_T *)def_functions.ga_data)
+ iptr->isn_arg.funcref.fr_func;
+ funcref->fr_func;
smsg("%4d FUNCREF %s $%d", current, df->df_ufunc->uf_name,
iptr->isn_arg.funcref.fr_var_idx + df->df_varcount);
funcref->fr_var_idx + dfunc->df_varcount);
}
break;