# HG changeset patch # User Bram Moolenaar # Date 1673452805 -3600 # Node ID 2f1af1b2f82da591e6820f67e9448f0d2fe6efba # Parent db1f32c411994156dd3143daf8f96eae45f8fb5f patch 9.0.1178: a child class cannot override functions from a base class Commit: https://github.com/vim/vim/commit/58b40092e616585a763cf4d214d47ccd9167d6f7 Author: Bram Moolenaar Date: Wed Jan 11 15:59:05 2023 +0000 patch 9.0.1178: a child class cannot override functions from a base class Problem: A child class cannot override functions from a base class. Solution: Allow overriding and implement "super". diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3432,4 +3432,10 @@ EXTERN char e_class_name_not_found_str[] INIT(= N_("E1353: Class name not found: %s")); EXTERN char e_cannot_extend_str[] INIT(= N_("E1354: Cannot extend %s")); -#endif +EXTERN char e_duplicate_function_str[] + INIT(= N_("E1355: Duplicate function: %s")); +EXTERN char e_super_must_be_followed_by_dot[] + INIT(= N_("E1356: \"super\" must be followed by a dot")); +EXTERN char e_using_super_not_in_class_function[] + INIT(= N_("E1357: Using \"super\" not in a class function")); +#endif diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -527,7 +527,10 @@ EXTERN int garbage_collect_at_exit INIT( #define t_dict_string (static_types[76]) #define t_const_dict_string (static_types[77]) -EXTERN type_T static_types[78] +#define t_super (static_types[78]) +#define t_const_super (static_types[79]) + +EXTERN type_T static_types[80] #ifdef DO_INIT = { // 0: t_unknown @@ -685,6 +688,10 @@ EXTERN type_T static_types[78] // 76: t_dict_string {VAR_DICT, 0, 0, TTFLAG_STATIC, &t_string, NULL}, {VAR_DICT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_string, NULL}, + + // 78: t_super (VAR_CLASS with tt_member set to &t_bool + {VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL}, + {VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_bool, NULL}, } #endif ; diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1465,6 +1465,7 @@ typedef struct { #define TTFLAG_NUMBER_OK 0x04 // tt_type is VAR_FLOAT, VAR_NUMBER is OK #define TTFLAG_STATIC 0x08 // one of the static types, e.g. t_any #define TTFLAG_CONST 0x10 // cannot be changed +#define TTFLAG_SUPER 0x20 // object from "super". typedef enum { ACCESS_PRIVATE, // read/write only inside th class @@ -1506,7 +1507,8 @@ struct class_S typval_T *class_members_tv; // allocated array of class member vals // class functions: "static def SomeMethod()" - int class_class_function_count; + int class_class_function_count; // total count + int class_class_function_count_child; // count without "extends" ufunc_T **class_class_functions; // allocated // object members: "this.varname" @@ -1514,7 +1516,8 @@ struct class_S ocmember_T *class_obj_members; // allocated // object methods: "def SomeMethod()" - int class_obj_method_count; + int class_obj_method_count; // total count + int class_obj_method_count_child; // count without "extends" ufunc_T **class_obj_methods; // allocated garray_T class_type_list; // used for type pointers diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -817,6 +817,27 @@ def Test_class_extends() endclass END v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar') + + lines =<< trim END + vim9script + class Base + this.name: string + def ToString(): string + return this.name + enddef + endclass + + class Child extends Base + this.age: number + def ToString(): string + return super.ToString() .. ': ' .. this.age + enddef + endclass + + var o = Child.new('John', 42) + assert_equal('John: 42', o.ToString()) + END + v9.CheckScriptSuccess(lines) enddef diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1178, +/**/ 1177, /**/ 1176, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -487,9 +487,21 @@ early_ret: if (uf != NULL) { - int is_new = STRNCMP(uf->uf_name, "new", 3) == 0; + char_u *name = uf->uf_name; + int is_new = STRNCMP(name, "new", 3) == 0; garray_T *fgap = has_static || is_new ? &classfunctions : &objmethods; + // Check the name isn't used already. + for (int i = 0; i < fgap->ga_len; ++i) + { + char_u *n = ((ufunc_T **)fgap->ga_data)[i]->uf_name; + if (STRCMP(name, n) == 0) + { + semsg(_(e_duplicate_function_str), name); + break; + } + } + if (ga_grow(fgap, 1) == OK) { if (is_new) @@ -793,7 +805,8 @@ early_ret: if (nf != NULL && ga_grow(&classfunctions, 1) == OK) { - ((ufunc_T **)classfunctions.ga_data)[classfunctions.ga_len] = nf; + ((ufunc_T **)classfunctions.ga_data)[classfunctions.ga_len] + = nf; ++classfunctions.ga_len; nf->uf_flags |= FC_NEW; @@ -808,6 +821,7 @@ early_ret: } } + // Move all the functions into the created class. // loop 1: class functions, loop 2: object methods for (int loop = 1; loop <= 2; ++loop) { @@ -834,26 +848,52 @@ early_ret: if (*fup == NULL) goto cleanup; + mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len); + vim_free(gap->ga_data); + if (loop == 1) + cl->class_class_function_count_child = gap->ga_len; + else + cl->class_obj_method_count_child = gap->ga_len; + int skipped = 0; for (int i = 0; i < parent_count; ++i) { // Copy functions from the parent. Can't use the same // function, because "uf_class" is different and compilation // will have a different result. + // Put them after the functions in the current class, object + // methods may be overruled, then "super.Method()" is used to + // find a method from the parent. // Skip "new" functions. TODO: not all of them. if (loop == 1 && STRNCMP( extends_cl->class_class_functions[i]->uf_name, "new", 3) == 0) ++skipped; else - *fup[i - skipped] = copy_function((loop == 1 + { + ufunc_T *pf = (loop == 1 ? extends_cl->class_class_functions - : extends_cl->class_obj_methods)[i]); + : extends_cl->class_obj_methods)[i]; + (*fup)[gap->ga_len + i - skipped] = copy_function(pf); + + // If the child class overrides a function from the parent + // the signature must be equal. + char_u *pname = pf->uf_name; + for (int ci = 0; ci < gap->ga_len; ++ci) + { + ufunc_T *cf = (*fup)[ci]; + char_u *cname = cf->uf_name; + if (STRCMP(pname, cname) == 0) + { + where_T where = WHERE_INIT; + where.wt_func_name = (char *)pname; + (void)check_type(pf->uf_func_type, cf->uf_func_type, + TRUE, where); + } + } + } } - mch_memmove(*fup + parent_count - skipped, gap->ga_data, - sizeof(ufunc_T *) * gap->ga_len); - vim_free(gap->ga_data); *fcount -= skipped; // Set the class pointer on all the functions and object methods. diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -43,16 +43,31 @@ lookup_local(char_u *name, size_t len, l if (len == 0) return FAIL; - if (len == 4 && STRNCMP(name, "this", 4) == 0 + if (((len == 4 && STRNCMP(name, "this", 4) == 0) + || (len == 5 && STRNCMP(name, "super", 5) == 0)) && cctx->ctx_ufunc != NULL && (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW))) { + int is_super = *name == 's'; if (lvar != NULL) { CLEAR_POINTER(lvar); - lvar->lv_name = (char_u *)"this"; + lvar->lv_name = (char_u *)(is_super ? "super" : "this"); if (cctx->ctx_ufunc->uf_class != NULL) + { lvar->lv_type = &cctx->ctx_ufunc->uf_class->class_object_type; + if (is_super) + { + type_T *type = get_type_ptr(cctx->ctx_type_list); + + if (type != NULL) + { + *type = *lvar->lv_type; + lvar->lv_type = type; + type->tt_flags |= TTFLAG_SUPER; + } + } + } } return OK; } diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -263,7 +263,21 @@ compile_class_object_index(cctx_T *cctx, return FAIL; } - if (type->tt_type == VAR_CLASS) + class_T *cl = (class_T *)type->tt_member; + int is_super = type->tt_flags & TTFLAG_SUPER; + if (type == &t_super) + { + if (cctx->ctx_ufunc == NULL || cctx->ctx_ufunc->uf_class == NULL) + emsg(_(e_using_super_not_in_class_function)); + else + { + is_super = TRUE; + cl = cctx->ctx_ufunc->uf_class; + // Remove &t_super from the stack. + --cctx->ctx_type_stack.ga_len; + } + } + else if (type->tt_type == VAR_CLASS) { garray_T *instr = &cctx->ctx_instr; if (instr->ga_len > 0) @@ -286,26 +300,28 @@ compile_class_object_index(cctx_T *cctx, return FAIL; size_t len = name_end - name; - class_T *cl = (class_T *)type->tt_member; if (*name_end == '(') { int function_count; + int child_count; ufunc_T **functions; if (type->tt_type == VAR_CLASS) { function_count = cl->class_class_function_count; + child_count = cl->class_class_function_count_child; functions = cl->class_class_functions; } else { // type->tt_type == VAR_OBJECT: method call function_count = cl->class_obj_method_count; + child_count = cl->class_obj_method_count_child; functions = cl->class_obj_methods; } ufunc_T *ufunc = NULL; - for (int i = 0; i < function_count; ++i) + for (int i = is_super ? child_count : 0; i < function_count; ++i) { ufunc_T *fp = functions[i]; // Use a separate pointer to avoid that ASAN complains about @@ -643,7 +659,17 @@ compile_load( if (name == NULL) return FAIL; - if (vim_strchr(name, AUTOLOAD_CHAR) != NULL) + if (STRCMP(name, "super") == 0 + && cctx->ctx_ufunc != NULL + && (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)) == 0) + { + // super.SomeFunc() in a class function: push &t_super type, this + // is recognized in compile_subscript(). + res = push_type_stack(cctx, &t_super); + if (*end != '.') + emsg(_(e_super_must_be_followed_by_dot)); + } + else if (vim_strchr(name, AUTOLOAD_CHAR) != NULL) { script_autoload(name, FALSE); res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);