]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0148: Vim9: can't call internal methods with objects v9.1.0148
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 3 Mar 2024 15:26:58 +0000 (16:26 +0100)
committerChristian Brabandt <cb@256bit.org>
Sun, 3 Mar 2024 15:34:51 +0000 (16:34 +0100)
Problem:  Vim9: can't call internal methods with objects
Solution: Add support for empty(), len() and string() function
          calls for objects (Yegappan Lakshmanan)

closes: #14129

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
16 files changed:
runtime/doc/builtin.txt
runtime/doc/tags
runtime/doc/todo.txt
runtime/doc/version9.txt
runtime/doc/vim9class.txt
src/errors.h
src/eval.c
src/evalfunc.c
src/proto/vim9class.pro
src/structs.h
src/testdir/test_vim9_class.vim
src/testdir/test_vim9_disassemble.vim
src/userfunc.c
src/version.c
src/vim9class.c
src/vim9expr.c

index dbad8802e19550adf3ed00d4249094ae8999a871..0d47e3653181abd89b3af5819675850f5044498e 100644 (file)
@@ -1,4 +1,4 @@
-*builtin.txt*  For Vim version 9.1.  Last change: 2024 Mar 01
+*builtin.txt*  For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2265,6 +2265,8 @@ empty({expr})                                             *empty()*
                - A |Job| is empty when it failed to start.
                - A |Channel| is empty when it is closed.
                - A |Blob| is empty when its length is zero.
+               - An |Object| is empty, when the |empty()| builtin method in
+                 the object (if present) returns true.
 
                For a long |List| this is much faster than comparing the
                length with zero.
@@ -5476,7 +5478,9 @@ len({expr})       The result is a Number, which is the length of the argument.
                When {expr} is a |Blob| the number of bytes is returned.
                When {expr} is a |Dictionary| the number of entries in the
                |Dictionary| is returned.
-               Otherwise an error is given and returns zero.
+               When {expr} is an |Object|, invokes the |len()| method in the
+               object (if present) to get the length.  Otherwise returns
+               zero.
 
                Can also be used as a |method|: >
                        mylist->len()
@@ -9587,6 +9591,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.
 
+               For an object, invokes the |string()| method to get a textual
+               representation of the object.  If the method is not present,
+               then the default representation is used.
+
                Can also be used as a |method|: >
                        mylist->string()
 
index efecedfb2a0f0cbaf3e9871bc6fd5eec84f38527..d6ed03a313a21165775191c475818bd48458a15c 100644 (file)
@@ -4518,6 +4518,8 @@ E1409     vim9class.txt   /*E1409*
 E141   message.txt     /*E141*
 E1410  vim9class.txt   /*E1410*
 E1411  vim9class.txt   /*E1411*
+E1412  vim9class.txt   /*E1412*
+E1413  vim9class.txt   /*E1413*
 E142   message.txt     /*E142*
 E143   autocmd.txt     /*E143*
 E144   various.txt     /*E144*
@@ -6183,6 +6185,7 @@ bugs      intro.txt       /*bugs*
 builtin-function-details       builtin.txt     /*builtin-function-details*
 builtin-function-list  builtin.txt     /*builtin-function-list*
 builtin-functions      builtin.txt     /*builtin-functions*
+builtin-object-methods vim9class.txt   /*builtin-object-methods*
 builtin-terms  term.txt        /*builtin-terms*
 builtin-tools  gui.txt /*builtin-tools*
 builtin.txt    builtin.txt     /*builtin.txt*
@@ -9153,9 +9156,12 @@ o_object-select  motion.txt      /*o_object-select*
 o_v    motion.txt      /*o_v*
 object vim9class.txt   /*object*
 object-const-variable  vim9class.txt   /*object-const-variable*
+object-empty() vim9class.txt   /*object-empty()*
 object-final-variable  vim9class.txt   /*object-final-variable*
+object-len()   vim9class.txt   /*object-len()*
 object-motions motion.txt      /*object-motions*
 object-select  motion.txt      /*object-select*
+object-string()        vim9class.txt   /*object-string()*
 objects        index.txt       /*objects*
 obtaining-exted        netbeans.txt    /*obtaining-exted*
 ocaml.vim      syntax.txt      /*ocaml.vim*
index 953ae47820fc84a3cf6ee38a97cdba4836b82b92..2b4a70a3a634a20530b99fd46a5d4a02867d2f3f 100644 (file)
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 9.1.  Last change: 2024 Feb 01
+*todo.txt*      For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -132,7 +132,6 @@ Further Vim9 improvements:
     Possibly issue #11981 can be fixed at the same time (has two examples).
   - Forward declaration of a class?  E.g. for Clone() function.
        Email lifepillar 2023 Mar 26
-  - object empty(), len() - can class define a method to be used for them?
   - When "Meta" is a class, is "const MetaAlias = Meta" allowed?  It should
     either work or given an error. Possibly give an error now and implement it
     later (using a typedef).  #12006
index 7947cb281ca277fe47bd08c10fab22c463fe0156..bf16d495343903bbac30517ecaf3beca01c1451c 100644 (file)
@@ -41543,6 +41543,11 @@ and is a work in progress.
 
 Support for Wayland UI.
 
+Vim9 script
+-----------
+Add support for internal builtin functions with vim9 objects, see
+|builtin-object-methods|
+
 Other improvements                             *new-other-9.2*
 ------------------
 
index ba821c1b29db42a348959e145356a91eb82908df..a00a5b787de8d4985238aa5a63c01b75aac1e011 100644 (file)
@@ -1,4 +1,4 @@
-*vim9class.txt*        For Vim version 9.1.  Last change: 2024 Jan 12
+*vim9class.txt*        For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -710,6 +710,32 @@ The initialization isn't needed, the list is empty by default.
                                                        *E1330*
 Some types cannot be used, such as "void", "null" and "v:none".
 
+Builtin Object Methods ~
+                                                       *builtin-object-methods*
+Some of the builtin functions like |empty()|, |len()| and |string()| can be
+used with an object.  An object can implement a method with the same name as
+these builtin functions to return an object-specific value.
+
+                                                       *E1412*
+The following builtin methods are supported:
+                                                       *object-empty()*
+    empty()  Invoked by the |empty()| function to check whether an object is
+            empty.  If this method is missing, then true is returned.  This
+            method should not accept any arguments and must return a boolean.
+                                                       *object-len()*
+    len()    Invoked by the |len()| function to return the length of an
+            object.  If this method is missing in the class, then an error is
+            given and zero is returned.  This method should not accept any
+            arguments and must return a number.
+                                                       *object-string()*
+    string() Invoked by the |string()| function to get a textual
+            representation of an object.  Also used by the |:echo| command
+            for an object.  If this method is missing in the class, then a
+            built-in default textual representation is used.  This method
+            should not accept any arguments and must return a string.
+
+                                                       *E1413*
+A class method cannot be used as a builtin method.
 
 Defining an interface ~
                                        *Interface* *:interface* *:endinterface*
index 65ee4e826e47f988da6d0584f1e120e3842a2475..0dbc5a571941a46b7b4890eb004f3f590b0dc994 100644 (file)
@@ -3579,8 +3579,12 @@ EXTERN char e_const_variable_not_supported_in_interface[]
        INIT(= N_("E1410: Const variable not supported in an interface"));
 EXTERN char e_missing_dot_after_object_str[]
        INIT(= N_("E1411: Missing dot after object \"%s\""));
+EXTERN char e_builtin_object_method_str_not_supported[]
+       INIT(= N_("E1412: Builtin object method \"%s\" not supported"));
+EXTERN char e_builtin_class_method_not_supported[]
+       INIT(= N_("E1413: Builtin class method not supported"));
 #endif
-// E1412 - E1499 unused (reserved for Vim9 class support)
+// E1415 - E1499 unused (reserved for Vim9 class support)
 EXTERN char e_cannot_mix_positional_and_non_positional_str[]
        INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
 EXTERN char e_fmt_arg_nr_unused_str[]
index fd44db6f7e7e76e044ce87446db358f89fc7f02a..ca5a2685fa62d5b5227721296a98bda527fa25c1 100644 (file)
@@ -6318,36 +6318,9 @@ echo_string_core(
            break;
 
        case VAR_OBJECT:
-           {
-               garray_T ga;
-               ga_init2(&ga, 1, 50);
-               ga_concat(&ga, (char_u *)"object of ");
-               object_T *obj = tv->vval.v_object;
-               class_T *cl = obj == NULL ? NULL : obj->obj_class;
-               ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
-                                                            : cl->class_name);
-               if (cl != NULL)
-               {
-                   ga_concat(&ga, (char_u *)" {");
-                   for (int i = 0; i < cl->class_obj_member_count; ++i)
-                   {
-                       if (i > 0)
-                           ga_concat(&ga, (char_u *)", ");
-                       ocmember_T *m = &cl->class_obj_members[i];
-                       ga_concat(&ga, m->ocm_name);
-                       ga_concat(&ga, (char_u *)": ");
-                       char_u *tf = NULL;
-                       ga_concat(&ga, echo_string_core(
-                                              (typval_T *)(obj + 1) + i,
-                                              &tf, numbuf, copyID, echo_style,
-                                              restore_copyID, composite_val));
-                       vim_free(tf);
-                   }
-                   ga_concat(&ga, (char_u *)"}");
-               }
-
-               *tofree = r = ga.ga_data;
-           }
+           *tofree = r = object_string(tv->vval.v_object, numbuf, copyID,
+                                       echo_style, restore_copyID,
+                                       composite_val);
            break;
 
        case VAR_FLOAT:
index b5d8c872ed06c7da49ca8925cd794ad73d24e949..5d6664c9a2a03bdd9a5f4d73db21778dac45ab65 100644 (file)
@@ -986,6 +986,7 @@ arg_len1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
            || type->tt_type == VAR_BLOB
            || type->tt_type == VAR_LIST
            || type->tt_type == VAR_DICT
+           || type->tt_type == VAR_OBJECT
            || type_any_or_unknown(type))
        return OK;
 
@@ -3981,7 +3982,7 @@ f_empty(typval_T *argvars, typval_T *rettv)
            n = argvars[0].vval.v_class != NULL;
            break;
        case VAR_OBJECT:
-           n = argvars[0].vval.v_object != NULL;
+           n = object_empty(argvars[0].vval.v_object);
            break;
 
        case VAR_BLOB:
@@ -7831,6 +7832,9 @@ f_len(typval_T *argvars, typval_T *rettv)
        case VAR_DICT:
            rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
            break;
+       case VAR_OBJECT:
+           rettv->vval.v_number = object_len(argvars[0].vval.v_object);
+           break;
        case VAR_UNKNOWN:
        case VAR_ANY:
        case VAR_VOID:
@@ -7843,7 +7847,6 @@ f_len(typval_T *argvars, typval_T *rettv)
        case VAR_CHANNEL:
        case VAR_INSTR:
        case VAR_CLASS:
-       case VAR_OBJECT:
        case VAR_TYPEALIAS:
            emsg(_(e_invalid_type_for_len));
            break;
index a746eb772902a0aca6e4eeb610e09c8822105832..1ed175e69f8a72320a371cf6220ad58e7655e4d3 100644 (file)
@@ -1,5 +1,7 @@
 /* vim9class.c */
 int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl);
+int is_valid_builtin_obj_methodname(char_u *funcname);
+ufunc_T *class_get_builtin_method(class_T *cl, class_builtin_T builtin_method, int *method_idx);
 void ex_class(exarg_T *eap);
 type_T *oc_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx);
 type_T *oc_member_type_by_idx(class_T *cl, int is_object, int member_idx);
@@ -34,6 +36,10 @@ void member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t le
 void defcompile_class(class_T *cl);
 void defcompile_classes_in_script(void);
 int is_class_name(char_u *name, typval_T *rettv);
+void protected_method_access_errmsg(char_u *method_name);
+int object_empty(object_T *obj);
+int object_len(object_T *obj);
+char_u *object_string(object_T *obj, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val);
 int class_instance_of(class_T *cl, class_T *other_cl);
 void f_instanceof(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
index df2c005e3d7334af10e2ae3192520362237ffbfd..2c6f553521945709f6e0f0113368fdaf3757f256 100644 (file)
@@ -1530,6 +1530,17 @@ typedef enum {
 #define OCMFLAG_FINAL          0x02    // "final" object/class member
 #define OCMFLAG_CONST          0x04    // "const" object/class member
 
+/*
+ * Object methods called by builtin functions (e.g. string(), empty(), etc.)
+ */
+typedef enum {
+    CLASS_BUILTIN_INVALID,
+    CLASS_BUILTIN_STRING,
+    CLASS_BUILTIN_EMPTY,
+    CLASS_BUILTIN_LEN,
+    CLASS_BUILTIN_MAX
+} class_builtin_T;
+
 /*
  * Entry for an object or class member variable.
  */
@@ -1593,6 +1604,9 @@ struct class_S
     int                class_obj_method_count_child;       // count without "extends"
     ufunc_T    **class_obj_methods;    // allocated
 
+                                       // index of builtin methods
+    int                class_builtin_methods[CLASS_BUILTIN_MAX];
+
     garray_T   class_type_list;        // used for type pointers
     type_T     class_type;             // type used for the class
     type_T     class_object_type;      // same as class_type but VAR_OBJECT
index 0bf7e9ceb60772726c6c8d6db934eb5e6b8da32c..12e3c48a3b6a06c1e6518022f8e23d92a08d6e83 100644 (file)
@@ -9659,33 +9659,6 @@ def Test_const_class_object_variable()
   v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
 enddef
 
-" Test for using double underscore prefix in a class/object method name.
-def Test_method_double_underscore_prefix()
-  # class method
-  var lines =<< trim END
-    vim9script
-    class A
-      static def __foo()
-        echo "foo"
-      enddef
-    endclass
-    defcompile
-  END
-  v9.CheckSourceFailure(lines, 'E1034: Cannot use reserved name __foo()', 3)
-
-  # object method
-  lines =<< trim END
-    vim9script
-    class A
-      def __foo()
-        echo "foo"
-      enddef
-    endclass
-    defcompile
-  END
-  v9.CheckSourceFailure(lines, 'E1034: Cannot use reserved name __foo()', 3)
-enddef
-
 " Test for compiling class/object methods using :defcompile
 def Test_defcompile_class()
   # defcompile all the classes in the current script
@@ -9769,4 +9742,534 @@ def Test_defcompile_class()
   v9.CheckScriptSuccess(lines)
 enddef
 
+" Test for cases common to all the object builtin methods
+def Test_object_builtin_method()
+  var lines =<< trim END
+    vim9script
+    class A
+      def abc()
+      enddef
+    endclass
+  END
+  v9.CheckSourceFailure(lines, 'E1267: Function name must start with a capital: abc()', 3)
+
+  for funcname in ["len", "string", "empty"]
+    lines =<< trim eval END
+      vim9script
+      class A
+        static def {funcname}(): number
+        enddef
+      endclass
+    END
+    v9.CheckSourceFailure(lines, 'E1413: Builtin class method not supported', 3)
+  endfor
+enddef
+
+" Test for using the empty() builtin method with an object
+" This is a legacy function to use the test_garbagecollect_now() function.
+func Test_object_empty()
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return true
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      assert_equal(true, empty(afoo))
+      assert_equal(true, afoo->empty())
+    enddef
+
+    var a = A.new()
+    assert_equal(1, empty(a))
+    assert_equal(1, a->empty())
+    test_garbagecollect_now()
+    assert_equal(1, empty(a))
+    Foo()
+    test_garbagecollect_now()
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " empty() should return 1 without a builtin method
+  let lines =<< trim END
+    vim9script
+    class A
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      assert_equal(1, empty(afoo))
+    enddef
+
+    var a = A.new()
+    assert_equal(1, empty(a))
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Unsupported signature for the empty() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty()
+      enddef
+    endclass
+  END
+  call v9.CheckSourceFailure(lines, 'E1383: Method "empty": type mismatch, expected func(): bool but got func()', 4)
+
+  " Error when calling the empty() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        throw "Failed to check emptiness"
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      var i = empty(afoo)
+    enddef
+
+    var a = A.new()
+    assert_fails('empty(a)', 'Failed to check emptiness')
+    assert_fails('Foo()', 'Failed to check emptiness')
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call empty() using an object from a script
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return true
+      enddef
+    endclass
+    var afoo = A.new()
+    assert_equal(true, afoo.empty())
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call empty() using an object from a method
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return true
+      enddef
+    endclass
+    def Foo()
+      var afoo = A.new()
+      assert_equal(true, afoo.empty())
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call empty() using "this" from an object method
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return true
+      enddef
+      def Foo(): bool
+        return this.empty()
+      enddef
+    endclass
+    def Bar()
+      var abar = A.new()
+      assert_equal(true, abar.Foo())
+    enddef
+    Bar()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Call empty() from a derived object
+  let lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return false
+      enddef
+    endclass
+    class B extends A
+      def empty(): bool
+        return true
+      enddef
+    endclass
+    def Foo(afoo: A)
+      assert_equal(true, empty(afoo))
+      var bfoo = B.new()
+      assert_equal(true, empty(bfoo))
+    enddef
+    var b = B.new()
+    assert_equal(1, empty(b))
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Invoking empty method using an interface
+  let lines =<< trim END
+    vim9script
+    interface A
+      def empty(): bool
+    endinterface
+    class B implements A
+      def empty(): bool
+        return false
+      enddef
+    endclass
+    def Foo(a: A)
+      assert_equal(false, empty(a))
+    enddef
+    var b = B.new()
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+endfunc
+
+" Test for using the len() builtin method with an object
+" This is a legacy function to use the test_garbagecollect_now() function.
+func Test_object_length()
+  let lines =<< trim END
+    vim9script
+    class A
+      var mylen: number = 0
+      def new(n: number)
+        this.mylen = n
+      enddef
+      def len(): number
+        return this.mylen
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new(12)
+      assert_equal(12, len(afoo))
+      assert_equal(12, afoo->len())
+    enddef
+
+    var a = A.new(22)
+    assert_equal(22, len(a))
+    assert_equal(22, a->len())
+    test_garbagecollect_now()
+    assert_equal(22, len(a))
+    Foo()
+    test_garbagecollect_now()
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " len() should return 0 without a builtin method
+  let lines =<< trim END
+    vim9script
+    class A
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      assert_equal(0, len(afoo))
+    enddef
+
+    var a = A.new()
+    assert_equal(0, len(a))
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Unsupported signature for the len() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def len()
+      enddef
+    endclass
+  END
+  call v9.CheckSourceFailure(lines, 'E1383: Method "len": type mismatch, expected func(): number but got func()', 4)
+
+  " Error when calling the len() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        throw "Failed to compute length"
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      var i = len(afoo)
+    enddef
+
+    var a = A.new()
+    assert_fails('len(a)', 'Failed to compute length')
+    assert_fails('Foo()', 'Failed to compute length')
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call len() using an object from a script
+  let lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        return 5
+      enddef
+    endclass
+    var afoo = A.new()
+    assert_equal(5, afoo.len())
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call len() using an object from a method
+  let lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        return 5
+      enddef
+    endclass
+    def Foo()
+      var afoo = A.new()
+      assert_equal(5, afoo.len())
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call len() using "this" from an object method
+  let lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        return 8
+      enddef
+      def Foo(): number
+        return this.len()
+      enddef
+    endclass
+    def Bar()
+      var abar = A.new()
+      assert_equal(8, abar.Foo())
+    enddef
+    Bar()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Call len() from a derived object
+  let lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        return 10
+      enddef
+    endclass
+    class B extends A
+      def len(): number
+        return 20
+      enddef
+    endclass
+    def Foo(afoo: A)
+      assert_equal(20, len(afoo))
+      var bfoo = B.new()
+      assert_equal(20, len(bfoo))
+    enddef
+    var b = B.new()
+    assert_equal(20, len(b))
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Invoking len method using an interface
+  let lines =<< trim END
+    vim9script
+    interface A
+      def len(): number
+    endinterface
+    class B implements A
+      def len(): number
+        return 123
+      enddef
+    endclass
+    def Foo(a: A)
+      assert_equal(123, len(a))
+    enddef
+    var b = B.new()
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+endfunc
+
+" Test for using the string() builtin method with an object
+" This is a legacy function to use the test_garbagecollect_now() function.
+func Test_object_string()
+  let lines =<< trim END
+    vim9script
+    class A
+      var name: string
+      def string(): string
+        return this.name
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new("foo-A")
+      assert_equal('foo-A', string(afoo))
+      assert_equal('foo-A', afoo->string())
+    enddef
+
+    var a = A.new("script-A")
+    assert_equal('script-A', string(a))
+    assert_equal('script-A', a->string())
+    assert_equal(['script-A'], execute('echo a')->split("\n"))
+    test_garbagecollect_now()
+    assert_equal('script-A', string(a))
+    Foo()
+    test_garbagecollect_now()
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " string() should return "object of A {}" without a builtin method
+  let lines =<< trim END
+    vim9script
+    class A
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      assert_equal('object of A {}', string(afoo))
+    enddef
+
+    var a = A.new()
+    assert_equal('object of A {}', string(a))
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Unsupported signature for the string() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def string()
+      enddef
+    endclass
+  END
+  call v9.CheckSourceFailure(lines, 'E1383: Method "string": type mismatch, expected func(): string but got func()', 4)
+
+  " Error when calling the string() method
+  let lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        throw "Failed to get text"
+      enddef
+    endclass
+
+    def Foo()
+      var afoo = A.new()
+      var i = string(afoo)
+    enddef
+
+    var a = A.new()
+    assert_fails('string(a)', 'Failed to get text')
+    assert_fails('Foo()', 'Failed to get text')
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call string() using an object from a script
+  let lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        return 'A'
+      enddef
+    endclass
+    var afoo = A.new()
+    assert_equal('A', afoo.string())
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call string() using an object from a method
+  let lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        return 'A'
+      enddef
+    endclass
+    def Foo()
+      var afoo = A.new()
+      assert_equal('A', afoo.string())
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " call string() using "this" from an object method
+  let lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        return 'A'
+      enddef
+      def Foo(): string
+        return this.string()
+      enddef
+    endclass
+    def Bar()
+      var abar = A.new()
+      assert_equal('A', abar.string())
+    enddef
+    Bar()
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Call string() from a derived object
+  let lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        return 'A'
+      enddef
+    endclass
+    class B extends A
+      def string(): string
+        return 'B'
+      enddef
+    endclass
+    def Foo(afoo: A)
+      assert_equal('B', string(afoo))
+      var bfoo = B.new()
+      assert_equal('B', string(bfoo))
+    enddef
+    var b = B.new()
+    assert_equal('B', string(b))
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  " Invoking string method using an interface
+  let lines =<< trim END
+    vim9script
+    interface A
+      def string(): string
+    endinterface
+    class B implements A
+      def string(): string
+        return 'B'
+      enddef
+    endclass
+    def Foo(a: A)
+      assert_equal('B', string(a))
+    enddef
+    var b = B.new()
+    Foo(b)
+  END
+  call v9.CheckSourceSuccess(lines)
+endfunc
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 1a192cc09242b568faea3e8646e506cad52662ce..645b04bdd04cbd063bd7f31695955d566556fb6c 100644 (file)
@@ -3273,4 +3273,167 @@ def Test_funcref_with_class()
   unlet g:instr
 enddef
 
+" Disassemble instructions for calls to a string() function in an object
+def Test_disassemble_object_string()
+  var lines =<< trim END
+    vim9script
+    class A
+      def string(): string
+        return 'A'
+      enddef
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = string(a)
+      s = string(A)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = string(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 METHODCALL A.string(argc 0)\_s*' ..
+    '4 STORE $1\_s*' ..
+    's = string(A)\_s*' ..
+    '5 LOADSCRIPT A-0 from .*\_s*' ..
+    '6 BCALL string(argc 1)\_s*' ..
+    '7 STORE $1\_s*' ..
+    '8 RETURN void', g:instr)
+  unlet g:instr
+
+  # Use the default string() function for a class
+  lines =<< trim END
+    vim9script
+    class A
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = string(a)
+      s = string(A)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = string(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 BCALL string(argc 1)\_s*' ..
+    '4 STORE $1\_s*' ..
+    's = string(A)\_s*' ..
+    '5 LOADSCRIPT A-0 from .*\_s*' ..
+    '6 BCALL string(argc 1)\_s*' ..
+    '7 STORE $1\_s*' ..
+    '8 RETURN void', g:instr)
+  unlet g:instr
+enddef
+
+" Disassemble instructions for calls to a empty() function in an object
+def Test_disassemble_object_empty()
+  var lines =<< trim END
+    vim9script
+    class A
+      def empty(): bool
+        return true
+      enddef
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = empty(a)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = empty(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 METHODCALL A.empty(argc 0)\_s*' ..
+    '4 STORE $1\_s*' ..
+    '5 RETURN void', g:instr)
+  unlet g:instr
+
+  # Use the default empty() function for a class
+  lines =<< trim END
+    vim9script
+    class A
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = empty(a)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = empty(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 BCALL empty(argc 1)\_s*' ..
+    '4 STORE $1\_s*' ..
+    '5 RETURN void', g:instr)
+  unlet g:instr
+enddef
+
+" Disassemble instructions for calls to a len() function in an object
+def Test_disassemble_object_len()
+  var lines =<< trim END
+    vim9script
+    class A
+      def len(): number
+        return 10
+      enddef
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = len(a)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = len(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 METHODCALL A.len(argc 0)\_s*' ..
+    '4 STORE $1\_s*' ..
+    '5 RETURN void', g:instr)
+  unlet g:instr
+
+  # Use the default len() function for a class
+  lines =<< trim END
+    vim9script
+    class A
+    endclass
+    def Bar()
+      var a = A.new()
+      var s = len(a)
+    enddef
+    g:instr = execute('disassemble Bar')
+  END
+  v9.CheckScriptSuccess(lines)
+  assert_match('<SNR>\d*_Bar\_s*' ..
+    'var a = A.new()\_s*' ..
+    '0 DCALL new(argc 0)\_s*' ..
+    '1 STORE $0\_s*' ..
+    'var s = len(a)\_s*' ..
+    '2 LOAD $0\_s*' ..
+    '3 BCALL len(argc 1)\_s*' ..
+    '4 STORE $1\_s*' ..
+    '5 RETURN void', g:instr)
+  unlet g:instr
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index e39ce6e4922caf555464b29adf27e4da4800cb98..00e499fd0f123c3f8150726f29a33c3f21063c8f 100644 (file)
@@ -4459,12 +4459,13 @@ trans_function_name_ext(
        }
     }
     // The function name must start with an upper case letter (unless it is a
-    // Vim9 class new() function or a Vim9 class private method)
+    // Vim9 class new() function or a Vim9 class private method or one of the
+    // supported Vim9 object builtin functions)
     else if (!(flags & TFN_INT)
            && (builtin_function(lv.ll_name, len)
                                   || (vim9script && *lv.ll_name == '_'))
            && !((flags & TFN_IN_CLASS)
-               && (STRNCMP(lv.ll_name, "new", 3) == 0
+               && (is_valid_builtin_obj_methodname(lv.ll_name)
                    || (*lv.ll_name == '_'))))
     {
        semsg(_(vim9script ? e_function_name_must_start_with_capital_str
index fc595f098c4e2c7352ecf631af1e848afbbafe66..cecf0892b36de52d3c221c57e5a6c9c0dfe1369c 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    148,
 /**/
     147,
 /**/
index 525f8d038900eb8c52cae3e648a5bdbebd0d4035..7357520199a6805157d51ab83d4d2944c5da2153 100644 (file)
@@ -973,6 +973,100 @@ is_valid_constructor(ufunc_T *uf, int is_abstract, int has_static)
     return TRUE;
 }
 
+/*
+ * Returns TRUE if 'uf' is a supported builtin method and has the correct
+ * method signature.
+ */
+    static int
+object_check_builtin_method_sig(ufunc_T *uf)
+{
+    char_u  *name = uf->uf_name;
+    int            valid = FALSE;
+    type_T  method_sig;
+    type_T  method_rt;
+    where_T where = WHERE_INIT;
+
+    // validate the method signature
+    CLEAR_FIELD(method_sig);
+    CLEAR_FIELD(method_rt);
+    method_sig.tt_type = VAR_FUNC;
+
+    if (STRCMP(name, "len") == 0)
+    {
+       // def __len(): number
+       method_rt.tt_type = VAR_NUMBER;
+       method_sig.tt_member = &method_rt;
+       valid = TRUE;
+    }
+    else if (STRCMP(name, "empty") == 0)
+    {
+       // def __empty(): bool
+       method_rt.tt_type = VAR_BOOL;
+       method_sig.tt_member = &method_rt;
+       valid = TRUE;
+    }
+    else if (STRCMP(name, "string") == 0)
+    {
+       // def __string(): string
+       method_rt.tt_type = VAR_STRING;
+       method_sig.tt_member = &method_rt;
+       valid = TRUE;
+    }
+    else
+       semsg(_(e_builtin_object_method_str_not_supported), uf->uf_name);
+
+    where.wt_func_name = (char *)uf->uf_name;
+    where.wt_kind = WT_METHOD;
+    if (valid && !check_type(&method_sig, uf->uf_func_type, TRUE, where))
+       valid = FALSE;
+
+    return valid;
+}
+
+/*
+ * Returns TRUE if "funcname" is a supported builtin object method name
+ */
+    int
+is_valid_builtin_obj_methodname(char_u *funcname)
+{
+    switch (funcname[0])
+    {
+       case 'e':
+           return STRNCMP(funcname, "empty", 5) == 0;
+
+       case 'l':
+           return STRNCMP(funcname, "len", 3) == 0;
+
+       case 'n':
+           return STRNCMP(funcname, "new", 3) == 0;
+
+       case 's':
+           return STRNCMP(funcname, "string", 6) == 0;
+    }
+
+    return FALSE;
+}
+
+
+/*
+ * Returns the builtin method "name" in object "obj".  Returns NULL if the
+ * method is not found.
+ */
+    ufunc_T *
+class_get_builtin_method(
+    class_T            *cl,
+    class_builtin_T    builtin_method,
+    int                        *method_idx)
+{
+    *method_idx = -1;
+
+    if (cl == NULL)
+       return NULL;
+
+    *method_idx = cl->class_builtin_methods[builtin_method];
+    return *method_idx != -1 ? cl->class_obj_methods[*method_idx] : NULL;
+}
+
 /*
  * Update the interface class lookup table for the member index on the
  * interface to the member index in the class implementing the interface.
@@ -1326,6 +1420,33 @@ add_classfuncs_objmethods(
     return OK;
 }
 
+/*
+ * Update the index of object methods called by builtin functions.
+ */
+    static void
+update_builtin_method_index(class_T *cl)
+{
+    int        i;
+
+    for (i = 0; i < CLASS_BUILTIN_MAX; i++)
+       cl->class_builtin_methods[i] = -1;
+
+    for (i = 0; i < cl->class_obj_method_count; i++)
+    {
+       ufunc_T *uf = cl->class_obj_methods[i];
+
+       if (cl->class_builtin_methods[CLASS_BUILTIN_STRING] == -1
+               && STRCMP(uf->uf_name, "string") == 0)
+           cl->class_builtin_methods[CLASS_BUILTIN_STRING] = i;
+       else if (cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] == -1 &&
+               STRCMP(uf->uf_name, "empty") == 0)
+           cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] = i;
+       else if (cl->class_builtin_methods[CLASS_BUILTIN_LEN] == -1 &&
+               STRCMP(uf->uf_name, "len") == 0)
+           cl->class_builtin_methods[CLASS_BUILTIN_LEN] = i;
+    }
+}
+
 /*
  * Return the end of the class name starting at "arg".  Valid characters in a
  * class name are alphanumeric characters and "_".  Also handles imported class
@@ -1721,13 +1842,10 @@ early_ret:
                          &varname_end, &has_type, &type_list, &type,
                          is_class ? &init_expr: NULL) == FAIL)
                break;
-           if (is_reserved_varname(varname, varname_end))
-           {
-               vim_free(init_expr);
-               break;
-           }
-           if (is_duplicate_variable(&classmembers, &objmembers, varname,
-                                                               varname_end))
+
+           if (is_reserved_varname(varname, varname_end)
+                   || is_duplicate_variable(&classmembers, &objmembers,
+                                                       varname, varname_end))
            {
                vim_free(init_expr);
                break;
@@ -1758,6 +1876,7 @@ early_ret:
        {
            exarg_T     ea;
            garray_T    lines_to_free;
+           int         is_new = STRNCMP(p, "new", 3) == 0;
 
            if (has_public)
            {
@@ -1774,12 +1893,17 @@ early_ret:
                break;
            }
 
-           if (*p == '_' && *(p + 1) == '_')
+           if (!is_class && *p == '_')
            {
-               // double underscore prefix for a method name is currently
-               // reserved.  This could be used in the future to support
-               // object methods called by Vim builtin functions.
-               semsg(_(e_cannot_use_reserved_name_str), p);
+               // private methods are not supported in an interface
+               semsg(_(e_protected_method_not_supported_in_interface), p);
+               break;
+           }
+
+           if (has_static && !is_new && SAFE_islower(*p) &&
+                                       is_valid_builtin_obj_methodname(p))
+           {
+               semsg(_(e_builtin_class_method_not_supported), p);
                break;
            }
 
@@ -1803,9 +1927,9 @@ early_ret:
            if (uf != NULL)
            {
                char_u  *name = uf->uf_name;
-               int     is_new = STRNCMP(name, "new", 3) == 0;
 
-               if (!is_class && *name == '_')
+               if (is_new && !is_valid_constructor(uf, is_abstract,
+                                                               has_static))
                {
                    // private variables are not supported in an interface
                    semsg(_(e_protected_method_not_supported_in_interface),
@@ -1813,8 +1937,10 @@ early_ret:
                    func_clear_free(uf, FALSE);
                    break;
                }
-               if (is_new && !is_valid_constructor(uf, is_abstract,
-                                                               has_static))
+
+               // check for builtin method
+               if (!is_new && SAFE_islower(*name) &&
+                                       !object_check_builtin_method_sig(uf))
                {
                    func_clear_free(uf, FALSE);
                    break;
@@ -1997,6 +2123,8 @@ early_ret:
                                                        &objmethods) == FAIL)
            goto cleanup;
 
+       update_builtin_method_index(cl);
+
        cl->class_type.tt_type = VAR_CLASS;
        cl->class_type.tt_class = cl;
        cl->class_object_type.tt_type = VAR_OBJECT;
@@ -3272,6 +3400,125 @@ is_class_name(char_u *name, typval_T *rettv)
     return FALSE;
 }
 
+/*
+ * Calls the object builtin method "name" with arguments "argv".  The value
+ * returned by the builtin method is in "rettv".  Returns OK or FAIL.
+ */
+    static int
+object_call_builtin_method(
+    object_T           *obj,
+    class_builtin_T    builtin_method,
+    int                        argc,
+    typval_T           *argv,
+    typval_T           *rettv)
+{
+    ufunc_T *uf;
+    int            midx;
+
+    if (obj == NULL)
+       return FAIL;
+
+    uf = class_get_builtin_method(obj->obj_class, builtin_method, &midx);
+    if (uf == NULL)
+       return FAIL;
+
+    funccall_T  *fc = create_funccal(uf, rettv);
+    int                r;
+
+    if (fc == NULL)
+       return FAIL;
+
+    ++obj->obj_refcount;
+
+    r = call_def_function(uf, argc, argv, 0, NULL, obj, fc, rettv);
+
+    remove_funccal();
+
+    return r;
+}
+
+/*
+ * Calls the object "empty()" method and returns the method retun value.  In
+ * case of an error, returns TRUE.
+ */
+    int
+object_empty(object_T *obj)
+{
+    typval_T   rettv;
+
+    if (object_call_builtin_method(obj, CLASS_BUILTIN_EMPTY, 0, NULL, &rettv)
+                                                               == FAIL)
+       return TRUE;
+
+    return tv_get_bool(&rettv);
+}
+
+/*
+ * Use the object "len()" method to get an object length.  Returns 0 if the
+ * method is not found or there is an error.
+ */
+    int
+object_len(object_T *obj)
+{
+    typval_T   rettv;
+
+    if (object_call_builtin_method(obj, CLASS_BUILTIN_LEN, 0, NULL, &rettv)
+                                                               == FAIL)
+       return 0;
+
+    return tv_to_number(&rettv);
+}
+
+/*
+ * Return a textual representation of object "obj"
+ */
+    char_u *
+object_string(
+    object_T   *obj,
+    char_u     *numbuf,
+    int                copyID,
+    int                echo_style,
+    int                restore_copyID,
+    int                composite_val)
+{
+    typval_T   rettv;
+
+    if (object_call_builtin_method(obj, CLASS_BUILTIN_STRING, 0, NULL, &rettv)
+                                                               == OK
+                                       && rettv.vval.v_string != NULL)
+       return rettv.vval.v_string;
+    else
+    {
+       garray_T ga;
+       ga_init2(&ga, 1, 50);
+
+       ga_concat(&ga, (char_u *)"object of ");
+       class_T *cl = obj == NULL ? NULL : obj->obj_class;
+       ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
+               : cl->class_name);
+       if (cl != NULL)
+       {
+           ga_concat(&ga, (char_u *)" {");
+           for (int i = 0; i < cl->class_obj_member_count; ++i)
+           {
+               if (i > 0)
+                   ga_concat(&ga, (char_u *)", ");
+               ocmember_T *m = &cl->class_obj_members[i];
+               ga_concat(&ga, m->ocm_name);
+               ga_concat(&ga, (char_u *)": ");
+               char_u *tf = NULL;
+               ga_concat(&ga, echo_string_core(
+                           (typval_T *)(obj + 1) + i,
+                           &tf, numbuf, copyID, echo_style,
+                           restore_copyID, composite_val));
+               vim_free(tf);
+           }
+           ga_concat(&ga, (char_u *)"}");
+       }
+       return ga.ga_data;
+    }
+}
+
 /*
  * Return TRUE when the class "cl", its base class or one of the implemented
  * interfaces matches the class "other_cl".
index 38a65d4424546bfab7d099c0e7abf59c115a1bbb..ee9712bfd9d5cf8f5eba620fb54d01e3fd850662 100644 (file)
@@ -1013,6 +1013,32 @@ failret:
     return FAIL;
 }
 
+/*
+ * Compile a builtin method call of an object (e.g. string(), len(), empty(),
+ * etc.) if the class implements it.
+ */
+    static int
+compile_builtin_method_call(cctx_T *cctx, class_builtin_T builtin_method)
+{
+    type_T     *type = get_decl_type_on_stack(cctx, 0);
+    int                res = FAIL;
+
+    // If the built in function is invoked on an object and the class
+    // implements the corresponding built in method, then invoke the object
+    // method.
+    if (type->tt_type == VAR_OBJECT)
+    {
+       int     method_idx;
+       ufunc_T *uf = class_get_builtin_method(type->tt_class, builtin_method,
+                                                       &method_idx);
+       if (uf != NULL)
+           res = generate_CALL(cctx, uf, type->tt_class, method_idx, 0);
+    }
+
+    return res;
+}
+
+
 /*
  * Compile a function call:  name(arg1, arg2)
  * "arg" points to "name", "arg + varlen" to the "(".
@@ -1170,6 +1196,20 @@ compile_call(
                    idx = -1;
            }
 
+           class_builtin_T     builtin_method = CLASS_BUILTIN_INVALID;
+           if (STRCMP(name, "string") == 0)
+               builtin_method = CLASS_BUILTIN_STRING;
+           else if (STRCMP(name, "empty") == 0)
+               builtin_method = CLASS_BUILTIN_EMPTY;
+           else if (STRCMP(name, "len") == 0)
+               builtin_method = CLASS_BUILTIN_LEN;
+           if (builtin_method != CLASS_BUILTIN_INVALID)
+           {
+               res = compile_builtin_method_call(cctx, builtin_method);
+               if (res == OK)
+                   idx = -1;
+           }
+
            if (idx >= 0)
                res = generate_BCALL(cctx, idx, argcount, argcount_init == 1);
        }