# HG changeset patch # User Christian Brabandt # Date 1709480706 -3600 # Node ID 5c1a025192edab49085a6f2f981c92525054e9dc # Parent 7f6302969e3d817054bc5c1c228413429701e1d4 patch 9.1.0148: Vim9: can't call internal methods with objects Commit: https://github.com/vim/vim/commit/d3eae7bc116297f70220f21ded436ed0a88066d8 Author: Yegappan Lakshmanan 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 Signed-off-by: Christian Brabandt diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- 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() diff --git a/runtime/doc/tags b/runtime/doc/tags --- 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* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt --- 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 diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt --- 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* ------------------ diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- 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* diff --git a/src/errors.h b/src/errors.h --- 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[] diff --git a/src/eval.c b/src/eval.c --- 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 --- 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; diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- 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 : */ diff --git a/src/structs.h b/src/structs.h --- 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 --- 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 --- 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('\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('\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('\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('\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('\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('\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 --- 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 --- 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 --- 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". */ diff --git a/src/vim9expr.c b/src/vim9expr.c --- 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); }