diff options
author | LemonBoy <thatlemon@gmail.com> | 2023-08-23 21:08:11 +0200 |
---|---|---|
committer | Christian Brabandt <cb@256bit.org> | 2023-08-23 21:08:11 +0200 |
commit | afe0466fb1695fa8b9782eea8a8e9f9540d4cb85 (patch) | |
tree | f71446a214e45f7c0cdcb186fe40d25d1a1da39d /src | |
parent | 1193951bebcff50d88403ce17dec5d3be14f131d (diff) |
patch 9.0.1786: Vim9: need instanceof() functionv9.0.1786
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 <cb@256bit.org>
Co-authored-by: LemonBoy <thatlemon@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/errors.h | 12 | ||||
-rw-r--r-- | src/evalfunc.c | 26 | ||||
-rw-r--r-- | src/globals.h | 16 | ||||
-rw-r--r-- | src/proto/typval.pro | 2 | ||||
-rw-r--r-- | src/proto/vim9class.pro | 2 | ||||
-rw-r--r-- | src/testdir/test_vim9_builtin.vim | 18 | ||||
-rw-r--r-- | src/testdir/test_vim9_class.vim | 33 | ||||
-rw-r--r-- | src/typval.c | 28 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9class.c | 64 | ||||
-rw-r--r-- | src/vim9type.c | 15 |
11 files changed, 199 insertions, 19 deletions
diff --git a/src/errors.h b/src/errors.h index 4b58cbbda8..ed77212d94 100644 --- 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_list[] 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 index 3e020bcde0..bdfd6325fe 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -277,6 +277,15 @@ arg_number(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) } /* + * 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 *decl_type UNUSED, argcontext_T *context } /* + * 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 index c1e2d6fbc4..68e7ef2a2d 100644 --- a/src/globals.h +++ b/src/globals.h @@ -534,7 +534,13 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE); #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 index 8cecdb8d81..db8f94ebb0 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -51,6 +51,8 @@ int check_for_list_or_dict_arg(typval_T *args, int idx); 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 index 707f4ec61c..ffe1c6444c 100644 --- 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 index d64366d03c..da8bc4299f 100644 --- 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 index 7e33c6d448..a650cc2509 100644 --- 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 index a760f356bf..e8aebee546 100644 --- a/src/typval.c +++ b/src/typval.c @@ -974,6 +974,34 @@ check_for_opt_buffer_or_dict_arg(typval_T *args, int idx) } /* + * 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 index 8758460886..05d6b05ea6 100644 --- 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, +/**/ 1785, /**/ 1784, diff --git a/src/vim9class.c b/src/vim9class.c index 00b1f7d98f..4f86a6fca5 100644 --- 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 index d16186ccb2..c42e4f4931 100644 --- 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; } |