]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1786: Vim9: need instanceof() function v9.0.1786
authorLemonBoy <thatlemon@gmail.com>
Wed, 23 Aug 2023 19:08:11 +0000 (21:08 +0200)
committerChristian Brabandt <cb@256bit.org>
Wed, 23 Aug 2023 19:08:11 +0000 (21:08 +0200)
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>
13 files changed:
runtime/doc/builtin.txt
runtime/doc/usr_41.txt
src/errors.h
src/evalfunc.c
src/globals.h
src/proto/typval.pro
src/proto/vim9class.pro
src/testdir/test_vim9_builtin.vim
src/testdir/test_vim9_class.vim
src/typval.c
src/version.c
src/vim9class.c
src/vim9type.c

index 99bc9e2be71cf6f34ec03c23bfe9e99597b21afe..236454793792e9e56656cf4ec06302a74d4866d5 100644 (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
index 2112fbb1b416f38253ba8db44a0a8340ab931e11..d5aa79e31724904041c3d781316da7cb525959fe 100644 (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
index 4b58cbbda84593c6f4fa8680fe022fbdf37ad18a..ed77212d94eb828fdce3fb80e0cf16924bebdc1e 100644 (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[]
index 3e020bcde074f646b3228283d8962813c46125bc..bdfd6325fed841919063b18d00bd6acaf049599a 100644 (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,
index c1e2d6fbc42c2c644ba94c52f1c6ef744d4d17fb..68e7ef2a2d85843795e7b12015f235cfb50a71e6 100644 (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
 ;
index 8cecdb8d812e716a235baa727c1a69d70806f9ba..db8f94ebb0f8d20a3f6ce33ebcd2a6c6d294e502 100644 (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);
index 707f4ec61c7483ea18ddfd646e9633a421e6a2f3..ffe1c6444ce3a45447c2345925d5e86674cc10be 100644 (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 : */
index d64366d03ccdffa2a58116f88574afc7b0ebbbdd..da8bc4299f995114c9d9fbae7ca4a8283af4be63 100644 (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
index 7e33c6d448282423136d256bdcfd474c83c48869..a650cc2509da5422f4a021c5de8f3fd566ce9f93 100644 (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).
index a760f356bf4e745ac1e0e9646ee73ebfef400eb5..e8aebee54654ada63cf6d4847a09e202a4e14456 100644 (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.
index 87584608868f3d6205ebf0e5bb8443573c9dc222..05d6b05ea6948a35e17128ba5b331adf02bcc266 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1786,
 /**/
     1785,
 /**/
index 00b1f7d98fa19c22c6627e6bf883990b0be36824..4f86a6fca52c4daa6afcfe3a222dcff7d1473906 100644 (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
index d16186ccb2fcee04cb4e3a60040ca3381baf65f7..c42e4f4931943f93d06fe8350eca8ff80cc82f5f 100644 (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;
        }