# HG changeset patch # User Bram Moolenaar # Date 1671399904 -3600 # Node ID 1bebc2093e6b3b72b93373d6deb892fe3117c688 # Parent ac877632b863228adb0a5715aeafa11196de09ac patch 9.0.1074: class members are not supported yet Commit: https://github.com/vim/vim/commit/d505d178858434e1afef0363a9fce4bcb1bc3d06 Author: Bram Moolenaar Date: Sun Dec 18 21:42:55 2022 +0000 patch 9.0.1074: class members are not supported yet Problem: Class members are not supported yet. Solution: Add initial support for class members. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3378,16 +3378,22 @@ EXTERN char e_cannot_get_object_member_t INIT(= N_("E1329: Cannot get object member type from initializer: %s")); EXTERN char e_invalid_type_for_object_member_str[] INIT(= N_("E1330: Invalid type for object member: %s")); -EXTERN char e_public_must_be_followed_by_this[] - INIT(= N_("E1331: Public must be followed by \"this\"")); -EXTERN char e_public_object_member_name_cannot_start_with_underscore_str[] - INIT(= N_("E1332: Public object member name cannot start with underscore: %s")); -EXTERN char e_cannot_access_private_object_member_str[] - INIT(= N_("E1333: Cannot access private object member: %s")); +EXTERN char e_public_must_be_followed_by_this_or_static[] + INIT(= N_("E1331: Public must be followed by \"this\" or \"static\"")); +EXTERN char e_public_member_name_cannot_start_with_underscore_str[] + INIT(= N_("E1332: Public member name cannot start with underscore: %s")); +EXTERN char e_cannot_access_private_member_str[] + INIT(= N_("E1333: Cannot access private member: %s")); EXTERN char e_object_member_not_found_str[] INIT(= N_("E1334: Object member not found: %s")); -EXTERN char e_object_member_is_not_writable_str[] - INIT(= N_("E1335: Object member is not writable: %s")); +EXTERN char e_member_is_not_writable_str[] + INIT(= N_("E1335: Member is not writable: %s")); #endif EXTERN char e_internal_error_shortmess_too_long[] INIT(= N_("E1336: Internal error: shortmess too long")); +#ifdef FEAT_EVAL +EXTERN char e_class_member_not_found_str[] + INIT(= N_("E1337: Class member not found: %s")); +EXTERN char e_member_not_found_on_class_str_str[] + INIT(= N_("E1338: Member not found on class \"%s\": %s")); +#endif diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1193,19 +1193,21 @@ get_lval( var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { - if (*p == '.' && lp->ll_tv->v_type != VAR_DICT - && lp->ll_tv->v_type != VAR_OBJECT - && lp->ll_tv->v_type != VAR_CLASS) + vartype_T v_type = lp->ll_tv->v_type; + + if (*p == '.' && v_type != VAR_DICT + && v_type != VAR_OBJECT + && v_type != VAR_CLASS) { if (!quiet) semsg(_(e_dot_can_only_be_used_on_dictionary_str), name); return NULL; } - if (lp->ll_tv->v_type != VAR_LIST - && lp->ll_tv->v_type != VAR_DICT - && lp->ll_tv->v_type != VAR_BLOB - && lp->ll_tv->v_type != VAR_OBJECT - && lp->ll_tv->v_type != VAR_CLASS) + if (v_type != VAR_LIST + && v_type != VAR_DICT + && v_type != VAR_BLOB + && v_type != VAR_OBJECT + && v_type != VAR_CLASS) { if (!quiet) emsg(_(e_can_only_index_list_dictionary_or_blob)); @@ -1214,9 +1216,9 @@ get_lval( // A NULL list/blob works like an empty list/blob, allocate one now. int r = OK; - if (lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) + if (v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) r = rettv_list_alloc(lp->ll_tv); - else if (lp->ll_tv->v_type == VAR_BLOB + else if (v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) r = rettv_blob_alloc(lp->ll_tv); if (r == FAIL) @@ -1278,7 +1280,7 @@ get_lval( // Optionally get the second index [ :expr]. if (*p == ':') { - if (lp->ll_tv->v_type == VAR_DICT) + if (v_type == VAR_DICT) { if (!quiet) emsg(_(e_cannot_slice_dictionary)); @@ -1334,7 +1336,7 @@ get_lval( ++p; } - if (lp->ll_tv->v_type == VAR_DICT) + if (v_type == VAR_DICT) { if (len == -1) { @@ -1435,7 +1437,7 @@ get_lval( clear_tv(&var1); lp->ll_tv = &lp->ll_di->di_tv; } - else if (lp->ll_tv->v_type == VAR_BLOB) + else if (v_type == VAR_BLOB) { long bloblen = blob_len(lp->ll_tv->vval.v_blob); @@ -1466,7 +1468,7 @@ get_lval( lp->ll_tv = NULL; break; } - else if (lp->ll_tv->v_type == VAR_LIST) + else if (v_type == VAR_LIST) { /* * Get the number and item for the only or first index of the List. @@ -1513,7 +1515,7 @@ get_lval( } else // v_type == VAR_CLASS || v_type == VAR_OBJECT { - class_T *cl = (lp->ll_tv->v_type == VAR_OBJECT + class_T *cl = (v_type == VAR_OBJECT && lp->ll_tv->vval.v_object != NULL) ? lp->ll_tv->vval.v_object->obj_class : lp->ll_tv->vval.v_class; @@ -1521,23 +1523,28 @@ get_lval( if (cl != NULL) { lp->ll_valtype = NULL; - for (int i = 0; i < cl->class_obj_member_count; ++i) + int count = v_type == VAR_OBJECT ? cl->class_obj_member_count + : cl->class_class_member_count; + ocmember_T *members = v_type == VAR_OBJECT + ? cl->class_obj_members + : cl->class_class_members; + for (int i = 0; i < count; ++i) { - objmember_T *om = cl->class_obj_members + i; - if (STRNCMP(om->om_name, key, p - key) == 0 - && om->om_name[p - key] == NUL) + ocmember_T *om = members + i; + if (STRNCMP(om->ocm_name, key, p - key) == 0 + && om->ocm_name[p - key] == NUL) { - switch (om->om_access) + switch (om->ocm_access) { case ACCESS_PRIVATE: - semsg(_(e_cannot_access_private_object_member_str), - om->om_name); + semsg(_(e_cannot_access_private_member_str), + om->ocm_name); return NULL; case ACCESS_READ: if (!(flags & GLV_READ_ONLY)) { - semsg(_(e_object_member_is_not_writable_str), - om->om_name); + semsg(_(e_member_is_not_writable_str), + om->ocm_name); return NULL; } break; @@ -1545,18 +1552,22 @@ get_lval( break; } - lp->ll_valtype = om->om_type; - - if (lp->ll_tv->v_type == VAR_OBJECT) + lp->ll_valtype = om->ocm_type; + + if (v_type == VAR_OBJECT) lp->ll_tv = ((typval_T *)( lp->ll_tv->vval.v_object + 1)) + i; - // TODO: what about a class? + else + lp->ll_tv = &cl->class_members_tv[i]; break; } } if (lp->ll_valtype == NULL) { - semsg(_(e_object_member_not_found_str), key); + if (v_type == VAR_OBJECT) + semsg(_(e_object_member_not_found_str), key); + else + semsg(_(e_class_member_not_found_str), key); return NULL; } } @@ -5936,8 +5947,8 @@ echo_string_core( { if (i > 0) ga_concat(&ga, (char_u *)", "); - objmember_T *m = &cl->class_obj_members[i]; - ga_concat(&ga, m->om_name); + ocmember_T *m = &cl->class_obj_members[i]; + ga_concat(&ga, m->ocm_name); ga_concat(&ga, (char_u *)": "); char_u *tf = NULL; ga_concat(&ga, echo_string_core( diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -32,6 +32,7 @@ int generate_GETITEM(cctx_T *cctx, int i int generate_SLICE(cctx_T *cctx, int count); int generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK); int generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name); +int generate_CLASSMEMBER(cctx_T *cctx, int load, class_T *cl, int idx); int generate_STORENR(cctx_T *cctx, int idx, varnumber_T value); int generate_LOAD(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name, type_T *type); int generate_LOADOUTER(cctx_T *cctx, int idx, int nesting, int loop_depth, int loop_idx, type_T *type); @@ -74,7 +75,7 @@ int generate_RANGE(cctx_T *cctx, char_u int generate_UNPACK(cctx_T *cctx, int var_count, int semicolon); int generate_cmdmods(cctx_T *cctx, cmdmod_T *cmod); int generate_undo_cmdmods(cctx_T *cctx); -int generate_store_var(cctx_T *cctx, assign_dest_T dest, int opt_flags, int vimvaridx, int scriptvar_idx, int scriptvar_sid, type_T *type, char_u *name); +int generate_store_var(cctx_T *cctx, assign_dest_T dest, int opt_flags, int vimvaridx, type_T *type, char_u *name, lhs_T *lhs); int inside_loop_scope(cctx_T *cctx); int generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count, int is_decl); void may_generate_prof_end(cctx_T *cctx, int prof_lnum); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1387,6 +1387,7 @@ typedef signed char int8_T; typedef double float_T; +typedef struct typval_S typval_T; typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; typedef struct partial_S partial_T; @@ -1466,14 +1467,14 @@ typedef enum { } omacc_T; /* - * Entry for an object member variable. + * Entry for an object or class member variable. */ typedef struct { - char_u *om_name; // allocated - omacc_T om_access; - type_T *om_type; - char_u *om_init; // allocated -} objmember_T; + char_u *ocm_name; // allocated + omacc_T ocm_access; + type_T *ocm_type; + char_u *ocm_init; // allocated +} ocmember_T; // "class_T": used for v_class of typval of VAR_CLASS struct class_S @@ -1481,14 +1482,25 @@ struct class_S char_u *class_name; // allocated int class_refcount; + // class members: "static varname" + int class_class_member_count; + 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 + + // object members: "this.varname" int class_obj_member_count; - objmember_T *class_obj_members; // allocated - + ocmember_T *class_obj_members; // allocated + + // object methods: "def SomeMethod()" int class_obj_method_count; ufunc_T **class_obj_methods; // allocated garray_T class_type_list; // used for type pointers - type_T class_type; + type_T class_type; // type used for the class type_T class_object_type; // same as class_type but VAR_OBJECT }; @@ -1513,7 +1525,7 @@ struct object_S /* * Structure to hold an internal variable without a name. */ -typedef struct +struct typval_S { vartype_T v_type; char v_lock; // see below: VAR_LOCKED, VAR_FIXED @@ -1534,7 +1546,7 @@ typedef struct class_T *v_class; // class value (can be NULL) object_T *v_object; // object value (can be NULL) } vval; -} typval_T; +}; // Values for "dv_scope". #define VAR_SCOPE 1 // a:, v:, s:, etc. scope dictionaries 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 @@ -306,6 +306,30 @@ def Test_class_object_member_access() assert_fails('trip.two = 22', 'E1335') trip.three = 33 assert_equal(33, trip.three) + + assert_fails('trip.four = 4', 'E1334') + END + v9.CheckScriptSuccess(lines) +enddef + +def Test_class_member_access() + var lines =<< trim END + vim9script + class TextPos + this.lnum = 1 + this.col = 1 + static counter = 0 + + def AddToCounter(nr: number) + counter += nr + enddef + endclass + + assert_equal(0, TextPos.counter) + TextPos.AddToCounter(3) + assert_equal(3, TextPos.counter) + + assert_fails('TextPos.counter += 5', 'E1335') 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 */ /**/ + 1074, +/**/ 1073, /**/ 1072, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -36,6 +36,8 @@ typedef enum { ISN_GET_OBJ_MEMBER, // object member, index is isn_arg.number ISN_STORE_THIS, // store value in "this" object member, index is // isn_arg.number + ISN_LOAD_CLASSMEMBER, // load class member, using classmember_T + ISN_STORE_CLASSMEMBER, // store in class member, using classmember_T // get and set variables ISN_LOAD, // push local variable isn_arg.number @@ -476,6 +478,12 @@ typedef struct { class_T *construct_class; // class the object is created from } construct_T; +// arguments to ISN_STORE_CLASSMEMBER and ISN_LOAD_CLASSMEMBER +typedef struct { + class_T *cm_class; + int cm_idx; +} classmember_T; + /* * Instruction */ @@ -528,6 +536,7 @@ struct isn_S { deferins_T defer; echowin_T echowin; construct_T construct; + classmember_T classmember; } isn_arg; }; @@ -538,7 +547,9 @@ struct dfunc_S { ufunc_T *df_ufunc; // struct containing most stuff int df_refcount; // how many ufunc_T point to this dfunc_T int df_idx; // index in def_functions - int df_deleted; // if TRUE function was deleted + char df_deleted; // if TRUE function was deleted + char df_delete_busy; // TRUE when in + // delete_def_function_contents() int df_script_seq; // Value of sctx_T sc_seq when the function // was compiled. char_u *df_name; // name used for error messages @@ -735,6 +746,7 @@ typedef enum { dest_window, dest_tab, dest_vimvar, + dest_class_member, dest_script, dest_reg, dest_expr, @@ -766,6 +778,10 @@ typedef struct { lvar_T lhs_local_lvar; // used for existing local destination lvar_T lhs_arg_lvar; // used for argument destination lvar_T *lhs_lvar; // points to destination lvar + + class_T *lhs_class; // for dest_class_member + int lhs_classmember_idx; // for dest_class_member + int lhs_scriptvar_sid; int lhs_scriptvar_idx; diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -22,6 +22,154 @@ #endif /* + * Parse a member declaration, both object and class member. + * Returns OK or FAIL. When OK then "varname_end" is set to just after the + * variable name and "type_ret" is set to the decleared or detected type. + * "init_expr" is set to the initialisation expression (allocated), if there is + * one. + */ + static int +parse_member( + exarg_T *eap, + char_u *line, + char_u *varname, + int has_public, // TRUE if "public" seen before "varname" + char_u **varname_end, + garray_T *type_list, + type_T **type_ret, + char_u **init_expr) +{ + *varname_end = to_name_end(varname, FALSE); + if (*varname == '_' && has_public) + { + semsg(_(e_public_member_name_cannot_start_with_underscore_str), line); + return FAIL; + } + + char_u *colon = skipwhite(*varname_end); + char_u *type_arg = colon; + type_T *type = NULL; + if (*colon == ':') + { + if (VIM_ISWHITE(**varname_end)) + { + semsg(_(e_no_white_space_allowed_before_colon_str), varname); + return FAIL; + } + if (!VIM_ISWHITE(colon[1])) + { + semsg(_(e_white_space_required_after_str_str), ":", varname); + return FAIL; + } + type_arg = skipwhite(colon + 1); + type = parse_type(&type_arg, type_list, TRUE); + if (type == NULL) + return FAIL; + } + + char_u *expr_start = skipwhite(type_arg); + char_u *expr_end = expr_start; + if (type == NULL && *expr_start != '=') + { + emsg(_(e_type_or_initialization_required)); + return FAIL; + } + + if (*expr_start == '=') + { + if (!VIM_ISWHITE(expr_start[-1]) || !VIM_ISWHITE(expr_start[1])) + { + semsg(_(e_white_space_required_before_and_after_str_at_str), + "=", type_arg); + return FAIL; + } + expr_start = skipwhite(expr_start + 1); + + expr_end = expr_start; + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, FALSE); + skip_expr(&expr_end, NULL); + + if (type == NULL) + { + // No type specified, use the type of the initializer. + typval_T tv; + tv.v_type = VAR_UNKNOWN; + char_u *expr = expr_start; + int res = eval0(expr, &tv, eap, &evalarg); + + if (res == OK) + type = typval2type(&tv, get_copyID(), type_list, + TVTT_DO_MEMBER); + if (type == NULL) + { + semsg(_(e_cannot_get_object_member_type_from_initializer_str), + expr_start); + clear_evalarg(&evalarg, NULL); + return FAIL; + } + } + clear_evalarg(&evalarg, NULL); + } + if (!valid_declaration_type(type)) + return FAIL; + + *type_ret = type; + if (expr_end > expr_start) + *init_expr = vim_strnsave(expr_start, expr_end - expr_start); + return OK; +} + +/* + * Add a member to an object or a class. + * Returns OK when successful, "init_expr" will be consumed then. + * Returns FAIL otherwise, caller might need to free "init_expr". + */ + static int +add_member( + garray_T *gap, + char_u *varname, + char_u *varname_end, + int has_public, + type_T *type, + char_u *init_expr) +{ + if (ga_grow(gap, 1) == FAIL) + return FAIL; + ocmember_T *m = ((ocmember_T *)gap->ga_data) + gap->ga_len; + m->ocm_name = vim_strnsave(varname, varname_end - varname); + m->ocm_access = has_public ? ACCESS_ALL + : *varname == '_' ? ACCESS_PRIVATE : ACCESS_READ; + m->ocm_type = type; + if (init_expr != NULL) + m->ocm_init = init_expr; + ++gap->ga_len; + return OK; +} + +/* + * Move the class or object members found while parsing a class into the class. + * "gap" contains the found members. + * "members" will be set to the newly allocated array of members and + * "member_count" set to the number of members. + * Returns OK or FAIL. + */ + static int +add_members_to_class( + garray_T *gap, + ocmember_T **members, + int *member_count) +{ + *member_count = gap->ga_len; + *members = gap->ga_len == 0 ? NULL : ALLOC_MULT(ocmember_T, gap->ga_len); + if (gap->ga_len > 0 && *members == NULL) + return FAIL; + mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len); + VIM_CLEAR(gap->ga_data); + return OK; +} + +/* * Handle ":class" and ":abstract class" up to ":endclass". */ void @@ -64,16 +212,23 @@ ex_class(exarg_T *eap) // extends SomeClass // implements SomeInterface // specifies SomeInterface - // check nothing follows - - // TODO: handle "is_export" if it is set + // check that nothing follows + // handle "is_export" if it is set garray_T type_list; // list of pointers to allocated types ga_init2(&type_list, sizeof(type_T *), 10); + // Growarray with class members declared in the class. + 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 object members declared in the class. garray_T objmembers; - ga_init2(&objmembers, sizeof(objmember_T), 10); + ga_init2(&objmembers, sizeof(ocmember_T), 10); // Growarray with object methods declared in the class. garray_T objmethods; @@ -92,12 +247,6 @@ ex_class(exarg_T *eap) break; char_u *line = skipwhite(theline); - // TODO: - // class members (public, read access, private): - // static varname - // public static varname - // static _varname - char_u *p = line; if (checkforcmd(&p, "endclass", 4)) { @@ -110,9 +259,6 @@ ex_class(exarg_T *eap) break; } - // "this._varname" - // "this.varname" - // "public this.varname" int has_public = FALSE; if (checkforcmd(&p, "public", 3)) { @@ -124,12 +270,17 @@ ex_class(exarg_T *eap) has_public = TRUE; p = skipwhite(line + 6); - if (STRNCMP(p, "this", 4) != 0) + if (STRNCMP(p, "this", 4) != 0 && STRNCMP(p, "static", 6) != 0) { - emsg(_(e_public_must_be_followed_by_this)); + emsg(_(e_public_must_be_followed_by_this_or_static)); break; } } + + // object members (public, read access, private): + // "this._varname" + // "this.varname" + // "public this.varname" if (STRNCMP(p, "this", 4) == 0) { if (p[4] != '.' || !eval_isnamec1(p[5])) @@ -138,95 +289,52 @@ ex_class(exarg_T *eap) break; } char_u *varname = p + 5; - char_u *varname_end = to_name_end(varname, FALSE); - if (*varname == '_' && has_public) - { - semsg(_(e_public_object_member_name_cannot_start_with_underscore_str), line); + 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; - } - - char_u *colon = skipwhite(varname_end); - char_u *type_arg = colon; - type_T *type = NULL; - if (*colon == ':') + if (add_member(&objmembers, varname, varname_end, + has_public, type, init_expr) == FAIL) { - if (VIM_ISWHITE(*varname_end)) - { - semsg(_(e_no_white_space_allowed_before_colon_str), - varname); - break; - } - if (!VIM_ISWHITE(colon[1])) - { - semsg(_(e_white_space_required_after_str_str), ":", - varname); - break; - } - type_arg = skipwhite(colon + 1); - type = parse_type(&type_arg, &type_list, TRUE); - if (type == NULL) - break; - } - - char_u *expr_start = skipwhite(type_arg); - char_u *expr_end = expr_start; - if (type == NULL && *expr_start != '=') - { - emsg(_(e_type_or_initialization_required)); + vim_free(init_expr); break; } + } - if (*expr_start == '=') + // 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 { - if (!VIM_ISWHITE(expr_start[-1]) || !VIM_ISWHITE(expr_start[1])) + // 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) { - semsg(_(e_white_space_required_before_and_after_str_at_str), - "=", type_arg); + vim_free(init_expr); break; } - expr_start = skipwhite(expr_start + 1); - - expr_end = expr_start; - evalarg_T evalarg; - fill_evalarg_from_eap(&evalarg, eap, FALSE); - skip_expr(&expr_end, NULL); - - if (type == NULL) - { - // No type specified, use the type of the initializer. - typval_T tv; - tv.v_type = VAR_UNKNOWN; - char_u *expr = expr_start; - int res = eval0(expr, &tv, eap, &evalarg); - - if (res == OK) - type = typval2type(&tv, get_copyID(), &type_list, - TVTT_DO_MEMBER); - if (type == NULL) - { - semsg(_(e_cannot_get_object_member_type_from_initializer_str), - expr_start); - clear_evalarg(&evalarg, NULL); - break; - } - } - clear_evalarg(&evalarg, NULL); } - if (!valid_declaration_type(type)) - break; - - if (ga_grow(&objmembers, 1) == FAIL) - break; - objmember_T *m = ((objmember_T *)objmembers.ga_data) - + objmembers.ga_len; - m->om_name = vim_strnsave(varname, varname_end - varname); - m->om_access = has_public ? ACCESS_ALL - : *varname == '_' ? ACCESS_PRIVATE - : ACCESS_READ; - m->om_type = type; - if (expr_end > expr_start) - m->om_init = vim_strnsave(expr_start, expr_end - expr_start); - ++objmembers.ga_len; } // constructors: @@ -238,12 +346,8 @@ ex_class(exarg_T *eap) // def someMethod() // enddef // TODO: - // static def someMethod() - // enddef // def someMethod() // enddef - // static def someMethod() - // enddef else if (checkforcmd(&p, "def", 3)) { exarg_T ea; @@ -282,22 +386,52 @@ ex_class(exarg_T *eap) class_T *cl = NULL; if (success) { + // "endclass" encountered without failures: Create the class. + cl = ALLOC_CLEAR_ONE(class_T); if (cl == NULL) goto cleanup; cl->class_refcount = 1; cl->class_name = vim_strnsave(arg, name_end - arg); + if (cl->class_name == NULL) + goto cleanup; - // Members are used by the new() function, add them here. - cl->class_obj_member_count = objmembers.ga_len; - cl->class_obj_members = objmembers.ga_len == 0 ? NULL - : ALLOC_MULT(objmember_T, objmembers.ga_len); - if (cl->class_name == NULL - || (objmembers.ga_len > 0 && cl->class_obj_members == NULL)) + // Add class and object members to "cl". + if (add_members_to_class(&classmembers, + &cl->class_class_members, + &cl->class_class_member_count) == FAIL + || add_members_to_class(&objmembers, + &cl->class_obj_members, + &cl->class_obj_member_count) == FAIL) goto cleanup; - mch_memmove(cl->class_obj_members, objmembers.ga_data, - sizeof(objmember_T) * objmembers.ga_len); - vim_free(objmembers.ga_data); + + if (cl->class_class_member_count > 0) + { + // Allocate a typval for each class member and initialize it. + cl->class_members_tv = ALLOC_CLEAR_MULT(typval_T, + cl->class_class_member_count); + if (cl->class_members_tv != NULL) + for (int i = 0; i < cl->class_class_member_count; ++i) + { + ocmember_T *m = &cl->class_class_members[i]; + typval_T *tv = &cl->class_members_tv[i]; + if (m->ocm_init != NULL) + { + typval_T *etv = eval_expr(m->ocm_init, eap); + if (etv != NULL) + { + *tv = *etv; + vim_free(etv); + } + } + else + { + // TODO: proper default value + tv->v_type = m->ocm_type->tt_type; + tv->vval.v_string = NULL; + } + } + } int have_new = FALSE; for (int i = 0; i < objmethods.ga_len; ++i) @@ -318,8 +452,8 @@ ex_class(exarg_T *eap) if (i > 0) ga_concat(&fga, (char_u *)", "); ga_concat(&fga, (char_u *)"this."); - objmember_T *m = cl->class_obj_members + i; - ga_concat(&fga, (char_u *)m->om_name); + ocmember_T *m = cl->class_obj_members + i; + ga_concat(&fga, (char_u *)m->ocm_name); ga_concat(&fga, (char_u *)" = v:none"); } ga_concat(&fga, (char_u *)")\nenddef\n"); @@ -355,6 +489,7 @@ 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) @@ -378,13 +513,7 @@ ex_class(exarg_T *eap) cl->class_type_list = type_list; // TODO: - // - Add the methods to the class - // - array with ufunc_T pointers - // - Fill hashtab with object members and methods - // - Generate the default new() method, if needed. - // Later: - // - class members - // - class methods + // - Fill hashtab with object members and methods ? // Add the class to the script-local variables. typval_T tv; @@ -404,13 +533,20 @@ cleanup: vim_free(cl); } - for (int i = 0; i < objmembers.ga_len; ++i) + for (int round = 1; round <= 2; ++round) { - objmember_T *m = ((objmember_T *)objmembers.ga_data) + i; - vim_free(m->om_name); - vim_free(m->om_init); + garray_T *gap = round == 1 ? &classmembers : &objmembers; + if (gap->ga_len == 0 || gap->ga_data == NULL) + continue; + + for (int i = 0; i < gap->ga_len; ++i) + { + ocmember_T *m = ((ocmember_T *)gap->ga_data) + i; + vim_free(m->ocm_name); + vim_free(m->ocm_init); + } + ga_clear(gap); } - ga_clear(&objmembers); for (int i = 0; i < objmethods.ga_len; ++i) { @@ -437,11 +573,11 @@ class_member_type( for (int i = 0; i < cl->class_obj_member_count; ++i) { - objmember_T *m = cl->class_obj_members + i; - if (STRNCMP(m->om_name, name, len) == 0 && m->om_name[len] == NUL) + ocmember_T *m = cl->class_obj_members + i; + if (STRNCMP(m->ocm_name, name, len) == 0 && m->ocm_name[len] == NUL) { *member_idx = i; - return m->om_type; + return m->ocm_type; } } return &t_any; @@ -572,13 +708,12 @@ class_object_index( { for (int i = 0; i < cl->class_obj_member_count; ++i) { - objmember_T *m = &cl->class_obj_members[i]; - if (STRNCMP(name, m->om_name, len) == 0 && m->om_name[len] == NUL) + ocmember_T *m = &cl->class_obj_members[i]; + if (STRNCMP(name, m->ocm_name, len) == 0 && m->ocm_name[len] == NUL) { if (*name == '_') { - semsg(_(e_cannot_access_private_object_member_str), - m->om_name); + semsg(_(e_cannot_access_private_member_str), m->ocm_name); return FAIL; } @@ -597,7 +732,31 @@ class_object_index( semsg(_(e_member_not_found_on_object_str_str), cl->class_name, name); } - // TODO: class member + else if (rettv->v_type == VAR_CLASS) + { + // class member + for (int i = 0; i < cl->class_class_member_count; ++i) + { + ocmember_T *m = &cl->class_class_members[i]; + if (STRNCMP(name, m->ocm_name, len) == 0 && m->ocm_name[len] == NUL) + { + if (*name == '_') + { + semsg(_(e_cannot_access_private_member_str), m->ocm_name); + return FAIL; + } + + typval_T *tv = &cl->class_members_tv[i]; + copy_tv(tv, rettv); + class_unref(cl); + + *arg = name_end; + return OK; + } + } + + semsg(_(e_member_not_found_on_class_str_str), cl->class_name, name); + } return FAIL; } @@ -708,15 +867,29 @@ copy_class(typval_T *from, typval_T *to) void class_unref(class_T *cl) { - if (cl != NULL && --cl->class_refcount <= 0) + if (cl != NULL && --cl->class_refcount <= 0 && cl->class_name != NULL) { - vim_free(cl->class_name); + // Freeing what the class contains may recursively come back here. + // Clear "class_name" first, if it is NULL the class does not need to + // be freed. + VIM_CLEAR(cl->class_name); + + for (int i = 0; i < cl->class_class_member_count; ++i) + { + ocmember_T *m = &cl->class_class_members[i]; + vim_free(m->ocm_name); + vim_free(m->ocm_init); + if (cl->class_members_tv != NULL) + clear_tv(&cl->class_members_tv[i]); + } + vim_free(cl->class_class_members); + vim_free(cl->class_members_tv); for (int i = 0; i < cl->class_obj_member_count; ++i) { - objmember_T *m = &cl->class_obj_members[i]; - vim_free(m->om_name); - vim_free(m->om_init); + ocmember_T *m = &cl->class_obj_members[i]; + vim_free(m->ocm_name); + vim_free(m->ocm_init); } vim_free(cl->class_obj_members); diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -1013,7 +1013,7 @@ compile_for(char_u *arg_start, cctx_T *c if (dest != dest_local) { if (generate_store_var(cctx, dest, opt_flags, vimvaridx, - 0, 0, type, name) == FAIL) + type, name, NULL) == FAIL) goto failed; } else if (varlen == 1 && *arg == '_') diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -302,6 +302,28 @@ script_var_exists(char_u *name, size_t l } /* + * If "name" is a class member in cctx->ctx_ufunc->uf_class return the index in + * class.class_class_members[]. + * Otherwise return -1; + */ + static int +class_member_index(char_u *name, size_t len, cctx_T *cctx) +{ + if (cctx == NULL || cctx->ctx_ufunc == NULL + || cctx->ctx_ufunc->uf_class == NULL) + return -1; + class_T *cl = cctx->ctx_ufunc->uf_class; + for (int i = 0; i < cl->class_class_member_count; ++i) + { + ocmember_T *m = &cl->class_class_members[i]; + if (STRNCMP(name, m->ocm_name, len) == 0 + && m->ocm_name[len] == NUL) + return i; + } + return -1; +} + +/* * Return TRUE if "name" is a local variable, argument, script variable or * imported. */ @@ -316,6 +338,7 @@ variable_exists(char_u *name, size_t len && (cctx->ctx_ufunc->uf_flags & FC_OBJECT) && STRNCMP(name, "this", 4) == 0))) || script_var_exists(name, len, cctx, NULL) == OK + || class_member_index(name, len, cctx) >= 0 || find_imported(name, len, FALSE) != NULL; } @@ -353,6 +376,9 @@ check_defined( if (len == 1 && *p == '_') return OK; + if (class_member_index(p, len, cctx) >= 0) + return OK; + if (script_var_exists(p, len, cctx, cstack) == OK) { if (is_arg) @@ -1195,14 +1221,12 @@ assignment_len(char_u *p, int *heredoc) * Generate the load instruction for "name". */ static void -generate_loadvar( - cctx_T *cctx, - assign_dest_T dest, - char_u *name, - lvar_T *lvar, - type_T *type) +generate_loadvar(cctx_T *cctx, lhs_T *lhs) { - switch (dest) + char_u *name = lhs->lhs_name; + type_T *type = lhs->lhs_type; + + switch (lhs->lhs_dest) { case dest_option: case dest_func_option: @@ -1245,6 +1269,7 @@ generate_loadvar( case dest_local: if (cctx->ctx_skip != SKIP_YES) { + lvar_T *lvar = lhs->lhs_lvar; if (lvar->lv_from_outer > 0) generate_LOADOUTER(cctx, lvar->lv_idx, lvar->lv_from_outer, lvar->lv_loop_depth, lvar->lv_loop_idx, type); @@ -1252,6 +1277,10 @@ generate_loadvar( generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); } break; + case dest_class_member: + generate_CLASSMEMBER(cctx, TRUE, lhs->lhs_class, + lhs->lhs_classmember_idx); + break; case dest_expr: // list or dict value should already be on the stack. break; @@ -1533,7 +1562,9 @@ compile_lhs( if (lookup_local(var_start, lhs->lhs_varlen, &lhs->lhs_local_lvar, cctx) == OK) + { lhs->lhs_lvar = &lhs->lhs_local_lvar; + } else { CLEAR_FIELD(lhs->lhs_arg_lvar); @@ -1549,6 +1580,7 @@ compile_lhs( lhs->lhs_lvar = &lhs->lhs_arg_lvar; } } + if (lhs->lhs_lvar != NULL) { if (is_decl) @@ -1557,6 +1589,12 @@ compile_lhs( return FAIL; } } + else if ((lhs->lhs_classmember_idx = class_member_index( + var_start, lhs->lhs_varlen, cctx)) >= 0) + { + lhs->lhs_dest = dest_class_member; + lhs->lhs_class = cctx->ctx_ufunc->uf_class; + } else { int script_namespace = lhs->lhs_varlen > 1 @@ -1965,8 +2003,7 @@ compile_load_lhs( return FAIL; } else - generate_loadvar(cctx, lhs->lhs_dest, lhs->lhs_name, - lhs->lhs_lvar, lhs->lhs_type); + generate_loadvar(cctx, lhs); return OK; } @@ -2998,20 +3035,20 @@ compile_def_function( for (int i = 0; i < ufunc->uf_class->class_obj_member_count; ++i) { - objmember_T *m = &ufunc->uf_class->class_obj_members[i]; - if (m->om_init != NULL) + ocmember_T *m = &ufunc->uf_class->class_obj_members[i]; + if (m->ocm_init != NULL) { - char_u *expr = m->om_init; + char_u *expr = m->ocm_init; if (compile_expr0(&expr, &cctx) == FAIL) goto erret; - if (!ends_excmd2(m->om_init, expr)) + if (!ends_excmd2(m->ocm_init, expr)) { semsg(_(e_trailing_characters_str), expr); goto erret; } } else - push_default_value(&cctx, m->om_type->tt_type, + push_default_value(&cctx, m->ocm_type->tt_type, FALSE, NULL); generate_STORE_THIS(&cctx, i); } @@ -3792,6 +3829,13 @@ delete_def_function_contents(dfunc_T *df { int idx; + // In same cases the instructions may refer to a class in which the + // function is defined and unreferencing the class may call back here + // recursively. Set the df_delete_busy to avoid problems. + if (dfunc->df_delete_busy) + return; + dfunc->df_delete_busy = TRUE; + ga_clear(&dfunc->df_def_args_isn); ga_clear_strings(&dfunc->df_var_names); @@ -3800,14 +3844,12 @@ delete_def_function_contents(dfunc_T *df for (idx = 0; idx < dfunc->df_instr_count; ++idx) delete_instr(dfunc->df_instr + idx); VIM_CLEAR(dfunc->df_instr); - dfunc->df_instr = NULL; } if (dfunc->df_instr_debug != NULL) { for (idx = 0; idx < dfunc->df_instr_debug_count; ++idx) delete_instr(dfunc->df_instr_debug + idx); VIM_CLEAR(dfunc->df_instr_debug); - dfunc->df_instr_debug = NULL; } #ifdef FEAT_PROFILE if (dfunc->df_instr_prof != NULL) @@ -3815,7 +3857,6 @@ delete_def_function_contents(dfunc_T *df for (idx = 0; idx < dfunc->df_instr_prof_count; ++idx) delete_instr(dfunc->df_instr_prof + idx); VIM_CLEAR(dfunc->df_instr_prof); - dfunc->df_instr_prof = NULL; } #endif @@ -3823,6 +3864,8 @@ delete_def_function_contents(dfunc_T *df dfunc->df_deleted = TRUE; if (dfunc->df_ufunc != NULL) dfunc->df_ufunc->uf_def_status = UF_NOT_COMPILED; + + dfunc->df_delete_busy = FALSE; } /* diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3817,6 +3817,27 @@ exec_instructions(ectx_T *ectx) goto on_error; break; + case ISN_LOAD_CLASSMEMBER: + { + if (GA_GROW_FAILS(&ectx->ec_stack, 1)) + goto theend; + classmember_T *cm = &iptr->isn_arg.classmember; + *STACK_TV_BOT(0) = + cm->cm_class->class_members_tv[cm->cm_idx]; + ++ectx->ec_stack.ga_len; + } + break; + + case ISN_STORE_CLASSMEMBER: + { + classmember_T *cm = &iptr->isn_arg.classmember; + tv = &cm->cm_class->class_members_tv[cm->cm_idx]; + clear_tv(tv); + *tv = *STACK_TV_BOT(-1); + --ectx->ec_stack.ga_len; + } + break; + // Load or store variable or argument from outer scope. case ISN_LOADOUTER: case ISN_STOREOUTER: @@ -6403,6 +6424,19 @@ list_instructions(char *pfx, isn_T *inst smsg("%s%4d STORERANGE", pfx, current); break; + case ISN_LOAD_CLASSMEMBER: + case ISN_STORE_CLASSMEMBER: + { + class_T *cl = iptr->isn_arg.classmember.cm_class; + int idx = iptr->isn_arg.classmember.cm_idx; + ocmember_T *ocm = &cl->class_class_members[idx]; + smsg("%s%4d %s CLASSMEMBER %s.%s", pfx, current, + iptr->isn_type == ISN_LOAD_CLASSMEMBER + ? "LOAD" : "STORE", + cl->class_name, ocm->ocm_name); + } + break; + // constants case ISN_PUSHNR: smsg("%s%4d PUSHNR %lld", pfx, current, diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -278,17 +278,16 @@ compile_class_object_index(cctx_T *cctx, { for (int i = 0; i < cl->class_obj_member_count; ++i) { - objmember_T *m = &cl->class_obj_members[i]; - if (STRNCMP(name, m->om_name, len) == 0 && m->om_name[len] == NUL) + ocmember_T *m = &cl->class_obj_members[i]; + if (STRNCMP(name, m->ocm_name, len) == 0 && m->ocm_name[len] == NUL) { if (*name == '_' && cctx->ctx_ufunc->uf_class != cl) { - semsg(_(e_cannot_access_private_object_member_str), - m->om_name); + semsg(_(e_cannot_access_private_member_str), m->ocm_name); return FAIL; } - generate_GET_OBJ_MEMBER(cctx, i, m->om_type); + generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type); *arg = name_end; return OK; diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -957,6 +957,38 @@ generate_STORE(cctx_T *cctx, isntype_T i } /* + * Generate an ISN_LOAD_CLASSMEMBER ("load" == TRUE) or ISN_STORE_CLASSMEMBER + * ("load" == FALSE) instruction. + */ + int +generate_CLASSMEMBER( + cctx_T *cctx, + int load, + class_T *cl, + int idx) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if (load) + { + ocmember_T *m = &cl->class_class_members[idx]; + isn = generate_instr_type(cctx, ISN_LOAD_CLASSMEMBER, m->ocm_type); + } + else + { + isn = generate_instr_drop(cctx, ISN_STORE_CLASSMEMBER, 1); + } + if (isn == NULL) + return FAIL; + isn->isn_arg.classmember.cm_class = cl; + ++cl->class_refcount; + isn->isn_arg.classmember.cm_idx = idx; + + return OK; +} + +/* * Generate an ISN_STOREOUTER instruction. */ static int @@ -2114,6 +2146,7 @@ generate_undo_cmdmods(cctx_T *cctx) /* * Generate a STORE instruction for "dest", not being "dest_local". + * "lhs" might be NULL. * Return FAIL when out of memory. */ int @@ -2122,10 +2155,9 @@ generate_store_var( assign_dest_T dest, int opt_flags, int vimvaridx, - int scriptvar_idx, - int scriptvar_sid, type_T *type, - char_u *name) + char_u *name, + lhs_T *lhs) { switch (dest) { @@ -2156,9 +2188,11 @@ generate_store_var( case dest_vimvar: return generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); case dest_script: + int scriptvar_idx = lhs->lhs_scriptvar_idx; + int scriptvar_sid = lhs->lhs_scriptvar_sid; if (scriptvar_idx < 0) { - isntype_T isn_type = ISN_STORES; + isntype_T isn_type = ISN_STORES; if (SCRIPT_ID_VALID(scriptvar_sid) && SCRIPT_ITEM(scriptvar_sid)->sn_import_autoload @@ -2177,6 +2211,10 @@ generate_store_var( } return generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, scriptvar_sid, scriptvar_idx, type); + case dest_class_member: + return generate_CLASSMEMBER(cctx, FALSE, + lhs->lhs_class, lhs->lhs_classmember_idx); + case dest_local: case dest_expr: // cannot happen @@ -2210,8 +2248,7 @@ generate_store_lhs(cctx_T *cctx, lhs_T * if (lhs->lhs_dest != dest_local) return generate_store_var(cctx, lhs->lhs_dest, lhs->lhs_opt_flags, lhs->lhs_vimvaridx, - lhs->lhs_scriptvar_idx, lhs->lhs_scriptvar_sid, - lhs->lhs_type, lhs->lhs_name); + lhs->lhs_type, lhs->lhs_name, lhs); if (lhs->lhs_lvar != NULL) { @@ -2422,6 +2459,11 @@ delete_instr(isn_T *isn) vim_free(isn->isn_arg.script.scriptref); break; + case ISN_LOAD_CLASSMEMBER: + case ISN_STORE_CLASSMEMBER: + class_unref(isn->isn_arg.classmember.cm_class); + break; + case ISN_TRY: vim_free(isn->isn_arg.tryref.try_ref); break;