mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +00:00 
			
		
		
		
	vim-patch:7.4.1727
Problem:    Cannot detect a crash in tests when caused by garbagecollect().
Solution:   Add garbagecollect_for_testing().  Do not free a job if is still
            useful.
ebf7dfa6f1
			
			
This commit is contained in:
		@@ -1791,6 +1791,9 @@ v:termresponse	The escape sequence returned by the terminal for the |t_RV|
 | 
			
		||||
		always 95 or bigger).  Pc is always zero.
 | 
			
		||||
		{only when compiled with |+termresponse| feature}
 | 
			
		||||
 | 
			
		||||
					*v:testing* *testing-variable*
 | 
			
		||||
v:testing	Must be set before using `test_garbagecollect_now()`.
 | 
			
		||||
 | 
			
		||||
				*v:this_session* *this_session-variable*
 | 
			
		||||
v:this_session	Full filename of the last loaded or saved session file.  See
 | 
			
		||||
		|:mksession|.  It is allowed to set this variable.  When no
 | 
			
		||||
@@ -2215,6 +2218,7 @@ tagfiles()			List	tags files used
 | 
			
		||||
tan({expr})			Float	tangent of {expr}
 | 
			
		||||
tanh({expr})			Float	hyperbolic tangent of {expr}
 | 
			
		||||
tempname()			String	name for a temporary file
 | 
			
		||||
test_garbagecollect_now()	none	free memory right now for testing
 | 
			
		||||
timer_start({time}, {callback} [, {options}])
 | 
			
		||||
				Number	create a timer
 | 
			
		||||
timer_stop({timer})		none	stop a timer
 | 
			
		||||
@@ -3589,18 +3593,25 @@ function({name} [, {arglist}] [, {dict}])
 | 
			
		||||
 | 
			
		||||
garbagecollect([{atexit}])				*garbagecollect()*
 | 
			
		||||
		Cleanup unused |Lists| and |Dictionaries| that have circular
 | 
			
		||||
		references.  There is hardly ever a need to invoke this
 | 
			
		||||
		function, as it is automatically done when Vim runs out of
 | 
			
		||||
		memory or is waiting for the user to press a key after
 | 
			
		||||
		'updatetime'.  Items without circular references are always
 | 
			
		||||
		freed when they become unused.
 | 
			
		||||
		references.
 | 
			
		||||
		
 | 
			
		||||
		There is hardly ever a need to invoke this function, as it is
 | 
			
		||||
		automatically done when Vim runs out of memory or is waiting
 | 
			
		||||
		for the user to press a key after 'updatetime'.  Items without
 | 
			
		||||
		circular references are always freed when they become unused.
 | 
			
		||||
		This is useful if you have deleted a very big |List| and/or
 | 
			
		||||
		|Dictionary| with circular references in a script that runs
 | 
			
		||||
		for a long time.
 | 
			
		||||
 | 
			
		||||
		When the optional {atexit} argument is one, garbage
 | 
			
		||||
		collection will also be done when exiting Vim, if it wasn't
 | 
			
		||||
		done before.  This is useful when checking for memory leaks.
 | 
			
		||||
 | 
			
		||||
		The garbage collection is not done immediately but only when
 | 
			
		||||
		it's safe to perform.  This is when waiting for the user to
 | 
			
		||||
		type a character.  To force garbage collection immediately use
 | 
			
		||||
		|test_garbagecollect_now()|.
 | 
			
		||||
 | 
			
		||||
get({list}, {idx} [, {default}])			*get()*
 | 
			
		||||
		Get item {idx} from |List| {list}.  When this item is not
 | 
			
		||||
		available return {default}.  Return zero when {default} is
 | 
			
		||||
@@ -7288,6 +7299,12 @@ termopen({cmd}[, {opts}])			{Nvim} *termopen()*
 | 
			
		||||
 | 
			
		||||
		See |terminal-emulator| for more information.
 | 
			
		||||
 | 
			
		||||
test_garbagecollect_now()			 *test_garbagecollect_now()*
 | 
			
		||||
		Like garbagecollect(), but executed right away.  This must
 | 
			
		||||
		only be called directly to avoid any structure to exist
 | 
			
		||||
		internally, and |v:testing| must have been set before calling
 | 
			
		||||
		any function.
 | 
			
		||||
 | 
			
		||||
tan({expr})						*tan()*
 | 
			
		||||
		Return the tangent of {expr}, measured in radians, as a |Float|
 | 
			
		||||
		in the range [-inf, inf].
 | 
			
		||||
 
 | 
			
		||||
@@ -389,6 +389,7 @@ static struct vimvar {
 | 
			
		||||
  VV(VV__NULL_LIST,     "_null_list",       VAR_LIST, VV_RO),
 | 
			
		||||
  VV(VV__NULL_DICT,     "_null_dict",       VAR_DICT, VV_RO),
 | 
			
		||||
  VV(VV_VIM_DID_ENTER,  "vim_did_enter",    VAR_NUMBER, VV_RO),
 | 
			
		||||
  VV(VV_TESTING,        "testing",          VAR_NUMBER, 0),
 | 
			
		||||
  VV(VV_TYPE_NUMBER,    "t_number",         VAR_NUMBER, VV_RO),
 | 
			
		||||
  VV(VV_TYPE_STRING,    "t_string",         VAR_NUMBER, VV_RO),
 | 
			
		||||
  VV(VV_TYPE_FUNC,      "t_func",           VAR_NUMBER, VV_RO),
 | 
			
		||||
@@ -648,8 +649,8 @@ void eval_clear(void)
 | 
			
		||||
    xfree(SCRIPT_SV(i));
 | 
			
		||||
  ga_clear(&ga_scripts);
 | 
			
		||||
 | 
			
		||||
  /* unreferenced lists and dicts */
 | 
			
		||||
  (void)garbage_collect();
 | 
			
		||||
  // unreferenced lists and dicts
 | 
			
		||||
  (void)garbage_collect(false);
 | 
			
		||||
 | 
			
		||||
  /* functions */
 | 
			
		||||
  free_all_functions();
 | 
			
		||||
@@ -5813,6 +5814,9 @@ int get_copyID(void)
 | 
			
		||||
  return current_copyID;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Used by get_func_tv()
 | 
			
		||||
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Garbage collection for lists and dictionaries.
 | 
			
		||||
 *
 | 
			
		||||
@@ -5835,16 +5839,19 @@ int get_copyID(void)
 | 
			
		||||
 | 
			
		||||
/// Do garbage collection for lists and dicts.
 | 
			
		||||
///
 | 
			
		||||
/// @param testing  true if called from test_garbagecollect_now().
 | 
			
		||||
/// @returns        true if some memory was freed.
 | 
			
		||||
bool garbage_collect(void)
 | 
			
		||||
bool garbage_collect(bool testing)
 | 
			
		||||
{
 | 
			
		||||
  bool abort = false;
 | 
			
		||||
#define ABORTING(func) abort = abort || func
 | 
			
		||||
 | 
			
		||||
  // Only do this once.
 | 
			
		||||
  want_garbage_collect = false;
 | 
			
		||||
  may_garbage_collect = false;
 | 
			
		||||
  garbage_collect_at_exit = false;
 | 
			
		||||
  if (!testing) {
 | 
			
		||||
    // Only do this once.
 | 
			
		||||
    want_garbage_collect = false;
 | 
			
		||||
    may_garbage_collect = false;
 | 
			
		||||
    garbage_collect_at_exit = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We advance by two because we add one for items referenced through
 | 
			
		||||
  // previous_funccal.
 | 
			
		||||
@@ -5954,6 +5961,12 @@ bool garbage_collect(void)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function call arguments, if v:testing is set.
 | 
			
		||||
  for (int i = 0; i < funcargs.ga_len; i++) {
 | 
			
		||||
	ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
 | 
			
		||||
							  copyID, NULL, NULL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // v: vars
 | 
			
		||||
  ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
 | 
			
		||||
 | 
			
		||||
@@ -6008,7 +6021,7 @@ bool garbage_collect(void)
 | 
			
		||||
    if (did_free_funccal) {
 | 
			
		||||
      // When a funccal was freed some more items might be garbage
 | 
			
		||||
      // collected, so run again.
 | 
			
		||||
      (void)garbage_collect();
 | 
			
		||||
      (void)garbage_collect(testing);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (p_verbose > 0) {
 | 
			
		||||
    verb_msg((char_u *)_(
 | 
			
		||||
@@ -7085,9 +7098,24 @@ get_func_tv (
 | 
			
		||||
    ret = FAIL;
 | 
			
		||||
 | 
			
		||||
  if (ret == OK) {
 | 
			
		||||
    ret = call_func(name, len, rettv, argcount, argvars,
 | 
			
		||||
    int	i = 0;
 | 
			
		||||
 | 
			
		||||
    if (get_vim_var_nr(VV_TESTING)) {
 | 
			
		||||
      // Prepare for calling garbagecollect_for_testing(), need to know
 | 
			
		||||
      // what variables are used on the call stack.
 | 
			
		||||
      if (funcargs.ga_itemsize == 0) {
 | 
			
		||||
        ga_init(&funcargs, (int)sizeof(typval_T *), 50);
 | 
			
		||||
      }
 | 
			
		||||
      for (i = 0; i < argcount; i++) {
 | 
			
		||||
        ga_grow(&funcargs, 1);
 | 
			
		||||
        ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
 | 
			
		||||
      }
 | 
			
		||||
	}
 | 
			
		||||
    ret = call_func(name, len, rettv, argcount, argvars, NULL,
 | 
			
		||||
                    firstline, lastline, doesrange, evaluate,
 | 
			
		||||
                    partial, selfdict);
 | 
			
		||||
 | 
			
		||||
    funcargs.ga_len -= i;
 | 
			
		||||
  } else if (!aborting()) {
 | 
			
		||||
    if (argcount == MAX_FUNC_ARGS) {
 | 
			
		||||
      emsg_funcname(N_("E740: Too many arguments for function %s"), name);
 | 
			
		||||
@@ -17306,6 +17334,14 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
 | 
			
		||||
  return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// "test_garbagecollect_now()" function
 | 
			
		||||
static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr)
 | 
			
		||||
{
 | 
			
		||||
  // This is dangerous, any Lists and Dicts used internally may be freed
 | 
			
		||||
  // while still in use.
 | 
			
		||||
  garbage_collect(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool callback_from_typval(Callback *callback, typval_T *arg)
 | 
			
		||||
{
 | 
			
		||||
  if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,7 @@ typedef enum {
 | 
			
		||||
    VV__NULL_LIST,  // List with NULL value. For test purposes only.
 | 
			
		||||
    VV__NULL_DICT,  // Dictionary with NULL value. For test purposes only.
 | 
			
		||||
    VV_VIM_DID_ENTER,
 | 
			
		||||
    VV_TESTING,
 | 
			
		||||
    VV_TYPE_NUMBER,
 | 
			
		||||
    VV_TYPE_STRING,
 | 
			
		||||
    VV_TYPE_FUNC,
 | 
			
		||||
 
 | 
			
		||||
@@ -303,6 +303,7 @@ return {
 | 
			
		||||
    tanh={args=1, func="float_op_wrapper", data="&tanh"},
 | 
			
		||||
    tempname={},
 | 
			
		||||
    termopen={args={1, 2}},
 | 
			
		||||
    test_garbagecollect_now={},
 | 
			
		||||
    timer_start={args={2,3}},
 | 
			
		||||
    timer_stop={args=1},
 | 
			
		||||
    tolower={args=1},
 | 
			
		||||
 
 | 
			
		||||
@@ -1327,8 +1327,9 @@ int using_script(void)
 | 
			
		||||
void before_blocking(void)
 | 
			
		||||
{
 | 
			
		||||
  updatescript(0);
 | 
			
		||||
  if (may_garbage_collect)
 | 
			
		||||
    garbage_collect();
 | 
			
		||||
  if (may_garbage_collect) {
 | 
			
		||||
    garbage_collect(false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -1366,10 +1367,11 @@ int vgetc(void)
 | 
			
		||||
  char_u buf[MB_MAXBYTES + 1];
 | 
			
		||||
  int i;
 | 
			
		||||
 | 
			
		||||
  /* Do garbage collection when garbagecollect() was called previously and
 | 
			
		||||
   * we are now at the toplevel. */
 | 
			
		||||
  if (may_garbage_collect && want_garbage_collect)
 | 
			
		||||
    garbage_collect();
 | 
			
		||||
  // Do garbage collection when garbagecollect() was called previously and
 | 
			
		||||
  // we are now at the toplevel.
 | 
			
		||||
  if (may_garbage_collect && want_garbage_collect) {
 | 
			
		||||
    garbage_collect(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * If a character was put back with vungetc, it was already processed.
 | 
			
		||||
 
 | 
			
		||||
@@ -624,8 +624,9 @@ void getout(int exitval)
 | 
			
		||||
  iconv_end();
 | 
			
		||||
#endif
 | 
			
		||||
  cs_end();
 | 
			
		||||
  if (garbage_collect_at_exit)
 | 
			
		||||
    garbage_collect();
 | 
			
		||||
  if (garbage_collect_at_exit) {
 | 
			
		||||
    garbage_collect(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mch_exit(exitval);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,12 @@ lang mess C
 | 
			
		||||
" Always use forward slashes.
 | 
			
		||||
set shellslash
 | 
			
		||||
 | 
			
		||||
" Make sure $HOME does not get read or written.
 | 
			
		||||
let $HOME = '/does/not/exist'
 | 
			
		||||
 | 
			
		||||
" Prepare for calling garbagecollect_for_testing().
 | 
			
		||||
let v:testing = 1
 | 
			
		||||
 | 
			
		||||
" Align with vim defaults.
 | 
			
		||||
set directory^=.
 | 
			
		||||
set nohidden
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										286
									
								
								src/nvim/testdir/test_lambda.vim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								src/nvim/testdir/test_lambda.vim
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
			
		||||
" Test for lambda and closure
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_feature()
 | 
			
		||||
  call assert_equal(1, has('lambda'))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_with_filter()
 | 
			
		||||
  let s:x = 2
 | 
			
		||||
  call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_with_map()
 | 
			
		||||
  let s:x = 1
 | 
			
		||||
  call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_with_sort()
 | 
			
		||||
  call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
" function! Test_lambda_with_timer()
 | 
			
		||||
"   if !has('timers')
 | 
			
		||||
"     return
 | 
			
		||||
"   endif
 | 
			
		||||
 | 
			
		||||
"   let s:n = 0
 | 
			
		||||
"   let s:timer_id = 0
 | 
			
		||||
"   function! s:Foo()
 | 
			
		||||
"     "let n = 0
 | 
			
		||||
"     let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
 | 
			
		||||
"   endfunction
 | 
			
		||||
 | 
			
		||||
"   call s:Foo()
 | 
			
		||||
"   sleep 200ms
 | 
			
		||||
"   " do not collect lambda
 | 
			
		||||
"   call garbagecollect()
 | 
			
		||||
"   let m = s:n
 | 
			
		||||
"   sleep 200ms
 | 
			
		||||
"   call timer_stop(s:timer_id)
 | 
			
		||||
"   call assert_true(m > 1)
 | 
			
		||||
"   call assert_true(s:n > m + 1)
 | 
			
		||||
"   call assert_true(s:n < 9)
 | 
			
		||||
" endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_with_partial()
 | 
			
		||||
  let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
 | 
			
		||||
  call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function Test_lambda_fails()
 | 
			
		||||
  call assert_equal(3, {a, b -> a + b}(1, 2))
 | 
			
		||||
  call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
 | 
			
		||||
  call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
 | 
			
		||||
endfunc
 | 
			
		||||
 | 
			
		||||
func Test_not_lambda()
 | 
			
		||||
  let x = {'>' : 'foo'}
 | 
			
		||||
  call assert_equal('foo', x['>'])
 | 
			
		||||
endfunc
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_capture_by_reference()
 | 
			
		||||
  let v = 1
 | 
			
		||||
  let l:F = {x -> x + v}
 | 
			
		||||
  let v = 2
 | 
			
		||||
  call assert_equal(12, l:F(10))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_side_effect()
 | 
			
		||||
  function! s:update_and_return(arr)
 | 
			
		||||
    let a:arr[1] = 5
 | 
			
		||||
    return a:arr
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  function! s:foo(arr)
 | 
			
		||||
    return {-> s:update_and_return(a:arr)}
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let arr = [3,2,1]
 | 
			
		||||
  call assert_equal([3, 5, 1], s:foo(arr)())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_refer_local_variable_from_other_scope()
 | 
			
		||||
  function! s:foo(X)
 | 
			
		||||
    return a:X() " refer l:x in s:bar()
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  function! s:bar()
 | 
			
		||||
    let x = 123
 | 
			
		||||
    return s:foo({-> x})
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  call assert_equal(123, s:bar())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_do_not_share_local_variable()
 | 
			
		||||
  function! s:define_funcs()
 | 
			
		||||
    let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
 | 
			
		||||
    let l:Two = {-> exists("a") ? a : "no"}
 | 
			
		||||
    return [l:One, l:Two]
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:F = s:define_funcs()
 | 
			
		||||
 | 
			
		||||
  call assert_equal('no', l:F[1]())
 | 
			
		||||
  call assert_equal('abc', l:F[0]())
 | 
			
		||||
  call assert_equal('no', l:F[1]())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_closure_counter()
 | 
			
		||||
  function! s:foo()
 | 
			
		||||
    let x = 0
 | 
			
		||||
    return {-> [execute("let x += 1"), x][-1]}
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:F = s:foo()
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
  call assert_equal(1, l:F())
 | 
			
		||||
  call assert_equal(2, l:F())
 | 
			
		||||
  call assert_equal(3, l:F())
 | 
			
		||||
  call assert_equal(4, l:F())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_with_a_var()
 | 
			
		||||
  function! s:foo()
 | 
			
		||||
    let x = 2
 | 
			
		||||
    return {... -> a:000 + [x]}
 | 
			
		||||
  endfunction
 | 
			
		||||
  function! s:bar()
 | 
			
		||||
    return s:foo()(1)
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  call assert_equal([1, 2], s:bar())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_call_lambda_from_lambda()
 | 
			
		||||
  function! s:foo(x)
 | 
			
		||||
    let l:F1 = {-> {-> a:x}}
 | 
			
		||||
    return {-> l:F1()}
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:F = s:foo(1)
 | 
			
		||||
  call assert_equal(1, l:F()())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_delfunc()
 | 
			
		||||
  function! s:gen()
 | 
			
		||||
    let pl = l:
 | 
			
		||||
    let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
 | 
			
		||||
    let l:Bar = l:Foo
 | 
			
		||||
    delfunction l:Foo
 | 
			
		||||
    return l:Bar
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:F = s:gen()
 | 
			
		||||
  call assert_fails(':call l:F()', 'E933:')
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_scope()
 | 
			
		||||
  function! s:NewCounter()
 | 
			
		||||
    let c = 0
 | 
			
		||||
    return {-> [execute('let c += 1'), c][-1]}
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  function! s:NewCounter2()
 | 
			
		||||
    return {-> [execute('let c += 100'), c][-1]}
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:C = s:NewCounter()
 | 
			
		||||
  let l:D = s:NewCounter2()
 | 
			
		||||
 | 
			
		||||
  call assert_equal(1, l:C())
 | 
			
		||||
  call assert_fails(':call l:D()', 'E15:') " E121: then E15:
 | 
			
		||||
  call assert_equal(2, l:C())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_share_scope()
 | 
			
		||||
  function! s:New()
 | 
			
		||||
    let c = 0
 | 
			
		||||
    let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
 | 
			
		||||
    let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
 | 
			
		||||
    return [l:Inc0, l:Dec0]
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let [l:Inc, l:Dec] = s:New()
 | 
			
		||||
 | 
			
		||||
  call assert_equal(1, l:Inc())
 | 
			
		||||
  call assert_equal(2, l:Inc())
 | 
			
		||||
  call assert_equal(1, l:Dec())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_circular_reference()
 | 
			
		||||
  function! s:Foo()
 | 
			
		||||
    let d = {}
 | 
			
		||||
    let d.f = {-> d}
 | 
			
		||||
    return d.f
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  call s:Foo()
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
  let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_lambda_combination()
 | 
			
		||||
  call assert_equal(2, {x -> {x -> x}}(1)(2))
 | 
			
		||||
  call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
 | 
			
		||||
  call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
 | 
			
		||||
  call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
 | 
			
		||||
 | 
			
		||||
  call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
 | 
			
		||||
  call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
 | 
			
		||||
 | 
			
		||||
  " Z combinator
 | 
			
		||||
  let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
 | 
			
		||||
  let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
 | 
			
		||||
  call assert_equal(120, Z(Fact)(5))
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_closure_counter()
 | 
			
		||||
  function! s:foo()
 | 
			
		||||
    let x = 0
 | 
			
		||||
    function! s:bar() closure
 | 
			
		||||
      let x += 1
 | 
			
		||||
      return x
 | 
			
		||||
    endfunction
 | 
			
		||||
    return function('s:bar')
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  let l:F = s:foo()
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
  call assert_equal(1, l:F())
 | 
			
		||||
  call assert_equal(2, l:F())
 | 
			
		||||
  call assert_equal(3, l:F())
 | 
			
		||||
  call assert_equal(4, l:F())
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! Test_closure_unlet()
 | 
			
		||||
  function! s:foo()
 | 
			
		||||
    let x = 1
 | 
			
		||||
    function! s:bar() closure
 | 
			
		||||
      unlet x
 | 
			
		||||
    endfunction
 | 
			
		||||
    call s:bar()
 | 
			
		||||
    return l:
 | 
			
		||||
  endfunction
 | 
			
		||||
 | 
			
		||||
  call assert_false(has_key(s:foo(), 'x'))
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
function! LambdaFoo()
 | 
			
		||||
  let x = 0
 | 
			
		||||
  function! LambdaBar() closure
 | 
			
		||||
    let x += 1
 | 
			
		||||
    return x
 | 
			
		||||
  endfunction
 | 
			
		||||
  return function('LambdaBar')
 | 
			
		||||
endfunction
 | 
			
		||||
 | 
			
		||||
func Test_closure_refcount()
 | 
			
		||||
  let g:Count = LambdaFoo()
 | 
			
		||||
  call test_garbagecollect_now()
 | 
			
		||||
  call assert_equal(1, g:Count())
 | 
			
		||||
  let g:Count2 = LambdaFoo()
 | 
			
		||||
  call test_garbagecollect_now()
 | 
			
		||||
  call assert_equal(1, g:Count2())
 | 
			
		||||
  call assert_equal(2, g:Count())
 | 
			
		||||
  call assert_equal(3, g:Count2())
 | 
			
		||||
 | 
			
		||||
  delfunc LambdaFoo
 | 
			
		||||
  delfunc LambdaBar
 | 
			
		||||
endfunc
 | 
			
		||||
 | 
			
		||||
func Test_named_function_closure()
 | 
			
		||||
  func! Afoo()
 | 
			
		||||
    let x = 14
 | 
			
		||||
    func! s:Abar() closure
 | 
			
		||||
      return x
 | 
			
		||||
    endfunc
 | 
			
		||||
    call assert_equal(14, s:Abar())
 | 
			
		||||
  endfunc
 | 
			
		||||
  call Afoo()
 | 
			
		||||
  call assert_equal(14, s:Abar())
 | 
			
		||||
  call garbagecollect()
 | 
			
		||||
  call assert_equal(14, s:Abar())
 | 
			
		||||
endfunc
 | 
			
		||||
@@ -714,7 +714,7 @@ static int included_patches[] = {
 | 
			
		||||
  1730,
 | 
			
		||||
  // 1729 NA
 | 
			
		||||
  1728,
 | 
			
		||||
  // 1727 NA
 | 
			
		||||
  1727,
 | 
			
		||||
  // 1726 NA
 | 
			
		||||
  // 1725 NA
 | 
			
		||||
  // 1724 NA
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user