diff options
Diffstat (limited to 'src/testdir/test_user_func.vim')
-rw-r--r-- | src/testdir/test_user_func.vim | 802 |
1 files changed, 802 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..ade259c --- /dev/null +++ b/src/testdir/test_user_func.vim @@ -0,0 +1,802 @@ +" 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() + let lines =<< trim END + vim9script + func DeferLevelTwo() + call writefile(['text'], 'XQuitallTwo', 'D') + qa! + endfunc + + def DeferLevelOne() + call writefile(['text'], 'XQuitallOne', 'D') + call DeferLevelTwo() + enddef + + DeferLevelOne() + END + call writefile(lines, 'XdeferQuitall', 'D') + let res = system(GetVimCommand() .. ' -X -S XdeferQuitall') + call assert_equal(0, v:shell_error) + call assert_false(filereadable('XQuitallOne')) + call assert_false(filereadable('XQuitallTwo')) +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') + let res = 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 + + +" vim: shiftwidth=2 sts=2 expandtab |