]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1012: Vim9: class interface inheritance not correctly working v9.1.1012
authorYegappan Lakshmanan <yegappan@yahoo.com>
Mon, 13 Jan 2025 06:30:11 +0000 (07:30 +0100)
committerChristian Brabandt <cb@256bit.org>
Mon, 13 Jan 2025 06:30:11 +0000 (07:30 +0100)
Problem:  Vim9: class interface inheritance not correctly working
Solution: make the class inherit the interfaces of the super class

fixes: #16395
closes: #16412

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c

index c20de25337dc26065518417576224a6decab2a0f..23281bcad75f11d7af094fd377ea85759189a15b 100644 (file)
@@ -6096,44 +6096,151 @@ enddef
 
 " Test for using an interface method using a child object when it is overridden
 " by the child class.
-" FIXME: This test fails.
-" def Test_interface_overridden_method_from_child()
-"   var lines =<< trim END
-"     vim9script
-"
-"     interface A
-"       def Foo(): string
-"     endinterface
-"
-"     class B implements A
-"       def Foo(): string
-"         return 'b-foo'
-"       enddef
-"     endclass
-"
-"     class C extends B
-"       def Bar(): string
-"         return 'bar'
-"       enddef
-"       def Foo(): string
-"         return 'c-foo'
-"       enddef
-"     endclass
-"
-"     def T1(a: A)
-"       assert_equal('c-foo', a.Foo())
-"     enddef
-"
-"     def T2(b: B)
-"       assert_equal('c-foo', b.Foo())
-"     enddef
-"
-"     var c = C.new()
-"     T1(c)
-"     T2(c)
-"   END
-"   v9.CheckSourceSuccess(lines)
-" enddef
+def Test_interface_overridden_method_from_child()
+  var lines =<< trim END
+    vim9script
+
+    interface A
+      def Foo(): string
+    endinterface
+
+    class B implements A
+      def Foo(): string
+        return 'b-foo'
+      enddef
+    endclass
+
+    class C extends B
+      def Bar(): string
+        return 'bar'
+      enddef
+      def Foo(): string
+        return 'c-foo'
+      enddef
+    endclass
+
+    def T1(a: A)
+      assert_equal('c-foo', a.Foo())
+    enddef
+
+    def T2(b: B)
+      assert_equal('c-foo', b.Foo())
+    enddef
+
+    var c = C.new()
+    T1(c)
+    T2(c)
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
+
+" Test for interface inheritance
+def Test_interface_inheritance()
+  var lines =<< trim END
+    vim9script
+
+    interface A
+      def A_Fn(): string
+    endinterface
+
+    interface B
+      def B_Fn(): string
+    endinterface
+
+    interface C
+      def C_Fn(): string
+    endinterface
+
+    class C1 implements A
+      def A_Fn(): string
+        return 'c1-a'
+      enddef
+    endclass
+
+    class C2 extends C1 implements B
+      def B_Fn(): string
+        return 'c2-b'
+      enddef
+      def A_Fn(): string
+        return 'c2-a'
+      enddef
+    endclass
+
+    class C3 extends C2 implements C
+      def C_Fn(): string
+        return 'c3-c'
+      enddef
+      def A_Fn(): string
+        return 'c3-a'
+      enddef
+      def B_Fn(): string
+        return 'c3-b'
+      enddef
+    endclass
+
+    def T1(a: A, s: string)
+      assert_equal(s, a.A_Fn())
+    enddef
+
+    def T2(b: B, s: string)
+      assert_equal(s, b.B_Fn())
+    enddef
+
+    def T3(c: C, s: string)
+      assert_equal(s, c.C_Fn())
+    enddef
+
+    def T4(c1: C1)
+      T1(c1, 'c3-a')
+    enddef
+
+    def T5(c2: C2)
+      T1(c2, 'c3-a')
+      T2(c2, 'c3-b')
+    enddef
+
+    def T6(c3: C3)
+      T1(c3, 'c3-a')
+      T2(c3, 'c3-b')
+      T3(c3, 'c3-c')
+    enddef
+
+    var o3 = C3.new()
+    T4(o3)
+    T5(o3)
+    T6(o3)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Both the parent and child classes implement the same interface
+  lines =<< trim END
+    vim9script
+
+    interface I
+      def Foo(): string
+    endinterface
+
+    class A implements I
+      def Foo(): string
+        return 'A-foo'
+      enddef
+    endclass
+
+    class B implements I
+      def Foo(): string
+        return 'B-foo'
+      enddef
+    endclass
+
+    def Bar(i1: I): string
+      return i1.Foo()
+    enddef
+
+    var b = B.new()
+    assert_equal('B-foo', Bar(b))
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
 
 " Test for abstract methods
 def Test_abstract_method()
@@ -7282,6 +7389,44 @@ def Test_implement_interface_with_different_variable_order()
   v9.CheckSourceSuccess(lines)
 enddef
 
+" Test for inheriting interfaces from an imported super class
+def Test_interface_inheritance_with_imported_super()
+  var lines =<< trim END
+    vim9script
+
+    export interface I
+      def F(): string
+    endinterface
+
+    export class A implements I
+      def F(): string
+        return 'A'
+      enddef
+    endclass
+  END
+  writefile(lines, 'Xinheritintfimportclass.vim', 'D')
+
+  lines =<< trim END
+    vim9script
+
+    import './Xinheritintfimportclass.vim' as i_imp
+
+    # class C extends i_imp.A
+    class C extends i_imp.A implements i_imp.I
+      def F(): string
+        return 'C'
+      enddef
+    endclass
+
+    def TestI(i: i_imp.I): string
+        return i.F()
+    enddef
+
+    assert_equal('C', TestI(C.new()))
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
+
 " Test for using "any" type for a variable in a sub-class while it has a
 " concrete type in the interface
 def Test_implements_using_var_type_any()
index a008d32404a78b66178faac262084b2e86ed7639..9dddabc05a60bfb8a6a07d66e3bad3cec8467dd3 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1012,
 /**/
     1011,
 /**/
index 7c7700ba19bd413b866a7ba8bc71121c4f672fa7..c3ccf0250a5fc772c12df0cf17be8ff71dc91a84 100644 (file)
@@ -782,7 +782,7 @@ validate_interface_methods(
     static int
 validate_implements_classes(
     garray_T   *impl_gap,
-    class_T    **intf_classes,
+    garray_T   *intf_classes_gap,
     garray_T   *objmethods_gap,
     garray_T   *objmembers_gap,
     class_T    *extends_cl)
@@ -812,7 +812,15 @@ validate_implements_classes(
        }
 
        class_T *ifcl = tv.vval.v_class;
-       intf_classes[i] = ifcl;
+       if (ga_grow(intf_classes_gap, 1) == FAIL)
+       {
+           success = FALSE;
+           clear_tv(&tv);
+           break;
+       }
+       ((class_T **)intf_classes_gap->ga_data)[intf_classes_gap->ga_len]
+                                                               = ifcl;
+       intf_classes_gap->ga_len++;
        ++ifcl->class_refcount;
 
        // check the variables of the interface match the members of the class
@@ -830,6 +838,80 @@ validate_implements_classes(
     return success;
 }
 
+/*
+ * Returns TRUE if the interface class "ifcl" is already present in the
+ * "intf_classes_gap" grow array.
+ */
+    static int
+is_interface_class_present(garray_T *intf_classes_gap, class_T *ifcl)
+{
+    for (int j = 0; j < intf_classes_gap->ga_len; j++)
+    {
+       if (((class_T **)intf_classes_gap)[j] == ifcl)
+           return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*
+ * Add interface "ifcl" from a super class to "intf_classes_gap" and the class
+ * name to "impl_gap".
+ */
+    static int
+add_interface_from_super_class(
+    class_T    *ifcl,
+    garray_T   *impl_gap,
+    garray_T   *intf_classes_gap)
+{
+    char_u     *intf_name;
+
+    // Add the interface name to "impl_gap"
+    intf_name = vim_strsave(ifcl->class_name);
+    if (intf_name == NULL)
+       return FALSE;
+
+    if (ga_grow(impl_gap, 1) == FAIL)
+       return FALSE;
+
+    char_u **intf_names = (char_u **)impl_gap->ga_data;
+    intf_names[impl_gap->ga_len] = intf_name;
+    impl_gap->ga_len++;
+
+    // Add the interface class to "intf_classes_gap"
+    if (ga_grow(intf_classes_gap, 1) == FAIL)
+       return FALSE;
+
+    class_T **intf_classes = (class_T **)intf_classes_gap->ga_data;
+    intf_classes[intf_classes_gap->ga_len] = ifcl;
+    intf_classes_gap->ga_len++;
+    ++ifcl->class_refcount;
+
+    return TRUE;
+}
+
+/*
+ * Add "super" class interfaces to "intf_classes_gap" (if not present already)
+ * Add the interface class names to "impl_gap".
+ */
+    static int
+add_super_class_interfaces(
+    class_T    *super,
+    garray_T   *impl_gap,
+    garray_T   *intf_classes_gap)
+{
+    // Iterate through all the interfaces implemented by "super"
+    for (int i = 0; i < super->class_interface_count; i++)
+    {
+       class_T *ifcl = super->class_interfaces_cl[i];
+
+       if (!is_interface_class_present(intf_classes_gap, ifcl))
+           add_interface_from_super_class(ifcl, impl_gap, intf_classes_gap);
+    }
+
+    return TRUE;
+}
+
 /*
  * Check no function argument name is used as a class member.
  * (Object members are always accessed with "this." prefix, so no need
@@ -2427,14 +2509,23 @@ early_ret:
        success = validate_abstract_class_methods(&classfunctions,
                                                &objmethods, extends_cl);
 
+    // Process the "implements" entries
     // Check all "implements" entries are valid.
-    if (success && ga_impl.ga_len > 0)
-    {
-       intf_classes = ALLOC_CLEAR_MULT(class_T *, ga_impl.ga_len);
+    garray_T  intf_classes_ga;
 
-       success = validate_implements_classes(&ga_impl, intf_classes,
+    ga_init2(&intf_classes_ga, sizeof(class_T *), 5);
+
+    if (success && ga_impl.ga_len > 0)
+       success = validate_implements_classes(&ga_impl, &intf_classes_ga,
                                        &objmethods, &objmembers, extends_cl);
-    }
+
+    // inherit the super class interfaces
+    if (success && extends_cl != NULL)
+       success = add_super_class_interfaces(extends_cl, &ga_impl,
+                                                       &intf_classes_ga);
+
+    intf_classes = intf_classes_ga.ga_data;
+    intf_classes_ga.ga_len = 0;
 
     // Check no function argument name is used as a class member.
     if (success)