commit 9c3ba551288a9f05d489dfee1077bab7261644be
parent 992543c361630c73dd30f16916bd30fe63eb8033
Author: zeertzjq <zeertzjq@outlook.com>
Date: Mon, 16 Feb 2026 21:39:23 +0800
Merge pull request #37902 from zeertzjq/vim-9.1.1668
vim-patch:partial:9.1.1668,9.1.1954
Diffstat:
9 files changed, 166 insertions(+), 54 deletions(-)
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
@@ -745,6 +745,7 @@ Blob manipulation: *blob-functions*
reverse() reverse the order of numbers in a blob
index() index of a value in a Blob
indexof() index in a Blob where an expression is true
+ items() get List of Blob index-value pairs
Other computation: *bitwise-function*
and() bitwise AND
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -5415,7 +5415,7 @@ items({expr}) *items()*
Return a |List| with all key/index and value pairs of {expr}.
Each |List| item is a list with two items:
- for a |Dict|: the key and the value
- - for a |List| or |String|: the index and the value
+ - for a |List|, |Blob| or |String|: the index and the value
The returned |List| is in arbitrary order for a |Dict|,
otherwise it's in ascending order of the index.
@@ -5428,6 +5428,7 @@ items({expr}) *items()*
endfor
echo items([1, 2, 3])
echo items("foobar")
+ echo items(0z0102)
<
Parameters: ~
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -4901,7 +4901,7 @@ function vim.fn.isnan(expr) end
--- Return a |List| with all key/index and value pairs of {expr}.
--- Each |List| item is a list with two items:
--- - for a |Dict|: the key and the value
---- - for a |List| or |String|: the index and the value
+--- - for a |List|, |Blob| or |String|: the index and the value
--- The returned |List| is in arbitrary order for a |Dict|,
--- otherwise it's in ascending order of the index.
---
@@ -4914,6 +4914,7 @@ function vim.fn.isnan(expr) end
--- endfor
--- echo items([1, 2, 3])
--- echo items("foobar")
+--- echo items(0z0102)
--- <
---
--- @param expr table|string
diff --git a/src/nvim/errors.h b/src/nvim/errors.h
@@ -197,6 +197,7 @@ EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line numbe
EXTERN const char e_reduce_of_an_empty_str_with_no_initial_value[] INIT(= N_("E998: Reduce of an empty %s with no initial value"));
+EXTERN const char e_invalid_value_for_blob_nr[] INIT(= N_("E1239: Invalid value for blob: 0x" PRIX64));
EXTERN const char e_stray_closing_curly_str[]
INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
EXTERN const char e_missing_close_curly_str[]
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -1315,9 +1315,13 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, bool copy, const bool
}
} else {
bool error = false;
- const char val = (char)tv_get_number_chk(rettv, &error);
+ const varnumber_T val = tv_get_number_chk(rettv, &error);
if (!error) {
- tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val);
+ if (val < 0 || val > 255) {
+ semsg(_(e_invalid_value_for_blob_nr), val);
+ } else {
+ tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val);
+ }
}
}
} else if (op != NULL && *op != '=') {
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -6044,7 +6044,7 @@ M.funcs = {
Return a |List| with all key/index and value pairs of {expr}.
Each |List| item is a list with two items:
- for a |Dict|: the key and the value
- - for a |List| or |String|: the index and the value
+ - for a |List|, |Blob| or |String|: the index and the value
The returned |List| is in arbitrary order for a |Dict|,
otherwise it's in ascending order of the index.
@@ -6057,6 +6057,7 @@ M.funcs = {
endfor
echo items([1, 2, 3])
echo items("foobar")
+ echo items(0z0102)
<
]=],
name = 'items',
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
@@ -93,14 +93,12 @@ static const char e_string_or_number_required_for_argument_nr[]
= N_("E1220: String or Number required for argument %d");
static const char e_string_or_list_required_for_argument_nr[]
= N_("E1222: String or List required for argument %d");
-static const char e_string_list_or_dict_required_for_argument_nr[]
- = N_("E1225: String, List or Dictionary required for argument %d");
+static const char e_list_dict_blob_or_string_required_for_argument_nr[]
+ = N_("E1225: List, Dictionary, Blob or String required for argument %d");
static const char e_list_or_blob_required_for_argument_nr[]
= N_("E1226: List or Blob required for argument %d");
static const char e_blob_required_for_argument_nr[]
= N_("E1238: Blob required for argument %d");
-static const char e_invalid_value_for_blob_nr[]
- = N_("E1239: Invalid value for blob: %d");
static const char e_string_list_or_blob_required_for_argument_nr[]
= N_("E1252: String, List or Blob required for argument %d");
static const char e_string_or_function_required_for_argument_nr[]
@@ -793,6 +791,30 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t
}
}
+/// "items(blob)" function
+/// Converts a Blob into a List of [index, byte] pairs.
+/// Caller must have already checked that argvars[0] is a Blob.
+/// A null blob behaves like an empty blob.
+static void tv_blob2items(typval_T *argvars, typval_T *rettv)
+{
+ blob_T *blob = argvars[0].vval.v_blob;
+
+ tv_list_alloc_ret(rettv, tv_blob_len(blob));
+
+ for (int i = 0; i < tv_blob_len(blob); i++) {
+ list_T *l2 = tv_list_alloc(2);
+ tv_list_append_list(rettv->vval.v_list, l2);
+ tv_list_append_number(l2, i);
+ tv_list_append_number(l2, tv_blob_get(blob, i));
+ }
+}
+
+/// "items(dict)" function
+static void tv_dict2items(typval_T *argvars, typval_T *rettv)
+{
+ tv_dict2list(argvars, rettv, kDict2ListItems);
+}
+
/// "items(list)" function
/// Caller must have already checked that argvars[0] is a List.
static void tv_list2items(typval_T *argvars, typval_T *rettv)
@@ -3226,9 +3248,7 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
/// @param[in] what What to save in rettv.
static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what)
{
- if ((what == kDict2ListItems
- ? tv_check_for_string_or_list_or_dict_arg(argvars, 0)
- : tv_check_for_dict_arg(argvars, 0)) == FAIL) {
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
tv_list_alloc_ret(rettv, 0);
return;
}
@@ -3267,15 +3287,19 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D
});
}
-/// "items(dict)" function
+/// "items()" function
void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
if (argvars[0].v_type == VAR_STRING) {
tv_string2items(argvars, rettv);
} else if (argvars[0].v_type == VAR_LIST) {
tv_list2items(argvars, rettv);
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ tv_blob2items(argvars, rettv);
+ } else if (argvars[0].v_type == VAR_DICT) {
+ tv_dict2items(argvars, rettv);
} else {
- tv_dict2list(argvars, rettv, kDict2ListItems);
+ semsg(_(e_list_dict_blob_or_string_required_for_argument_nr), 1);
}
}
@@ -4511,19 +4535,6 @@ int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int id
|| tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL;
}
-/// Give an error and return FAIL unless "args[idx]" is a string or a list or a dict
-int tv_check_for_string_or_list_or_dict_arg(const typval_T *const args, const int idx)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
-{
- if (args[idx].v_type != VAR_STRING
- && args[idx].v_type != VAR_LIST
- && args[idx].v_type != VAR_DICT) {
- semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1);
- return FAIL;
- }
- return OK;
-}
-
/// Give an error and return FAIL unless "args[idx]" is a string
/// or a function reference.
int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx)
diff --git a/test/old/testdir/test_blob.vim b/test/old/testdir/test_blob.vim
@@ -884,4 +884,24 @@ func Test_indexof()
call assert_fails('let i = indexof(b, " ")', 'E15:')
endfunc
+" Test for using the items() function with a blob
+func Test_blob_items()
+ let lines =<< trim END
+ call assert_equal([[0, 0xAA], [1, 0xBB], [2, 0xCC]], 0zAABBCC->items())
+ call assert_equal([[0, 0]], 0z00->items())
+ call assert_equal([], 0z->items())
+ call assert_equal([], v:_null_blob->items())
+ END
+ call CheckSourceLegacyAndVim9Success(lines)
+endfunc
+
+" Test for setting a byte in a blob with invalid value
+func Test_blob_byte_set_invalid_value()
+ let lines =<< trim END
+ VAR b = 0zD0C3E4E18E1B
+ LET b[0] = 229539777187355
+ END
+ call CheckSourceLegacyAndVim9Failure(lines, 'E1239: Invalid value for blob:')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/vim9.vim b/test/old/testdir/vim9.vim
@@ -89,20 +89,24 @@ func CheckLegacyFailure(lines, error)
endtry
endfunc
+" Translate "lines" to legacy Vim script
+func s:LegacyTrans(lines)
+ return a:lines->mapnew({_, v ->
+ \ v->substitute('\<VAR\>', 'let', 'g')
+ \ ->substitute('\<LET\>', 'let', 'g')
+ \ ->substitute('\<LSTART\>', '{', 'g')
+ \ ->substitute('\<LMIDDLE\>', '->', 'g')
+ \ ->substitute('\<LEND\>', '}', 'g')
+ \ ->substitute('\<TRUE\>', '1', 'g')
+ \ ->substitute('\<FALSE\>', '0', 'g')
+ \ ->substitute('#"', ' "', 'g')
+ \ })
+endfunc
+
" Execute "lines" in a legacy function, translated as in
" CheckLegacyAndVim9Success()
func CheckTransLegacySuccess(lines)
- let legacylines = a:lines->mapnew({_, v ->
- \ v->substitute('\<VAR\>', 'let', 'g')
- \ ->substitute('\<LET\>', 'let', 'g')
- \ ->substitute('\<LSTART\>', '{', 'g')
- \ ->substitute('\<LMIDDLE\>', '->', 'g')
- \ ->substitute('\<LEND\>', '}', 'g')
- \ ->substitute('\<TRUE\>', '1', 'g')
- \ ->substitute('\<FALSE\>', '0', 'g')
- \ ->substitute('#"', ' "', 'g')
- \ })
- call CheckLegacySuccess(legacylines)
+ call CheckLegacySuccess(s:LegacyTrans(a:lines))
endfunc
func CheckTransDefSuccess(lines)
@@ -143,6 +147,65 @@ func CheckLegacyAndVim9Failure(lines, error)
call CheckLegacyFailure(legacylines, legacyError)
endfunc
+" Check that "lines" inside a legacy function has no error.
+func CheckSourceLegacySuccess(lines)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ :source
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+" Check that "lines" inside a legacy function results in the expected error
+func CheckSourceLegacyFailure(lines, error)
+ let cwd = getcwd()
+ new
+ call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()'])
+ let bnr = bufnr()
+ try
+ call assert_fails('source', a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ exe $':bw! {bnr}'
+ endtry
+endfunc
+
+" Execute "lines" in a legacy function, translated as in
+" CheckSourceLegacyAndVim9Success()
+func CheckSourceTransLegacySuccess(lines)
+ call CheckSourceLegacySuccess(s:LegacyTrans(a:lines))
+endfunc
+
+" Execute "lines" in a :def function, translated as in
+" CheckLegacyAndVim9Success()
+func CheckSourceTransDefSuccess(lines)
+ return
+endfunc
+
+" Execute "lines" in a Vim9 script, translated as in
+" CheckLegacyAndVim9Success()
+func CheckSourceTransVim9Success(lines)
+ return
+endfunc
+
+" Execute "lines" in a legacy function, :def function and Vim9 script.
+" Use 'VAR' for a declaration.
+" Use 'LET' for an assignment
+" Use ' #"' for a comment
+" Use LSTART arg LMIDDLE expr LEND for lambda
+" Use 'TRUE' for 1 in legacy, true in Vim9
+" Use 'FALSE' for 0 in legacy, false in Vim9
+func CheckSourceLegacyAndVim9Success(lines)
+ call CheckSourceTransLegacySuccess(a:lines)
+endfunc
+
" :source a list of "lines" and check whether it fails with "error"
func CheckSourceScriptFailure(lines, error, lnum = -3)
if get(a:lines, 0, '') ==# 'vim9script'
@@ -195,26 +258,10 @@ func CheckSourceScriptSuccess(lines)
endtry
endfunc
-func CheckSourceSuccess(lines)
- call CheckSourceScriptSuccess(a:lines)
-endfunc
-
-func CheckSourceFailure(lines, error, lnum = -3)
- call CheckSourceScriptFailure(a:lines, a:error, a:lnum)
-endfunc
-
-func CheckSourceFailureList(lines, errors, lnum = -3)
- call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum)
-endfunc
-
func CheckSourceDefSuccess(lines)
return
endfunc
-func CheckSourceDefAndScriptSuccess(lines)
- return
-endfunc
-
func CheckSourceDefCompileSuccess(lines)
return
endfunc
@@ -235,3 +282,28 @@ func CheckSourceDefExecAndScriptFailure(lines, error, lnum = -3)
return
endfunc
+func CheckSourceSuccess(lines)
+ call CheckSourceScriptSuccess(a:lines)
+endfunc
+
+func CheckSourceFailure(lines, error, lnum = -3)
+ call CheckSourceScriptFailure(a:lines, a:error, a:lnum)
+endfunc
+
+func CheckSourceFailureList(lines, errors, lnum = -3)
+ call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum)
+endfunc
+
+func CheckSourceDefAndScriptSuccess(lines)
+ return
+endfunc
+
+" Execute "lines" in a legacy function, :def function and Vim9 script.
+" Use 'VAR' for a declaration.
+" Use 'LET' for an assignment
+" Use ' #"' for a comment
+func CheckSourceLegacyAndVim9Failure(lines, error)
+ let legacyError = type(a:error) == type('string') ? a:error : a:error[0]
+ call CheckSourceLegacyFailure(s:LegacyTrans(a:lines), legacyError)
+endfunc
+