# HG changeset patch # User Christian Brabandt # Date 1692818108 -7200 # Node ID e4851934751a78c507a02e542d1764395246c79c # Parent ccfca4f03a2b3bf4ab7064cfca7c0d0045687eb1 patch 9.0.1786: Vim9: need instanceof() function Commit: https://github.com/vim/vim/commit/afe0466fb1695fa8b9782eea8a8e9f9540d4cb85 Author: LemonBoy Date: Wed Aug 23 21:08:11 2023 +0200 patch 9.0.1786: Vim9: need instanceof() function Problem: Vim9: need instanceof() function Solution: Implement instanceof() builtin Implemented in the same form as Python's isinstance because it allows for checking multiple class types at the same time. closes: #12867 Signed-off-by: Christian Brabandt Co-authored-by: LemonBoy diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -310,6 +310,7 @@ inputrestore() Number restore typeahea inputsave() Number save and clear typeahead inputsecret({prompt} [, {text}]) String like input() but hiding the text insert({object}, {item} [, {idx}]) List insert {item} in {object} [before {idx}] +instanceof({object}, {class}) Number |TRUE| if {object} is an instance of {class} interrupt() none interrupt script execution invert({expr}) Number bitwise invert isabsolutepath({path}) Number |TRUE| if {path} is an absolute path @@ -5052,6 +5053,17 @@ insert({object}, {item} [, {idx}]) *in Can also be used as a |method|: > mylist->insert(item) +instanceof({object}, {class}) *instanceof()* + The result is a Number, which is |TRUE| when the {object} argument is a + direct or indirect instance of a |Class| specified by {class}. + When {class} is a |List| the function returns |TRUE| when {object} is an + instance of any of the specified classes. + Example: > + instanceof(animal, [Dog, Cat]) + +< Can also be used as a |method|: > + myobj->instanceof(mytype) + interrupt() *interrupt()* Interrupt script execution. It works more or less like the user typing CTRL-C, most commands won't execute and control diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -877,6 +877,7 @@ Other computation: *bitwise-function srand() initialize seed used by rand() Variables: *var-functions* + instanceof() check if a variable is an instance of a given class type() type of a variable as a number typename() type of a variable as text islocked() check if a variable is locked diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -1570,9 +1570,12 @@ EXTERN char e_too_many_signs_defined[] EXTERN char e_unknown_printer_font_str[] INIT(= N_("E613: Unknown printer font: %s")); #endif -// E614 unused -// E615 unused -// E616 unused +EXTERN char e_class_required[] + INIT(= N_("E614: Class required")); +EXTERN char e_object_required[] + INIT(= N_("E615: Object required")); +EXTERN char e_object_required_for_argument_nr[] + INIT(= N_("E616: Object required for argument %d")); #ifdef FEAT_GUI_GTK EXTERN char e_cannot_be_changed_in_gtk_GUI[] INIT(= N_("E617: Cannot be changed in the GTK GUI")); @@ -1777,7 +1780,8 @@ EXTERN char e_can_only_compare_list_with INIT(= N_("E691: Can only compare List with List")); EXTERN char e_invalid_operation_for_list[] INIT(= N_("E692: Invalid operation for List")); -// E693 unused +EXTERN char e_list_or_class_required_for_argument_nr[] + INIT(= N_("E693: List or Class required for argument %d")); EXTERN char e_invalid_operation_for_funcrefs[] INIT(= N_("E694: Invalid operation for Funcrefs")); EXTERN char e_cannot_index_a_funcref[] diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -277,6 +277,15 @@ arg_number(type_T *type, type_T *decl_ty } /* + * Check "type" is an object. + */ + static int +arg_object(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) +{ + return check_arg_type(&t_object, type, context); +} + +/* * Check "type" is a dict of 'any'. */ static int @@ -745,6 +754,20 @@ arg_string_or_func(type_T *type, type_T } /* + * Check "type" is a list of 'any' or a class. + */ + static int +arg_class_or_list(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) +{ + if (type->tt_type == VAR_CLASS + || type->tt_type == VAR_LIST + || type_any_or_unknown(type)) + return OK; + arg_type_mismatch(&t_class, type, context->arg_idx + 1); + return FAIL; +} + +/* * Check "type" is a list of 'any' or a blob or a string. */ static int @@ -1125,6 +1148,7 @@ static argcheck_T arg1_len[] = {arg_len1 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr}; static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool}; static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string_mod, arg_filter_func}; +static argcheck_T arg2_instanceof[] = {arg_object, arg_class_or_list}; static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func}; static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, NULL}; static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any}; @@ -2124,6 +2148,8 @@ static funcentry_T global_functions[] = ret_string, f_inputsecret}, {"insert", 2, 3, FEARG_1, arg23_insert, ret_first_arg, f_insert}, + {"instanceof", 2, 2, FEARG_1, arg2_instanceof, + ret_bool, f_instanceof}, {"interrupt", 0, 0, 0, NULL, ret_void, f_interrupt}, {"invert", 1, 1, FEARG_1, arg1_number, diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -534,7 +534,13 @@ EXTERN int garbage_collect_at_exit INIT( #define t_super (static_types[80]) #define t_const_super (static_types[81]) -EXTERN type_T static_types[82] +#define t_object (static_types[82]) +#define t_const_object (static_types[83]) + +#define t_class (static_types[84]) +#define t_const_class (static_types[85]) + +EXTERN type_T static_types[86] #ifdef DO_INIT = { // 0: t_unknown @@ -700,6 +706,14 @@ EXTERN type_T static_types[82] // 80: t_super (VAR_CLASS with tt_member set to &t_bool {VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL, NULL}, {VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_bool, NULL, NULL}, + + // 82: t_object + {VAR_OBJECT, 0, 0, TTFLAG_STATIC, NULL, NULL, NULL}, + {VAR_OBJECT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL}, + + // 84: t_class + {VAR_CLASS, 0, 0, TTFLAG_STATIC, NULL, NULL, NULL}, + {VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL}, } #endif ; diff --git a/src/proto/typval.pro b/src/proto/typval.pro --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -51,6 +51,8 @@ int check_for_list_or_dict_arg(typval_T int check_for_list_or_dict_or_blob_arg(typval_T *args, int idx); int check_for_list_or_dict_or_blob_or_string_arg(typval_T *args, int idx); int check_for_opt_buffer_or_dict_arg(typval_T *args, int idx); +int check_for_object_arg(typval_T *args, int idx); +int check_for_class_or_list_arg(typval_T *args, int idx); char_u *tv_get_string(typval_T *varp); char_u *tv_get_string_strict(typval_T *varp); char_u *tv_get_string_buf(typval_T *varp, char_u *buf); diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -15,4 +15,6 @@ void class_unref(class_T *cl); void object_created(object_T *obj); void object_cleared(object_T *obj); int object_free_nonref(int copyID); +void f_instanceof(typval_T *argvars, typval_T *rettv); +int class_instance_of(class_T *cl, class_T *other_cl); /* vim: set ft=c : */ diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -2301,6 +2301,24 @@ def Test_insert() v9.CheckDefAndScriptFailure(['insert([2, 3], 1, "x")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3']) enddef +def Test_instanceof() + var lines =<< trim END + vim9script + class Foo + endclass + instanceof('hello', Foo) + END + v9.CheckScriptFailure(lines, 'E616: Object required for argument 1') + + lines =<< trim END + vim9script + class Foo + endclass + instanceof(Foo.new(), 123) + END + v9.CheckScriptFailure(lines, 'E693: List or Class required for argument 2') +enddef + def Test_invert() v9.CheckDefAndScriptFailure(['invert("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1']) enddef 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 @@ -2367,6 +2367,39 @@ def Test_call_method_in_extended_class() v9.CheckScriptSuccess(lines) enddef +def Test_instanceof() + var lines =<< trim END + vim9script + + class Base1 + endclass + + class Base2 extends Base1 + endclass + + interface Intf1 + endinterface + + class Mix1 implements Intf1 + endclass + + class Base3 extends Mix1 + endclass + + var b1 = Base1.new() + var b2 = Base2.new() + var b3 = Base3.new() + + assert_true(instanceof(b1, Base1)) + assert_true(instanceof(b2, Base1)) + assert_false(instanceof(b1, Base2)) + assert_true(instanceof(b3, Mix1)) + assert_false(instanceof(b3, [])) + assert_true(instanceof(b3, [Base1, Base2, Intf1])) + END + v9.CheckScriptSuccess(lines) +enddef + " Test for calling a method in the parent class that is extended partially. " This used to fail with the 'E118: Too many arguments for function: Text' error " message (Github issue #12524). diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -974,6 +974,34 @@ check_for_opt_buffer_or_dict_arg(typval_ } /* + * Give an error and return FAIL unless "args[idx]" is an object. + */ + int +check_for_object_arg(typval_T *args, int idx) +{ + if (args[idx].v_type != VAR_OBJECT) + { + semsg(_(e_object_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/* + * Give an error and return FAIL unless "args[idx]" is a class or a list. + */ + int +check_for_class_or_list_arg(typval_T *args, int idx) +{ + if (args[idx].v_type != VAR_CLASS && args[idx].v_type != VAR_LIST) + { + semsg(_(e_list_or_class_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/* * Get the string value of a variable. * If it is a Number variable, the number is converted into a string. * tv_get_string() uses a single, static buffer. YOU CAN ONLY USE IT ONCE! diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1786, +/**/ 1783, /**/ 1782, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -1913,5 +1913,69 @@ object_free_nonref(int copyID) return did_free; } +/* + * Return TRUE when the class "cl", its base class or one of the implemented interfaces + * matches the class "other_cl". + */ + int +class_instance_of(class_T *cl, class_T *other_cl) +{ + if (cl == other_cl) + return TRUE; + + // Recursively check the base classes. + for (; cl != NULL; cl = cl->class_extends) + { + if (cl == other_cl) + return TRUE; + // Check the implemented interfaces. + for (int i = cl->class_interface_count - 1; i >= 0; --i) + if (cl->class_interfaces_cl[i] == other_cl) + return TRUE; + } + + return FALSE; +} + +/* + * "instanceof(object, classinfo)" function + */ + void +f_instanceof(typval_T *argvars, typval_T *rettv) +{ + typval_T *object_tv = &argvars[0]; + typval_T *classinfo_tv = &argvars[1]; + listitem_T *li; + + rettv->vval.v_number = VVAL_FALSE; + + if (check_for_object_arg(argvars, 0) == FAIL + || check_for_class_or_list_arg(argvars, 1) == FAIL) + return; + + if (classinfo_tv->v_type == VAR_LIST) + { + FOR_ALL_LIST_ITEMS(classinfo_tv->vval.v_list, li) + { + if (li->li_tv.v_type != VAR_CLASS) + { + emsg(_(e_class_required)); + return; + } + + if (class_instance_of(object_tv->vval.v_object->obj_class, + li->li_tv.vval.v_class) == TRUE) + { + rettv->vval.v_number = VVAL_TRUE; + return; + } + } + } + else if (classinfo_tv->v_type == VAR_CLASS) + { + rettv->vval.v_number = class_instance_of(object_tv->vval.v_object->obj_class, + classinfo_tv->vval.v_class); + } +} #endif // FEAT_EVAL diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -908,20 +908,7 @@ check_type_maybe( if (actual->tt_type != VAR_OBJECT) return FAIL; // don't use tt_class - // check the class, base class or an implemented interface matches - class_T *cl; - for (cl = actual->tt_class; cl != NULL; cl = cl->class_extends) - { - if (expected->tt_class == cl) - break; - int i; - for (i = cl->class_interface_count - 1; i >= 0; --i) - if (expected->tt_class == cl->class_interfaces_cl[i]) - break; - if (i >= 0) - break; - } - if (cl == NULL) + if (class_instance_of(actual->tt_class, expected->tt_class) == FALSE) ret = FAIL; }