diff options
-rw-r--r-- | runtime/doc/eval.txt | 86 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 2 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 10 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 1383 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval/encode.c | 67 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.h | 9 | ||||
-rw-r--r-- | src/nvim/eval_defs.h | 15 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/normal.c | 2 | ||||
-rw-r--r-- | src/nvim/tag.c | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_expr.vim | 22 | ||||
-rw-r--r-- | src/nvim/testdir/test_partial.vim | 330 | ||||
-rw-r--r-- | src/nvim/testdir/test_timers.vim | 23 | ||||
-rw-r--r-- | src/nvim/testdir/test_viml.vim | 81 | ||||
-rw-r--r-- | src/nvim/version.c | 59 | ||||
-rw-r--r-- | test/functional/core/job_partial_spec.lua | 27 | ||||
-rw-r--r-- | test/functional/core/job_spec.lua | 39 | ||||
-rw-r--r-- | test/functional/ex_cmds/dict_notifications_spec.lua | 20 | ||||
-rw-r--r-- | test/functional/legacy/055_list_and_dict_types_spec.lua | 10 | ||||
-rw-r--r-- | test/functional/terminal/buffer_spec.lua | 2 |
23 files changed, 1747 insertions, 449 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 53f14c7d47..367f64df9b 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -49,6 +49,9 @@ String A NUL terminated string of 8-bit unsigned characters (bytes). Funcref A reference to a function |Funcref|. Example: function("strlen") + It can be bound to a dictionary and arguments, it then works + like a Partial. + Example: function("Callback", [arg], myDict) List An ordered sequence of items |List|. Example: [1, 2, ['a', 'b']] @@ -139,6 +142,40 @@ The name of the referenced function can be obtained with |string()|. > You can use |call()| to invoke a Funcref and use a list variable for the arguments: > :let r = call(Fn, mylist) +< + *Partial* +A Funcref optionally binds a Dictionary and/or arguments. This is also called +a Partial. This is created by passing the Dictionary and/or arguments to +function(). When calling the function the Dictionary and/or arguments will be +passed to the function. Example: > + + let Cb = function('Callback', ['foo'], myDict) + call Cb() + +This will invoke the function as if using: > + call myDict.Callback('foo') + +Note that binding a function to a Dictionary also happens when the function is +a member of the Dictionary: > + + let myDict.myFunction = MyFunction + call myDict.myFunction() + +Here MyFunction() will get myDict passed as "self". This happens when the +"myFunction" member is accessed. When assigning "myFunction" to otherDict +and calling it, it will be bound to otherDict: > + + let otherDict.myFunction = myDict.myFunction + call otherDict.myFunction() + +Now "self" will be "otherDict". But when the dictionary was bound explicitly +this won't happen: > + + let myDict.myFunction = function(MyFunction, myDict) + let otherDict.myFunction = myDict.myFunction + call otherDict.myFunction() + +Here "self" will be "myDict", because it was bound explitly. 1.3 Lists ~ @@ -1913,10 +1950,12 @@ foldlevel({lnum}) Number fold level at {lnum} foldtext() String line displayed for closed fold foldtextresult({lnum}) String text for closed fold at {lnum} foreground() Number bring the Vim window to the foreground -function({name}) Funcref reference to function {name} +function({name} [, {arglist}] [, {dict}]) + Funcref reference to function {name} garbagecollect([{atexit}]) none free memory, breaking cyclic references get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def} get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def} +get({func}, {what}) any get property of funcref/partial {func} getbufline({expr}, {lnum} [, {end}]) List lines {lnum} to {end} of buffer {expr} getbufvar({expr}, {varname} [, {def}]) @@ -3483,10 +3522,46 @@ foreground() Move the Vim window to the foreground. Useful when sent from {only in the Win32 GUI and console version} -function({name}) *function()* *E700* + *function()* *E700* *E922* *E929* +function({name} [, {arglist}] [, {dict}]) Return a |Funcref| variable that refers to function {name}. {name} can be a user defined function or an internal function. + When {arglist} or {dict} is present this creates a partial. + That mans the argument list and/or the dictionary is stored in + the Funcref and will be used when the Funcref is called. + + The arguments are passed to the function in front of other + arguments. Example: > + func Callback(arg1, arg2, name) + ... + let Func = function('Callback', ['one', 'two']) + ... + call Func('name') +< Invokes the function as with: > + call Callback('one', 'two', 'name') + +< The Dictionary is only useful when calling a "dict" function. + In that case the {dict} is passed in as "self". Example: > + function Callback() dict + echo "called for " . self.name + endfunction + ... + let context = {"name": "example"} + let Func = function('Callback', context) + ... + call Func() " will echo: called for example + +< The argument list and the Dictionary can be combined: > + function Callback(arg1, count) dict + ... + let context = {"name": "example"} + let Func = function('Callback', ['one'], context) + ... + call Func(500) +< Invokes the function as with: > + call context.Callback('one', 500) + garbagecollect([{atexit}]) *garbagecollect()* Cleanup unused |Lists| and |Dictionaries| that have circular @@ -3510,6 +3585,13 @@ get({dict}, {key} [, {default}]) Get item with key {key} from |Dictionary| {dict}. When this item is not available return {default}. Return zero when {default} is omitted. +get({func}, {what}) + Get item {what} from Funcref {func}. Possible values for + {what} are: + 'name' The function name + 'func' The function + 'dict' The dictionary + 'args' The list with arguments *getbufline()* getbufline({expr}, {lnum} [, {end}]) diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7ccdfd2bdd..cdd616b6c1 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -235,7 +235,7 @@ Additional differences: These legacy Vim features may be implemented in the future, but they are not planned for the current milestone. -- vim.bindeval() (new feature in Vim 7.4 Python interface) +- |if_py|: vim.bindeval() and vim.Function() are not supported - |if_lua| - |if_perl| - |if_mzscheme| diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 72db7c0782..b004cfc7a1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -360,6 +360,9 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_FUNC(fun) \ TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + TYPVAL_ENCODE_CONV_NIL() + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) @@ -482,6 +485,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT @@ -653,7 +657,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) if (!object_to_vim(item, &li->li_tv, err)) { // cleanup listitem_free(li); - list_free(list, true); + list_free(list); return false; } @@ -677,7 +681,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); // cleanup - dict_free(dict, true); + dict_free(dict); return false; } @@ -686,7 +690,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup dictitem_free(di); - dict_free(dict, true); + dict_free(dict); return false; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e59e955aed..b17b59f7a6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -225,7 +225,7 @@ Object nvim_call_function(String fname, Array args, Error *err) &rettv, (int) args.size, vim_args, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, - NULL); + NULL, NULL); if (r == FAIL) { api_set_error(err, Exception, _("Error calling function.")); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2a2f94f629..a205c37d6e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -217,9 +217,11 @@ static int echo_attr = 0; /* attributes used for ":echo" */ /* The names of packages that once were loaded are remembered. */ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; -/* list heads for garbage collection */ -static dict_T *first_dict = NULL; /* list of all dicts */ -static list_T *first_list = NULL; /* list of all lists */ +// List heads for garbage collection. Although there can be a reference loop +// from partial to dict to partial, we don't need to keep track of the partial, +// since it will get freed when the dict is unused and gets freed. +static dict_T *first_dict = NULL; // list of all dicts +static list_T *first_list = NULL; // list of all lists #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] @@ -411,6 +413,21 @@ static struct vimvar { static dictitem_T vimvars_var; // variable used for v: #define vimvarht vimvardict.dv_hashtab +typedef enum { + kCallbackNone, + kCallbackFuncref, + kCallbackPartial, +} CallbackType; + +typedef struct { + union { + char_u *funcref; + partial_T *partial; + } data; + CallbackType type; +} Callback; +#define CALLBACK_NONE ((Callback){ .type = kCallbackNone }) + typedef struct { union { LibuvProcess uv; @@ -422,15 +439,14 @@ typedef struct { bool exited; bool rpc; int refcount; - ufunc_T *on_stdout, *on_stderr, *on_exit; - dict_T *self; + Callback on_stdout, on_stderr, on_exit; int *status_ptr; uint64_t id; MultiQueue *events; } TerminalJobData; typedef struct dict_watcher { - ufunc_T *callback; + Callback callback; char *key_pattern; QUEUE node; bool busy; // prevent recursion if the dict is changed in the callback @@ -438,7 +454,7 @@ typedef struct dict_watcher { typedef struct { TerminalJobData *data; - ufunc_T *callback; + Callback *callback; const char *type; list_T *received; int status; @@ -451,7 +467,7 @@ typedef struct { int refcount; long timeout; bool stopped; - ufunc_T *callback; + Callback callback; } timer_T; typedef void (*FunPtr)(void); @@ -1208,7 +1224,7 @@ int call_vim_function( rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL); + &doesrange, true, NULL, NULL); if (safe) { --sandbox; restore_funccal(save_funccalp); @@ -2455,6 +2471,7 @@ static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: break; @@ -2756,8 +2773,9 @@ void ex_call(exarg_T *eap) typval_T rettv; linenr_T lnum; int doesrange; - int failed = FALSE; + bool failed = false; funcdict_T fudi; + partial_T *partial = NULL; if (eap->skip) { /* trans_function_name() doesn't work well when skipping, use eval0() @@ -2770,7 +2788,7 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi); + tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { /* Still need to give an error message for missing key. */ EMSG2(_(e_dictkey), fudi.fd_newkey); @@ -2784,9 +2802,12 @@ void ex_call(exarg_T *eap) if (fudi.fd_dict != NULL) ++fudi.fd_dict->dv_refcount; - /* If it is the name of a variable of type VAR_FUNC use its contents. */ + // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its + // contents. For VAR_PARTIAL get its partial, unless we already have one + // from trans_function_name(). len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, FALSE); + name = deref_func_name(tofree, &len, + partial != NULL ? NULL : &partial, false); /* Skip white space to allow ":call func ()". Not good, but required for * backward compatibility. */ @@ -2817,9 +2838,9 @@ void ex_call(exarg_T *eap) } arg = startarg; if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, - eap->line1, eap->line2, &doesrange, - !eap->skip, fudi.fd_dict) == FAIL) { - failed = TRUE; + eap->line1, eap->line2, &doesrange, + !eap->skip, partial, fudi.fd_dict) == FAIL) { + failed = true; break; } @@ -3182,6 +3203,7 @@ static void item_lock(typval_T *tv, int deep, int lock) case VAR_FLOAT: case VAR_STRING: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: break; case VAR_UNKNOWN: @@ -3754,26 +3776,37 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) if (type == TYPE_NEQUAL) n1 = !n1; } - } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC) { - if (rettv->v_type != var2.v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { - if (rettv->v_type != var2.v_type) - EMSG(_("E693: Can only compare Funcref with Funcref")); - else - EMSG(_("E694: Invalid operation for Funcrefs")); + } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL + || var2.v_type == VAR_PARTIAL) { + if (type != TYPE_EQUAL && type != TYPE_NEQUAL) { + EMSG(_("E694: Invalid operation for Funcrefs")); clear_tv(rettv); clear_tv(&var2); return FAIL; + } + if ((rettv->v_type == VAR_PARTIAL + && rettv->vval.v_partial == NULL) + || (var2.v_type == VAR_PARTIAL + && var2.vval.v_partial == NULL)) { + // when a partial is NULL assume not equal + n1 = false; + } else if (type_is) { + if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC) { + // strings are considered the same if their value is + // the same + n1 = tv_equal(rettv, &var2, ic, false); + } else if (rettv->v_type == VAR_PARTIAL + && var2.v_type == VAR_PARTIAL) { + n1 = (rettv->vval.v_partial == var2.vval.v_partial); + } else { + n1 = false; + } } else { - /* Compare two Funcrefs for being equal or unequal. */ - if (rettv->vval.v_string == NULL - || var2.vval.v_string == NULL) - n1 = FALSE; - else - n1 = STRCMP(rettv->vval.v_string, - var2.vval.v_string) == 0; - if (type == TYPE_NEQUAL) - n1 = !n1; + n1 = tv_equal(rettv, &var2, ic, false); + } + if (type == TYPE_NEQUAL) { + n1 = !n1; } } /* @@ -4308,14 +4341,15 @@ static int eval7( ret = FAIL; } else { if (**arg == '(') { // recursive! + partial_T *partial; // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, !evaluate); + s = deref_func_name(s, &len, &partial, !evaluate); // Invoke the function. ret = get_func_tv(s, len, rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, NULL); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, partial, NULL); // If evaluate is false rettv->v_type was not set in // get_func_tv, but it's needed in handle_subscript() to parse @@ -4418,7 +4452,8 @@ eval_index ( char_u *key = NULL; switch (rettv->v_type) { - case VAR_FUNC: { + case VAR_FUNC: + case VAR_PARTIAL: { if (verbose) { EMSG(_("E695: Cannot index a Funcref")); } @@ -4638,6 +4673,7 @@ eval_index ( break; case VAR_SPECIAL: case VAR_FUNC: + case VAR_PARTIAL: case VAR_FLOAT: case VAR_UNKNOWN: break; // Not evaluating, skipping over subscript @@ -4891,10 +4927,29 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/* - * Allocate a variable for a List and fill it from "*arg". - * Return OK or FAIL. - */ +static void partial_free(partial_T *pt) +{ + for (int i = 0; i < pt->pt_argc; i++) { + clear_tv(&pt->pt_argv[i]); + } + xfree(pt->pt_argv); + dict_unref(pt->pt_dict); + func_unref(pt->pt_name); + xfree(pt->pt_name); + xfree(pt); +} + +/// Unreference a closure: decrement the reference count and free it when it +/// becomes zero. +void partial_unref(partial_T *pt) +{ + if (pt != NULL && --pt->pt_refcount <= 0) { + partial_free(pt); + } +} + +/// Allocate a variable for a List and fill it from "*arg". +/// Return OK or FAIL. static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; @@ -4928,8 +4983,9 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) if (**arg != ']') { EMSG2(_("E697: Missing end of List ']': %s"), *arg); failret: - if (evaluate) - list_free(l, TRUE); + if (evaluate) { + list_free(l); + } return FAIL; } @@ -4973,47 +5029,48 @@ static list_T *rettv_list_alloc(typval_T *rettv) return l; } -/* - * Unreference a list: decrement the reference count and free it when it - * becomes zero. - */ -void list_unref(list_T *l) -{ - if (l != NULL && --l->lv_refcount <= 0) - list_free(l, TRUE); +/// Unreference a list: decrement the reference count and free it when it +/// becomes zero. +void list_unref(list_T *l) { + if (l != NULL && --l->lv_refcount <= 0) { + list_free(l); + } } -/* - * Free a list, including all items it points to. - * Ignores the reference count. - */ -void -list_free ( - list_T *l, - int recurse /* Free Lists and Dictionaries recursively. */ -) -{ +/// Free a list, including all items it points to. +/// Ignores the reference count. +static void list_free_contents(list_T *l) { listitem_T *item; - /* Remove the list from the list of lists for garbage collection. */ - if (l->lv_used_prev == NULL) - first_list = l->lv_used_next; - else - l->lv_used_prev->lv_used_next = l->lv_used_next; - if (l->lv_used_next != NULL) - l->lv_used_next->lv_used_prev = l->lv_used_prev; - for (item = l->lv_first; item != NULL; item = l->lv_first) { - /* Remove the item before deleting it. */ + // Remove the item before deleting it. l->lv_first = item->li_next; - if (recurse || (item->li_tv.v_type != VAR_LIST - && item->li_tv.v_type != VAR_DICT)) - clear_tv(&item->li_tv); + clear_tv(&item->li_tv); xfree(item); } +} + +static void list_free_list(list_T *l) { + // Remove the list from the list of lists for garbage collection. + if (l->lv_used_prev == NULL) { + first_list = l->lv_used_next; + } else { + l->lv_used_prev->lv_used_next = l->lv_used_next; + } + if (l->lv_used_next != NULL) { + l->lv_used_next->lv_used_prev = l->lv_used_prev; + } + xfree(l); } +void list_free(list_T *l) { + if (!in_free_unref_items) { + list_free_contents(l); + list_free_list(l); + } +} + /* * Allocate a list item. * It is not initialized, don't forget to set v_lock. @@ -5125,6 +5182,60 @@ dict_equal ( static int tv_equal_recurse_limit; +static bool func_equal( + typval_T *tv1, + typval_T *tv2, + bool ic // ignore case +) { + char_u *s1, *s2; + dict_T *d1, *d2; + int a1, a2; + + // empty and NULL function name considered the same + s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string + : tv1->vval.v_partial->pt_name; + if (s1 != NULL && *s1 == NUL) { + s1 = NULL; + } + s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string + : tv2->vval.v_partial->pt_name; + if (s2 != NULL && *s2 == NUL) { + s2 = NULL; + } + if (s1 == NULL || s2 == NULL) { + if (s1 != s2) { + return false; + } + } else if (STRCMP(s1, s2) != 0) { + return false; + } + + // empty dict and NULL dict is different + d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; + d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; + if (d1 == NULL || d2 == NULL) { + if (d1 != d2) { + return false; + } + } else if (!dict_equal(d1, d2, ic, true)) { + return false; + } + + // empty list and no list considered the same + a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; + a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; + if (a1 != a2) { + return false; + } + for (int i = 0; i < a1; i++) { + if (!tv_equal(tv1->vval.v_partial->pt_argv + i, + tv2->vval.v_partial->pt_argv + i, ic, true)) { + return false; + } + } + return true; +} + /* * Return TRUE if "tv1" and "tv2" have the same value. * Compares the items just like "==" would compare them, but strings and @@ -5143,9 +5254,6 @@ tv_equal ( static int recursive_cnt = 0; /* catch recursive loops */ int r; - if (tv1->v_type != tv2->v_type) - return FALSE; - /* Catch lists and dicts that have an endless loop by limiting * recursiveness to a limit. We guess they are equal then. * A fixed limit has the problem of still taking an awful long time. @@ -5159,6 +5267,20 @@ tv_equal ( return TRUE; } + // For VAR_FUNC and VAR_PARTIAL only compare the function name. + if ((tv1->v_type == VAR_FUNC + || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) + && (tv2->v_type == VAR_FUNC + || (tv2->v_type == VAR_PARTIAL && tv2->vval.v_partial != NULL))) { + recursive_cnt++; + r = func_equal(tv1, tv2, ic); + recursive_cnt--; + return r; + } + if (tv1->v_type != tv2->v_type) { + return false; + } + switch (tv1->v_type) { case VAR_LIST: ++recursive_cnt; @@ -5172,11 +5294,6 @@ tv_equal ( --recursive_cnt; return r; - case VAR_FUNC: - return tv1->vval.v_string != NULL - && tv2->vval.v_string != NULL - && STRCMP(tv1->vval.v_string, tv2->vval.v_string) == 0; - case VAR_NUMBER: return tv1->vval.v_number == tv2->vval.v_number; @@ -5191,6 +5308,8 @@ tv_equal ( case VAR_SPECIAL: return tv1->vval.v_special == tv2->vval.v_special; + case VAR_FUNC: + case VAR_PARTIAL: case VAR_UNKNOWN: // VAR_UNKNOWN can be the result of an invalid expression, let’s say it does // not equal anything, not even self. @@ -5804,7 +5923,17 @@ bool garbage_collect(void) { TerminalJobData *data; map_foreach_value(jobs, data, { - ABORTING(set_ref_dict)(data->self, copyID); + set_ref_in_callback(&data->on_stdout, copyID, NULL, NULL); + set_ref_in_callback(&data->on_stderr, copyID, NULL, NULL); + set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); + }) + } + + // Timers + { + timer_T *timer; + map_foreach_value(timers, timer, { + set_ref_in_callback(&timer->callback, copyID, NULL, NULL); }) } @@ -5880,42 +6009,63 @@ bool garbage_collect(void) /// @return true, if something was freed. static int free_unref_items(int copyID) { + dict_T *dd, *dd_next; + list_T *ll, *ll_next; bool did_free = false; + // Let all "free" functions know that we are here. This means no + // dictionaries, lists, or jobs are to be freed, because we will + // do that here. + in_free_unref_items = true; + + // PASS 1: free the contents of the items. We don't free the items + // themselves yet, so that it is possible to decrement refcount counters. + // Go through the list of dicts and free items without the copyID. // Don't free dicts that are referenced internally. - for (dict_T *dd = first_dict; dd != NULL; ) { + for (dict_T *dd = first_dict; dd != NULL; dd = dd->dv_used_next) { if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { // Free the Dictionary and ordinary items it contains, but don't // recurse into Lists and Dictionaries, they will be in the list - // of dicts or list of lists. */ - dict_T *dd_next = dd->dv_used_next; - dict_free(dd, FALSE); + // of dicts or list of lists. + dict_free_contents(dd); did_free = true; - dd = dd_next; - } else { - dd = dd->dv_used_next; } } // Go through the list of lists and free items without the copyID. // But don't free a list that has a watcher (used in a for loop), these // are not referenced anywhere. - for (list_T *ll = first_list; ll != NULL;) { + for (list_T *ll = first_list; ll != NULL; ll = ll->lv_used_next) { if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. - list_T* ll_next = ll->lv_used_next; - list_free(ll, FALSE); + list_free_contents(ll); did_free = true; - ll = ll_next; - } else { - ll = ll->lv_used_next; } } + // PASS 2: free the items themselves. + for (dd = first_dict; dd != NULL; dd = dd_next) { + dd_next = dd->dv_used_next; + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { + dict_free_dict(dd); + } + } + + for (ll = first_list; ll != NULL; ll = ll_next) { + ll_next = ll->lv_used_next; + if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + && ll->lv_watch == NULL) { + // Free the List and ordinary items it contains, but don't recurse + // into Lists and Dictionaries, they will be in the list of dicts + // or list of lists. + list_free_list(ll); + } + } + in_free_unref_items = false; return did_free; } @@ -6033,6 +6183,24 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, *ht_stack = newitem; } } + + QUEUE *w = NULL; + DictWatcher *watcher = NULL; + QUEUE_FOREACH(w, &dd->watchers) { + watcher = dictwatcher_node_data(w); + set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack); + } + } + if (tv->v_type == VAR_PARTIAL) { + partial_T *pt = tv->vval.v_partial; + int i; + + if (pt != NULL) { + for (i = 0; i < pt->pt_argc; i++) { + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + } + } } break; } @@ -6058,6 +6226,26 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } + case VAR_PARTIAL: { + partial_T *pt = tv->vval.v_partial; + + // A partial does not have a copyID, because it cannot contain itself. + if (pt != NULL) { + if (pt->pt_dict != NULL) { + typval_T dtv; + + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + abort = abort || set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + for (int i = 0; i < pt->pt_argc; i++) { + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + } + } + break; + } case VAR_FUNC: case VAR_UNKNOWN: case VAR_SPECIAL: @@ -6181,31 +6369,18 @@ void dict_clear(dict_T *d) */ void dict_unref(dict_T *d) { - if (d != NULL && --d->dv_refcount <= 0) - dict_free(d, TRUE); + if (d != NULL && --d->dv_refcount <= 0) { + dict_free(d); + } } -/* - * Free a Dictionary, including all items it contains. - * Ignores the reference count. - */ -void -dict_free ( - dict_T *d, - int recurse /* Free Lists and Dictionaries recursively. */ -) -{ +/// Free a Dictionary, including all items it contains. +/// Ignores the reference count. +static void dict_free_contents(dict_T *d) { int todo; hashitem_T *hi; dictitem_T *di; - /* Remove the dict from the list of dicts for garbage collection. */ - if (d->dv_used_prev == NULL) - first_dict = d->dv_used_next; - else - d->dv_used_prev->dv_used_next = d->dv_used_next; - if (d->dv_used_next != NULL) - d->dv_used_next->dv_used_prev = d->dv_used_prev; /* Lock the hashtab, we don't want it to resize while freeing items. */ hash_lock(&d->dv_hashtab); @@ -6217,9 +6392,7 @@ dict_free ( * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); - if (recurse || (di->di_tv.v_type != VAR_LIST - && di->di_tv.v_type != VAR_DICT)) - clear_tv(&di->di_tv); + clear_tv(&di->di_tv); xfree(di); --todo; } @@ -6233,9 +6406,29 @@ dict_free ( } hash_clear(&d->dv_hashtab); +} + +static void dict_free_dict(dict_T *d) { + // Remove the dict from the list of dicts for garbage collection. + if (d->dv_used_prev == NULL) { + first_dict = d->dv_used_next; + } else { + d->dv_used_prev->dv_used_next = d->dv_used_next; + } + if (d->dv_used_next != NULL) { + d->dv_used_next->dv_used_prev = d->dv_used_prev; + } + xfree(d); } +void dict_free(dict_T *d) { + if (!in_free_unref_items) { + dict_free_contents(d); + dict_free_dict(d); + } +} + /* * Allocate a Dictionary item. * The "key" is copied to the new item. @@ -6477,49 +6670,28 @@ dictitem_T *dict_find(dict_T *d, char_u *key, int len) /// @param[out] result The address where a pointer to the wanted callback /// will be left. /// @return true/false on success/failure. -static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result) +static bool get_dict_callback(dict_T *d, char *key, Callback *result) { dictitem_T *di = dict_find(d, (uint8_t *)key, -1); if (di == NULL) { - *result = NULL; + result->type = kCallbackNone; return true; } - - if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING) { - EMSG(_("Argument is not a function or function name")); - *result = NULL; - return false; - } - if ((*result = find_ufunc(di->di_tv.vval.v_string)) == NULL) { + if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING + && di->di_tv.v_type != VAR_PARTIAL) { + EMSG(_("Argument is not a function or function name")); + result->type = kCallbackNone; return false; } - (*result)->uf_refcount++; - return true; -} - -static ufunc_T *find_ufunc(uint8_t *name) -{ - uint8_t *n = name; - ufunc_T *rv = NULL; - if (*n > '9' || *n < '0') { - if ((n = trans_function_name(&n, false, TFN_INT|TFN_QUIET, NULL))) { - rv = find_func(n); - xfree(n); - } - } else { - // dict function, name is already translated - rv = find_func(n); - } - - if (!rv) { - EMSG2(_("Function %s doesn't exist"), name); - return NULL; - } - - return rv; + typval_T tv; + copy_tv(&di->di_tv, &tv); + set_selfdict(&tv, d); + bool res = callback_from_typval(result, &tv); + clear_tv(&tv); + return res; } /// Get a string item from a dictionary. @@ -6642,8 +6814,9 @@ static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate) if (**arg != '}') { EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: - if (evaluate) - dict_free(d, TRUE); + if (evaluate) { + dict_free(d); + } return FAIL; } @@ -6794,14 +6967,20 @@ static VimLFuncDef *find_internal_func(const char *const name) return find_internal_func_gperf(name, len); } -/* - * Check if "name" is a variable of type VAR_FUNC. If so, return the function - * name it contains, otherwise return "name". - */ -static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload) +/// Check if "name" is a variable of type VAR_FUNC. If so, return the function +/// name it contains, otherwise return "name". +/// If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set +/// "partialp". +static char_u *deref_func_name( + char_u *name, int *lenp, + partial_T **partialp, bool no_autoload +) { dictitem_T *v; int cc; + if (partialp != NULL) { + *partialp = NULL; + } cc = name[*lenp]; name[*lenp] = NUL; @@ -6816,6 +6995,20 @@ static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload) return v->di_tv.vval.v_string; } + if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { + partial_T *pt = v->di_tv.vval.v_partial; + + if (pt == NULL) { + *lenp = 0; + return (char_u *)""; // just in case + } + if (partialp != NULL) { + *partialp = pt; + } + *lenp = (int)STRLEN(pt->pt_name); + return pt->pt_name; + } + return name; } @@ -6833,7 +7026,8 @@ get_func_tv ( linenr_T lastline, /* last line of range */ int *doesrange, /* return: function handled range */ int evaluate, - dict_T *selfdict /* Dictionary for "self" */ + partial_T *partial, // for extra arguments + dict_T *selfdict // Dictionary for "self" ) { char_u *argp; @@ -6845,10 +7039,11 @@ get_func_tv ( * Get the arguments. */ argp = *arg; - while (argcount < MAX_FUNC_ARGS) { - argp = skipwhite(argp + 1); /* skip the '(' or ',' */ - if (*argp == ')' || *argp == ',' || *argp == NUL) + while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { break; + } if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { ret = FAIL; break; @@ -6862,14 +7057,16 @@ get_func_tv ( else ret = FAIL; - if (ret == OK) + if (ret == OK) { ret = call_func(name, len, rettv, argcount, argvars, - firstline, lastline, doesrange, evaluate, selfdict); - else if (!aborting()) { - if (argcount == MAX_FUNC_ARGS) + firstline, lastline, doesrange, evaluate, + partial, selfdict); + } else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); - else + } else { emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } } while (--argcount >= 0) @@ -6879,28 +7076,6 @@ get_func_tv ( return ret; } - -/* - * Call a function with its resolved parameters - * Return FAIL when the function can't be called, OK otherwise. - * Also returns OK when an error was encountered while executing the function. - */ -int -call_func ( - char_u *funcname, /* name of the function */ - int len, /* length of "name" */ - typval_T *rettv, /* return value goes here */ - int argcount, /* number of "argvars" */ - typval_T *argvars, /* vars for arguments, must have "argcount" - PLUS ONE elements! */ - linenr_T firstline, /* first line of range */ - linenr_T lastline, /* last line of range */ - int *doesrange, /* return: function handled range */ - int evaluate, - dict_T *selfdict /* Dictionary for "self" */ -) -{ - int ret = FAIL; #define ERROR_UNKNOWN 0 #define ERROR_TOOMANY 1 #define ERROR_TOOFEW 2 @@ -6908,35 +7083,31 @@ call_func ( #define ERROR_DICT 4 #define ERROR_NONE 5 #define ERROR_OTHER 6 - int error = ERROR_NONE; - int llen; - ufunc_T *fp; +#define ERROR_BOTH 7 #define FLEN_FIXED 40 - char_u fname_buf[FLEN_FIXED + 1]; - char_u *fname; - char_u *name; - /* Make a copy of the name, if it comes from a funcref variable it could - * be changed or deleted in the called function. */ - name = vim_strnsave(funcname, len); +/// In a script change <SID>name() and s:name() to K_SNR 123_name(). +/// Change <SNR>123_name() to K_SNR 123_name(). +/// Use "fname_buf[FLEN_FIXED + 1]" when it fits, otherwise allocate memory +/// (slow). +static char_u * +fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { + int llen; + char_u *fname; + int i; - /* - * In a script change <SID>name() and s:name() to K_SNR 123_name(). - * Change <SNR>123_name() to K_SNR 123_name(). - * Use fname_buf[] when it fits, otherwise allocate memory (slow). - */ llen = eval_fname_script(name); if (llen > 0) { fname_buf[0] = K_SPECIAL; fname_buf[1] = KS_EXTRA; fname_buf[2] = (int)KE_SNR; - int i = 3; + i = 3; if (eval_fname_sid(name)) { // "<SID>" or "s:" if (current_SID <= 0) { - error = ERROR_SCRIPT; + *error = ERROR_SCRIPT; } else { - vim_snprintf((char *)fname_buf + 3, ARRAY_SIZE(fname_buf) - 3, - "%" PRId64 "_", (int64_t)current_SID); + snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_", + (int64_t)current_SID); i = (int)STRLEN(fname_buf); } } @@ -6945,13 +7116,83 @@ call_func ( fname = fname_buf; } else { fname = xmalloc(i + STRLEN(name + llen) + 1); - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + if (fname == NULL) { + *error = ERROR_OTHER; + } else { + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } } - } else + } else { fname = name; + } + + return fname; +} + +/// Call a function with its resolved parameters +/// Return FAIL when the function can't be called, OK otherwise. +/// Also returns OK when an error was encountered while executing the function. +int +call_func( + char_u *funcname, // name of the function + int len, // length of "name" + typval_T *rettv, // return value goes here + int argcount_in, // number of "argvars" + typval_T *argvars_in, // vars for arguments, must have "argcount" + // PLUS ONE elements! + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // return: function handled range + bool evaluate, + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" +) +{ + int ret = FAIL; + int error = ERROR_NONE; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + char_u *name; + int argcount = argcount_in; + typval_T *argvars = argvars_in; + dict_T *selfdict = selfdict_in; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + int argv_clear = 0; + + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. + name = vim_strnsave(funcname, len); + if (name == NULL) { + return ret; + } + + fname = fname_trans_sid(name, fname_buf, &tofree, &error); - *doesrange = FALSE; + *doesrange = false; + + if (partial != NULL) { + // When the function has a partial with a dict and there is a dict + // argument, use the dict argument. That is backwards compatible. + // When the dict was bound explicitly use the one from the partial. + if (partial->pt_dict != NULL + && (selfdict_in == NULL || !partial->pt_auto)) { + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + copy_tv(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (int i = 0; i < argcount_in; i++) { + argv[i + argv_clear] = argvars_in[i]; + } + argvars = argv; + argcount = partial->pt_argc + argcount_in; + } + } /* execute the function if no errors detected and executing */ @@ -7059,8 +7300,10 @@ call_func ( } } - if (fname != name && fname != fname_buf) - xfree(fname); + while (argv_clear > 0) { + clear_tv(&argv[--argv_clear]); + } + xfree(tofree); xfree(name); return ret; @@ -7823,7 +8066,8 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) byteidx(argvars, rettv, TRUE); } -int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) +int func_call(char_u *name, typval_T *args, partial_T *partial, + dict_T *selfdict, typval_T *rettv) { listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; @@ -7833,7 +8077,7 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) for (item = args->vval.v_list->lv_first; item != NULL; item = item->li_next) { - if (argc == MAX_FUNC_ARGS) { + if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); break; } @@ -7843,10 +8087,11 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) copy_tv(&item->li_tv, &argv[argc++]); } - if (item == NULL) + if (item == NULL) { r = call_func(name, (int)STRLEN(name), rettv, argc, argv, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, TRUE, selfdict); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); + } /* Free the arguments. */ while (argc > 0) @@ -7855,12 +8100,11 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) return r; } -/* - * "call(func, arglist)" function - */ +/// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *func; + partial_T *partial = NULL; dict_T *selfdict = NULL; if (argvars[1].v_type != VAR_LIST) { @@ -7870,12 +8114,17 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].vval.v_list == NULL) return; - if (argvars[0].v_type == VAR_FUNC) + if (argvars[0].v_type == VAR_FUNC) { func = argvars[0].vval.v_string; - else + } else if (argvars[0].v_type == VAR_PARTIAL) { + partial = argvars[0].vval.v_partial; + func = partial->pt_name; + } else { func = get_tv_string(&argvars[0]); - if (*func == NUL) - return; /* type error or empty name */ + } + if (*func == NUL) { + return; // type error or empty name + } if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT) { @@ -7885,7 +8134,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) selfdict = argvars[2].vval.v_dict; } - (void)func_call(func, &argvars[1], selfdict, rettv); + func_call(func, &argvars[1], partial, selfdict, rettv); } /* @@ -8316,16 +8565,14 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - ufunc_T *func = find_ufunc(argvars[2].vval.v_string); - if (!func) { - // Invalid function name. Error already reported by `find_ufunc`. + Callback callback; + if (!callback_from_typval(&callback, &argvars[2])) { return; } - func->uf_refcount++; DictWatcher *watcher = xmalloc(sizeof(DictWatcher)); watcher->key_pattern = xmemdupz(key_pattern, key_len); - watcher->callback = func; + watcher->callback = callback; watcher->busy = false; QUEUE_INSERT_TAIL(&argvars[0].vval.v_dict->watchers, &watcher->node); } @@ -8361,9 +8608,8 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - ufunc_T *func = find_ufunc(argvars[2].vval.v_string); - if (!func) { - // Invalid function name. Error already reported by `find_ufunc`. + Callback callback; + if (!callback_from_typval(&callback, &argvars[2])) { return; } @@ -8373,13 +8619,15 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool matched = false; QUEUE_FOREACH(w, &dict->watchers) { watcher = dictwatcher_node_data(w); - if (func == watcher->callback + if (callback_equal(&watcher->callback, &callback) && !strcmp(watcher->key_pattern, key_pattern)) { matched = true; break; } } + callback_free(&callback); + if (!matched) { EMSG("Couldn't find a watcher matching key and callback"); return; @@ -8467,6 +8715,9 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = argvars[0].vval.v_string == NULL || *argvars[0].vval.v_string == NUL; break; + case VAR_PARTIAL: + n = false; + break; case VAR_NUMBER: n = argvars[0].vval.v_number == 0; break; @@ -9342,35 +9593,155 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *s; + char_u *name; + bool use_string = false; + partial_T *arg_pt = NULL; + + if (argvars[0].v_type == VAR_FUNC) { + // function(MyFunc, [arg], dict) + s = argvars[0].vval.v_string; + } else if (argvars[0].v_type == VAR_PARTIAL + && argvars[0].vval.v_partial != NULL) { + // function(dict.MyFunc, [arg]) + arg_pt = argvars[0].vval.v_partial; + s = arg_pt->pt_name; + } else { + // function('MyFunc', [arg], dict) + s = get_tv_string(&argvars[0]); + use_string = true; + } - s = get_tv_string(&argvars[0]); - if (s == NULL || *s == NUL || ascii_isdigit(*s)) + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) { EMSG2(_(e_invarg2), s); - /* Don't check an autoload name for existence here. */ - else if (vim_strchr(s, AUTOLOAD_CHAR) == NULL && !function_exists(s)) + } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL + && !function_exists(s)) { + // Don't check an autoload name for existence here. EMSG2(_("E700: Unknown function: %s"), s); - else { + } else { + int dict_idx = 0; + int arg_idx = 0; + list_T *list = NULL; if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0) { char sid_buf[25]; int off = *s == 's' ? 2 : 5; - /* Expand s: and <SID> into <SNR>nr_, so that the function can - * also be called from another script. Using trans_function_name() - * would also work, but some plugins depend on the name being - * printable text. */ - sprintf(sid_buf, "<SNR>%" PRId64 "_", (int64_t)current_SID); - rettv->vval.v_string = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - STRCPY(rettv->vval.v_string, sid_buf); - STRCAT(rettv->vval.v_string, s + off); - } else - rettv->vval.v_string = vim_strsave(s); - rettv->v_type = VAR_FUNC; + // Expand s: and <SID> into <SNR>nr_, so that the function can + // also be called from another script. Using trans_function_name() + // would also work, but some plugins depend on the name being + // printable text. + snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", + (int64_t)current_SID); + name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); + if (name != NULL) { + STRCPY(name, sid_buf); + STRCAT(name, s + off); + } + } else { + name = vim_strsave(s); + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + // function(name, [args], dict) + arg_idx = 1; + dict_idx = 2; + } else if (argvars[1].v_type == VAR_DICT) { + // function(name, dict) + dict_idx = 1; + } else { + // function(name, [args]) + arg_idx = 1; + } + if (dict_idx > 0) { + if (argvars[dict_idx].v_type != VAR_DICT) { + EMSG(_("E922: expected a dict")); + xfree(name); + return; + } + if (argvars[dict_idx].vval.v_dict == NULL) { + dict_idx = 0; + } + } + if (arg_idx > 0) { + if (argvars[arg_idx].v_type != VAR_LIST) { + EMSG(_("E923: Second argument of function() must be " + "a list or a dict")); + xfree(name); + return; + } + list = argvars[arg_idx].vval.v_list; + if (list == NULL || list->lv_len == 0) { + arg_idx = 0; + } + } + } + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + + // result is a VAR_PARTIAL + if (pt != NULL) { + if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { + listitem_T *li; + int i = 0; + int arg_len = 0; + int lv_len = 0; + + if (arg_pt != NULL) { + arg_len = arg_pt->pt_argc; + } + if (list != NULL) { + lv_len = list->lv_len; + } + pt->pt_argc = arg_len + lv_len; + pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * pt->pt_argc); + if (pt->pt_argv == NULL) { + xfree(pt); + xfree(name); + return; + } else { + for (i = 0; i < arg_len; i++) { + copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + for (li = list->lv_first; li != NULL; li = li->li_next) { + copy_tv(&li->li_tv, &pt->pt_argv[i++]); + } + } + } + } + + // For "function(dict.func, [], dict)" and "func" is a partial + // use "dict". That is backwards compatible. + if (dict_idx > 0) { + // The dict is bound explicitly, pt_auto is false + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (pt->pt_dict->dv_refcount)++; + } else if (arg_pt != NULL) { + // If the dict was bound automatically the result is also + // bound automatically. + pt->pt_dict = arg_pt->pt_dict; + pt->pt_auto = arg_pt->pt_auto; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; + } + } + + pt->pt_refcount = 1; + pt->pt_name = name; + func_ref(pt->pt_name); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } else { + // result is a VAR_FUNC + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); + } } } -/* - * "garbagecollect()" function - */ +/// "garbagecollect()" function static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) { /* This is postponed until we are back at the toplevel, because we may be @@ -9406,8 +9777,50 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (di != NULL) tv = &di->di_tv; } - } else + } else if (argvars[0].v_type == VAR_PARTIAL + || argvars[0].v_type == VAR_FUNC) { + partial_T *pt; + partial_T fref_pt; + + if (argvars[0].v_type == VAR_PARTIAL) { + pt = argvars[0].vval.v_partial; + } else { + memset(&fref_pt, 0, sizeof(fref_pt)); + fref_pt.pt_name = argvars[0].vval.v_string; + pt = &fref_pt; + } + + if (pt != NULL) { + char_u *what = get_tv_string(&argvars[1]); + + if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0) { + rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); + if (pt->pt_name != NULL) { + rettv->vval.v_string = vim_strsave(pt->pt_name); + } + } else if (STRCMP(what, "dict") == 0) { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = pt->pt_dict; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; + } + } else if (STRCMP(what, "args") == 0) { + rettv->v_type = VAR_LIST; + if (rettv_list_alloc(rettv) != NULL) { + int i; + + for (i = 0; i < pt->pt_argc; i++) { + list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); + } + } + } else { + EMSG2(_(e_invarg2), what); + } + return; + } + } else { EMSG2(_(e_listdictarg), "get()"); + } if (tv == NULL) { if (argvars[2].v_type != VAR_UNKNOWN) @@ -11660,7 +12073,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_T *job_opts = NULL; bool detach = false, rpc = false, pty = false; - ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL; + Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE, + on_exit = CALLBACK_NONE; char *cwd = NULL; if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; @@ -11692,7 +12106,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - job_opts, pty, rpc, detach, cwd); + pty, rpc, detach, cwd); Process *proc = (Process *)&data->proc; if (pty) { @@ -11710,10 +12124,10 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - if (!rpc && !on_stdout) { + if (!rpc && on_stdout.type == kCallbackNone) { proc->out = NULL; } - if (!on_stderr) { + if (on_stderr.type == kCallbackNone) { proc->err = NULL; } common_job_start(data, rettv); @@ -11971,6 +12385,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) case VAR_SPECIAL: case VAR_FLOAT: case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E701: Invalid type for len()")); break; } @@ -13920,8 +14335,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // The last item of argv must be NULL argv[i] = NULL; - TerminalJobData *data = common_job_init(argv, NULL, NULL, NULL, - NULL, false, true, false, NULL); + TerminalJobData *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE, + CALLBACK_NONE, false, true, false, + NULL); common_job_start(data, rettv); } @@ -15035,6 +15451,7 @@ typedef struct { bool item_compare_numbers; bool item_compare_float; char_u *item_compare_func; + partial_T *item_compare_partial; dict_T *item_compare_selfdict; int item_compare_func_err; } sortinfo_T; @@ -15141,6 +15558,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) typval_T rettv; typval_T argv[3]; int dummy; + char_u *func_name; + partial_T *partial = sortinfo->item_compare_partial; // shortcut after failure in previous call; compare all items equal if (sortinfo->item_compare_func_err) { @@ -15150,16 +15569,21 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) si1 = (sortItem_T *)s1; si2 = (sortItem_T *)s2; + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = partial->pt_name; + } // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. copy_tv(&si1->item->li_tv, &argv[0]); copy_tv(&si2->item->li_tv, &argv[1]); rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this - res = call_func(sortinfo->item_compare_func, - (int)STRLEN(sortinfo->item_compare_func), + res = call_func(func_name, + (int)STRLEN(func_name), &rettv, 2, argv, 0L, 0L, &dummy, true, - sortinfo->item_compare_selfdict); + partial, sortinfo->item_compare_selfdict); clear_tv(&argv[0]); clear_tv(&argv[1]); @@ -15235,12 +15659,15 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_numbers = false; info.item_compare_float = false; info.item_compare_func = NULL; + info.item_compare_partial = NULL; info.item_compare_selfdict = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { /* optional second argument: {func} */ if (argvars[1].v_type == VAR_FUNC) { info.item_compare_func = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; } else { int error = FALSE; @@ -15300,14 +15727,16 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_func_err = false; // Test the compare function. - if (info.item_compare_func != NULL + if ((info.item_compare_func != NULL + || info.item_compare_partial != NULL) && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) == ITEM_COMPARE_FAIL) { EMSG(_("E702: Sort compare function failed")); } else { // Sort the array with item pointers. qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL ? + (info.item_compare_func == NULL + && info.item_compare_partial == NULL ? item_compare_not_keeping_zero : item_compare2_not_keeping_zero)); @@ -15328,7 +15757,8 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; - if (info.item_compare_func != NULL) { + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { item_compare_func_ptr = item_compare2_keeping_zero; } else { item_compare_func_ptr = item_compare_keeping_zero; @@ -16485,7 +16915,8 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL; + Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE, + on_exit = CALLBACK_NONE; dict_T *job_opts = NULL; char *cwd = "."; if (argvars[1].v_type == VAR_DICT) { @@ -16509,7 +16940,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - job_opts, true, false, false, cwd); + true, false, false, cwd); data->proc.pty.width = curwin->w_width; data->proc.pty.height = curwin->w_height; data->proc.pty.term_name = xstrdup("xterm-256color"); @@ -16555,6 +16986,125 @@ static void f_test(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* Used for unit testing. Change the code below to your liking. */ } +static bool callback_from_typval(Callback *callback, typval_T *arg) +{ + if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { + callback->data.partial = arg->vval.v_partial; + callback->data.partial->pt_refcount++; + callback->type = kCallbackPartial; + } else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) { + char_u *name = arg->vval.v_string; + func_ref(name); + callback->data.funcref = vim_strsave(name); + callback->type = kCallbackFuncref; + } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) { + callback->type = kCallbackNone; + } else { + EMSG(_("E921: Invalid callback argument")); + return false; + } + return true; +} + + +/// Unref/free callback +static void callback_free(Callback *callback) +{ + switch (callback->type) { + case kCallbackFuncref: + func_unref(callback->data.funcref); + xfree(callback->data.funcref); + break; + + case kCallbackPartial: + partial_unref(callback->data.partial); + break; + + case kCallbackNone: + break; + + default: + abort(); + } + callback->type = kCallbackNone; +} + +static bool callback_equal(Callback *cb1, Callback *cb2) +{ + if (cb1->type != cb2->type) { + return false; + } + switch (cb1->type) { + case kCallbackFuncref: + return STRCMP(cb1->data.funcref, cb2->data.funcref) == 0; + + case kCallbackPartial: + // FIXME: this is inconsistent with tv_equal but is needed for precision + // maybe change dictwatcheradd to return a watcher id instead? + return cb1->data.partial == cb2->data.partial; + + case kCallbackNone: + return true; + + default: + abort(); + } +} + +static bool callback_call(Callback *callback, int argcount_in, + typval_T *argvars_in, typval_T *rettv) +{ + partial_T *partial; + char_u *name; + switch (callback->type) { + case kCallbackFuncref: + name = callback->data.funcref; + partial = NULL; + break; + + case kCallbackPartial: + partial = callback->data.partial; + name = partial->pt_name; + break; + + case kCallbackNone: + return false; + break; + + default: + abort(); + } + + int dummy; + return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, + true, partial, NULL); +} + +static bool set_ref_in_callback(Callback *callback, int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + typval_T tv; + switch (callback->type) { + case kCallbackFuncref: + case kCallbackNone: + break; + + case kCallbackPartial: + tv.v_type = VAR_PARTIAL; + tv.vval.v_partial = callback->data.partial; + return set_ref_in_item(&tv, copyID, ht_stack, list_stack); + break; + + + default: + abort(); + } + return false; +} + + /// "timer_start(timeout, callback, opts)" function static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -16579,16 +17129,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - if (argvars[1].v_type != VAR_FUNC && argvars[1].v_type != VAR_STRING) { - EMSG2(e_invarg2, "funcref"); - return; - } - ufunc_T *func = find_ufunc(argvars[1].vval.v_string); - if (!func) { - // Invalid function name. Error already reported by `find_ufunc`. + Callback callback; + if (!callback_from_typval(&callback, &argvars[1])) { return; } - func->uf_refcount++; timer = xmalloc(sizeof *timer); timer->refcount = 1; @@ -16596,7 +17140,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) timer->repeat_count = repeat; timer->timeout = timeout; timer->timer_id = last_timer_id++; - timer->callback = func; + timer->callback = callback; time_watcher_init(&main_loop, &timer->tw, timer); timer->tw.events = multiqueue_new_child(main_loop.events); @@ -16640,15 +17184,14 @@ static void timer_due_cb(TimeWatcher *tw, void *data) timer_stop(timer); } - typval_T argv[1]; + typval_T argv[2]; init_tv(argv); argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = timer->timer_id; typval_T rettv; init_tv(&rettv); - call_user_func(timer->callback, ARRAY_SIZE(argv), argv, &rettv, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL); + callback_call(&timer->callback, 1, argv, &rettv); clear_tv(&rettv); if (!timer->stopped && timer->timeout == 0) { @@ -16677,7 +17220,7 @@ static void timer_close_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; multiqueue_free(timer->tw.events); - user_func_unref(timer->callback); + callback_free(&timer->callback); pmap_del(uint64_t)(timers, timer->timer_id); timer_decref(timer); } @@ -16847,6 +17390,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) switch (argvars[0].v_type) { case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; case VAR_STRING: n = VAR_TYPE_STRING; break; + case VAR_PARTIAL: case VAR_FUNC: n = VAR_TYPE_FUNC; break; case VAR_LIST: n = VAR_TYPE_LIST; break; case VAR_DICT: n = VAR_TYPE_DICT; break; @@ -18056,21 +18600,29 @@ handle_subscript ( while (ret == OK && (**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT) - || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC))) + || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL))) && !ascii_iswhite(*(*arg - 1))) { if (**arg == '(') { - /* need to copy the funcref so that we can clear rettv */ + partial_T *pt = NULL; + // need to copy the funcref so that we can clear rettv if (evaluate) { functv = *rettv; rettv->v_type = VAR_UNKNOWN; - /* Invoke the function. Recursive! */ - s = functv.vval.v_string; - } else + // Invoke the function. Recursive! + if (functv.v_type == VAR_PARTIAL) { + pt = functv.vval.v_partial; + s = pt->pt_name; + } else { + s = functv.vval.v_string; + } + } else { s = (char_u *)""; + } ret = get_func_tv(s, (int)STRLEN(s), rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, selfdict); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, pt, selfdict); /* Clear the funcref afterwards, so that deleting it while * evaluating the arguments is possible (see test55). */ @@ -18101,10 +18653,80 @@ handle_subscript ( } } } + + // Turn "dict.Func" into a partial for "Func" bound to "dict". + if (selfdict != NULL + && (rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL)) { + set_selfdict(rettv, selfdict); + } + dict_unref(selfdict); return ret; } +static void set_selfdict(typval_T *rettv, dict_T *selfdict) { + // Don't do this when "dict.Func" is already a partial that was bound + // explicitly (pt_auto is false). + if (rettv->v_type == VAR_PARTIAL && !rettv->vval.v_partial->pt_auto + && rettv->vval.v_partial->pt_dict != NULL) { + return; + } + char_u *fname = rettv->v_type == VAR_FUNC ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; + char_u *tofree = NULL; + ufunc_T *fp; + char_u fname_buf[FLEN_FIXED + 1]; + int error; + + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + + fp = find_func(fname); + xfree(tofree); + + // Turn "dict.Func" into a partial for "Func" with "dict". + if (fp != NULL && (fp->uf_flags & FC_DICT)) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + + if (pt != NULL) { + pt->pt_refcount = 1; + pt->pt_dict = selfdict; + (selfdict->dv_refcount)++; + pt->pt_auto = true; + if (rettv->v_type == VAR_FUNC) { + // Just a function: Take over the function name and use selfdict. + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // Partial: copy the function name, use selfdict and copy + // args. Can't take over name or args, the partial might + // be referenced elsewhere. + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + if (ret_pt->pt_argc > 0) { + size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; + pt->pt_argv = (typval_T *)xmalloc(arg_size); + if (pt->pt_argv == NULL) { + // out of memory: drop the arguments + pt->pt_argc = 0; + } else { + pt->pt_argc = ret_pt->pt_argc; + for (i = 0; i < pt->pt_argc; i++) { + copy_tv(&ret_pt->pt_argv[i], &pt->pt_argv[i]); + } + } + } + partial_unref(ret_pt); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } + } +} + /* * Free the memory for a variable type-value. */ @@ -18118,6 +18740,9 @@ void free_tv(typval_T *varp) case VAR_STRING: xfree(varp->vval.v_string); break; + case VAR_PARTIAL: + partial_unref(varp->vval.v_partial); + break; case VAR_LIST: list_unref(varp->vval.v_list); break; @@ -18182,6 +18807,13 @@ void free_tv(typval_T *varp) tv->v_lock = VAR_UNLOCKED; \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ + do { \ + partial_unref(pt); \ + pt = NULL; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ do { \ list_unref(tv->vval.v_list); \ @@ -18258,6 +18890,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_LIST_START @@ -18315,6 +18948,7 @@ long get_tv_number_chk(typval_T *varp, int *denote) EMSG(_("E805: Using a Float as a Number")); break; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E703: Using a Funcref as a Number")); break; case VAR_STRING: @@ -18362,6 +18996,7 @@ static float_T get_tv_float(typval_T *varp) return varp->vval.v_float; break; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E891: Using a Funcref as a Float")); break; case VAR_STRING: @@ -18458,6 +19093,7 @@ static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) sprintf((char *)buf, "%" PRId64, (int64_t)varp->vval.v_number); return buf; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E729: using Funcref as a String")); break; case VAR_LIST: @@ -18503,11 +19139,10 @@ static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload) return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); } -/* - * Find variable "varname" in hashtab "ht" with name "htname". - * Returns NULL if not found. - */ -static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload) +/// Find variable "varname" in hashtab "ht" with name "htname". +/// Returns NULL if not found. +static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, + char_u *varname, bool no_autoload) { hashitem_T *hi; @@ -18793,11 +19428,11 @@ list_one_var_a ( msg_puts(name); msg_putchar(' '); msg_advance(22); - if (type == VAR_NUMBER) + if (type == VAR_NUMBER) { msg_putchar('#'); - else if (type == VAR_FUNC) + } else if (type == VAR_FUNC || type == VAR_PARTIAL) { msg_putchar('*'); - else if (type == VAR_LIST) { + } else if (type == VAR_LIST) { msg_putchar('['); if (*string == '[') ++string; @@ -18810,8 +19445,9 @@ list_one_var_a ( msg_outtrans(string); - if (type == VAR_FUNC) + if (type == VAR_FUNC || type == VAR_PARTIAL) { msg_puts((char_u *)"()"); + } if (*first) { msg_clr_eos(); *first = FALSE; @@ -18849,8 +19485,10 @@ set_var ( } v = find_var_in_ht(ht, 0, varname, TRUE); - if (tv->v_type == VAR_FUNC && var_check_func_name(name, v == NULL)) + if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) + && var_check_func_name(name, v == NULL)) { return; + } if (v != NULL) { // existing variable, need to clear the value @@ -19050,6 +19688,14 @@ void copy_tv(typval_T *from, typval_T *to) } } break; + case VAR_PARTIAL: + if (from->vval.v_partial == NULL) { + to->vval.v_partial = NULL; + } else { + to->vval.v_partial = from->vval.v_partial; + (to->vval.v_partial->pt_refcount)++; + } + break; case VAR_LIST: if (from->vval.v_list != NULL) { to->vval.v_list->lv_refcount++; @@ -19102,6 +19748,7 @@ int var_item_copy(const vimconv_T *const conv, case VAR_NUMBER: case VAR_FLOAT: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: copy_tv(from, to); break; @@ -19461,7 +20108,7 @@ void ex_function(exarg_T *eap) // s:func script-local function name // g:func global function name, same as "func" p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi); + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { /* @@ -19547,7 +20194,8 @@ void ex_function(exarg_T *eap) else arg = fudi.fd_newkey; if (arg != NULL && (fudi.fd_di == NULL - || fudi.fd_di->di_tv.v_type != VAR_FUNC)) { + || (fudi.fd_di->di_tv.v_type != VAR_FUNC + && fudi.fd_di->di_tv.v_type != VAR_PARTIAL))) { int j = (*arg == K_SPECIAL) ? 3 : 0; while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) : eval_isnamec(arg[j]))) @@ -19728,7 +20376,7 @@ void ex_function(exarg_T *eap) p = skipwhite(p + 1); } p += eval_fname_script(p); - xfree(trans_function_name(&p, TRUE, 0, NULL)); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { nesting++; indent += 2; @@ -19946,7 +20594,8 @@ trans_function_name ( char_u **pp, int skip, /* only find the end, don't evaluate */ int flags, - funcdict_T *fdp /* return: info about dictionary used */ + funcdict_T *fdp, // return: info about dictionary used + partial_T **partial // return: partial of a FuncRef ) { char_u *name = NULL; @@ -20008,6 +20657,13 @@ trans_function_name ( if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { name = vim_strsave(lv.ll_tv->vval.v_string); *pp = end; + } else if (lv.ll_tv->v_type == VAR_PARTIAL + && lv.ll_tv->vval.v_partial != NULL) { + name = vim_strsave(lv.ll_tv->vval.v_partial->pt_name); + *pp = end; + if (partial != NULL) { + *partial = lv.ll_tv->vval.v_partial; + } } else { if (!skip && !(flags & TFN_QUIET) && (fdp == NULL || lv.ll_dict == NULL @@ -20030,14 +20686,17 @@ trans_function_name ( /* Check if the name is a Funcref. If so, use the value. */ if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, flags & TFN_NO_AUTOLOAD); - if (name == lv.ll_exp_name) + name = deref_func_name(lv.ll_exp_name, &len, partial, + flags & TFN_NO_AUTOLOAD); + if (name == lv.ll_exp_name) { name = NULL; + } } else { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, flags & TFN_NO_AUTOLOAD); - if (name == *pp) + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + if (name == *pp) { name = NULL; + } } if (name != NULL) { name = vim_strsave(name); @@ -20235,8 +20894,8 @@ static int function_exists(char_u *name) char_u *p; int n = FALSE; - p = trans_function_name(&nm, FALSE, TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD, - NULL); + p = trans_function_name(&nm, false, TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD, + NULL, NULL); nm = skipwhite(nm); /* Only accept "funcname", "funcname ", "funcname (..." and @@ -20556,7 +21215,7 @@ void ex_delfunction(exarg_T *eap) funcdict_T fudi; p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi); + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) @@ -21915,10 +22574,9 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) } static inline TerminalJobData *common_job_init(char **argv, - ufunc_T *on_stdout, - ufunc_T *on_stderr, - ufunc_T *on_exit, - dict_T *self, + Callback on_stdout, + Callback on_stderr, + Callback on_exit, bool pty, bool rpc, bool detach, @@ -21929,7 +22587,6 @@ static inline TerminalJobData *common_job_init(char **argv, data->on_stdout = on_stdout; data->on_stderr = on_stderr; data->on_exit = on_exit; - data->self = self; data->events = multiqueue_new_child(main_loop.events); data->rpc = rpc; if (pty) { @@ -21954,8 +22611,8 @@ static inline TerminalJobData *common_job_init(char **argv, /// common code for getting job callbacks for jobstart, termopen and rpcstart /// /// @return true/false on success/failure. -static inline bool common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout, - ufunc_T **on_stderr, ufunc_T **on_exit) +static inline bool common_job_callbacks(dict_T *vopts, Callback *on_stdout, + Callback *on_stderr, Callback *on_exit) { if (get_dict_callback(vopts, "on_stdout", on_stdout) && get_dict_callback(vopts, "on_stderr", on_stderr) @@ -21963,15 +22620,10 @@ static inline bool common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout, vopts->dv_refcount++; return true; } - if (*on_stdout) { - user_func_unref(*on_stdout); - } - if (*on_stderr) { - user_func_unref(*on_stderr); - } - if (*on_exit) { - user_func_unref(*on_exit); - } + + callback_free(on_stdout); + callback_free(on_stderr); + callback_free(on_exit); return false; } @@ -22026,19 +22678,10 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) static inline void free_term_job_data_event(void **argv) { TerminalJobData *data = argv[0]; - if (data->on_stdout) { - user_func_unref(data->on_stdout); - } - if (data->on_stderr) { - user_func_unref(data->on_stderr); - } - if (data->on_exit) { - user_func_unref(data->on_exit); - } + callback_free(&data->on_stdout); + callback_free(&data->on_stderr); + callback_free(&data->on_exit); - if (data->self) { - dict_unref(data->self); - } multiqueue_free(data->events); pmap_del(uint64_t)(jobs, data->id); xfree(data); @@ -22052,8 +22695,9 @@ static inline void free_term_job_data(TerminalJobData *data) } // vimscript job callbacks must be executed on Nvim main loop -static inline void process_job_event(TerminalJobData *data, ufunc_T *callback, - const char *type, char *buf, size_t count, int status) +static inline void process_job_event(TerminalJobData *data, Callback *callback, + const char *type, char *buf, size_t count, + int status) { JobEvent event_data; event_data.received = NULL; @@ -22093,18 +22737,19 @@ static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, void *job, bool eof) { TerminalJobData *data = job; - on_job_output(stream, job, buf, count, eof, data->on_stdout, "stdout"); + on_job_output(stream, job, buf, count, eof, &data->on_stdout, "stdout"); } static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, void *job, bool eof) { TerminalJobData *data = job; - on_job_output(stream, job, buf, count, eof, data->on_stderr, "stderr"); + on_job_output(stream, job, buf, count, eof, &data->on_stderr, "stderr"); } static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, - size_t count, bool eof, ufunc_T *callback, const char *type) + size_t count, bool eof, Callback *callback, + const char *type) { if (eof) { return; @@ -22121,7 +22766,7 @@ static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, terminal_receive(data->term, ptr, count); } - if (callback) { + if (callback->type != kCallbackNone) { process_job_event(data, callback, type, ptr, count, 0); } @@ -22145,7 +22790,7 @@ static void eval_job_process_exit_cb(Process *proc, int status, void *d) *data->status_ptr = status; } - process_job_event(data, data->on_exit, "exit", NULL, 0, status); + process_job_event(data, &data->on_exit, "exit", NULL, 0, status); term_job_data_decref(data); } @@ -22204,38 +22849,30 @@ static void on_job_event(JobEvent *ev) return; } - typval_T argv[3]; - int argc = ev->callback->uf_args.ga_len; - - if (argc > 0) { - argv[0].v_type = VAR_NUMBER; - argv[0].v_lock = 0; - argv[0].vval.v_number = ev->data->id; - } + typval_T argv[4]; - if (argc > 1) { - if (ev->received) { - argv[1].v_type = VAR_LIST; - argv[1].v_lock = 0; - argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; - } else { - argv[1].v_type = VAR_NUMBER; - argv[1].v_lock = 0; - argv[1].vval.v_number = ev->status; - } + argv[0].v_type = VAR_NUMBER; + argv[0].v_lock = 0; + argv[0].vval.v_number = ev->data->id; + + if (ev->received) { + argv[1].v_type = VAR_LIST; + argv[1].v_lock = 0; + argv[1].vval.v_list = ev->received; + argv[1].vval.v_list->lv_refcount++; + } else { + argv[1].v_type = VAR_NUMBER; + argv[1].v_lock = 0; + argv[1].vval.v_number = ev->status; } - if (argc > 2) { - argv[2].v_type = VAR_STRING; - argv[2].v_lock = 0; - argv[2].vval.v_string = (uint8_t *)ev->type; - } + argv[2].v_type = VAR_STRING; + argv[2].v_lock = 0; + argv[2].vval.v_string = (uint8_t *)ev->type; typval_T rettv; init_tv(&rettv); - call_user_func(ev->callback, argc, argv, &rettv, curwin->w_cursor.lnum, - curwin->w_cursor.lnum, ev->data->self); + callback_call(ev->callback, 3, argv, &rettv); clear_tv(&rettv); } @@ -22301,6 +22938,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) curwin->w_cursor.lnum, &dummy, true, + NULL, NULL); list_unref(arguments); @@ -22358,7 +22996,7 @@ static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv, typval_T *oldtv) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) { - typval_T argv[3]; + typval_T argv[4]; for (size_t i = 0; i < ARRAY_SIZE(argv); i++) { init_tv(argv + i); } @@ -22391,8 +23029,7 @@ static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv, if (!watcher->busy && dictwatcher_matches(watcher, key)) { init_tv(&rettv); watcher->busy = true; - call_user_func(watcher->callback, ARRAY_SIZE(argv), argv, &rettv, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL); + callback_call(&watcher->callback, 3, argv, &rettv); watcher->busy = false; clear_tv(&rettv); } @@ -22423,7 +23060,7 @@ static bool dictwatcher_matches(DictWatcher *watcher, const char *key) static void dictwatcher_free(DictWatcher *watcher) FUNC_ATTR_NONNULL_ALL { - user_func_unref(watcher->callback); + callback_free(&watcher->callback); xfree(watcher->key_pattern); xfree(watcher); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 61a5438d8c..a0671cac1f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -103,7 +103,7 @@ return { foldtext={}, foldtextresult={args=1}, foreground={}, - ['function']={args=1}, + ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, get={args={2, 3}}, getbufline={args={2, 3}}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 869eae74a3..61d7a38007 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -315,6 +315,60 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, ga_append(gap, ')'); \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ + do { \ + int i; \ + ga_concat(gap, "function("); \ + if (&pt->pt_name != NULL) { \ + size_t len; \ + char_u *p; \ + len = 3; \ + len += STRLEN(pt->pt_name); \ + for (p = pt->pt_name; *p != NUL; mb_ptr_adv(p)) { \ + if (*p == '\'') { \ + len++; \ + } \ + } \ + char_u *r, *s; \ + s = r = xmalloc(len); \ + if (r != NULL) { \ + *r++ = '\''; \ + for (p = pt->pt_name; *p != NUL; ) { \ + if (*p == '\'') { \ + *r++ = '\''; \ + } \ + MB_COPY_CHAR(p, r); \ + } \ + *r++ = '\''; \ + *r++ = NUL; \ + } \ + ga_concat(gap, s); \ + xfree(s); \ + } \ + if (pt->pt_argc > 0) { \ + ga_concat(gap, ", ["); \ + for (i = 0; i < pt->pt_argc; i++) { \ + if (i > 0) { \ + ga_concat(gap, ", "); \ + } \ + char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \ + ga_concat(gap, tofree); \ + xfree(tofree); \ + } \ + ga_append(gap, ']'); \ + } \ + if (pt->pt_dict != NULL) { \ + typval_T dtv; \ + ga_concat(gap, ", "); \ + dtv.v_type = VAR_DICT; \ + dtv.vval.v_dict = pt->pt_dict; \ + char *tofree = encode_tv2string(&dtv, NULL); \ + ga_concat(gap, tofree); \ + xfree(tofree); \ + } \ + ga_append(gap, ')'); \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ ga_concat(gap, "[]") @@ -661,6 +715,12 @@ static inline int convert_to_json_string(garray_T *const gap, "attempt to dump function reference"), \ mpstack, objname) +#undef TYPVAL_ENCODE_CONV_PARTIAL +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ + return conv_error(_("E474: Error while dumping %s, %s: " \ + "attempt to dump partial"), \ + mpstack, objname) + /// Check whether given key can be used in json_encode() /// /// @param[in] tv Key to check. @@ -718,6 +778,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT @@ -846,6 +907,11 @@ char *encode_tv2json(typval_T *tv, size_t *len) "attempt to dump function reference"), \ mpstack, objname) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + return conv_error(_("E5004: Error while dumping %s, %s: " \ + "attempt to dump partial"), \ + mpstack, objname) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ msgpack_pack_array(packer, 0) @@ -902,6 +968,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 868877bd47..b79158b30c 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -69,6 +69,11 @@ /// /// @param fun Function name. +/// @def TYPVAL_ENCODE_CONV_PARTIAL +/// @brief Macros used to convert a partial +/// +/// @param pt Partial name. + /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list /// @@ -248,6 +253,10 @@ static int name##_convert_one_value(firstargtype firstargname, \ TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \ break; \ } \ + case VAR_PARTIAL: { \ + TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \ + break; \ + } \ case VAR_LIST: { \ if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \ TYPVAL_ENCODE_CONV_EMPTY_LIST(); \ diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 91d544074f..f478d19ca1 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -15,6 +15,7 @@ typedef double float_T; typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; +typedef struct partial_S partial_T; /// Special variable values typedef enum { @@ -35,12 +36,13 @@ typedef enum { VAR_UNKNOWN = 0, ///< Unknown (unspecified) value. VAR_NUMBER, ///< Number, .v_number is used. VAR_STRING, ///< String, .v_string is used. - VAR_FUNC, ///< Function referene, .v_string is used for function name. + VAR_FUNC, ///< Function reference, .v_string is used as function name. VAR_LIST, ///< List, .v_list is used. VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. VAR_SPECIAL, ///< Special value (true, false, null), .v_special ///< is used. + VAR_PARTIAL, ///< Partial, .v_partial is used. } VarType; /// Structure that holds an internal variable value @@ -54,6 +56,7 @@ typedef struct { char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. list_T *v_list; ///< List for VAR_LIST, can be NULL. dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. + partial_T *v_partial; ///< Closure: function with args. } vval; ///< Actual value. } typval_T; @@ -144,6 +147,16 @@ struct dictvar_S { QUEUE watchers; ///< Dictionary key watchers set by user code. }; +struct partial_S { + int pt_refcount; ///< Reference count. + char_u *pt_name; ///< Function name. + bool pt_auto; ///< when true the partial was created for using + ///< dict.member in handle_subscript(). + int pt_argc; ///< Number of arguments. + typval_T *pt_argv; ///< Arguments in allocated array. + dict_T *pt_dict; ///< Dict for "self". +}; + // structure used for explicit stack while garbage collecting hash tables typedef struct ht_stack_S { hashtab_T *ht; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index acdda9b657..f7edeed933 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1235,6 +1235,8 @@ EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */ EXTERN int ignored; EXTERN char *ignoredp; +EXTERN bool in_free_unref_items INIT(= false); + // If a msgpack-rpc channel should be started over stdin/stdout EXTERN bool embedded_mode INIT(= false); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 312777d3b9..88ebdef9b3 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2466,7 +2466,7 @@ do_mouse ( (int) strlen(tab_page_click_defs[mouse_col].func), &rettv, ARRAY_SIZE(argv), argv, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL); + &doesrange, true, NULL, NULL); clear_tv(&rettv); break; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 1df1952f53..597f737410 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -790,7 +790,7 @@ do_tag ( vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); set_errorlist(curwin, list, ' ', IObuff); - list_free(list, TRUE); + list_free(list); xfree(fname); xfree(cmd); diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c79d9380d4..5da122c45d 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -14,6 +14,7 @@ source test_matchadd_conceal_utf8.vim source test_menu.vim source test_messages.vim source test_options.vim +source test_partial.vim source test_popup.vim source test_regexp_utf8.vim source test_statusline.vim diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 66a10b05e1..cc5e9587ed 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -15,6 +15,28 @@ func Test_version() call assert_false(has('patch-9.9.1')) endfunc +func Test_equal() + let base = {} + func base.method() + return 1 + endfunc + func base.other() dict + return 1 + endfunc + let instance = copy(base) + call assert_true(base.method == instance.method) + call assert_true([base.method] == [instance.method]) + call assert_true(base.other == instance.other) + call assert_true([base.other] == [instance.other]) + + call assert_false(base.method == base.other) + call assert_false([base.method] == [base.other]) + call assert_false(base.method == instance.other) + call assert_false([base.method] == [instance.other]) + + call assert_fails('echo base.method > instance.method') +endfunc + func Test_strgetchar() call assert_equal(char2nr('a'), strgetchar('axb', 0)) call assert_equal(char2nr('x'), strgetchar('axb', 1)) diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim new file mode 100644 index 0000000000..3a6e162453 --- /dev/null +++ b/src/nvim/testdir/test_partial.vim @@ -0,0 +1,330 @@ +" Test binding arguments to a Funcref. + +func MyFunc(arg1, arg2, arg3) + return a:arg1 . '/' . a:arg2 . '/' . a:arg3 +endfunc + +func MySort(up, one, two) + if a:one == a:two + return 0 + endif + if a:up + return a:one > a:two ? 1 : -1 + endif + return a:one < a:two ? 1 : -1 +endfunc + +func Test_partial_args() + let Cb = function('MyFunc', ["foo", "bar"]) + + call Cb("zzz") + call assert_equal("foo/bar/xxx", Cb("xxx")) + call assert_equal("foo/bar/yyy", call(Cb, ["yyy"])) + let Cb2 = function(Cb) + call assert_equal("foo/bar/zzz", Cb2("zzz")) + let Cb3 = function(Cb, ["www"]) + call assert_equal("foo/bar/www", Cb3()) + + let Cb = function('MyFunc', []) + call assert_equal("a/b/c", Cb("a", "b", "c")) + let Cb2 = function(Cb, []) + call assert_equal("a/b/d", Cb2("a", "b", "d")) + let Cb3 = function(Cb, ["a", "b"]) + call assert_equal("a/b/e", Cb3("e")) + + let Sort = function('MySort', [1]) + call assert_equal([1, 2, 3], sort([3, 1, 2], Sort)) + let Sort = function('MySort', [0]) + call assert_equal([3, 2, 1], sort([3, 1, 2], Sort)) +endfunc + +func MyDictFunc(arg1, arg2) dict + return self.name . '/' . a:arg1 . '/' . a:arg2 +endfunc + +func Test_partial_dict() + let dict = {'name': 'hello'} + let Cb = function('MyDictFunc', ["foo", "bar"], dict) + call assert_equal("hello/foo/bar", Cb()) + call assert_fails('Cb("xxx")', 'E492:') + + let Cb = function('MyDictFunc', [], dict) + call assert_equal("hello/ttt/xxx", Cb("ttt", "xxx")) + call assert_fails('Cb("yyy")', 'E492:') + + let Cb = function('MyDictFunc', ["foo"], dict) + call assert_equal("hello/foo/xxx", Cb("xxx")) + call assert_fails('Cb()', 'E492:') + let Cb = function('MyDictFunc', dict) + call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy")) + call assert_fails('Cb("fff")', 'E492:') + + let dict = {"tr": function('tr', ['hello', 'h', 'H'])} + call assert_equal("Hello", dict.tr()) +endfunc + +func Test_partial_implicit() + let dict = {'name': 'foo'} + func dict.MyFunc(arg) dict + return self.name . '/' . a:arg + endfunc + + call assert_equal('foo/bar', dict.MyFunc('bar')) + + call assert_fails('let func = dict.MyFunc', 'E704:') + let Func = dict.MyFunc + call assert_equal('foo/aaa', Func('aaa')) + + let Func = function(dict.MyFunc, ['bbb']) + call assert_equal('foo/bbb', Func()) +endfunc + +fun InnerCall(funcref) + return a:funcref +endfu + +fun OuterCall() + let opt = { 'func' : function('sin') } + call InnerCall(opt.func) +endfu + +func Test_function_in_dict() + call OuterCall() +endfunc + +function! s:cache_clear() dict + return self.name +endfunction + +func Test_script_function_in_dict() + let s:obj = {'name': 'foo'} + let s:obj2 = {'name': 'bar'} + + let s:obj['clear'] = function('s:cache_clear') + + call assert_equal('foo', s:obj.clear()) + let F = s:obj.clear + call assert_equal('foo', F()) + call assert_equal('foo', call(s:obj.clear, [], s:obj)) + call assert_equal('bar', call(s:obj.clear, [], s:obj2)) + + let s:obj2['clear'] = function('s:cache_clear') + call assert_equal('bar', s:obj2.clear()) + let B = s:obj2.clear + call assert_equal('bar', B()) +endfunc + +function! s:cache_arg(arg) dict + let s:result = self.name . '/' . a:arg + return s:result +endfunction + +func Test_script_function_in_dict_arg() + let s:obj = {'name': 'foo'} + let s:obj['clear'] = function('s:cache_arg') + + call assert_equal('foo/bar', s:obj.clear('bar')) + let F = s:obj.clear + let s:result = '' + call assert_equal('foo/bar', F('bar')) + call assert_equal('foo/bar', s:result) + + let s:obj['clear'] = function('s:cache_arg', ['bar']) + call assert_equal('foo/bar', s:obj.clear()) + let s:result = '' + call s:obj.clear() + call assert_equal('foo/bar', s:result) + + let F = s:obj.clear + call assert_equal('foo/bar', F()) + let s:result = '' + call F() + call assert_equal('foo/bar', s:result) + + call assert_equal('foo/bar', call(s:obj.clear, [], s:obj)) +endfunc + +func Test_partial_exists() + let F = function('MyFunc') + call assert_true(exists('*F')) + let lF = [F] + call assert_true(exists('*lF[0]')) + + let F = function('MyFunc', ['arg']) + call assert_true(exists('*F')) + let lF = [F] + call assert_true(exists('*lF[0]')) +endfunc + +func Test_partial_string() + let F = function('MyFunc') + call assert_equal("function('MyFunc')", string(F)) + let F = function('MyFunc', ['foo']) + call assert_equal("function('MyFunc', ['foo'])", string(F)) + let F = function('MyFunc', ['foo', 'bar']) + call assert_equal("function('MyFunc', ['foo', 'bar'])", string(F)) + let d = {'one': 1} + let F = function('MyFunc', d) + call assert_equal("function('MyFunc', {'one': 1})", string(F)) + let F = function('MyFunc', ['foo'], d) + call assert_equal("function('MyFunc', ['foo'], {'one': 1})", string(F)) +endfunc + +func Test_func_unref() + let obj = {} + function! obj.func() abort + endfunction + let funcnumber = matchstr(string(obj.func), '^function(''\zs.\{-}\ze''') + call assert_true(exists('*{' . funcnumber . '}')) + unlet obj + call assert_false(exists('*{' . funcnumber . '}')) +endfunc + +func Test_redefine_dict_func() + let d = {} + function d.test4() + endfunction + let d.test4 = d.test4 + try + function! d.test4(name) + endfunction + catch + call assert_true(v:errmsg, v:exception) + endtry +endfunc + +" This caused double free on exit if EXITFREE is defined. +func Test_cyclic_list_arg() + let l = [] + let Pt = function('string', [l]) + call add(l, Pt) + unlet l + unlet Pt +endfunc + +" This caused double free on exit if EXITFREE is defined. +func Test_cyclic_dict_arg() + let d = {} + let Pt = function('string', [d]) + let d.Pt = Pt + unlet d + unlet Pt +endfunc + +func Ignored(job1, job2, status) +endfunc + +" func Test_cycle_partial_job() +" let job = job_start('echo') +" call job_setoptions(job, {'exit_cb': function('Ignored', [job])}) +" unlet job +" endfunc + +" func Test_ref_job_partial_dict() +" let g:ref_job = job_start('echo') +" let d = {'a': 'b'} +" call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)}) +" endfunc + +func Test_auto_partial_rebind() + let dict1 = {'name': 'dict1'} + func! dict1.f1() + return self.name + endfunc + let dict1.f2 = function(dict1.f1, dict1) + + call assert_equal('dict1', dict1.f1()) + call assert_equal('dict1', dict1['f1']()) + call assert_equal('dict1', dict1.f2()) + call assert_equal('dict1', dict1['f2']()) + + let dict2 = {'name': 'dict2'} + let dict2.f1 = dict1.f1 + let dict2.f2 = dict1.f2 + + call assert_equal('dict2', dict2.f1()) + call assert_equal('dict2', dict2['f1']()) + call assert_equal('dict1', dict2.f2()) + call assert_equal('dict1', dict2['f2']()) +endfunc + +func Test_get_partial_items() + let dict = {'name': 'hello'} + let args = ["foo", "bar"] + let Func = function('MyDictFunc') + let Cb = function('MyDictFunc', args, dict) + + call assert_equal(Func, get(Cb, 'func')) + call assert_equal('MyDictFunc', get(Cb, 'name')) + call assert_equal(args, get(Cb, 'args')) + call assert_equal(dict, get(Cb, 'dict')) + call assert_fails('call get(Cb, "xxx")', 'E475:') + + call assert_equal(Func, get(Func, 'func')) + call assert_equal('MyDictFunc', get(Func, 'name')) + call assert_equal([], get(Func, 'args')) + call assert_true(empty( get(Func, 'dict'))) +endfunc + +func Test_compare_partials() + let d1 = {} + let d2 = {} + + function d1.f1() dict + endfunction + + function d1.f2() dict + endfunction + + let F1 = get(d1, 'f1') + let F2 = get(d1, 'f2') + + let F1d1 = function(F1, d1) + let F2d1 = function(F2, d2) + let F1d1a1 = function(F1d1, [1]) + let F1d1a12 = function(F1d1, [1, 2]) + let F1a1 = function(F1, [1]) + let F1a2 = function(F1, [2]) + let F1d2 = function(F1, d2) + let d3 = {'f1': F1, 'f2': F2} + let F1d3 = function(F1, d3) + let F1ad1 = function(F1, [d1]) + let F1ad3 = function(F1, [d3]) + + call assert_match('^function(''\d\+'')$', string(F1)) " Not a partial + call assert_match('^function(''\d\+'')$', string(F2)) " Not a partial + call assert_match('^function(''\d\+'', {.*})$', string(F1d1)) " A partial + call assert_match('^function(''\d\+'', {.*})$', string(F2d1)) " A partial + call assert_match('^function(''\d\+'', \[.*\])$', string(F1a1)) " No dict + + " != + let X = F1 + call assert_false(F1 != X) " same function + let X = F1d1 + call assert_false(F1d1 != X) " same partial + let X = F1d1a1 + call assert_false(F1d1a1 != X) " same partial + let X = F1a1 + call assert_false(F1a1 != X) " same partial + + call assert_true(F1 != F2) " Different functions + call assert_true(F1 != F1d1) " Partial /= non-partial + call assert_true(F1d1a1 != F1d1a12) " Different number of arguments + call assert_true(F1a1 != F1d1a12) " One has no dict + call assert_true(F1a1 != F1a2) " Different arguments + call assert_true(F1d2 != F1d1) " Different dictionaries + call assert_false(F1d1 != F1d3) " Equal dictionaries, even though d1 isnot d3 + + " isnot, option 1 + call assert_true(F1 isnot# F2) " Different functions + call assert_true(F1 isnot# F1d1) " Partial /= non-partial + call assert_true(F1d1 isnot# F1d3) " d1 isnot d3, even though d1 == d3 + call assert_true(F1a1 isnot# F1d1a12) " One has no dict + call assert_true(F1a1 isnot# F1a2) " Different number of arguments + call assert_true(F1ad1 isnot# F1ad3) " In arguments d1 isnot d3 + + " isnot, option 2 + call assert_true(F1 isnot# F2) " Different functions + call assert_true(F1 isnot# F1d1) " Partial /= non-partial + call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 9f58a35909..d92cbe6897 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -8,6 +8,10 @@ func MyHandler(timer) let s:val += 1 endfunc +func MyHandlerWithLists(lists, timer) + let x = string(a:lists) +endfunc + func Test_oneshot() let s:val = 0 let timer = timer_start(50, 'MyHandler') @@ -30,3 +34,22 @@ func Test_repeat_many() call assert_true(s:val > 1) call assert_true(s:val < 5) endfunc + +func Test_with_partial_callback() + let s:val = 0 + let s:meow = {} + function s:meow.bite(...) + let s:val += 1 + endfunction + + call timer_start(50, s:meow.bite) + sleep 200m + call assert_equal(1, s:val) +endfunc + +func Test_retain_partial() + call timer_start(100, function('MyHandlerWithLists', [['a']])) + call garbagecollect() + sleep 200m +endfunc +" vim: ts=2 sw=0 et diff --git a/src/nvim/testdir/test_viml.vim b/src/nvim/testdir/test_viml.vim index a11d62f5cf..a2997b6d4d 100644 --- a/src/nvim/testdir/test_viml.vim +++ b/src/nvim/testdir/test_viml.vim @@ -943,6 +943,7 @@ func Test_type() call assert_equal(0, type(0)) call assert_equal(1, type("")) call assert_equal(2, type(function("tr"))) + call assert_equal(2, type(function("tr", [8]))) call assert_equal(3, type([])) call assert_equal(4, type({})) call assert_equal(5, type(0.0)) @@ -984,6 +985,86 @@ func Test_skip() endfunc "------------------------------------------------------------------------------- +" Test 93: :echo and string() {{{1 +"------------------------------------------------------------------------------- + +func Test_echo_and_string() + " String + let a = 'foo bar' + redir => result + echo a + echo string(a) + redir END + let l = split(result, "\n") + call assert_equal(["foo bar", + \ "'foo bar'"], l) + + " Float + if has('float') + let a = -1.2e0 + redir => result + echo a + echo string(a) + redir END + let l = split(result, "\n") + call assert_equal(["-1.2", + \ "-1.2"], l) + endif + + " Funcref + redir => result + echo function('string') + echo string(function('string')) + redir END + let l = split(result, "\n") + call assert_equal(["string", + \ "function('string')"], l) + + " Empty dictionaries in a list + let a = {} + redir => result + echo [a, a, a] + echo string([a, a, a]) + redir END + let l = split(result, "\n") + call assert_equal(["[{}, {}, {}]", + \ "[{}, {}, {}]"], l) + + " Empty dictionaries in a dictionary + let a = {} + let b = {"a": a, "b": a} + redir => result + echo b + echo string(b) + redir END + let l = split(result, "\n") + call assert_equal(["{'a': {}, 'b': {}}", + \ "{'a': {}, 'b': {}}"], l) + + " Empty lists in a list + let a = [] + redir => result + echo [a, a, a] + echo string([a, a, a]) + redir END + let l = split(result, "\n") + call assert_equal(["[[], [], []]", + \ "[[], [], []]"], l) + + " Empty lists in a dictionary + let a = [] + let b = {"a": a, "b": a} + redir => result + echo b + echo string(b) + redir END + let l = split(result, "\n") + call assert_equal(["{'a': [], 'b': []}", + \ "{'a': [], 'b': []}"], l) + +endfunc + +"------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker " vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") diff --git a/src/nvim/version.c b/src/nvim/version.c index 348bf3c014..7de0b508a3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -565,7 +565,7 @@ static int included_patches[] = { // 1878 NA // 1877 NA // 1876, - // 1875, + 1875, // 1874 NA // 1873 NA // 1872 NA @@ -578,9 +578,9 @@ static int included_patches[] = { // 1865 NA // 1864 NA // 1863 NA - // 1862, + // 1862 NA // 1861, - // 1860 NA + 1860, // 1859 NA // 1858 NA // 1857 NA @@ -598,15 +598,14 @@ static int included_patches[] = { // 1845 NA // 1844, // 1843 NA - // 1842, + 1842, // 1841, 1840, // 1839, // 1838, // 1837, - // 1836, + 1836, 1835, - // 1834, 1833, 1832, 1831, @@ -711,7 +710,7 @@ static int included_patches[] = { 1734, // 1733 NA 1732, - // 1731, + // 1731 NA 1730, // 1729 NA 1728, @@ -722,12 +721,12 @@ static int included_patches[] = { 1723, // 1722 NA // 1721 NA - // 1720, - // 1719, - // 1718, + // 1720 NA + 1719, + 1718, // 1717 NA 1716, - // 1715, + 1715, 1714, // 1713 NA 1712, @@ -797,14 +796,14 @@ static int included_patches[] = { 1648, 1647, // 1646 NA - // 1645, - // 1644, + 1645, + // 1644 NA 1643, 1642, 1641, 1640, - // 1639, - // 1638, + 1639, + 1638, // 1637 NA // 1636 NA // 1635 NA @@ -834,10 +833,10 @@ static int included_patches[] = { // 1611 NA // 1610 NA // 1609 NA - // 1608, - // 1607, - // 1606, - // 1605, + 1608, + 1607, + 1606, + 1605, 1604, 1603, // 1602 NA @@ -852,20 +851,20 @@ static int included_patches[] = { // 1593 NA 1592, 1591, - // 1590, - // 1589, + 1590, + 1589, 1588, // 1587 NA - // 1586, - // 1585, + 1586, + 1585, // 1584 NA // 1583 NA - // 1582, - // 1581, - // 1580, + 1582, + 1581, + 1580, // 1579 NA 1578, - // 1577, + 1577, 1576, // 1575 NA 1574, @@ -878,12 +877,12 @@ static int included_patches[] = { 1567, // 1566 NA 1565, - // 1564, - // 1563, + 1564, + 1563, // 1562 NA // 1561 NA // 1560 NA - // 1559, + 1559, 1558, 1557, // 1556 NA diff --git a/test/functional/core/job_partial_spec.lua b/test/functional/core/job_partial_spec.lua new file mode 100644 index 0000000000..b60f239db9 --- /dev/null +++ b/test/functional/core/job_partial_spec.lua @@ -0,0 +1,27 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eq, next_msg, nvim, source = helpers.clear, helpers.eq, + helpers.next_message, helpers.nvim, helpers.source + +if helpers.pending_win32(pending) then return end + +describe('jobs with partials', function() + local channel + + before_each(function() + clear() + channel = nvim('get_api_info')[1] + nvim('set_var', 'channel', channel) + end) + + it('works correctly', function() + source([[ + function PrintArgs(a1, a2, id, data, event) + call rpcnotify(g:channel, '1', a:a1, a:a2, a:data, a:event) + endfunction + let Callback = function('PrintArgs', ["foo", "bar"]) + let g:job_opts = {'on_stdout': Callback} + call jobstart(['echo'], g:job_opts) + ]]) + eq({'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, next_msg()) + end) +end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 79cc877cac..34085fa522 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -18,7 +18,7 @@ describe('jobs', function() channel = nvim('get_api_info')[1] nvim('set_var', 'channel', channel) source([[ - function! s:OnEvent(id, data, event) + function! s:OnEvent(id, data, event) dict let userdata = get(self, 'user') call rpcnotify(g:channel, a:event, userdata, a:data) endfunction @@ -265,9 +265,12 @@ describe('jobs', function() eq({'notification', 'exit', {45, 10}}, next_msg()) end) - it('cannot redefine callbacks being used by a job', function() + it('can redefine callbacks being used by a job', function() local screen = Screen.new() screen:attach() + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + }) local script = [[ function! g:JobHandler(job_id, data, event) endfunction @@ -283,20 +286,20 @@ describe('jobs', function() feed(':function! g:JobHandler(job_id, data, event)<cr>') feed(':endfunction<cr>') screen:expect([[ - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - :function! g:JobHandler(job_id, data, event) | - : :endfunction | - E127: Cannot redefine function JobHandler: It is in u| - se | - Press ENTER or type command to continue^ | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | ]]) end) @@ -317,7 +320,7 @@ describe('jobs', function() source([[ let g:dict = {'id': 10} let g:exits = 0 - function g:dict.on_exit(id, code) + function g:dict.on_exit(id, code, event) if a:code != 5 throw 'Error!' endif @@ -365,7 +368,7 @@ describe('jobs', function() eq({'notification', 'wait', {{-2}}}, next_msg()) end) - it('can be called recursively', function() + pending('can be called recursively', function() source([[ let g:opts = {} let g:counter = 0 diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index dc87312911..e6f7609016 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source local eq, next_msg = helpers.eq, helpers.next_message local exc_exec = helpers.exc_exec +local command = helpers.command describe('dictionary change notifications', function() @@ -229,11 +230,9 @@ describe('dictionary change notifications', function() exc_exec('call dictwatcherdel(g:, "invalid_key", "g:Watcher2")')) end) - it("fails to add/remove if the callback doesn't exist", function() - eq("Vim(call):Function g:InvalidCb doesn't exist", - exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")')) - eq("Vim(call):Function g:InvalidCb doesn't exist", - exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")')) + it("does not fail to add/remove if the callback doesn't exist", function() + command('call dictwatcheradd(g:, "key", "g:InvalidCb")') + command('call dictwatcherdel(g:, "key", "g:InvalidCb")') end) it('fails with empty keys', function() @@ -243,15 +242,18 @@ describe('dictionary change notifications', function() exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")')) end) - it('fails to replace a watcher function', function() + it('does not fail to replace a watcher function', function() source([[ function! g:ReplaceWatcher2() - function! g:Watcher2() + function! g:Watcher2(dict, key, value) + call rpcnotify(g:channel, '2b', a:key, a:value) endfunction endfunction ]]) - eq("Vim(function):E127: Cannot redefine function Watcher2: It is in use", - exc_exec('call g:ReplaceWatcher2()')) + command('call g:ReplaceWatcher2()') + command('let g:key = "value"') + eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg()) + end) end) end) diff --git a/test/functional/legacy/055_list_and_dict_types_spec.lua b/test/functional/legacy/055_list_and_dict_types_spec.lua index b9e5a8bc03..dbe9e1bc7f 100644 --- a/test/functional/legacy/055_list_and_dict_types_spec.lua +++ b/test/functional/legacy/055_list_and_dict_types_spec.lua @@ -274,17 +274,13 @@ describe('list and dictionary types', function() let dict.data = [1,2,3] call dict.func("len: ") let x = dict.func("again: ") - try - let Fn = dict.func - call Fn('xxx') - catch - $put =v:exception[:15] - endtry]]) + let Fn = dict.func + call Fn('xxx')]]) expect([[ len: 3 again: 3 - Vim(call):E725: ]]) + xxx3]]) end) it('Function in script-local List or Dict', function() diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 427aa011e9..cecd67d7fa 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -167,7 +167,7 @@ describe('terminal buffer', function() local tbuf = eval('bufnr("%")') source([[ - function! SplitWindow() + function! SplitWindow(id, data, event) new call feedkeys("iabc\<Esc>") endfunction |