]> granicus.if.org Git - vim/commitdiff
patch 9.0.1152: class "implements" argument not implemented v9.0.1152
authorBram Moolenaar <Bram@vim.org>
Fri, 6 Jan 2023 18:42:20 +0000 (18:42 +0000)
committerBram Moolenaar <Bram@vim.org>
Fri, 6 Jan 2023 18:42:20 +0000 (18:42 +0000)
Problem:    Class "implements" argument not implemented.
Solution:   Implement "implements" argument.  Add basic checks for when a
            class implements an interface.

src/alloc.c
src/errors.h
src/eval.c
src/evalvars.c
src/structs.h
src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c

index 583ea87743e4d6ac92549551218d1f2fee05d99e..e3e6d0430bae40a83815ac51add280fa1ab84624 100644 (file)
@@ -813,7 +813,7 @@ ga_copy_string(garray_T *gap, char_u *p)
 
 /*
  * Add string "p" to "gap".
- * When out of memory "p" is freed and FAIL is returned.
+ * When out of memory FAIL is returned (caller may want to free "p").
  */
     int
 ga_add_string(garray_T *gap, char_u *p)
index b86b1c33f29b2b482ba6121d991cb50ccdcb3872..7344e2ef4a9d7542ce54435efad03350741c8b2b 100644 (file)
@@ -3414,4 +3414,12 @@ EXTERN char e_cannot_initialize_member_in_interface[]
        INIT(= N_("E1344: Cannot initialize a member in an interface"));
 EXTERN char e_not_valid_command_in_interface_str[]
        INIT(= N_("E1345: Not a valid command in an interface: %s"));
+EXTERN char e_interface_name_not_found_str[]
+       INIT(= N_("E1346: Interface name not found: %s"));
+EXTERN char e_not_valid_interface_str[]
+       INIT(= N_("E1347: Not a valid interface: %s"));
+EXTERN char e_member_str_of_interface_str_not_implemented[]
+       INIT(= N_("E1348: Member \"%s\" of interface \"%s\" not implemented"));
+EXTERN char e_function_str_of_interface_str_not_implemented[]
+       INIT(= N_("E1349: Function \"%s\" of interface \"%s\" not implemented"));
 #endif
index c9d2151dad14be9df11b9ec95ee418885fe3bb3a..0460dec3cc7407f51cb54b3de6a4f3e8e800be19 100644 (file)
@@ -5676,7 +5676,8 @@ set_ref_in_item(
        case VAR_CLASS:
            {
                class_T *cl = tv->vval.v_class;
-               if (cl != NULL && cl->class_copyID != copyID)
+               if (cl != NULL && cl->class_copyID != copyID
+                                 && (cl->class_flags && CLASS_INTERFACE) == 0)
                {
                    cl->class_copyID = copyID;
                    for (int i = 0; !abort
index a1970427a2c114806d103f08e653bbf0f83b43d2..c2cb61c37a0d01f57ec25bc44ac9df13c3588e16 100644 (file)
@@ -2913,7 +2913,7 @@ set_cmdarg(exarg_T *eap, char_u *oldarg)
     int
 eval_variable(
     char_u     *name,
-    int                len,            // length of "name"
+    int                len,            // length of "name" or zero
     scid_T     sid,            // script ID for imported item or zero
     typval_T   *rettv,         // NULL when only checking existence
     dictitem_T **dip,          // non-NULL when typval's dict item is needed
@@ -2923,12 +2923,15 @@ eval_variable(
     typval_T   *tv = NULL;
     int                found = FALSE;
     hashtab_T  *ht = NULL;
-    int                cc;
+    int                cc = 0;
     type_T     *type = NULL;
 
-    // truncate the name, so that we can use strcmp()
-    cc = name[len];
-    name[len] = NUL;
+    if (len > 0)
+    {
+       // truncate the name, so that we can use strcmp()
+       cc = name[len];
+       name[len] = NUL;
+    }
 
     // Check for local variable when debugging.
     if ((tv = lookup_debug_var(name)) == NULL)
@@ -3095,7 +3098,8 @@ eval_variable(
        }
     }
 
-    name[len] = cc;
+    if (len > 0)
+       name[len] = cc;
 
     return ret;
 }
index bcf9d03d2fca6709db9eb75a6c970d5fe0851e49..78108c18ffe6e38bea8d1f58dc57c3cd74fd1889 100644 (file)
@@ -1494,6 +1494,10 @@ struct class_S
     int                class_refcount;
     int                class_copyID;           // used by garbage collection
 
+    // interfaces declared for the class
+    int                class_interface_count;
+    char_u     **class_interfaces;     // allocated array of names
+
     // class members: "static varname"
     int                class_class_member_count;
     ocmember_T *class_class_members;   // allocated
index 8c37a609062550aabd1c12fcec14acd12f737af5..f8fb494c41cc962dbe8493045279fc2bb18ecb25 100644 (file)
@@ -612,5 +612,58 @@ def Test_interface_basics()
   v9.CheckScriptFailure(lines, 'E1345: Not a valid command in an interface: return 5')
 enddef
 
+def Test_class_implements_interface()
+  var lines =<< trim END
+      vim9script
+
+      interface Some
+        static count: number
+        def Method(nr: number)
+      endinterface
+
+      class SomeImpl implements Some
+        static count: number
+        def Method(nr: number)
+          echo nr
+        enddef
+      endclass
+  END
+  v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+      vim9script
+
+      interface Some
+        static counter: number
+        def Method(nr: number)
+      endinterface
+
+      class SomeImpl implements Some
+        static count: number
+        def Method(nr: number)
+          echo nr
+        enddef
+      endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1348: Member "counter" of interface "Some" not implemented')
+
+  lines =<< trim END
+      vim9script
+
+      interface Some
+        static count: number
+        def Methods(nr: number)
+      endinterface
+
+      class SomeImpl implements Some
+        static count: number
+        def Method(nr: number)
+          echo nr
+        enddef
+      endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1349: Function "Methods" of interface "Some" not implemented')
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 78fad97ce04e5c10f9989cf773352d22f92e24b7..74af145cdbc60a9c9205e8e1185fef05af1c2ce3 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1152,
 /**/
     1151,
 /**/
index 682211a59f1c7472f0b122ae6ff9debffc989c0b..eba21ce6e9fcd0515183348f6d5447b13f026145 100644 (file)
@@ -227,15 +227,50 @@ ex_class(exarg_T *eap)
        semsg(_(e_white_space_required_after_name_str), arg);
        return;
     }
+    char_u *name_start = arg;
 
     // TODO:
     //    generics: <Tkey, Tentry>
-    //    extends SomeClass
-    //    implements SomeInterface
-    //    specifies SomeInterface
-    //    check that nothing follows
     //   handle "is_export" if it is set
 
+    // Names for "implements SomeInterface"
+    garray_T   ga_impl;
+    ga_init2(&ga_impl, sizeof(char_u *), 5);
+
+    arg = skipwhite(name_end);
+    while (*arg != NUL && *arg != '#' && *arg != '\n')
+    {
+       // TODO:
+       //    extends SomeClass
+       //    specifies SomeInterface
+       if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10]))
+       {
+           arg = skipwhite(arg + 10);
+           char_u *impl_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
+           if (!IS_WHITE_OR_NUL(*impl_end))
+           {
+               semsg(_(e_white_space_required_after_name_str), arg);
+               goto early_ret;
+           }
+           char_u *iname = vim_strnsave(arg, impl_end - arg);
+           if (iname == NULL)
+               goto early_ret;
+           if (ga_add_string(&ga_impl, iname) == FAIL)
+           {
+               vim_free(iname);
+               goto early_ret;
+           }
+           arg = skipwhite(impl_end);
+       }
+       else
+       {
+           semsg(_(e_trailing_characters_str), arg);
+early_ret:
+           ga_clear_strings(&ga_impl);
+           return;
+       }
+    }
+
     garray_T   type_list;          // list of pointers to allocated types
     ga_init2(&type_list, sizeof(type_T *), 10);
 
@@ -438,6 +473,114 @@ ex_class(exarg_T *eap)
     }
     vim_free(theline);
 
+    // Check a few things before defining the class.
+    if (success && ga_impl.ga_len > 0)
+    {
+       // Check all "implements" entries are valid and correct.
+       for (int i = 0; i < ga_impl.ga_len && success; ++i)
+       {
+           char_u *impl = ((char_u **)ga_impl.ga_data)[i];
+           typval_T tv;
+           tv.v_type = VAR_UNKNOWN;
+           if (eval_variable(impl, 0, 0, &tv, NULL,
+                                  EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT) == FAIL)
+           {
+               semsg(_(e_interface_name_not_found_str), impl);
+               success = FALSE;
+               break;
+           }
+
+           if (tv.v_type != VAR_CLASS
+                   || tv.vval.v_class == NULL
+                   || (tv.vval.v_class->class_flags & CLASS_INTERFACE) == 0)
+           {
+               semsg(_(e_not_valid_interface_str), impl);
+               success = FALSE;
+           }
+
+           // check the members of the interface match the members of the class
+           class_T *ifcl = tv.vval.v_class;
+           for (int loop = 1; loop <= 2 && success; ++loop)
+           {
+               // loop == 1: check class members
+               // loop == 2: check object members
+               int if_count = loop == 1 ? ifcl->class_class_member_count
+                                        : ifcl->class_obj_member_count;
+               if (if_count == 0)
+                   continue;
+               ocmember_T *if_ms = loop == 1 ? ifcl->class_class_members
+                                              : ifcl->class_obj_members;
+               ocmember_T *cl_ms = (ocmember_T *)(loop == 1
+                                                   ? classmembers.ga_data
+                                                   : objmembers.ga_data);
+               int cl_count = loop == 1 ? classmembers.ga_len
+                                                          : objmembers.ga_len;
+               for (int if_i = 0; if_i < if_count; ++if_i)
+               {
+                   int cl_i;
+                   for (cl_i = 0; cl_i < cl_count; ++cl_i)
+                   {
+                       ocmember_T *m = &cl_ms[cl_i];
+                       if (STRCMP(if_ms[if_i].ocm_name, m->ocm_name) == 0)
+                       {
+                           // TODO: check type
+                           break;
+                       }
+                   }
+                   if (cl_i == cl_count)
+                   {
+                       semsg(_(e_member_str_of_interface_str_not_implemented),
+                                                  if_ms[if_i].ocm_name, impl);
+                       success = FALSE;
+                       break;
+                   }
+               }
+           }
+
+           // check the functions/methods of the interface match the
+           // functions/methods of the class
+           for (int loop = 1; loop <= 2 && success; ++loop)
+           {
+               // loop == 1: check class functions
+               // loop == 2: check object methods
+               int if_count = loop == 1 ? ifcl->class_class_function_count
+                                        : ifcl->class_obj_method_count;
+               if (if_count == 0)
+                   continue;
+               ufunc_T **if_fp = loop == 1 ? ifcl->class_class_functions
+                                           : ifcl->class_obj_methods;
+               ufunc_T **cl_fp = (ufunc_T **)(loop == 1
+                                               ? classfunctions.ga_data
+                                               : objmethods.ga_data);
+               int cl_count = loop == 1 ? classfunctions.ga_len
+                                                          : objmethods.ga_len;
+               for (int if_i = 0; if_i < if_count; ++if_i)
+               {
+                   char_u *if_name = if_fp[if_i]->uf_name;
+                   int cl_i;
+                   for (cl_i = 0; cl_i < cl_count; ++cl_i)
+                   {
+                       char_u *cl_name = cl_fp[cl_i]->uf_name;
+                       if (STRCMP(if_name, cl_name) == 0)
+                       {
+                           // TODO: check return and argument types
+                           break;
+                       }
+                   }
+                   if (cl_i == cl_count)
+                   {
+                       semsg(_(e_function_str_of_interface_str_not_implemented),
+                                                               if_name, impl);
+                       success = FALSE;
+                       break;
+                   }
+               }
+           }
+
+           clear_tv(&tv);
+       }
+    }
+
     class_T *cl = NULL;
     if (success)
     {
@@ -450,10 +593,23 @@ ex_class(exarg_T *eap)
            cl->class_flags = CLASS_INTERFACE;
 
        cl->class_refcount = 1;
-       cl->class_name = vim_strnsave(arg, name_end - arg);
+       cl->class_name = vim_strnsave(name_start, name_end - name_start);
        if (cl->class_name == NULL)
            goto cleanup;
 
+       if (ga_impl.ga_len > 0)
+       {
+           // Move the "implements" names into the class.
+           cl->class_interface_count = ga_impl.ga_len;
+           cl->class_interfaces = ALLOC_MULT(char_u *, ga_impl.ga_len);
+           if (cl->class_interfaces == NULL)
+               goto cleanup;
+           for (int i = 0; i < ga_impl.ga_len; ++i)
+               cl->class_interfaces[i] = ((char_u **)ga_impl.ga_data)[i];
+           CLEAR_POINTER(ga_impl.ga_data);
+           ga_impl.ga_len = 0;
+       }
+
        // Add class and object members to "cl".
        if (add_members_to_class(&classmembers,
                                    &cl->class_class_members,
@@ -499,7 +655,7 @@ ex_class(exarg_T *eap)
                have_new = TRUE;
                break;
            }
-       if (!have_new)
+       if (is_class && !have_new)
        {
            // No new() method was defined, add the default constructor.
            garray_T fga;
@@ -589,6 +745,7 @@ ex_class(exarg_T *eap)
        // - Fill hashtab with object members and methods ?
 
        // Add the class to the script-local variables.
+       // TODO: handle other context, e.g. in a function
        typval_T tv;
        tv.v_type = VAR_CLASS;
        tv.vval.v_class = cl;
@@ -607,6 +764,8 @@ cleanup:
        vim_free(cl);
     }
 
+    ga_clear_strings(&ga_impl);
+
     for (int round = 1; round <= 2; ++round)
     {
        garray_T *gap = round == 1 ? &classmembers : &objmembers;
@@ -986,6 +1145,10 @@ class_unref(class_T *cl)
        // be freed.
        VIM_CLEAR(cl->class_name);
 
+       for (int i = 0; i < cl->class_interface_count; ++i)
+           vim_free(((char_u **)cl->class_interfaces)[i]);
+       vim_free(cl->class_interfaces);
+
        for (int i = 0; i < cl->class_class_member_count; ++i)
        {
            ocmember_T *m = &cl->class_class_members[i];