To: vim_dev@googlegroups.com Subject: Patch 9.0.1053 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1053 Problem: Default constructor arguments are not optional. Solution: Use "= v:none" to make constructor arguments optional. Files: runtime/doc/vim9class.txt, src/errors.h, src/userfunc.c, src/vim9class.c, src/vim9compile.c, src/vim9execute.c, src/vim9instr.c, src/proto/vim9instr.pro, src/vim9.h, src/testdir/test_vim9_class.vim *** ../vim-9.0.1052/runtime/doc/vim9class.txt 2022-12-08 15:32:11.075034154 +0000 --- runtime/doc/vim9class.txt 2022-12-13 18:41:05.293974808 +0000 *************** *** 96,102 **** this.col = col enddef endclass ! You can create an object from this class with the new() method: > var pos = TextPosition.new(1, 1) --- 96,102 ---- this.col = col enddef endclass ! < *object* *Object* You can create an object from this class with the new() method: > var pos = TextPosition.new(1, 1) *************** *** 104,110 **** The object members "lnum" and "col" can be accessed directly: > echo $'The text position is ({pos.lnum}, {pos.col})' ! If you have been using other object-oriented languages you will notice that in Vim the object members are consistently referred to with the "this." prefix. This is different from languages like Java and TypeScript. This --- 104,110 ---- The object members "lnum" and "col" can be accessed directly: > echo $'The text position is ({pos.lnum}, {pos.col})' ! < *E1317* *E1327* If you have been using other object-oriented languages you will notice that in Vim the object members are consistently referred to with the "this." prefix. This is different from languages like Java and TypeScript. This *************** *** 329,342 **** ============================================================================== ! 5. More class details *Vim9-class* Defining a class ~ *:class* *:endclass* *:abstract* A class is defined between `:class` and `:endclass`. The whole class is defined in one script file. It is not possible to add to a class later. ! A class can only be defined in a |Vim9| script file. *E1315* A class cannot be defined inside a function. It is possible to define more than one class in a script file. Although it --- 329,342 ---- ============================================================================== ! 5. More class details *Vim9-class* *Class* *class* Defining a class ~ *:class* *:endclass* *:abstract* A class is defined between `:class` and `:endclass`. The whole class is defined in one script file. It is not possible to add to a class later. ! A class can only be defined in a |Vim9| script file. *E1316* A class cannot be defined inside a function. It is possible to define more than one class in a script file. Although it *************** *** 361,367 **** *E1314* The class name should be CamelCased. It must start with an uppercase letter. That avoids clashing with builtin types. ! After the class name these optional items can be used. Each can appear only once. They can appear in any order, although this order is recommended: > extends ClassName --- 361,367 ---- *E1314* The class name should be CamelCased. It must start with an uppercase letter. That avoids clashing with builtin types. ! *E1315* After the class name these optional items can be used. Each can appear only once. They can appear in any order, although this order is recommended: > extends ClassName *************** *** 377,382 **** --- 377,396 ---- interface, which is often done in many languages, especially Java. + Items in a class ~ + *E1318* *E1325* *E1326* + Inside a class, in betweeen `:class` and `:endclass`, these items can appear: + - An object member declaration: > + this._memberName: memberType + this.memberName: memberType + public this.memberName: memberType + - A constructor method: > + def new(arguments) + def newName(arguments) + - An object method: > + def SomeMethod(arguments) + + Defining an interface ~ *:interface* *:endinterface* An interface is defined between `:interface` and `:endinterface`. It may be *************** *** 417,427 **** Then The default constructor will be: > ! def new(this.name, this.age, this.gender) enddef All object members will be used, also private access ones. Multiple constructors ~ --- 431,462 ---- Then The default constructor will be: > ! def new(this.name = v:none, this.age = v:none, this.gender = v:none) enddef All object members will be used, also private access ones. + The "= v:none" default values make the arguments optional. Thus you can also + call `new()` without any arguments. No assignment will happen and the default + value for the object members will be used. This is a more useful example, + with default values: > + + class TextPosition + this.lnum: number = 1 + this.col: number = 1 + endclass + + If you want the constructor to have mandatory arguments, you need to write it + yourself. For example, if for the AutoNew class above you insist on getting + the name, you can define the constructor like this: > + + def new(this.name, this.age = v:none, this.gender = v:none) + enddef + < *E1328* + Note that you cannot use another default value than "v:none" here. If you + want to initialize the object members, do it where they are declared. This + way you only need to look in one place for the default values. + Multiple constructors ~ *** ../vim-9.0.1052/src/errors.h 2022-12-09 21:41:43.904327284 +0000 --- src/errors.h 2022-12-13 15:46:28.110463023 +0000 *************** *** 3372,3375 **** --- 3372,3377 ---- INIT(= N_("E1326: Member not found on object \"%s\": %s")); EXTERN char e_object_required_found_str[] INIT(= N_("E1327: Object required, found %s")); + EXTERN char e_constructor_default_value_must_be_vnone_str[] + INIT(= N_("E1328: Constructor default value must be v:none: %s")); #endif *** ../vim-9.0.1052/src/userfunc.c 2022-12-10 18:42:09.094378801 +0000 --- src/userfunc.c 2022-12-13 17:05:23.549043681 +0000 *************** *** 224,230 **** char_u *p; int c; int any_default = FALSE; - char_u *expr; char_u *whitep = *argp; if (newargs != NULL) --- 224,229 ---- *************** *** 302,307 **** --- 301,334 ---- arg = p; while (ASCII_ISALNUM(*p) || *p == '_') ++p; + char_u *argend = p; + + if (*skipwhite(p) == '=') + { + char_u *defval = skipwhite(skipwhite(p) + 1); + if (STRNCMP(defval, "v:none", 6) != 0) + { + semsg(_(e_constructor_default_value_must_be_vnone_str), p); + goto err_ret; + } + any_default = TRUE; + p = defval + 6; + + if (ga_grow(default_args, 1) == FAIL) + goto err_ret; + + char_u *expr = vim_strsave((char_u *)"v:none"); + if (expr == NULL) + goto err_ret; + ((char_u **)(default_args->ga_data)) + [default_args->ga_len] = expr; + default_args->ga_len++; + } + else if (any_default) + { + emsg(_(e_non_default_argument_follows_default_argument)); + goto err_ret; + } // TODO: check the argument is indeed a member if (newargs != NULL && ga_grow(newargs, 1) == FAIL) *************** *** 309,315 **** if (newargs != NULL) { ((char_u **)(newargs->ga_data))[newargs->ga_len] = ! vim_strnsave(arg, p - arg); newargs->ga_len++; if (argtypes != NULL && ga_grow(argtypes, 1) == OK) --- 336,342 ---- if (newargs != NULL) { ((char_u **)(newargs->ga_data))[newargs->ga_len] = ! vim_strnsave(arg, argend - arg); newargs->ga_len++; if (argtypes != NULL && ga_grow(argtypes, 1) == OK) *************** *** 322,336 **** if (ga_grow(newlines, 1) == OK) { // "this.name = name" ! int len = 5 + (p - arg) + 3 + (p - arg) + 1; char_u *assignment = alloc(len); if (assignment != NULL) { ! c = *p; ! *p = NUL; ! vim_snprintf((char *)assignment, len, "this.%s = %s", arg, arg); ! *p = c; ((char_u **)(newlines->ga_data))[ newlines->ga_len++] = assignment; } --- 349,370 ---- if (ga_grow(newlines, 1) == OK) { // "this.name = name" ! int len = 5 + (argend - arg) + 3 + (argend - arg) + 1; ! if (any_default) ! len += 14 + 10; char_u *assignment = alloc(len); if (assignment != NULL) { ! c = *argend; ! *argend = NUL; ! if (any_default) ! vim_snprintf((char *)assignment, len, ! "ifargisset %d this.%s = %s", ! default_args->ga_len - 1, arg, arg); ! else ! vim_snprintf((char *)assignment, len, "this.%s = %s", arg, arg); ! *argend = c; ((char_u **)(newlines->ga_data))[ newlines->ga_len++] = assignment; } *************** *** 361,367 **** // find the end of the expression (doesn't evaluate it) any_default = TRUE; p = skipwhite(np + 1); ! expr = p; if (eval1(&p, &rettv, NULL) != FAIL) { if (!skip) --- 395,401 ---- // find the end of the expression (doesn't evaluate it) any_default = TRUE; p = skipwhite(np + 1); ! char_u *expr = p; if (eval1(&p, &rettv, NULL) != FAIL) { if (!skip) *** ../vim-9.0.1052/src/vim9class.c 2022-12-10 18:42:09.090378817 +0000 --- src/vim9class.c 2022-12-13 17:10:48.921759464 +0000 *************** *** 269,274 **** --- 269,275 ---- ga_concat(&fga, (char_u *)"this."); objmember_T *m = cl->class_obj_members + i; ga_concat(&fga, (char_u *)m->om_name); + ga_concat(&fga, (char_u *)" = v:none"); } ga_concat(&fga, (char_u *)")\nenddef\n"); ga_append(&fga, NUL); *** ../vim-9.0.1052/src/vim9compile.c 2022-12-10 18:42:09.090378817 +0000 --- src/vim9compile.c 2022-12-13 17:01:53.268332112 +0000 *************** *** 2195,2202 **** * Return "arg" if it does not look like a variable list. */ static char_u * ! compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) { char_u *var_start; char_u *p; char_u *end = arg; --- 2195,2207 ---- * Return "arg" if it does not look like a variable list. */ static char_u * ! compile_assignment( ! char_u *arg_start, ! exarg_T *eap, ! cmdidx_T cmdidx, ! cctx_T *cctx) { + char_u *arg = arg_start; char_u *var_start; char_u *p; char_u *end = arg; *************** *** 2206,2211 **** --- 2211,2217 ---- int semicolon = 0; int did_generate_slice = FALSE; garray_T *instr = &cctx->ctx_instr; + int jump_instr_idx = instr->ga_len; char_u *op; int oplen = 0; int heredoc = FALSE; *************** *** 2216,2221 **** --- 2222,2244 ---- lhs_T lhs; long start_lnum = SOURCING_LNUM; + int has_arg_is_set_prefix = STRNCMP(arg, "ifargisset ", 11) == 0; + if (has_arg_is_set_prefix) + { + arg += 11; + int def_idx = getdigits(&arg); + arg = skipwhite(arg); + + // Use a JUMP_IF_ARG_NOT_SET instruction to skip if the value was not + // given and the default value is "v:none". + int off = STACK_FRAME_SIZE + (cctx->ctx_ufunc->uf_va_name != NULL + ? 1 : 0); + int count = cctx->ctx_ufunc->uf_def_args.ga_len; + if (generate_JUMP_IF_ARG(cctx, ISN_JUMP_IF_ARG_NOT_SET, + def_idx - count - off) == FAIL) + goto theend; + } + // Skip over the "varname" or "[varname, varname]" to get to any "=". p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE); if (p == NULL) *************** *** 2636,2641 **** --- 2659,2671 ---- if (var_idx + 1 < var_count) var_start = skipwhite(lhs.lhs_end + 1); + + if (has_arg_is_set_prefix) + { + // set instruction index in JUMP_IF_ARG_SET to here + isn_T *isn = ((isn_T *)instr->ga_data) + jump_instr_idx; + isn->isn_arg.jumparg.jump_where = instr->ga_len; + } } // For "[var, var] = expr" drop the "expr" value. *************** *** 2711,2719 **** } } ! if (*eap->cmd == '[') { - // might be "[var, var] = expr" *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); if (*line == NULL) return FAIL; --- 2741,2749 ---- } } ! // might be "[var, var] = expr" or "ifargisset this.member = expr" ! if (*eap->cmd == '[' || STRNCMP(eap->cmd, "ifargisset ", 11) == 0) { *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); if (*line == NULL) return FAIL; *************** *** 2994,3000 **** int count = ufunc->uf_def_args.ga_len; int first_def_arg = ufunc->uf_args.ga_len - count; int i; - char_u *arg; int off = STACK_FRAME_SIZE + (ufunc->uf_va_name != NULL ? 1 : 0); int did_set_arg_type = FALSE; --- 3024,3029 ---- *************** *** 3002,3024 **** SOURCING_LNUM = 0; // line number unknown for (i = 0; i < count; ++i) { type_T *val_type; int arg_idx = first_def_arg + i; where_T where = WHERE_INIT; - int r; int jump_instr_idx = instr->ga_len; isn_T *isn; // Use a JUMP_IF_ARG_SET instruction to skip if the value was given. ! if (generate_JUMP_IF_ARG_SET(&cctx, i - count - off) == FAIL) goto erret; // Make sure later arguments are not found. ufunc->uf_args_visible = arg_idx; ! arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i]; ! r = compile_expr0(&arg, &cctx); ! if (r == FAIL) goto erret; --- 3031,3057 ---- SOURCING_LNUM = 0; // line number unknown for (i = 0; i < count; ++i) { + char_u *arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i]; + if (STRCMP(arg, "v:none") == 0) + // "arg = v:none" means the argument is optional without + // setting a value when the argument is missing. + continue; + type_T *val_type; int arg_idx = first_def_arg + i; where_T where = WHERE_INIT; int jump_instr_idx = instr->ga_len; isn_T *isn; // Use a JUMP_IF_ARG_SET instruction to skip if the value was given. ! if (generate_JUMP_IF_ARG(&cctx, ISN_JUMP_IF_ARG_SET, ! i - count - off) == FAIL) goto erret; // Make sure later arguments are not found. ufunc->uf_args_visible = arg_idx; ! int r = compile_expr0(&arg, &cctx); if (r == FAIL) goto erret; *** ../vim-9.0.1052/src/vim9execute.c 2022-12-10 18:42:09.090378817 +0000 --- src/vim9execute.c 2022-12-13 18:36:25.062067849 +0000 *************** *** 2068,2074 **** } /* ! * Store a value in a list, dict or blob variable. * Returns OK, FAIL or NOTDONE (uncatchable error). */ static int --- 2068,2074 ---- } /* ! * Store a value in a list, dict, blob or object variable. * Returns OK, FAIL or NOTDONE (uncatchable error). */ static int *************** *** 2177,2185 **** { long lidx = (long)tv_idx->vval.v_number; blob_T *blob = tv_dest->vval.v_blob; ! varnumber_T nr; ! int error = FALSE; ! int len; if (blob == NULL) { --- 2177,2185 ---- { long lidx = (long)tv_idx->vval.v_number; blob_T *blob = tv_dest->vval.v_blob; ! varnumber_T nr; ! int error = FALSE; ! int len; if (blob == NULL) { *************** *** 2209,2214 **** --- 2209,2215 ---- long idx = (long)tv_idx->vval.v_number; object_T *obj = tv_dest->vval.v_object; typval_T *otv = (typval_T *)(obj + 1); + clear_tv(&otv[idx]); otv[idx] = *tv; } else *************** *** 4293,4302 **** // Jump if an argument with a default value was already set and not // v:none. case ISN_JUMP_IF_ARG_SET: tv = STACK_TV_VAR(iptr->isn_arg.jumparg.jump_arg_off); ! if (tv->v_type != VAR_UNKNOWN ! && !(tv->v_type == VAR_SPECIAL ! && tv->vval.v_number == VVAL_NONE)) ectx->ec_iidx = iptr->isn_arg.jumparg.jump_where; break; --- 4294,4305 ---- // Jump if an argument with a default value was already set and not // v:none. case ISN_JUMP_IF_ARG_SET: + case ISN_JUMP_IF_ARG_NOT_SET: tv = STACK_TV_VAR(iptr->isn_arg.jumparg.jump_arg_off); ! int arg_set = tv->v_type != VAR_UNKNOWN ! && !(tv->v_type == VAR_SPECIAL ! && tv->vval.v_number == VVAL_NONE); ! if (iptr->isn_type == ISN_JUMP_IF_ARG_SET ? arg_set : !arg_set) ectx->ec_iidx = iptr->isn_arg.jumparg.jump_where; break; *************** *** 6632,6637 **** --- 6635,6646 ---- iptr->isn_arg.jumparg.jump_arg_off + STACK_FRAME_SIZE, iptr->isn_arg.jump.jump_where); break; + + case ISN_JUMP_IF_ARG_NOT_SET: + smsg("%s%4d JUMP_IF_ARG_NOT_SET arg[%d] -> %d", pfx, current, + iptr->isn_arg.jumparg.jump_arg_off + STACK_FRAME_SIZE, + iptr->isn_arg.jump.jump_where); + break; case ISN_FOR: { *** ../vim-9.0.1052/src/vim9instr.c 2022-12-10 18:42:09.094378801 +0000 --- src/vim9instr.c 2022-12-13 16:32:29.707108836 +0000 *************** *** 1408,1422 **** } /* ! * Generate an ISN_JUMP_IF_ARG_SET instruction. */ int ! generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off) { isn_T *isn; RETURN_OK_IF_SKIP(cctx); ! if ((isn = generate_instr(cctx, ISN_JUMP_IF_ARG_SET)) == NULL) return FAIL; isn->isn_arg.jumparg.jump_arg_off = arg_off; // jump_where is set later --- 1408,1422 ---- } /* ! * Generate an ISN_JUMP_IF_ARG_SET or ISN_JUMP_IF_ARG_NOT_SET instruction. */ int ! generate_JUMP_IF_ARG(cctx_T *cctx, isntype_T isn_type, int arg_off) { isn_T *isn; RETURN_OK_IF_SKIP(cctx); ! if ((isn = generate_instr(cctx, isn_type)) == NULL) return FAIL; isn->isn_arg.jumparg.jump_arg_off = arg_off; // jump_where is set later *************** *** 2479,2484 **** --- 2479,2485 ---- case ISN_GETITEM: case ISN_GET_OBJ_MEMBER: case ISN_JUMP: + case ISN_JUMP_IF_ARG_NOT_SET: case ISN_JUMP_IF_ARG_SET: case ISN_LISTAPPEND: case ISN_LISTINDEX: *** ../vim-9.0.1052/src/proto/vim9instr.pro 2022-12-10 18:42:09.094378801 +0000 --- src/proto/vim9instr.pro 2022-12-13 17:01:03.324121725 +0000 *************** *** 47,53 **** int generate_DEF(cctx_T *cctx, char_u *name, size_t len); int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); int generate_WHILE(cctx_T *cctx, int funcref_idx); ! int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off); int generate_FOR(cctx_T *cctx, int loop_idx); int generate_ENDLOOP(cctx_T *cctx, loop_info_T *loop_info); int generate_TRYCONT(cctx_T *cctx, int levels, int where); --- 47,53 ---- int generate_DEF(cctx_T *cctx, char_u *name, size_t len); int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); int generate_WHILE(cctx_T *cctx, int funcref_idx); ! int generate_JUMP_IF_ARG(cctx_T *cctx, isntype_T isn_type, int arg_off); int generate_FOR(cctx_T *cctx, int loop_idx); int generate_ENDLOOP(cctx_T *cctx, loop_info_T *loop_info); int generate_TRYCONT(cctx_T *cctx, int levels, int where); *** ../vim-9.0.1052/src/vim9.h 2022-12-10 18:42:09.094378801 +0000 --- src/vim9.h 2022-12-13 16:33:19.363129186 +0000 *************** *** 124,129 **** --- 124,131 ---- ISN_JUMP, // jump if condition is matched isn_arg.jump ISN_JUMP_IF_ARG_SET, // jump if argument is already set, uses // isn_arg.jumparg + ISN_JUMP_IF_ARG_NOT_SET, // jump if argument is not set, uses + // isn_arg.jumparg // loop ISN_FOR, // get next item from a list, uses isn_arg.forloop *************** *** 260,266 **** int jump_where; // position to jump to } jump_T; ! // arguments to ISN_JUMP_IF_ARG_SET typedef struct { int jump_arg_off; // argument index, negative int jump_where; // position to jump to --- 262,268 ---- int jump_where; // position to jump to } jump_T; ! // arguments to ISN_JUMP_IF_ARG_SET and ISN_JUMP_IF_ARG_NOT_SET typedef struct { int jump_arg_off; // argument index, negative int jump_where; // position to jump to *** ../vim-9.0.1052/src/testdir/test_vim9_class.vim 2022-12-10 19:03:48.044388086 +0000 --- src/testdir/test_vim9_class.vim 2022-12-13 18:37:49.146067719 +0000 *************** *** 182,186 **** --- 182,237 ---- v9.CheckScriptSuccess(lines) enddef + def Test_class_default_new() + var lines =<< trim END + vim9script + + class TextPosition + this.lnum: number = 1 + this.col: number = 1 + endclass + + var pos = TextPosition.new() + assert_equal(1, pos.lnum) + assert_equal(1, pos.col) + + pos = TextPosition.new(v:none, v:none) + assert_equal(1, pos.lnum) + assert_equal(1, pos.col) + + pos = TextPosition.new(3, 22) + assert_equal(3, pos.lnum) + assert_equal(22, pos.col) + + pos = TextPosition.new(v:none, 33) + assert_equal(1, pos.lnum) + assert_equal(33, pos.col) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + class Person + this.name: string + this.age: number = 42 + this.education: string = "unknown" + + def new(this.name, this.age = v:none, this.education = v:none) + enddef + endclass + + var piet = Person.new("Piet") + assert_equal("Piet", piet.name) + assert_equal(42, piet.age) + assert_equal("unknown", piet.education) + + var chris = Person.new("Chris", 4, "none") + assert_equal("Chris", chris.name) + assert_equal(4, chris.age) + assert_equal("none", chris.education) + END + v9.CheckScriptSuccess(lines) + enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-9.0.1052/src/version.c 2022-12-13 13:42:32.179427634 +0000 --- src/version.c 2022-12-13 18:21:37.554157231 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1053, /**/ -- GALAHAD: No. Look, I can tackle this lot single-handed! GIRLS: Yes, yes, let him Tackle us single-handed! "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///