# HG changeset patch # User Bram Moolenaar # Date 1564862407 -7200 # Node ID e259d11e29006f4661386ce6476b4cdb12b69552 # Parent b9a3d542a0429c60e452c892e566db4b58d3e088 patch 8.1.1803: all builtin functions are global commit https://github.com/vim/vim/commit/ac92e25a33c37ec5becbfffeccda136c73b761ac Author: Bram Moolenaar Date: Sat Aug 3 21:58:38 2019 +0200 patch 8.1.1803: all builtin functions are global Problem: All builtin functions are global. Solution: Add the method call operator ->. Implemented for a limited number of functions. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1114,6 +1114,8 @@ in any order. E.g., these are all possi expr9[expr1].name expr9.name[expr1] expr9(expr1, ...)[expr1].name + expr9->(expr1, ...)[expr1] +Evaluation is always from left to right. expr8[expr1] item of String or |List| *expr-[]* *E111* @@ -1213,6 +1215,11 @@ expr8(expr1, ...) |Funcref| function cal When expr8 is a |Funcref| type variable, invoke the function it refers to. +expr8->name([args]) method call *method* + +For global methods this is the same as: > + name(expr8 [, args]) +There can also be methods specifically for the type of "expr8". *expr9* number @@ -2877,6 +2884,8 @@ add({object}, {expr}) *add()* item. Use |extend()| to concatenate |Lists|. When {object} is a |Blob| then {expr} must be a number. Use |insert()| to add an item at another position. + Can also be used as a |method|: > + mylist->add(val1)->add(val2) and({expr}, {expr}) *and()* @@ -3512,6 +3521,8 @@ copy({expr}) Make a copy of {expr}. For changing an item changes the contents of both |Lists|. A |Dictionary| is copied in a similar way as a |List|. Also see |deepcopy()|. + Can also be used as a |method|: > + mylist->copy() cos({expr}) *cos()* Return the cosine of {expr}, measured in radians, as a |Float|. @@ -3548,6 +3559,8 @@ count({comp}, {expr} [, {ic} [, {start}] When {comp} is a string then the number of not overlapping occurrences of {expr} is returned. Zero is returned when {expr} is an empty string. + Can also be used as a |method|: > + mylist->count(val) *cscope_connection()* cscope_connection([{num} , {dbpath} [, {prepend}]]) @@ -3731,6 +3744,8 @@ empty({expr}) *empty()* For a long |List| this is much faster than comparing the length with zero. + Can also be used as a |method|: > + mylist->empty() escape({string}, {chars}) *escape()* Escape the characters in {chars} that occur in {string} with a @@ -4041,6 +4056,9 @@ extend({expr1}, {expr2} [, {expr3}]) * fails. Returns {expr1}. + Can also be used as a |method|: > + mylist->extend(otherlist) + feedkeys({string} [, {mode}]) *feedkeys()* Characters in {string} are queued for processing as if they @@ -4154,6 +4172,8 @@ filter({expr1}, {expr2}) *filter()* Funcref errors inside a function are ignored, unless it was defined with the "abort" flag. + Can also be used as a |method|: > + mylist->filter(expr2) finddir({name} [, {path} [, {count}]]) *finddir()* Find directory {name} in {path}. Supports both downwards and @@ -4416,6 +4436,8 @@ get({list}, {idx} [, {default}]) *get( Get item {idx} from |List| {list}. When this item is not available return {default}. Return zero when {default} is omitted. + Can also be used as a |method|: > + mylist->get(idx) get({blob}, {idx} [, {default}]) Get byte {idx} from |Blob| {blob}. When this byte is not available return {default}. Return -1 when {default} is @@ -5689,6 +5711,9 @@ insert({object}, {item} [, {idx}]) *in Note that when {item} is a |List| it is inserted as a single item. Use |extend()| to concatenate |Lists|. + Can also be used as a |method|: > + mylist->insert(item) + invert({expr}) *invert()* Bitwise invert. The argument is converted to a number. A List, Dict or Float argument causes an error. Example: > @@ -5740,6 +5765,8 @@ items({dict}) *items()* echo key . ': ' . value endfor +< Can also be used as a |method|: > + mydict->items() job_ functions are documented here: |job-functions-details| @@ -5755,6 +5782,9 @@ join({list} [, {sep}]) *join()* converted into a string like with |string()|. The opposite function is |split()|. + Can also be used as a |method|: > + mylist->join() + js_decode({string}) *js_decode()* This is similar to |json_decode()| with these differences: - Object key names do not have to be in quotes. @@ -5840,7 +5870,10 @@ keys({dict}) *keys()* Return a |List| with all the keys of {dict}. The |List| is in arbitrary order. Also see |items()| and |values()|. - *len()* *E701* + Can also be used as a |method|: > + mydict->keys() + +< *len()* *E701* len({expr}) The result is a Number, which is the length of the argument. When {expr} is a String or a Number the length in bytes is used, as with |strlen()|. @@ -5851,7 +5884,10 @@ len({expr}) The result is a Number, whic |Dictionary| is returned. Otherwise an error is given. - *libcall()* *E364* *E368* + Can also be used as a |method|: > + mylist->len() + +< *libcall()* *E364* *E368* libcall({libname}, {funcname}, {argument}) Call function {funcname} in the run-time library {libname} with single argument {argument}. @@ -6136,6 +6172,8 @@ map({expr1}, {expr2}) *map()* Funcref errors inside a function are ignored, unless it was defined with the "abort" flag. + Can also be used as a |method|: > + mylist->map(expr2) maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* When {dict} is omitted or zero: Return the rhs of mapping @@ -6462,7 +6500,10 @@ max({expr}) Return the maximum value of items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. - *min()* + Can also be used as a |method|: > + mylist->max() + +< *min()* min({expr}) Return the minimum value of all items in {expr}. {expr} can be a list or a dictionary. For a dictionary, it returns the minimum of all values in the dictionary. @@ -6470,7 +6511,10 @@ min({expr}) Return the minimum value of items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. - *mkdir()* *E739* + Can also be used as a |method|: > + mylist->min() + +< *mkdir()* *E739* mkdir({name} [, {path} [, {prot}]]) Create directory {name}. @@ -7154,6 +7198,9 @@ remove({list}, {idx} [, {end}]) *remo < Use |delete()| to remove a file. + Can also be used as a |method|: > + mylist->remove(idx) + remove({blob}, {idx} [, {end}]) Without {end}: Remove the byte at {idx} from |Blob| {blob} and return the byte. @@ -7189,6 +7236,8 @@ repeat({expr}, {count}) *repeat()* :let longlist = repeat(['a', 'b'], 3) < Results in ['a', 'b', 'a', 'b', 'a', 'b']. + Can also be used as a |method|: > + mylist->repeat(count) resolve({filename}) *resolve()* *E655* On MS-Windows, when {filename} is a shortcut (a .lnk file), @@ -7206,13 +7255,15 @@ resolve({filename}) *resolve()* *E65 current directory (provided the result is still a relative path name) and also keeps a trailing path separator. - *reverse()* -reverse({object}) + +reverse({object}) *reverse()* Reverse the order of items in {object} in-place. {object} can be a |List| or a |Blob|. Returns {object}. If you want an object to remain unmodified make a copy first: > :let revlist = reverse(copy(mylist)) +< Can also be used as a |method|: > + mylist->reverse() round({expr}) *round()* Round off {expr} to the nearest integral value and return it @@ -8070,7 +8121,10 @@ sort({list} [, {func} [, {dict}]]) *so on numbers, text strings will sort next to each other, in the same order as they were originally. - Also see |uniq()|. + Can also be used as a |method|: > + mylist->sort() + +< Also see |uniq()|. Example: > func MyCompare(i1, i2) @@ -8378,7 +8432,10 @@ string({expr}) Return {expr} converted t replaced by "[...]" or "{...}". Using eval() on the result will then fail. - Also see |strtrans()|. + Can also be used as a |method|: > + mylist->string() + +< Also see |strtrans()|. *strlen()* strlen({expr}) The result is a Number, which is the length of the String @@ -9000,6 +9057,9 @@ type({expr}) The result is a Number repr < To check if the v:t_ variables exist use this: > :if exists('v:t_number') +< Can also be used as a |method|: > + mylist->type() + undofile({name}) *undofile()* Return the name of the undo file that would be used for a file with name {name} when writing. This uses the 'undodir' @@ -9064,10 +9124,15 @@ uniq({list} [, {func} [, {dict}]]) *un < The default compare function uses the string representation of each item. For the use of {func} and {dict} see |sort()|. + Can also be used as a |method|: > + mylist->uniq() + values({dict}) *values()* Return a |List| with all the values of {dict}. The |List| is in arbitrary order. Also see |items()| and |keys()|. + Can also be used as a |method|: > + mydict->values() virtcol({expr}) *virtcol()* The result is a Number, which is the screen column of the file diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -4412,6 +4412,7 @@ eval6( * + in front unary plus (ignored) * trailing [] subscript in String or List * trailing .name entry in Dictionary + * trailing ->name() method call * * "arg" must point to the first non-white of the expression. * "arg" is advanced to the next non-white after the recognized expression. @@ -4690,13 +4691,12 @@ eval7( funcexe_T funcexe; // Invoke the function. - funcexe.argv_func = NULL; + vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.firstline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum; funcexe.doesrange = &len; funcexe.evaluate = evaluate; funcexe.partial = partial; - funcexe.selfdict = NULL; ret = get_func_tv(s, len, rettv, arg, &funcexe); } vim_free(s); @@ -4802,6 +4802,70 @@ eval7( } /* + * Evaluate "->method()". + * "*arg" points to the '-'. + * Returns FAIL or OK. "*arg" is advanced to after the ')'. + */ + static int +eval_method( + char_u **arg, + typval_T *rettv, + int evaluate, + int verbose) /* give error messages */ +{ + char_u *name; + long len; + funcexe_T funcexe; + int ret = OK; + typval_T base = *rettv; + + // Skip over the ->. + *arg += 2; + + // Locate the method name. + name = *arg; + for (len = 0; ASCII_ISALNUM(name[len]) || name[len] == '_'; ++len) + ; + if (len == 0) + { + if (verbose) + emsg(_("E260: Missing name after ->")); + return FAIL; + } + + // Check for the "(". Skip over white space after it. + if (name[len] != '(') + { + if (verbose) + semsg(_(e_missingparen), name); + return FAIL; + } + *arg += len; + + vim_memset(&funcexe, 0, sizeof(funcexe)); + funcexe.evaluate = evaluate; + funcexe.basetv = &base; + rettv->v_type = VAR_UNKNOWN; + ret = get_func_tv(name, len, rettv, arg, &funcexe); + + /* Clear the funcref afterwards, so that deleting it while + * evaluating the arguments is possible (see test55). */ + if (evaluate) + clear_tv(&base); + + /* Stop the expression evaluation when immediately aborting on + * error, or when an interrupt occurred or an exception was thrown + * but not caught. */ + if (aborting()) + { + if (ret == OK) + clear_tv(rettv); + ret = FAIL; + } + return ret; +} + +/* * Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". * "*arg" points to the '[' or '.'. * Returns FAIL or OK. "*arg" is advanced to after the ']'. @@ -7359,9 +7423,13 @@ check_vars(char_u *name, int len) } /* - * Handle expr[expr], expr[expr:expr] subscript and .name lookup. - * Also handle function call with Funcref variable: func(expr) - * Can all be combined: dict.func(expr)[idx]['func'](expr) + * Handle: + * - expr[expr], expr[expr:expr] subscript + * - ".name" lookup + * - function call with Funcref variable: func(expr) + * - method call: var->method() + * + * Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len() */ int handle_subscript( @@ -7378,14 +7446,15 @@ handle_subscript( // "." is ".name" lookup when we found a dict or when evaluating and // scriptversion is at least 2, where string concatenation is "..". while (ret == OK - && (**arg == '[' - || (**arg == '.' && (rettv->v_type == VAR_DICT + && (((**arg == '[' + || (**arg == '.' && (rettv->v_type == VAR_DICT || (!evaluate && (*arg)[1] != '.' && current_sctx.sc_version >= 2))) - || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC + || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC || rettv->v_type == VAR_PARTIAL))) - && !VIM_ISWHITE(*(*arg - 1))) + && !VIM_ISWHITE(*(*arg - 1))) + || (**arg == '-' && (*arg)[1] == '>'))) { if (**arg == '(') { @@ -7410,10 +7479,9 @@ handle_subscript( else s = (char_u *)""; - funcexe.argv_func = NULL; + vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.firstline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum; - funcexe.doesrange = NULL; funcexe.evaluate = evaluate; funcexe.partial = pt; funcexe.selfdict = selfdict; @@ -7436,6 +7504,14 @@ handle_subscript( dict_unref(selfdict); selfdict = NULL; } + else if (**arg == '-') + { + if (eval_method(arg, rettv, evaluate, verbose) == FAIL) + { + clear_tv(rettv); + ret = FAIL; + } + } else /* **arg == '[' || **arg == '.' */ { dict_unref(selfdict); diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -412,14 +412,16 @@ static void f_xor(typval_T *argvars, typ * Array with names and number of arguments of all internal functions * MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH! */ -static struct fst +typedef struct { char *f_name; /* function name */ char f_min_argc; /* minimal number of arguments */ char f_max_argc; /* maximal number of arguments */ void (*f_func)(typval_T *args, typval_T *rvar); /* implementation of function */ -} functions[] = +} funcentry_T; + +static funcentry_T global_functions[] = { #ifdef FEAT_FLOAT {"abs", 1, 1, f_abs}, @@ -987,6 +989,37 @@ static struct fst {"xor", 2, 2, f_xor}, }; +/* + * Methods that call the internal function with the base as the first argument. + */ +static funcentry_T base_methods[] = +{ + {"add", 1, 1, f_add}, + {"copy", 0, 0, f_copy}, + {"count", 1, 3, f_count}, + {"empty", 0, 0, f_empty}, + {"extend", 1, 2, f_extend}, + {"filter", 1, 1, f_filter}, + {"get", 1, 2, f_get}, + {"index", 1, 3, f_index}, + {"insert", 1, 2, f_insert}, + {"items", 0, 0, f_items}, + {"join", 0, 1, f_join}, + {"keys", 0, 0, f_keys}, + {"len", 0, 0, f_len}, + {"map", 1, 1, f_map}, + {"max", 0, 0, f_max}, + {"min", 0, 0, f_min}, + {"remove", 1, 2, f_remove}, + {"repeat", 1, 1, f_repeat}, + {"reverse", 0, 0, f_reverse}, + {"sort", 0, 2, f_sort}, + {"string", 0, 0, f_string}, + {"type", 0, 0, f_type}, + {"uniq", 0, 2, f_uniq}, + {"values", 0, 0, f_values}, +}; + #if defined(FEAT_CMDL_COMPL) || defined(PROTO) /* @@ -1007,11 +1040,11 @@ get_function_name(expand_T *xp, int idx) if (name != NULL) return name; } - if (++intidx < (int)(sizeof(functions) / sizeof(struct fst))) - { - STRCPY(IObuff, functions[intidx].f_name); + if (++intidx < (int)(sizeof(global_functions) / sizeof(funcentry_T))) + { + STRCPY(IObuff, global_functions[intidx].f_name); STRCAT(IObuff, "("); - if (functions[intidx].f_max_argc == 0) + if (global_functions[intidx].f_max_argc == 0) STRCAT(IObuff, ")"); return IObuff; } @@ -1043,21 +1076,25 @@ get_expr_name(expand_T *xp, int idx) #endif /* FEAT_CMDL_COMPL */ /* - * Find internal function in table above. + * Find internal function in table "functions". * Return index, or -1 if not found */ - int + static int find_internal_func( - char_u *name) /* name of the function */ + char_u *name, // name of the function + funcentry_T *functions) // functions table to use { int first = 0; - int last = (int)(sizeof(functions) / sizeof(struct fst)) - 1; + int last; int cmp; int x; - /* - * Find the function name in the table. Binary search. - */ + if (functions == global_functions) + last = (int)(sizeof(global_functions) / sizeof(funcentry_T)) - 1; + else + last = (int)(sizeof(base_methods) / sizeof(funcentry_T)) - 1; + + // Find the function name in the table. Binary search. while (first <= last) { x = first + ((unsigned)(last - first) >> 1); @@ -1073,6 +1110,12 @@ find_internal_func( } int +has_internal_func(char_u *name) +{ + return find_internal_func(name, global_functions) >= 0; +} + + int call_internal_func( char_u *name, int argcount, @@ -1081,15 +1124,47 @@ call_internal_func( { int i; - i = find_internal_func(name); + i = find_internal_func(name, global_functions); if (i < 0) return ERROR_UNKNOWN; - if (argcount < functions[i].f_min_argc) + if (argcount < global_functions[i].f_min_argc) return ERROR_TOOFEW; - if (argcount > functions[i].f_max_argc) + if (argcount > global_functions[i].f_max_argc) return ERROR_TOOMANY; argvars[argcount].v_type = VAR_UNKNOWN; - functions[i].f_func(argvars, rettv); + global_functions[i].f_func(argvars, rettv); + return ERROR_NONE; +} + +/* + * Invoke a method for base->method(). + */ + int +call_internal_method( + char_u *name, + int argcount, + typval_T *argvars, + typval_T *rettv, + typval_T *basetv) +{ + int i; + int fi; + typval_T argv[MAX_FUNC_ARGS + 1]; + + fi = find_internal_func(name, base_methods); + if (fi < 0) + return ERROR_UNKNOWN; + if (argcount < base_methods[fi].f_min_argc) + return ERROR_TOOFEW; + if (argcount > base_methods[fi].f_max_argc) + return ERROR_TOOMANY; + + argv[0] = *basetv; + for (i = 0; i < argcount; ++i) + argv[i + 1] = argvars[i]; + argv[argcount + 1].v_type = VAR_UNKNOWN; + + base_methods[fi].f_func(argv, rettv); return ERROR_NONE; } diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1592,6 +1592,7 @@ EXTERN char e_write[] INIT(= N_("E80: E EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required")); #ifdef FEAT_EVAL EXTERN char e_usingsid[] INIT(= N_("E81: Using not in a script context")); +EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); #endif #ifdef FEAT_CLIENTSERVER EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received")); diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro --- a/src/proto/evalfunc.pro +++ b/src/proto/evalfunc.pro @@ -1,8 +1,9 @@ /* evalfunc.c */ char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); -int find_internal_func(char_u *name); +int has_internal_func(char_u *name); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); +int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv); linenr_T tv_get_lnum(typval_T *argvars); buf_T *buflist_find_by_name(char_u *name, int curtab_only); buf_T *tv_get_buf(typval_T *tv, int curtab_only); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1619,6 +1619,7 @@ typedef struct { int evaluate; // actually evaluate expressions partial_T *partial; // for extra arguments dict_T *selfdict; // Dictionary for "self" + typval_T *basetv; // base for base->method() } funcexe_T; struct partial_S diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -180,6 +180,7 @@ NEW_TESTS = \ test_matchadd_conceal \ test_matchadd_conceal_utf8 \ test_memory_usage \ + test_method \ test_menu \ test_messages \ test_mksession \ @@ -373,6 +374,7 @@ NEW_TESTS_RES = \ test_marks.res \ test_matchadd_conceal.res \ test_memory_usage.res \ + test_method.res \ test_mksession.res \ test_nested_function.res \ test_netbeans.res \ diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_method.vim @@ -0,0 +1,61 @@ +" Tests for ->method() + +func Test_list() + let l = [1, 2, 3] + call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4)) + call assert_equal(l, l->copy()) + call assert_equal(1, l->count(2)) + call assert_false(l->empty()) + call assert_true([]->empty()) + call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5])) + call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2')) + call assert_equal(2, l->get(1)) + call assert_equal(1, l->index(2)) + call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) + call assert_fails('let x = l->items()', 'E715:') + call assert_equal('1 2 3', l->join()) + call assert_fails('let x = l->keys()', 'E715:') + call assert_equal(3, l->len()) + call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) + call assert_equal(3, l->max()) + call assert_equal(1, l->min()) + call assert_equal(2, [1, 2, 3]->remove(1)) + call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2)) + call assert_equal([3, 2, 1], [1, 2, 3]->reverse()) + call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort()) + call assert_equal('[1, 2, 3]', l->string()) + call assert_equal(v:t_list, l->type()) + call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) + call assert_fails('let x = l->values()', 'E715:') +endfunc + +func Test_dict() + let d = #{one: 1, two: 2, three: 3} + + call assert_equal(d, d->copy()) + call assert_equal(1, d->count(2)) + call assert_false(d->empty()) + call assert_true({}->empty()) + call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4})) + call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4')) + call assert_equal(2, d->get('two')) + call assert_fails("let x = d->index(2)", 'E897:') + call assert_fails("let x = d->insert(0)", 'E899:') + call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items()) + call assert_fails("let x = d->join()", 'E714:') + call assert_equal(['one', 'two', 'three'], d->keys()) + call assert_equal(3, d->len()) + call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1')) + call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1')) + call assert_equal(3, d->max()) + call assert_equal(1, d->min()) + call assert_equal(2, d->remove("two")) + let d.two = 2 + call assert_fails('let x = d->repeat(2)', 'E731:') + call assert_fails('let x = d->reverse()', 'E899:') + call assert_fails('let x = d->sort()', 'E686:') + call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string()) + call assert_equal(v:t_dict, d->type()) + call assert_fails('let x = d->uniq()', 'E686:') + call assert_equal([1, 2, 3], d->values()) +endfunc diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1431,10 +1431,9 @@ func_call( { funcexe_T funcexe; - funcexe.argv_func = NULL; + vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.firstline = curwin->w_cursor.lnum; funcexe.lastline = curwin->w_cursor.lnum; - funcexe.doesrange = NULL; funcexe.evaluate = TRUE; funcexe.partial = partial; funcexe.selfdict = selfdict; @@ -1555,7 +1554,10 @@ call_func( /* * User defined function. */ - if (partial != NULL && partial->pt_func != NULL) + if (funcexe->basetv != NULL) + // TODO: support User function: base->Method() + fp = NULL; + else if (partial != NULL && partial->pt_func != NULL) fp = partial->pt_func; else fp = find_func(rfname); @@ -1625,6 +1627,14 @@ call_func( } } } + else if (funcexe->basetv != NULL) + { + /* + * Find the method name in the table, call its implementation. + */ + error = call_internal_method(fname, argcount, argvars, rettv, + funcexe->basetv); + } else { /* @@ -2715,7 +2725,7 @@ eval_fname_script(char_u *p) translated_function_exists(char_u *name) { if (builtin_function(name, -1)) - return find_internal_func(name) >= 0; + return has_internal_func(name); return find_func(name) != NULL; } @@ -3084,7 +3094,7 @@ ex_call(exarg_T *eap) if (*startarg != '(') { - semsg(_("E107: Missing parentheses: %s"), eap->arg); + semsg(_(e_missingparen), eap->arg); goto end; } @@ -3120,7 +3130,7 @@ ex_call(exarg_T *eap) } arg = startarg; - funcexe.argv_func = NULL; + vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.firstline = eap->line1; funcexe.lastline = eap->line2; funcexe.doesrange = &doesrange; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -774,6 +774,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1803, +/**/ 1802, /**/ 1801,