diff options
author | Yegappan Lakshmanan <yegappan@yahoo.com> | 2024-03-03 16:26:58 +0100 |
---|---|---|
committer | Christian Brabandt <cb@256bit.org> | 2024-03-03 16:34:51 +0100 |
commit | d3eae7bc116297f70220f21ded436ed0a88066d8 (patch) | |
tree | 29837ff70cabc4f3238be13df845dcb6ace10da9 /src | |
parent | 215703563757a4464907ead6fb9edaeb7f430bea (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.h | 6 | ||||
-rw-r--r-- | src/eval.c | 33 | ||||
-rw-r--r-- | src/evalfunc.c | 7 | ||||
-rw-r--r-- | src/proto/vim9class.pro | 6 | ||||
-rw-r--r-- | src/structs.h | 14 | ||||
-rw-r--r-- | src/testdir/test_vim9_class.vim | 557 | ||||
-rw-r--r-- | src/testdir/test_vim9_disassemble.vim | 163 | ||||
-rw-r--r-- | src/userfunc.c | 5 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9class.c | 279 | ||||
-rw-r--r-- | src/vim9expr.c | 40 |
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) { @@ -1774,12 +1893,17 @@ early_ret: |