1
0
forked from aniani/vim

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 <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan 2024-04-15 19:19:52 +02:00 committed by Christian Brabandt
parent a59e031aa0
commit bce51d9005
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
15 changed files with 160 additions and 33 deletions

View File

@ -575,17 +575,16 @@ skip_expr_concatenate(
/* /*
* Convert "tv" to a string. * Convert "tv" to a string.
* When "convert" is TRUE convert a List into a sequence of lines and a Dict * When "join_list" is TRUE convert a List into a sequence of lines.
* into a textual representation of the Dict.
* Returns an allocated string (NULL when out of memory). * Returns an allocated string (NULL when out of memory).
*/ */
char_u * char_u *
typval2string(typval_T *tv, int convert) typval2string(typval_T *tv, int join_list)
{ {
garray_T ga; garray_T ga;
char_u *retval; char_u *retval;
if (convert && tv->v_type == VAR_LIST) if (join_list && tv->v_type == VAR_LIST)
{ {
ga_init2(&ga, sizeof(char), 80); ga_init2(&ga, sizeof(char), 80);
if (tv->vval.v_list != NULL) if (tv->vval.v_list != NULL)
@ -597,8 +596,16 @@ typval2string(typval_T *tv, int convert)
ga_append(&ga, NUL); ga_append(&ga, NUL);
retval = (char_u *)ga.ga_data; retval = (char_u *)ga.ga_data;
} }
else if (convert && tv->v_type == VAR_DICT) else if (tv->v_type == VAR_LIST || tv->v_type == VAR_DICT)
retval = dict2string(tv, get_copyID(), FALSE); {
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 else
retval = vim_strsave(tv_get_string(tv)); retval = vim_strsave(tv_get_string(tv));
return retval; return retval;
@ -607,13 +614,13 @@ typval2string(typval_T *tv, int convert)
/* /*
* Top level evaluation function, returning a string. Does not handle line * Top level evaluation function, returning a string. Does not handle line
* breaks. * 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. * Return pointer to allocated memory, or NULL for failure.
*/ */
char_u * char_u *
eval_to_string_eap( eval_to_string_eap(
char_u *arg, char_u *arg,
int convert, int join_list,
exarg_T *eap, exarg_T *eap,
int use_simple_function) int use_simple_function)
{ {
@ -631,7 +638,7 @@ eval_to_string_eap(
retval = NULL; retval = NULL;
else else
{ {
retval = typval2string(&tv, convert); retval = typval2string(&tv, join_list);
clear_tv(&tv); clear_tv(&tv);
} }
clear_evalarg(&evalarg, NULL); clear_evalarg(&evalarg, NULL);
@ -642,10 +649,10 @@ eval_to_string_eap(
char_u * char_u *
eval_to_string( eval_to_string(
char_u *arg, char_u *arg,
int convert, int join_list,
int use_simple_function) 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);
} }
/* /*

View File

@ -662,7 +662,7 @@ eval_one_expr_in_str(char_u *p, garray_T *gap, int evaluate)
if (evaluate) if (evaluate)
{ {
*block_end = NUL; *block_end = NUL;
expr_val = eval_to_string(block_start, TRUE, FALSE); expr_val = eval_to_string(block_start, FALSE, FALSE);
*block_end = '}'; *block_end = '}';
if (expr_val == NULL) if (expr_val == NULL)
return NULL; return NULL;

View File

@ -14,9 +14,9 @@ void init_evalarg(evalarg_T *evalarg);
void clear_evalarg(evalarg_T *evalarg, exarg_T *eap); void clear_evalarg(evalarg_T *evalarg, exarg_T *eap);
int skip_expr(char_u **pp, evalarg_T *evalarg); 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); 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 *typval2string(typval_T *tv, int join_list);
char_u *eval_to_string_eap(char_u *arg, int convert, exarg_T *eap, int use_simple_function); 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 convert, 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); 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); varnumber_T eval_to_number(char_u *expr, int use_simple_function);
typval_T *eval_expr(char_u *arg, exarg_T *eap); typval_T *eval_expr(char_u *arg, exarg_T *eap);

View File

@ -7,7 +7,7 @@ int generate_CONSTRUCT(cctx_T *cctx, class_T *cl);
int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type); 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_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type);
int generate_STORE_THIS(cctx_T *cctx, int idx); 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); 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); vartype_T operator_type(type_T *type1, type_T *type2);
int generate_two_op(cctx_T *cctx, char_u *op); int generate_two_op(cctx_T *cctx, char_u *op);

View File

@ -953,6 +953,18 @@ func Test_string_interp()
#" Dict interpolation #" Dict interpolation
VAR d = {'a': 10, 'b': [1, 2]} VAR d = {'a': 10, 'b': [1, 2]}
call assert_equal("{'a': 10, 'b': [1, 2]}", $'{d}') 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. #" Stray closing brace.
call assert_fails('echo $"moo}"', 'E1278:') call assert_fails('echo $"moo}"', 'E1278:')

View File

@ -696,6 +696,41 @@ END
END END
call assert_equal(["let d2 = {'a': 10, 'b': 'ss', 'c': {}}"], code) 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 = 'xxx'
let code =<< eval trim END let code =<< eval trim END
let n = {5 + let n = {5 +

View File

@ -328,4 +328,12 @@ func Test_put_dict()
bw! bw!
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab

View File

@ -2994,6 +2994,56 @@ def Test_heredoc_expr()
CODE CODE
v9.CheckDefAndScriptSuccess(lines) 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 lines =<< trim CODE
var code =<< eval trim END var code =<< eval trim END
var s = "{$SOME_ENV_VAR}" var s = "{$SOME_ENV_VAR}"

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
335,
/**/ /**/
334, 334,
/**/ /**/

View File

@ -460,7 +460,7 @@ typedef struct {
// arguments to ISN_2STRING and ISN_2STRING_ANY // arguments to ISN_2STRING and ISN_2STRING_ANY
typedef struct { typedef struct {
int offset; int offset;
int tolerant; int flags;
} tostring_T; } tostring_T;
// arguments to ISN_2BOOL // arguments to ISN_2BOOL
@ -880,3 +880,10 @@ typedef enum {
// flags for call_def_function() // flags for call_def_function()
#define DEF_USE_PT_ARGV 1 // use the partial arguments #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

View File

@ -1931,7 +1931,7 @@ compile_throw(char_u *arg, cctx_T *cctx UNUSED)
return NULL; return NULL;
if (cctx->ctx_skip == SKIP_YES) if (cctx->ctx_skip == SKIP_YES)
return p; return p;
if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
return NULL; return NULL;
if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL) if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL)
return NULL; return NULL;
@ -2359,7 +2359,7 @@ compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx)
p += 2; p += 2;
if (compile_expr0(&p, cctx) == FAIL) if (compile_expr0(&p, cctx) == FAIL)
return NULL; return NULL;
may_generate_2STRING(-1, TRUE, cctx); may_generate_2STRING(-1, TOSTRING_TOLERANT, cctx);
++count; ++count;
p = skipwhite(p); p = skipwhite(p);
if (*p != '`') if (*p != '`')

View File

@ -1222,7 +1222,7 @@ compile_one_expr_in_str(char_u *p, cctx_T *cctx)
} }
if (compile_expr0(&block_start, cctx) == FAIL) if (compile_expr0(&block_start, cctx) == FAIL)
return NULL; return NULL;
may_generate_2STRING(-1, TRUE, cctx); may_generate_2STRING(-1, TOSTRING_INTERPOLATE, cctx);
return block_end + 1; return block_end + 1;
} }
@ -2421,7 +2421,7 @@ compile_assign_unlet(
return FAIL; return FAIL;
} }
if (dest_type == VAR_DICT if (dest_type == VAR_DICT
&& may_generate_2STRING(-1, FALSE, cctx) == FAIL) && may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
return FAIL; return FAIL;
if (dest_type == VAR_LIST || dest_type == VAR_BLOB) if (dest_type == VAR_LIST || dest_type == VAR_BLOB)
{ {
@ -2975,7 +2975,7 @@ compile_assignment(
if (*op == '.') if (*op == '.')
{ {
if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
goto theend; goto theend;
} }
else else

View File

@ -1638,7 +1638,7 @@ store_var(char_u *name, typval_T *tv)
* Return FAIL if not allowed. * Return FAIL if not allowed.
*/ */
static int 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) if (tv->v_type == VAR_STRING)
return OK; return OK;
@ -1657,7 +1657,7 @@ do_2string(typval_T *tv, int is_2string_any, int tolerant)
case VAR_BLOB: break; case VAR_BLOB: break;
case VAR_LIST: case VAR_LIST:
if (tolerant) if (tostring_flags & TOSTRING_TOLERANT)
{ {
char_u *s, *e, *p; char_u *s, *e, *p;
garray_T ga; garray_T ga;
@ -1690,6 +1690,8 @@ do_2string(typval_T *tv, int is_2string_any, int tolerant)
tv->vval.v_string = ga.ga_data; tv->vval.v_string = ga.ga_data;
return OK; return OK;
} }
if (tostring_flags & TOSTRING_INTERPOLATE)
break;
// FALLTHROUGH // FALLTHROUGH
default: to_string_error(tv->v_type); default: to_string_error(tv->v_type);
return FAIL; return FAIL;
@ -5685,7 +5687,7 @@ exec_instructions(ectx_T *ectx)
SOURCING_LNUM = iptr->isn_lnum; SOURCING_LNUM = iptr->isn_lnum;
if (do_2string(STACK_TV_BOT(iptr->isn_arg.tostring.offset), if (do_2string(STACK_TV_BOT(iptr->isn_arg.tostring.offset),
iptr->isn_type == ISN_2STRING_ANY, iptr->isn_type == ISN_2STRING_ANY,
iptr->isn_arg.tostring.tolerant) == FAIL) iptr->isn_arg.tostring.flags) == FAIL)
goto on_error; goto on_error;
break; break;

View File

@ -142,7 +142,7 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
typep->type_curr = &t_any; typep->type_curr = &t_any;
typep->type_decl = &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) || generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
return FAIL; return FAIL;
if (keeping_dict != NULL) if (keeping_dict != NULL)
@ -1598,7 +1598,7 @@ compile_dict(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
} }
if (isn->isn_type == ISN_PUSHS) if (isn->isn_type == ISN_PUSHS)
key = isn->isn_arg.string; 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; return FAIL;
*arg = skipwhite(*arg); *arg = skipwhite(*arg);
if (**arg != ']') if (**arg != ']')
@ -3014,8 +3014,8 @@ compile_expr6(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
ppconst->pp_is_const = FALSE; ppconst->pp_is_const = FALSE;
if (*op == '.') if (*op == '.')
{ {
if (may_generate_2STRING(-2, FALSE, cctx) == FAIL if (may_generate_2STRING(-2, TOSTRING_NONE, cctx) == FAIL
|| may_generate_2STRING(-1, FALSE, cctx) == FAIL) || may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
return FAIL; return FAIL;
if (generate_CONCAT(cctx, 2) == FAIL) if (generate_CONCAT(cctx, 2) == FAIL)
return FAIL; return FAIL;

View File

@ -191,10 +191,12 @@ generate_STORE_THIS(cctx_T *cctx, int idx)
/* /*
* If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING.
* But only for simple types. * 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 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; isn_T *isn;
isntype_T isntype = ISN_2STRING; isntype_T isntype = ISN_2STRING;
@ -223,11 +225,13 @@ may_generate_2STRING(int offset, int tolerant, cctx_T *cctx)
// conversion possible when tolerant // conversion possible when tolerant
case VAR_LIST: case VAR_LIST:
case VAR_DICT: case VAR_DICT:
if (tolerant) if (tostring_flags & TOSTRING_TOLERANT)
{ {
isntype = ISN_2STRING_ANY; isntype = ISN_2STRING_ANY;
break; break;
} }
if (tostring_flags & TOSTRING_INTERPOLATE)
break;
// FALLTHROUGH // FALLTHROUGH
// conversion not possible // conversion not possible
@ -249,7 +253,7 @@ may_generate_2STRING(int offset, int tolerant, cctx_T *cctx)
if ((isn = generate_instr(cctx, isntype)) == NULL) if ((isn = generate_instr(cctx, isntype)) == NULL)
return FAIL; return FAIL;
isn->isn_arg.tostring.offset = offset; isn->isn_arg.tostring.offset = offset;
isn->isn_arg.tostring.tolerant = tolerant; isn->isn_arg.tostring.flags = tostring_flags;
return OK; return OK;
} }