diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 4fb77eea00..76b47c2e96 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.2. Last change: 2020 Dec 29 +*eval.txt* For Vim version 8.2. Last change: 2021 Jan 10 VIM REFERENCE MANUAL by Bram Moolenaar @@ -49,7 +49,7 @@ There are ten types of variables: *Number* *Integer* Number A 32 or 64 bit signed number. |expr-number| The number of bits is available in |v:numbersize|. - Examples: -123 0x10 0177 0b1011 + Examples: -123 0x10 0177 0o177 0b1011 Float A floating point number. |floating-point-format| *Float* {only when compiled with the |+float| feature} @@ -97,9 +97,10 @@ the Number. Examples: Conversion from a String to a Number only happens in legacy Vim script, not in Vim9 script. It is done by converting the first digits to a number. Hexadecimal "0xf9", Octal "017" or "0o17", and Binary "0b10" -numbers are recognized (NOTE: when using |scriptversion-4| octal with a -leading "0" is not recognized). If the String doesn't start with digits, the -result is zero. +numbers are recognized +NOTE: when using |scriptversion-4| octal with a leading "0" is not recognized. +The 0o notation requires patch 8.2.0886. +If the String doesn't start with digits, the result is zero. Examples: String "456" --> Number 456 ~ String "6bar" --> Number 6 ~ @@ -1150,7 +1151,7 @@ expr7 *expr7* For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one). For '-' the sign of the number is changed. -For '+' the number is unchanged. +For '+' the number is unchanged. Note: "++" has no effect. A String will be converted to a Number first. @@ -1191,6 +1192,7 @@ start with one! If the length of the String is less than the index, the result is an empty String. A negative index always results in an empty string (reason: backward compatibility). Use [-1:] to get the last byte or character. +In Vim9 script a negative index is used like with a list: count from the end. If expr8 is a |List| then it results the item at index expr1. See |list-index| for possible index values. If the index is out of range this results in an @@ -1318,8 +1320,8 @@ When using the lambda form there must be no white space between the } and the number ------ number number constant *expr-number* - *hex-number* *octal-number* *binary-number* + *0x* *hex-number* *0o* *octal-number* *binary-number* Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B) and Octal (starting with 0, 0o or 0O). @@ -1572,7 +1574,7 @@ Note how execute() is used to execute an Ex command. That's ugly though. Lambda expressions have internal names like '42'. If you get an error for a lambda expression, you can find what it is with the following command: > - :function {'42'} + :function 42 See also: |numbered-function| ============================================================================== @@ -2487,12 +2489,13 @@ ch_status({handle} [, {options}]) changenr() Number current change number char2nr({expr} [, {utf8}]) Number ASCII/UTF8 value of first char in {expr} charclass({string}) Number character class of {string} +charcol({expr}) Number column number of cursor or mark charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} chdir({dir}) String change current working directory cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches -col({expr}) Number column nr of cursor or mark +col({expr}) Number column byte index of cursor or mark complete({startcol}, {matches}) none set Insert mode completion complete_add({expr}) Number add completion match complete_check() Number check for key typed during completion @@ -2570,6 +2573,7 @@ getbufvar({expr}, {varname} [, {def}]) getchangelist([{expr}]) List list of change list items getchar([expr]) Number get one character from the user getcharmod() Number modifiers for the last typed character +getcharpos({expr}) List position of cursor, mark, etc. getcharsearch() Dict last character search getcmdline() String return the current command-line getcmdpos() Number return cursor position in command-line @@ -2578,6 +2582,7 @@ getcmdwintype() String return current command-line window type getcompletion({pat}, {type} [, {filtered}]) List list of cmdline completion matches getcurpos([{winnr}]) List position of the cursor +getcursorcharpos([{winnr}]) List character position of the cursor getcwd([{winnr} [, {tabnr}]]) String get the current working directory getenv({name}) String return environment variable getfontname([{name}]) String name of font being used @@ -2840,8 +2845,10 @@ setbufline({expr}, {lnum}, {text}) setbufvar({expr}, {varname}, {val}) none set {varname} in buffer {expr} to {val} setcellwidths({list}) none set character cell width overrides +setcharpos({expr}, {list}) Number set the {expr} position to {list} setcharsearch({dict}) Dict set character search from {dict} setcmdpos({pos}) Number set cursor position in command-line +setcursorcharpos({list}) Number move cursor to position in {list} setenv({name}, {val}) none set environment variable setfperm({fname}, {mode}) Number set {fname} file permissions to {mode} setline({lnum}, {line}) Number set line {lnum} to {line} @@ -3525,8 +3532,8 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()* < The first and third echo result in 3 ('e' plus composing character is 3 bytes), the second echo results in 1 ('e' is one byte). - Only works differently from byteidx() when 'encoding' is set to - a Unicode encoding. + Only works differently from byteidx() when 'encoding' is set + to a Unicode encoding. Can also be used as a |method|: > GetName()->byteidxcomp(idx) @@ -3602,6 +3609,18 @@ charclass({string}) *charclass()* other specific Unicode class The class is used in patterns and word motions. + *charcol()* +charcol({expr}) Same as |col()| but returns the character index of the column + position given with {expr} instead of the byte position. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + charcol('.') returns 3 + col('.') returns 7 + +< Can also be used as a |method|: > + GetPos()->col() +< *charidx()* charidx({string}, {idx} [, {countcc}]) Return the character index of the byte at {idx} in {string}. @@ -3692,7 +3711,8 @@ col({expr}) The result is a Number, which is the byte index of the column out of range then col() returns zero. To get the line number use |line()|. To get both use |getpos()|. - For the screen column position use |virtcol()|. + For the screen column position use |virtcol()|. For the + character position use |charcol()|. Note that only marks in the current file can be used. Examples: > col(".") column of cursor @@ -3993,6 +4013,9 @@ cursor({list}) This is like the return value of |getpos()| or |getcurpos()|, but without the first item. + To position the cursor using the character count, use + |setcursorcharpos()|. + Does not change the jumplist. If {lnum} is greater than the number of lines in the buffer, the cursor will be positioned at the last line in the buffer. @@ -5232,6 +5255,20 @@ getcharmod() *getcharmod()* character itself are obtained. Thus Shift-a results in "A" without a modifier. + *getcharpos()* +getcharpos({expr}) + Get the position for {expr}. Same as |getpos()| but the column + number in the returned List is a character index instead of + a byte index. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + getcharpos('.') returns [0, 5, 3, 0] + getpos('.') returns [0, 5, 7, 0] +< + Can also be used as a |method|: > + GetMark()->getcharpos() + getcharsearch() *getcharsearch()* Return the current character search information as a {dict} with the following entries: @@ -5357,8 +5394,11 @@ getcurpos([{winid}]) includes an extra "curswant" item in the list: [0, lnum, col, off, curswant] ~ The "curswant" number is the preferred column when moving the - cursor vertically. Also see |getpos()|. - The first "bufnum" item is always zero. + cursor vertically. Also see |getcursorcharpos()| and + |getpos()|. + The first "bufnum" item is always zero. The byte position of + the cursor is returned in 'col'. To get the character + position, use |getcursorcharpos()|. The optional {winid} argument can specify the window. It can be the window number or the |window-ID|. The last known @@ -5372,7 +5412,24 @@ getcurpos([{winid}]) call setpos('.', save_cursor) < Note that this only works within the window. See |winrestview()| for restoring more state. - *getcwd()* + + Can also be used as a |method|: > + GetWinid()->getcurpos() + +< *getcursorcharpos()* +getcursorcharpos([{winid}]) + Same as |getcurpos()| but the column number in the returned + List is a character index instead of a byte index. + + Example: + With the cursor on '보' in line 3 with text "여보세요": > + getcursorcharpos() returns [0, 3, 2, 0, 3] + getcurpos() returns [0, 3, 4, 0, 3] + +< Can also be used as a |method|: > + GetWinid()->getcursorcharpos() + +< *getcwd()* getcwd([{winnr} [, {tabnr}]]) The result is a String, which is the name of the current working directory. @@ -5679,16 +5736,18 @@ getpos({expr}) Get the position for {expr}. For possible values of {expr} Note that for '< and '> Visual mode matters: when it is "V" (visual line mode) the column of '< is zero and the column of '> is a large number. + The column number in the returned List is the byte position + within the line. To get the character position in the line, + use |getcharpos()| This can be used to save and restore the position of a mark: > let save_a_mark = getpos("'a") ... call setpos("'a", save_a_mark) -< Also see |getcurpos()| and |setpos()|. +< Also see |getcharpos()|, |getcurpos()| and |setpos()|. Can also be used as a |method|: > GetMark()->getpos() - getqflist([{what}]) *getqflist()* Returns a |List| with all the current quickfix errors. Each list item is a dictionary with these entries: @@ -7554,8 +7613,10 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* < *max()* -max({expr}) Return the maximum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +max({expr}) Return the maximum value of all items in {expr}. Example: > + echo max([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the maximum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7625,8 +7686,10 @@ menu_info({name} [, {mode}]) *menu_info()* < *min()* -min({expr}) Return the minimum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +min({expr}) Return the minimum value of all items in {expr}. Example: > + echo min([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the minimum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7643,13 +7706,13 @@ mkdir({name} [, {path} [, {prot}]]) necessary. Otherwise it must be "". If {prot} is given it is used to set the protection bits of - the new directory. The default is 0755 (rwxr-xr-x: r/w for - the user readable for others). Use 0700 to make it unreadable - for others. This is only used for the last part of {name}. - Thus if you create /tmp/foo/bar then /tmp/foo will be created - with 0755. + the new directory. The default is 0o755 (rwxr-xr-x: r/w for + the user, readable for others). Use 0o700 to make it + unreadable for others. This is only used for the last part of + {name}. Thus if you create /tmp/foo/bar then /tmp/foo will be + created with 0o755. Example: > - :call mkdir($HOME . "/tmp/foo/bar", "p", 0700) + :call mkdir($HOME . "/tmp/foo/bar", "p", 0o700) < This function is not available in the |sandbox|. @@ -9212,6 +9275,19 @@ setcellwidths({list}) *setcellwidths()* < You can use the script $VIMRUNTIME/tools/emoji_list.vim to see the effect for known emoji characters. +setcharpos({expr}, {list}) *setcharpos()* + Same as |setpos()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 8: > + call setcharpos('.', [0, 8, 4, 0]) +< positions the cursor on the fourth character '요'. > + call setpos('.', [0, 8, 4, 0]) +< positions the cursor on the second character '보'. + + Can also be used as a |method|: > + GetPosition()->setcharpos('.') setcharsearch({dict}) *setcharsearch()* Set the current character search information to {dict}, @@ -9254,6 +9330,21 @@ setcmdpos({pos}) *setcmdpos()* Can also be used as a |method|: > GetPos()->setcmdpos() +setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()* +setcursorcharpos({list}) + Same as |cursor()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 4: > + call setcursorcharpos(4, 3) +< positions the cursor on the third character '세'. > + call cursor(4, 3) +< positions the cursor on the first character '여'. + + Can also be used as a |method|: > + GetCursorPos()->setcursorcharpos() + setenv({name}, {val}) *setenv()* Set environment variable {name} to {val}. When {val} is |v:null| the environment variable is deleted. @@ -9365,7 +9456,8 @@ setpos({expr}, {list}) "lnum" and "col" are the position in the buffer. The first column is 1. Use a zero "lnum" to delete a mark. If "col" is - smaller than 1 then 1 is used. + smaller than 1 then 1 is used. To use the character count + instead of the byte count, use |setcharpos()|. The "off" number is only used when 'virtualedit' is set. Then it is the offset in screen columns from the start of the @@ -9385,7 +9477,7 @@ setpos({expr}, {list}) Returns 0 when the position could be set, -1 otherwise. An error message is given if {expr} is invalid. - Also see |getpos()| and |getcurpos()|. + Also see |setcharpos()|, |getpos()| and |getcurpos()|. This does not restore the preferred column for moving vertically; if you set the cursor position with this, |j| and diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index a035038e47..da095e13f4 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -753,6 +753,11 @@ Cursor and mark position: *cursor-functions* *mark-functions* screenchar() get character code at a screen line/row screenchars() get character codes at a screen line/row screenstring() get string of characters at a screen line/row + charcol() character number of the cursor or a mark + getcharpos() get character position of cursor, mark, etc. + setcharpos() set character position of cursor, mark, etc. + getcursorcharpos() get character position of the cursor + setcursorcharpos() set character position of the cursor Working with text in the current buffer: *text-functions* getline() get a line or list of lines from the buffer diff --git a/src/charset.c b/src/charset.c index bf5af37205..4289360e47 100644 --- a/src/charset.c +++ b/src/charset.c @@ -1594,6 +1594,12 @@ vim_isbdigit(int c) return (c == '0' || c == '1'); } + static int +vim_isodigit(int c) +{ + return (c >= '0' && c <= '7'); +} + /* * Vim's own character class functions. These exist because many library * islower()/toupper() etc. do not work properly: they crash when used with @@ -1831,7 +1837,7 @@ vim_str2nr( // binary ptr += 2; else if ((what & STR2NR_OOCT) - && (pre == 'O' || pre == 'o') && vim_isbdigit(ptr[2]) + && (pre == 'O' || pre == 'o') && vim_isodigit(ptr[2]) && (maxlen == 0 || maxlen > 2)) // octal with prefix "0o" ptr += 2; diff --git a/src/errors.h b/src/errors.h index e71b706004..47e9876f68 100644 --- a/src/errors.h +++ b/src/errors.h @@ -341,3 +341,5 @@ EXTERN char e_mismatched_endfunction[] INIT(= N_("E1151: Mismatched endfunction")); EXTERN char e_mismatched_enddef[] INIT(= N_("E1152: Mismatched enddef")); +EXTERN char e_invalid_operation_for_bool[] + INIT(= N_("E1153: Invalid operation for bool")); diff --git a/src/eval.c b/src/eval.c index 02cfd7d7dd..a0877f649b 100644 --- a/src/eval.c +++ b/src/eval.c @@ -721,8 +721,10 @@ call_func_retlist( #ifdef FEAT_FOLDING /* - * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding - * it in "*cp". Doesn't give error messages. + * Evaluate "arg", which is 'foldexpr'. + * Note: caller must set "curwin" to match "arg". + * Returns the foldlevel, and any character preceding it in "*cp". Doesn't + * give error messages. */ int eval_foldexpr(char_u *arg, int *cp) @@ -809,6 +811,7 @@ get_lval( int len; hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; + int writing; // Clear everything in "lp". CLEAR_POINTER(lp); @@ -882,10 +885,10 @@ get_lval( cc = *p; *p = NUL; - // Only pass &ht when we would write to the variable, it prevents autoload - // as well. - v = find_var(lp->ll_name, (flags & GLV_READ_ONLY) ? NULL : &ht, - flags & GLV_NO_AUTOLOAD); + // When we would write to the variable pass &ht and prevent autoload. + writing = !(flags & GLV_READ_ONLY); + v = find_var(lp->ll_name, writing ? &ht : NULL, + (flags & GLV_NO_AUTOLOAD) || writing); if (v == NULL && !quiet) semsg(_(e_undefined_variable_str), lp->ll_name); *p = cc; @@ -1972,13 +1975,15 @@ eval_func( int len = name_len; partial_T *partial; int ret = OK; + type_T *type = NULL; if (!evaluate) check_vars(s, len); // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, &partial, !evaluate); + s = deref_func_name(s, &len, &partial, + in_vim9script() ? &type : NULL, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. @@ -1996,6 +2001,7 @@ eval_func( funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; + funcexe.check_type = type; ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); } vim_free(s); @@ -2649,7 +2655,7 @@ eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { char_u *p; int getnext; - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; int len = 2; int type_is = FALSE; @@ -5047,6 +5053,61 @@ string2float( } #endif +/* + * Convert the specified byte index of line 'lnum' in buffer 'buf' to a + * character index. Works only for loaded buffers. Returns -1 on failure. + * The index of the first character is one. + */ + int +buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx) +{ + char_u *str; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + if (*str == NUL) + return 1; + + return mb_charlen_len(str, byteidx + 1); +} + +/* + * Convert the specified character index of line 'lnum' in buffer 'buf' to a + * byte index. Works only for loaded buffers. Returns -1 on failure. The index + * of the first byte and the first character is one. + */ + int +buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + char_u *str; + char_u *t; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + // Convert the character offset to a byte offset + t = str; + while (*t != NUL && --charidx > 0) + t += mb_ptr2len(t); + + return t - str + 1; +} + /* * Translate a String variable into a position. * Returns NULL when there is an error. @@ -5055,7 +5116,8 @@ string2float( var2fpos( typval_T *varp, int dollar_lnum, // TRUE when $ is last line - int *fnum) // set to fnum for '0, 'A, etc. + int *fnum, // set to fnum for '0, 'A, etc. + int charcol) // return character column { char_u *name; static pos_T pos; @@ -5077,7 +5139,10 @@ var2fpos( pos.lnum = list_find_nr(l, 0L, &error); if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) return NULL; // invalid line number - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) + len = (long)mb_charlen(ml_get(pos.lnum)); + else + len = (long)STRLEN(ml_get(pos.lnum)); // Get the column number // We accept "$" for the column number: last column. @@ -5112,18 +5177,29 @@ var2fpos( if (name == NULL) return NULL; if (name[0] == '.') // cursor - return &curwin->w_cursor; + { + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; + } if (name[0] == 'v' && name[1] == NUL) // Visual start { if (VIsual_active) - return &VIsual; - return &curwin->w_cursor; + pos = VIsual; + else + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; } if (name[0] == '\'') // mark { pp = getmark_buf_fnum(curbuf, name[1], FALSE, fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) return NULL; + if (charcol) + pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1; return pp; } @@ -5158,7 +5234,10 @@ var2fpos( else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(ml_get_curline()); + if (charcol) + pos.col = (colnr_T)mb_charlen(ml_get_curline()); + else + pos.col = (colnr_T)STRLEN(ml_get_curline()); } return &pos; } @@ -5178,7 +5257,8 @@ list2fpos( typval_T *arg, pos_T *posp, int *fnump, - colnr_T *curswantp) + colnr_T *curswantp, + int charcol) { list_T *l = arg->vval.v_list; long i = 0; @@ -5210,6 +5290,18 @@ list2fpos( n = list_find_nr(l, i++, NULL); // col if (n < 0) return FAIL; + // If character position is specified, then convert to byte position + if (charcol) + { + buf_T *buf; + + // Get the text for the specified line in a loaded buffer + buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return FAIL; + + n = buf_charidx_to_byteidx(buf, posp->lnum, n); + } posp->col = n; n = list_find_nr(l, i, NULL); // off @@ -5542,97 +5634,6 @@ eval_isdictc(int c) return ASCII_ISALNUM(c) || c == '_'; } -/* - * Return the character "str[index]" where "index" is the character index. If - * "index" is out of range NULL is returned. - */ - char_u * -char_from_string(char_u *str, varnumber_T index) -{ - size_t nbyte = 0; - varnumber_T nchar = index; - size_t slen; - - if (str == NULL || index < 0) - return NULL; - slen = STRLEN(str); - while (nchar > 0 && nbyte < slen) - { - nbyte += MB_CPTR2LEN(str + nbyte); - --nchar; - } - if (nbyte >= slen) - return NULL; - return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte)); -} - -/* - * Get the byte index for character index "idx" in string "str" with length - * "str_len". - * If going over the end return "str_len". - * If "idx" is negative count from the end, -1 is the last character. - * When going over the start return -1. - */ - static long -char_idx2byte(char_u *str, size_t str_len, varnumber_T idx) -{ - varnumber_T nchar = idx; - size_t nbyte = 0; - - if (nchar >= 0) - { - while (nchar > 0 && nbyte < str_len) - { - nbyte += MB_CPTR2LEN(str + nbyte); - --nchar; - } - } - else - { - nbyte = str_len; - while (nchar < 0 && nbyte > 0) - { - --nbyte; - nbyte -= mb_head_off(str, str + nbyte); - ++nchar; - } - if (nchar < 0) - return -1; - } - return (long)nbyte; -} - -/* - * Return the slice "str[first:last]" using character indexes. - * Return NULL when the result is empty. - */ - char_u * -string_slice(char_u *str, varnumber_T first, varnumber_T last) -{ - long start_byte, end_byte; - size_t slen; - - if (str == NULL) - return NULL; - slen = STRLEN(str); - start_byte = char_idx2byte(str, slen, first); - if (start_byte < 0) - start_byte = 0; // first index very negative: use zero - if (last == -1) - end_byte = (long)slen; - else - { - end_byte = char_idx2byte(str, slen, last); - if (end_byte >= 0 && end_byte < (long)slen) - // end index is inclusive - end_byte += MB_CPTR2LEN(str + end_byte); - } - - if (start_byte >= (long)slen || end_byte <= start_byte) - return NULL; - return vim_strnsave(str + start_byte, end_byte - start_byte); -} - /* * Handle: * - expr[expr], expr[expr:expr] subscript diff --git a/src/evalfunc.c b/src/evalfunc.c index 8e49013dda..1428b8f457 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -47,6 +47,7 @@ static void f_ceil(typval_T *argvars, typval_T *rettv); #endif static void f_changenr(typval_T *argvars, typval_T *rettv); static void f_char2nr(typval_T *argvars, typval_T *rettv); +static void f_charcol(typval_T *argvars, typval_T *rettv); static void f_charidx(typval_T *argvars, typval_T *rettv); static void f_col(typval_T *argvars, typval_T *rettv); static void f_confirm(typval_T *argvars, typval_T *rettv); @@ -87,12 +88,14 @@ static void f_function(typval_T *argvars, typval_T *rettv); static void f_garbagecollect(typval_T *argvars, typval_T *rettv); static void f_get(typval_T *argvars, typval_T *rettv); static void f_getchangelist(typval_T *argvars, typval_T *rettv); +static void f_getcharpos(typval_T *argvars, typval_T *rettv); static void f_getcharsearch(typval_T *argvars, typval_T *rettv); static void f_getenv(typval_T *argvars, typval_T *rettv); static void f_getfontname(typval_T *argvars, typval_T *rettv); static void f_getjumplist(typval_T *argvars, typval_T *rettv); static void f_getpid(typval_T *argvars, typval_T *rettv); static void f_getcurpos(typval_T *argvars, typval_T *rettv); +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_getpos(typval_T *argvars, typval_T *rettv); static void f_getreg(typval_T *argvars, typval_T *rettv); static void f_getreginfo(typval_T *argvars, typval_T *rettv); @@ -190,7 +193,9 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv); static void f_searchpair(typval_T *argvars, typval_T *rettv); static void f_searchpairpos(typval_T *argvars, typval_T *rettv); static void f_searchpos(typval_T *argvars, typval_T *rettv); +static void f_setcharpos(typval_T *argvars, typval_T *rettv); static void f_setcharsearch(typval_T *argvars, typval_T *rettv); +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_setenv(typval_T *argvars, typval_T *rettv); static void f_setfperm(typval_T *argvars, typval_T *rettv); static void f_setpos(typval_T *argvars, typval_T *rettv); @@ -790,6 +795,8 @@ static funcentry_T global_functions[] = ret_number, f_char2nr}, {"charclass", 1, 1, FEARG_1, NULL, ret_number, f_charclass}, + {"charcol", 1, 1, FEARG_1, NULL, + ret_number, f_charcol}, {"charidx", 2, 3, FEARG_1, NULL, ret_number, f_charidx}, {"chdir", 1, 1, FEARG_1, NULL, @@ -928,6 +935,8 @@ static funcentry_T global_functions[] = ret_number, f_getchar}, {"getcharmod", 0, 0, 0, NULL, ret_number, f_getcharmod}, + {"getcharpos", 1, 1, FEARG_1, NULL, + ret_list_number, f_getcharpos}, {"getcharsearch", 0, 0, 0, NULL, ret_dict_any, f_getcharsearch}, {"getcmdline", 0, 0, 0, NULL, @@ -942,6 +951,8 @@ static funcentry_T global_functions[] = ret_list_string, f_getcompletion}, {"getcurpos", 0, 1, FEARG_1, NULL, ret_list_number, f_getcurpos}, + {"getcursorcharpos", 0, 1, FEARG_1, NULL, + ret_list_number, f_getcursorcharpos}, {"getcwd", 0, 2, FEARG_1, NULL, ret_string, f_getcwd}, {"getenv", 1, 1, FEARG_1, NULL, @@ -1394,10 +1405,14 @@ static funcentry_T global_functions[] = ret_void, f_setbufvar}, {"setcellwidths", 1, 1, FEARG_1, NULL, ret_void, f_setcellwidths}, + {"setcharpos", 2, 2, FEARG_2, NULL, + ret_number, f_setcharpos}, {"setcharsearch", 1, 1, FEARG_1, NULL, ret_void, f_setcharsearch}, {"setcmdpos", 1, 1, FEARG_1, NULL, ret_number, f_setcmdpos}, + {"setcursorcharpos", 1, 3, FEARG_1, NULL, + ret_number, f_setcursorcharpos}, {"setenv", 2, 2, FEARG_2, NULL, ret_void, f_setenv}, {"setfperm", 2, 2, FEARG_1, NULL, @@ -1753,7 +1768,7 @@ static funcentry_T global_functions[] = {"winrestview", 1, 1, FEARG_1, NULL, ret_void, f_winrestview}, {"winsaveview", 0, 0, 0, NULL, - ret_dict_any, f_winsaveview}, + ret_dict_number, f_winsaveview}, {"winwidth", 1, 1, FEARG_1, NULL, ret_number, f_winwidth}, {"wordcount", 0, 0, 0, NULL, @@ -1914,6 +1929,15 @@ internal_func_ret_type(int idx, int argcount, type_T **argtypes) return global_functions[idx].f_retfunc(argcount, argtypes); } +/* + * Return TRUE if "idx" is for the map() function. + */ + int +internal_func_is_map(int idx) +{ + return global_functions[idx].f_func == f_map; +} + /* * Check the argument count to use for internal function "idx". * Returns -1 for failure, 0 if no method base accepted, 1 if method base is @@ -2423,6 +2447,61 @@ f_char2nr(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = tv_get_string(&argvars[0])[0]; } +/* + * Get the current cursor column and store it in 'rettv'. If 'charcol' is TRUE, + * returns the character index of the column. Otherwise, returns the byte index + * of the column. + */ + static void +get_col(typval_T *argvars, typval_T *rettv, int charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) + { + if (fp->col == MAXCOL) + { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + else + col = MAXCOL; + } + else + { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) + { + char_u *p = ml_get_cursor(); + + if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, + curwin->w_virtcol - curwin->w_cursor.coladd)) + { + int l; + + if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) + col += l; + } + } + } + } + rettv->vval.v_number = col; +} + +/* + * "charcol()" function + */ + static void +f_charcol(typval_T *argvars, typval_T *rettv) +{ + get_col(argvars, rettv, TRUE); +} + /* * "charidx()" function */ @@ -2497,42 +2576,7 @@ get_optional_window(typval_T *argvars, int idx) static void f_col(typval_T *argvars, typval_T *rettv) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) - { - if (fp->col == MAXCOL) - { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; - } - else - { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) - { - char_u *p = ml_get_cursor(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) - { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, FALSE); } /* @@ -2633,26 +2677,24 @@ f_cosh(typval_T *argvars, typval_T *rettv) #endif /* - * "cursor(lnum, col)" function, or - * "cursor(list)" - * - * Moves the cursor to the specified line and column. - * Returns 0 when the position could be set, -1 otherwise. + * Set the cursor position. + * If 'charcol' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. */ static void -f_cursor(typval_T *argvars, typval_T *rettv) +set_cursorpos(typval_T *argvars, typval_T *rettv, int charcol) { long line, col; long coladd = 0; int set_curswant = TRUE; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; @@ -2666,15 +2708,24 @@ f_cursor(typval_T *argvars, typval_T *rettv) set_curswant = FALSE; } } - else + else if ((argvars[0].v_type == VAR_NUMBER || + argvars[0].v_type == VAR_STRING) + && argvars[1].v_type == VAR_NUMBER) { line = tv_get_lnum(argvars); if (line < 0) semsg(_(e_invarg2), tv_get_string(&argvars[0])); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) + col = buf_charidx_to_byteidx(curbuf, line, col); if (argvars[2].v_type != VAR_UNKNOWN) coladd = (long)tv_get_number_chk(&argvars[2], NULL); } + else + { + emsg(_(e_invarg)); + return; + } if (line < 0 || col < 0 || coladd < 0) return; // type error; errmsg already given if (line > 0) @@ -2693,6 +2744,19 @@ f_cursor(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = 0; } +/* + * "cursor(lnum, col)" function, or + * "cursor(list)" + * + * Moves the cursor to the specified line and column. + * Returns 0 when the position could be set, -1 otherwise. + */ + static void +f_cursor(typval_T *argvars, typval_T *rettv) +{ + set_cursorpos(argvars, rettv, FALSE); +} + #ifdef MSWIN /* * "debugbreak()" function @@ -3497,7 +3561,8 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) { name = s; trans_name = trans_function_name(&name, &is_global, FALSE, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, + NULL, NULL, NULL); if (*name != NUL) s = NULL; } @@ -3886,6 +3951,88 @@ f_getchangelist(typval_T *argvars, typval_T *rettv) #endif } + static void +getpos_both( + typval_T *argvars, + typval_T *rettv, + int getcurpos, + int charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + list_T *l; + int fnum = -1; + + if (rettv_list_alloc(rettv) == OK) + { + l = rettv->vval.v_list; + if (getcurpos) + { + if (argvars[0].v_type != VAR_UNKNOWN) + { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) + fp = &wp->w_cursor; + } + else + fp = &curwin->w_cursor; + if (fp != NULL && charcol) + { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, + pos.col) - 1; + fp = &pos; + } + } + else + fp = var2fpos(&argvars[0], TRUE, &fnum, charcol); + if (fnum != -1) + list_append_number(l, (varnumber_T)fnum); + else + list_append_number(l, (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum + : (varnumber_T)0); + list_append_number(l, (fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : + (varnumber_T)0); + if (getcurpos) + { + int save_set_curswant = curwin->w_set_curswant; + colnr_T save_curswant = curwin->w_curswant; + colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) + update_curswant(); + list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL + ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) + { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } + } + else + rettv->vval.v_number = FALSE; +} + +/* + * "getcharpos()" function + */ + static void +f_getcharpos(typval_T *argvars UNUSED, typval_T *rettv) +{ + getpos_both(argvars, rettv, FALSE, TRUE); +} + /* * "getcharsearch()" function */ @@ -4018,77 +4165,19 @@ f_getpid(typval_T *argvars UNUSED, typval_T *rettv) rettv->vval.v_number = mch_get_pid(); } - static void -getpos_both( - typval_T *argvars, - typval_T *rettv, - int getcurpos) -{ - pos_T *fp = NULL; - win_T *wp = curwin; - list_T *l; - int fnum = -1; - - if (rettv_list_alloc(rettv) == OK) - { - l = rettv->vval.v_list; - if (getcurpos) - { - if (argvars[0].v_type != VAR_UNKNOWN) - { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) - fp = &wp->w_cursor; - } - else - fp = &curwin->w_cursor; - } - else - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fnum != -1) - list_append_number(l, (varnumber_T)fnum); - else - list_append_number(l, (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum - : (varnumber_T)0); - list_append_number(l, (fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : - (varnumber_T)0); - if (getcurpos) - { - int save_set_curswant = curwin->w_set_curswant; - colnr_T save_curswant = curwin->w_curswant; - colnr_T save_virtcol = curwin->w_virtcol; - - if (wp == curwin) - update_curswant(); - list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (wp == curwin && save_set_curswant) - { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } - } - else - rettv->vval.v_number = FALSE; -} - /* * "getcurpos()" function */ static void f_getcurpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, TRUE); + getpos_both(argvars, rettv, TRUE, FALSE); +} + + static void +f_getcursorcharpos(typval_T *argvars, typval_T *rettv) +{ + getpos_both(argvars, rettv, TRUE, TRUE); } /* @@ -4097,7 +4186,7 @@ f_getcurpos(typval_T *argvars, typval_T *rettv) static void f_getpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, FALSE); + getpos_both(argvars, rettv, FALSE, FALSE); } /* @@ -6217,14 +6306,14 @@ f_line(typval_T *argvars, typval_T *rettv) == OK) { check_cursor(); - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); } restore_win_noblock(save_curwin, save_curtab, TRUE); } } else // use current window - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; @@ -8099,6 +8188,60 @@ f_searchpos(typval_T *argvars, typval_T *rettv) list_append_number(rettv->vval.v_list, (varnumber_T)n); } +/* + * Set the cursor or mark position. + * If 'charpos' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. + */ + static void +set_position(typval_T *argvars, typval_T *rettv, int charpos) +{ + pos_T pos; + int fnum; + char_u *name; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(argvars); + if (name != NULL) + { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) + { + if (pos.col != MAXCOL && --pos.col < 0) + pos.col = 0; + if ((name[0] == '.' && name[1] == NUL)) + { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) + { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = FALSE; + } + check_cursor(); + rettv->vval.v_number = 0; + } + else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) + { + // set mark + if (setmark_pos(name[1], &pos, fnum) == OK) + rettv->vval.v_number = 0; + } + else + emsg(_(e_invarg)); + } + } +} +/* + * "setcharpos()" function + */ + static void +f_setcharpos(typval_T *argvars, typval_T *rettv) +{ + set_position(argvars, rettv, TRUE); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED) { @@ -8140,6 +8283,15 @@ f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED) } } +/* + * "setcursorcharpos" function + */ + static void +f_setcursorcharpos(typval_T *argvars, typval_T *rettv UNUSED) +{ + set_cursorpos(argvars, rettv, TRUE); +} + /* * "setenv()" function */ @@ -8199,41 +8351,7 @@ f_setfperm(typval_T *argvars, typval_T *rettv) static void f_setpos(typval_T *argvars, typval_T *rettv) { - pos_T pos; - int fnum; - char_u *name; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - name = tv_get_string_chk(argvars); - if (name != NULL) - { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) - { - if (pos.col != MAXCOL && --pos.col < 0) - pos.col = 0; - if (name[0] == '.' && name[1] == NUL) - { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) - { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = FALSE; - } - check_cursor(); - rettv->vval.v_number = 0; - } - else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) - { - // set mark - if (setmark_pos(name[1], &pos, fnum) == OK) - rettv->vval.v_number = 0; - } - else - emsg(_(e_invarg)); - } - } + set_position(argvars, rettv, FALSE); } /* @@ -9981,7 +10099,7 @@ f_virtcol(typval_T *argvars, typval_T *rettv) int fnum = curbuf->b_fnum; int len; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], FALSE, &fnum, FALSE); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { diff --git a/src/evalvars.c b/src/evalvars.c index a6d2988d77..90e2dd19ab 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2641,8 +2641,7 @@ check_vars(char_u *name, int len) * Find variable "name" in the list of variables. * Return a pointer to it if found, NULL if not found. * Careful: "a:0" variables don't have a name. - * When "htp" is not NULL we are writing to the variable, set "htp" to the - * hashtab_T used. + * When "htp" is not NULL set "htp" to the hashtab_T used. */ dictitem_T * find_var(char_u *name, hashtab_T **htp, int no_autoload) @@ -2656,12 +2655,12 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload) *htp = ht; if (ht == NULL) return NULL; - ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) return ret; // Search in parent scope for lambda - ret = find_var_in_scoped_ht(name, no_autoload || htp != NULL); + ret = find_var_in_scoped_ht(name, no_autoload); if (ret != NULL) return ret; @@ -2671,8 +2670,7 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload) ht = get_script_local_ht(); if (ht != NULL) { - ret = find_var_in_ht(ht, *name, varname, - no_autoload || htp != NULL); + ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) { if (htp != NULL) diff --git a/src/ex_cmds.c b/src/ex_cmds.c index c61810fe6c..24763d94d3 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -1360,8 +1360,8 @@ do_shell( #endif #ifdef MSWIN int winstart = FALSE; - int keep_termcap = FALSE; #endif + int keep_termcap = !termcap_active; /* * Disallow shell commands for "rvim". @@ -1395,9 +1395,7 @@ do_shell( msg_putchar('\r'); // put cursor at start of line if (!autocmd_busy) { -#ifdef MSWIN if (!keep_termcap) -#endif stoptermcap(); } #ifdef MSWIN @@ -1488,9 +1486,7 @@ do_shell( } #endif // FEAT_GUI_MSWIN -#ifdef MSWIN if (!keep_termcap) // if keep_termcap is TRUE didn't stop termcap -#endif starttermcap(); // start termcap if not done by wait_return() /* diff --git a/src/ex_docmd.c b/src/ex_docmd.c index d9617bea06..3f8d1710a7 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2744,6 +2744,28 @@ parse_command_modifiers( } p = skip_range(eap->cmd, TRUE, NULL); + + // In Vim9 script a variable can shadow a command modifier: + // verbose = 123 + // verbose += 123 + // silent! verbose = func() + // verbose.member = 2 + // verbose[expr] = 2 + // But not: + // verbose [a, b] = list + if (in_vim9script()) + { + char_u *s, *n; + + for (s = p; ASCII_ISALPHA(*s); ++s) + ; + n = skipwhite(s); + if (vim_strchr((char_u *)".=", *n) != NULL + || *s == '[' + || (*n != NUL && n[1] == '=')) + break; + } + switch (*p) { // When adding an entry, also modify cmd_exists(). diff --git a/src/if_ruby.c b/src/if_ruby.c index 9e58902141..a8c8379b62 100644 --- a/src/if_ruby.c +++ b/src/if_ruby.c @@ -612,11 +612,13 @@ rb_check_type_stub(VALUE obj, int t) { dll_rb_check_type(obj, t); } +# if VIM_SIZEOF_INT < VIM_SIZEOF_LONG // 64 bits only unsigned long rb_num2uint_stub(VALUE x) { return dll_rb_num2uint(x); } +# endif void ruby_malloc_size_overflow_stub(size_t x, size_t y) { diff --git a/src/list.c b/src/list.c index 56ee5a5778..2b44ebacb8 100644 --- a/src/list.c +++ b/src/list.c @@ -924,7 +924,7 @@ list_slice_or_index( if (!range) { if (verbose) - semsg(_(e_listidx), n1); + semsg(_(e_listidx), n1_arg); return FAIL; } n1 = n1 < 0 ? 0 : len; @@ -2188,10 +2188,13 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) int stride = l->lv_u.nonmat.lv_stride; // List from range(): loop over the numbers - l->lv_first = NULL; - l->lv_u.mat.lv_last = NULL; - l->lv_len = 0; - l->lv_u.mat.lv_idx_item = NULL; + if (filtermap != FILTERMAP_MAPNEW) + { + l->lv_first = NULL; + l->lv_u.mat.lv_last = NULL; + l->lv_len = 0; + l->lv_u.mat.lv_idx_item = NULL; + } for (idx = 0; idx < len; ++idx) { diff --git a/src/mbyte.c b/src/mbyte.c index c967de6248..3fa608b0ea 100644 --- a/src/mbyte.c +++ b/src/mbyte.c @@ -4308,7 +4308,7 @@ mb_charlen(char_u *str) return count; } -#if defined(FEAT_SPELL) || defined(PROTO) +#if (defined(FEAT_SPELL) || defined(FEAT_EVAL)) || defined(PROTO) /* * Like mb_charlen() but for a string with specified length. */ diff --git a/src/proto/eval.pro b/src/proto/eval.pro index ec453645a1..66aa430334 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -55,8 +55,10 @@ char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copy char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); char_u *string_quote(char_u *str, int function); int string2float(char_u *text, float_T *value); -pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum); -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp); +int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx); +pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol); +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int char_col); int get_env_len(char_u **arg); int get_id_len(char_u **arg); int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose); @@ -64,8 +66,6 @@ char_u *find_name_end(char_u *arg, char_u **expr_start, char_u **expr_end, int f int eval_isnamec(int c); int eval_isnamec1(int c); int eval_isdictc(int c); -char_u *char_from_string(char_u *str, varnumber_T index); -char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last); int handle_subscript(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro index 562a7c83d9..ad4b98a29d 100644 --- a/src/proto/evalfunc.pro +++ b/src/proto/evalfunc.pro @@ -6,6 +6,7 @@ int has_internal_func(char_u *name); char *internal_func_name(int idx); int internal_func_check_arg_types(type_T **types, int idx, int argcount); type_T *internal_func_ret_type(int idx, int argcount, type_T **argtypes); +int internal_func_is_map(int idx); int check_internal_func(int idx, int argcount); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); void call_internal_func_by_idx(int idx, typval_T *argvars, typval_T *rettv); diff --git a/src/proto/typval.pro b/src/proto/typval.pro index 46f893147a..7a65376c6e 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -18,7 +18,7 @@ char_u *tv_get_string_buf_chk(typval_T *varp, char_u *buf); char_u *tv_stringify(typval_T *varp, char_u *buf); int tv_check_lock(typval_T *tv, char_u *name, int use_gettext); void copy_tv(typval_T *from, typval_T *to); -int typval_compare(typval_T *typ1, typval_T *typ2, exptype_T type, int ic); +int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, int ic); char_u *typval_tostring(typval_T *arg); int tv_islocked(typval_T *tv); int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive); diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index f3bd2ae731..08dedae0a6 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -4,7 +4,7 @@ hashtab_T *func_tbl_get(void); char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); -char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); +char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, type_T **type, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); @@ -31,7 +31,7 @@ int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, void user_func_error(int error, char_u *name); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *printable_func_name(ufunc_T *fp); -char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); +char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type); char_u *untrans_function_name(char_u *name); void list_functions(regmatch_T *regmatch); ufunc_T *define_function(exarg_T *eap, char_u *name_arg); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index 52ccb0608c..7800d3ba59 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -1,6 +1,6 @@ /* vim9compile.c */ int check_defined(char_u *p, size_t len, cctx_T *cctx); -int check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2); +int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2); int use_typecheck(type_T *actual, type_T *expected); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); @@ -9,7 +9,7 @@ char_u *peek_next_line_from_context(cctx_T *cctx); char_u *next_line_from_context(cctx_T *cctx, int skip_comment); char_u *to_name_end(char_u *arg, int use_namespace); char_u *to_name_const_end(char_u *arg); -exptype_T get_compare_type(char_u *p, int *len, int *type_is); +exprtype_T get_compare_type(char_u *p, int *len, int *type_is); void error_white_both(char_u *op, int len); int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro index 8e948c5508..2337a6c450 100644 --- a/src/proto/vim9execute.pro +++ b/src/proto/vim9execute.pro @@ -1,6 +1,8 @@ /* vim9execute.c */ void to_string_error(vartype_T vartype); void funcstack_check_refcount(funcstack_T *funcstack); +char_u *char_from_string(char_u *str, varnumber_T index); +char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last); int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx); int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void ex_disassemble(exarg_T *eap); diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro index ae947e699f..bc306687f8 100644 --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -16,6 +16,7 @@ void type_mismatch(type_T *expected, type_T *actual); void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); int check_type(type_T *expected, type_T *actual, int give_msg, int argidx); int check_arg_type(type_T *expected, type_T *actual, int argidx); +int check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name); char_u *skip_type(char_u *start, int optional); type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error); int equal_type(type_T *type1, type_T *type2); diff --git a/src/structs.h b/src/structs.h index ddedeef551..5ad27e262f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1944,6 +1944,7 @@ typedef struct { partial_T *partial; // for extra arguments dict_T *selfdict; // Dictionary for "self" typval_T *basetv; // base for base->method() + type_T *check_type; // type from funcref or NULL } funcexe_T; /* @@ -1964,6 +1965,14 @@ typedef struct funcstack_S int fs_copyID; // for garray_T collection } funcstack_T; +typedef struct outer_S outer_T; +struct outer_S { + garray_T *out_stack; // stack from outer scope + int out_frame_idx; // index of stack frame in out_stack + outer_T *out_up; // outer scope of outer scope or NULL + int out_up_is_copy; // don't free out_up +}; + struct partial_S { int pt_refcount; // reference count @@ -1974,11 +1983,11 @@ struct partial_S int pt_auto; // when TRUE the partial was created for using // dict.member in handle_subscript() - // For a compiled closure: the arguments and local variables. - garray_T *pt_ectx_stack; // where to find local vars - int pt_ectx_frame; // index of function frame in uf_ectx_stack - funcstack_T *pt_funcstack; // copy of stack, used after context - // function returns + // For a compiled closure: the arguments and local variables scope + outer_T pt_outer; + + funcstack_T *pt_funcstack; // copy of stack, used after context + // function returns int pt_argc; // number of arguments typval_T *pt_argv; // arguments in allocated array @@ -4047,7 +4056,7 @@ typedef enum EXPR_MULT, // * EXPR_DIV, // / EXPR_REM, // % -} exptype_T; +} exprtype_T; /* * Structure used for reading in json_decode(). diff --git a/src/tag.c b/src/tag.c index 3dfb8fee5d..e3cd87677c 100644 --- a/src/tag.c +++ b/src/tag.c @@ -4201,7 +4201,7 @@ tagstack_push_items(win_T *wp, list_T *l) // parse 'from' for the cursor position before the tag jump if ((di = dict_find(itemdict, (char_u *)"from", -1)) == NULL) continue; - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, FALSE) != OK) continue; if ((tagname = dict_get_string(itemdict, (char_u *)"tagname", TRUE)) == NULL) diff --git a/src/testdir/test_cursor_func.vim b/src/testdir/test_cursor_func.vim index cbaad24187..19b3416b79 100644 --- a/src/testdir/test_cursor_func.vim +++ b/src/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -123,4 +123,187 @@ func Test_screenpos_number() bwipe! endfunc +func SaveVisualStartCharPos() + call add(g:VisualStartPos, getcharpos('v')) + return '' +endfunc + +" Test for the getcharpos() function +func Test_getcharpos() + call assert_fails('call getcharpos({})', 'E731:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 4, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos) + call assert_equal([0, 2, 9, 0], getcharpos('v')) + let g:VisualStartPos = [] + exe "normal 3Gv$\o\" + call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos) + let g:VisualStartPos = [] + exe "normal 1Gv$\o\" + call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos) + vunmap + + %bw! +endfunc + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', test_null_list())) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + call setcharpos("'m", [0, 2, 9, 0]) + normal `m + call assert_equal([2, 11], [line('.'), col('.')]) + + %bw! + call assert_equal(-1, setcharpos('.', [10, 3, 1, 0])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + return '' +endfunc + +" Test for the charcol() function +func Test_charcol() + call assert_fails('call charcol({})', 'E731:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([7, 9, 5], g:VisualStartCol) + call assert_equal(9, charcol('v')) + let g:VisualStartCol = [] + exe "normal 3Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + let g:VisualStartCol = [] + exe "normal 1Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + vunmap + + %bw! +endfunc + +" Test for getcursorcharpos() +func Test_getcursorcharpos() + call assert_equal(getcursorcharpos(), getcursorcharpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], getcursorcharpos()) + normal 4G9l + call assert_equal([0, 4, 9, 0, 9], getcursorcharpos()) + + let winid = win_getid() + normal 2G5l + wincmd w + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(test_null_list())', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim index 316fb42f22..e5f521c85c 100644 --- a/src/testdir/test_eval_stuff.vim +++ b/src/testdir/test_eval_stuff.vim @@ -218,6 +218,7 @@ func Test_vvar_scriptversion4() call assert_equal(15, 0o17) call assert_equal(15, 0O17) call assert_equal(18, 018) + call assert_equal(511, 0o777) call assert_equal(64, 0b1'00'00'00) call assert_equal(1048576, 0x10'00'00) call assert_equal(32768, 0o10'00'00) @@ -233,6 +234,7 @@ func Test_vvar_scriptversion1() call assert_equal(15, 0o17) call assert_equal(15, 0O17) call assert_equal(18, 018) + call assert_equal(511, 0o777) endfunc func Test_scriptversion_fail() diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index 2a21ca065f..21480633c7 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -1091,6 +1091,13 @@ def Test_assign_lambda() assert_equal(123, FuncRef_Any()) END CheckScriptSuccess(lines) + + lines =<< trim END + var Ref: func(number) + Ref = (j) => !j + END + CheckDefFailure(lines, 'E1012: Type mismatch; expected func(number) but got func(any): bool') + CheckScriptFailure(['vim9script'] + lines, 'E1012: Type mismatch; expected func(number) but got func(any): any') enddef def Test_heredoc() @@ -1457,5 +1464,32 @@ def Test_unlet() assert_equal('', $ENVVAR) enddef +def Test_assign_command_modifier() + var lines =<< trim END + var verbose = 0 + verbose = 1 + assert_equal(1, verbose) + silent verbose = 2 + assert_equal(2, verbose) + silent verbose += 2 + assert_equal(4, verbose) + silent verbose -= 1 + assert_equal(3, verbose) + + var topleft = {one: 1} + sandbox topleft.one = 3 + assert_equal({one: 3}, topleft) + leftabove topleft[' '] = 4 + assert_equal({one: 3, ' ': 4}, topleft) + + var x: number + var y: number + silent [x, y] = [1, 2] + assert_equal(1, x) + assert_equal(2, y) + END + CheckDefAndScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 4eb2d76dfd..673f0c0562 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -231,7 +231,7 @@ def Test_extend_arg_types() assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, s:string_keep)) var res: list> - extend(res, map([1, 2], (_, v) => ({}))) + extend(res, mapnew([1, 2], (_, v) => ({}))) assert_equal([{}, {}], res) CheckDefFailure(['extend([1, 2], 3)'], 'E1013: Argument 2: type mismatch, expected list but got number') @@ -320,6 +320,15 @@ def Test_map_function_arg() CheckDefAndScriptSuccess(lines) enddef +def Test_map_item_type() + var lines =<< trim END + var l = ['a', 'b', 'c'] + map(l, (k, v) => k .. '/' .. v ) + assert_equal(['0/a', '1/b', '2/c'], l) + END + CheckDefAndScriptSuccess(lines) +enddef + def Test_filereadable() assert_false(filereadable("")) assert_false(filereadable(test_null_string())) @@ -728,7 +737,7 @@ enddef def Test_submatch() var pat = 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)' - var Rep = () => range(10)->map((_, v) => submatch(v, true))->string() + var Rep = () => range(10)->mapnew((_, v) => submatch(v, true))->string() var actual = substitute('A123456789', pat, Rep, '') var expected = "[['A123456789'], ['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9']]" actual->assert_equal(expected) @@ -786,6 +795,16 @@ def Test_winrestcmd() close enddef +def Test_winsaveview() + var view: dict = winsaveview() + + var lines =<< trim END + var view: list = winsaveview() + END + CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected list but got dict', 1) +enddef + + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim index 5b84feceea..f5cb1c8739 100644 --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -661,6 +661,12 @@ def Test_range_after_command_modifier() bwipe! enddef +def Test_silent_pattern() + new + silent! :/pat/put _ + bwipe! +enddef + def Test_eval_command() var from = 3 var to = 5 diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index b0f84a6e1e..ca9b90e886 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -117,8 +117,7 @@ def Test_disassemble_exec_expr() '\d 2STRING stack\[-1\]\_s*' .. '\d\+ PUSHS ".txt"\_s*' .. '\d\+ EXECCONCAT 4\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -134,8 +133,7 @@ def Test_disassemble_yank_range() '\d EXEC norm! m\[jjm\]\_s*' .. ' :''\[,''\]yank\_s*' .. '\d EXEC :''\[,''\]yank\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -149,8 +147,7 @@ def Test_disassemble_put_expr() ' :3put ="text"\_s*' .. '\d PUSHS "text"\_s*' .. '\d PUT = 3\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -164,8 +161,7 @@ def Test_disassemble_put_range() ' :$-2put a\_s*' .. '\d RANGE $-2\_s*' .. '\d PUT a range\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -273,8 +269,7 @@ def Test_disassemble_store_member() '\d\+ PUSHS "a"\_s*' .. '\d\+ LOAD $1\_s*' .. '\d\+ STOREDICT\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -297,8 +292,7 @@ def Test_disassemble_store_index() '\d LOAD $0\_s*' .. '\d MEMBER dd\_s*' .. '\d STOREINDEX\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -333,8 +327,7 @@ def Test_disassemble_list_assign() '\d\+ STORE $1\_s*' .. '\d\+ SLICE 2\_s*' .. '\d\+ STORE $2\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -362,8 +355,7 @@ def Test_disassemble_list_add() '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -390,8 +382,7 @@ def Test_disassemble_blob_add() '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ BLOBAPPEND\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -576,18 +567,17 @@ def Test_disassemble_closure() var res = execute('disass g:Append') assert_match('\d\_s*' .. 'local ..= arg\_s*' .. - '\d LOADOUTER $0\_s*' .. + '\d LOADOUTER level 1 $0\_s*' .. '\d LOAD arg\[-1\]\_s*' .. '\d CONCAT\_s*' .. - '\d STOREOUTER $0\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d STOREOUTER level 1 $0\_s*' .. + '\d RETURN 0', res) res = execute('disass g:Get') assert_match('\d\_s*' .. 'return local\_s*' .. - '\d LOADOUTER $0\_s*' .. + '\d LOADOUTER level 1 $0\_s*' .. '\d RETURN', res) @@ -615,8 +605,7 @@ def Test_disassemble_pcall() '\d PCALL top (argc 1)\_s*' .. '\d PCALL end\_s*' .. '\d DROP\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -865,8 +854,7 @@ def Test_disassemble_function() '\d PUSHS "UserFunc"\_s*' .. '\d BCALL funcref(argc 1)\_s*' .. '\d STORE $2\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -893,8 +881,7 @@ def Test_disassemble_channel() 'var chan1: channel\_s*' .. '\d PUSHCHANNEL 0\_s*' .. '\d STORE $2\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -966,8 +953,7 @@ def Test_nested_func() 'echomsg "inner"\_s*' .. 'enddef\_s*' .. '\d NEWFUNC \d\+ Inner\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -989,8 +975,7 @@ def Test_nested_def_list() '\d DEF /Info\_s*' .. 'def /Info/\_s*' .. '\d DEF /Info/\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -1122,8 +1107,7 @@ def Test_disassemble_for_loop_unpack() 'endfor\_s*' .. '\d\+ JUMP -> 8\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', instr) enddef @@ -1143,8 +1127,7 @@ def Test_disassemble_typecast() '\d NEWLIST size 2\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN\_s*', + '\d RETURN 0\_s*', instr) enddef @@ -1631,8 +1614,7 @@ def Test_dsassemble_falsy_op() 'echo "" ?? "empty string"\_s*' .. '\d\+ PUSHS "empty string"\_s*' .. '\d\+ ECHO 1\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -1659,8 +1641,7 @@ def Test_disassemble_compare_const() 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' .. '\d PUSHNR 42.*' .. '\d ECHO 1.*' .. - '\d PUSHNR 0.*' .. - '\d RETURN.*', + '\d RETURN 0', instr) else # condition false, function just returns @@ -1668,8 +1649,7 @@ def Test_disassemble_compare_const() 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '[ \n]*' .. 'echo 42[ \n]*' .. 'endif[ \n]*' .. - '\s*\d PUSHNR 0.*' .. - '\d RETURN.*', + '\d RETURN 0', instr) endif @@ -1707,8 +1687,7 @@ def Test_disassemble_execute() '\d\+ LOAD $1\_s*' .. '\d\+ CONCAT\_s*' .. '\d\+ EXECUTE 1\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -1727,8 +1706,7 @@ def Test_disassemble_echomsg() "echoerr 'went' .. 'wrong'\\_s*" .. '\d PUSHS "wentwrong"\_s*' .. '\d ECHOERR 1\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -1837,8 +1815,7 @@ def Test_shuffle() '\d SHUFFLE 2 up 1\_s*' .. '\d BCALL append(argc 2)\_s*' .. '\d DROP\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -1861,8 +1838,7 @@ def Test_silent() '\d PUSHS "error"\_s*' .. '\d ECHOERR 1\_s*' .. '\d CMDMOD_REV\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index ae5ca2816c..50537c858a 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -597,6 +597,18 @@ def Test_expr4_equal() CheckDefFailure(["var x = 'a' == "], 'E1097:', 3) CheckDefExecFailure(['var items: any', 'eval 1', 'eval 2', 'if items == []', 'endif'], 'E691:', 4) + + CheckDefExecFailure(['var x: any = "a"', 'echo x == true'], 'E1072: Cannot compare string with bool', 2) + CheckDefExecFailure(["var x: any = true", 'echo x == ""'], 'E1072: Cannot compare bool with string', 2) + CheckDefExecFailure(["var x: any = 99", 'echo x == true'], 'E1138', 2) + CheckDefExecFailure(["var x: any = 'a'", 'echo x == 99'], 'E1030:', 2) + + for op in ['>', '>=', '<', '<=', '=~', '!~'] + CheckDefExecFailure([ + "var a: any = 'a'", + 'var b: any = true', + 'echo a ' .. op .. ' b'], 'E1072:', 3) + endfor enddef " test != comperator @@ -1847,10 +1859,10 @@ def Test_expr7_lambda() # line continuation inside lambda with "cond ? expr : expr" works var ll = range(3) - map(ll, (k, v) => v % 2 ? { + var dll = mapnew(ll, (k, v) => v % 2 ? { ['111']: 111 } : {} ) - assert_equal([{}, {111: 111}, {}], ll) + assert_equal([{}, {111: 111}, {}], dll) ll = range(3) map(ll, (k, v) => v == 8 || v @@ -1934,10 +1946,10 @@ def Test_expr7_new_lambda() # line continuation inside lambda with "cond ? expr : expr" works var ll = range(3) - map(ll, (k, v) => v % 2 ? { + var dll = mapnew(ll, (k, v) => v % 2 ? { ['111']: 111 } : {} ) - assert_equal([{}, {111: 111}, {}], ll) + assert_equal([{}, {111: 111}, {}], dll) ll = range(3) map(ll, (k, v) => v == 8 || v @@ -2304,7 +2316,7 @@ def Test_expr7_any_index_slice() # string is permissive, index out of range accepted g:teststring = 'abcdef' assert_equal('b', g:teststring[1]) - assert_equal('', g:teststring[-1]) + assert_equal('f', g:teststring[-1]) assert_equal('', g:teststring[99]) assert_equal('b', g:teststring[1 : 1]) @@ -2368,10 +2380,10 @@ def Test_expr7_any_index_slice() CheckDefExecFailure(['echo g:testblob[-3]'], 'E979:', 1) CheckScriptFailure(['vim9script', 'echo g:testblob[-3]'], 'E979:', 2) - CheckDefExecFailure(['echo g:testlist[4]'], 'E684:', 1) + CheckDefExecFailure(['echo g:testlist[4]'], 'E684: list index out of range: 4', 1) CheckScriptFailure(['vim9script', 'echo g:testlist[4]'], 'E684:', 2) CheckDefExecFailure(['echo g:testlist[-5]'], 'E684:', 1) - CheckScriptFailure(['vim9script', 'echo g:testlist[-5]'], 'E684:', 2) + CheckScriptFailure(['vim9script', 'echo g:testlist[-5]'], 'E684: list index out of range: -5', 2) CheckDefExecFailure(['echo g:testdict["a" : "b"]'], 'E719:', 1) CheckScriptFailure(['vim9script', 'echo g:testdict["a" : "b"]'], 'E719:', 2) @@ -2802,15 +2814,23 @@ enddef def Test_expr7_string_subscript() var lines =<< trim END var text = 'abcdef' - assert_equal('', text[-1]) + assert_equal('f', text[-1]) assert_equal('a', text[0]) assert_equal('e', text[4]) assert_equal('f', text[5]) assert_equal('', text[6]) + text = 'ábçdë' + assert_equal('ë', text[-1]) + assert_equal('d', text[-2]) + assert_equal('ç', text[-3]) + assert_equal('b', text[-4]) + assert_equal('á', text[-5]) + assert_equal('', text[-6]) + text = 'ábçdëf' assert_equal('', text[-999]) - assert_equal('', text[-1]) + assert_equal('f', text[-1]) assert_equal('á', text[0]) assert_equal('b', text[1]) assert_equal('ç', text[2]) @@ -2904,12 +2924,21 @@ def Test_expr7_list_subscript() assert_equal([], list[0 : -6]) assert_equal([], list[0 : -99]) END - CheckDefSuccess(lines) - CheckScriptSuccess(['vim9script'] + lines) + CheckDefAndScriptSuccess(lines) lines = ['var l = [0, 1, 2]', 'echo l[g:astring : g:theone]'] CheckDefExecFailure(lines, 'E1012:') CheckScriptFailure(['vim9script'] + lines, 'E1030:', 3) + + lines =<< trim END + vim9script + var ld = [] + def Func() + eval ld[0].key + enddef + defcompile + END + CheckScriptSuccess(lines) enddef def Test_expr7_dict_subscript() @@ -2918,6 +2947,15 @@ def Test_expr7_dict_subscript() var l = [{lnum: 2}, {lnum: 1}] var res = l[0].lnum > l[1].lnum assert_true(res) + + var dd = {} + def Func1() + eval dd.key1.key2 + enddef + def Func2() + eval dd['key1'].key2 + enddef + defcompile END CheckScriptSuccess(lines) enddef @@ -2926,25 +2964,25 @@ def Test_expr7_subscript_linebreak() var range = range( 3) var l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range # comment - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range # comment - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) assert_equal('1', l[ diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index e1d4853411..e87c33e909 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -116,6 +116,14 @@ def Test_missing_endfunc_enddef() CheckScriptFailure(lines, 'E126:', 2) enddef +def Test_enddef_dict_key() + var d = { + enddef: 'x', + endfunc: 'y', + } + assert_equal({enddef: 'x', endfunc: 'y'}, d) +enddef + def ReturnString(): string return 'string' enddef @@ -579,6 +587,22 @@ def Test_call_funcref_wrong_args() CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:') CheckScriptFailure(head + ["funcMap['func']('str', 123, [1], 4)"] + tail, 'E118:') + + var lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + echo Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) + + lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + call Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) enddef def Test_call_lambda_args() @@ -1492,7 +1516,7 @@ def Test_unknown_function() 'delfunc g:NotExist'], 'E700:') enddef -def RefFunc(Ref: func(string): string): string +def RefFunc(Ref: func(any): any): string return Ref('more') enddef @@ -1739,7 +1763,7 @@ enddef def Shadowed(): list var FuncList: list = [() => 42] - return FuncList->map((_, Shadowed) => Shadowed()) + return FuncList->mapnew((_, Shadowed) => Shadowed()) enddef def Test_lambda_arg_shadows_func() @@ -1768,7 +1792,7 @@ enddef def Line_continuation_in_lambda(): list var x = range(97, 100) - ->map((_, v) => nr2char(v) + ->mapnew((_, v) => nr2char(v) ->toupper()) ->reverse() return x @@ -1778,6 +1802,33 @@ def Test_line_continuation_in_lambda() Line_continuation_in_lambda()->assert_equal(['D', 'C', 'B', 'A']) enddef +def Test_list_lambda() + timer_start(1000, (_) => 0) + var body = execute(timer_info()[0].callback + ->string() + ->substitute("('", ' ', '') + ->substitute("')", '', '') + ->substitute('function\zs', ' ', '')) + assert_match('def \d\+(_: any, ...): number\n1 return 0\n enddef', body) +enddef + +def DoFilterThis(a: string): list + # closure nested inside another closure using argument + var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0) + return ['x', 'y', 'a', 'x2', 'c']->Filter() +enddef + +def Test_nested_closure_using_argument() + assert_equal(['x', 'x2'], DoFilterThis('x')) +enddef + +def Test_triple_nested_closure() + var what = 'x' + var Match = (val: string, cmp: string): bool => stridx(val, cmp) == 0 + var Filter = (l) => filter(l, (_, v) => Match(v, what)) + assert_equal(['x', 'x2'], ['x', 'y', 'a', 'x2', 'c']->Filter()) +enddef + func Test_silent_echo() CheckScreendump @@ -1857,7 +1908,7 @@ def Test_recursive_call() enddef def TreeWalk(dir: string): list - return readdir(dir)->map((_, val) => + return readdir(dir)->mapnew((_, val) => fnamemodify(dir .. '/' .. val, ':p')->isdirectory() ? {[val]: TreeWalk(dir .. '/' .. val)} : val diff --git a/src/typval.c b/src/typval.c index d16f0e6df8..7e4e63d17d 100644 --- a/src/typval.c +++ b/src/typval.c @@ -649,7 +649,7 @@ copy_tv(typval_T *from, typval_T *to) typval_compare( typval_T *typ1, // first operand typval_T *typ2, // second operand - exptype_T type, // operator + exprtype_T type, // operator int ic) // ignore case { int i; @@ -834,6 +834,30 @@ typval_compare( default: break; // avoid gcc warning } } + else if (in_vim9script() && (typ1->v_type == VAR_BOOL + || typ2->v_type == VAR_BOOL)) + { + if (typ1->v_type != typ2->v_type) + { + semsg(_(e_cannot_compare_str_with_str), + vartype_name(typ1->v_type), vartype_name(typ2->v_type)); + clear_tv(typ1); + return FAIL; + } + n1 = typ1->vval.v_number; + n2 = typ2->vval.v_number; + switch (type) + { + case EXPR_IS: + case EXPR_EQUAL: n1 = (n1 == n2); break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = (n1 != n2); break; + default: + emsg(_(e_invalid_operation_for_bool)); + clear_tv(typ1); + return FAIL; + } + } else { s1 = tv_get_string_buf(typ1, buf1); @@ -1555,7 +1579,7 @@ tv_get_lnum(typval_T *argvars) if (lnum <= 0) // no valid number, try using arg like line() { int fnum; - pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum); + pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; diff --git a/src/userfunc.c b/src/userfunc.c index 38930a2169..ded9ef7b0f 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -668,7 +668,7 @@ get_lambda_tv( goto errret; } else - fp->uf_ret_type = &t_unknown; + fp->uf_ret_type = &t_any; } fp->uf_lines = newlines; @@ -727,47 +727,68 @@ errret: * name it contains, otherwise return "name". * If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set * "partialp". + * If "type" is not NULL and a Vim9 script-local variable is found look up the + * type of the variable. */ char_u * -deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload) +deref_func_name( + char_u *name, + int *lenp, + partial_T **partialp, + type_T **type, + int no_autoload) { dictitem_T *v; int cc; - char_u *s; + char_u *s = NULL; + hashtab_T *ht; if (partialp != NULL) *partialp = NULL; cc = name[*lenp]; name[*lenp] = NUL; - v = find_var(name, NULL, no_autoload); + v = find_var(name, &ht, no_autoload); name[*lenp] = cc; - if (v != NULL && v->di_tv.v_type == VAR_FUNC) + if (v != NULL) { - if (v->di_tv.vval.v_string == NULL) + if (v->di_tv.v_type == VAR_FUNC) { - *lenp = 0; - return (char_u *)""; // just in case + if (v->di_tv.vval.v_string == NULL) + { + *lenp = 0; + return (char_u *)""; // just in case + } + s = v->di_tv.vval.v_string; + *lenp = (int)STRLEN(s); } - s = v->di_tv.vval.v_string; - *lenp = (int)STRLEN(s); - return s; - } - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) - { - partial_T *pt = v->di_tv.vval.v_partial; - - if (pt == NULL) + if (v->di_tv.v_type == VAR_PARTIAL) { - *lenp = 0; - return (char_u *)""; // just in case + partial_T *pt = v->di_tv.vval.v_partial; + + if (pt == NULL) + { + *lenp = 0; + return (char_u *)""; // just in case + } + if (partialp != NULL) + *partialp = pt; + s = partial_name(pt); + *lenp = (int)STRLEN(s); + } + + if (s != NULL) + { + if (type != NULL && ht == get_script_local_ht()) + { + svar_T *sv = find_typval_in_script(&v->di_tv); + + if (sv != NULL) + *type = sv->sv_type; + } + return s; } - if (partialp != NULL) - *partialp = pt; - s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; } return name; @@ -2387,6 +2408,14 @@ call_func( } } + if (error == FCERR_NONE && funcexe->check_type != NULL && funcexe->evaluate) + { + // Check that the argument types are OK for the types of the funcref. + if (check_argument_types(funcexe->check_type, argvars, argcount, + name) == FAIL) + error = FCERR_OTHER; + } + if (error == FCERR_NONE && funcexe->evaluate) { char_u *rfname = fname; @@ -2629,7 +2658,8 @@ trans_function_name( int skip, // only find the end, don't evaluate int flags, funcdict_T *fdp, // return: info about dictionary used - partial_T **partial) // return: partial of a FuncRef + partial_T **partial, // return: partial of a FuncRef + type_T **type) // return: type of funcref if not NULL { char_u *name = NULL; char_u *start; @@ -2733,7 +2763,7 @@ trans_function_name( if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, + name = deref_func_name(lv.ll_exp_name, &len, partial, type, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) name = NULL; @@ -2741,7 +2771,8 @@ trans_function_name( else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name(*pp, &len, partial, type, + flags & TFN_NO_AUTOLOAD); if (name == *pp) name = NULL; } @@ -3063,8 +3094,16 @@ define_function(exarg_T *eap, char_u *name_arg) } else { - name = trans_function_name(&p, &is_global, eap->skip, - TFN_NO_AUTOLOAD, &fudi, NULL); + if (STRNCMP(p, "", 8) == 0) + { + p += 8; + (void)getdigits(&p); + name = vim_strnsave(eap->arg, p - eap->arg); + CLEAR_FIELD(fudi); + } + else + name = trans_function_name(&p, &is_global, eap->skip, + TFN_NO_AUTOLOAD, &fudi, NULL, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { @@ -3413,8 +3452,10 @@ define_function(exarg_T *eap, char_u *name_arg) ; // Check for "endfunction" or "enddef". + // When a ":" follows it must be a dict key; "enddef: value," if (checkforcmd(&p, nesting_def[nesting] - ? "enddef" : "endfunction", 4)) + ? "enddef" : "endfunction", 4) + && *p != ':') { if (nesting-- == 0) { @@ -3453,7 +3494,7 @@ define_function(exarg_T *eap, char_u *name_arg) // not find it. else if (nesting_def[nesting]) { - if (checkforcmd(&p, "endfunction", 4)) + if (checkforcmd(&p, "endfunction", 4) && *p != ':') emsg(_(e_mismatched_endfunction)); } else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4)) @@ -3479,7 +3520,8 @@ define_function(exarg_T *eap, char_u *name_arg) if (*p == '!') p = skipwhite(p + 1); p += eval_fname_script(p); - vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, NULL)); + vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, + NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) @@ -3616,7 +3658,7 @@ define_function(exarg_T *eap, char_u *name_arg) { hashtab_T *ht; - v = find_var(name, &ht, FALSE); + v = find_var(name, &ht, TRUE); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), @@ -4007,7 +4049,7 @@ function_exists(char_u *name, int no_deref) flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) flag |= TFN_NO_DEREF; - p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL); + p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL, NULL); nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and @@ -4027,7 +4069,7 @@ get_expanded_name(char_u *name, int check) int is_global = FALSE; p = trans_function_name(&nm, &is_global, FALSE, - TFN_INT|TFN_QUIET, NULL, NULL); + TFN_INT|TFN_QUIET, NULL, NULL, NULL); if (p != NULL && *nm == NUL && (!check || translated_function_exists(p, is_global))) @@ -4097,7 +4139,8 @@ ex_delfunction(exarg_T *eap) int is_global = FALSE; p = eap->arg; - name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, NULL); + name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, + NULL, NULL); vim_free(fudi.fd_newkey); if (name == NULL) { @@ -4328,6 +4371,7 @@ ex_call(exarg_T *eap) funcdict_T fudi; partial_T *partial = NULL; evalarg_T evalarg; + type_T *type = NULL; fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) @@ -4343,8 +4387,8 @@ ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, NULL, eap->skip, - TFN_INT, &fudi, &partial); + tofree = trans_function_name(&arg, NULL, eap->skip, TFN_INT, + &fudi, &partial, in_vim9script() ? &type : NULL); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. @@ -4363,8 +4407,8 @@ ex_call(exarg_T *eap) // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, - partial != NULL ? NULL : &partial, FALSE); + name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, + in_vim9script() && type == NULL ? &type : NULL, FALSE); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. @@ -4416,6 +4460,7 @@ ex_call(exarg_T *eap) funcexe.evaluate = !eap->skip; funcexe.partial = partial; funcexe.selfdict = fudi.fd_dict; + funcexe.check_type = type; if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) { failed = TRUE; diff --git a/src/version.c b/src/version.c index 3b28f2b5c4..35ca2acc3c 100644 --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,50 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2327, +/**/ + 2326, +/**/ + 2325, +/**/ + 2324, +/**/ + 2323, +/**/ + 2322, +/**/ + 2321, +/**/ + 2320, +/**/ + 2319, +/**/ + 2318, +/**/ + 2317, +/**/ + 2316, +/**/ + 2315, +/**/ + 2314, +/**/ + 2313, +/**/ + 2312, +/**/ + 2311, +/**/ + 2310, +/**/ + 2309, +/**/ + 2308, +/**/ + 2307, +/**/ + 2306, /**/ 2305, /**/ diff --git a/src/vim9.h b/src/vim9.h index 49103ba556..b0c465deb2 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -33,7 +33,7 @@ typedef enum { ISN_LOADWDICT, // push w: dict ISN_LOADTDICT, // push t: dict ISN_LOADS, // push s: variable isn_arg.loadstore - ISN_LOADOUTER, // push variable from outer scope isn_arg.number + ISN_LOADOUTER, // push variable from outer scope isn_arg.outer ISN_LOADSCRIPT, // push script-local variable isn_arg.script. ISN_LOADOPT, // push option isn_arg.string ISN_LOADENV, // push environment variable isn_arg.string @@ -47,7 +47,7 @@ typedef enum { ISN_STOREW, // pop into window-local variable isn_arg.string ISN_STORET, // pop into tab-local variable isn_arg.string ISN_STORES, // pop into script variable isn_arg.loadstore - ISN_STOREOUTER, // pop variable into outer scope isn_arg.number + ISN_STOREOUTER, // pop variable into outer scope isn_arg.outer ISN_STORESCRIPT, // pop into script variable isn_arg.script ISN_STOREOPT, // pop into option isn_arg.string ISN_STOREENV, // pop into environment variable isn_arg.string @@ -84,6 +84,7 @@ typedef enum { ISN_PCALL, // call partial, use isn_arg.pfunc ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set ISN_RETURN, // return, result is on top of stack + ISN_RETURN_ZERO, // Push zero, then return ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref ISN_NEWFUNC, // create a global function from a lambda function ISN_DEF, // list functions @@ -104,12 +105,12 @@ typedef enum { ISN_ADDLIST, // add two lists ISN_ADDBLOB, // add two blobs - // operation with two arguments; isn_arg.op.op_type is exptype_T + // operation with two arguments; isn_arg.op.op_type is exprtype_T ISN_OPNR, ISN_OPFLOAT, ISN_OPANY, - // comparative operations; isn_arg.op.op_type is exptype_T, op_ic used + // comparative operations; isn_arg.op.op_type is exprtype_T, op_ic used ISN_COMPAREBOOL, ISN_COMPARESPECIAL, ISN_COMPARENR, @@ -216,7 +217,7 @@ typedef struct { // arguments to ISN_OPNR, ISN_OPFLOAT, etc. typedef struct { - exptype_T op_type; + exprtype_T op_type; int op_ic; // TRUE with '#', FALSE with '?', else MAYBE } opexpr_T; @@ -302,6 +303,12 @@ typedef struct { int unp_semicolon; // last item gets list of remainder } unpack_T; +// arguments to ISN_LOADOUTER and ISN_STOREOUTER +typedef struct { + int outer_idx; // index + int outer_depth; // nesting level, stack frames to go up +} isn_outer_T; + /* * Instruction */ @@ -341,6 +348,7 @@ struct isn_S { put_T put; cmod_T cmdmod; unpack_T unpack; + isn_outer_T outer; } isn_arg; }; @@ -367,10 +375,13 @@ struct dfunc_S { // Number of entries used by stack frame for a function call. // - ec_dfunc_idx: function index // - ec_iidx: instruction index -// - ec_outer_stack: stack used for closures TODO: can we avoid this? -// - ec_outer_frame: stack frame for closures +// - ec_outer: stack used for closures // - ec_frame_idx: previous frame index -#define STACK_FRAME_SIZE 5 +#define STACK_FRAME_FUNC_OFF 0 +#define STACK_FRAME_IIDX_OFF 1 +#define STACK_FRAME_OUTER_OFF 2 +#define STACK_FRAME_IDX_OFF 3 +#define STACK_FRAME_SIZE 4 #ifdef DEFINE_VIM9_GLOBALS diff --git a/src/vim9compile.c b/src/vim9compile.c index 9810f410ad..bcbc57dd12 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -108,7 +108,7 @@ typedef struct { char_u *lv_name; type_T *lv_type; int lv_idx; // index of the variable on the stack - int lv_from_outer; // when TRUE using ctx_outer scope + int lv_from_outer; // nesting level, using ctx_outer scope int lv_const; // when TRUE cannot be assigned to int lv_arg; // when TRUE this is an argument } lvar_T; @@ -149,7 +149,7 @@ static void delete_def_function_contents(dfunc_T *dfunc, int mark_deleted); /* * Lookup variable "name" in the local scope and return it in "lvar". - * "lvar->lv_from_outer" is set accordingly. + * "lvar->lv_from_outer" is incremented accordingly. * If "lvar" is NULL only check if the variable can be found. * Return FAIL if not found. */ @@ -172,7 +172,7 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) if (lvar != NULL) { *lvar = *lvp; - lvar->lv_from_outer = FALSE; + lvar->lv_from_outer = 0; } return OK; } @@ -186,7 +186,7 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) if (lvar != NULL) { cctx->ctx_outer_used = TRUE; - lvar->lv_from_outer = TRUE; + ++lvar->lv_from_outer; } return OK; } @@ -258,7 +258,7 @@ arg_exists( if (arg_exists(name, len, idxp, type, gen_load_outer, cctx->ctx_outer) == OK) { - *gen_load_outer = TRUE; + ++*gen_load_outer; return OK; } } @@ -669,7 +669,7 @@ generate_two_op(cctx_T *cctx, char_u *op) * Return ISN_DROP when failed. */ static isntype_T -get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) +get_compare_isn(exprtype_T exprtype, vartype_T type1, vartype_T type2) { isntype_T isntype = ISN_DROP; @@ -699,22 +699,22 @@ get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) && (type2 == VAR_NUMBER || type2 ==VAR_FLOAT))) isntype = ISN_COMPAREANY; - if ((exptype == EXPR_IS || exptype == EXPR_ISNOT) + if ((exprtype == EXPR_IS || exprtype == EXPR_ISNOT) && (isntype == ISN_COMPAREBOOL || isntype == ISN_COMPARESPECIAL || isntype == ISN_COMPARENR || isntype == ISN_COMPAREFLOAT)) { semsg(_(e_cannot_use_str_with_str), - exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); + exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); return ISN_DROP; } if (isntype == ISN_DROP - || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL && (type1 == VAR_BOOL || type1 == VAR_SPECIAL || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) - || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL - && exptype != EXPR_IS && exptype != EXPR_ISNOT + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL + && exprtype != EXPR_IS && exprtype != EXPR_ISNOT && (type1 == VAR_BLOB || type2 == VAR_BLOB || type1 == VAR_LIST || type2 == VAR_LIST)))) { @@ -726,7 +726,7 @@ get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) } int -check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2) +check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2) { if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP) return FAIL; @@ -737,7 +737,7 @@ check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2) * Generate an ISN_COMPARE* instruction with a boolean result. */ static int -generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) +generate_COMPARE(cctx_T *cctx, exprtype_T exprtype, int ic) { isntype_T isntype; isn_T *isn; @@ -752,13 +752,13 @@ generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) // checking. type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; - isntype = get_compare_isn(exptype, type1, type2); + isntype = get_compare_isn(exprtype, type1, type2); if (isntype == ISN_DROP) return FAIL; if ((isn = generate_instr(cctx, isntype)) == NULL) return FAIL; - isn->isn_arg.op.op_type = exptype; + isn->isn_arg.op.op_type = exprtype; isn->isn_arg.op.op_ic = ic; // takes two arguments, puts one bool back @@ -857,7 +857,9 @@ use_typecheck(type_T *actual, type_T *expected) || (actual->tt_type == VAR_FUNC && (expected->tt_type == VAR_FUNC || expected->tt_type == VAR_PARTIAL) - && (actual->tt_member == &t_any || actual->tt_argcount < 0))) + && (actual->tt_member == &t_any || actual->tt_argcount < 0) + && ((actual->tt_member == &t_void) + == (expected->tt_member == &t_void)))) return TRUE; if ((actual->tt_type == VAR_LIST || actual->tt_type == VAR_DICT) && actual->tt_type == expected->tt_type) @@ -1173,6 +1175,23 @@ generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) return OK; } +/* + * Generate an ISN_STOREOUTER instruction. + */ + static int +generate_STOREOUTER(cctx_T *cctx, int idx, int level) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, ISN_STOREOUTER, 1)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = level; + + return OK; +} + /* * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) */ @@ -1231,6 +1250,27 @@ generate_LOAD( return OK; } +/* + * Generate an ISN_LOADOUTER instruction + */ + static int +generate_LOADOUTER( + cctx_T *cctx, + int idx, + int nesting, + type_T *type) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_LOADOUTER, type)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = nesting; + + return OK; +} + /* * Generate an ISN_LOADV instruction for v:var. */ @@ -1437,6 +1477,11 @@ generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc) isn->isn_arg.funcref.fr_func = ufunc->uf_dfunc_idx; cctx->ctx_has_closure = 1; + // if the referenced function is a closure, it may use items further up in + // the nested context, including this one. + if (ufunc->uf_flags & FC_CLOSURE) + cctx->ctx_ufunc->uf_flags |= FC_CLOSURE; + if (ga_grow(stack, 1) == FAIL) return FAIL; ((type_T **)stack->ga_data)[stack->ga_len] = @@ -1547,6 +1592,7 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) garray_T *stack = &cctx->ctx_type_stack; int argoff; type_T **argtypes = NULL; + type_T *maptype = NULL; RETURN_OK_IF_SKIP(cctx); argoff = check_internal_func(func_idx, argcount); @@ -1567,6 +1613,8 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) argtypes = ((type_T **)stack->ga_data) + stack->ga_len - argcount; if (internal_func_check_arg_types(argtypes, func_idx, argcount) == FAIL) return FAIL; + if (internal_func_is_map(func_idx)) + maptype = *argtypes; } if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL) @@ -1582,6 +1630,11 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) internal_func_ret_type(func_idx, argcount, argtypes); ++stack->ga_len; + if (maptype != NULL && maptype->tt_member != NULL + && maptype->tt_member != &t_any) + // Check that map() didn't change the item types. + generate_TYPECHECK(cctx, maptype, -1); + return OK; } @@ -1790,9 +1843,9 @@ generate_PCALL( stack->ga_len + offset]; type_T *expected; - if (varargs && i >= type->tt_min_argcount - 1) + if (varargs && i >= type->tt_argcount - 1) expected = type->tt_args[ - type->tt_min_argcount - 1]->tt_member; + type->tt_argcount - 1]->tt_member; else expected = type->tt_args[i]; if (need_type(actual, expected, offset, @@ -1854,7 +1907,10 @@ generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len) } // change dict type to dict member type if (type->tt_type == VAR_DICT) - ((type_T **)stack->ga_data)[stack->ga_len - 1] = type->tt_member; + { + ((type_T **)stack->ga_data)[stack->ga_len - 1] = + type->tt_member == &t_unknown ? &t_any : type->tt_member; + } return OK; } @@ -2587,7 +2643,7 @@ compile_load( size_t len = end - *arg; int idx; int gen_load = FALSE; - int gen_load_outer = FALSE; + int gen_load_outer = 0; name = vim_strnsave(*arg, end - *arg); if (name == NULL) @@ -2595,7 +2651,7 @@ compile_load( if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx) == OK) { - if (!gen_load_outer) + if (gen_load_outer == 0) gen_load = TRUE; } else @@ -2606,8 +2662,8 @@ compile_load( { type = lvar.lv_type; idx = lvar.lv_idx; - if (lvar.lv_from_outer) - gen_load_outer = TRUE; + if (lvar.lv_from_outer != 0) + gen_load_outer = lvar.lv_from_outer; else gen_load = TRUE; } @@ -2629,9 +2685,9 @@ compile_load( } if (gen_load) res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); - if (gen_load_outer) + if (gen_load_outer > 0) { - res = generate_LOAD(cctx, ISN_LOADOUTER, idx, NULL, type); + res = generate_LOADOUTER(cctx, idx, gen_load_outer, type); cctx->ctx_outer_used = TRUE; } } @@ -3346,10 +3402,10 @@ get_vim_constant(char_u **arg, typval_T *rettv) } } - exptype_T + exprtype_T get_compare_type(char_u *p, int *len, int *type_is) { - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; int i; switch (p[0]) @@ -3748,7 +3804,12 @@ compile_subscript( return FAIL; } if ((*typep)->tt_type == VAR_DICT) + { *typep = (*typep)->tt_member; + if (*typep == &t_unknown) + // empty dict was used + *typep = &t_any; + } else { if (need_type(*typep, &t_dict_any, -2, cctx, @@ -3786,7 +3847,12 @@ compile_subscript( else { if ((*typep)->tt_type == VAR_LIST) + { *typep = (*typep)->tt_member; + if (*typep == &t_unknown) + // empty list was used + *typep = &t_any; + } if (generate_instr_drop(cctx, vtype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX, 1) == FAIL) @@ -4344,7 +4410,7 @@ compile_expr5(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) static int compile_expr4(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; char_u *p; char_u *next; int len = 2; @@ -4812,7 +4878,8 @@ compile_return(char_u *arg, int check_return_type, cctx_T *cctx) { stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; if (check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL - || cctx->ctx_ufunc->uf_ret_type == &t_unknown)) + || cctx->ctx_ufunc->uf_ret_type == &t_unknown + || cctx->ctx_ufunc->uf_ret_type == &t_any)) { cctx->ctx_ufunc->uf_ret_type = stack_type; } @@ -5117,9 +5184,9 @@ generate_loadvar( generate_LOADV(cctx, name + 2, TRUE); break; case dest_local: - if (lvar->lv_from_outer) - generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, - NULL, type); + if (lvar->lv_from_outer > 0) + generate_LOADOUTER(cctx, lvar->lv_idx, lvar->lv_from_outer, + type); else generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); break; @@ -6175,7 +6242,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) // optimization: turn "var = 123" from ISN_PUSHNR + // ISN_STORE into ISN_STORENR - if (!lhs.lhs_lvar->lv_from_outer + if (lhs.lhs_lvar->lv_from_outer == 0 && instr->ga_len == instr_count + 1 && isn->isn_type == ISN_PUSHNR) { @@ -6187,9 +6254,9 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) if (stack->ga_len > 0) --stack->ga_len; } - else if (lhs.lhs_lvar->lv_from_outer) - generate_STORE(cctx, ISN_STOREOUTER, - lhs.lhs_lvar->lv_idx, NULL); + else if (lhs.lhs_lvar->lv_from_outer > 0) + generate_STOREOUTER(cctx, lhs.lhs_lvar->lv_idx, + lhs.lhs_lvar->lv_from_outer); else generate_STORE(cctx, ISN_STORE, lhs.lhs_lvar->lv_idx, NULL); } @@ -6213,6 +6280,77 @@ theend: return ret; } +/* + * Check for an assignment at "eap->cmd", compile it if found. + * Return NOTDONE if there is none, FAIL for failure, OK if done. + */ + static int +may_compile_assignment(exarg_T *eap, char_u **line, cctx_T *cctx) +{ + char_u *pskip; + char_u *p; + + // Assuming the command starts with a variable or function name, + // find what follows. + // Skip over "var.member", "var[idx]" and the like. + // Also "&opt = val", "$ENV = val" and "@r = val". + pskip = (*eap->cmd == '&' || *eap->cmd == '$' || *eap->cmd == '@') + ? eap->cmd + 1 : eap->cmd; + p = to_name_end(pskip, TRUE); + if (p > eap->cmd && *p != NUL) + { + char_u *var_end; + int oplen; + int heredoc; + + if (eap->cmd[0] == '@') + var_end = eap->cmd + 2; + else + var_end = find_name_end(pskip, NULL, NULL, + FNE_CHECK_START | FNE_INCL_BR); + oplen = assignment_len(skipwhite(var_end), &heredoc); + if (oplen > 0) + { + size_t len = p - eap->cmd; + + // Recognize an assignment if we recognize the variable + // name: + // "g:var = expr" + // "local = expr" where "local" is a local var. + // "script = expr" where "script" is a script-local var. + // "import = expr" where "import" is an imported var + // "&opt = expr" + // "$ENV = expr" + // "@r = expr" + if (*eap->cmd == '&' + || *eap->cmd == '$' + || *eap->cmd == '@' + || ((len) > 2 && eap->cmd[1] == ':') + || lookup_local(eap->cmd, len, NULL, cctx) == OK + || arg_exists(eap->cmd, len, NULL, NULL, NULL, cctx) == OK + || script_var_exists(eap->cmd, len, FALSE, cctx) == OK + || find_imported(eap->cmd, len, cctx) != NULL) + { + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL || *line == eap->cmd) + return FAIL; + return OK; + } + } + } + + if (*eap->cmd == '[') + { + // [var, var] = expr + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL) + return FAIL; + if (*line != eap->cmd) + return OK; + } + return NOTDONE; +} + /* * Check if "name" can be "unlet". */ @@ -7835,68 +7973,14 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx) if (!starts_with_colon) { - char_u *pskip; + int assign; - // Assuming the command starts with a variable or function name, - // find what follows. - // Skip over "var.member", "var[idx]" and the like. - // Also "&opt = val", "$ENV = val" and "@r = val". - pskip = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@') - ? ea.cmd + 1 : ea.cmd; - p = to_name_end(pskip, TRUE); - if (p > ea.cmd && *p != NUL) - { - char_u *var_end; - int oplen; - int heredoc; - - if (ea.cmd[0] == '@') - var_end = ea.cmd + 2; - else - var_end = find_name_end(pskip, NULL, NULL, - FNE_CHECK_START | FNE_INCL_BR); - oplen = assignment_len(skipwhite(var_end), &heredoc); - if (oplen > 0) - { - size_t len = p - ea.cmd; - - // Recognize an assignment if we recognize the variable - // name: - // "g:var = expr" - // "local = expr" where "local" is a local var. - // "script = expr" where "script" is a script-local var. - // "import = expr" where "import" is an imported var - // "&opt = expr" - // "$ENV = expr" - // "@r = expr" - if (*ea.cmd == '&' - || *ea.cmd == '$' - || *ea.cmd == '@' - || ((len) > 2 && ea.cmd[1] == ':') - || lookup_local(ea.cmd, len, NULL, &cctx) == OK - || arg_exists(ea.cmd, len, NULL, NULL, - NULL, &cctx) == OK - || script_var_exists(ea.cmd, len, - FALSE, &cctx) == OK - || find_imported(ea.cmd, len, &cctx) != NULL) - { - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL || line == ea.cmd) - goto erret; - goto nextline; - } - } - } - - if (*ea.cmd == '[') - { - // [var, var] = expr - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL) - goto erret; - if (line != ea.cmd) - goto nextline; - } + // Check for assignment after command modifiers. + assign = may_compile_assignment(&ea, &line, &cctx); + if (assign == OK) + goto nextline; + if (assign == FAIL) + goto erret; } /* @@ -8170,8 +8254,7 @@ nextline: } // Return zero if there is no return at the end. - generate_PUSHNR(&cctx, 0); - generate_instr(&cctx, ISN_RETURN); + generate_instr(&cctx, ISN_RETURN_ZERO); } { @@ -8463,6 +8546,7 @@ delete_instr(isn_T *isn) case ISN_PUSHSPEC: case ISN_PUT: case ISN_RETURN: + case ISN_RETURN_ZERO: case ISN_SHUFFLE: case ISN_SLICE: case ISN_STORE: diff --git a/src/vim9execute.c b/src/vim9execute.c index b6297d7eba..378b10443a 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -58,8 +58,7 @@ struct ectx_S { garray_T ec_stack; // stack of typval_T values int ec_frame_idx; // index in ec_stack: context of ec_dfunc_idx - garray_T *ec_outer_stack; // stack used for closures - int ec_outer_frame; // stack frame in ec_outer_stack + outer_T *ec_outer; // outer scope used for closures, allocated garray_T ec_trystack; // stack of trycmd_T values int ec_in_catch; // when TRUE in catch or finally block @@ -149,6 +148,9 @@ exe_newlist(int count, ectx_T *ectx) /* * Call compiled function "cdf_idx" from compiled code. + * This adds a stack frame and sets the instruction pointer to the start of the + * called function. + * If "pt" is not null use "pt->pt_outer" for ec_outer. * * Stack has: * - current arguments (already there) @@ -160,7 +162,7 @@ exe_newlist(int count, ectx_T *ectx) * - reserved space for local variables */ static int -call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) +call_dfunc(int cdf_idx, partial_T *pt, int argcount_arg, ectx_T *ectx) { int argcount = argcount_arg; dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + cdf_idx; @@ -243,11 +245,10 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) ectx->ec_stack.ga_len += arg_to_add; // Store current execution state in stack frame for ISN_RETURN. - STACK_TV_BOT(0)->vval.v_number = ectx->ec_dfunc_idx; - STACK_TV_BOT(1)->vval.v_number = ectx->ec_iidx; - STACK_TV_BOT(2)->vval.v_string = (void *)ectx->ec_outer_stack; - STACK_TV_BOT(3)->vval.v_number = ectx->ec_outer_frame; - STACK_TV_BOT(4)->vval.v_number = ectx->ec_frame_idx; + STACK_TV_BOT(STACK_FRAME_FUNC_OFF)->vval.v_number = ectx->ec_dfunc_idx; + STACK_TV_BOT(STACK_FRAME_IIDX_OFF)->vval.v_number = ectx->ec_iidx; + STACK_TV_BOT(STACK_FRAME_OUTER_OFF)->vval.v_string = (void *)ectx->ec_outer; + STACK_TV_BOT(STACK_FRAME_IDX_OFF)->vval.v_number = ectx->ec_frame_idx; ectx->ec_frame_idx = ectx->ec_stack.ga_len; // Initialize local variables @@ -262,11 +263,32 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) } ectx->ec_stack.ga_len += STACK_FRAME_SIZE + varcount; - if (ufunc->uf_partial != NULL) + if (pt != NULL || ufunc->uf_partial != NULL || ufunc->uf_flags & FC_CLOSURE) { - ectx->ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; - ectx->ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; + outer_T *outer = ALLOC_CLEAR_ONE(outer_T); + + if (outer == NULL) + return FAIL; + if (pt != NULL) + { + *outer = pt->pt_outer; + outer->out_up_is_copy = TRUE; + } + else if (ufunc->uf_partial != NULL) + { + *outer = ufunc->uf_partial->pt_outer; + outer->out_up_is_copy = TRUE; + } + else + { + outer->out_stack = &ectx->ec_stack; + outer->out_frame_idx = ectx->ec_frame_idx; + outer->out_up = ectx->ec_outer; + } + ectx->ec_outer = outer; } + else + ectx->ec_outer = NULL; // Set execution state to the start of the called function. ectx->ec_dfunc_idx = cdf_idx; @@ -415,8 +437,9 @@ handle_closure_in_use(ectx_T *ectx, int free_arguments) { ++funcstack->fs_refcount; pt->pt_funcstack = funcstack; - pt->pt_ectx_stack = &funcstack->fs_ga; - pt->pt_ectx_frame = ectx->ec_frame_idx - top; + pt->pt_outer.out_stack = &funcstack->fs_ga; + pt->pt_outer.out_frame_idx = ectx->ec_frame_idx - top; + pt->pt_outer.out_up = ectx->ec_outer; } } } @@ -502,17 +525,21 @@ func_return(ectx_T *ectx) // The return value should be on top of the stack. However, when aborting // it may not be there and ec_frame_idx is the top of the stack. ret_idx = ectx->ec_stack.ga_len - 1; - if (ret_idx == ectx->ec_frame_idx + 4) + if (ret_idx == ectx->ec_frame_idx + STACK_FRAME_IDX_OFF) ret_idx = 0; + vim_free(ectx->ec_outer); + // Restore the previous frame. - ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame_idx)->vval.v_number; - ectx->ec_iidx = STACK_TV(ectx->ec_frame_idx + 1)->vval.v_number; - ectx->ec_outer_stack = - (void *)STACK_TV(ectx->ec_frame_idx + 2)->vval.v_string; - ectx->ec_outer_frame = STACK_TV(ectx->ec_frame_idx + 3)->vval.v_number; + ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_FUNC_OFF)->vval.v_number; + ectx->ec_iidx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_IIDX_OFF)->vval.v_number; + ectx->ec_outer = (void *)STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_OUTER_OFF)->vval.v_string; // restoring ec_frame_idx must be last - ectx->ec_frame_idx = STACK_TV(ectx->ec_frame_idx + 4)->vval.v_number; + ectx->ec_frame_idx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_IDX_OFF)->vval.v_number; dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ectx->ec_instr = dfunc->df_instr; @@ -598,10 +625,19 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx) /* * Execute a user defined function. + * If the function is compiled this will add a stack frame and set the + * instruction pointer at the start of the function. + * Otherwise the function is called here. + * If "pt" is not null use "pt->pt_outer" for ec_outer. * "iptr" can be used to replace the instruction with a more efficient one. */ static int -call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx, isn_T *iptr) +call_ufunc( + ufunc_T *ufunc, + partial_T *pt, + int argcount, + ectx_T *ectx, + isn_T *iptr) { typval_T argvars[MAX_FUNC_ARGS]; funcexe_T funcexe; @@ -634,7 +670,7 @@ call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx, isn_T *iptr) iptr->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; iptr->isn_arg.dfunc.cdf_argcount = argcount; } - return call_dfunc(ufunc->uf_dfunc_idx, argcount, ectx); + return call_dfunc(ufunc->uf_dfunc_idx, pt, argcount, ectx); } if (call_prepare(argcount, argvars, ectx) == FAIL) @@ -707,7 +743,7 @@ call_by_name(char_u *name, int argcount, ectx_T *ectx, isn_T *iptr) } if (ufunc != NULL) - return call_ufunc(ufunc, argcount, ectx, iptr); + return call_ufunc(ufunc, NULL, argcount, ectx, iptr); return FAIL; } @@ -742,15 +778,8 @@ call_partial(typval_T *tv, int argcount_arg, ectx_T *ectx) } if (pt->pt_func != NULL) - { - int ret = call_ufunc(pt->pt_func, argcount, ectx, NULL); + return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL); - // closure may need the function context where it was defined - ectx->ec_outer_stack = pt->pt_ectx_stack; - ectx->ec_outer_frame = pt->pt_ectx_frame; - - return ret; - } name = pt->pt_name; } else if (tv->v_type == VAR_FUNC) @@ -863,6 +892,108 @@ allocate_if_null(typval_T *tv) } } +/* + * Return the character "str[index]" where "index" is the character index. If + * "index" is out of range NULL is returned. + */ + char_u * +char_from_string(char_u *str, varnumber_T index) +{ + size_t nbyte = 0; + varnumber_T nchar = index; + size_t slen; + + if (str == NULL) + return NULL; + slen = STRLEN(str); + + // do the same as for a list: a negative index counts from the end + if (index < 0) + { + int clen = 0; + + for (nbyte = 0; nbyte < slen; ++clen) + nbyte += MB_CPTR2LEN(str + nbyte); + nchar = clen + index; + if (nchar < 0) + // unlike list: index out of range results in empty string + return NULL; + } + + for (nbyte = 0; nchar > 0 && nbyte < slen; --nchar) + nbyte += MB_CPTR2LEN(str + nbyte); + if (nbyte >= slen) + return NULL; + return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte)); +} + +/* + * Get the byte index for character index "idx" in string "str" with length + * "str_len". + * If going over the end return "str_len". + * If "idx" is negative count from the end, -1 is the last character. + * When going over the start return -1. + */ + static long +char_idx2byte(char_u *str, size_t str_len, varnumber_T idx) +{ + varnumber_T nchar = idx; + size_t nbyte = 0; + + if (nchar >= 0) + { + while (nchar > 0 && nbyte < str_len) + { + nbyte += MB_CPTR2LEN(str + nbyte); + --nchar; + } + } + else + { + nbyte = str_len; + while (nchar < 0 && nbyte > 0) + { + --nbyte; + nbyte -= mb_head_off(str, str + nbyte); + ++nchar; + } + if (nchar < 0) + return -1; + } + return (long)nbyte; +} + +/* + * Return the slice "str[first:last]" using character indexes. + * Return NULL when the result is empty. + */ + char_u * +string_slice(char_u *str, varnumber_T first, varnumber_T last) +{ + long start_byte, end_byte; + size_t slen; + + if (str == NULL) + return NULL; + slen = STRLEN(str); + start_byte = char_idx2byte(str, slen, first); + if (start_byte < 0) + start_byte = 0; // first index very negative: use zero + if (last == -1) + end_byte = (long)slen; + else + { + end_byte = char_idx2byte(str, slen, last); + if (end_byte >= 0 && end_byte < (long)slen) + // end index is inclusive + end_byte += MB_CPTR2LEN(str + end_byte); + } + + if (start_byte >= (long)slen || end_byte <= start_byte) + return NULL; + return vim_strnsave(str + start_byte, end_byte - start_byte); +} + static svar_T * get_script_svar(scriptref_T *sref, ectx_T *ectx) { @@ -937,8 +1068,10 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx) // The closure needs to find arguments and local // variables in the current stack. - pt->pt_ectx_stack = &ectx->ec_stack; - pt->pt_ectx_frame = ectx->ec_frame_idx; + pt->pt_outer.out_stack = &ectx->ec_stack; + pt->pt_outer.out_frame_idx = ectx->ec_frame_idx; + pt->pt_outer.out_up = ectx->ec_outer; + pt->pt_outer.out_up_is_copy = TRUE; // If this function returns and the closure is still // being used, we need to make a copy of the context @@ -1005,9 +1138,6 @@ call_def_function( // Get pointer to a local variable on the stack. Negative for arguments. #define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx) -// Like STACK_TV_VAR but use the outer scope -#define STACK_OUT_TV_VAR(idx) (((typval_T *)ectx.ec_outer_stack->ga_data) + ectx.ec_outer_frame + STACK_FRAME_SIZE + idx) - if (ufunc->uf_def_status == UF_NOT_COMPILED || (ufunc->uf_def_status == UF_TO_BE_COMPILED && compile_def_function(ufunc, FALSE, NULL) == FAIL)) @@ -1111,24 +1241,24 @@ call_def_function( ectx.ec_frame_idx = ectx.ec_stack.ga_len; initial_frame_idx = ectx.ec_frame_idx; - if (partial != NULL) + if (partial != NULL || ufunc->uf_partial != NULL) { - if (partial->pt_ectx_stack == NULL && current_ectx != NULL) + ectx.ec_outer = ALLOC_CLEAR_ONE(outer_T); + if (ectx.ec_outer == NULL) + goto failed_early; + if (partial != NULL) { - // TODO: is this always the right way? - ectx.ec_outer_stack = ¤t_ectx->ec_stack; - ectx.ec_outer_frame = current_ectx->ec_frame_idx; + if (partial->pt_outer.out_stack == NULL && current_ectx != NULL) + { + if (current_ectx->ec_outer != NULL) + *ectx.ec_outer = *current_ectx->ec_outer; + } + else + *ectx.ec_outer = partial->pt_outer; } else - { - ectx.ec_outer_stack = partial->pt_ectx_stack; - ectx.ec_outer_frame = partial->pt_ectx_frame; - } - } - else if (ufunc->uf_partial != NULL) - { - ectx.ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; - ectx.ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; + *ectx.ec_outer = ufunc->uf_partial->pt_outer; + ectx.ec_outer->out_up_is_copy = TRUE; } // dummy frame entries @@ -1410,15 +1540,6 @@ call_def_function( ++ectx.ec_stack.ga_len; break; - // load variable or argument from outer scope - case ISN_LOADOUTER: - if (GA_GROW(&ectx.ec_stack, 1) == FAIL) - goto failed; - copy_tv(STACK_OUT_TV_VAR(iptr->isn_arg.number), - STACK_TV_BOT(0)); - ++ectx.ec_stack.ga_len; - break; - // load v: variable case ISN_LOADV: if (GA_GROW(&ectx.ec_stack, 1) == FAIL) @@ -1614,14 +1735,6 @@ call_def_function( *tv = *STACK_TV_BOT(0); break; - // store variable or argument in outer scope - case ISN_STOREOUTER: - --ectx.ec_stack.ga_len; - tv = STACK_OUT_TV_VAR(iptr->isn_arg.number); - clear_tv(tv); - *tv = *STACK_TV_BOT(0); - break; - // store s: variable in old script case ISN_STORES: { @@ -1902,6 +2015,43 @@ call_def_function( } break; + // load or store variable or argument from outer scope + case ISN_LOADOUTER: + case ISN_STOREOUTER: + { + int depth = iptr->isn_arg.outer.outer_depth; + outer_T *outer = ectx.ec_outer; + + while (depth > 1 && outer != NULL) + { + outer = outer->out_up; + --depth; + } + if (outer == NULL) + { + SOURCING_LNUM = iptr->isn_lnum; + iemsg("LOADOUTER depth more than scope levels"); + goto failed; + } + tv = ((typval_T *)outer->out_stack->ga_data) + + outer->out_frame_idx + STACK_FRAME_SIZE + + iptr->isn_arg.outer.outer_idx; + if (iptr->isn_type == ISN_LOADOUTER) + { + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + else + { + --ectx.ec_stack.ga_len; + clear_tv(tv); + *tv = *STACK_TV_BOT(0); + } + } + break; + // unlet item in list or dict variable case ISN_UNLETINDEX: { @@ -2140,7 +2290,7 @@ call_def_function( // call a :def function case ISN_DCALL: SOURCING_LNUM = iptr->isn_lnum; - if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx, + if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx, NULL, iptr->isn_arg.dfunc.cdf_argcount, &ectx) == FAIL) goto on_error; @@ -2205,6 +2355,16 @@ call_def_function( break; // return from a :def function call + case ISN_RETURN_ZERO: + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + ++ectx.ec_stack.ga_len; + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + tv->v_lock = 0; + // FALLTHROUGH + case ISN_RETURN: { garray_T *trystack = &ectx.ec_trystack; @@ -2613,11 +2773,11 @@ call_def_function( { typval_T *tv1 = STACK_TV_BOT(-2); typval_T *tv2 = STACK_TV_BOT(-1); - exptype_T exptype = iptr->isn_arg.op.op_type; + exprtype_T exprtype = iptr->isn_arg.op.op_type; int ic = iptr->isn_arg.op.op_ic; SOURCING_LNUM = iptr->isn_lnum; - typval_compare(tv1, tv2, exptype, ic); + typval_compare(tv1, tv2, exprtype, ic); clear_tv(tv2); --ectx.ec_stack.ga_len; } @@ -3148,11 +3308,13 @@ call_def_function( goto failed; ++ectx.ec_stack.ga_len; tv = STACK_TV_BOT(-1); + ea.line2 = 0; ea.addr_count = 0; ea.addr_type = ADDR_LINES; ea.cmd = iptr->isn_arg.string; + ea.skip = FALSE; if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL) - goto failed; + goto on_error; if (ea.addr_count == 0) tv->vval.v_number = curwin->w_cursor.lnum; else @@ -3387,6 +3549,15 @@ failed_early: vim_free(ectx.ec_stack.ga_data); vim_free(ectx.ec_trystack.ga_data); + while (ectx.ec_outer != NULL) + { + outer_T *up = ectx.ec_outer->out_up_is_copy + ? NULL : ectx.ec_outer->out_up; + + vim_free(ectx.ec_outer); + ectx.ec_outer = up; + } + // Not sure if this is necessary. suppress_errthrow = save_suppress_errthrow; @@ -3423,7 +3594,7 @@ ex_disassemble(exarg_T *eap) } else fname = trans_function_name(&arg, &is_global, FALSE, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL); + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL); if (fname == NULL) { semsg(_(e_invarg2), eap->arg); @@ -3508,17 +3679,27 @@ ex_disassemble(exarg_T *eap) (varnumber_T)(iptr->isn_arg.number)); break; case ISN_LOAD: - case ISN_LOADOUTER: { - char *add = iptr->isn_type == ISN_LOAD ? "" : "OUTER"; - if (iptr->isn_arg.number < 0) - smsg("%4d LOAD%s arg[%lld]", current, add, + smsg("%4d LOAD arg[%lld]", current, (varnumber_T)(iptr->isn_arg.number + STACK_FRAME_SIZE)); else - smsg("%4d LOAD%s $%lld", current, add, - (varnumber_T)(iptr->isn_arg.number)); + smsg("%4d LOAD $%lld", current, + (varnumber_T)(iptr->isn_arg.number)); + } + break; + case ISN_LOADOUTER: + { + if (iptr->isn_arg.number < 0) + smsg("%4d LOADOUTER level %d arg[%d]", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx + + STACK_FRAME_SIZE); + else + smsg("%4d LOADOUTER level %d $%d", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx); } break; case ISN_LOADV: @@ -3585,16 +3766,22 @@ ex_disassemble(exarg_T *eap) break; case ISN_STORE: + if (iptr->isn_arg.number < 0) + smsg("%4d STORE arg[%lld]", current, + iptr->isn_arg.number + STACK_FRAME_SIZE); + else + smsg("%4d STORE $%lld", current, iptr->isn_arg.number); + break; case ISN_STOREOUTER: { - char *add = iptr->isn_type == ISN_STORE ? "" : "OUTER"; - if (iptr->isn_arg.number < 0) - smsg("%4d STORE%s arg[%lld]", current, add, - (varnumber_T)(iptr->isn_arg.number + STACK_FRAME_SIZE)); + smsg("%4d STOREOUTEr level %d arg[%d]", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx + STACK_FRAME_SIZE); else - smsg("%4d STORE%s $%lld", current, add, - (varnumber_T)(iptr->isn_arg.number)); + smsg("%4d STOREOUTER level %d $%d", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx); } break; case ISN_STOREV: @@ -3802,6 +3989,9 @@ ex_disassemble(exarg_T *eap) case ISN_RETURN: smsg("%4d RETURN", current); break; + case ISN_RETURN_ZERO: + smsg("%4d RETURN 0", current); + break; case ISN_FUNCREF: { funcref_T *funcref = &iptr->isn_arg.funcref; diff --git a/src/vim9type.c b/src/vim9type.c index 2344874a33..c65f747483 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -527,6 +527,46 @@ check_arg_type(type_T *expected, type_T *actual, int argidx) return check_type(expected, actual, TRUE, argidx); } +/* + * Check that the arguments of "type" match "argvars[argcount]". + * Return OK/FAIL. + */ + int +check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name) +{ + int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0; + int i; + + if (type->tt_type != VAR_FUNC && type->tt_type != VAR_PARTIAL) + return OK; // just in case + if (argcount < type->tt_min_argcount - varargs) + { + semsg(_(e_toofewarg), name); + return FAIL; + } + if (!varargs && type->tt_argcount >= 0 && argcount > type->tt_argcount) + { + semsg(_(e_toomanyarg), name); + return FAIL; + } + if (type->tt_args == NULL) + return OK; // cannot check + + + for (i = 0; i < argcount; ++i) + { + type_T *expected; + + if (varargs && i >= type->tt_argcount - 1) + expected = type->tt_args[type->tt_argcount - 1]->tt_member; + else + expected = type->tt_args[i]; + if (check_typval_type(expected, &argvars[i], i + 1) == FAIL) + return FAIL; + } + return OK; +} + /* * Skip over a type definition and return a pointer to just after it. * When "optional" is TRUE then a leading "?" is accepted.