From 521e45f2a8c0619335288accdda0f0aaa1fc6513 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 24 Oct 2016 23:53:07 -0700 Subject: vim-patch:7.4.1559 Problem: Passing cookie to a callback is clumsy. Solution: Change function() to take arguments and return a partial. https://github.com/vim/vim/commit/1735bc988c546cc962c5f94792815b4d7cb79710 --- src/nvim/api/private/helpers.c | 4 + src/nvim/api/vim.c | 2 +- src/nvim/eval.c | 410 +++++++++++++++++++++++++++++--------- src/nvim/eval.lua | 2 +- src/nvim/eval/encode.c | 20 ++ src/nvim/eval/typval_encode.h | 9 + src/nvim/eval_defs.h | 13 +- src/nvim/normal.c | 2 +- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_partial.vim | 43 ++++ src/nvim/version.c | 2 +- 11 files changed, 412 insertions(+), 96 deletions(-) create mode 100644 src/nvim/testdir/test_partial.vim (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 72db7c0782..0acdff3232 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 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..6dd0b8cb4a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1208,7 +1208,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 +2455,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 +2757,9 @@ void ex_call(exarg_T *eap) typval_T rettv; linenr_T lnum; int doesrange; - int failed = FALSE; + int failed = false; funcdict_T fudi; + partial_T *partial; if (eap->skip) { /* trans_function_name() doesn't work well when skipping, use eval0() @@ -2786,7 +2788,7 @@ void ex_call(exarg_T *eap) /* If it is the name of a variable of type VAR_FUNC use its contents. */ len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, FALSE); + name = deref_func_name(tofree, &len, &partial, false); /* Skip white space to allow ":call func ()". Not good, but required for * backward compatibility. */ @@ -2817,9 +2819,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 +3184,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,7 +3757,9 @@ 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) { + } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL + || var2.v_type == VAR_PARTIAL) { if (rettv->v_type != var2.v_type || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { if (rettv->v_type != var2.v_type) @@ -3764,16 +3769,21 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) clear_tv(rettv); clear_tv(&var2); return FAIL; + } else if (rettv->v_type == VAR_PARTIAL) { + // Partials are only equal when identical. + n1 = rettv->vval.v_partial != NULL + && rettv->vval.v_partial == var2.vval.v_partial; } 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; + || 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; } } /* @@ -4308,14 +4318,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 +4429,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 +4650,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 @@ -5177,6 +5190,10 @@ tv_equal ( && tv2->vval.v_string != NULL && STRCMP(tv1->vval.v_string, tv2->vval.v_string) == 0; + case VAR_PARTIAL: + return tv1->vval.v_partial != NULL + && tv1->vval.v_partial == tv2->vval.v_partial; + case VAR_NUMBER: return tv1->vval.v_number == tv2->vval.v_number; @@ -6059,6 +6076,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, } case VAR_FUNC: + case VAR_PARTIAL: case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6797,12 +6815,18 @@ static VimLFuncDef *find_internal_func(const char *const name) /* * Check if "name" is a variable of type VAR_FUNC. If so, return the function * name it contains, otherwise return "name". + * If "name" is of type VAR_PARTIAL also return "partial" */ -static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload) +static char_u *deref_func_name( + char_u *name, int *lenp, + partial_T **partial, int no_autoload +) { dictitem_T *v; int cc; + *partial = NULL; + cc = name[*lenp]; name[*lenp] = NUL; v = find_var(name, NULL, no_autoload); @@ -6816,6 +6840,16 @@ 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 = v->di_tv.vval.v_partial; + if (*partial == NULL) { + *lenp = 0; + return (char_u *)""; // just in case + } + *lenp = (int)STRLEN((*partial)->pt_name); + return (*partial)->pt_name; + } + return name; } @@ -6833,7 +6867,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 +6880,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 +6898,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) @@ -6886,18 +6924,19 @@ get_func_tv ( * 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 */ +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 int evaluate, - dict_T *selfdict /* Dictionary for "self" */ + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" ) { int ret = FAIL; @@ -6908,6 +6947,7 @@ call_func ( #define ERROR_DICT 4 #define ERROR_NONE 5 #define ERROR_OTHER 6 +#define ERROR_BOTH 7 int error = ERROR_NONE; int llen; ufunc_T *fp; @@ -6915,9 +6955,14 @@ call_func ( 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. */ + 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); /* @@ -6953,6 +6998,27 @@ call_func ( *doesrange = FALSE; + if (partial != NULL) { + if (partial->pt_dict != NULL) { + if (selfdict_in != NULL) { + error = ERROR_BOTH; + } + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + int i; + + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + copy_tv(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (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 */ if (evaluate && error == ERROR_NONE) { @@ -7056,11 +7122,19 @@ call_func ( emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), name); break; + case ERROR_BOTH: + emsg_funcname(N_("E924: can't have both a \"self\" dict and " + "a partial: %s"), name); + break; } } - if (fname != name && fname != fname_buf) + while (argv_clear > 0) { + clear_tv(&argv[--argv_clear]); + } + if (fname != name && fname != fname_buf) { xfree(fname); + } xfree(name); return ret; @@ -7823,7 +7897,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 +7908,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 +7918,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 +7931,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 +7945,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 +7965,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); + (void)func_call(func, &argvars[1], partial, selfdict, rettv); } /* @@ -8467,6 +8547,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,6 +9425,7 @@ 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; s = get_tv_string(&argvars[0]); if (s == NULL || *s == NUL || ascii_isdigit(*s)) @@ -9354,23 +9438,112 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) char sid_buf[25]; int off = *s == 's' ? 2 : 5; - /* Expand s: and into 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, "%" 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 into 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), "%" PRId64 "_", + (int64_t)current_SID); + name = xmalloc((int)(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) { + partial_T *pt; + int dict_idx = 0; + int arg_idx = 0; + + 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 && (argvars[dict_idx].v_type != VAR_DICT + || argvars[dict_idx].vval.v_dict == NULL)) { + EMSG(_("E922: expected a dict")); + xfree(name); + return; + } + if (arg_idx > 0 && (argvars[arg_idx].v_type != VAR_LIST + || argvars[arg_idx].vval.v_list == NULL)) { + EMSG(_("E923: Second argument of function() must be a list or a dict")); + xfree(name); + return; + } + + pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + if (pt != NULL) { + if (arg_idx > 0) { + list_T *list = argvars[arg_idx].vval.v_list; + listitem_T *li; + int i = 0; + + pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * list->lv_len); + if (pt->pt_argv == NULL) { + xfree(pt); + xfree(name); + return; + } else { + pt->pt_argc = list->lv_len; + for (li = list->lv_first; li != NULL; li = li->li_next) { + copy_tv(&li->li_tv, &pt->pt_argv[i++]); + } + } + } + + if (dict_idx > 0) { + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (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 { + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); + } } } -/* - * "garbagecollect()" function - */ +static void partial_free(partial_T *pt) +{ + int i; + + for (i = 0; i < pt->pt_argc; i++) { + clear_tv(&pt->pt_argv[i]); + } + xfree(pt->pt_argv); + 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); + } +} + +/// "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 @@ -11971,6 +12144,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; } @@ -15035,6 +15209,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 +15316,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 +15327,22 @@ 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 +15418,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 +15486,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 +15516,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; @@ -16865,6 +17054,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; } + case VAR_PARTIAL: n = 8; break; case VAR_UNKNOWN: { EMSG2(_(e_intern2), "f_type(UNKNOWN)"); break; @@ -18052,11 +18242,13 @@ handle_subscript ( char_u *s; int len; typval_T functv; + partial_T *pt = NULL; 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 */ @@ -18064,13 +18256,19 @@ handle_subscript ( functv = *rettv; rettv->v_type = VAR_UNKNOWN; - /* Invoke the function. Recursive! */ - s = functv.vval.v_string; - } else + // Invoke the function. Recursive! + if (rettv->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). */ @@ -18118,6 +18316,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 +18383,12 @@ void free_tv(typval_T *varp) tv->v_lock = VAR_UNLOCKED; \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + do { \ + partial_unref(partial); \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ do { \ list_unref(tv->vval.v_list); \ @@ -18258,6 +18465,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 +18523,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 +18571,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 +18668,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: @@ -18793,11 +19004,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 +19021,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 +19061,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 +19264,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 +19324,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; @@ -19956,6 +20179,7 @@ trans_function_name ( char_u sid_buf[20]; int len; lval_T lv; + partial_T *partial; if (fdp != NULL) memset(fdp, 0, sizeof(funcdict_T)); @@ -20030,14 +20254,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); @@ -22301,6 +22528,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) curwin->w_cursor.lnum, &dummy, true, + NULL, NULL); list_unref(arguments); 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..57507f4430 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -315,6 +315,13 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, ga_append(gap, ')'); \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + do { \ + ga_concat(gap, "partial("); \ + TYPVAL_ENCODE_CONV_STRING(partial, STRLEN(partial)); \ + ga_append(gap, ')'); \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ ga_concat(gap, "[]") @@ -661,6 +668,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(partial) \ + 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 +731,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 +860,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(_("E951: Error while dumping %s, %s: " \ + "attempt to dump partial"), \ + mpstack, objname) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ msgpack_pack_array(packer, 0) @@ -902,6 +921,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..ce3bb0ee08 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 partial 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..a33ac10e61 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,7 +36,8 @@ 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_PARTIAL, ///< Partial, .v_partial is used. VAR_LIST, ///< List, .v_list is used. VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. @@ -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,14 @@ 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. + 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/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/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_partial.vim b/src/nvim/testdir/test_partial.vim new file mode 100644 index 0000000000..061f839668 --- /dev/null +++ b/src/nvim/testdir/test_partial.vim @@ -0,0 +1,43 @@ +" 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 + endif + return a:one < a:two +endfunc + +func Test_partial_args() + let Cb = function('MyFunc', ["foo", "bar"]) + call assert_equal("foo/bar/xxx", Cb("xxx")) + call assert_equal("foo/bar/yyy", call(Cb, ["yyy"])) + + 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', ["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()', 'E492:') +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 348bf3c014..aac3ba44a2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -883,7 +883,7 @@ static int included_patches[] = { // 1562 NA // 1561 NA // 1560 NA - // 1559, + 1559, 1558, 1557, // 1556 NA -- cgit From 66922d89cce6ee381244d37f7c0c324cae815b45 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Tue, 25 Oct 2016 22:48:08 -0700 Subject: vim-patch:7.4.1563 Problem: Partial test fails on windows. Solution: Return 1 or -1 from compare function. https://github.com/vim/vim/commit/790500a8e65bee295ef51a59dfa67ecbaab8ea17 --- src/nvim/testdir/test_partial.vim | 4 ++-- src/nvim/version.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 061f839668..998b232206 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -9,9 +9,9 @@ func MySort(up, one, two) return 0 endif if a:up - return a:one > a:two + return a:one > a:two ? 1 : -1 endif - return a:one < a:two + return a:one < a:two ? 1 : -1 endfunc func Test_partial_args() diff --git a/src/nvim/version.c b/src/nvim/version.c index aac3ba44a2..feb83e495a 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -879,7 +879,7 @@ static int included_patches[] = { // 1566 NA 1565, // 1564, - // 1563, + 1563, // 1562 NA // 1561 NA // 1560 NA -- cgit From f90551b0e67c1389cb91dfedbe9a111c677e67e2 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Tue, 25 Oct 2016 23:03:04 -0700 Subject: vim-patch:7.4.1564 Problem: An empty list in function() causes an error. Solution: Handle an empty list like there is no list of arguments. https://github.com/vim/vim/commit/346418c624f1bc7c04c98907134a2b284e6452dd --- src/nvim/eval.c | 51 ++++++++++++++++++++++++--------------- src/nvim/testdir/test_partial.vim | 10 +++++++- src/nvim/version.c | 2 +- 3 files changed, 41 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6dd0b8cb4a..36303d8313 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9428,12 +9428,16 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u *name; s = get_tv_string(&argvars[0]); - if (s == NULL || *s == NUL || ascii_isdigit(*s)) + if (s == NULL || *s == NUL || 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 (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, "", 5) == 0) { char sid_buf[25]; int off = *s == 's' ? 2 : 5; @@ -9454,10 +9458,6 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[1].v_type != VAR_UNKNOWN) { - partial_T *pt; - int dict_idx = 0; - int arg_idx = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { // function(name, [args], dict) arg_idx = 1; @@ -9469,23 +9469,34 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) // function(name, [args]) arg_idx = 1; } - if (dict_idx > 0 && (argvars[dict_idx].v_type != VAR_DICT - || argvars[dict_idx].vval.v_dict == NULL)) { - EMSG(_("E922: expected a dict")); - xfree(name); - return; + 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 && (argvars[arg_idx].v_type != VAR_LIST - || argvars[arg_idx].vval.v_list == NULL)) { - EMSG(_("E923: Second argument of function() must be a list or a dict")); - xfree(name); - return; + 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) { + partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - pt = (partial_T *)xcalloc(1, sizeof(partial_T)); if (pt != NULL) { if (arg_idx > 0) { - list_T *list = argvars[arg_idx].vval.v_list; listitem_T *li; int i = 0; diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 998b232206..abb655841b 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -19,6 +19,9 @@ func Test_partial_args() call assert_equal("foo/bar/xxx", Cb("xxx")) call assert_equal("foo/bar/yyy", call(Cb, ["yyy"])) + let Cb = function('MyFunc', []) + call assert_equal("a/b/c", Cb("a", "b", "c")) + let Sort = function('MySort', [1]) call assert_equal([1, 2, 3], sort([3, 1, 2], Sort)) let Sort = function('MySort', [0]) @@ -34,10 +37,15 @@ func Test_partial_dict() 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()', 'E492:') + call assert_fails('Cb("fff")', 'E492:') endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index feb83e495a..66adaaff1e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -878,7 +878,7 @@ static int included_patches[] = { 1567, // 1566 NA 1565, - // 1564, + 1564, 1563, // 1562 NA // 1561 NA -- cgit From 529482d6844474f6eab9feed5162008b652fd931 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 12:23:49 -0700 Subject: vim-patch:7.4.1577 Problem: Cannot pass "dict.Myfunc" around as a partial. Solution: Create a partial when expected. https://github.com/vim/vim/commit/ab1fa3955f25dfdb7e329c3bd76e175c93c8cb5e --- src/nvim/eval.c | 51 ++++++++++++++++++++++++++++++++++----- src/nvim/testdir/test_partial.vim | 18 ++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 64 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 36303d8313..57c1c8e210 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -170,6 +170,8 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s="); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_float_as_string = N_("E806: using Float as a String"); +static char *e_dict_both = N_("E924: can't have both a \"self\" dict and " + "a partial: %s"); static char_u * const empty_string = (char_u *)""; static char_u * const namespace_char = (char_u *)"abglstvw"; @@ -7123,8 +7125,7 @@ call_func( name); break; case ERROR_BOTH: - emsg_funcname(N_("E924: can't have both a \"self\" dict and " - "a partial: %s"), name); + emsg_funcname(N_(e_dict_both, name); break; } } @@ -9426,11 +9427,26 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *s; char_u *name; + bool use_string = false; + + 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]) + s = argvars[0].vval.v_partial->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); - } 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 { @@ -9475,6 +9491,11 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(name); return; } + if (argvars[0].v_type == VAR_PARTIAL) { + EMSG2(_(e_dict_both), name); + xfree(name); + return; + } if (argvars[dict_idx].vval.v_dict == NULL) { dict_idx = 0; } @@ -9513,7 +9534,10 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - if (dict_idx > 0) { + if (argvars[0].v_type == VAR_PARTIAL) { + pt->pt_dict = argvars[0].vval.v_partial->pt_dict; + (pt->pt_dict->dv_refcount)++; + } else if (dict_idx > 0) { pt->pt_dict = argvars[dict_idx].vval.v_dict; (pt->pt_dict->dv_refcount)++; } @@ -18268,7 +18292,7 @@ handle_subscript ( rettv->v_type = VAR_UNKNOWN; // Invoke the function. Recursive! - if (rettv->v_type == VAR_PARTIAL) { + if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; s = pt->pt_name; } else { @@ -18310,6 +18334,21 @@ handle_subscript ( } } } + + if (rettv->v_type == VAR_FUNC && selfdict != NULL) { + partial_T *pt = (partial_T *)alloc_clear(sizeof(partial_T)); + + // Turn "dict.Func" into a partial for "Func" with "dict". + if (pt != NULL) { + pt->pt_dict = selfdict; + selfdict = NULL; + pt->pt_name = rettv->vval.v_string; + func_ref(pt->pt_name); + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } + } + dict_unref(selfdict); return ret; } diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index abb655841b..caae1a8cb1 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -49,3 +49,21 @@ func Test_partial_dict() call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy")) call assert_fails('Cb("fff")', 'E492:') 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()) + + call assert_fails('call function(dict.MyFunc, ["bbb"], dict)', 'E924:') +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 66adaaff1e..4de8296ccd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -865,7 +865,7 @@ static int included_patches[] = { // 1580, // 1579 NA 1578, - // 1577, + 1577, 1576, // 1575 NA 1574, -- cgit From 2c1b4c7f3c13b61dab70d6ad3507b90c7a60e4aa Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 12:26:06 -0700 Subject: vim-patch:7.4.1580 Problem: Crash when using function reference. (Luchr) Solution: Set initial refcount. (Ken Takata) https://github.com/vim/vim/commit/7a5c46a9df7ef01a4f6a620861c35400d5ad28d9 --- src/nvim/eval.c | 1 + src/nvim/testdir/test_partial.vim | 14 ++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 57c1c8e210..fc0b2ee23d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18340,6 +18340,7 @@ handle_subscript ( // Turn "dict.Func" into a partial for "Func" with "dict". if (pt != NULL) { + pt->pt_refcount = 1; pt->pt_dict = selfdict; selfdict = NULL; pt->pt_name = rettv->vval.v_string; diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index caae1a8cb1..f6f6b87a29 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -67,3 +67,17 @@ func Test_partial_implicit() call assert_fails('call function(dict.MyFunc, ["bbb"], dict)', 'E924:') 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 + diff --git a/src/nvim/version.c b/src/nvim/version.c index 4de8296ccd..9964f92132 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -862,7 +862,7 @@ static int included_patches[] = { // 1583 NA // 1582, // 1581, - // 1580, + 1580, // 1579 NA 1578, 1577, -- cgit From 5cf0c99755581f789973a4fa4bb3d95a61a01341 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 12:42:15 -0700 Subject: vim-patch:7.4.1581 Problem: Using ":call dict.func()" where the function is a partial does not work. Using "dict.func()" where the function does not take a Dictionary does not work. Solution: Handle partial properly in ":call". (Yasuhiro Matsumoto) https://github.com/vim/vim/commit/65639032bb7b17996cd255d1508a1df4ad528a1f --- src/nvim/eval.c | 88 +++++++++++++++++++++++---------------- src/nvim/testdir/test_partial.vim | 5 +++ src/nvim/version.c | 2 +- 3 files changed, 59 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fc0b2ee23d..5cd59157e9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2774,7 +2774,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); @@ -2788,9 +2788,19 @@ 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, &partial, false); + name = deref_func_name(tofree, &len, + partial != NULL ? NULL : &partial, false); + + // When calling fdict.func(), where "func" is a partial, use "fdict" + // instead of the dict in the partial, for backwards compatibility. + // TODO(vim): Do use the arguments in the partial? + if (fudi.fd_dict != NULL) { + partial = NULL; + } /* Skip white space to allow ":call func ()". Not good, but required for * backward compatibility. */ @@ -6525,7 +6535,7 @@ 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))) { + if ((n = trans_function_name(&n, false, TFN_INT|TFN_QUIET, NULL, NULL))) { rv = find_func(n); xfree(n); } @@ -6814,20 +6824,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". - * If "name" is of type VAR_PARTIAL also return "partial" - */ +/// 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 **partial, int no_autoload + partial_T **partialp, int no_autoload ) { dictitem_T *v; int cc; - - *partial = NULL; + if (partialp != NULL) { + *partialp = NULL; + } cc = name[*lenp]; name[*lenp] = NUL; @@ -6843,13 +6853,17 @@ static char_u *deref_func_name( } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - *partial = v->di_tv.vval.v_partial; - if (*partial == NULL) { + partial_T *pt = v->di_tv.vval.v_partial; + + if (pt == NULL) { *lenp = 0; return (char_u *)""; // just in case } - *lenp = (int)STRLEN((*partial)->pt_name); - return (*partial)->pt_name; + if (partialp != NULL) { + *partialp = pt; + } + *lenp = (int)STRLEN(pt->pt_name); + return pt->pt_name; } return name; @@ -7125,7 +7139,7 @@ call_func( name); break; case ERROR_BOTH: - emsg_funcname(N_(e_dict_both, name); + emsg_funcname(N_(e_dict_both), name); break; } } @@ -18336,17 +18350,21 @@ handle_subscript ( } if (rettv->v_type == VAR_FUNC && selfdict != NULL) { - partial_T *pt = (partial_T *)alloc_clear(sizeof(partial_T)); + ufunc_T *fp = find_func(rettv->vval.v_string); // Turn "dict.Func" into a partial for "Func" with "dict". - if (pt != NULL) { - pt->pt_refcount = 1; - pt->pt_dict = selfdict; - selfdict = NULL; - pt->pt_name = rettv->vval.v_string; - func_ref(pt->pt_name); - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; + 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 = NULL; + pt->pt_name = rettv->vval.v_string; + func_ref(pt->pt_name); + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } } } @@ -19735,7 +19753,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) { /* @@ -20002,7 +20020,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; @@ -20220,7 +20238,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; @@ -20230,7 +20249,6 @@ trans_function_name ( char_u sid_buf[20]; int len; lval_T lv; - partial_T *partial; if (fdp != NULL) memset(fdp, 0, sizeof(funcdict_T)); @@ -20305,14 +20323,14 @@ 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, &partial, + 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, &partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == *pp) { name = NULL; } @@ -20513,8 +20531,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 @@ -20834,7 +20852,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) diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index f6f6b87a29..cb853f9335 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -16,6 +16,8 @@ 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"])) @@ -48,6 +50,9 @@ func Test_partial_dict() 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() diff --git a/src/nvim/version.c b/src/nvim/version.c index 9964f92132..5d35c0fa76 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -861,7 +861,7 @@ static int included_patches[] = { // 1584 NA // 1583 NA // 1582, - // 1581, + 1581, 1580, // 1579 NA 1578, -- cgit From e2258598cacebf3c90bbb8e13789194c417d8dad Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 21:40:40 -0700 Subject: vim-patch:7.4.1582 Problem: Get E923 when using function(dict.func, [], dict). (Kent Sibilev) Storing a function with a dict in a variable drops the dict if the function is script-local. Solution: Translate the function name. Use dict arg if present. https://github.com/vim/vim/commit/6f2e4b36c9d9908e1cace2b1b96e2c154a837bc2 --- src/nvim/eval.c | 154 +++++++++++++++++++++----------------- src/nvim/testdir/test_partial.vim | 23 +++++- src/nvim/version.c | 2 +- 3 files changed, 109 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5cd59157e9..157075eb81 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -170,8 +170,6 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s="); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_float_as_string = N_("E806: using Float as a String"); -static char *e_dict_both = N_("E924: can't have both a \"self\" dict and " - "a partial: %s"); static char_u * const empty_string = (char_u *)""; static char_u * const namespace_char = (char_u *)"abglstvw"; @@ -6933,12 +6931,65 @@ get_func_tv ( return ret; } +#define ERROR_UNKNOWN 0 +#define ERROR_TOOMANY 1 +#define ERROR_TOOFEW 2 +#define ERROR_SCRIPT 3 +#define ERROR_DICT 4 +#define ERROR_NONE 5 +#define ERROR_OTHER 6 +#define ERROR_BOTH 7 +#define FLEN_FIXED 40 -/* - * 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. - */ +/// In a script change name() and s:name() to K_SNR 123_name(). +/// Change 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; + + llen = eval_fname_script(name); + if (llen > 0) { + fname_buf[0] = K_SPECIAL; + fname_buf[1] = KS_EXTRA; + fname_buf[2] = (int)KE_SNR; + i = 3; + if (eval_fname_sid(name)) { // "" or "s:" + if (current_SID <= 0) { + *error = ERROR_SCRIPT; + } else { + vim_snprintf((char *)fname_buf + 3, ARRAY_SIZE(fname_buf) - 3, + "%" PRId64 "_", (int64_t)current_SID); + i = (int)STRLEN(fname_buf); + } + } + + if (i + STRLEN(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; + } else { + fname = xmalloc((unsigned)(i + STRLEN(name + llen) + 1)); + if (fname == NULL) { + *error = ERROR_OTHER; + } else { + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } + } + } 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 @@ -6956,21 +7007,12 @@ call_func( ) { int ret = FAIL; -#define ERROR_UNKNOWN 0 -#define ERROR_TOOMANY 1 -#define ERROR_TOOFEW 2 -#define ERROR_SCRIPT 3 -#define ERROR_DICT 4 -#define ERROR_NONE 5 -#define ERROR_OTHER 6 -#define ERROR_BOTH 7 int error = ERROR_NONE; - int llen; ufunc_T *fp; -#define FLEN_FIXED 40 char_u fname_buf[FLEN_FIXED + 1]; - char_u *fname; - char_u *name; + char_u *tofree = NULL; + char_u *fname; + char_u *name; int argcount = argcount_in; typval_T *argvars = argvars_in; dict_T *selfdict = selfdict_in; @@ -6980,44 +7022,20 @@ call_func( // 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; + } - /* - * In a script change name() and s:name() to K_SNR 123_name(). - * Change 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; - if (eval_fname_sid(name)) { // "" or "s:" - if (current_SID <= 0) { - error = ERROR_SCRIPT; - } else { - vim_snprintf((char *)fname_buf + 3, ARRAY_SIZE(fname_buf) - 3, - "%" PRId64 "_", (int64_t)current_SID); - i = (int)STRLEN(fname_buf); - } - } - if (i + STRLEN(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); - fname = fname_buf; - } else { - fname = xmalloc(i + STRLEN(name + llen) + 1); - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); - } - } else - fname = name; + fname = fname_trans_sid(name, fname_buf, &tofree, &error); *doesrange = FALSE; if (partial != NULL) { if (partial->pt_dict != NULL) { - if (selfdict_in != NULL) { - error = ERROR_BOTH; + // When the function has a partial with a dict and there is a dict + // argument, use the dict argument. That is backwards compatible. + if (selfdict_in == NULL) { + selfdict = partial->pt_dict; } selfdict = partial->pt_dict; } @@ -7138,18 +7156,13 @@ call_func( emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), name); break; - case ERROR_BOTH: - emsg_funcname(N_(e_dict_both), name); - break; } } while (argv_clear > 0) { clear_tv(&argv[--argv_clear]); } - if (fname != name && fname != fname_buf) { - xfree(fname); - } + xfree(tofree); xfree(name); return ret; @@ -9505,11 +9518,6 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(name); return; } - if (argvars[0].v_type == VAR_PARTIAL) { - EMSG2(_(e_dict_both), name); - xfree(name); - return; - } if (argvars[dict_idx].vval.v_dict == NULL) { dict_idx = 0; } @@ -9548,11 +9556,13 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - if (argvars[0].v_type == VAR_PARTIAL) { - pt->pt_dict = argvars[0].vval.v_partial->pt_dict; + // For "function(dict.func, [], dict)" and "func" is a partial + // use "dict". That is backwards compatible. + if (dict_idx > 0) { + pt->pt_dict = argvars[dict_idx].vval.v_dict; (pt->pt_dict->dv_refcount)++; - } else if (dict_idx > 0) { - pt->pt_dict = argvars[dict_idx].vval.v_dict; + } else if (argvars[0].v_type == VAR_PARTIAL) { + pt->pt_dict = argvars[0].vval.v_partial->pt_dict; (pt->pt_dict->dv_refcount)++; } @@ -18350,7 +18360,17 @@ handle_subscript ( } if (rettv->v_type == VAR_FUNC && selfdict != NULL) { - ufunc_T *fp = find_func(rettv->vval.v_string); + char_u *fname; + 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(rettv->vval.v_string, 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)) { diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index cb853f9335..7941ec01c2 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -69,8 +69,6 @@ func Test_partial_implicit() let Func = function(dict.MyFunc, ['bbb']) call assert_equal('foo/bbb', Func()) - - call assert_fails('call function(dict.MyFunc, ["bbb"], dict)', 'E924:') endfunc fun InnerCall(funcref) @@ -86,3 +84,24 @@ 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 diff --git a/src/nvim/version.c b/src/nvim/version.c index 5d35c0fa76..9f3bf74a99 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -860,7 +860,7 @@ static int included_patches[] = { // 1585, // 1584 NA // 1583 NA - // 1582, + 1582, 1581, 1580, // 1579 NA -- cgit From 27b2fb944a4b9bce0f06e7c1f2267949c8edab06 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 21:44:57 -0700 Subject: vim-patch:7.4.1585 Problem: Partial is not recognized everywhere. Solution: Check for partial in trans_function_name(). (Yasuhiro Matsumoto) Add a test. https://github.com/vim/vim/commit/d22a18928ebcb465393da1418bb88204b97badb1 --- src/nvim/eval.c | 4 ++++ src/nvim/testdir/test_partial.vim | 12 ++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 157075eb81..51320ea2af 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20321,6 +20321,10 @@ 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; } else { if (!skip && !(flags & TFN_QUIET) && (fdp == NULL || lv.ll_dict == NULL diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 7941ec01c2..75cbfe2f25 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -105,3 +105,15 @@ func Test_script_function_in_dict() let B = s:obj2.clear call assert_equal('bar', B()) 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 diff --git a/src/nvim/version.c b/src/nvim/version.c index 9f3bf74a99..e6be91e2fd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -857,7 +857,7 @@ static int included_patches[] = { 1588, // 1587 NA // 1586, - // 1585, + 1585, // 1584 NA // 1583 NA 1582, -- cgit From cf2701b269a0fd1490da4296774b9fe426100640 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 21:56:02 -0700 Subject: vim-patch:7.4.1586 Problem: Nesting partials doesn't work. Solution: Append arguments. (Ken Takata) https://github.com/vim/vim/commit/8a1bb046378f4bc68d6a04af2eab80fb3ce04da6 --- src/nvim/eval.c | 39 +++++++++++++++++++++++++++++---------- src/nvim/testdir/test_partial.vim | 8 ++++++++ src/nvim/version.c | 2 +- 3 files changed, 38 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 51320ea2af..c61476cbfe 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9455,6 +9455,7 @@ 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) @@ -9462,7 +9463,8 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (argvars[0].v_type == VAR_PARTIAL && argvars[0].vval.v_partial != NULL) { // function(dict.MyFunc, [arg]) - s = argvars[0].vval.v_partial->pt_name; + arg_pt = argvars[0].vval.v_partial; + s = arg_pt->pt_name; } else { // function('MyFunc', [arg], dict) s = get_tv_string(&argvars[0]); @@ -9535,23 +9537,37 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - if (dict_idx > 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) { + 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; - pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * list->lv_len); + 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 { - pt->pt_argc = list->lv_len; - for (li = list->lv_first; li != NULL; li = li->li_next) { - copy_tv(&li->li_tv, &pt->pt_argv[i++]); + 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++]); + } } } } @@ -9561,9 +9577,11 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (dict_idx > 0) { pt->pt_dict = argvars[dict_idx].vval.v_dict; (pt->pt_dict->dv_refcount)++; - } else if (argvars[0].v_type == VAR_PARTIAL) { - pt->pt_dict = argvars[0].vval.v_partial->pt_dict; - (pt->pt_dict->dv_refcount)++; + } else if (arg_pt != NULL) { + pt->pt_dict = arg_pt->pt_dict; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; + } } pt->pt_refcount = 1; @@ -9573,6 +9591,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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); diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 75cbfe2f25..ae4f74cf5b 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -20,9 +20,17 @@ func Test_partial_args() 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)) diff --git a/src/nvim/version.c b/src/nvim/version.c index e6be91e2fd..da9387c14d 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -856,7 +856,7 @@ static int included_patches[] = { // 1589, 1588, // 1587 NA - // 1586, + 1586, 1585, // 1584 NA // 1583 NA -- cgit From 531249a4acdef36ed44e8bbf1355f2a80a5792a5 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 13:48:32 -0700 Subject: vim-patch:7.4.1589 Problem: Combining dict and args with partial doesn't always work. Solution: Use the arguments from the partial. https://github.com/vim/vim/commit/9e63f61cb01c70fd71652f54b2d01ee27b2a3534 --- src/nvim/eval.c | 44 ++++++++++++++++++++++++++++----------- src/nvim/testdir/test_partial.vim | 30 ++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 63 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c61476cbfe..38ddd55855 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2759,7 +2759,7 @@ void ex_call(exarg_T *eap) int doesrange; int failed = false; funcdict_T fudi; - partial_T *partial; + partial_T *partial = NULL; if (eap->skip) { /* trans_function_name() doesn't work well when skipping, use eval0() @@ -2793,13 +2793,6 @@ void ex_call(exarg_T *eap) name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); - // When calling fdict.func(), where "func" is a partial, use "fdict" - // instead of the dict in the partial, for backwards compatibility. - // TODO(vim): Do use the arguments in the partial? - if (fudi.fd_dict != NULL) { - partial = NULL; - } - /* Skip white space to allow ":call func ()". Not good, but required for * backward compatibility. */ startarg = skipwhite(arg); @@ -18378,15 +18371,17 @@ handle_subscript ( } } - if (rettv->v_type == VAR_FUNC && selfdict != NULL) { - char_u *fname; + if ((rettv->v_type == VAR_FUNC || rettv->v_type == VAR_PARTIAL) + && selfdict != NULL) { + 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(rettv->vval.v_string, fname_buf, &tofree, &error); + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); fp = find_func(fname); xfree(tofree); @@ -18399,7 +18394,29 @@ handle_subscript ( pt->pt_refcount = 1; pt->pt_dict = selfdict; selfdict = NULL; - pt->pt_name = rettv->vval.v_string; + if (rettv->v_type == VAR_FUNC) { + // just a function: use selfdict + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // partial: use selfdict and copy args + pt->pt_name = vim_strsave(ret_pt->pt_name); + if (ret_pt->pt_argc > 0) { + pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * ret_pt->pt_argc); + 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); + } func_ref(pt->pt_name); rettv->v_type = VAR_PARTIAL; rettv->vval.v_partial = pt; @@ -20344,6 +20361,9 @@ trans_function_name ( && 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 diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index ae4f74cf5b..2b6b56c358 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -114,6 +114,36 @@ func Test_script_function_in_dict() 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')) diff --git a/src/nvim/version.c b/src/nvim/version.c index da9387c14d..24fe2ecd9e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -853,7 +853,7 @@ static int included_patches[] = { 1592, 1591, // 1590, - // 1589, + 1589, 1588, // 1587 NA 1586, -- cgit From 86706011a67efb3e248691bf2391355d0e0e7d50 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 13:51:04 -0700 Subject: vim-patch:7.4.1590 Problem: Warning for shadowed variable. (Christian Brabandt) Solution: Move the variable into a local block. https://github.com/vim/vim/commit/3f242a844e83a5a04943869f6e3bcbf8650dc465 --- src/nvim/eval.c | 4 ++-- src/nvim/version.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 38ddd55855..1cd52832ee 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18313,7 +18313,6 @@ handle_subscript ( char_u *s; int len; typval_T functv; - partial_T *pt = NULL; while (ret == OK && (**arg == '[' @@ -18322,7 +18321,8 @@ handle_subscript ( || 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; diff --git a/src/nvim/version.c b/src/nvim/version.c index 24fe2ecd9e..fe03b6915b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -852,7 +852,7 @@ static int included_patches[] = { // 1593 NA 1592, 1591, - // 1590, + 1590, 1589, 1588, // 1587 NA -- cgit From 34a7814219b835e8d552cac890ccdf67aad65b2a Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 26 Oct 2016 22:50:07 -0700 Subject: vim-patch:7.4.1605 Problem: Catching exception that won't be thrown. Solution: Remove try/catch. https://github.com/vim/vim/commit/3905e291fe4375ca5c59efa9ffcb01a39c7be3a9 --- src/nvim/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index fe03b6915b..2e711797f4 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -837,7 +837,7 @@ static int included_patches[] = { // 1608, // 1607, // 1606, - // 1605, + 1605, 1604, 1603, // 1602 NA -- cgit From 5241ca7d7a8c3a08af8bbfbf7cca3381241a915b Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 13:53:53 -0700 Subject: vim-patch:7.4.1606 Problem: Having type() handle a Funcref that is or isn't a partial differently causes problems for existing scripts. Solution: Make type() return the same value. (Thinca) https://github.com/vim/vim/commit/953cc7fb139dc2ba8590f8b03a095b63f4e1208f --- src/nvim/eval.c | 2 +- src/nvim/testdir/test_viml.vim | 1 + src/nvim/version.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1cd52832ee..1be3476e35 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17107,6 +17107,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; @@ -17125,7 +17126,6 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; } - case VAR_PARTIAL: n = 8; break; case VAR_UNKNOWN: { EMSG2(_(e_intern2), "f_type(UNKNOWN)"); break; diff --git a/src/nvim/testdir/test_viml.vim b/src/nvim/testdir/test_viml.vim index a11d62f5cf..2f9a72d618 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)) diff --git a/src/nvim/version.c b/src/nvim/version.c index 2e711797f4..a3df79d0c3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -836,7 +836,7 @@ static int included_patches[] = { // 1609 NA // 1608, // 1607, - // 1606, + 1606, 1605, 1604, 1603, -- cgit From 3213b28c01313c7f0e7e0e01f72a0fbfef85fa3e Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 14:05:27 -0700 Subject: vim-patch:7.4.1607 Problem: Comparing a function that exists on two dicts is not backwards compatible. (Thinca) Solution: Only compare the function, not what the partial adds. https://github.com/vim/vim/commit/f0e86a0dbddc18568910e9e4aaae0cd88ca8087a --- src/nvim/eval.c | 51 +++++++++++++++++------------------------- src/nvim/testdir/test_expr.vim | 22 ++++++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 44 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1be3476e35..50bcfe4e2a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3763,28 +3763,13 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC || rettv->v_type == VAR_PARTIAL || var2.v_type == VAR_PARTIAL) { - 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")); + if (type != TYPE_EQUAL && type != TYPE_NEQUAL) { + EMSG(_("E694: Invalid operation for Funcrefs")); clear_tv(rettv); clear_tv(&var2); return FAIL; - } else if (rettv->v_type == VAR_PARTIAL) { - // Partials are only equal when identical. - n1 = rettv->vval.v_partial != NULL - && rettv->vval.v_partial == var2.vval.v_partial; - } 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; - } } + n1 = tv_equal(rettv, &var2, false, false); if (type == TYPE_NEQUAL) { n1 = !n1; } @@ -5159,8 +5144,20 @@ tv_equal ( static int recursive_cnt = 0; /* catch recursive loops */ int r; - if (tv1->v_type != tv2->v_type) - return FALSE; + // 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))) { + s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string + : tv1->vval.v_partial->pt_name; + s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string + : tv2->vval.v_partial->pt_name; + return (s1 != NULL && s2 != NULL && STRCMP(s1, s2) == 0); + } + 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. @@ -5188,15 +5185,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_PARTIAL: - return tv1->vval.v_partial != NULL - && tv1->vval.v_partial == tv2->vval.v_partial; - case VAR_NUMBER: return tv1->vval.v_number == tv2->vval.v_number; @@ -5211,6 +5199,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. @@ -18404,7 +18394,8 @@ handle_subscript ( // partial: use selfdict and copy args pt->pt_name = vim_strsave(ret_pt->pt_name); if (ret_pt->pt_argc > 0) { - pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * ret_pt->pt_argc); + 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; 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/version.c b/src/nvim/version.c index a3df79d0c3..442598c643 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -835,7 +835,7 @@ static int included_patches[] = { // 1610 NA // 1609 NA // 1608, - // 1607, + 1607, 1606, 1605, 1604, -- cgit From bae31b764a5607fad5d914f271e93e10c2d0bfbe Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 22:44:42 -0700 Subject: vim-patch:7.4.1608 Problem: string() doesn't handle a partial. Solution: Make a string from a partial. https://github.com/vim/vim/commit/5c29154b521e9948190be653cfda666ecbb63b5b --- src/nvim/eval/encode.c | 29 +++++++++++-- src/nvim/testdir/test_partial.vim | 86 +++++++++++++++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 113 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 57507f4430..65570a6f30 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -317,9 +317,32 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ do { \ - ga_concat(gap, "partial("); \ - TYPVAL_ENCODE_CONV_STRING(partial, STRLEN(partial)); \ - ga_append(gap, ')'); \ + partial_T *pt = tv->vval.v_partial; \ + garray_T ga; \ + int i; \ + ga_init(&ga, 1, 100); \ + ga_concat(&ga, (char_u *)"function("); \ + if (&pt->pt_name != NULL) { \ + TYPVAL_ENCODE_CONV_STRING((char *)pt->pt_name, sizeof(pt->pt_name)); \ + } \ + if (pt != NULL && pt->pt_argc > 0) { \ + ga_concat(&ga, (char_u *)", ["); \ + for (i = 0; i < pt->pt_argc; i++) { \ + if (i > 0) { \ + ga_concat(&ga, (char_u *)", "); \ + } \ + ga_concat(&ga, encode_tv2string(&pt->pt_argv[i], NULL)); \ + } \ + ga_concat(&ga, (char_u *)"]"); \ + } \ + if (pt != NULL && pt->pt_dict != NULL) { \ + typval_T dtv; \ + ga_concat(&ga, (char_u *)", "); \ + dtv.v_type = VAR_DICT; \ + dtv.vval.v_dict = pt->pt_dict; \ + ga_concat(&ga, encode_tv2string(&dtv, NULL)); \ + } \ + ga_concat(&ga, (char_u *)")"); \ } while (0) #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 2b6b56c358..b5909910c7 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -155,3 +155,89 @@ func Test_partial_exists() 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 causes 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 causes 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 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 Cb = function('MyDictFunc', ["foo", "bar"], dict) + call assert_equal('MyDictFunc', get(Cb, 'func')) + call assert_equal(["foo", "bar"], get(Cb, 'args')) + call assert_equal(dict, get(Cb, 'dict')) + call assert_fails('call get(Cb, "xxx")', 'E475:') +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 442598c643..6bf99ca410 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -834,7 +834,7 @@ static int included_patches[] = { // 1611 NA // 1610 NA // 1609 NA - // 1608, + 1608, 1607, 1606, 1605, -- cgit From 1945013eb7d6c37aa58486ceb74e04c79bb4a7b9 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 15:19:26 -0700 Subject: vim-patch:7.4.1638 Problem: When binding a function to a dict the reference count is wrong. Solution: Decrement dict reference count, only reference the function when actually making a copy. (Ken Takata) https://github.com/vim/vim/commit/e4eb6ff089e79e659acf33c17dd0fda7177de526 --- src/nvim/eval.c | 10 +++++++--- src/nvim/version.c | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 50bcfe4e2a..8ca70222b1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9590,6 +9590,7 @@ static void partial_free(partial_T *pt) 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); @@ -18385,14 +18386,18 @@ handle_subscript ( pt->pt_dict = selfdict; selfdict = NULL; if (rettv->v_type == VAR_FUNC) { - // just a function: use selfdict + // 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: use selfdict and copy args + // 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); @@ -18408,7 +18413,6 @@ handle_subscript ( } partial_unref(ret_pt); } - func_ref(pt->pt_name); rettv->v_type = VAR_PARTIAL; rettv->vval.v_partial = pt; } diff --git a/src/nvim/version.c b/src/nvim/version.c index 6bf99ca410..14042a8d38 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -804,7 +804,7 @@ static int included_patches[] = { 1641, 1640, // 1639, - // 1638, + 1638, // 1637 NA // 1636 NA // 1635 NA -- cgit From 9d91218213b4a4a8cdb64a35e19a3a7c68dc99d0 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 27 Oct 2016 15:30:23 -0700 Subject: vim-patch:7.4.1639 Problem: Invoking garbage collection may cause a double free. Solution: Don't free the dict in a partial when recursive is FALSE. https://github.com/vim/vim/commit/5f436fcf9960c95702820d5ac1b8b612995f6c04 --- src/nvim/eval.c | 83 ++++++++++++++++++++++++++++++++++-------------------- src/nvim/version.c | 2 +- 2 files changed, 53 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8ca70222b1..68334a552c 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] @@ -6024,10 +6026,19 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, FUNC_ATTR_WARN_UNUSED_RESULT { bool abort = false; + dict_T *dd; switch (tv->v_type) { + case VAR_PARTIAL: case VAR_DICT: { - dict_T *dd = tv->vval.v_dict; + if (tv->v_type == VAR_DICT) { + dd = tv->vval.v_dict; + } else if (tv->vval.v_partial != NULL) { + dd = tv->vval.v_partial->pt_dict; + } else { + dd = NULL; + } + if (dd != NULL && dd->dv_copyID != copyID) { // Didn't see this dict yet. dd->dv_copyID = copyID; @@ -6069,7 +6080,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, } case VAR_FUNC: - case VAR_PARTIAL: case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6127,6 +6137,31 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } +static void partial_free(partial_T *pt, bool free_dict) +{ + int i; + + for (i = 0; i < pt->pt_argc; i++) { + clear_tv(&pt->pt_argv[i]); + } + xfree(pt->pt_argv); + if (free_dict) { + 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, true); + } +} + /* * Allocate an empty header for a dictionary. */ @@ -6229,8 +6264,18 @@ dict_free ( 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); + && di->di_tv.v_type != VAR_DICT)) { + if (!recurse && di->di_tv.v_type == VAR_PARTIAL) { + partial_T *pt = di->di_tv.vval.v_partial; + + // We unref the partial but not the dict it refers to. + if (pt != NULL && --pt->pt_refcount == 0) { + partial_free(pt, false); + } + } else { + clear_tv(&di->di_tv); + } + } xfree(di); --todo; } @@ -9582,29 +9627,6 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -static void partial_free(partial_T *pt) -{ - int i; - - for (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); - } -} - /// "garbagecollect()" function static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -18505,7 +18527,6 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ do { \ - partial_unref(partial); \ tv->v_lock = VAR_UNLOCKED; \ } while (0) diff --git a/src/nvim/version.c b/src/nvim/version.c index 14042a8d38..9efb8848cc 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -803,7 +803,7 @@ static int included_patches[] = { 1642, 1641, 1640, - // 1639, + 1639, 1638, // 1637 NA // 1636 NA -- cgit From 04f328f1ae7151e559b3c39cb2fce6575f2a3ebb Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Tue, 6 Dec 2016 22:44:21 -0700 Subject: vim-patch:7.4.1644 Mark as NA --- src/nvim/version.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 9efb8848cc..197c12367c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -797,8 +797,8 @@ static int included_patches[] = { 1648, 1647, // 1646 NA - // 1645, - // 1644, + 1645, + // 1644 NA 1643, 1642, 1641, -- cgit From c6bc1e7babf121406e1f8acda6381f4582dc0009 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 01:13:42 -0700 Subject: vim-patch:7.4.1645 Problem: When a dict contains a partial it can't be redefined as a function. (Nikolai Pavlov) Solution: Remove the partial when overwriting with a function. https://github.com/vim/vim/commit/c5fbe8af4cd80789f831b78aa44ff0b238138769 --- src/nvim/eval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 68334a552c..b570001131 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19911,7 +19911,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]))) -- cgit From 0645787741252ba7ad980a47af8db5d37685beae Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 12:24:03 -0700 Subject: vim-patch:7.4.1715 Problem: Double free when a partial is in a cycle with a list or dict. (Nikolai Pavlov) Solution: Do not free a nested list or dict used by the partial. https://github.com/vim/vim/commit/ddecc25947dbdd689d5bcaed32f298a08abdd497 --- src/nvim/eval.c | 107 ++++++++++++++++++++++++++++++++--------------------- src/nvim/version.c | 2 +- 2 files changed, 65 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b570001131..b7d5d8bcab 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4894,10 +4894,52 @@ 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, bool recursive) +{ + int i; + + for (i = 0; i < pt->pt_argc; i++) { + typval_T *tv = &pt->pt_argv[i]; + + if (recursive || (tv->v_type != VAR_DICT && tv->v_type != VAR_LIST)) { + clear_tv(&pt->pt_argv[i]); + } + } + xfree(pt->pt_argv); + if (recursive) { + 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, true); + } +} + +/// Like clear_tv(), but do not free lists or dictionaries. +/// This is when called via free_unref_items(). +static void clear_tv_no_recurse(typval_T *tv) { + if (tv->v_type == VAR_PARTIAL) { + partial_T *pt = tv->vval.v_partial; + + // We unref the partial but not the dict or any list it refers to + if (pt != NULL && --pt->pt_refcount == 0) { + partial_free(pt, false); + } + } else if (tv->v_type != VAR_LIST && tv->v_type != VAR_DICT) { + clear_tv(tv); + } +} + +/// 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; @@ -5009,9 +5051,11 @@ list_free ( for (item = l->lv_first; item != NULL; item = l->lv_first) { /* 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)) + if (recurse) { clear_tv(&item->li_tv); + } else { + clear_tv_no_recurse(&item->li_tv); + } xfree(item); } xfree(l); @@ -6055,6 +6099,16 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_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 = set_ref_in_item(&pt->pt_argv[i], copyID, ht_stack, list_stack); + } + } + } break; } @@ -6137,31 +6191,6 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -static void partial_free(partial_T *pt, bool free_dict) -{ - int i; - - for (i = 0; i < pt->pt_argc; i++) { - clear_tv(&pt->pt_argv[i]); - } - xfree(pt->pt_argv); - if (free_dict) { - 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, true); - } -} - /* * Allocate an empty header for a dictionary. */ @@ -6263,18 +6292,10 @@ 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)) { - if (!recurse && di->di_tv.v_type == VAR_PARTIAL) { - partial_T *pt = di->di_tv.vval.v_partial; - - // We unref the partial but not the dict it refers to. - if (pt != NULL && --pt->pt_refcount == 0) { - partial_free(pt, false); - } - } else { - clear_tv(&di->di_tv); - } + if (recurse) { + clear_tv(&di->di_tv); + } else { + clear_tv_no_recurse(&di->di_tv); } xfree(di); --todo; diff --git a/src/nvim/version.c b/src/nvim/version.c index 197c12367c..ac45e2caad 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -727,7 +727,7 @@ static int included_patches[] = { // 1718, // 1717 NA 1716, - // 1715, + 1715, 1714, // 1713 NA 1712, -- cgit From e97e24c77e70ec8bdaca51b0b477ca0c38ea35e0 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 26 Nov 2016 16:00:24 -0700 Subject: vim-patch:7.4.1718 Problem: Coverity: not using return value of set_ref_in_item(). Solution: Use the return value. https://github.com/vim/vim/commit/d56374e25df0b317b01423a01f158157faa647fa --- src/nvim/eval.c | 3 ++- src/nvim/version.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b7d5d8bcab..8d5578fb50 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6105,7 +6105,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, if (pt != NULL) { for (i = 0; i < pt->pt_argc; i++) { - abort = set_ref_in_item(&pt->pt_argv[i], copyID, ht_stack, list_stack); + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); } } } diff --git a/src/nvim/version.c b/src/nvim/version.c index ac45e2caad..00e2bfac91 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -723,8 +723,8 @@ static int included_patches[] = { // 1722 NA // 1721 NA // 1720, - // 1719, - // 1718, + 1719, + 1718, // 1717 NA 1716, 1715, -- cgit From 25438f149fda66375ed54a735e4477f3f4d87338 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sun, 30 Oct 2016 15:10:11 -0700 Subject: vim-patch:7.4.1719 Problem: Leaking memory when there is a cycle involving a job and a partial. Solution: Add a copyID to job and channel. Set references in items referred by them. Go through all jobs and channels to find unreferenced items. Also, decrement reference counts when garbage collecting. https://github.com/vim/vim/commit/107e1eef1df3b786ad3ad49fbdb9e058649303b5 --- src/nvim/api/private/helpers.c | 6 +- src/nvim/eval.c | 234 ++++++++++++++++++++------------------ src/nvim/globals.h | 2 + src/nvim/tag.c | 2 +- src/nvim/testdir/test_partial.vim | 19 +++- src/nvim/testdir/test_timers.vim | 13 +++ 6 files changed, 162 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 0acdff3232..b004cfc7a1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -657,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; } @@ -681,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; } @@ -690,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/eval.c b/src/nvim/eval.c index 8d5578fb50..2888ddc82d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4894,21 +4894,15 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -static void partial_free(partial_T *pt, bool recursive) +static void partial_free(partial_T *pt) { int i; for (i = 0; i < pt->pt_argc; i++) { - typval_T *tv = &pt->pt_argv[i]; - - if (recursive || (tv->v_type != VAR_DICT && tv->v_type != VAR_LIST)) { - clear_tv(&pt->pt_argv[i]); - } + clear_tv(&pt->pt_argv[i]); } xfree(pt->pt_argv); - if (recursive) { - dict_unref(pt->pt_dict); - } + dict_unref(pt->pt_dict); func_unref(pt->pt_name); xfree(pt->pt_name); xfree(pt); @@ -4919,22 +4913,7 @@ static void partial_free(partial_T *pt, bool recursive) void partial_unref(partial_T *pt) { if (pt != NULL && --pt->pt_refcount <= 0) { - partial_free(pt, true); - } -} - -/// Like clear_tv(), but do not free lists or dictionaries. -/// This is when called via free_unref_items(). -static void clear_tv_no_recurse(typval_T *tv) { - if (tv->v_type == VAR_PARTIAL) { - partial_T *pt = tv->vval.v_partial; - - // We unref the partial but not the dict or any list it refers to - if (pt != NULL && --pt->pt_refcount == 0) { - partial_free(pt, false); - } - } else if (tv->v_type != VAR_LIST && tv->v_type != VAR_DICT) { - clear_tv(tv); + partial_free(pt); } } @@ -4973,8 +4952,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; } @@ -5018,49 +4998,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) { - clear_tv(&item->li_tv); - } else { - clear_tv_no_recurse(&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. @@ -5936,42 +5915,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; } @@ -6070,18 +6070,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, FUNC_ATTR_WARN_UNUSED_RESULT { bool abort = false; - dict_T *dd; switch (tv->v_type) { - case VAR_PARTIAL: case VAR_DICT: { - if (tv->v_type == VAR_DICT) { - dd = tv->vval.v_dict; - } else if (tv->vval.v_partial != NULL) { - dd = tv->vval.v_partial->pt_dict; - } else { - dd = NULL; - } + dict_T *dd = tv->vval.v_dict; if (dd != NULL && dd->dv_copyID != copyID) { // Didn't see this dict yet. @@ -6134,6 +6126,27 @@ 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; + int i; + + // 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 (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: @@ -6257,31 +6270,19 @@ 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); @@ -6293,11 +6294,7 @@ dict_free ( * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); - if (recurse) { - clear_tv(&di->di_tv); - } else { - clear_tv_no_recurse(&di->di_tv); - } + clear_tv(&di->di_tv); xfree(di); --todo; } @@ -6311,9 +6308,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. @@ -6720,8 +6737,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; } @@ -18547,7 +18565,7 @@ void free_tv(typval_T *varp) tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ do { \ tv->v_lock = VAR_UNLOCKED; \ } while (0) diff --git a/src/nvim/globals.h b/src/nvim/globals.h index acdda9b657..85ec305778 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 int 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/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_partial.vim b/src/nvim/testdir/test_partial.vim index b5909910c7..7562c7fd2a 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -193,7 +193,7 @@ func Test_redefine_dict_func() endtry endfunc -" This causes double free on exit if EXITFREE is defined. +" This caused double free on exit if EXITFREE is defined. func Test_cyclic_list_arg() let l = [] let Pt = function('string', [l]) @@ -202,7 +202,7 @@ func Test_cyclic_list_arg() unlet Pt endfunc -" This causes double free on exit if EXITFREE is defined. +" This caused double free on exit if EXITFREE is defined. func Test_cyclic_dict_arg() let d = {} let Pt = function('string', [d]) @@ -211,6 +211,21 @@ func Test_cyclic_dict_arg() 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() diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 9f58a35909..abf4e43ec3 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -30,3 +30,16 @@ 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 +" vim: ts=2 sw=0 et -- cgit From eb337c9949d679eeb9f0995963c639fdf7772fd4 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 26 Nov 2016 16:09:40 -0700 Subject: vim-patch:7.4.1720 Mark as NA --- src/nvim/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 00e2bfac91..f091b12810 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -722,7 +722,7 @@ static int included_patches[] = { 1723, // 1722 NA // 1721 NA - // 1720, + // 1720 NA 1719, 1718, // 1717 NA -- cgit From c82dc7a6fd4987fee72320133579b008815b28d1 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 12:38:36 -0700 Subject: vim-patch:7.4.1836 Problem: When using a partial on a dictionary it always gets bound to that dictionary. Solution: Make a difference between binding a function to a dictionary explicitly or automatically. https://github.com/vim/vim/commit/1d429610bf9e99a6252be8abbc910d6667e4d1da --- src/nvim/eval.c | 30 ++++++++++++++++++++---------- src/nvim/eval_defs.h | 2 ++ src/nvim/version.c | 3 +-- 3 files changed, 23 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2888ddc82d..14e6003ca0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7099,12 +7099,11 @@ call_func( *doesrange = FALSE; if (partial != NULL) { - if (partial->pt_dict != NULL) { - // When the function has a partial with a dict and there is a dict - // argument, use the dict argument. That is backwards compatible. - if (selfdict_in == NULL) { - selfdict = partial->pt_dict; - } + // 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) { @@ -9643,10 +9642,14 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) // For "function(dict.func, [], dict)" and "func" is a partial // use "dict". That is backwards compatible. if (dict_idx > 0) { - pt->pt_dict = argvars[dict_idx].vval.v_dict; - (pt->pt_dict->dv_refcount)++; + // 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)++; } @@ -18424,8 +18427,14 @@ handle_subscript ( } } - if ((rettv->v_type == VAR_FUNC || rettv->v_type == VAR_PARTIAL) - && selfdict != NULL) { + // Turn "dict.Func" into a partial for "Func" bound to "dict". + // Don't do this when "Func" is already a partial that was bound + // explicitly (pt_auto is false). + if (self != NULL + && (rettv->v_type == VAR_FUNC + || (rettv->v_type == VAR_PARTIAL + && (rettv->vval.v_partial->pt_auto + || rettv->vval.v_partial->pt_dict == NULL)))) { char_u *fname = rettv->v_type == VAR_FUNC ? rettv->vval.v_string : rettv->vval.v_partial->pt_name; char_u *tofree = NULL; @@ -18446,6 +18455,7 @@ handle_subscript ( if (pt != NULL) { pt->pt_refcount = 1; pt->pt_dict = selfdict; + pt->pt_auto = true; selfdict = NULL; if (rettv->v_type == VAR_FUNC) { // Just a function: Take over the function name and use diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index a33ac10e61..74d0782356 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -150,6 +150,8 @@ struct dictvar_S { 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". diff --git a/src/nvim/version.c b/src/nvim/version.c index f091b12810..88c0185f57 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -604,9 +604,8 @@ static int included_patches[] = { // 1839, // 1838, // 1837, - // 1836, + 1836, 1835, - // 1834, 1833, 1832, 1831, -- cgit From 02c58d8a07a6d7ed450c8c7146c93f4dca665ba2 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 12:49:28 -0700 Subject: vim-patch:7.4.1839 Problem: Cannot get the items stored in a partial. Solution: Support using get() on a partial. https://github.com/vim/vim/commit/2bbf8eff6fab16d86e7bcfc0da1962d31bec7891 --- src/nvim/eval.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 14e6003ca0..be1eb3ef1c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9706,8 +9706,40 @@ 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) { + partial_T *pt = argvars[0].vval.v_partial; + + if (pt != NULL) { + char_u *what = get_tv_string(&argvars[1]); + + if (STRCMP(what, "func") == 0) { + rettv->v_type = 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) @@ -18430,7 +18462,7 @@ handle_subscript ( // Turn "dict.Func" into a partial for "Func" bound to "dict". // Don't do this when "Func" is already a partial that was bound // explicitly (pt_auto is false). - if (self != NULL + if (selfdict != NULL && (rettv->v_type == VAR_FUNC || (rettv->v_type == VAR_PARTIAL && (rettv->vval.v_partial->pt_auto -- cgit From c52856af2c5f3b12b89671d91c35689fb2f84a70 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 28 Oct 2016 22:58:11 -0700 Subject: vim-patch:7.4.1842 Problem: get() works for Partial but not for Funcref. Solution: Accept Funcref. Also return the function itself. (Nikolai Pavlov) https://github.com/vim/vim/commit/03e19a04ac2ca55643663b97b6ab94043233dcbd --- src/nvim/eval.c | 18 ++++++++++++++---- src/nvim/testdir/test_partial.vim | 15 ++++++++++++--- src/nvim/version.c | 2 +- 3 files changed, 27 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index be1eb3ef1c..2139d651cd 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9706,14 +9706,24 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (di != NULL) tv = &di->di_tv; } - } else if (argvars[0].v_type == VAR_PARTIAL) { - partial_T *pt = argvars[0].vval.v_partial; + } 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) { - rettv->v_type = VAR_STRING; + 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); } diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 7562c7fd2a..f97d283022 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -250,9 +250,18 @@ endfunc func Test_get_partial_items() let dict = {'name': 'hello'} - let Cb = function('MyDictFunc', ["foo", "bar"], dict) - call assert_equal('MyDictFunc', get(Cb, 'func')) - call assert_equal(["foo", "bar"], get(Cb, 'args')) + 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 diff --git a/src/nvim/version.c b/src/nvim/version.c index 88c0185f57..e391c61c23 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -598,7 +598,7 @@ static int included_patches[] = { // 1845 NA // 1844, // 1843 NA - // 1842, + 1842, // 1841, 1840, // 1839, -- cgit From 537cee4883a00bc3dd8ac3524c8f704a372bdcc8 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 2 Nov 2016 22:11:25 -0700 Subject: vim-patch:7.4.1860 Problem: Using a partial for timer_start() may cause a crash. Solution: Set the copyID in timer objects. (Ozaki Kiichi) https://github.com/vim/vim/commit/e3188e261569ae512fb1ae2653b57fdd9e259ca3 --- src/nvim/testdir/test_timers.vim | 31 ++++++++++++++++++++----------- src/nvim/version.c | 2 +- 2 files changed, 21 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index abf4e43ec3..136f32a80e 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') @@ -31,15 +35,20 @@ func Test_repeat_many() 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_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/version.c b/src/nvim/version.c index e391c61c23..296952e45c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -580,7 +580,7 @@ static int included_patches[] = { // 1863 NA // 1862, // 1861, - // 1860 NA + 1860, // 1859 NA // 1858 NA // 1857 NA -- cgit From b42347da45933a2d1ed54e9ca65db37f50d3e8be Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Tue, 6 Dec 2016 22:47:40 -0700 Subject: vim-patch:7.4.1862 Mark as NA --- src/nvim/testdir/test_viml.vim | 80 ++++++++++++++++++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 2 files changed, 81 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_viml.vim b/src/nvim/testdir/test_viml.vim index 2f9a72d618..a2997b6d4d 100644 --- a/src/nvim/testdir/test_viml.vim +++ b/src/nvim/testdir/test_viml.vim @@ -984,6 +984,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 diff --git a/src/nvim/version.c b/src/nvim/version.c index 296952e45c..f42000c6c0 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -578,7 +578,7 @@ static int included_patches[] = { // 1865 NA // 1864 NA // 1863 NA - // 1862, + // 1862 NA // 1861, 1860, // 1859 NA -- cgit From 6c5dd6827f5d1347cdf90f8a2f76d6a732e93931 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 29 Oct 2016 12:02:58 -0700 Subject: vim-patch:7.4.1875 Problem: Comparing functions and partials doesn't work well. Solution: Add tests. (Nikolai Pavlov) Compare the dict and arguments in the partial. https://github.com/vim/vim/commit/8e759ba8651428995b338b66c615367259f79766 --- src/nvim/eval.c | 105 ++++++++++++++++++++++++++++++++------ src/nvim/testdir/test_partial.vim | 63 +++++++++++++++++++++++ src/nvim/version.c | 2 +- 3 files changed, 153 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2139d651cd..afc2e36704 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3771,7 +3771,26 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) clear_tv(&var2); return FAIL; } - n1 = tv_equal(rettv, &var2, false, false); + 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 { + n1 = tv_equal(rettv, &var2, ic, false); + } if (type == TYPE_NEQUAL) { n1 = !n1; } @@ -5151,6 +5170,61 @@ dict_equal ( static int tv_equal_recurse_limit; +static int func_equal( + typval_T *tv1, + typval_T *tv2, + int ic // ignore case +) { + char_u *s1, *s2; + dict_T *d1, *d2; + int a1, a2; + int i; + + // 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 (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 @@ -5169,21 +5243,6 @@ tv_equal ( static int recursive_cnt = 0; /* catch recursive loops */ int r; - // 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))) { - s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string - : tv1->vval.v_partial->pt_name; - s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string - : tv2->vval.v_partial->pt_name; - return (s1 != NULL && s2 != NULL && STRCMP(s1, s2) == 0); - } - 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. @@ -5197,6 +5256,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; diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index f97d283022..3a6e162453 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -265,3 +265,66 @@ func Test_get_partial_items() 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/version.c b/src/nvim/version.c index f42000c6c0..4d17832638 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 -- cgit From 2c4e92abea2244e033be0c7534685485cfb5ddc9 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 3 Nov 2016 12:09:22 -0700 Subject: vim-patch:7.4.1731 Mark as NA Leave a note in vim_diff.txt about it. --- src/nvim/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 4d17832638..7de0b508a3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -710,7 +710,7 @@ static int included_patches[] = { 1734, // 1733 NA 1732, - // 1731, + // 1731 NA 1730, // 1729 NA 1728, -- cgit From a21c687661eac61702fe492264a3c9036bc62f41 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 29 Oct 2016 14:55:53 -0700 Subject: Fixes. --- src/nvim/eval.c | 62 +++++++++++++++++-------------------------- src/nvim/eval/encode.c | 56 +++++++++++++++++++++++++++----------- src/nvim/eval/typval_encode.h | 2 +- src/nvim/eval_defs.h | 2 +- src/nvim/globals.h | 2 +- 5 files changed, 68 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index afc2e36704..e764117f1a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2759,7 +2759,7 @@ 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; @@ -4915,9 +4915,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) static void partial_free(partial_T *pt) { - int i; - - for (i = 0; i < pt->pt_argc; i++) { + for (int i = 0; i < pt->pt_argc; i++) { clear_tv(&pt->pt_argv[i]); } xfree(pt->pt_argv); @@ -5170,15 +5168,14 @@ dict_equal ( static int tv_equal_recurse_limit; -static int func_equal( +static bool func_equal( typval_T *tv1, typval_T *tv2, - int ic // ignore case + bool ic // ignore case ) { char_u *s1, *s2; dict_T *d1, *d2; int a1, a2; - int i; // empty and NULL function name considered the same s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string @@ -5216,7 +5213,7 @@ static int func_equal( if (a1 != a2) { return false; } - for (i = 0; i < a1; i++) { + 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; @@ -6147,7 +6144,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, switch (tv->v_type) { case VAR_DICT: { dict_T *dd = tv->vval.v_dict; - if (dd != NULL && dd->dv_copyID != copyID) { // Didn't see this dict yet. dd->dv_copyID = copyID; @@ -6201,7 +6197,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, case VAR_PARTIAL: { partial_T *pt = tv->vval.v_partial; - int i; // A partial does not have a copyID, because it cannot contain itself. if (pt != NULL) { @@ -6213,7 +6208,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, abort = abort || set_ref_in_item(&dtv, copyID, ht_stack, list_stack); } - for (i = 0; i < pt->pt_argc; i++) { + for (int i = 0; i < pt->pt_argc; i++) { abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, ht_stack, list_stack); } @@ -6356,7 +6351,6 @@ static void dict_free_contents(dict_T *d) { dictitem_T *di; - /* Lock the hashtab, we don't want it to resize while freeing items. */ hash_lock(&d->dv_hashtab); assert(d->dv_hashtab.ht_locked > 0); @@ -6969,7 +6963,7 @@ static VimLFuncDef *find_internal_func(const char *const name) /// "partialp". static char_u *deref_func_name( char_u *name, int *lenp, - partial_T **partialp, int no_autoload + partial_T **partialp, bool no_autoload ) { dictitem_T *v; @@ -7102,17 +7096,16 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { if (current_SID <= 0) { *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); } } - if (i + STRLEN(name + llen) < FLEN_FIXED) { STRCPY(fname_buf + i, name + llen); fname = fname_buf; } else { - fname = xmalloc((unsigned)(i + STRLEN(name + llen) + 1)); + fname = xmalloc(i + STRLEN(name + llen) + 1); if (fname == NULL) { *error = ERROR_OTHER; } else { @@ -7142,14 +7135,14 @@ call_func( linenr_T firstline, // first line of range linenr_T lastline, // last line of range int *doesrange, // return: function handled range - int evaluate, + 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; + ufunc_T *fp; char_u fname_buf[FLEN_FIXED + 1]; char_u *tofree = NULL; char_u *fname; @@ -7169,7 +7162,7 @@ call_func( 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 @@ -7180,12 +7173,10 @@ call_func( selfdict = partial->pt_dict; } if (error == ERROR_NONE && partial->pt_argc > 0) { - int i; - for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { copy_tv(&partial->pt_argv[argv_clear], &argv[argv_clear]); } - for (i = 0; i < argcount_in; i++) { + for (int i = 0; i < argcount_in; i++) { argv[i + argv_clear] = argvars_in[i]; } argvars = argv; @@ -8133,7 +8124,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], partial, selfdict, rettv); + func_call(func, &argvars[1], partial, selfdict, rettv); } /* @@ -9611,7 +9602,6 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) use_string = true; } - s = get_tv_string(&argvars[0]); if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) { EMSG2(_(e_invarg2), s); } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL @@ -9622,7 +9612,6 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) int dict_idx = 0; int arg_idx = 0; list_T *list = NULL; - if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { char sid_buf[25]; int off = *s == 's' ? 2 : 5; @@ -9633,7 +9622,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) // printable text. snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", (int64_t)current_SID); - name = xmalloc((int)(STRLEN(sid_buf) + STRLEN(s + off) + 1)); + name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); if (name != NULL) { STRCPY(name, sid_buf); STRCAT(name, s + off); @@ -15570,11 +15559,10 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) si2 = (sortItem_T *)s2; if (partial == NULL) { - func_name = sortinfo->item_compare_func; + func_name = sortinfo->item_compare_func; } else { - func_name = partial->pt_name; + 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]); @@ -18573,8 +18561,7 @@ handle_subscript ( pt->pt_auto = true; selfdict = NULL; if (rettv->v_type == VAR_FUNC) { - // Just a function: Take over the function name and use - // selfdict. + // 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; @@ -18692,6 +18679,8 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ do { \ + partial_unref(pt); \ + pt = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) @@ -19020,11 +19009,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; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 65570a6f30..51393e5337 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -315,34 +315,58 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, ga_append(gap, ')'); \ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ do { \ - partial_T *pt = tv->vval.v_partial; \ - garray_T ga; \ int i; \ - ga_init(&ga, 1, 100); \ - ga_concat(&ga, (char_u *)"function("); \ + ga_concat(gap, "function("); \ if (&pt->pt_name != NULL) { \ - TYPVAL_ENCODE_CONV_STRING((char *)pt->pt_name, sizeof(pt->pt_name)); \ + 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 != NULL && pt->pt_argc > 0) { \ - ga_concat(&ga, (char_u *)", ["); \ + if (pt->pt_argc > 0) { \ + ga_concat(gap, ", ["); \ for (i = 0; i < pt->pt_argc; i++) { \ if (i > 0) { \ - ga_concat(&ga, (char_u *)", "); \ + ga_concat(gap, ", "); \ } \ - ga_concat(&ga, encode_tv2string(&pt->pt_argv[i], NULL)); \ + char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \ + ga_concat(gap, tofree); \ + xfree(tofree); \ } \ - ga_concat(&ga, (char_u *)"]"); \ + ga_append(gap, ']'); \ } \ - if (pt != NULL && pt->pt_dict != NULL) { \ + if (pt->pt_dict != NULL) { \ typval_T dtv; \ - ga_concat(&ga, (char_u *)", "); \ + ga_concat(gap, ", "); \ dtv.v_type = VAR_DICT; \ dtv.vval.v_dict = pt->pt_dict; \ - ga_concat(&ga, encode_tv2string(&dtv, NULL)); \ + char *tofree = encode_tv2string(&dtv, NULL); \ + ga_concat(gap, tofree); \ + xfree(tofree); \ } \ - ga_concat(&ga, (char_u *)")"); \ + ga_append(gap, ')'); \ } while (0) #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ @@ -692,7 +716,7 @@ static inline int convert_to_json_string(garray_T *const gap, mpstack, objname) #undef TYPVAL_ENCODE_CONV_PARTIAL -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ +#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ return conv_error(_("E474: Error while dumping %s, %s: " \ "attempt to dump partial"), \ mpstack, objname) diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index ce3bb0ee08..b79158b30c 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -72,7 +72,7 @@ /// @def TYPVAL_ENCODE_CONV_PARTIAL /// @brief Macros used to convert a partial /// -/// @param partial Partial name. +/// @param pt Partial name. /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 74d0782356..f478d19ca1 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -37,12 +37,12 @@ typedef enum { VAR_NUMBER, ///< Number, .v_number is used. VAR_STRING, ///< String, .v_string is used. VAR_FUNC, ///< Function reference, .v_string is used as function name. - VAR_PARTIAL, ///< Partial, .v_partial is used. 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 diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 85ec305778..f7edeed933 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1235,7 +1235,7 @@ EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */ EXTERN int ignored; EXTERN char *ignoredp; -EXTERN int in_free_unref_items INIT(= false); +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); -- cgit From 0f681c80e1e9c9060394365dae12f8ebd5736176 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 1 Nov 2016 10:54:32 +0100 Subject: Make partials work with jobs, timers, and dictwatchers. --- src/nvim/eval.c | 481 ++++++++++++++++++++++++--------------- src/nvim/testdir/test_timers.vim | 1 + 2 files changed, 295 insertions(+), 187 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e764117f1a..043e799b4e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -413,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; @@ -424,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 @@ -440,7 +454,7 @@ typedef struct dict_watcher { typedef struct { TerminalJobData *data; - ufunc_T *callback; + Callback *callback; const char *type; list_T *received; int status; @@ -453,7 +467,7 @@ typedef struct { int refcount; long timeout; bool stopped; - ufunc_T *callback; + Callback callback; } timer_T; typedef void (*FunPtr)(void); @@ -5909,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); }) } @@ -6159,6 +6183,13 @@ 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; @@ -6639,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, 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. @@ -8555,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); } @@ -8600,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; } @@ -8612,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; @@ -12064,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; @@ -12096,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) { @@ -12114,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); @@ -14325,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); } @@ -16904,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) { @@ -16928,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"); @@ -16974,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) { @@ -16998,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; @@ -17015,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); @@ -17059,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) { @@ -17096,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); } @@ -18531,70 +18655,76 @@ handle_subscript ( } // Turn "dict.Func" into a partial for "Func" bound to "dict". - // Don't do this when "Func" is already a partial that was bound - // explicitly (pt_auto is false). if (selfdict != NULL && (rettv->v_type == VAR_FUNC - || (rettv->v_type == VAR_PARTIAL - && (rettv->vval.v_partial->pt_auto - || rettv->vval.v_partial->pt_dict == NULL)))) { - 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); + || rettv->v_type == VAR_PARTIAL)) { + set_selfdict(rettv, selfdict); + } - // 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)); + dict_unref(selfdict); + return ret; +} - if (pt != NULL) { - pt->pt_refcount = 1; - pt->pt_dict = selfdict; - pt->pt_auto = true; - selfdict = NULL; - 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; +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; - // 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]); - } + // 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; + partial_unref(ret_pt); } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; } } - - dict_unref(selfdict); - return ret; } /* @@ -22444,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, @@ -22458,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) { @@ -22483,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) @@ -22492,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; } @@ -22555,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); @@ -22581,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; @@ -22622,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; @@ -22650,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); } @@ -22674,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); } @@ -22733,38 +22849,30 @@ static void on_job_event(JobEvent *ev) return; } - typval_T argv[3]; - int argc = ev->callback->uf_args.ga_len; + typval_T argv[4]; - if (argc > 0) { - argv[0].v_type = VAR_NUMBER; - argv[0].v_lock = 0; - argv[0].vval.v_number = ev->data->id; - } - - 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); } @@ -22888,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); } @@ -22921,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); } @@ -22953,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/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 136f32a80e..d92cbe6897 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -41,6 +41,7 @@ func Test_with_partial_callback() function s:meow.bite(...) let s:val += 1 endfunction + call timer_start(50, s:meow.bite) sleep 200m call assert_equal(1, s:val) -- cgit From 5e4eb18eb0242794c0b3a622f7acf0d3e6856c05 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 11 Nov 2016 13:13:55 -0700 Subject: Add some tests and cleanup. --- src/nvim/eval.c | 2 +- src/nvim/eval/encode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 043e799b4e..a205c37d6e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9664,7 +9664,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (arg_idx > 0) { if (argvars[arg_idx].v_type != VAR_LIST) { - EMSG(_("E923: Second argument of function() must be" + EMSG(_("E923: Second argument of function() must be " "a list or a dict")); xfree(name); return; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 51393e5337..61d7a38007 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -908,7 +908,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) mpstack, objname) #define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ - return conv_error(_("E951: Error while dumping %s, %s: " \ + return conv_error(_("E5004: Error while dumping %s, %s: " \ "attempt to dump partial"), \ mpstack, objname) -- cgit