]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.1.1803: all builtin functions are global v8.1.1803
authorBram Moolenaar <Bram@vim.org>
Sat, 3 Aug 2019 19:58:38 +0000 (21:58 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 3 Aug 2019 19:58:38 +0000 (21:58 +0200)
Problem:    All builtin functions are global.
Solution:   Add the method call operator ->.  Implemented for a limited number
            of functions.

runtime/doc/eval.txt
src/eval.c
src/evalfunc.c
src/globals.h
src/proto/evalfunc.pro
src/structs.h
src/testdir/Make_all.mak
src/testdir/test_method.vim [new file with mode: 0644]
src/userfunc.c
src/version.c

index fdddc22edcdd2018213ebc65df2ce7740958b8f2..925446a9ca59825e22e46b290d40646ca5501d3c 100644 (file)
@@ -1114,6 +1114,8 @@ in any order.  E.g., these are all possible:
        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 call
 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 Numbers and Strings this isn't
                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}]])                        *count()*
                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}])                      *extend()*
                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}])                        *insert()*
                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, which is the length of the argument.
                |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 all items in {expr}.
                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 all items in {expr}.
                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}])                           *remove()*
 <
                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()* *E655*
                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}]])                       *sort()* *E702*
                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 to a String.  If {expr} is a Number,
                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 representing the type of {expr}.
 <              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}]])                      *uniq()* *E882*
 <              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
index 6d7bd66775b742375b4bfa035a3a1bda0851170d..7875edeafe6687fbce386336fa30c8f45e04e7ed 100644 (file)
@@ -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);
@@ -4801,6 +4801,70 @@ eval7(
     return ret;
 }
 
+/*
+ * 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 '.'.
@@ -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);
index 54fc2f5489f8a251d23a393c05924cf3c2a2ffef..7213e0d19ce8fec97525ad3570766f7d5bda639b 100644 (file)
@@ -412,14 +412,16 @@ static void f_xor(typval_T *argvars, typval_T *rettv);
  * 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)))
+    if (++intidx < (int)(sizeof(global_functions) / sizeof(funcentry_T)))
     {
-       STRCPY(IObuff, functions[intidx].f_name);
+       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);
@@ -1072,6 +1109,12 @@ find_internal_func(
     return -1;
 }
 
+    int
+has_internal_func(char_u *name)
+{
+    return find_internal_func(name, global_functions) >= 0;
+}
+
     int
 call_internal_func(
        char_u      *name,
@@ -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;
 }
 
index 2d646865bbe72c62ef5f46dc5a2aefc52722cb7b..6faee3bb29dc4424d26f81761a57a1f86d4aef27 100644 (file)
@@ -1592,6 +1592,7 @@ EXTERN char e_write[]             INIT(= N_("E80: Error while writing"));
 EXTERN char e_zerocount[]      INIT(= N_("E939: Positive count required"));
 #ifdef FEAT_EVAL
 EXTERN char e_usingsid[]       INIT(= N_("E81: Using <SID> 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"));
index afd5f631eae9a6d82695282b9d8aaabd3290e55d..932193d3cb3876d9baf1bbc96655eb4865d759e9 100644 (file)
@@ -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);
index 73cacef2044692524684c30ade78060e2fa4469d..c34bbeb7ba2cc67a879df0386a73a7aa86c19786 100644 (file)
@@ -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
index a0898dc02a894762299b7a5b9cf997bb5e539752..b3abcbae0766225a6463087eabd4865f671db257 100644 (file)
@@ -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 (file)
index 0000000..e0631e6
--- /dev/null
@@ -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
index 9d2063b34bf948a83409abfee34ab27778911eb2..8e834a3e55db9edb71cc854b94d305aa915a9d89 100644 (file)
@@ -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;
index 6268b8eb6f507c1ad5dc854e8837c2fdcd45984a..5e027dd2bb0b48cbf6fda2077564768342ed49f7 100644 (file)
@@ -773,6 +773,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1803,
 /**/
     1802,
 /**/