diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim index cd67b54153..684bf6d1f6 100644 --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -135,6 +135,16 @@ def Test_class_basic() END v9.CheckScriptFailure(lines, 'E1069:') + # Test for unsupported comment specifier + lines =<< trim END + vim9script + class Something + # comment + #{ + endclass + END + v9.CheckScriptFailure(lines, 'E1170:') + lines =<< trim END vim9script @@ -162,6 +172,44 @@ def Test_class_basic() assert_equal('object', typename(pos)) END v9.CheckScriptSuccess(lines) + + # When referencing object methods, space cannot be used after a "." + lines =<< trim END + vim9script + class A + def Foo(): number + return 10 + enddef + endclass + var a = A.new() + var v = a. Foo() + END + v9.CheckScriptFailure(lines, 'E1202:') + + # Using an object without specifying a method or a member variable + lines =<< trim END + vim9script + class A + def Foo(): number + return 10 + enddef + endclass + var a = A.new() + var v = a. + END + v9.CheckScriptFailure(lines, 'E15:') + + # Error when parsing the arguments of an object method. + lines =<< trim END + vim9script + class A + def Foo() + enddef + endclass + var a = A.new() + var v = a.Foo(,) + END + v9.CheckScriptFailure(lines, 'E15:') enddef def Test_class_defined_twice() @@ -628,6 +676,15 @@ def Test_class_object_member_inits() endclass END v9.CheckScriptFailure(lines, 'E1330:') + + # Test for initializing an object member with an unknown variable/type + lines =<< trim END + vim9script + class A + this.value = init_val + endclass + END + v9.CheckScriptFailureList(lines, ['E121:', 'E1329:']) enddef def Test_class_object_member_access() @@ -658,6 +715,15 @@ def Test_class_object_member_access() END v9.CheckScriptSuccess(lines) + # Test for a public member variable name beginning with an underscore + lines =<< trim END + vim9script + class A + public this._val = 10 + endclass + END + v9.CheckScriptFailure(lines, 'E1332:') + lines =<< trim END vim9script @@ -735,6 +801,33 @@ def Test_class_object_member_access() .x END v9.CheckScriptSuccess(lines) + + # Test for "public" cannot be abbreviated + lines =<< trim END + vim9script + class Something + pub this.val = 1 + endclass + END + v9.CheckScriptFailure(lines, 'E1065:') + + # Test for "public" keyword must be followed by "this" or "static". + lines =<< trim END + vim9script + class Something + public val = 1 + endclass + END + v9.CheckScriptFailure(lines, 'E1331:') + + # Test for "static" cannot be abbreviated + lines =<< trim END + vim9script + class Something + stat this.val = 1 + endclass + END + v9.CheckScriptFailure(lines, 'E1065:') enddef def Test_class_object_compare() @@ -1002,6 +1095,42 @@ def Test_class_member() s.Method(7) END v9.CheckScriptFailure(lines, 'E1341: Variable already declared in the class: count') + + # Test for using an invalid type for a member variable + lines =<< trim END + vim9script + class A + this.val: xxx + endclass + END + v9.CheckScriptFailure(lines, 'E1010:') + + # Test for no space before or after the '=' when initializing a member + # variable + lines =<< trim END + vim9script + class A + this.val: number= 10 + endclass + END + v9.CheckScriptFailure(lines, 'E1004:') + lines =<< trim END + vim9script + class A + this.val: number =10 + endclass + END + v9.CheckScriptFailure(lines, 'E1004:') + + # Access a non-existing member + lines =<< trim END + vim9script + class A + endclass + var a = A.new() + var v = a.bar + END + v9.CheckScriptFailure(lines, 'E1326:') enddef func Test_class_garbagecollect() @@ -1072,6 +1201,18 @@ def Test_class_function() assert_equal(2, Value.GetCount()) END v9.CheckScriptSuccess(lines) + + # Test for cleaning up after a class definition failure when using class + # functions. + lines =<< trim END + vim9script + class A + static def Foo() + enddef + aaa + endclass + END + v9.CheckScriptFailure(lines, 'E1318:') enddef def Test_class_defcompile() @@ -1100,6 +1241,40 @@ def Test_class_defcompile() defcompile C.Fc END v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string') + + # Trying to compile a function using a non-existing class variable + lines =<< trim END + vim9script + defcompile x.Foo() + END + v9.CheckScriptFailure(lines, 'E475:') + + # Trying to compile a function using a variable which is not a class + lines =<< trim END + vim9script + var x: number + defcompile x.Foo() + END + v9.CheckScriptFailure(lines, 'E475:') + + # Trying to compile a function without specifying the name + lines =<< trim END + vim9script + class A + endclass + defcompile A. + END + v9.CheckScriptFailure(lines, 'E475:') + + # Trying to compile a non-existing class object member function + lines =<< trim END + vim9script + class A + endclass + var a = A.new() + defcompile a.Foo() + END + v9.CheckScriptFailureList(lines, ['E1334:', 'E475:']) enddef def Test_class_object_to_string() @@ -1345,6 +1520,62 @@ def Test_class_implements_interface() Test() END v9.CheckScriptSuccess(lines) + + # Interface name after "extends" doesn't end in a space or NUL character + lines =<< trim END + vim9script + interface A + endinterface + class B extends A" + endclass + END + v9.CheckScriptFailure(lines, 'E1315:') + + # Trailing characters after a class name + lines =<< trim END + vim9script + class A bbb + endclass + END + v9.CheckScriptFailure(lines, 'E488:') + + # using "implements" with a non-existing class + lines =<< trim END + vim9script + class A implements B + endclass + END + v9.CheckScriptFailure(lines, 'E1346:') + + # using "implements" with a regular class + lines =<< trim END + vim9script + class A + endclass + class B implements A + endclass + END + v9.CheckScriptFailure(lines, 'E1347:') + + # using "implements" with a variable + lines =<< trim END + vim9script + var T: number = 10 + class A implements T + endclass + END + v9.CheckScriptFailure(lines, 'E1347:') + + # all the class methods in an "interface" should be implemented + lines =<< trim END + vim9script + interface A + static def Foo() + endinterface + class B implements A + endclass + END + v9.CheckScriptFailure(lines, 'E1349:') enddef def Test_call_interface_method() @@ -1717,6 +1948,16 @@ def Test_class_extends() assert_equal("object of Success {success: true, value: 'asdf'}", string(v)) END v9.CheckScriptSuccess(lines) + + # class name after "extends" doesn't end in a space or NUL character + lines =<< trim END + vim9script + class A + endclass + class B extends A" + endclass + END + v9.CheckScriptFailure(lines, 'E1315:') enddef def Test_using_base_class() @@ -1841,6 +2082,16 @@ def Test_abstract_class() endclass END v9.CheckScriptFailure(lines, 'E1316:') + + # Abstract class cannot have a "new" function + lines =<< trim END + vim9script + abstract class Base + def new() + enddef + endclass + END + v9.CheckScriptFailure(lines, 'E1359:') enddef def Test_closure_in_class() @@ -2063,4 +2314,127 @@ def Test_call_method_in_parent_class() v9.CheckScriptSuccess(lines) enddef +" Test for calling methods from three levels of classes +def Test_multi_level_method_call() + var lines =<< trim END + vim9script + + var A_func1: number = 0 + var A_func2: number = 0 + var A_func3: number = 0 + var B_func2: number = 0 + var B_func3: number = 0 + var C_func3: number = 0 + + class A + def Func1() + A_func1 += 1 + enddef + + def Func2() + A_func2 += 1 + enddef + + def Func3() + A_func3 += 1 + enddef + endclass + + class B extends A + def Func2() + B_func2 += 1 + enddef + + def Func3() + B_func3 += 1 + enddef + endclass + + class C extends B + def Func3() + C_func3 += 1 + enddef + endclass + + def A_CallFuncs(a: A) + a.Func1() + a.Func2() + a.Func3() + enddef + + def B_CallFuncs(b: B) + b.Func1() + b.Func2() + b.Func3() + enddef + + def C_CallFuncs(c: C) + c.Func1() + c.Func2() + c.Func3() + enddef + + var cobj = C.new() + A_CallFuncs(cobj) + B_CallFuncs(cobj) + C_CallFuncs(cobj) + assert_equal(3, A_func1) + assert_equal(0, A_func2) + assert_equal(0, A_func3) + assert_equal(3, B_func2) + assert_equal(0, B_func3) + assert_equal(3, C_func3) + END + v9.CheckScriptSuccess(lines) +enddef + +" Test for using members from three levels of classes +def Test_multi_level_member_access() + var lines =<< trim END + vim9script + + class A + this.val1: number = 0 + this.val2: number = 0 + this.val3: number = 0 + endclass + + class B extends A + this.val2: number = 0 + this.val3: number = 0 + endclass + + class C extends B + this.val3: number = 0 + endclass + + def A_members(a: A) + a.val1 += 1 + a.val2 += 1 + a.val3 += 1 + enddef + + def B_members(b: B) + b.val1 += 1 + b.val2 += 1 + b.val3 += 1 + enddef + + def C_members(c: C) + c.val1 += 1 + c.val2 += 1 + c.val3 += 1 + enddef + + var cobj = C.new() + A_members(cobj) + B_members(cobj) + C_members(cobj) + assert_equal(3, cobj.val1) + assert_equal(3, cobj.val2) + assert_equal(3, cobj.val3) + END + v9.CheckScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index f120730eae..325a46efe6 100644 --- a/src/version.c +++ b/src/version.c @@ -695,6 +695,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1737, /**/ 1736, /**/ diff --git a/src/vim9class.c b/src/vim9class.c index b04fb0fecc..e310dec336 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -230,6 +230,117 @@ object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl) return table[idx]; } +/* + * Update the interface class lookup table for the member index on the + * interface to the member index in the class implementing the interface. + * And a lookup table for the object method index on the interface + * to the object method index in the class implementing the interface. + * This is also used for updating the lookup table for the extended class + * hierarchy. + */ + static int +update_member_method_lookup_table( + class_T *ifcl, + class_T *cl, + garray_T *objmethods, + int pobj_method_offset, + int is_interface) +{ + if (ifcl == NULL) + return OK; + + // Table for members. + itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T) + + ifcl->class_obj_member_count * sizeof(int)); + if (if2cl == NULL) + return FAIL; + if2cl->i2c_next = ifcl->class_itf2class; + ifcl->class_itf2class = if2cl; + if2cl->i2c_class = cl; + if2cl->i2c_is_method = FALSE; + + for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i) + for (int cl_i = 0; cl_i < cl->class_obj_member_count; ++cl_i) + { + if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, + cl->class_obj_members[cl_i].ocm_name) == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = cl_i; + break; + } + } + + // Table for methods. + if2cl = alloc_clear(sizeof(itf2class_T) + + ifcl->class_obj_method_count * sizeof(int)); + if (if2cl == NULL) + return FAIL; + if2cl->i2c_next = ifcl->class_itf2class; + ifcl->class_itf2class = if2cl; + if2cl->i2c_class = cl; + if2cl->i2c_is_method = TRUE; + + for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i) + { + int done = FALSE; + for (int cl_i = 0; cl_i < objmethods->ga_len; ++cl_i) + { + if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, + ((ufunc_T **)objmethods->ga_data)[cl_i]->uf_name) + == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = cl_i; + done = TRUE; + break; + } + } + + // extended class object method is not overridden by the child class. + // Keep the method declared in one of the parent classes in the + // lineage. + if (!done && !is_interface) + { + // If "ifcl" is not the immediate parent of "cl", then search in + // the intermediate parent classes. + if (cl->class_extends != ifcl) + { + class_T *parent = cl->class_extends; + int method_offset = objmethods->ga_len; + + while (!done && parent != NULL && parent != ifcl) + { + + for (int cl_i = 0; + cl_i < parent->class_obj_method_count_child; ++cl_i) + { + if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, + parent->class_obj_methods[cl_i]->uf_name) + == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = method_offset + cl_i; + done = TRUE; + break; + } + } + method_offset += parent->class_obj_method_count_child; + parent = parent->class_extends; + } + } + + if (!done) + { + int *table = (int *)(if2cl + 1); + table[if_i] = pobj_method_offset + if_i; + } + } + } + + return OK; +} + /* * Handle ":class" and ":abstract class" up to ":endclass". * Handle ":interface" up to ":endinterface". @@ -534,6 +645,7 @@ early_ret: { emsg(_(e_cannot_define_new_function_in_abstract_class)); success = FALSE; + func_clear_free(uf, FALSE); break; } garray_T *fgap = has_static || is_new @@ -654,6 +766,8 @@ early_ret: { semsg(_(e_not_valid_interface_str), impl); success = FALSE; + clear_tv(&tv); + break; } class_T *ifcl = tv.vval.v_class; @@ -839,83 +953,35 @@ early_ret: if (cl->class_interface_count > 0 || extends_cl != NULL) { - // For each interface add a lookuptable for the member index on the - // interface to the member index in this class. - // And a lookuptable for the object method index on the interface + // For each interface add a lookup table for the member index on + // the interface to the member index in this class. + // And a lookup table for the object method index on the interface // to the object method index in this class. - // Also do this for the extended class, if any. - for (int i = 0; i <= cl->class_interface_count; ++i) + for (int i = 0; i < cl->class_interface_count; ++i) { - class_T *ifcl = i < cl->class_interface_count - ? cl->class_interfaces_cl[i] - : extends_cl; - if (ifcl == NULL) - continue; + class_T *ifcl = cl->class_interfaces_cl[i]; - // Table for members. - itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T) - + ifcl->class_obj_member_count * sizeof(int)); - if (if2cl == NULL) + if (update_member_method_lookup_table(ifcl, cl, &objmethods, + 0, TRUE) == FAIL) goto cleanup; - if2cl->i2c_next = ifcl->class_itf2class; - ifcl->class_itf2class = if2cl; - if2cl->i2c_class = cl; - if2cl->i2c_is_method = FALSE; + } - for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i) - for (int cl_i = 0; cl_i < cl->class_obj_member_count; - ++cl_i) - { - if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, - cl->class_obj_members[cl_i].ocm_name) == 0) - { - int *table = (int *)(if2cl + 1); - table[if_i] = cl_i; - break; - } - } + // Update the lookup table for the extended class, if nay + if (extends_cl != NULL) + { + class_T *pclass = extends_cl; + int pobj_method_offset = objmethods.ga_len; - // Table for methods. - if2cl = alloc_clear(sizeof(itf2class_T) - + ifcl->class_obj_method_count * sizeof(int)); - if (if2cl == NULL) - goto cleanup; - if2cl->i2c_next = ifcl->class_itf2class; - ifcl->class_itf2class = if2cl; - if2cl->i2c_class = cl; - if2cl->i2c_is_method = TRUE; - - for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i) + // Update the entire lineage of extended classes. + while (pclass != NULL) { - int done = FALSE; - for (int cl_i = 0; cl_i < objmethods.ga_len; ++cl_i) - { - if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, - ((ufunc_T **)objmethods.ga_data)[cl_i]->uf_name) - == 0) - { - int *table = (int *)(if2cl + 1); - table[if_i] = cl_i; - done = TRUE; - break; - } - } + if (update_member_method_lookup_table(pclass, cl, + &objmethods, pobj_method_offset, FALSE) == + FAIL) + goto cleanup; - if (!done && extends_cl != NULL) - { - for (int cl_i = 0; - cl_i < extends_cl->class_obj_method_count; ++cl_i) - { - if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, - extends_cl->class_obj_methods[cl_i]->uf_name) - == 0) - { - int *table = (int *)(if2cl + 1); - table[if_i] = objmethods.ga_len + cl_i; - break; - } - } - } + pobj_method_offset += pclass->class_obj_method_count_child; + pclass = pclass->class_extends; } } }