summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2024-03-03 16:26:58 +0100
committerChristian Brabandt <cb@256bit.org>2024-03-03 16:34:51 +0100
commitd3eae7bc116297f70220f21ded436ed0a88066d8 (patch)
tree29837ff70cabc4f3238be13df845dcb6ace10da9 /src
parent215703563757a4464907ead6fb9edaeb7f430bea (diff)
patch 9.1.0148: Vim9: can't call internal methods with objectsv9.1.0148
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>
Diffstat (limited to 'src')
-rw-r--r--src/errors.h6
-rw-r--r--src/eval.c33
-rw-r--r--src/evalfunc.c7
-rw-r--r--src/proto/vim9class.pro6
-rw-r--r--src/structs.h14
-rw-r--r--src/testdir/test_vim9_class.vim557
-rw-r--r--src/testdir/test_vim9_disassemble.vim163
-rw-r--r--src/userfunc.c5
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c279
-rw-r--r--src/vim9expr.c40
11 files changed, 1034 insertions, 78 deletions
diff --git a/src/errors.h b/src/errors.h
index 65ee4e826e..0dbc5a5719 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -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[]
diff --git a/src/eval.c b/src/eval.c
index fd44db6f7e..ca5a2685fa 100644
--- 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:
diff --git a/src/evalfunc.c b/src/evalfunc.c
index b5d8c872ed..5d6664c9a2 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -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;
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index a746eb7729..1ed175e69f 100644
--- 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, 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 : */
diff --git a/src/structs.h b/src/structs.h
index df2c005e3d..2c6f553521 100644
--- 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
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 0bf7e9ceb6..12e3c48a3b 100644
--- 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
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 1a192cc092..645b04bdd0 100644
--- 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
diff --git a/src/userfunc.c b/src/userfunc.c
index e39ce6e492..00e499fd0f 100644
--- 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
diff --git a/src/version.c b/src/version.c
index fc595f098c..cecf0892b3 100644
--- 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,
diff --git a/src/vim9class.c b/src/vim9class.c
index 525f8d0389..7357520199 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -974,6 +974,100 @@ is_valid_constructor(ufunc_T *uf, int is_abstract, int has_static)
}
/*
+ * 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)</