0
0
mirror of https://github.com/vim/vim.git synced 2025-07-04 23:07:33 -04:00

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 <cb@256bit.org>
Co-authored-by: LemonBoy <thatlemon@gmail.com>
This commit is contained in:
LemonBoy 2023-08-23 21:08:11 +02:00 committed by Christian Brabandt
parent 1193951beb
commit afe0466fb1
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
13 changed files with 212 additions and 19 deletions

View File

@ -310,6 +310,7 @@ inputrestore() Number restore typeahead
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}]) *insert()*
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

View File

@ -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

View File

@ -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[]

View File

@ -276,6 +276,15 @@ arg_number(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
return check_arg_type(&t_number, type, 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'.
*/
@ -744,6 +753,20 @@ arg_string_or_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context
return FAIL;
}
/*
* 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.
*/
@ -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,

View File

@ -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
;

View File

@ -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);

View File

@ -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 : */

View File

@ -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

View File

@ -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).

View File

@ -973,6 +973,34 @@ check_for_opt_buffer_or_dict_arg(typval_T *args, int idx)
return OK;
}
/*
* 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.

View File

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

View File

@ -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

View File

@ -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;
}