# HG changeset patch # User Christian Brabandt # Date 1713202204 -7200 # Node ID 3cacfb6527664de4190b20b2ce0deca759741451 # Parent 44754f99767f4057d56a3e2a4abb0250727eddfc patch 9.1.0335: String interpolation fails for List type Commit: https://github.com/vim/vim/commit/bce51d9005dd1c5bc002acbac2e12b649abcb013 Author: Yegappan Lakshmanan Date: Mon Apr 15 19:19:52 2024 +0200 patch 9.1.0335: String interpolation fails for List type Problem: String interpolation fails for List type Solution: use implicit string(list) for string interpolation and :put = (Yegappan Lakshmanan) related: #14529 closes: #14556 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -575,17 +575,16 @@ skip_expr_concatenate( /* * Convert "tv" to a string. - * When "convert" is TRUE convert a List into a sequence of lines and a Dict - * into a textual representation of the Dict. + * When "join_list" is TRUE convert a List into a sequence of lines. * Returns an allocated string (NULL when out of memory). */ char_u * -typval2string(typval_T *tv, int convert) +typval2string(typval_T *tv, int join_list) { garray_T ga; char_u *retval; - if (convert && tv->v_type == VAR_LIST) + if (join_list && tv->v_type == VAR_LIST) { ga_init2(&ga, sizeof(char), 80); if (tv->vval.v_list != NULL) @@ -597,8 +596,16 @@ typval2string(typval_T *tv, int convert) ga_append(&ga, NUL); retval = (char_u *)ga.ga_data; } - else if (convert && tv->v_type == VAR_DICT) - retval = dict2string(tv, get_copyID(), FALSE); + else if (tv->v_type == VAR_LIST || tv->v_type == VAR_DICT) + { + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + + retval = tv2string(tv, &tofree, numbuf, 0); + // Make a copy if we have a value but it's not in allocated memory. + if (retval != NULL && tofree == NULL) + retval = vim_strsave(retval); + } else retval = vim_strsave(tv_get_string(tv)); return retval; @@ -607,13 +614,13 @@ typval2string(typval_T *tv, int convert) /* * Top level evaluation function, returning a string. Does not handle line * breaks. - * When "convert" is TRUE convert a List into a sequence of lines. + * When "join_list" is TRUE convert a List into a sequence of lines. * Return pointer to allocated memory, or NULL for failure. */ char_u * eval_to_string_eap( char_u *arg, - int convert, + int join_list, exarg_T *eap, int use_simple_function) { @@ -631,7 +638,7 @@ eval_to_string_eap( retval = NULL; else { - retval = typval2string(&tv, convert); + retval = typval2string(&tv, join_list); clear_tv(&tv); } clear_evalarg(&evalarg, NULL); @@ -642,10 +649,10 @@ eval_to_string_eap( char_u * eval_to_string( char_u *arg, - int convert, + int join_list, int use_simple_function) { - return eval_to_string_eap(arg, convert, NULL, use_simple_function); + return eval_to_string_eap(arg, join_list, NULL, use_simple_function); } /* diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -662,7 +662,7 @@ eval_one_expr_in_str(char_u *p, garray_T if (evaluate) { *block_end = NUL; - expr_val = eval_to_string(block_start, TRUE, FALSE); + expr_val = eval_to_string(block_start, FALSE, FALSE); *block_end = '}'; if (expr_val == NULL) return NULL; diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -14,9 +14,9 @@ void init_evalarg(evalarg_T *evalarg); void clear_evalarg(evalarg_T *evalarg, exarg_T *eap); int skip_expr(char_u **pp, evalarg_T *evalarg); int skip_expr_concatenate(char_u **arg, char_u **start, char_u **end, evalarg_T *evalarg); -char_u *typval2string(typval_T *tv, int convert); -char_u *eval_to_string_eap(char_u *arg, int convert, exarg_T *eap, int use_simple_function); -char_u *eval_to_string(char_u *arg, int convert, int use_simple_function); +char_u *typval2string(typval_T *tv, int join_list); +char_u *eval_to_string_eap(char_u *arg, int join_list, exarg_T *eap, int use_simple_function); +char_u *eval_to_string(char_u *arg, int join_list, int use_simple_function); char_u *eval_to_string_safe(char_u *arg, int use_sandbox, int keep_script_version, int use_simple_function); varnumber_T eval_to_number(char_u *expr, int use_simple_function); typval_T *eval_expr(char_u *arg, exarg_T *eap); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -7,7 +7,7 @@ int generate_CONSTRUCT(cctx_T *cctx, cla int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type); int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type); int generate_STORE_THIS(cctx_T *cctx, int idx); -int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx); +int may_generate_2STRING(int offset, int tostring_flags, cctx_T *cctx); int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type); vartype_T operator_type(type_T *type1, type_T *type2); int generate_two_op(cctx_T *cctx, char_u *op); diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim --- a/src/testdir/test_expr.vim +++ b/src/testdir/test_expr.vim @@ -953,6 +953,18 @@ func Test_string_interp() #" Dict interpolation VAR d = {'a': 10, 'b': [1, 2]} call assert_equal("{'a': 10, 'b': [1, 2]}", $'{d}') + VAR emptydict = {} + call assert_equal("a{}b", $'a{emptydict}b') + VAR nulldict = test_null_dict() + call assert_equal("a{}b", $'a{nulldict}b') + + #" List interpolation + VAR l = ['a', 'b', 'c'] + call assert_equal("['a', 'b', 'c']", $'{l}') + VAR emptylist = [] + call assert_equal("a[]b", $'a{emptylist}b') + VAR nulllist = test_null_list() + call assert_equal("a[]b", $'a{nulllist}b') #" Stray closing brace. call assert_fails('echo $"moo}"', 'E1278:') diff --git a/src/testdir/test_let.vim b/src/testdir/test_let.vim --- a/src/testdir/test_let.vim +++ b/src/testdir/test_let.vim @@ -696,6 +696,41 @@ END END call assert_equal(["let d2 = {'a': 10, 'b': 'ss', 'c': {}}"], code) + " Empty dictionary + let d1 = {} + let code =<< eval trim END + let d2 = {d1} + END + call assert_equal(["let d2 = {}"], code) + + " null dictionary + let d1 = test_null_dict() + let code =<< eval trim END + let d2 = {d1} + END + call assert_equal(["let d2 = {}"], code) + + " Evaluate a List + let l1 = ['a', 'b', 'c'] + let code =<< eval trim END + let l2 = {l1} + END + call assert_equal(["let l2 = ['a', 'b', 'c']"], code) + + " Empty List + let l1 = [] + let code =<< eval trim END + let l2 = {l1} + END + call assert_equal(["let l2 = []"], code) + + " Null List + let l1 = test_null_list() + let code =<< eval trim END + let l2 = {l1} + END + call assert_equal(["let l2 = []"], code) + let code = 'xxx' let code =<< eval trim END let n = {5 + diff --git a/src/testdir/test_put.vim b/src/testdir/test_put.vim --- a/src/testdir/test_put.vim +++ b/src/testdir/test_put.vim @@ -328,4 +328,12 @@ func Test_put_dict() bw! endfunc +func Test_put_list() + new + let l = ['a', 'b', 'c'] + put! =l + call assert_equal(['a', 'b', 'c', ''], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -2994,6 +2994,56 @@ def Test_heredoc_expr() CODE v9.CheckDefAndScriptSuccess(lines) + # Evaluate an empty dictionary + lines =<< trim CODE + var d1 = {} + var code =<< trim eval END + var d2 = {d1} + END + assert_equal(["var d2 = {}"], code) + CODE + v9.CheckDefAndScriptSuccess(lines) + + # Evaluate a null dictionary + lines =<< trim CODE + var d1 = test_null_dict() + var code =<< trim eval END + var d2 = {d1} + END + assert_equal(["var d2 = {}"], code) + CODE + v9.CheckDefAndScriptSuccess(lines) + + # Evaluate a List + lines =<< trim CODE + var l1 = ['a', 'b', 'c'] + var code =<< trim eval END + var l2 = {l1} + END + assert_equal(["var l2 = ['a', 'b', 'c']"], code) + CODE + v9.CheckDefAndScriptSuccess(lines) + + # Evaluate an empty List + lines =<< trim CODE + var l1 = [] + var code =<< trim eval END + var l2 = {l1} + END + assert_equal(["var l2 = []"], code) + CODE + v9.CheckDefAndScriptSuccess(lines) + + # Evaluate a null List + lines =<< trim CODE + var l1 = test_null_list() + var code =<< trim eval END + var l2 = {l1} + END + assert_equal(["var l2 = []"], code) + CODE + v9.CheckDefAndScriptSuccess(lines) + lines =<< trim CODE var code =<< eval trim END var s = "{$SOME_ENV_VAR}" diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 335, +/**/ 334, /**/ 333, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -460,7 +460,7 @@ typedef struct { // arguments to ISN_2STRING and ISN_2STRING_ANY typedef struct { int offset; - int tolerant; + int flags; } tostring_T; // arguments to ISN_2BOOL @@ -880,3 +880,10 @@ typedef enum { // flags for call_def_function() #define DEF_USE_PT_ARGV 1 // use the partial arguments + +// Flag used for conversion to string by may_generate_2STRING() +#define TOSTRING_NONE 0x0 +// Convert a List to series of values separated by newline +#define TOSTRING_INTERPOLATE 0x1 +// Convert a List to a textual representation of the list "[...]" +#define TOSTRING_TOLERANT 0x2 diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -1931,7 +1931,7 @@ compile_throw(char_u *arg, cctx_T *cctx return NULL; if (cctx->ctx_skip == SKIP_YES) return p; - if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) + if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) return NULL; if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL) return NULL; @@ -2359,7 +2359,7 @@ compile_exec(char_u *line_arg, exarg_T * p += 2; if (compile_expr0(&p, cctx) == FAIL) return NULL; - may_generate_2STRING(-1, TRUE, cctx); + may_generate_2STRING(-1, TOSTRING_TOLERANT, cctx); ++count; p = skipwhite(p); if (*p != '`') diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1222,7 +1222,7 @@ compile_one_expr_in_str(char_u *p, cctx_ } if (compile_expr0(&block_start, cctx) == FAIL) return NULL; - may_generate_2STRING(-1, TRUE, cctx); + may_generate_2STRING(-1, TOSTRING_INTERPOLATE, cctx); return block_end + 1; } @@ -2421,7 +2421,7 @@ compile_assign_unlet( return FAIL; } if (dest_type == VAR_DICT - && may_generate_2STRING(-1, FALSE, cctx) == FAIL) + && may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) return FAIL; if (dest_type == VAR_LIST || dest_type == VAR_BLOB) { @@ -2975,7 +2975,7 @@ compile_assignment( if (*op == '.') { - if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) + if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) goto theend; } else diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1638,7 +1638,7 @@ store_var(char_u *name, typval_T *tv) * Return FAIL if not allowed. */ static int -do_2string(typval_T *tv, int is_2string_any, int tolerant) +do_2string(typval_T *tv, int is_2string_any, int tostring_flags) { if (tv->v_type == VAR_STRING) return OK; @@ -1657,7 +1657,7 @@ do_2string(typval_T *tv, int is_2string_ case VAR_BLOB: break; case VAR_LIST: - if (tolerant) + if (tostring_flags & TOSTRING_TOLERANT) { char_u *s, *e, *p; garray_T ga; @@ -1690,6 +1690,8 @@ do_2string(typval_T *tv, int is_2string_ tv->vval.v_string = ga.ga_data; return OK; } + if (tostring_flags & TOSTRING_INTERPOLATE) + break; // FALLTHROUGH default: to_string_error(tv->v_type); return FAIL; @@ -5685,7 +5687,7 @@ exec_instructions(ectx_T *ectx) SOURCING_LNUM = iptr->isn_lnum; if (do_2string(STACK_TV_BOT(iptr->isn_arg.tostring.offset), iptr->isn_type == ISN_2STRING_ANY, - iptr->isn_arg.tostring.tolerant) == FAIL) + iptr->isn_arg.tostring.flags) == FAIL) goto on_error; break; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -142,7 +142,7 @@ compile_member(int is_slice, int *keepin typep->type_curr = &t_any; typep->type_decl = &t_any; } - if (may_generate_2STRING(-1, FALSE, cctx) == FAIL + if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL || generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL) return FAIL; if (keeping_dict != NULL) @@ -1598,7 +1598,7 @@ compile_dict(char_u **arg, cctx_T *cctx, } if (isn->isn_type == ISN_PUSHS) key = isn->isn_arg.string; - else if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) + else if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) return FAIL; *arg = skipwhite(*arg); if (**arg != ']') @@ -3014,8 +3014,8 @@ compile_expr6(char_u **arg, cctx_T *cctx ppconst->pp_is_const = FALSE; if (*op == '.') { - if (may_generate_2STRING(-2, FALSE, cctx) == FAIL - || may_generate_2STRING(-1, FALSE, cctx) == FAIL) + if (may_generate_2STRING(-2, TOSTRING_NONE, cctx) == FAIL + || may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) return FAIL; if (generate_CONCAT(cctx, 2) == FAIL) return FAIL; diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -191,10 +191,12 @@ generate_STORE_THIS(cctx_T *cctx, int id /* * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. * But only for simple types. - * When "tolerant" is TRUE convert most types to string, e.g. a List. + * When tostring_flags has TOSTRING_TOLERANT, convert a List to a series of + * strings. When tostring_flags has TOSTRING_INTERPOLATE, convert a List or a + * Dict to the corresponding textual representation. */ int -may_generate_2STRING(int offset, int tolerant, cctx_T *cctx) +may_generate_2STRING(int offset, int tostring_flags, cctx_T *cctx) { isn_T *isn; isntype_T isntype = ISN_2STRING; @@ -223,11 +225,13 @@ may_generate_2STRING(int offset, int tol // conversion possible when tolerant case VAR_LIST: case VAR_DICT: - if (tolerant) + if (tostring_flags & TOSTRING_TOLERANT) { isntype = ISN_2STRING_ANY; break; } + if (tostring_flags & TOSTRING_INTERPOLATE) + break; // FALLTHROUGH // conversion not possible @@ -249,7 +253,7 @@ may_generate_2STRING(int offset, int tol if ((isn = generate_instr(cctx, isntype)) == NULL) return FAIL; isn->isn_arg.tostring.offset = offset; - isn->isn_arg.tostring.tolerant = tolerant; + isn->isn_arg.tostring.flags = tostring_flags; return OK; }