changeset 24357:108a6e2497f6 v8.2.2719

patch 8.2.2719: Vim9: appending to dict item doesn't work in a :def function Commit: https://github.com/vim/vim/commit/e42939af87be3adad8c08ceb454e3a31114d7165 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Apr 5 17:11:17 2021 +0200 patch 8.2.2719: Vim9: appending to dict item doesn't work in a :def function Problem: Vim9: appending to dict item doesn't work in a :def function. Solution: Implement assignment with operator on indexed item.
author Bram Moolenaar <Bram@vim.org>
date Mon, 05 Apr 2021 17:15:06 +0200
parents 03088264506d
children 33c9ef35e406
files src/testdir/test_vim9_assign.vim src/version.c src/vim9compile.c
diffstat 3 files changed, 198 insertions(+), 121 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -1116,7 +1116,6 @@ enddef
 
 def Test_assign_dict_with_op()
   var lines =<< trim END
-    vim9script
     var ds: dict<string> = {a: 'x'}
     ds['a'] ..= 'y'
     ds.a ..= 'z'
@@ -1148,8 +1147,46 @@ def Test_assign_dict_with_op()
     dn.a %= 6
     assert_equal(2, dn.a)
   END
-  # TODO: this should also work with a :def function
-  CheckScriptSuccess(lines)
+  CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_list_with_op()
+  var lines =<< trim END
+    var ls: list<string> = ['x']
+    ls[0] ..= 'y'
+    assert_equal('xy', ls[0])
+
+    var ln: list<number> = [9]
+    ln[0] += 2
+    assert_equal(11, ln[0])
+
+    ln[0] -= 3
+    assert_equal(8, ln[0])
+
+    ln[0] *= 2
+    assert_equal(16, ln[0])
+
+    ln[0] /= 3
+    assert_equal(5, ln[0])
+
+    ln[0] %= 3
+    assert_equal(2, ln[0])
+  END
+  CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_with_op_fails()
+  var lines =<< trim END
+      var s = 'abc'
+      s[1] += 'x'
+  END
+  CheckDefAndScriptFailure2(lines, 'E1141:', 'E689:', 2)
+
+  lines =<< trim END
+      var s = 'abc'
+      s[1] ..= 'x'
+  END
+  CheckDefAndScriptFailure2(lines, 'E1141:', 'E689:', 2)
 enddef
 
 def Test_assign_lambda()
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2719,
+/**/
     2718,
 /**/
     2717,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2650,6 +2650,112 @@ clear_ppconst(ppconst_T *ppconst)
 }
 
 /*
+ * Compile getting a member from a list/dict/string/blob.  Stack has the
+ * indexable value and the index.
+ */
+    static int
+compile_member(int is_slice, cctx_T *cctx)
+{
+    type_T	**typep;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    vartype_T	vtype;
+    type_T	*valtype;
+
+    // We can index a list and a dict.  If we don't know the type
+    // we can use the index value type.
+    // TODO: If we don't know use an instruction to figure it out at
+    // runtime.
+    typep = ((type_T **)stack->ga_data) + stack->ga_len
+						  - (is_slice ? 3 : 2);
+    vtype = (*typep)->tt_type;
+    valtype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+    // If the index is a string, the variable must be a Dict.
+    if (*typep == &t_any && valtype == &t_string)
+	vtype = VAR_DICT;
+    if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
+    {
+	if (need_type(valtype, &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL)
+	    return FAIL;
+	if (is_slice)
+	{
+	    valtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
+	    if (need_type(valtype, &t_number, -2, 0, cctx,
+							 FALSE, FALSE) == FAIL)
+		return FAIL;
+	}
+    }
+
+    if (vtype == VAR_DICT)
+    {
+	if (is_slice)
+	{
+	    emsg(_(e_cannot_slice_dictionary));
+	    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, 0, cctx,
+							 FALSE, FALSE) == FAIL)
+		return FAIL;
+	    *typep = &t_any;
+	}
+	if (may_generate_2STRING(-1, cctx) == FAIL)
+	    return FAIL;
+	if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
+	    return FAIL;
+    }
+    else if (vtype == VAR_STRING)
+    {
+	*typep = &t_string;
+	if ((is_slice
+		? generate_instr_drop(cctx, ISN_STRSLICE, 2)
+		: generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL)
+	    return FAIL;
+    }
+    else if (vtype == VAR_BLOB)
+    {
+	emsg("Sorry, blob index and slice not implemented yet");
+	return FAIL;
+    }
+    else if (vtype == VAR_LIST || *typep == &t_any)
+    {
+	if (is_slice)
+	{
+	    if (generate_instr_drop(cctx,
+		     vtype == VAR_LIST ?  ISN_LISTSLICE : ISN_ANYSLICE,
+							    2) == FAIL)
+		return FAIL;
+	}
+	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)
+		return FAIL;
+	}
+    }
+    else
+    {
+	emsg(_(e_string_list_dict_or_blob_required));
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
  * Generate an instruction to load script-local variable "name", without the
  * leading "s:".
  * Also finds imported variables.
@@ -3934,10 +4040,6 @@ compile_subscript(
 	}
 	else if (**arg == '[')
 	{
-	    garray_T	*stack = &cctx->ctx_type_stack;
-	    type_T	**typep;
-	    type_T	*valtype;
-	    vartype_T	vtype;
 	    int		is_slice = FALSE;
 
 	    // list index: list[123]
@@ -4004,99 +4106,8 @@ compile_subscript(
 	    }
 	    *arg = *arg + 1;
 
-	    // We can index a list and a dict.  If we don't know the type
-	    // we can use the index value type.
-	    // TODO: If we don't know use an instruction to figure it out at
-	    // runtime.
-	    typep = ((type_T **)stack->ga_data) + stack->ga_len
-							  - (is_slice ? 3 : 2);
-	    vtype = (*typep)->tt_type;
-	    valtype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
-	    // If the index is a string, the variable must be a Dict.
-	    if (*typep == &t_any && valtype == &t_string)
-		vtype = VAR_DICT;
-	    if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
-	    {
-		if (need_type(valtype, &t_number, -1, 0, cctx,
-							 FALSE, FALSE) == FAIL)
-		    return FAIL;
-		if (is_slice)
-		{
-		    valtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
-		    if (need_type(valtype, &t_number, -2, 0, cctx,
-							 FALSE, FALSE) == FAIL)
-			return FAIL;
-		}
-	    }
-
-	    if (vtype == VAR_DICT)
-	    {
-		if (is_slice)
-		{
-		    emsg(_(e_cannot_slice_dictionary));
-		    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, 0, cctx,
-							 FALSE, FALSE) == FAIL)
-			return FAIL;
-		    *typep = &t_any;
-		}
-		if (may_generate_2STRING(-1, cctx) == FAIL)
-		    return FAIL;
-		if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
-		    return FAIL;
-	    }
-	    else if (vtype == VAR_STRING)
-	    {
-		*typep = &t_string;
-		if ((is_slice
-			? generate_instr_drop(cctx, ISN_STRSLICE, 2)
-			: generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL)
-		    return FAIL;
-	    }
-	    else if (vtype == VAR_BLOB)
-	    {
-		emsg("Sorry, blob index and slice not implemented yet");
+	    if (compile_member(is_slice, cctx) == FAIL)
 		return FAIL;
-	    }
-	    else if (vtype == VAR_LIST || *typep == &t_any)
-	    {
-		if (is_slice)
-		{
-		    if (generate_instr_drop(cctx,
-			     vtype == VAR_LIST ?  ISN_LISTSLICE : ISN_ANYSLICE,
-								    2) == FAIL)
-			return FAIL;
-		}
-		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)
-			return FAIL;
-		}
-	    }
-	    else
-	    {
-		emsg(_(e_string_list_dict_or_blob_required));
-		return FAIL;
-	    }
 	}
 	else if (*p == '.' && p[1] != '.')
 	{
@@ -5905,9 +5916,11 @@ compile_lhs(
 	    lhs->lhs_type = lhs->lhs_lvar->lv_type;
     }
 
-    if (oplen == 3 && !heredoc && lhs->lhs_dest != dest_global
-		    && lhs->lhs_type->tt_type != VAR_STRING
-		    && lhs->lhs_type->tt_type != VAR_ANY)
+    if (oplen == 3 && !heredoc
+		   && lhs->lhs_dest != dest_global
+		   && !lhs->lhs_has_index
+		   && lhs->lhs_type->tt_type != VAR_STRING
+		   && lhs->lhs_type->tt_type != VAR_ANY)
     {
 	emsg(_(e_can_only_concatenate_to_string));
 	return FAIL;
@@ -5993,26 +6006,21 @@ compile_lhs(
 }
 
 /*
- * Assignment to a list or dict member, or ":unlet" for the item, using the
- * information in "lhs".
- * Returns OK or FAIL.
- */
-    static int
-compile_assign_unlet(
+ * For an assignment with an index, compile the "idx" in "var[idx]" or "key" in
+ * "var.key".
+ */
+    static int
+compile_assign_index(
 	char_u	*var_start,
 	lhs_T	*lhs,
 	int	is_assign,
-	type_T	*rhs_type,
+	int	*range,
 	cctx_T	*cctx)
 {
+    size_t	varlen = lhs->lhs_varlen;
     char_u	*p;
-    int		r;
-    vartype_T	dest_type;
-    size_t	varlen = lhs->lhs_varlen;
-    garray_T    *stack = &cctx->ctx_type_stack;
-    int		range = FALSE;
-
-    // Compile the "idx" in "var[idx]" or "key" in "var.key".
+    int		r = OK;
+
     p = var_start + varlen;
     if (*p == '[')
     {
@@ -6027,7 +6035,7 @@ compile_assign_unlet(
 		semsg(_(e_cannot_use_range_with_assignment_str), p);
 		return FAIL;
 	    }
-	    range = TRUE;
+	    *range = TRUE;
 	    p = skipwhite(p);
 	    if (!IS_WHITE_OR_NUL(p[-1]) || !IS_WHITE_OR_NUL(p[1]))
 	    {
@@ -6053,7 +6061,29 @@ compile_assign_unlet(
 
 	r = generate_PUSHS(cctx, key);
     }
-    if (r == FAIL)
+    return r;
+}
+
+/*
+ * Assignment to a list or dict member, or ":unlet" for the item, using the
+ * information in "lhs".
+ * Returns OK or FAIL.
+ */
+    static int
+compile_assign_unlet(
+	char_u	*var_start,
+	lhs_T	*lhs,
+	int	is_assign,
+	type_T	*rhs_type,
+	cctx_T	*cctx)
+{
+    char_u	*p;
+    vartype_T	dest_type;
+    size_t	varlen = lhs->lhs_varlen;
+    garray_T    *stack = &cctx->ctx_type_stack;
+    int		range = FALSE;
+
+    if (compile_assign_index(var_start, lhs, is_assign, &range, cctx) == FAIL)
 	return FAIL;
 
     if (lhs->lhs_type == &t_any)
@@ -6330,9 +6360,17 @@ compile_assignment(char_u *arg, exarg_T 
 
 			if (lhs.lhs_has_index)
 			{
-			    // TODO: get member from list or dict
-			    emsg("Index with operation not supported yet");
-			    goto theend;
+			    int range = FALSE;
+
+			    // Get member from list or dict.  First compile the
+			    // index value.
+			    if (compile_assign_index(var_start, &lhs,
+						   TRUE, &range, cctx) == FAIL)
+				goto theend;
+
+			    // Get the member.
+			    if (compile_member(FALSE, cctx) == FAIL)
+				goto theend;
 			}
 		    }