changeset 34472:5c1a025192ed v9.1.0148

patch 9.1.0148: Vim9: can't call internal methods with objects Commit: https://github.com/vim/vim/commit/d3eae7bc116297f70220f21ded436ed0a88066d8 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Sun Mar 3 16:26:58 2024 +0100 patch 9.1.0148: Vim9: can't call internal methods with objects 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>
author Christian Brabandt <cb@256bit.org>
date Sun, 03 Mar 2024 16:45:06 +0100
parents 7f6302969e3d
children f448a86f3b88
files 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
diffstat 16 files changed, 1085 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -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, whic
 		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 t
 		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()
 
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -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-se
 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*
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -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
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -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*
 ------------------
 
--- a/runtime/doc/vim9class.txt
+++ b/runtime/doc/vim9class.txt
@@ -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 lis
 							*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*
--- a/src/errors.h
+++ b/src/errors.h
@@ -3579,8 +3579,12 @@ EXTERN char e_const_variable_not_support
 	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\""));
-#endif
-// E1412 - E1499 unused (reserved for Vim9 class support)
+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
+// 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[]
--- a/src/eval.c
+++ b/src/eval.c
@@ -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:
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -986,6 +986,7 @@ arg_len1(type_T *type, type_T *decl_type
 	    || 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 *ret
 	    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;
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -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, v
 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 : */
--- a/src/structs.h
+++ b/src/structs.h
@@ -1531,6 +1531,17 @@ typedef enum {
 #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.
  */
 typedef struct {
@@ -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
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -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
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -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
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -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
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    148,
+/**/
     147,
 /**/
     146,
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -974,6 +974,100 @@ is_valid_constructor(ufunc_T *uf, int is
 }
 
 /*
+ * 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.
  * And a lookup table for the object method index on the interface
@@ -1327,6 +1421,33 @@ add_classfuncs_objmethods(
 }
 
 /*
+ * 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
  * names.
@@ -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;
@@ -3273,6 +3401,125 @@ is_class_name(char_u *name, typval_T *re
 }
 
 /*
+ * 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".
  */
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -1014,6 +1014,32 @@ failret:
 }
 
 /*
+ * 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 "(".
  * "argcount_init" is 1 for "value->method()"
@@ -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);
 	}