1
0
forked from aniani/vim

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>
This commit is contained in:
Yegappan Lakshmanan
2024-03-03 16:26:58 +01:00
committed by Christian Brabandt
parent 2157035637
commit d3eae7bc11
16 changed files with 1083 additions and 83 deletions

View File

@@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.1. Last change: 2024 Mar 01 *builtin.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@@ -2265,6 +2265,8 @@ empty({expr}) *empty()*
- A |Job| is empty when it failed to start. - A |Job| is empty when it failed to start.
- A |Channel| is empty when it is closed. - A |Channel| is empty when it is closed.
- A |Blob| is empty when its length is zero. - 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 For a long |List| this is much faster than comparing the
length with zero. length with zero.
@@ -5476,7 +5478,9 @@ len({expr}) The result is a Number, which is the length of the argument.
When {expr} is a |Blob| the number of bytes is returned. When {expr} is a |Blob| the number of bytes is returned.
When {expr} is a |Dictionary| the number of entries in the When {expr} is a |Dictionary| the number of entries in the
|Dictionary| is returned. |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|: > Can also be used as a |method|: >
mylist->len() mylist->len()
@@ -9587,6 +9591,10 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
replaced by "[...]" or "{...}". Using eval() on the result replaced by "[...]" or "{...}". Using eval() on the result
will then fail. 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|: > Can also be used as a |method|: >
mylist->string() mylist->string()

View File

@@ -4518,6 +4518,8 @@ E1409 vim9class.txt /*E1409*
E141 message.txt /*E141* E141 message.txt /*E141*
E1410 vim9class.txt /*E1410* E1410 vim9class.txt /*E1410*
E1411 vim9class.txt /*E1411* E1411 vim9class.txt /*E1411*
E1412 vim9class.txt /*E1412*
E1413 vim9class.txt /*E1413*
E142 message.txt /*E142* E142 message.txt /*E142*
E143 autocmd.txt /*E143* E143 autocmd.txt /*E143*
E144 various.txt /*E144* E144 various.txt /*E144*
@@ -6183,6 +6185,7 @@ bugs intro.txt /*bugs*
builtin-function-details builtin.txt /*builtin-function-details* builtin-function-details builtin.txt /*builtin-function-details*
builtin-function-list builtin.txt /*builtin-function-list* builtin-function-list builtin.txt /*builtin-function-list*
builtin-functions builtin.txt /*builtin-functions* builtin-functions builtin.txt /*builtin-functions*
builtin-object-methods vim9class.txt /*builtin-object-methods*
builtin-terms term.txt /*builtin-terms* builtin-terms term.txt /*builtin-terms*
builtin-tools gui.txt /*builtin-tools* builtin-tools gui.txt /*builtin-tools*
builtin.txt builtin.txt /*builtin.txt* builtin.txt builtin.txt /*builtin.txt*
@@ -9153,9 +9156,12 @@ o_object-select motion.txt /*o_object-select*
o_v motion.txt /*o_v* o_v motion.txt /*o_v*
object vim9class.txt /*object* object vim9class.txt /*object*
object-const-variable vim9class.txt /*object-const-variable* object-const-variable vim9class.txt /*object-const-variable*
object-empty() vim9class.txt /*object-empty()*
object-final-variable vim9class.txt /*object-final-variable* object-final-variable vim9class.txt /*object-final-variable*
object-len() vim9class.txt /*object-len()*
object-motions motion.txt /*object-motions* object-motions motion.txt /*object-motions*
object-select motion.txt /*object-select* object-select motion.txt /*object-select*
object-string() vim9class.txt /*object-string()*
objects index.txt /*objects* objects index.txt /*objects*
obtaining-exted netbeans.txt /*obtaining-exted* obtaining-exted netbeans.txt /*obtaining-exted*
ocaml.vim syntax.txt /*ocaml.vim* ocaml.vim syntax.txt /*ocaml.vim*

View File

@@ -1,4 +1,4 @@
*todo.txt* For Vim version 9.1. Last change: 2024 Feb 01 *todo.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar 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). Possibly issue #11981 can be fixed at the same time (has two examples).
- Forward declaration of a class? E.g. for Clone() function. - Forward declaration of a class? E.g. for Clone() function.
Email lifepillar 2023 Mar 26 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 - 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 either work or given an error. Possibly give an error now and implement it
later (using a typedef). #12006 later (using a typedef). #12006

View File

@@ -41543,6 +41543,11 @@ and is a work in progress.
Support for Wayland UI. 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* Other improvements *new-other-9.2*
------------------ ------------------

View File

@@ -1,4 +1,4 @@
*vim9class.txt* For Vim version 9.1. Last change: 2024 Jan 12 *vim9class.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@@ -710,6 +710,32 @@ The initialization isn't needed, the list is empty by default.
*E1330* *E1330*
Some types cannot be used, such as "void", "null" and "v:none". 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 ~ Defining an interface ~
*Interface* *:interface* *:endinterface* *Interface* *:interface* *:endinterface*

View File

@@ -3579,8 +3579,12 @@ EXTERN char e_const_variable_not_supported_in_interface[]
INIT(= N_("E1410: Const variable not supported in an interface")); INIT(= N_("E1410: Const variable not supported in an interface"));
EXTERN char e_missing_dot_after_object_str[] EXTERN char e_missing_dot_after_object_str[]
INIT(= N_("E1411: Missing dot after object \"%s\"")); 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 #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[] EXTERN char e_cannot_mix_positional_and_non_positional_str[]
INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s")); INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
EXTERN char e_fmt_arg_nr_unused_str[] EXTERN char e_fmt_arg_nr_unused_str[]

View File

@@ -6318,36 +6318,9 @@ echo_string_core(
break; break;
case VAR_OBJECT: case VAR_OBJECT:
{ *tofree = r = object_string(tv->vval.v_object, numbuf, copyID,
garray_T ga; echo_style, restore_copyID,
ga_init2(&ga, 1, 50); composite_val);
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;
}
break; break;
case VAR_FLOAT: case VAR_FLOAT:

View File

@@ -986,6 +986,7 @@ arg_len1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
|| type->tt_type == VAR_BLOB || type->tt_type == VAR_BLOB
|| type->tt_type == VAR_LIST || type->tt_type == VAR_LIST
|| type->tt_type == VAR_DICT || type->tt_type == VAR_DICT
|| type->tt_type == VAR_OBJECT
|| type_any_or_unknown(type)) || type_any_or_unknown(type))
return OK; return OK;
@@ -3981,7 +3982,7 @@ f_empty(typval_T *argvars, typval_T *rettv)
n = argvars[0].vval.v_class != NULL; n = argvars[0].vval.v_class != NULL;
break; break;
case VAR_OBJECT: case VAR_OBJECT:
n = argvars[0].vval.v_object != NULL; n = object_empty(argvars[0].vval.v_object);
break; break;
case VAR_BLOB: case VAR_BLOB:
@@ -7831,6 +7832,9 @@ f_len(typval_T *argvars, typval_T *rettv)
case VAR_DICT: case VAR_DICT:
rettv->vval.v_number = dict_len(argvars[0].vval.v_dict); rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
break; break;
case VAR_OBJECT:
rettv->vval.v_number = object_len(argvars[0].vval.v_object);
break;
case VAR_UNKNOWN: case VAR_UNKNOWN:
case VAR_ANY: case VAR_ANY:
case VAR_VOID: case VAR_VOID:
@@ -7843,7 +7847,6 @@ f_len(typval_T *argvars, typval_T *rettv)
case VAR_CHANNEL: case VAR_CHANNEL:
case VAR_INSTR: case VAR_INSTR:
case VAR_CLASS: case VAR_CLASS:
case VAR_OBJECT:
case VAR_TYPEALIAS: case VAR_TYPEALIAS:
emsg(_(e_invalid_type_for_len)); emsg(_(e_invalid_type_for_len));
break; break;

View File

@@ -1,5 +1,7 @@
/* vim9class.c */ /* vim9class.c */
int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl); 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); 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(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); 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_class(class_T *cl);
void defcompile_classes_in_script(void); void defcompile_classes_in_script(void);
int is_class_name(char_u *name, typval_T *rettv); 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); int class_instance_of(class_T *cl, class_T *other_cl);
void f_instanceof(typval_T *argvars, typval_T *rettv); void f_instanceof(typval_T *argvars, typval_T *rettv);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -1530,6 +1530,17 @@ typedef enum {
#define OCMFLAG_FINAL 0x02 // "final" object/class member #define OCMFLAG_FINAL 0x02 // "final" object/class member
#define OCMFLAG_CONST 0x04 // "const" object/class member #define OCMFLAG_CONST 0x04 // "const" object/class member
/*
* Object methods called by builtin functions (e.g. string(), empty(), etc.)
*/
typedef enum {
CLASS_BUILTIN_INVALID,
CLASS_BUILTIN_STRING,
CLASS_BUILTIN_EMPTY,
CLASS_BUILTIN_LEN,
CLASS_BUILTIN_MAX
} class_builtin_T;
/* /*
* Entry for an object or class member variable. * Entry for an object or class member variable.
*/ */
@@ -1593,6 +1604,9 @@ struct class_S
int class_obj_method_count_child; // count without "extends" int class_obj_method_count_child; // count without "extends"
ufunc_T **class_obj_methods; // allocated 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 garray_T class_type_list; // used for type pointers
type_T class_type; // type used for the class type_T class_type; // type used for the class
type_T class_object_type; // same as class_type but VAR_OBJECT type_T class_object_type; // same as class_type but VAR_OBJECT

View File

@@ -9659,33 +9659,6 @@ def Test_const_class_object_variable()
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3) v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
enddef 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 " Test for compiling class/object methods using :defcompile
def Test_defcompile_class() def Test_defcompile_class()
# defcompile all the classes in the current script # defcompile all the classes in the current script
@@ -9769,4 +9742,534 @@ def Test_defcompile_class()
v9.CheckScriptSuccess(lines) v9.CheckScriptSuccess(lines)
enddef 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 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -3273,4 +3273,167 @@ def Test_funcref_with_class()
unlet g:instr unlet g:instr
enddef 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 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -4459,12 +4459,13 @@ trans_function_name_ext(
} }
} }
// The function name must start with an upper case letter (unless it is a // 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) else if (!(flags & TFN_INT)
&& (builtin_function(lv.ll_name, len) && (builtin_function(lv.ll_name, len)
|| (vim9script && *lv.ll_name == '_')) || (vim9script && *lv.ll_name == '_'))
&& !((flags & TFN_IN_CLASS) && !((flags & TFN_IN_CLASS)
&& (STRNCMP(lv.ll_name, "new", 3) == 0 && (is_valid_builtin_obj_methodname(lv.ll_name)
|| (*lv.ll_name == '_')))) || (*lv.ll_name == '_'))))
{ {
semsg(_(vim9script ? e_function_name_must_start_with_capital_str semsg(_(vim9script ? e_function_name_must_start_with_capital_str

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
148,
/**/ /**/
147, 147,
/**/ /**/

View File

@@ -973,6 +973,100 @@ is_valid_constructor(ufunc_T *uf, int is_abstract, int has_static)
return TRUE; return TRUE;
} }
/*
* Returns TRUE if 'uf' is a supported builtin method and has the correct
* method signature.
*/
static int
object_check_builtin_method_sig(ufunc_T *uf)
{
char_u *name = uf->uf_name;
int valid = FALSE;
type_T method_sig;
type_T method_rt;
where_T where = WHERE_INIT;
// validate the method signature
CLEAR_FIELD(method_sig);
CLEAR_FIELD(method_rt);
method_sig.tt_type = VAR_FUNC;
if (STRCMP(name, "len") == 0)
{
// def __len(): number
method_rt.tt_type = VAR_NUMBER;
method_sig.tt_member = &method_rt;
valid = TRUE;
}
else if (STRCMP(name, "empty") == 0)
{
// def __empty(): bool
method_rt.tt_type = VAR_BOOL;
method_sig.tt_member = &method_rt;
valid = TRUE;
}
else if (STRCMP(name, "string") == 0)
{
// def __string(): string
method_rt.tt_type = VAR_STRING;
method_sig.tt_member = &method_rt;
valid = TRUE;
}
else
semsg(_(e_builtin_object_method_str_not_supported), uf->uf_name);
where.wt_func_name = (char *)uf->uf_name;
where.wt_kind = WT_METHOD;
if (valid && !check_type(&method_sig, uf->uf_func_type, TRUE, where))
valid = FALSE;
return valid;
}
/*
* Returns TRUE if "funcname" is a supported builtin object method name
*/
int
is_valid_builtin_obj_methodname(char_u *funcname)
{
switch (funcname[0])
{
case 'e':
return STRNCMP(funcname, "empty", 5) == 0;
case 'l':
return STRNCMP(funcname, "len", 3) == 0;
case 'n':
return STRNCMP(funcname, "new", 3) == 0;
case 's':
return STRNCMP(funcname, "string", 6) == 0;
}
return FALSE;
}
/*
* Returns the builtin method "name" in object "obj". Returns NULL if the
* method is not found.
*/
ufunc_T *
class_get_builtin_method(
class_T *cl,
class_builtin_T builtin_method,
int *method_idx)
{
*method_idx = -1;
if (cl == NULL)
return NULL;
*method_idx = cl->class_builtin_methods[builtin_method];
return *method_idx != -1 ? cl->class_obj_methods[*method_idx] : NULL;
}
/* /*
* Update the interface class lookup table for the member index on the * Update the interface class lookup table for the member index on the
* interface to the member index in the class implementing the interface. * interface to the member index in the class implementing the interface.
@@ -1326,6 +1420,33 @@ add_classfuncs_objmethods(
return OK; return OK;
} }
/*
* Update the index of object methods called by builtin functions.
*/
static void
update_builtin_method_index(class_T *cl)
{
int i;
for (i = 0; i < CLASS_BUILTIN_MAX; i++)
cl->class_builtin_methods[i] = -1;
for (i = 0; i < cl->class_obj_method_count; i++)
{
ufunc_T *uf = cl->class_obj_methods[i];
if (cl->class_builtin_methods[CLASS_BUILTIN_STRING] == -1
&& STRCMP(uf->uf_name, "string") == 0)
cl->class_builtin_methods[CLASS_BUILTIN_STRING] = i;
else if (cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] == -1 &&
STRCMP(uf->uf_name, "empty") == 0)
cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] = i;
else if (cl->class_builtin_methods[CLASS_BUILTIN_LEN] == -1 &&
STRCMP(uf->uf_name, "len") == 0)
cl->class_builtin_methods[CLASS_BUILTIN_LEN] = i;
}
}
/* /*
* Return the end of the class name starting at "arg". Valid characters in a * Return the end of the class name starting at "arg". Valid characters in a
* class name are alphanumeric characters and "_". Also handles imported class * class name are alphanumeric characters and "_". Also handles imported class
@@ -1721,13 +1842,10 @@ early_ret:
&varname_end, &has_type, &type_list, &type, &varname_end, &has_type, &type_list, &type,
is_class ? &init_expr: NULL) == FAIL) is_class ? &init_expr: NULL) == FAIL)
break; break;
if (is_reserved_varname(varname, varname_end))
{ if (is_reserved_varname(varname, varname_end)
vim_free(init_expr); || is_duplicate_variable(&classmembers, &objmembers,
break; varname, varname_end))
}
if (is_duplicate_variable(&classmembers, &objmembers, varname,
varname_end))
{ {
vim_free(init_expr); vim_free(init_expr);
break; break;
@@ -1758,6 +1876,7 @@ early_ret:
{ {
exarg_T ea; exarg_T ea;
garray_T lines_to_free; garray_T lines_to_free;
int is_new = STRNCMP(p, "new", 3) == 0;
if (has_public) if (has_public)
{ {
@@ -1774,12 +1893,17 @@ early_ret:
break; break;
} }
if (*p == '_' && *(p + 1) == '_') if (!is_class && *p == '_')
{ {
// double underscore prefix for a method name is currently // private methods are not supported in an interface
// reserved. This could be used in the future to support semsg(_(e_protected_method_not_supported_in_interface), p);
// object methods called by Vim builtin functions. break;
semsg(_(e_cannot_use_reserved_name_str), p); }
if (has_static && !is_new && SAFE_islower(*p) &&
is_valid_builtin_obj_methodname(p))
{
semsg(_(e_builtin_class_method_not_supported), p);
break; break;
} }
@@ -1803,9 +1927,9 @@ early_ret:
if (uf != NULL) if (uf != NULL)
{ {
char_u *name = uf->uf_name; 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 // private variables are not supported in an interface
semsg(_(e_protected_method_not_supported_in_interface), semsg(_(e_protected_method_not_supported_in_interface),
@@ -1813,8 +1937,10 @@ early_ret:
func_clear_free(uf, FALSE); func_clear_free(uf, FALSE);
break; 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); func_clear_free(uf, FALSE);
break; break;
@@ -1997,6 +2123,8 @@ early_ret:
&objmethods) == FAIL) &objmethods) == FAIL)
goto cleanup; goto cleanup;
update_builtin_method_index(cl);
cl->class_type.tt_type = VAR_CLASS; cl->class_type.tt_type = VAR_CLASS;
cl->class_type.tt_class = cl; cl->class_type.tt_class = cl;
cl->class_object_type.tt_type = VAR_OBJECT; cl->class_object_type.tt_type = VAR_OBJECT;
@@ -3272,6 +3400,125 @@ is_class_name(char_u *name, typval_T *rettv)
return FALSE; return FALSE;
} }
/*
* Calls the object builtin method "name" with arguments "argv". The value
* returned by the builtin method is in "rettv". Returns OK or FAIL.
*/
static int
object_call_builtin_method(
object_T *obj,
class_builtin_T builtin_method,
int argc,
typval_T *argv,
typval_T *rettv)
{
ufunc_T *uf;
int midx;
if (obj == NULL)
return FAIL;
uf = class_get_builtin_method(obj->obj_class, builtin_method, &midx);
if (uf == NULL)
return FAIL;
funccall_T *fc = create_funccal(uf, rettv);
int r;
if (fc == NULL)
return FAIL;
++obj->obj_refcount;
r = call_def_function(uf, argc, argv, 0, NULL, obj, fc, rettv);
remove_funccal();
return r;
}
/*
* Calls the object "empty()" method and returns the method retun value. In
* case of an error, returns TRUE.
*/
int
object_empty(object_T *obj)
{
typval_T rettv;
if (object_call_builtin_method(obj, CLASS_BUILTIN_EMPTY, 0, NULL, &rettv)
== FAIL)
return TRUE;
return tv_get_bool(&rettv);
}
/*
* Use the object "len()" method to get an object length. Returns 0 if the
* method is not found or there is an error.
*/
int
object_len(object_T *obj)
{
typval_T rettv;
if (object_call_builtin_method(obj, CLASS_BUILTIN_LEN, 0, NULL, &rettv)
== FAIL)
return 0;
return tv_to_number(&rettv);
}
/*
* Return a textual representation of object "obj"
*/
char_u *
object_string(
object_T *obj,
char_u *numbuf,
int copyID,
int echo_style,
int restore_copyID,
int composite_val)
{
typval_T rettv;
if (object_call_builtin_method(obj, CLASS_BUILTIN_STRING, 0, NULL, &rettv)
== OK
&& rettv.vval.v_string != NULL)
return rettv.vval.v_string;
else
{
garray_T ga;
ga_init2(&ga, 1, 50);
ga_concat(&ga, (char_u *)"object of ");
class_T *cl = obj == NULL ? NULL : obj->obj_class;
ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
: cl->class_name);
if (cl != NULL)
{
ga_concat(&ga, (char_u *)" {");
for (int i = 0; i < cl->class_obj_member_count; ++i)
{
if (i > 0)
ga_concat(&ga, (char_u *)", ");
ocmember_T *m = &cl->class_obj_members[i];
ga_concat(&ga, m->ocm_name);
ga_concat(&ga, (char_u *)": ");
char_u *tf = NULL;
ga_concat(&ga, echo_string_core(
(typval_T *)(obj + 1) + i,
&tf, numbuf, copyID, echo_style,
restore_copyID, composite_val));
vim_free(tf);
}
ga_concat(&ga, (char_u *)"}");
}
return ga.ga_data;
}
}
/* /*
* Return TRUE when the class "cl", its base class or one of the implemented * Return TRUE when the class "cl", its base class or one of the implemented
* interfaces matches the class "other_cl". * interfaces matches the class "other_cl".

View File

@@ -1013,6 +1013,32 @@ failret:
return FAIL; return FAIL;
} }
/*
* Compile a builtin method call of an object (e.g. string(), len(), empty(),
* etc.) if the class implements it.
*/
static int
compile_builtin_method_call(cctx_T *cctx, class_builtin_T builtin_method)
{
type_T *type = get_decl_type_on_stack(cctx, 0);
int res = FAIL;
// If the built in function is invoked on an object and the class
// implements the corresponding built in method, then invoke the object
// method.
if (type->tt_type == VAR_OBJECT)
{
int method_idx;
ufunc_T *uf = class_get_builtin_method(type->tt_class, builtin_method,
&method_idx);
if (uf != NULL)
res = generate_CALL(cctx, uf, type->tt_class, method_idx, 0);
}
return res;
}
/* /*
* Compile a function call: name(arg1, arg2) * Compile a function call: name(arg1, arg2)
* "arg" points to "name", "arg + varlen" to the "(". * "arg" points to "name", "arg + varlen" to the "(".
@@ -1170,6 +1196,20 @@ compile_call(
idx = -1; 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) if (idx >= 0)
res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); res = generate_BCALL(cctx, idx, argcount, argcount_init == 1);
} }