Commit: patch 9.2.0972: extend() family does not accept a blob
Christian Brabandt <[email protected]>
| Newsgroups | gmane.editors.vim.devel |
|---|---|
| Message-ID | <[email protected]> |
patch 9.2.0972: extend() family does not accept a blob Commit: https://github.com/vim/vim/commit/bad0490bfb340bd4c59399903323c02c4bfbc5e3 Author: Bakudankun <[email protected]> Date: Tue Aug 18 20:22:38 2026 +0000 patch 9.2.0972: extend() family does not accept a blob Problem: extend() family does not accept a blob type Solution: Implement missing blob functionality (Bakudankun). closes: #20958 Co-authored-by: Hirohito Higashi <[email protected]> Signed-off-by: Bakudankun <[email protected]> Signed-off-by: Christian Brabandt <[email protected]> diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 11934bcfc..737cac1fe 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -187,12 +187,12 @@ expand({expr} [, {nosuf} [, {list}]]) expandcmd({string} [, {options}]) String expand {string} like with `:edit` extend({expr1}, {expr2} [, {expr3}]) - List/Dict + List/Dict/Blob insert items of {expr2} into {expr1} extendnew({expr1}, {expr2} [, {expr3}]) - List/Dict - like |extend()| but creates a new List - or Dictionary + List/Dict/Blob + like |extend()| but creates a new + List, Dictionary or Blob feedkeys({string} [, {mode}]) none add key sequence to typeahead buffer filecopy({from}, {to}) Number |TRUE| if copying file {from} to {to} worked @@ -633,8 +633,8 @@ simplify({filename}) String simplify filename as much as possible sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} slice({expr}, {start} [, {end}]) - String/List/Blob - slice of a String, List or Blob + String/List/Blob/Tuple + slice of a String, List, Blob or Tuple sort({list} [, {how} [, {dict}]]) List sort {list}, compare with {how} sound_clear() none stop playing all sounds @@ -3130,24 +3130,27 @@ expandcmd({string} [, {options}]) *expandcmd()* extend({expr1}, {expr2} [, {expr3}]) *extend()* - {expr1} and {expr2} must be both |Lists| or both - |Dictionaries|. + {expr1} and {expr2} must be both |Lists|, |Dictionaries| or + |Blobs|. - If they are |Lists|: Append {expr2} to {expr1}. + If they are |Lists| or |Blobs|: Append {expr2} to {expr1}. If {expr3} is given insert the items of {expr2} before the - item with index {expr3} in {expr1}. When {expr3} is zero + item with index/byte {expr3} in {expr1}. When {expr3} is zero insert before the first item. When {expr3} is equal to len({expr1}) then {expr2} is appended. Examples: > :echo sort(extend(mylist, [7, 5])) :call extend(mylist, [2, 3], 1) -< When {expr1} is the same List as {expr2} then the number of - items copied is equal to the original length of the List. +< When {expr1} is the same List or Blob as {expr2} then the + number of items copied is equal to the original length of the + List or the Blob. E.g., when {expr3} is 1 you get N new copies of the first item (where N is the original length of the List). - Use |add()| to concatenate one item to a list. To concatenate - two lists into a new list use the + operator: > + Use |add()| to concatenate one item to a list or a blob. + To concatenate two lists or blobs into a new one use the + + operator: > :let newlist = [1, 2, 3] + [4, 5] + :let newblob = 0z1122 + 0z3344 < If they are |Dictionaries|: Add all entries from {expr2} to {expr1}. @@ -3160,7 +3163,7 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* {expr1} is changed when {expr2} is not empty. If necessary make a copy of {expr1} first or use |extendnew()| to return a - new List/Dictionary. + new List/Dictionary/Blob. {expr2} remains unchanged. When {expr1} is locked and {expr2} is not empty the operation fails. @@ -3169,17 +3172,17 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* Can also be used as a |method|: > mylist->extend(otherlist) < - Return type: list<{type}> or dict<{type}> depending on {expr1} - and {expr2}, in case of error: |Number| + Return type: list<{type}>, dict<{type}> or blob depending on + {expr1} and {expr2}, in case of error: |Number| extendnew({expr1}, {expr2} [, {expr3}]) *extendnew()* Like |extend()| but instead of adding items to {expr1} a new - List or Dictionary is created and returned. {expr1} remains - unchanged. + List, Dictionary or Blob is created and returned. {expr1} + remains unchanged. - Return type: list<{type}> or dict<{type}> depending on {expr1} - and {expr2}, in case of error: |Number| + Return type: list<{type}>, dict<{type}> or blob depending on + {expr1} and {expr2}, in case of error: |Number| feedkeys({string} [, {mode}]) *feedkeys()* @@ -10731,7 +10734,7 @@ slice({expr}, {start} [, {end}]) *slice()* Can also be used as a |method|: > GetList()->slice(offset) < - Return type: list<{type}> or tuple<{type}> + Return type: |String|, list<{type}>, blob or tuple<{type}> sort({list} [, {how} [, {dict}]]) *sort()* *E702* diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cedb60d28..c8f3aa6c2 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 9.2. Last change: 2026 Aug 17 +*eval.txt* For Vim version 9.2. Last change: 2026 Aug 18 VIM REFERENCE MANUAL by Bram Moolenaar @@ -993,8 +993,10 @@ Two blobs can be concatenated with the "+" operator: > :let longblob = myblob + 0z4455 :let longblob = 0z4455 + myblob < -A blob can be concatenated with another one in-place using |:let+=|: > +A blob can be concatenated with another one in-place using |:let+=| or +|extend()|: > :let myblob += 0z6677 + :call extend(myblob, 0z6677) < See |blob-modification| below for more about changing a blob in-place. @@ -1039,9 +1041,11 @@ To change part of a blob you can specify the first and last byte to be modified. The value must have the same number of bytes in the range: > :let blob[3:5] = 0z334455 -To add items to a Blob in-place, you can use |:let+=| (|blob-concatenation|): > +To add items to a Blob in-place, you can use |:let+=| or |extend()| +(|blob-concatenation|): > :let blobA = 0z1122 :let blobA += 0z3344 + :call extend(blobA, 0z5566) < When two variables refer to the same Blob, changing one Blob in-place will cause the referenced Blob to be changed in-place: > diff --git a/runtime/doc/tags b/runtime/doc/tags index a8ff635fe..ca5828a1e 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -4790,6 +4790,7 @@ E1576 tagsrch.txt /*E1576* E1577 options.txt /*E1577* E1578 spell.txt /*E1578* E158 sign.txt /*E158* +E1581 vim9.txt /*E1581* E159 sign.txt /*E159* E16 cmdline.txt /*E16* E160 sign.txt /*E160* diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index cb816a0cb..c1ecf81b2 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1,4 +1,4 @@ -*usr_41.txt* For Vim version 9.2. Last change: 2026 Jun 09 +*usr_41.txt* For Vim version 9.2. Last change: 2026 Aug 18 VIM USER MANUAL by Bram Moolenaar @@ -921,11 +921,28 @@ Floating point computation: *float-functions* isnan() check for not a number Blob manipulation: *blob-functions* + get() get a byte without error for wrong index + len() number of bytes in a Blob + empty() check if Blob is empty + insert() insert a byte somewhere in a Blob + add() append a byte to a Blob + extend() append a Blob to a Blob + extendnew() make a new Blob and append bytes + remove() remove one or more bytes from a Blob + copy() make a copy of a Blob + filter() remove selected bytes from a Blob + map() change each byte of a Blob + mapnew() make a new Blob with changed bytes + foreach() apply function to Blob bytes + reduce() reduce a Blob to a value + slice() take a slice of a Blob blob2list() get a list of numbers from a blob list2blob() get a blob from a list of numbers reverse() reverse the order of numbers in a blob + string() String representation of a Blob index() index of a value in a Blob indexof() index in a Blob where an expression is true + repeat() repeat a Blob multiple times items() get List of Blob index-value pairs Other computation: *bitwise-function* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index f38fe1693..e2675c5f2 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.2. Last change: 2026 Aug 16 +*version9.txt* For Vim version 9.2. Last change: 2026 Aug 18 VIM REFERENCE MANUAL by Bram Moolenaar @@ -52700,6 +52700,9 @@ Changed ~ - |serverlist()| can return a list of all available server names. - Improve visual appearance when the terminal does not support colors. - Improve diff highlighting for terminals with 256 or less colors. +- |listener_add()| accepts a Dictionary of options and can include the + resulting text of a change |listener-text|. +- |extend()| and |extendnew()| also accept |Blobs|. *added-9.3* diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt index e69b62acd..082b9ba85 100644 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1,4 +1,4 @@ -*vim9.txt* For Vim version 9.2. Last change: 2026 Aug 16 +*vim9.txt* For Vim version 9.2. Last change: 2026 Aug 18 VIM REFERENCE MANUAL by Bram Moolenaar @@ -495,7 +495,7 @@ And with autocommands: > Although using a :def function probably works better. *E1022* *E1103* *E1130* *E1131* *E1133* - *E1134* + *E1134* *E1581* Declaring a variable with a type but without an initializer will initialize to false (for bool), empty (for string, list, dict, etc.) or zero (for number, any, etc.). This matters especially when using the "any" type, the value will diff --git a/src/blob.c b/src/blob.c index ad2b02bbb..3f7417409 100644 --- a/src/blob.c +++ b/src/blob.c @@ -57,34 +57,37 @@ rettv_blob_set(typval_T *rettv, blob_T *b) ++b->bv_refcount; } - int -blob_copy(blob_T *from, typval_T *to) +/* + * Create a copy of a blob. + * The refcount of the new blob is set to 1. + * Returns NULL when "orig" is NULL or out of memory. + */ + blob_T * +blob_copy(blob_T *orig) { int len; + blob_T *copy; - to->v_type = VAR_BLOB; - to->v_lock = 0; - if (from == NULL) - { - to->vval.v_blob = NULL; - return OK; - } + if (orig == NULL) + return NULL; - if (rettv_blob_alloc(to) == FAIL) - return FAIL; + copy = blob_alloc(); + if (copy == NULL) + return NULL; + ++copy->bv_refcount; - len = from->bv_ga.ga_len; + len = orig->bv_ga.ga_len; if (len > 0) { - to->vval.v_blob->bv_ga.ga_data = - vim_memsave(from->bv_ga.ga_data, len); - if (to->vval.v_blob->bv_ga.ga_data == NULL) + copy->bv_ga.ga_data = + vim_memsave(orig->bv_ga.ga_data, len); + if (copy->bv_ga.ga_data == NULL) len = 0; } - to->vval.v_blob->bv_ga.ga_len = len; - to->vval.v_blob->bv_ga.ga_maxlen = len; + copy->bv_ga.ga_len = len; + copy->bv_ga.ga_maxlen = len; - return OK; + return copy; } void @@ -666,7 +669,9 @@ blob_filter_map( b_ret = b; if (filtermap == FILTERMAP_MAPNEW) { - if (blob_copy(b, rettv) == FAIL) + rettv->v_lock = 0; + rettv->vval.v_blob = blob_copy(b); + if (rettv->vval.v_blob == NULL) return; b_ret = rettv->vval.v_blob; } @@ -774,6 +779,121 @@ blob_insert_func(typval_T *argvars, typval_T *rettv) copy_tv(&argvars[0], rettv); } +/* + * Extend "b1" with "b2". "b1" must not be NULL. + * If "bef" is equal to the length of b1 append at the end, + * otherwise insert before this index. + * Caller must check that "bef" is valid. + * Returns FAIL when out of memory. + */ + static int +blob_extend(blob_T *b1, blob_T *b2, long bef) +{ + int len1, len2; + char_u *p1; + + // NULL blob is equivalent to an empty blob: nothing to do. + if (b2 == NULL || b2->bv_ga.ga_len == 0) + return OK; + + len1 = b1->bv_ga.ga_len; + len2 = b2->bv_ga.ga_len; + + if (ga_grow(&b1->bv_ga, len2) == FAIL) + return FAIL; + + p1 = (char_u *)b1->bv_ga.ga_data; + + if (b1 == b2) + { + // Inserting a blob into itself + mch_memmove(p1 + bef, p1, (size_t)len1); + if (bef < len1) + memcpy(p1 + bef + len1, p1 + bef * 2, (size_t)(len1 - bef)); + } + else + { + if (bef < len1) + mch_memmove(p1 + bef + len2, p1 + bef, (size_t)(len1 - bef)); + + memcpy(p1 + bef, b2->bv_ga.ga_data, (size_t)len2); + } + + b1->bv_ga.ga_len += len2; + + return OK; +} + +/* + * extend() a Blob. Append Blob argvars[1] to Blob argvars[0] before index + * argvars[3] and return the resulting blob in "rettv". "is_new" is TRUE for + * extendnew(). + */ + void +blob_extend_func( + typval_T *argvars, + char_u *arg_errmsg, + int is_new, + typval_T *rettv) +{ + blob_T *b1, *b2; + long before; + int error = FALSE; + + b1 = argvars[0].vval.v_blob; + if (b1 == NULL) + { + emsg(_(e_cannot_extend_null_blob)); + return; + } + if (is_new || !value_check_lock(b1->bv_lock, arg_errmsg, TRUE)) + { + if (is_new) + { + b1 = blob_copy(b1); + if (b1 == NULL) + return; + } + + b2 = argvars[1].vval.v_blob; + if (b2 == NULL) + goto theend; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + goto cleanup; // type error; errmsg already given + if (before < 0) + before = b1->bv_ga.ga_len + before; + if (before < 0 || before > b1->bv_ga.ga_len) + { + semsg(_(e_blob_index_out_of_range_nr), before); + goto cleanup; + } + } + else + before = b1->bv_ga.ga_len; + if (blob_extend(b1, b2, before) == FAIL) + goto cleanup; + +theend: + if (is_new) + { + rettv->v_type = VAR_BLOB; + rettv->v_lock = 0; + rettv->vval.v_blob = b1; + } + else + copy_tv(&argvars[0], rettv); + return; + +cleanup: + if (is_new) + blob_unref(b1); + } +} + /* * Implementation of reduce() for Blob "argvars[0]" using the function "expr" * starting with the optional initial value "argvars[2]" and return the result diff --git a/src/errors.h b/src/errors.h index 350ffe704..b0e56608d 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3822,3 +3822,7 @@ EXTERN char e_completeopt_escape_cannot_be_used_with_nargs_underscore[] EXTERN char e_too_many_text_properties_on_a_single_line[] INIT(= N_("E1580: Too many text properties on a single line")); #endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_extend_null_blob[] + INIT(= N_("E1581: Cannot extend a null blob")); +#endif diff --git a/src/eval.c b/src/eval.c index 759c4acb4..bb6f452b0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2890,12 +2890,9 @@ eval_for_line( fi->fi_bi = 0; if (tv.vval.v_blob != NULL) { - typval_T btv; - // Make a copy, so that the iteration still works when the // blob is changed. - blob_copy(tv.vval.v_blob, &btv); - fi->fi_blob = btv.vval.v_blob; + fi->fi_blob = blob_copy(tv.vval.v_blob); } clear_tv(&tv); } @@ -7886,7 +7883,16 @@ item_copy( ret = FAIL; break; case VAR_BLOB: - ret = blob_copy(from->vval.v_blob, to); + to->v_type = VAR_BLOB; + to->v_lock = 0; + if (from->vval.v_blob == NULL) + to->vval.v_blob = NULL; + else + { + to->vval.v_blob = blob_copy(from->vval.v_blob); + if (to->vval.v_blob == NULL) + ret = FAIL; + } break; case VAR_DICT: to->v_type = VAR_DICT; diff --git a/src/evalfunc.c b/src/evalfunc.c index bf8f00f7a..c2c2a6ab3 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -627,12 +627,16 @@ arg_string_or_blob( } /* - * Check "type" is a list of 'any' or a dict of 'any'. + * Check "type" is a list of 'any', a tuple of 'any' or dict of 'any'. */ static int -arg_list_or_dict(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) +arg_list_or_tuple_or_dict( + type_T *type, + type_T *decl_type UNUSED, + argcontext_T *context) { if (type->tt_type == VAR_LIST + || type->tt_type == VAR_TUPLE || type->tt_type == VAR_DICT || type_any_or_unknown(type)) return OK; @@ -641,31 +645,17 @@ arg_list_or_dict(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) } /* - * Check "type" is a list of 'any' or a dict of 'any'. And modifiable. + * Check "type" is a list of 'any', a dict of 'any' or a blob. */ static int -arg_list_or_dict_mod( +arg_list_or_dict_or_blob( type_T *type, - type_T *decl_type, + type_T *decl_type UNUSED, argcontext_T *context) -{ - if (arg_list_or_dict(type, decl_type, context) == FAIL) - return FAIL; - return arg_type_modifiable(type, context->arg_idx + 1); -} - -/* - * Check "type" is a list of 'any', a tuple of 'any' or dict of 'any'. - */ - static int -arg_list_or_tuple_or_dict( - type_T *type, - type_T *decl_type UNUSED, - argcontext_T *context) { if (type->tt_type == VAR_LIST - || type->tt_type == VAR_TUPLE || type->tt_type == VAR_DICT + || type->tt_type == VAR_BLOB || type_any_or_unknown(type)) return OK; arg_type_mismatch(&t_list_any, type, context->arg_idx + 1); @@ -679,16 +669,12 @@ arg_list_or_tuple_or_dict( static int arg_list_or_dict_or_blob_mod( type_T *type, - type_T *decl_type UNUSED, + type_T *decl_type, argcontext_T *context) { - if (type->tt_type == VAR_LIST - || type->tt_type == VAR_DICT - || type->tt_type == VAR_BLOB - || type_any_or_unknown(type)) - return arg_type_modifiable(type, context->arg_idx + 1); - arg_type_mismatch(&t_list_any, type, context->arg_idx + 1); - return FAIL; + if (arg_list_or_dict_or_blob(type, decl_type, context) == FAIL) + return FAIL; + return arg_type_modifiable(type, context->arg_idx + 1); } /* @@ -1107,7 +1093,7 @@ arg_extend3(type_T *type, type_T *decl_type, argcontext_T *context) { type_T *first_type = context->arg_types[context->arg_idx - 2].type_curr; - if (first_type->tt_type == VAR_LIST) + if (first_type->tt_type == VAR_LIST || first_type->tt_type == VAR_BLOB) return arg_number(type, decl_type, context); if (first_type->tt_type == VAR_DICT) return arg_string(type, decl_type, context); @@ -1359,8 +1345,8 @@ static argcheck_T arg13_cursor[] = {arg_cursor1, arg_number, arg_number}; static argcheck_T arg12_deepcopy[] = {arg_any, arg_bool}; static argcheck_T arg12_execute[] = {arg_string_or_list_string, arg_string}; static argcheck_T arg12_getchar[] = {arg_bool_or_nr, arg_dict_any}; -static argcheck_T arg23_extend[] = {arg_list_or_dict_mod, arg_same_as_prev, arg_extend3}; -static argcheck_T arg23_extendnew[] = {arg_list_or_dict, arg_same_struct_as_prev, arg_extend3}; +static argcheck_T arg23_extend[] = {arg_list_or_dict_or_blob_mod, arg_same_as_prev, arg_extend3}; +static argcheck_T arg23_extendnew[] = {arg_list_or_dict_or_blob, arg_same_struct_as_prev, arg_extend3}; static argcheck_T arg23_get[] = {arg_get1, arg_string_or_nr, arg_any}; static argcheck_T arg14_glob[] = {arg_string, arg_bool, arg_bool, arg_bool}; static argcheck_T arg25_globpath[] = {arg_string, arg_string, arg_bool, arg_bool, arg_bool}; @@ -1665,6 +1651,8 @@ ret_extend(int argcount, return &t_list_any; if (argtypes[0].type_curr->tt_type == VAR_DICT) return &t_dict_any; + if (argtypes[0].type_curr->tt_type == VAR_BLOB) + return &t_blob; } return argtypes[0].type_curr; } diff --git a/src/list.c b/src/list.c index 0ea2e13bd..24efe6bcf 100644 --- a/src/list.c +++ b/src/list.c @@ -3176,13 +3176,16 @@ extend(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg, int is_new) type = argvars[0].vval.v_dict->dv_type; dict_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv); } + else if (argvars[0].v_type == VAR_BLOB && argvars[1].v_type == VAR_BLOB) + blob_extend_func(argvars, arg_errmsg, is_new, rettv); else - semsg(_(e_argument_of_str_must_be_list_or_dictionary), func_name); + semsg(_(e_argument_of_str_must_be_list_dictionary_or_blob), func_name); } /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function + * "extend(blob, blob [, idx])" function */ void f_extend(typval_T *argvars, typval_T *rettv) @@ -3195,6 +3198,7 @@ f_extend(typval_T *argvars, typval_T *rettv) /* * "extendnew(list, list [, idx])" function * "extendnew(dict, dict [, action])" function + * "extendnew(blob, blob [, idx])" function */ void f_extendnew(typval_T *argvars, typval_T *rettv) diff --git a/src/po/vim.pot b/src/po/vim.pot index 8f7f2e070..fbf6c473f 100644 --- a/src/po/vim.pot +++ b/src/po/vim.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Vim " "Report-Msgid-Bugs-To: [email protected] " -"POT-Creation-Date: 2026-07-29 19:37+0000 " +"POT-Creation-Date: 2026-08-18 20:22+0000 " "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE " "Last-Translator: FULL NAME <EMAIL@ADDRESS> " "Language-Team: LANGUAGE <[email protected]> " @@ -8902,6 +8902,9 @@ msgstr "" msgid "E1580: Too many text properties on a single line" msgstr "" +msgid "E1581: Cannot extend a null blob" +msgstr "" + #. type of cmdline window or 0 #. result of cmdline window or 0 #. buffer of cmdline window or NULL diff --git a/src/proto/blob.pro b/src/proto/blob.pro index f24c268ae..cd32dcba7 100644 --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -2,7 +2,7 @@ blob_T *blob_alloc(void); int rettv_blob_alloc(typval_T *rettv); void rettv_blob_set(typval_T *rettv, blob_T *b); -int blob_copy(blob_T *from, typval_T *to); +blob_T *blob_copy(blob_T *orig); void blob_free(blob_T *b); void blob_unref(blob_T *b); long blob_len(blob_T *b); @@ -23,6 +23,7 @@ void blob_add(typval_T *argvars, typval_T *rettv); void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); void blob_filter_map(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr, char_u *arg_errmsg, typval_T *rettv); void blob_insert_func(typval_T *argvars, typval_T *rettv); +void blob_extend_func(typval_T *argvars, char_u *arg_errmsg, int is_new, typval_T *rettv); void blob_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv); void blob_reverse(blob_T *b, typval_T *rettv); void f_blob2list(typval_T *argvars, typval_T *rettv); diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim index 88c2e07bd..9a8f807ec 100644 --- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -1193,7 +1193,7 @@ func Test_listdict_compare_complex() call v9.CheckLegacyAndVim9Success(lines) endfunc -" Test for extending lists and dictionaries +" Test for extending lists, dictionaries and blobs func Test_listdict_extend() " Test extend() with lists @@ -1273,8 +1273,59 @@ func Test_listdict_extend() call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E475:') call assert_equal({'a': 'A', 'b': 'B'}, d) - call assert_fails("call extend([1, 2], 1)", 'E712:') - call assert_fails("call extend([1, 2], {})", 'E712:') + " Test extend() with blobs + + " Pass the same Blob to extend() + let lines =<< trim END + VAR b = 0z010203 + call assert_equal(0z010203010203, extend(b, b)) + call assert_equal(0z010203010203, b) + + LET b = 0z010203 + call assert_equal(0z010203040506, extend(b, 0z040506)) + call assert_equal(0z010203040506, b) + + LET b = 0z010203 + call extend(b, 0z040506, 0) + call assert_equal(0z040506010203, b) + + LET b = 0z010203 + call extend(b, 0z040506, 1) + call assert_equal(0z010405060203, b) + + LET b = 0z010203 + call extend(b, 0z040506, 3) + call assert_equal(0z010203040506, b) + + LET b = 0z010203 + call extend(b, 0z040506, -1) + call assert_equal(0z010204050603, b) + + LET b = 0z010203 + call extend(b, 0z040506, -3) + call assert_equal(0z040506010203, b) + + LET b = 0z010203 + call assert_equal(0z010203, b->extend(0z)) + call assert_equal(0z010203, b->extend(test_null_blob())) + call assert_equal(0z010203, b) + END + call v9.CheckLegacyAndVim9Success(lines) + + let b = 0z010203 + call assert_fails("call extend(b, 0z040506, 4)", 'E979:') + call assert_fails("call extend(b, 0z040506, -4)", 'E979:') + call assert_fails("call extend(b, 0z040506, 1.2)", 'E805:') + + lockvar b + call assert_fails("call extend(b, 0z040506)", 'E741:') + unlockvar b + + call assert_fails('call extend(test_null_blob(), test_null_blob())', 'E1581:') + call assert_fails('call extendnew(test_null_blob(), 0z01)', 'E1581:') + + call assert_fails("call extend([1, 2], 1)", 'E896:') + call assert_fails("call extend([1, 2], {})", 'E896:') " Extend g: dictionary with an invalid variable name call assert_fails("call extend(g:, {'-!' : 10})", 'E461:') @@ -1295,6 +1346,23 @@ func Test_listdict_extend() call assert_equal([1, 5, 7, 1, 5, 7], l) END call v9.CheckLegacyAndVim9Success(lines) + + " Extend a blob with itself. + let lines =<< trim END + VAR b = 0z010507 + call extend(b, b, 0) + call assert_equal(0z010507010507, b) + LET b = 0z010507 + call extend(b, b, 1) + call assert_equal(0z010105070507, b) + LET b = 0z010507 + call extend(b, b, 2) + call assert_equal(0z010501050707, b) + LET b = 0z010507 + call extend(b, b, 3) + call assert_equal(0z010507010507, b) + END + call v9.CheckLegacyAndVim9Success(lines) endfunc func Test_listdict_extendnew() @@ -1321,6 +1389,18 @@ func Test_listdict_extendnew() let d2['c'] = 'C' call assert_equal({'a': {'b': 'B'}, 'c': 'C'}, d2) call assert_equal({'a': {'b': 'B'}}, d) + + " Test extendnew() with blobs + let b = 0z010203 + call assert_equal(0z0102030405, extendnew(b, 0z0405)) + call assert_equal(0z010203, b) + lockvar b + call assert_equal(0z0102030405, extendnew(b, 0z0405)) + let b2 = extendnew(b, test_null_blob()) + call assert_equal(0z010203, b2) + let b2 += 0z04 + call assert_equal(0z01020304, b2) + call assert_equal(0z010203, b) endfunc func s:check_scope_dict(x, fixed) @@ -1606,6 +1686,7 @@ func Test_extendnew_leak() " This used to leak memory for i in range(100) | silent! call extendnew([], [], []) | endfor for i in range(100) | silent! call extendnew({}, {}, {}) | endfor + for i in range(100) | silent! call extendnew(0z, 0z, 0z) | endfor endfunc " Test for comparing deeply nested List/Dict values diff --git a/src/testdir/test_tuple.vim b/src/testdir/test_tuple.vim index 00428a5b0..de61fb09d 100644 --- a/src/testdir/test_tuple.vim +++ b/src/testdir/test_tuple.vim @@ -1755,9 +1755,9 @@ func Test_tuple_extend() call extendnew(t, (4, 5)) END call v9.CheckSourceLegacyAndVim9Failure(lines, [ - \ 'E712: Argument of extend() must be a List or Dictionary', + \ 'E896: Argument of extend() must be a List, Dictionary or Blob', \ 'E1013: Argument 1: type mismatch, expected list<any> but got tuple<number, number, number>', - \ 'E712: Argument of extend() must be a List or Dictionary']) + \ 'E896: Argument of extend() must be a List, Dictionary or Blob']) endfunc " Test for filter() with a tuple diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 49ab862d1..a32fe559b 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -1162,6 +1162,11 @@ def Test_extend_arg_types() assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, 'keep')) assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, g:string_keep)) + assert_equal(0z010203, extend(0z0102, 0z03)) + assert_equal(0z030102, extend(0z0102, 0z03, 0)) + assert_equal(0z010302, extend(0z0102, 0z03, 1)) + assert_equal(0z010302, extend(0z0102, 0z03, g:number_one)) + # mix of types is OK without a declaration var res: list<dict<any>> @@ -1182,14 +1187,17 @@ def Test_extend_arg_types() END v9.CheckSourceDefAndScriptSuccess(lines) - v9.CheckSourceDefAndScriptFailure(['extend("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E712: Argument of extend() must be a List or Dictionary']) - v9.CheckSourceDefAndScriptFailure(['extend([1, 2], 3)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E712: Argument of extend() must be a List or Dictionary']) + v9.CheckSourceDefAndScriptFailure(['extend("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E896: Argument of extend() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extend([1, 2], 3)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E896: Argument of extend() must be a List, Dictionary or Blob']) v9.CheckSourceDefAndScriptFailure(['var ll = [1, 2]', 'extend(ll, ["x"])'], ['E1013: Argument 2: type mismatch, expected list<number> but got list<string>', 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>']) v9.CheckSourceDefFailure(['extend([1, 2], [3], "x")'], 'E1013: Argument 3: type mismatch, expected number but got string') v9.CheckSourceDefFailure(['extend({a: 1}, 42)'], 'E1013: Argument 2: type mismatch, expected dict<any> but got number') v9.CheckSourceDefFailure(['extend({a: 1}, {b: 2}, 1)'], 'E1013: Argument 3: type mismatch, expected string but got number') + v9.CheckSourceDefFailure(['extend(0z01, 42)'], 'E1013: Argument 2: type mismatch, expected blob but got number') + v9.CheckSourceDefFailure(['extend(0z01, 0z02, 1.1)'], 'E1013: Argument 3: type mismatch, expected number but got float') + v9.CheckSourceScriptFailure(['vim9script', 'var l = [1]', 'extend(l, ["b", 1])'], 'E1013: Argument 2: type mismatch, expected list<number> but got list<any> in extend()') enddef @@ -1305,6 +1313,19 @@ def Test_extend_const() END v9.CheckSourceDefSuccess(lines) + lines =<< trim END + const b = 0z0102 + extend(b, 0z03) + END + v9.CheckSourceDefFailure(lines, 'E1307: Argument 1: Trying to modify a const blob') + + lines =<< trim END + final b = 0z0102 + extend(b, 0z03) + assert_equal(0z010203, b) + END + v9.CheckSourceDefSuccess(lines) + # item in a for loop is final lines =<< trim END var l: list<dict<any>> = [{n: 1}] @@ -1318,11 +1339,15 @@ enddef def Test_extendnew() assert_equal([1, 2, 'a'], extendnew([1, 2], ['a'])) assert_equal({one: 1, two: 'a'}, extendnew({one: 1}, {two: 'a'})) - - v9.CheckSourceDefAndScriptFailure(['extendnew({a: 1}, 42)'], ['E1013: Argument 2: type mismatch, expected dict<number> but got number', 'E712: Argument of extendnew() must be a List or Dictionary']) - v9.CheckSourceDefAndScriptFailure(['extendnew({a: 1}, [42])'], ['E1013: Argument 2: type mismatch, expected dict<number> but got list<number>', 'E712: Argument of extendnew() must be a List or Dictionary']) - v9.CheckSourceDefAndScriptFailure(['extendnew([1, 2], "x")'], ['E1013: Argument 2: type mismatch, expected list<number> but got string', 'E712: Argument of extendnew() must be a List or Dictionary']) - v9.CheckSourceDefAndScriptFailure(['extendnew([1, 2], {x: 1})'], ['E1013: Argument 2: type mismatch, expected list<number> but got dict<number>', 'E712: Argument of extendnew() must be a List or Dictionary']) + assert_equal(0z010203, extendnew(0z0102, 0z03)) + + v9.CheckSourceDefAndScriptFailure(['extendnew({a: 1}, 42)'], ['E1013: Argument 2: type mismatch, expected dict<number> but got number', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew({a: 1}, [42])'], ['E1013: Argument 2: type mismatch, expected dict<number> but got list<number>', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew([1, 2], "x")'], ['E1013: Argument 2: type mismatch, expected list<number> but got string', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew([1, 2], {x: 1})'], ['E1013: Argument 2: type mismatch, expected list<number> but got dict<number>', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew(0z0102, "x")'], ['E1013: Argument 2: type mismatch, expected blob but got string', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew(0z0102, [42])'], ['E1013: Argument 2: type mismatch, expected blob but got list<number>', 'E896: Argument of extendnew() must be a List, Dictionary or Blob']) + v9.CheckSourceDefAndScriptFailure(['extendnew(0z0102, 0z03, 1.1)'], ['E1013: Argument 3: type mismatch, expected number but got float', 'E805: Using a Float as a Number']) enddef def Test_feedkeys() diff --git a/src/version.c b/src/version.c index c03f5e545..68353e0ba 100644 --- a/src/version.c +++ b/src/version.c @@ -763,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 972, /**/ 971, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index c8790d634..b72e5700b 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2996,7 +2996,8 @@ execute_for(isn_T *iptr, ectx_T *ectx) // changed. if (idxtv->vval.v_number == -1 && blob != NULL) { - blob_copy(blob, ltv); + ltv->v_lock = 0; + ltv->vval.v_blob = blob_copy(blob); blob_unref(blob); blob = ltv->vval.v_blob; } @@ -4740,7 +4741,8 @@ exec_instructions(ectx_T *ectx) tv->vval.v_float = iptr->isn_arg.fnumber; break; case ISN_PUSHBLOB: - blob_copy(iptr->isn_arg.blob, tv); + tv->v_type = VAR_BLOB; + tv->vval.v_blob = blob_copy(iptr->isn_arg.blob); break; case ISN_PUSHFUNC: tv->v_type = VAR_FUNC; diff --git a/src/vim9instr.c b/src/vim9instr.c index 069818b35..a3e9243ca 100644 --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -1006,7 +1006,8 @@ generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) isn_T *isn; RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) + if ((isn = generate_instr_type2(cctx, ISN_PUSHBLOB, + &t_blob, &t_blob)) == NULL) return FAIL; isn->isn_arg.blob = blob; -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wwQRh-00AhrE-HV%40256bit.org.