# HG changeset patch # User Bram Moolenaar # Date 1672578007 -3600 # Node ID 8bbc932fbd0970cfee0b25e6f4204ec2d642230c # Parent c183cc7e45aef15e0d66fd3258ec412aa717f7a2 patch 9.0.1123: class function not implemented yet Commit: https://github.com/vim/vim/commit/6bafdd41cbf8c06bc00f19dcf4e1c8292460b4dd Author: Bram Moolenaar Date: Sun Jan 1 12:58:33 2023 +0000 patch 9.0.1123: class function not implemented yet Problem: Class function not implemented yet. Solution: Implement defining and calling a class function. diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -6,6 +6,7 @@ void ex_enum(exarg_T *eap); void ex_type(exarg_T *eap); int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); ufunc_T *find_class_func(char_u **arg); +int class_member_exists(char_u *name, class_T **cl_ret, int *idx_ret, cctx_T *cctx); void copy_object(typval_T *from, typval_T *to); void object_unref(object_T *obj); void copy_class(typval_T *from, typval_T *to); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1493,9 +1493,9 @@ struct class_S ocmember_T *class_class_members; // allocated typval_T *class_members_tv; // allocated array of class member vals - // class methods: "static def SomeMethod()" - int class_class_method_count; - ufunc_T **class_class_methods; // allocated + // class functions: "static def SomeMethod()" + int class_class_function_count; + ufunc_T **class_class_functions; // allocated // object members: "this.varname" int class_obj_member_count; 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 @@ -377,7 +377,7 @@ def Test_class_member_access() static _secret = 7 public static anybody = 42 - def AddToCounter(nr: number) + static def AddToCounter(nr: number) counter += nr enddef endclass @@ -403,6 +403,32 @@ def Test_class_member_access() v9.CheckScriptSuccess(lines) enddef +def Test_class_function() + var lines =<< trim END + vim9script + class Value + this.value = 0 + static objects = 0 + + def new(v: number) + this.value = v + ++objects + enddef + + static def GetCount(): number + return objects + enddef + endclass + + assert_equal(0, Value.GetCount()) + var v1 = Value.new(2) + assert_equal(1, Value.GetCount()) + var v2 = Value.new(7) + assert_equal(2, Value.GetCount()) + END + v9.CheckScriptSuccess(lines) +enddef + def Test_class_object_to_string() var lines =<< trim END vim9script 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 */ /**/ + 1123, +/**/ 1122, /**/ 1121, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -223,9 +223,9 @@ ex_class(exarg_T *eap) garray_T classmembers; ga_init2(&classmembers, sizeof(ocmember_T), 10); - // Growarray with object methods declared in the class. - garray_T classmethods; - ga_init2(&classmethods, sizeof(ufunc_T *), 10); + // Growarray with functions declared in the class. + garray_T classfunctions; + ga_init2(&classfunctions, sizeof(ufunc_T *), 10); // Growarray with object members declared in the class. garray_T objmembers; @@ -288,6 +288,19 @@ ex_class(exarg_T *eap) } } + int has_static = FALSE; + char_u *ps = p; + if (checkforcmd(&p, "static", 4)) + { + if (STRNCMP(ps, "static", 6) != 0) + { + semsg(_(e_command_cannot_be_shortened_str), ps); + break; + } + has_static = TRUE; + p = skipwhite(ps + 6); + } + // object members (public, read access, private): // "this._varname" // "this.varname" @@ -314,47 +327,15 @@ ex_class(exarg_T *eap) } } - // class members and methods - else if (checkforcmd(&p, "static", 6)) - { - p = skipwhite(p); - if (checkforcmd(&p, "def", 3)) - { - // TODO: class method - // static def someMethod() - // enddef - // static def someMethod() - // enddef - } - else - { - // class members (public, read access, private): - // "static _varname" - // "static varname" - // "public static varname" - char_u *varname = p; - char_u *varname_end = NULL; - type_T *type = NULL; - char_u *init_expr = NULL; - if (parse_member(eap, line, varname, has_public, - &varname_end, &type_list, &type, &init_expr) == FAIL) - break; - if (add_member(&classmembers, varname, varname_end, - has_public, type, init_expr) == FAIL) - { - vim_free(init_expr); - break; - } - } - } - // constructors: // def new() // enddef // def newOther() // enddef - // methods: - // def someMethod() + // object methods and class functions: + // def SomeMethod() + // enddef + // static def ClassFunction() // enddef // TODO: // def someMethod() @@ -364,6 +345,8 @@ ex_class(exarg_T *eap) exarg_T ea; garray_T lines_to_free; + // TODO: error for "public static def Func()"? + CLEAR_FIELD(ea); ea.cmd = line; ea.arg = p; @@ -376,13 +359,38 @@ ex_class(exarg_T *eap) ga_clear_strings(&lines_to_free); // TODO: how about errors? - if (uf != NULL && ga_grow(&objmethods, 1) == OK) + int is_new = STRNCMP(uf->uf_name, "new", 3) == 0; + garray_T *fgap = has_static || is_new + ? &classfunctions : &objmethods; + if (uf != NULL && ga_grow(fgap, 1) == OK) { - if (STRNCMP(uf->uf_name, "new", 3) == 0) + if (is_new) uf->uf_flags |= FC_NEW; - ((ufunc_T **)objmethods.ga_data)[objmethods.ga_len] = uf; - ++objmethods.ga_len; + ((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf; + ++fgap->ga_len; + } + } + + // class members + else if (has_static) + { + // class members (public, read access, private): + // "static _varname" + // "static varname" + // "public static varname" + char_u *varname = p; + char_u *varname_end = NULL; + type_T *type = NULL; + char_u *init_expr = NULL; + if (parse_member(eap, line, varname, has_public, + &varname_end, &type_list, &type, &init_expr) == FAIL) + break; + if (add_member(&classmembers, varname, varname_end, + has_public, type, init_expr) == FAIL) + { + vim_free(init_expr); + break; } } @@ -445,8 +453,8 @@ ex_class(exarg_T *eap) } int have_new = FALSE; - for (int i = 0; i < objmethods.ga_len; ++i) - if (STRCMP(((ufunc_T **)objmethods.ga_data)[i]->uf_name, + for (int i = 0; i < classfunctions.ga_len; ++i) + if (STRCMP(((ufunc_T **)classfunctions.ga_data)[i]->uf_name, "new") == 0) { have_new = TRUE; @@ -483,10 +491,10 @@ ex_class(exarg_T *eap) ga_clear_strings(&lines_to_free); vim_free(fga.ga_data); - if (nf != NULL && ga_grow(&objmethods, 1) == OK) + if (nf != NULL && ga_grow(&classfunctions, 1) == OK) { - ((ufunc_T **)objmethods.ga_data)[objmethods.ga_len] = nf; - ++objmethods.ga_len; + ((ufunc_T **)classfunctions.ga_data)[classfunctions.ga_len] = nf; + ++classfunctions.ga_len; nf->uf_flags |= FC_NEW; nf->uf_ret_type = get_type_ptr(&type_list); @@ -500,21 +508,35 @@ ex_class(exarg_T *eap) } } - // TODO: class methods - cl->class_obj_method_count = objmethods.ga_len; - cl->class_obj_methods = ALLOC_MULT(ufunc_T *, objmethods.ga_len); - if (cl->class_obj_methods == NULL) - goto cleanup; - mch_memmove(cl->class_obj_methods, objmethods.ga_data, - sizeof(ufunc_T *) * objmethods.ga_len); - vim_free(objmethods.ga_data); + // loop 1: class functions, loop 2: object methods + for (int loop = 1; loop <= 2; ++loop) + { + garray_T *gap = loop == 1 ? &classfunctions : &objmethods; + int *fcount = loop == 1 ? &cl->class_class_function_count + : &cl->class_obj_method_count; + ufunc_T ***fup = loop == 1 ? &cl->class_class_functions + : &cl->class_obj_methods; - // Set the class pointer on all the object methods. - for (int i = 0; i < objmethods.ga_len; ++i) - { - ufunc_T *fp = cl->class_obj_methods[i]; - fp->uf_class = cl; - fp->uf_flags |= FC_OBJECT; // TODO: not for class method + *fcount = gap->ga_len; + if (gap->ga_len == 0) + { + *fup = NULL; + continue; + } + *fup = ALLOC_MULT(ufunc_T *, gap->ga_len); + if (*fup == NULL) + goto cleanup; + mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len); + vim_free(gap->ga_data); + + // Set the class pointer on all the object methods. + for (int i = 0; i < gap->ga_len; ++i) + { + ufunc_T *fp = (*fup)[i]; + fp->uf_class = cl; + if (loop == 2) + fp->uf_flags |= FC_OBJECT; + } } cl->class_type.tt_type = VAR_CLASS; @@ -539,6 +561,7 @@ cleanup: if (cl != NULL) { vim_free(cl->class_name); + vim_free(cl->class_class_functions); vim_free(cl->class_obj_members); vim_free(cl->class_obj_methods); vim_free(cl); @@ -565,6 +588,14 @@ cleanup: func_clear_free(uf, FALSE); } ga_clear(&objmethods); + + for (int i = 0; i < classfunctions.ga_len; ++i) + { + ufunc_T *uf = ((ufunc_T **)classfunctions.ga_data)[i]; + func_clear_free(uf, FALSE); + } + ga_clear(&classfunctions); + clear_type_list(&type_list); } @@ -627,7 +658,7 @@ ex_type(exarg_T *eap UNUSED) /* * Evaluate what comes after a class: * - class member: SomeClass.varname - * - class method: SomeClass.SomeMethod() + * - class function: SomeClass.SomeMethod() * - class constructor: SomeClass.new() * - object member: someObject.varname * - object method: someObject.SomeMethod() @@ -664,9 +695,13 @@ class_object_index( : rettv->vval.v_object->obj_class; if (*name_end == '(') { - for (int i = 0; i < cl->class_obj_method_count; ++i) + int on_class = rettv->v_type == VAR_CLASS; + int count = on_class ? cl->class_class_function_count + : cl->class_obj_method_count; + for (int i = 0; i < count; ++i) { - ufunc_T *fp = cl->class_obj_methods[i]; + ufunc_T *fp = on_class ? cl->class_class_functions[i] + : cl->class_obj_methods[i]; // Use a separate pointer to avoid that ASAN complains about // uf_name[] only being 4 characters. char_u *ufname = (char_u *)fp->uf_name; @@ -805,9 +840,13 @@ find_class_func(char_u **arg) goto fail_after_eval; len = fname_end - fname; - for (int i = 0; i < cl->class_obj_method_count; ++i) + int count = tv.v_type == VAR_CLASS ? cl->class_class_function_count + : cl->class_obj_method_count; + ufunc_T **funcs = tv.v_type == VAR_CLASS ? cl->class_class_functions + : cl->class_obj_methods; + for (int i = 0; i < count; ++i) { - ufunc_T *fp = cl->class_obj_methods[i]; + ufunc_T *fp = funcs[i]; // Use a separate pointer to avoid that ASAN complains about // uf_name[] only being 4 characters. char_u *ufname = (char_u *)fp->uf_name; @@ -824,6 +863,35 @@ fail_after_eval: } /* + * If "cctx->ctx_ufunc" indicates we are in a class, check if "name" is a class + * member. If it is then return TRUE and set "cl_ret" and "idx_ret". + */ + int +class_member_exists( + char_u *name, + class_T **cl_ret, + int *idx_ret, + cctx_T *cctx) +{ + if (cctx->ctx_ufunc == NULL || cctx->ctx_ufunc->uf_class == NULL) + return FALSE; + class_T *cl = cctx->ctx_ufunc->uf_class; + + for (int idx = 0; idx < cl->class_class_member_count; ++idx) + { + ocmember_T *m = &cl->class_class_members[idx]; + if (STRCMP(m->ocm_name, name) == 0) + { + *cl_ret = cl; + *idx_ret = idx; + return TRUE; + } + } + + return FALSE; +} + +/* * Make a copy of an object. */ void diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -587,7 +587,8 @@ compile_load( } else { - lvar_T lvar; + lvar_T lvar; + class_T *cl = NULL; if (lookup_local(*arg, len, &lvar, cctx) == OK) { @@ -602,6 +603,10 @@ compile_load( else gen_load = TRUE; } + else if (class_member_exists(name, &cl, &idx, cctx)) + { + res = generate_CLASSMEMBER(cctx, TRUE, cl, idx); + } else { // "var" can be script-local even without using "s:" if it