changeset 26388:8aba638e91eb v8.2.3725

patch 8.2.3725: cannot use a lambda for 'completefunc' and 'omnifunc' Commit: https://github.com/vim/vim/commit/8658c759f05b317707d56e3b65a5ef63930c7498 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Fri Dec 3 11:09:29 2021 +0000 patch 8.2.3725: cannot use a lambda for 'completefunc' and 'omnifunc' Problem: Cannot use a lambda for 'completefunc' and 'omnifunc'. Solution: Implement lambda support. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/9257)
author Bram Moolenaar <Bram@vim.org>
date Fri, 03 Dec 2021 12:15:04 +0100
parents b6eb7e295973
children c3782f7ba000
files runtime/doc/options.txt src/buffer.c src/insexpand.c src/option.c src/optionstr.c src/proto/insexpand.pro src/proto/tag.pro src/proto/userfunc.pro src/structs.h src/tag.c src/testdir/test_ins_complete.vim src/testdir/test_tagfunc.vim src/userfunc.c src/version.c
diffstat 14 files changed, 740 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -373,8 +373,8 @@ Note: In the future more global options 
 
 						*option-value-function*
 Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
-'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name
-or a function reference or a lambda function.  Examples:
+'operatorfunc', 'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc') are set to
+a function name or a function reference or a lambda function.  Examples:
 >
 	set opfunc=MyOpFunc
 	set opfunc=function('MyOpFunc')
@@ -1939,7 +1939,9 @@ A jump table for the options with a shor
 	This option specifies a function to be used for Insert mode completion
 	with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
 	See |complete-functions| for an explanation of how the function is
-	invoked and what it should return.
+	invoked and what it should return.  The value can be the name of a
+	function, a |lambda| or a |Funcref|. See |option-value-function| for
+	more information.
 	This option cannot be set from a |modeline| or in the |sandbox|, for
 	security reasons.
 
@@ -5616,7 +5618,9 @@ A jump table for the options with a shor
 	This option specifies a function to be used for Insert mode omni
 	completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
 	See |complete-functions| for an explanation of how the function is
-	invoked and what it should return.
+	invoked and what it should return.  The value can be the name of a
+	function, a |lambda| or a |Funcref|. See |option-value-function| for
+	more information.
 	This option is usually set by a filetype plugin:
 	|:filetype-plugin-on|
 	This option cannot be set from a |modeline| or in the |sandbox|, for
@@ -8075,6 +8079,8 @@ A jump table for the options with a shor
 			feature}
 	This option specifies a function to be used for thesaurus completion
 	with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+	The value can be the name of a function, a |lambda| or a |Funcref|.
+	See |option-value-function| for more information.
 
 	This option cannot be set from a |modeline| or in the |sandbox|, for
 	security reasons.
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2326,8 +2326,11 @@ free_buf_options(
     clear_string_option(&buf->b_p_cpt);
 #ifdef FEAT_COMPL_FUNC
     clear_string_option(&buf->b_p_cfu);
+    free_callback(&buf->b_cfu_cb);
     clear_string_option(&buf->b_p_ofu);
+    free_callback(&buf->b_ofu_cb);
     clear_string_option(&buf->b_p_tsrfu);
+    free_callback(&buf->b_tsrfu_cb);
 #endif
 #ifdef FEAT_QUICKFIX
     clear_string_option(&buf->b_p_gp);
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -2237,6 +2237,113 @@ ins_compl_next_buf(buf_T *buf, int flag)
 }
 
 #ifdef FEAT_COMPL_FUNC
+
+# ifdef FEAT_EVAL
+static callback_T cfu_cb;	    // 'completefunc' callback function
+static callback_T ofu_cb;	    // 'omnifunc' callback function
+static callback_T tsrfu_cb;	    // 'thesaurusfunc' callback function
+# endif
+
+/*
+ * Copy a global callback function to a buffer local callback.
+ */
+    static void
+copy_global_to_buflocal_cb(callback_T *globcb, callback_T *bufcb)
+{
+    free_callback(bufcb);
+    if (globcb->cb_name != NULL && *globcb->cb_name != NUL)
+	copy_callback(bufcb, globcb);
+}
+
+/*
+ * Parse the 'completefunc' option value and set the callback function.
+ * Invoked when the 'completefunc' option is set. The option value can be a
+ * name of a function (string), or function(<name>) or funcref(<name>) or a
+ * lambda expression.
+ */
+    int
+set_completefunc_option(void)
+{
+    int	retval;
+
+    retval = option_set_callback_func(curbuf->b_p_cfu, &cfu_cb);
+    if (retval == OK)
+	set_buflocal_cfu_callback(curbuf);
+
+    return retval;
+}
+
+/*
+ * Copy the global 'completefunc' callback function to the buffer-local
+ * 'completefunc' callback for 'buf'.
+ */
+    void
+set_buflocal_cfu_callback(buf_T *buf UNUSED)
+{
+# ifdef FEAT_EVAL
+    copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb);
+# endif
+}
+
+/*
+ * Parse the 'omnifunc' option value and set the callback function.
+ * Invoked when the 'omnifunc' option is set. The option value can be a
+ * name of a function (string), or function(<name>) or funcref(<name>) or a
+ * lambda expression.
+ */
+    int
+set_omnifunc_option(void)
+{
+    int	retval;
+
+    retval = option_set_callback_func(curbuf->b_p_ofu, &ofu_cb);
+    if (retval == OK)
+	set_buflocal_ofu_callback(curbuf);
+
+    return retval;
+}
+
+/*
+ * Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc'
+ * callback for 'buf'.
+ */
+    void
+set_buflocal_ofu_callback(buf_T *buf UNUSED)
+{
+# ifdef FEAT_EVAL
+    copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb);
+# endif
+}
+
+/*
+ * Parse the 'thesaurusfunc' option value and set the callback function.
+ * Invoked when the 'thesaurusfunc' option is set. The option value can be a
+ * name of a function (string), or function(<name>) or funcref(<name>) or a
+ * lambda expression.
+ */
+    int
+set_thesaurusfunc_option(void)
+{
+    int	retval;
+
+    if (*curbuf->b_p_tsrfu != NUL)
+    {
+	// buffer-local option set
+	free_callback(&curbuf->b_tsrfu_cb);
+	retval = option_set_callback_func(curbuf->b_p_tsrfu,
+							&curbuf->b_tsrfu_cb);
+    }
+    else
+    {
+	// global option set
+	free_callback(&tsrfu_cb);
+	retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
+    }
+
+    return retval;
+}
+
+
 /*
  * Get the user-defined completion function name for completion 'type'
  */
@@ -2257,6 +2364,20 @@ get_complete_funcname(int type)
 }
 
 /*
+ * Get the callback to use for insert mode completion.
+ */
+    callback_T *
+get_insert_callback(int type)
+{
+    if (type == CTRL_X_FUNCTION)
+	return &curbuf->b_cfu_cb;
+    if (type == CTRL_X_OMNI)
+	return &curbuf->b_ofu_cb;
+    // CTRL_X_THESAURUS
+    return (*curbuf->b_p_tsrfu != NUL) ? &curbuf->b_tsrfu_cb : &tsrfu_cb;
+}
+
+/*
  * Execute user defined complete function 'completefunc', 'omnifunc' or
  * 'thesaurusfunc', and get matches in "matches".
  * "type" is either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS.
@@ -2269,8 +2390,10 @@ expand_by_function(int type, char_u *bas
     typval_T	args[3];
     char_u	*funcname;
     pos_T	pos;
+    callback_T	*cb;
     typval_T	rettv;
     int		save_State = State;
+    int		retval;
 
     funcname = get_complete_funcname(type);
     if (*funcname == NUL)
@@ -2289,8 +2412,11 @@ expand_by_function(int type, char_u *bas
     // Insert mode in another buffer.
     ++textwinlock;
 
+    cb = get_insert_callback(type);
+    retval = call_callback(cb, 0, &rettv, 2, args);
+
     // Call a function, which returns a list or dict.
-    if (call_vim_function(funcname, 2, args, &rettv) == OK)
+    if (retval == OK)
     {
 	switch (rettv.v_type)
 	{
@@ -3971,6 +4097,7 @@ ins_complete(int c, int enable_pum)
 	    char_u	*funcname;
 	    pos_T	pos;
 	    int		save_State = State;
+	    callback_T	*cb;
 
 	    // Call 'completefunc' or 'omnifunc' and get pattern length as a
 	    // string
@@ -3991,7 +4118,8 @@ ins_complete(int c, int enable_pum)
 	    args[2].v_type = VAR_UNKNOWN;
 	    pos = curwin->w_cursor;
 	    ++textwinlock;
-	    col = call_func_retnr(funcname, 2, args);
+	    cb = get_insert_callback(ctrl_x_mode);
+	    col = call_callback_retnr(cb, 2, args);
 	    --textwinlock;
 
 	    State = save_State;
@@ -4339,6 +4467,11 @@ quote_meta(char_u *dest, char_u *src, in
 free_insexpand_stuff(void)
 {
     VIM_CLEAR(compl_orig_text);
+# ifdef FEAT_EVAL
+    free_callback(&cfu_cb);
+    free_callback(&ofu_cb);
+    free_callback(&tsrfu_cb);
+# endif
 }
 #endif
 
--- a/src/option.c
+++ b/src/option.c
@@ -5927,13 +5927,15 @@ buf_copy_options(buf_T *buf, int flags)
 #ifdef FEAT_COMPL_FUNC
 	    buf->b_p_cfu = vim_strsave(p_cfu);
 	    COPY_OPT_SCTX(buf, BV_CFU);
+	    set_buflocal_cfu_callback(buf);
 	    buf->b_p_ofu = vim_strsave(p_ofu);
 	    COPY_OPT_SCTX(buf, BV_OFU);
+	    set_buflocal_ofu_callback(buf);
 #endif
 #ifdef FEAT_EVAL
 	    buf->b_p_tfu = vim_strsave(p_tfu);
 	    COPY_OPT_SCTX(buf, BV_TFU);
-	    buf_set_tfu_callback(buf);
+	    set_buflocal_tfu_callback(buf);
 #endif
 	    buf->b_p_sts = p_sts;
 	    COPY_OPT_SCTX(buf, BV_STS);
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2307,6 +2307,29 @@ ambw_end:
 # endif
 #endif
 
+#ifdef FEAT_COMPL_FUNC
+    // 'completefunc'
+    else if (gvarp == &p_cfu)
+    {
+	if (set_completefunc_option() == FAIL)
+	    errmsg = e_invarg;
+    }
+
+    // 'omnifunc'
+    else if (gvarp == &p_ofu)
+    {
+	if (set_omnifunc_option() == FAIL)
+	    errmsg = e_invarg;
+    }
+
+    // 'thesaurusfunc'
+    else if (gvarp == &p_tsrfu)
+    {
+	if (set_thesaurusfunc_option() == FAIL)
+	    errmsg = e_invarg;
+    }
+#endif
+
     // 'operatorfunc'
     else if (varp == &p_opfunc)
     {
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -39,6 +39,12 @@ int ins_compl_bs(void);
 void ins_compl_addleader(int c);
 void ins_compl_addfrommatch(void);
 int ins_compl_prep(int c);
+int set_completefunc_option(void);
+void set_buflocal_cfu_callback(buf_T *buf);
+int set_omnifunc_option(void);
+void set_buflocal_ofu_callback(buf_T *buf);
+int set_thesaurusfunc_option(void);
+callback_T *get_insert_callback(int type);
 void f_complete(typval_T *argvars, typval_T *rettv);
 void f_complete_add(typval_T *argvars, typval_T *rettv);
 void f_complete_check(typval_T *argvars, typval_T *rettv);
--- a/src/proto/tag.pro
+++ b/src/proto/tag.pro
@@ -1,7 +1,7 @@
 /* tag.c */
 int set_tagfunc_option(void);
 void free_tagfunc_option(void);
-void buf_set_tfu_callback(buf_T *buf);
+void set_buflocal_tfu_callback(buf_T *buf);
 int do_tag(char_u *tag, int type, int count, int forceit, int verbose);
 void tag_freematch(void);
 void do_tags(exarg_T *eap);
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -28,6 +28,7 @@ int builtin_function(char_u *name, int l
 int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv);
 int get_callback_depth(void);
 int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
+varnumber_T call_callback_retnr(callback_T *callback, int argcount, typval_T *argvars);
 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 *printable_func_name(ufunc_T *fp);
--- a/src/structs.h
+++ b/src/structs.h
@@ -2876,7 +2876,9 @@ struct file_buffer
 #endif
 #ifdef FEAT_COMPL_FUNC
     char_u	*b_p_cfu;	// 'completefunc'
+    callback_T	b_cfu_cb;	// 'completefunc' callback
     char_u	*b_p_ofu;	// 'omnifunc'
+    callback_T	b_ofu_cb;	// 'omnifunc' callback
 #endif
 #ifdef FEAT_EVAL
     char_u	*b_p_tfu;	// 'tagfunc' option value
@@ -2982,6 +2984,7 @@ struct file_buffer
     char_u	*b_p_tsr;	// 'thesaurus' local value
 #ifdef FEAT_COMPL_FUNC
     char_u	*b_p_tsrfu;	// 'thesaurusfunc' local value
+    callback_T	b_tsrfu_cb;	// 'thesaurusfunc' callback
 #endif
     long	b_p_ul;		// 'undolevels' local value
 #ifdef FEAT_PERSISTENT_UNDO
--- a/src/tag.c
+++ b/src/tag.c
@@ -115,7 +115,7 @@ static callback_T tfu_cb;	    // 'tagfun
  * a function (string), or function(<name>) or funcref(<name>) or a lambda.
  */
     int
-set_tagfunc_option()
+set_tagfunc_option(void)
 {
 #ifdef FEAT_EVAL
     free_callback(&tfu_cb);
@@ -148,7 +148,7 @@ free_tagfunc_option(void)
  * callback for 'buf'.
  */
     void
-buf_set_tfu_callback(buf_T *buf UNUSED)
+set_buflocal_tfu_callback(buf_T *buf UNUSED)
 {
 #ifdef FEAT_EVAL
     free_callback(&buf->b_tfu_cb);
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -2,6 +2,7 @@
 
 source screendump.vim
 source check.vim
+source vim9.vim
 
 " Test for insert expansion
 func Test_ins_complete()
@@ -867,4 +868,503 @@ func Test_z1_complete_no_history()
   close!
 endfunc
 
+" Test for different ways of setting the 'completefunc' option
+func Test_completefunc_callback()
+  " Test for using a function()
+  func MycompleteFunc1(findstart, base)
+    call add(g:MycompleteFunc1_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set completefunc=function('MycompleteFunc1')
+  new | only
+  call setline(1, 'one')
+  let g:MycompleteFunc1_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'one']], g:MycompleteFunc1_args)
+  bw!
+
+  " Using a funcref variable to set 'completefunc'
+  let Fn = function('MycompleteFunc1')
+  let &completefunc = string(Fn)
+  new | only
+  call setline(1, 'two')
+  let g:MycompleteFunc1_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'two']], g:MycompleteFunc1_args)
+  call assert_fails('let &completefunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a funcref()
+  func MycompleteFunc2(findstart, base)
+    call add(g:MycompleteFunc2_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set completefunc=funcref('MycompleteFunc2')
+  new | only
+  call setline(1, 'three')
+  let g:MycompleteFunc2_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'three']], g:MycompleteFunc2_args)
+  bw!
+
+  " Using a funcref variable to set 'completefunc'
+  let Fn = funcref('MycompleteFunc2')
+  let &completefunc = string(Fn)
+  new | only
+  call setline(1, 'four')
+  let g:MycompleteFunc2_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'four']], g:MycompleteFunc2_args)
+  call assert_fails('let &completefunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a lambda function
+  func MycompleteFunc3(findstart, base)
+    call add(g:MycompleteFunc3_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set completefunc={a,\ b,\ ->\ MycompleteFunc3(a,\ b,)}
+  new | only
+  call setline(1, 'five')
+  let g:MycompleteFunc3_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'five']], g:MycompleteFunc3_args)
+  bw!
+
+  " Set 'completefunc' to a lambda expression
+  let &completefunc = '{a, b -> MycompleteFunc3(a, b)}'
+  new | only
+  call setline(1, 'six')
+  let g:MycompleteFunc3_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'six']], g:MycompleteFunc3_args)
+  bw!
+
+  " Set 'completefunc' to a variable with a lambda expression
+  let Lambda = {a, b -> MycompleteFunc3(a, b)}
+  let &completefunc = string(Lambda)
+  new | only
+  call setline(1, 'seven')
+  let g:MycompleteFunc3_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'seven']], g:MycompleteFunc3_args)
+  call assert_fails('let &completefunc = Lambda', 'E729:')
+  bw!
+
+  " Test for using a lambda function with incorrect return value
+  let Lambda = {s -> strlen(s)}
+  let &completefunc = string(Lambda)
+  new | only
+  call setline(1, 'eight')
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  bw!
+
+  " Test for clearing the 'completefunc' option
+  set completefunc=''
+  set completefunc&
+
+  call assert_fails("set completefunc=function('abc')", "E700:")
+  call assert_fails("set completefunc=funcref('abc')", "E700:")
+  let &completefunc = "{a -> 'abc'}"
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+
+  " Vim9 tests
+  let lines =<< trim END
+    vim9script
+
+    # Test for using function()
+    def MycompleteFunc1(findstart: number, base: string): any
+      add(g:MycompleteFunc1_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    set completefunc=function('MycompleteFunc1')
+    new | only
+    setline(1, 'one')
+    g:MycompleteFunc1_args = []
+    feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'one']], g:MycompleteFunc1_args)
+    bw!
+
+    # Test for using a lambda
+    def MycompleteFunc2(findstart: number, base: string): any
+      add(g:MycompleteFunc2_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    &completefunc = '(a, b) => MycompleteFunc2(a, b)'
+    new | only
+    setline(1, 'two')
+    g:MycompleteFunc2_args = []
+    feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'two']], g:MycompleteFunc2_args)
+    bw!
+
+    # Test for using a variable with a lambda expression
+    var Fn: func = (a, b) => MycompleteFunc2(a, b)
+    &completefunc = string(Fn)
+    new | only
+    setline(1, 'three')
+    g:MycompleteFunc2_args = []
+    feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'three']], g:MycompleteFunc2_args)
+    bw!
+  END
+  call CheckScriptSuccess(lines)
+
+  " Using Vim9 lambda expression in legacy context should fail
+  set completefunc=(a,\ b)\ =>\ g:MycompleteFunc2(a,\ b)
+  new | only
+  let g:MycompleteFunc2_args = []
+  call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
+  call assert_equal([], g:MycompleteFunc2_args)
+
+  " cleanup
+  delfunc MycompleteFunc1
+  delfunc MycompleteFunc2
+  delfunc MycompleteFunc3
+  set completefunc&
+  %bw!
+endfunc
+
+" Test for different ways of setting the 'omnifunc' option
+func Test_omnifunc_callback()
+  " Test for using a function()
+  func MyomniFunc1(findstart, base)
+    call add(g:MyomniFunc1_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set omnifunc=function('MyomniFunc1')
+  new | only
+  call setline(1, 'one')
+  let g:MyomniFunc1_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'one']], g:MyomniFunc1_args)
+  bw!
+
+  " Using a funcref variable to set 'omnifunc'
+  let Fn = function('MyomniFunc1')
+  let &omnifunc = string(Fn)
+  new | only
+  call setline(1, 'two')
+  let g:MyomniFunc1_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'two']], g:MyomniFunc1_args)
+  call assert_fails('let &omnifunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a funcref()
+  func MyomniFunc2(findstart, base)
+    call add(g:MyomniFunc2_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set omnifunc=funcref('MyomniFunc2')
+  new | only
+  call setline(1, 'three')
+  let g:MyomniFunc2_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'three']], g:MyomniFunc2_args)
+  bw!
+
+  " Using a funcref variable to set 'omnifunc'
+  let Fn = funcref('MyomniFunc2')
+  let &omnifunc = string(Fn)
+  new | only
+  call setline(1, 'four')
+  let g:MyomniFunc2_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'four']], g:MyomniFunc2_args)
+  call assert_fails('let &omnifunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a lambda function
+  func MyomniFunc3(findstart, base)
+    call add(g:MyomniFunc3_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set omnifunc={a,\ b,\ ->\ MyomniFunc3(a,\ b,)}
+  new | only
+  call setline(1, 'five')
+  let g:MyomniFunc3_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'five']], g:MyomniFunc3_args)
+  bw!
+
+  " Set 'omnifunc' to a lambda expression
+  let &omnifunc = '{a, b -> MyomniFunc3(a, b)}'
+  new | only
+  call setline(1, 'six')
+  let g:MyomniFunc3_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'six']], g:MyomniFunc3_args)
+  bw!
+
+  " Set 'omnifunc' to a variable with a lambda expression
+  let Lambda = {a, b -> MyomniFunc3(a, b)}
+  let &omnifunc = string(Lambda)
+  new | only
+  call setline(1, 'seven')
+  let g:MyomniFunc3_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'seven']], g:MyomniFunc3_args)
+  call assert_fails('let &omnifunc = Lambda', 'E729:')
+  bw!
+
+  " Test for using a lambda function with incorrect return value
+  let Lambda = {s -> strlen(s)}
+  let &omnifunc = string(Lambda)
+  new | only
+  call setline(1, 'eight')
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  bw!
+
+  " Test for clearing the 'omnifunc' option
+  set omnifunc=''
+  set omnifunc&
+
+  call assert_fails("set omnifunc=function('abc')", "E700:")
+  call assert_fails("set omnifunc=funcref('abc')", "E700:")
+  let &omnifunc = "{a -> 'abc'}"
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+
+  " Vim9 tests
+  let lines =<< trim END
+    vim9script
+
+    # Test for using function()
+    def MyomniFunc1(findstart: number, base: string): any
+      add(g:MyomniFunc1_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    set omnifunc=function('MyomniFunc1')
+    new | only
+    setline(1, 'one')
+    g:MyomniFunc1_args = []
+    feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'one']], g:MyomniFunc1_args)
+    bw!
+
+    # Test for using a lambda
+    def MyomniFunc2(findstart: number, base: string): any
+      add(g:MyomniFunc2_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    &omnifunc = '(a, b) => MyomniFunc2(a, b)'
+    new | only
+    setline(1, 'two')
+    g:MyomniFunc2_args = []
+    feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'two']], g:MyomniFunc2_args)
+    bw!
+
+    # Test for using a variable with a lambda expression
+    var Fn: func = (a, b) => MyomniFunc2(a, b)
+    &omnifunc = string(Fn)
+    new | only
+    setline(1, 'three')
+    g:MyomniFunc2_args = []
+    feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'three']], g:MyomniFunc2_args)
+    bw!
+  END
+  call CheckScriptSuccess(lines)
+
+  " Using Vim9 lambda expression in legacy context should fail
+  set omnifunc=(a,\ b)\ =>\ g:MyomniFunc2(a,\ b)
+  new | only
+  let g:MyomniFunc2_args = []
+  call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
+  call assert_equal([], g:MyomniFunc2_args)
+
+  " cleanup
+  delfunc MyomniFunc1
+  delfunc MyomniFunc2
+  delfunc MyomniFunc3
+  set omnifunc&
+  %bw!
+endfunc
+
+" Test for different ways of setting the 'thesaurusfunc' option
+func Test_thesaurusfunc_callback()
+  " Test for using a function()
+  func MytsrFunc1(findstart, base)
+    call add(g:MytsrFunc1_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set thesaurusfunc=function('MytsrFunc1')
+  new | only
+  call setline(1, 'one')
+  let g:MytsrFunc1_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'one']], g:MytsrFunc1_args)
+  bw!
+
+  " Using a funcref variable to set 'thesaurusfunc'
+  let Fn = function('MytsrFunc1')
+  let &thesaurusfunc = string(Fn)
+  new | only
+  call setline(1, 'two')
+  let g:MytsrFunc1_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'two']], g:MytsrFunc1_args)
+  call assert_fails('let &thesaurusfunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a funcref()
+  func MytsrFunc2(findstart, base)
+    call add(g:MytsrFunc2_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set thesaurusfunc=funcref('MytsrFunc2')
+  new | only
+  call setline(1, 'three')
+  let g:MytsrFunc2_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'three']], g:MytsrFunc2_args)
+  bw!
+
+  " Using a funcref variable to set 'thesaurusfunc'
+  let Fn = funcref('MytsrFunc2')
+  let &thesaurusfunc = string(Fn)
+  new | only
+  call setline(1, 'four')
+  let g:MytsrFunc2_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'four']], g:MytsrFunc2_args)
+  call assert_fails('let &thesaurusfunc = Fn', 'E729:')
+  bw!
+
+  " Test for using a lambda function
+  func MytsrFunc3(findstart, base)
+    call add(g:MytsrFunc3_args, [a:findstart, a:base])
+    return a:findstart ? 0 : []
+  endfunc
+  set thesaurusfunc={a,\ b,\ ->\ MytsrFunc3(a,\ b,)}
+  new | only
+  call setline(1, 'five')
+  let g:MytsrFunc3_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'five']], g:MytsrFunc3_args)
+  bw!
+
+  " Set 'thesaurusfunc' to a lambda expression
+  let &thesaurusfunc = '{a, b -> MytsrFunc3(a, b)}'
+  new | only
+  call setline(1, 'six')
+  let g:MytsrFunc3_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'six']], g:MytsrFunc3_args)
+  bw!
+
+  " Set 'thesaurusfunc' to a variable with a lambda expression
+  let Lambda = {a, b -> MytsrFunc3(a, b)}
+  let &thesaurusfunc = string(Lambda)
+  new | only
+  call setline(1, 'seven')
+  let g:MytsrFunc3_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'seven']], g:MytsrFunc3_args)
+  call assert_fails('let &thesaurusfunc = Lambda', 'E729:')
+  bw!
+
+  " Test for using a lambda function with incorrect return value
+  let Lambda = {s -> strlen(s)}
+  let &thesaurusfunc = string(Lambda)
+  new | only
+  call setline(1, 'eight')
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  bw!
+
+  " Test for clearing the 'thesaurusfunc' option
+  set thesaurusfunc=''
+  set thesaurusfunc&
+
+  call assert_fails("set thesaurusfunc=function('abc')", "E700:")
+  call assert_fails("set thesaurusfunc=funcref('abc')", "E700:")
+  let &thesaurusfunc = "{a -> 'abc'}"
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+
+  " Vim9 tests
+  let lines =<< trim END
+    vim9script
+
+    # Test for using function()
+    def MytsrFunc1(findstart: number, base: string): any
+      add(g:MytsrFunc1_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    set thesaurusfunc=function('MytsrFunc1')
+    new | only
+    setline(1, 'one')
+    g:MytsrFunc1_args = []
+    feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'one']], g:MytsrFunc1_args)
+    bw!
+
+    # Test for using a lambda
+    def MytsrFunc2(findstart: number, base: string): any
+      add(g:MytsrFunc2_args, [findstart, base])
+      return findstart ? 0 : []
+    enddef
+    &thesaurusfunc = '(a, b) => MytsrFunc2(a, b)'
+    new | only
+    setline(1, 'two')
+    g:MytsrFunc2_args = []
+    feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'two']], g:MytsrFunc2_args)
+    bw!
+
+    # Test for using a variable with a lambda expression
+    var Fn: func = (a, b) => MytsrFunc2(a, b)
+    &thesaurusfunc = string(Fn)
+    new | only
+    setline(1, 'three')
+    g:MytsrFunc2_args = []
+    feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+    assert_equal([[1, ''], [0, 'three']], g:MytsrFunc2_args)
+    bw!
+  END
+  call CheckScriptSuccess(lines)
+
+  " Using Vim9 lambda expression in legacy context should fail
+  set thesaurusfunc=(a,\ b)\ =>\ g:MytsrFunc2(a,\ b)
+  new | only
+  let g:MytsrFunc2_args = []
+  call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
+  call assert_equal([], g:MytsrFunc2_args)
+  bw!
+
+  " Use a buffer-local value and a global value
+  func MytsrFunc4(findstart, base)
+    call add(g:MytsrFunc4_args, [a:findstart, a:base])
+    return a:findstart ? 0 : ['sunday']
+  endfunc
+  set thesaurusfunc&
+  setlocal thesaurusfunc=function('MytsrFunc4')
+  call setline(1, 'sun')
+  let g:MytsrFunc4_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+  call assert_equal('sunday', getline(1))
+  call assert_equal([[1, ''], [0, 'sun']], g:MytsrFunc4_args)
+  new
+  call setline(1, 'sun')
+  let g:MytsrFunc4_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+  call assert_equal('sun', getline(1))
+  call assert_equal([], g:MytsrFunc4_args)
+  set thesaurusfunc=function('MytsrFunc1')
+  wincmd w
+  call setline(1, 'sun')
+  let g:MytsrFunc4_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+  call assert_equal('sunday', getline(1))
+  call assert_equal([[1, ''], [0, 'sun']], g:MytsrFunc4_args)
+
+  " cleanup
+  set thesaurusfunc&
+  delfunc MytsrFunc1
+  delfunc MytsrFunc2
+  delfunc MytsrFunc3
+  delfunc MytsrFunc4
+  %bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/testdir/test_tagfunc.vim
+++ b/src/testdir/test_tagfunc.vim
@@ -119,6 +119,12 @@ func Test_tagfunc_settagstack()
   delfunc Mytagfunc2
 endfunc
 
+" Script local tagfunc callback function
+func s:ScriptLocalTagFunc(pat, flags, info)
+  let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info]
+  return v:null
+endfunc
+
 " Test for different ways of setting the 'tagfunc' option
 func Test_tagfunc_callback()
   " Test for using a function()
@@ -161,6 +167,21 @@ func Test_tagfunc_callback()
   call assert_equal(['a14', '', {}], g:MytagFunc2_args)
   call assert_fails('let &tagfunc = Fn', 'E729:')
 
+  " Test for using a script local function
+  set tagfunc=<SID>ScriptLocalTagFunc
+  new | only
+  let g:ScriptLocalFuncArgs = []
+  call assert_fails('tag a15', 'E433:')
+  call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs)
+
+  " Test for using a script local funcref variable
+  let Fn = function("s:ScriptLocalTagFunc")
+  let &tagfunc= string(Fn)
+  new | only
+  let g:ScriptLocalFuncArgs = []
+  call assert_fails('tag a16', 'E433:')
+  call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+
   " Test for using a lambda function
   func MytagFunc3(pat, flags, info)
     let g:MytagFunc3_args = [a:pat, a:flags, a:info]
@@ -169,30 +190,30 @@ func Test_tagfunc_callback()
   set tagfunc={a,\ b,\ c\ ->\ MytagFunc3(a,\ b,\ c)}
   new | only
   let g:MytagFunc3_args = []
-  call assert_fails('tag a15', 'E433:')
-  call assert_equal(['a15', '', {}], g:MytagFunc3_args)
+  call assert_fails('tag a17', 'E433:')
+  call assert_equal(['a17', '', {}], g:MytagFunc3_args)
 
   " Set 'tagfunc' to a lambda expression
   let &tagfunc = '{a, b, c -> MytagFunc3(a, b, c)}'
   new | only
   let g:MytagFunc3_args = []
-  call assert_fails('tag a16', 'E433:')
-  call assert_equal(['a16', '', {}], g:MytagFunc3_args)
+  call assert_fails('tag a18', 'E433:')
+  call assert_equal(['a18', '', {}], g:MytagFunc3_args)
 
   " Set 'tagfunc' to a variable with a lambda expression
   let Lambda = {a, b, c -> MytagFunc3(a, b, c)}
   let &tagfunc = string(Lambda)
   new | only
   let g:MytagFunc3_args = []
-  call assert_fails("tag a17", "E433:")
-  call assert_equal(['a17', '', {}], g:MytagFunc3_args)
+  call assert_fails("tag a19", "E433:")
+  call assert_equal(['a19', '', {}], g:MytagFunc3_args)
   call assert_fails('let &tagfunc = Lambda', 'E729:')
 
   " Test for using a lambda function with incorrect return value
   let Lambda = {s -> strlen(s)}
   let &tagfunc = string(Lambda)
   new | only
-  call assert_fails("tag a17", "E987:")
+  call assert_fails("tag a20", "E987:")
 
   " Test for clearing the 'tagfunc' option
   set tagfunc=''
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3169,6 +3169,29 @@ call_callback(
 }
 
 /*
+ * call the 'callback' function and return the result as a number.
+ * Returns -1 when calling the function fails.  Uses argv[0] to argv[argc - 1]
+ * for the function arguments. argv[argc] should have type VAR_UNKNOWN.
+ */
+    varnumber_T
+call_callback_retnr(
+    callback_T	*callback,
+    int		argcount,	// number of "argvars"
+    typval_T	*argvars)	// vars for arguments, must have "argcount"
+				// PLUS ONE elements!
+{
+    typval_T	rettv;
+    varnumber_T	retval;
+
+    if (call_callback(callback, 0, &rettv, argcount, argvars) == FAIL)
+	return -1;
+
+    retval = tv_get_number_chk(&rettv, NULL);
+    clear_tv(&rettv);
+    return retval;
+}
+
+/*
  * Give an error message for the result of a function.
  * Nothing if "error" is FCERR_NONE.
  */
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3725,
+/**/
     3724,
 /**/
     3723,