summaryrefslogtreecommitdiffstats
path: root/src/testdir/test_user_func.vim
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
commit029f72b1a93430b24b88eb3a72c6114d9f149737 (patch)
tree765d5c2041967f9c6fef195fe343d9234a030e90 /src/testdir/test_user_func.vim
parentInitial commit. (diff)
downloadvim-029f72b1a93430b24b88eb3a72c6114d9f149737.tar.xz
vim-029f72b1a93430b24b88eb3a72c6114d9f149737.zip
Adding upstream version 2:9.1.0016.upstream/2%9.1.0016
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/testdir/test_user_func.vim')
-rw-r--r--src/testdir/test_user_func.vim971
1 files changed, 971 insertions, 0 deletions
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
new file mode 100644
index 0000000..57a093c
--- /dev/null
+++ b/src/testdir/test_user_func.vim
@@ -0,0 +1,971 @@
+" Test for user functions.
+" Also test an <expr> mapping calling a function.
+" Also test that a builtin function cannot be replaced.
+" Also test for regression when calling arbitrary expression.
+
+source check.vim
+source shared.vim
+import './vim9.vim' as v9
+
+func Table(title, ...)
+ let ret = a:title
+ let idx = 1
+ while idx <= a:0
+ exe "let ret = ret . a:" . idx
+ let idx = idx + 1
+ endwhile
+ return ret
+endfunc
+
+func Compute(n1, n2, divname)
+ if a:n2 == 0
+ return "fail"
+ endif
+ exe "let g:" . a:divname . " = ". a:n1 / a:n2
+ return "ok"
+endfunc
+
+func Expr1()
+ silent! normal! v
+ return "111"
+endfunc
+
+func Expr2()
+ call search('XX', 'b')
+ return "222"
+endfunc
+
+func ListItem()
+ let g:counter += 1
+ return g:counter . '. '
+endfunc
+
+func ListReset()
+ let g:counter = 0
+ return ''
+endfunc
+
+func FuncWithRef(a)
+ unlet g:FuncRef
+ return a:a
+endfunc
+
+func Test_user_func()
+ let g:FuncRef = function("FuncWithRef")
+ let g:counter = 0
+ inoremap <expr> ( ListItem()
+ inoremap <expr> [ ListReset()
+ imap <expr> + Expr1()
+ imap <expr> * Expr2()
+ let g:retval = "nop"
+
+ call assert_equal('xxx4asdf', Table("xxx", 4, "asdf"))
+ call assert_equal('fail', Compute(45, 0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', Compute(45, 5, "retval"))
+ call assert_equal(9, g:retval)
+ call assert_equal(333, g:FuncRef(333))
+
+ let g:retval = "nop"
+ call assert_equal('xxx4asdf', "xxx"->Table(4, "asdf"))
+ call assert_equal('fail', 45->Compute(0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', 45->Compute(5, "retval"))
+ call assert_equal(9, g:retval)
+ " call assert_equal(333, 333->g:FuncRef())
+
+ enew
+
+ normal oXX+-XX
+ call assert_equal('XX111-XX', getline('.'))
+ normal o---*---
+ call assert_equal('---222---', getline('.'))
+ normal o(one
+ call assert_equal('1. one', getline('.'))
+ normal o(two
+ call assert_equal('2. two', getline('.'))
+ normal o[(one again
+ call assert_equal('1. one again', getline('.'))
+
+ " Try to overwrite a function in the global (g:) scope
+ call assert_equal(3, max([1, 2, 3]))
+ call assert_fails("call extend(g:, {'max': function('min')})", 'E704:')
+ call assert_equal(3, max([1, 2, 3]))
+
+ " Try to overwrite an user defined function with a function reference
+ call assert_fails("let Expr1 = function('min')", 'E705:')
+
+ " Regression: the first line below used to throw ?E110: Missing ')'?
+ " Second is here just to prove that this line is correct when not skipping
+ " rhs of &&.
+ call assert_equal(0, (0 && (function('tr'))(1, 2, 3)))
+ call assert_equal(1, (1 && (function('tr'))(1, 2, 3)))
+
+ delfunc Table
+ delfunc Compute
+ delfunc Expr1
+ delfunc Expr2
+ delfunc ListItem
+ delfunc ListReset
+ unlet g:retval g:counter
+ enew!
+endfunc
+
+func Log(val, base = 10)
+ return log(a:val) / log(a:base)
+endfunc
+
+func Args(mandatory, optional = v:null, ...)
+ return deepcopy(a:)
+endfunc
+
+func Args2(a = 1, b = 2, c = 3)
+ return deepcopy(a:)
+endfunc
+
+func MakeBadFunc()
+ func s:fcn(a, b=1, c)
+ endfunc
+endfunc
+
+func Test_default_arg()
+ call assert_equal(1.0, Log(10))
+ call assert_equal(log(10), Log(10, exp(1)))
+ call assert_fails("call Log(1,2,3)", 'E118:')
+
+ let res = Args(1)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, v:null)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2,3)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 1)
+
+ call assert_fails("call MakeBadFunc()", 'E989:')
+ call assert_fails("fu F(a=1 ,) | endf", 'E1068:')
+
+ let d = Args2(7, v:none, 9)
+ call assert_equal([7, 2, 9], [d.a, d.b, d.c])
+
+ call assert_equal("\n"
+ \ .. " function Args2(a = 1, b = 2, c = 3)\n"
+ \ .. "1 return deepcopy(a:)\n"
+ \ .. " endfunction",
+ \ execute('func Args2'))
+
+ " Error in default argument expression
+ let l =<< trim END
+ func F1(x = y)
+ return a:x * 2
+ endfunc
+ echo F1()
+ END
+ let @a = l->join("\n")
+ call assert_fails("exe @a", 'E121:')
+endfunc
+
+func s:addFoo(lead)
+ return a:lead .. 'foo'
+endfunc
+
+func Test_user_method()
+ eval 'bar'->s:addFoo()->assert_equal('barfoo')
+endfunc
+
+func Test_method_with_linebreaks()
+ let lines =<< trim END
+ vim9script
+
+ export def Scan(ll: list<number>): func(func(number))
+ return (Emit: func(number)) => {
+ for v in ll
+ Emit(v)
+ endfor
+ }
+ enddef
+
+ export def Build(Cont: func(func(number))): list<number>
+ var result: list<number> = []
+ Cont((v) => {
+ add(result, v)
+ })
+ return result
+ enddef
+
+ export def Noop(Cont: func(func(number))): func(func(number))
+ return (Emit: func(number)) => {
+ Cont(Emit)
+ }
+ enddef
+ END
+ call writefile(lines, 'Xlib.vim', 'D')
+
+ let lines =<< trim END
+ vim9script
+
+ import "./Xlib.vim" as lib
+
+ const x = [1, 2, 3]
+
+ var result = lib.Scan(x)->lib.Noop()->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)->lib.Noop()
+ ->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)
+ ->lib.Noop()->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)
+ ->lib.Noop()
+ ->lib.Build()
+ assert_equal([1, 2, 3], result)
+ END
+ call v9.CheckScriptSuccess(lines)
+endfunc
+
+func Test_failed_call_in_try()
+ try | call UnknownFunc() | catch | endtry
+endfunc
+
+" Test for listing user-defined functions
+func Test_function_list()
+ call assert_fails("function Xabc", 'E123:')
+endfunc
+
+" Test for <sfile>, <slnum> in a function
+func Test_sfile_in_function()
+ func Xfunc()
+ call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>'))
+ call assert_equal('2', expand('<slnum>'))
+ endfunc
+ call Xfunc()
+ delfunc Xfunc
+endfunc
+
+" Test trailing text after :endfunction {{{1
+func Test_endfunction_trailing()
+ call assert_false(exists('*Xtest'))
+
+ exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'"
+ call assert_true(exists('*Xtest'))
+ call assert_equal('yes', done)
+ delfunc Xtest
+ unlet done
+
+ exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'"
+ call assert_true(exists('*Xtest'))
+ call assert_equal('yes', done)
+ delfunc Xtest
+ unlet done
+
+ " trailing line break
+ exe "func Xtest()\necho 'hello'\nendfunc\n"
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+
+ set verbose=1
+ exe "func Xtest()\necho 'hello'\nendfunc \" garbage"
+ call assert_notmatch('W22:', split(execute('1messages'), "\n")[0])
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+
+ exe "func Xtest()\necho 'hello'\nendfunc garbage"
+ call assert_match('W22:', split(execute('1messages'), "\n")[0])
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+ set verbose=0
+
+ func Xtest(a1, a2)
+ echo a:a1 .. a:a2
+ endfunc
+ set verbose=15
+ redir @a
+ call Xtest(123, repeat('x', 100))
+ redir END
+ call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a'))
+ delfunc Xtest
+ set verbose=0
+
+ function Foo()
+ echo 'hello'
+ endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+ delfunc Foo
+endfunc
+
+func Test_delfunction_force()
+ delfunc! Xtest
+ delfunc! Xtest
+ func Xtest()
+ echo 'nothing'
+ endfunc
+ delfunc! Xtest
+ delfunc! Xtest
+
+ " Try deleting the current function
+ call assert_fails('delfunc Test_delfunction_force', 'E131:')
+endfunc
+
+func Test_function_defined_line()
+ CheckNotGui
+
+ let lines =<< trim [CODE]
+ " F1
+ func F1()
+ " F2
+ func F2()
+ "
+ "
+ "
+ return
+ endfunc
+ " F3
+ execute "func F3()\n\n\n\nreturn\nendfunc"
+ " F4
+ execute "func F4()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ endfunc
+ " F5
+ execute "func F5()\n\n\n\nreturn\nendfunc"
+ " F6
+ execute "func F6()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ call F1()
+ verbose func F1
+ verbose func F2
+ verbose func F3
+ verbose func F4
+ verbose func F5
+ verbose func F6
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xtest.vim', 'D')
+ let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim')
+ call assert_equal(0, v:shell_error)
+
+ let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 2$', m)
+
+ let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 4$', m)
+
+ let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 11$', m)
+
+ let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 13$', m)
+
+ let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 21$', m)
+
+ let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 23$', m)
+endfunc
+
+" Test for defining a function reference in the global scope
+func Test_add_funcref_to_global_scope()
+ let x = g:
+ let caught_E862 = 0
+ try
+ func x.Xfunc()
+ return 1
+ endfunc
+ catch /E862:/
+ let caught_E862 = 1
+ endtry
+ call assert_equal(1, caught_E862)
+endfunc
+
+func Test_funccall_garbage_collect()
+ func Func(x, ...)
+ call add(a:x, a:000)
+ endfunc
+ call Func([], [])
+ " Must not crash cause by invalid freeing
+ call test_garbagecollect_now()
+ call assert_true(v:true)
+ delfunc Func
+endfunc
+
+" Test for script-local function
+func <SID>DoLast()
+ call append(line('$'), "last line")
+endfunc
+
+func s:DoNothing()
+ call append(line('$'), "nothing line")
+endfunc
+
+func Test_script_local_func()
+ set nocp nomore viminfo+=nviminfo
+ new
+ nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr>
+
+ normal _x
+ call assert_equal('nothing line', getline(2))
+ call assert_equal('last line', getline(3))
+ close!
+
+ " Try to call a script local function in global scope
+ let lines =<< trim [CODE]
+ :call assert_fails('call s:Xfunc()', 'E81:')
+ :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:')
+ :call writefile(v:errors, 'Xresult')
+ :qall
+
+ [CODE]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '-s Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for errors in defining new functions
+func Test_func_def_error()
+ call assert_fails('func Xfunc abc ()', 'E124:')
+ call assert_fails('func Xfunc(', 'E125:')
+ call assert_fails('func xfunc()', 'E128:')
+
+ " Try to redefine a function that is in use
+ let caught_E127 = 0
+ try
+ func! Test_func_def_error()
+ endfunc
+ catch /E127:/
+ let caught_E127 = 1
+ endtry
+ call assert_equal(1, caught_E127)
+
+ " Try to define a function in a dict twice
+ let d = {}
+ let lines =<< trim END
+ func d.F1()
+ return 1
+ endfunc
+ END
+ let l = join(lines, "\n") . "\n"
+ exe l
+ call assert_fails('exe l', 'E717:')
+ call assert_fails('call feedkeys(":func d.F1()\<CR>", "xt")', 'E717:')
+
+ " Define an autoload function with an incorrect file name
+ call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript', 'D')
+ call assert_fails('source Xscript', 'E746:')
+
+ " Try to list functions using an invalid search pattern
+ call assert_fails('function /\%(/', 'E53:')
+endfunc
+
+" Test for deleting a function
+func Test_del_func()
+ call assert_fails('delfunction Xabc', 'E117:')
+ let d = {'a' : 10}
+ call assert_fails('delfunc d.a', 'E718:')
+ func d.fn()
+ return 1
+ endfunc
+
+ " cannot delete the dict function by number
+ let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '')
+ call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:')
+
+ delfunc d.fn
+ call assert_equal({'a' : 10}, d)
+endfunc
+
+" Test for calling return outside of a function
+func Test_return_outside_func()
+ call writefile(['return 10'], 'Xscript', 'D')
+ call assert_fails('source Xscript', 'E133:')
+endfunc
+
+" Test for errors in calling a function
+func Test_func_arg_error()
+ " Too many arguments
+ call assert_fails("call call('min', range(1,20))", 'E118:')
+ call assert_fails("call call('min', range(1,21))", 'E699:')
+ call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)',
+ \ 'E740:')
+
+ " Missing dict argument
+ func Xfunc() dict
+ return 1
+ endfunc
+ call assert_fails('call Xfunc()', 'E725:')
+ delfunc Xfunc
+endfunc
+
+func Test_func_dict()
+ let mydict = {'a': 'b'}
+ function mydict.somefunc() dict
+ return len(self)
+ endfunc
+
+ call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict))
+ call assert_equal(2, mydict.somefunc())
+ call assert_match("^\n function \\d\\\+() dict"
+ \ .. "\n1 return len(self)"
+ \ .. "\n endfunction$", execute('func mydict.somefunc'))
+ call assert_fails('call mydict.nonexist()', 'E716:')
+endfunc
+
+func Test_func_range()
+ new
+ call setline(1, range(1, 8))
+ func FuncRange() range
+ echo a:firstline
+ echo a:lastline
+ endfunc
+ 3
+ call assert_equal("\n3\n3", execute('call FuncRange()'))
+ call assert_equal("\n4\n6", execute('4,6 call FuncRange()'))
+ call assert_equal("\n function FuncRange() range"
+ \ .. "\n1 echo a:firstline"
+ \ .. "\n2 echo a:lastline"
+ \ .. "\n endfunction",
+ \ execute('function FuncRange'))
+
+ bwipe!
+endfunc
+
+" Test for memory allocation failure when defining a new function
+func Test_funcdef_alloc_failure()
+ new
+ let lines =<< trim END
+ func Xtestfunc()
+ return 321
+ endfunc
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xtestfunc'))
+ call assert_fails('delfunc Xtestfunc', 'E117:')
+ %d _
+ let lines =<< trim END
+ def g:Xvim9func(): number
+ return 456
+ enddef
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xvim9func'))
+ "call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ "call assert_fails('source', 'E342:')
+ "call assert_false(exists('*Xtestfunc'))
+ "call assert_fails('delfunc Xtestfunc', 'E117:')
+ bw!
+endfunc
+
+func AddDefer(arg1, ...)
+ call extend(g:deferred, [a:arg1])
+ if a:0 == 1
+ call extend(g:deferred, [a:1])
+ endif
+endfunc
+
+func WithDeferTwo()
+ call extend(g:deferred, ['in Two'])
+ for nr in range(3)
+ defer AddDefer('Two' .. nr)
+ endfor
+ call extend(g:deferred, ['end Two'])
+endfunc
+
+func WithDeferOne()
+ call extend(g:deferred, ['in One'])
+ call writefile(['text'], 'Xfuncdefer')
+ defer delete('Xfuncdefer')
+ defer AddDefer('One')
+ call WithDeferTwo()
+ call extend(g:deferred, ['end One'])
+endfunc
+
+func WithPartialDefer()
+ call extend(g:deferred, ['in Partial'])
+ let Part = funcref('AddDefer', ['arg1'])
+ defer Part("arg2")
+ call extend(g:deferred, ['end Partial'])
+endfunc
+
+func Test_defer()
+ let g:deferred = []
+ call WithDeferOne()
+
+ call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
+ unlet g:deferred
+
+ call assert_equal('', glob('Xfuncdefer'))
+
+ call assert_fails('defer delete("Xfuncdefer")->Another()', 'E488:')
+ call assert_fails('defer delete("Xfuncdefer").member', 'E488:')
+
+ let g:deferred = []
+ call WithPartialDefer()
+ call assert_equal(['in Partial', 'end Partial', 'arg1', 'arg2'], g:deferred)
+ unlet g:deferred
+
+ let Part = funcref('AddDefer', ['arg1'], {})
+ call assert_fails('defer Part("arg2")', 'E1300:')
+endfunc
+
+func DeferLevelTwo()
+ call writefile(['text'], 'XDeleteTwo', 'D')
+ throw 'someerror'
+endfunc
+
+def DeferLevelOne()
+ call writefile(['text'], 'XDeleteOne', 'D')
+ call g:DeferLevelTwo()
+enddef
+
+func Test_defer_throw()
+ let caught = 'no'
+ try
+ call DeferLevelOne()
+ catch /someerror/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+ call assert_false(filereadable('XDeleteOne'))
+ call assert_false(filereadable('XDeleteTwo'))
+endfunc
+
+func Test_defer_quitall_func()
+ let lines =<< trim END
+ func DeferLevelTwo()
+ call writefile(['text'], 'XQuitallFuncTwo', 'D')
+ call writefile(['quit'], 'XQuitallFuncThree', 'a')
+ qa!
+ endfunc
+
+ func DeferLevelOne()
+ call writefile(['text'], 'XQuitalFunclOne', 'D')
+ defer DeferLevelTwo()
+ endfunc
+
+ call DeferLevelOne()
+ END
+ call writefile(lines, 'XdeferQuitallFunc', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallFunc')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('XQuitallFuncOne'))
+ call assert_false(filereadable('XQuitallFuncTwo'))
+ call assert_equal(['quit'], readfile('XQuitallFuncThree'))
+
+ call delete('XQuitallFuncThree')
+endfunc
+
+func Test_defer_quitall_def()
+ let lines =<< trim END
+ vim9script
+ def DeferLevelTwo()
+ call writefile(['text'], 'XQuitallDefTwo', 'D')
+ call writefile(['quit'], 'XQuitallDefThree', 'a')
+ qa!
+ enddef
+
+ def DeferLevelOne()
+ call writefile(['text'], 'XQuitallDefOne', 'D')
+ defer DeferLevelTwo()
+ enddef
+
+ DeferLevelOne()
+ END
+ call writefile(lines, 'XdeferQuitallDef', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallDef')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('XQuitallDefOne'))
+ call assert_false(filereadable('XQuitallDefTwo'))
+ call assert_equal(['quit'], readfile('XQuitallDefThree'))
+
+ call delete('XQuitallDefThree')
+endfunc
+
+func Test_defer_quitall_autocmd()
+ let lines =<< trim END
+ func DeferLevelFive()
+ defer writefile(['5'], 'XQuitallAutocmd', 'a')
+ qa!
+ endfunc
+
+ autocmd User DeferAutocmdFive call DeferLevelFive()
+
+ def DeferLevelFour()
+ defer writefile(['4'], 'XQuitallAutocmd', 'a')
+ doautocmd User DeferAutocmdFive
+ enddef
+
+ func DeferLevelThree()
+ defer writefile(['3'], 'XQuitallAutocmd', 'a')
+ call DeferLevelFour()
+ endfunc
+
+ autocmd User DeferAutocmdThree ++nested call DeferLevelThree()
+
+ def DeferLevelTwo()
+ defer writefile(['2'], 'XQuitallAutocmd', 'a')
+ doautocmd User DeferAutocmdThree
+ enddef
+
+ func DeferLevelOne()
+ defer writefile(['1'], 'XQuitallAutocmd', 'a')
+ call DeferLevelTwo()
+ endfunc
+
+ autocmd User DeferAutocmdOne ++nested call DeferLevelOne()
+
+ doautocmd User DeferAutocmdOne
+ END
+ call writefile(lines, 'XdeferQuitallAutocmd', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallAutocmd')
+ call assert_equal(0, v:shell_error)
+ call assert_equal(['5', '4', '3', '2', '1'], readfile('XQuitallAutocmd'))
+
+ call delete('XQuitallAutocmd')
+endfunc
+
+func Test_defer_quitall_in_expr_func()
+ let lines =<< trim END
+ def DefIndex(idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ if val == 'b'
+ qa!
+ endif
+ return val == 'c'
+ enddef
+
+ def Test_defer_in_funcref()
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref('g:DefIndex')))
+ enddef
+ call Test_defer_in_funcref()
+ END
+ call writefile(lines, 'XdeferQuitallExpr', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('Xentry0'))
+ call assert_false(filereadable('Xentry1'))
+ call assert_false(filereadable('Xentry2'))
+endfunc
+
+func FuncIndex(idx, val)
+ call writefile([a:idx .. ': ' .. a:val], 'Xentry' .. a:idx, 'D')
+ return a:val == 'c'
+endfunc
+
+def DefIndex(idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ return val == 'c'
+enddef
+
+def DefIndexXtra(xtra: string, idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ return val == 'c'
+enddef
+
+def Test_defer_in_funcref()
+ assert_equal(2, indexof(['a', 'b', 'c'], function('g:FuncIndex')))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], g:DefIndex))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], function('g:DefIndex')))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref(g:DefIndex)))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], function(g:DefIndexXtra, ['xtra'])))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref(g:DefIndexXtra, ['xtra'])))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+enddef
+
+func Test_defer_wrong_arguments()
+ call assert_fails('defer delete()', 'E119:')
+ call assert_fails('defer FuncIndex(1)', 'E119:')
+ call assert_fails('defer delete(1, 2, 3)', 'E118:')
+ call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:')
+
+ let lines =<< trim END
+ def DeferFunc0()
+ defer delete()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferFunc3()
+ defer delete(1, 2, 3)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferFunc2()
+ defer delete(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+
+ def g:FuncOneArg(arg: string)
+ echo arg
+ enddef
+
+ let lines =<< trim END
+ def DeferUserFunc0()
+ defer g:FuncOneArg()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferUserFunc2()
+ defer g:FuncOneArg(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferUserFunc1()
+ defer g:FuncOneArg(1)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+endfunc
+
+" Test for calling a deferred function after an exception
+func Test_defer_after_exception()
+ let g:callTrace = []
+ func Bar()
+ let g:callTrace += [1]
+ throw 'InnerException'
+ endfunc
+
+ func Defer()
+ let g:callTrace += [2]
+ let g:callTrace += [3]
+ try
+ call Bar()
+ catch /InnerException/
+ let g:callTrace += [4]
+ endtry
+ let g:callTrace += [5]
+ let g:callTrace += [6]
+ endfunc
+
+ func Foo()
+ defer Defer()
+ throw "TestException"
+ endfunc
+
+ try
+ call Foo()
+ catch /TestException/
+ let g:callTrace += [7]
+ endtry
+ call assert_equal([2, 3, 1, 4, 5, 6, 7], g:callTrace)
+
+ delfunc Defer
+ delfunc Foo
+ delfunc Bar
+ unlet g:callTrace
+endfunc
+
+" Test for multiple deferred function which throw exceptions.
+" Exceptions thrown by deferred functions should result in error messages but
+" not propagated into the calling functions.
+func Test_multidefer_with_exception()
+ let g:callTrace = []
+ func Except()
+ let g:callTrace += [1]
+ throw 'InnerException'
+ let g:callTrace += [2]
+ endfunc
+
+ func FirstDefer()
+ let g:callTrace += [3]
+ let g:callTrace += [4]
+ endfunc
+
+ func SecondDeferWithExcept()
+ let g:callTrace += [5]
+ call Except()
+ let g:callTrace += [6]
+ endfunc
+
+ func ThirdDefer()
+ let g:callTrace += [7]
+ let g:callTrace += [8]
+ endfunc
+
+ func Foo()
+ let g:callTrace += [9]
+ defer FirstDefer()
+ defer SecondDeferWithExcept()
+ defer ThirdDefer()
+ let g:callTrace += [10]
+ endfunc
+
+ let v:errmsg = ''
+ try
+ let g:callTrace += [11]
+ call Foo()
+ let g:callTrace += [12]
+ catch /TestException/
+ let g:callTrace += [13]
+ catch
+ let g:callTrace += [14]
+ finally
+ let g:callTrace += [15]
+ endtry
+ let g:callTrace += [16]
+
+ call assert_equal('E605: Exception not caught: InnerException', v:errmsg)
+ call assert_equal([11, 9, 10, 7, 8, 5, 1, 3, 4, 12, 15, 16], g:callTrace)
+
+ unlet g:callTrace
+ delfunc Except
+ delfunc FirstDefer
+ delfunc SecondDeferWithExcept
+ delfunc ThirdDefer
+ delfunc Foo
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab