changeset 17079:00ffed9bbb65 v8.1.1539

patch 8.1.1539: not easy to define a variable and lock it commit https://github.com/vim/vim/commit/9937a055437ef67b57a1bdec8f0799b669c9dbf0 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jun 15 15:45:06 2019 +0200 patch 8.1.1539: not easy to define a variable and lock it Problem: Not easy to define a variable and lock it. Solution: Add ":const".
author Bram Moolenaar <Bram@vim.org>
date Sat, 15 Jun 2019 16:00:05 +0200
parents 44dab1a4d0ef
children 6fd092960cdf
files runtime/doc/eval.txt src/eval.c src/ex_cmdidxs.h src/ex_cmds.h src/proto/eval.pro src/testdir/Make_all.mak src/testdir/test_const.vim src/version.c
diffstat 8 files changed, 398 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -11598,8 +11598,31 @@ text...
 			No error message is given for a non-existing
 			variable, also without !.
 			If the system does not support deleting an environment
-			variable, it is made emtpy.
-
+			variable, it is made empty.
+
+						*:cons* *:const* *E996*
+:cons[t] {var-name} = {expr1}
+:cons[t] [{name1}, {name2}, ...] = {expr1}
+:cons[t] [{name1}, {name2}, ...] .= {expr1}
+:cons[t] [{name}, ..., ; {lastname}] = {expr1}
+:cons[t] {var-name} =<< [trim] {marker}
+text...
+text...
+{marker}
+			Similar to |:let|, but additionally lock the variable
+			after setting the value.  This is the same as locking
+			the variable with |:lockvar| just after |:let|, thus: >
+				:const x = 1
+<			is equivalent to: >
+				:let x = 1
+				:lockvar 1 x
+<			This is useful if you want to make sure the variable
+			is not modified.
+							*E995*
+			|:const| does not allow to for changing a variable. >
+				:let x = 1
+				:const x = 2  " Error!
+<
 :lockv[ar][!] [depth] {name} ...			*:lockvar* *:lockv*
 			Lock the internal variable {name}.  Locking means that
 			it can no longer be changed (until it is unlocked).
--- a/src/eval.c
+++ b/src/eval.c
@@ -28,6 +28,7 @@ static char *e_missbrac = N_("E111: Miss
 static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
 static char *e_letwrong = N_("E734: Wrong variable type for %s=");
 static char *e_illvar = N_("E461: Illegal variable name: %s");
+static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
 #ifdef FEAT_FLOAT
 static char *e_float_as_string = N_("E806: using Float as a String");
 #endif
@@ -212,7 +213,8 @@ static struct vimvar
 static dictitem_T	vimvars_var;		/* variable used for v: */
 #define vimvarht  vimvardict.dv_hashtab
 
-static int ex_let_vars(char_u *arg, typval_T *tv, int copy, int semicolon, int var_count, char_u *nextchars);
+static void ex_let_const(exarg_T *eap, int is_const);
+static int ex_let_vars(char_u *arg, typval_T *tv, int copy, int semicolon, int var_count, int is_const, char_u *nextchars);
 static char_u *skip_var_list(char_u *arg, int *var_count, int *semicolon);
 static char_u *skip_var_one(char_u *arg);
 static void list_glob_vars(int *first);
@@ -222,8 +224,8 @@ static void list_tab_vars(int *first);
 static void list_vim_vars(int *first);
 static void list_script_vars(int *first);
 static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first);
-static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, char_u *endchars, char_u *op);
-static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, char_u *op);
+static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int is_const, char_u *endchars, char_u *op);
+static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, int is_const, char_u *op);
 static int tv_op(typval_T *tv1, typval_T *tv2, char_u  *op);
 static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep);
 static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit);
@@ -248,6 +250,7 @@ static typval_T *alloc_string_tv(char_u 
 static void delete_var(hashtab_T *ht, hashitem_T *hi);
 static void list_one_var(dictitem_T *v, char *prefix, int *first);
 static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first);
+static void set_var_const(char_u *name, typval_T *tv, int copy, int is_const);
 static int tv_check_lock(typval_T *tv, char_u *name, int use_gettext);
 static char_u *find_option_end(char_u **arg, int *opt_flags);
 
@@ -526,9 +529,9 @@ var_redir_start(char_u *name, int append
     tv.v_type = VAR_STRING;
     tv.vval.v_string = (char_u *)"";
     if (append)
-	set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)".");
+	set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)".");
     else
-	set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"=");
+	set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)"=");
     clear_lval(redir_lval);
     err = did_emsg;
     did_emsg |= save_emsg;
@@ -601,7 +604,8 @@ var_redir_stop(void)
 	    redir_endp = get_lval(redir_varname, NULL, redir_lval,
 					FALSE, FALSE, 0, FNE_CHECK_START);
 	    if (redir_endp != NULL && redir_lval->ll_name != NULL)
-		set_var_lval(redir_lval, redir_endp, &tv, FALSE, (char_u *)".");
+		set_var_lval(redir_lval, redir_endp, &tv, FALSE, FALSE,
+								(char_u *)".");
 	    clear_lval(redir_lval);
 	}
 
@@ -1338,6 +1342,24 @@ heredoc_get(exarg_T *eap, char_u *cmd)
     void
 ex_let(exarg_T *eap)
 {
+    ex_let_const(eap, FALSE);
+}
+
+/*
+ * ":const"			list all variable values
+ * ":const var1 var2"		list variable values
+ * ":const var = expr"		assignment command.
+ * ":const [var1, var2] = expr"	unpack list.
+ */
+    void
+ex_const(exarg_T *eap)
+{
+    ex_let_const(eap, TRUE);
+}
+
+    static void
+ex_let_const(exarg_T *eap, int is_const)
+{
     char_u	*arg = eap->arg;
     char_u	*expr = NULL;
     typval_T	rettv;
@@ -1396,7 +1418,7 @@ ex_let(exarg_T *eap)
 	    op[0] = '=';
 	    op[1] = NUL;
 	    (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
-									  op);
+								is_const, op);
 	    clear_tv(&rettv);
 	}
     }
@@ -1429,7 +1451,7 @@ ex_let(exarg_T *eap)
 	else if (i != FAIL)
 	{
 	    (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
-									  op);
+								 is_const, op);
 	    clear_tv(&rettv);
 	}
     }
@@ -1447,9 +1469,10 @@ ex_let(exarg_T *eap)
 ex_let_vars(
     char_u	*arg_start,
     typval_T	*tv,
-    int		copy,		/* copy values from "tv", don't move */
-    int		semicolon,	/* from skip_var_list() */
-    int		var_count,	/* from skip_var_list() */
+    int		copy,		// copy values from "tv", don't move
+    int		semicolon,	// from skip_var_list()
+    int		var_count,	// from skip_var_list()
+    int		is_const,	// lock variables for const
     char_u	*nextchars)
 {
     char_u	*arg = arg_start;
@@ -1463,7 +1486,7 @@ ex_let_vars(
 	/*
 	 * ":let var = expr" or ":for var in list"
 	 */
-	if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL)
+	if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL)
 	    return FAIL;
 	return OK;
     }
@@ -1493,7 +1516,8 @@ ex_let_vars(
     while (*arg != ']')
     {
 	arg = skipwhite(arg + 1);
-	arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars);
+	arg = ex_let_one(arg, &item->li_tv, TRUE, is_const,
+						   (char_u *)",;]", nextchars);
 	item = item->li_next;
 	if (arg == NULL)
 	    return FAIL;
@@ -1517,8 +1541,8 @@ ex_let_vars(
 	    ltv.vval.v_list = l;
 	    l->lv_refcount = 1;
 
-	    arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE,
-						    (char_u *)"]", nextchars);
+	    arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE, is_const,
+						     (char_u *)"]", nextchars);
 	    clear_tv(&ltv);
 	    if (arg == NULL)
 		return FAIL;
@@ -1805,11 +1829,12 @@ list_arg_vars(exarg_T *eap, char_u *arg,
  */
     static char_u *
 ex_let_one(
-    char_u	*arg,		/* points to variable name */
-    typval_T	*tv,		/* value to assign to variable */
-    int		copy,		/* copy value from "tv" */
-    char_u	*endchars,	/* valid chars after variable name  or NULL */
-    char_u	*op)		/* "+", "-", "."  or NULL*/
+    char_u	*arg,		// points to variable name
+    typval_T	*tv,		// value to assign to variable
+    int		copy,		// copy value from "tv"
+    int		is_const,	// lock variable for const
+    char_u	*endchars,	// valid chars after variable name  or NULL
+    char_u	*op)		// "+", "-", "."  or NULL
 {
     int		c1;
     char_u	*name;
@@ -1824,6 +1849,11 @@ ex_let_one(
      */
     if (*arg == '$')
     {
+	if (is_const)
+	{
+	    emsg(_("E996: Cannot lock an environment variable"));
+	    return NULL;
+	}
 	/* Find the end of the name. */
 	++arg;
 	name = arg;
@@ -1879,6 +1909,11 @@ ex_let_one(
      */
     else if (*arg == '&')
     {
+	if (is_const)
+	{
+	    emsg(_("E996: Cannot lock an option"));
+	    return NULL;
+	}
 	/* Find the end of the name. */
 	p = find_option_end(&arg, &opt_flags);
 	if (p == NULL || (endchars != NULL
@@ -1943,6 +1978,11 @@ ex_let_one(
      */
     else if (*arg == '@')
     {
+	if (is_const)
+	{
+	    emsg(_("E996: Cannot lock a register"));
+	    return NULL;
+	}
 	++arg;
 	if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL)
 	    semsg(_(e_letwrong), op);
@@ -1988,7 +2028,7 @@ ex_let_one(
 		emsg(_(e_letunexp));
 	    else
 	    {
-		set_var_lval(&lv, p, tv, copy, op);
+		set_var_lval(&lv, p, tv, copy, is_const, op);
 		arg_end = p;
 	    }
 	}
@@ -2430,6 +2470,7 @@ set_var_lval(
     char_u	*endp,
     typval_T	*rettv,
     int		copy,
+    int		is_const,    // Disallow to modify existing variable for :const
     char_u	*op)
 {
     int		cc;
@@ -2495,6 +2536,13 @@ set_var_lval(
 	{
 	    typval_T tv;
 
+	    if (is_const)
+	    {
+		emsg(_(e_cannot_mod));
+		*endp = cc;
+		return;
+	    }
+
 	    // handle +=, -=, *=, /=, %= and .=
 	    di = NULL;
 	    if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name),
@@ -2509,7 +2557,7 @@ set_var_lval(
 	    }
 	}
 	else
-	    set_var(lp->ll_name, rettv, copy);
+	    set_var_const(lp->ll_name, rettv, copy, is_const);
 	*endp = cc;
     }
     else if (var_check_lock(lp->ll_newkey == NULL
@@ -2521,6 +2569,12 @@ set_var_lval(
 	listitem_T *ll_li = lp->ll_li;
 	int	    ll_n1 = lp->ll_n1;
 
+	if (is_const)
+	{
+	    emsg(_("E996: Cannot lock a range"));
+	    return;
+	}
+
 	/*
 	 * Check whether any of the list items is locked
 	 */
@@ -2574,6 +2628,11 @@ set_var_lval(
 	/*
 	 * Assign to a List or Dictionary item.
 	 */
+	if (is_const)
+	{
+	    emsg(_("E996: Cannot lock a list or dict"));
+	    return;
+	}
 	if (lp->ll_newkey != NULL)
 	{
 	    if (op != NULL && *op != '=')
@@ -2860,8 +2919,8 @@ next_for_item(void *fi_void, char_u *arg
 	tv.v_lock = VAR_FIXED;
 	tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi);
 	++fi->fi_bi;
-	return ex_let_vars(arg, &tv, TRUE,
-			      fi->fi_semicolon, fi->fi_varcount, NULL) == OK;
+	return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
+					   fi->fi_varcount, FALSE, NULL) == OK;
     }
 
     item = fi->fi_lw.lw_item;
@@ -2870,8 +2929,8 @@ next_for_item(void *fi_void, char_u *arg
     else
     {
 	fi->fi_lw.lw_item = item->li_next;
-	result = (ex_let_vars(arg, &item->li_tv, TRUE,
-			      fi->fi_semicolon, fi->fi_varcount, NULL) == OK);
+	result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon,
+					  fi->fi_varcount, FALSE, NULL) == OK);
     }
     return result;
 }
@@ -8051,7 +8110,22 @@ list_one_var_a(
 set_var(
     char_u	*name,
     typval_T	*tv,
-    int		copy)	    /* make copy of value in "tv" */
+    int		copy)	    // make copy of value in "tv"
+{
+    set_var_const(name, tv, copy, FALSE);
+}
+
+/*
+ * Set variable "name" to value in "tv".
+ * If the variable already exists and "is_const" is FALSE the value is updated.
+ * Otherwise the variable is created.
+ */
+    static void
+set_var_const(
+    char_u	*name,
+    typval_T	*tv,
+    int		copy,	    // make copy of value in "tv"
+    int		is_const)   // disallow to modify existing variable
 {
     dictitem_T	*v;
     char_u	*varname;
@@ -8075,6 +8149,12 @@ set_var(
 
     if (v != NULL)
     {
+	if (is_const)
+	{
+	    emsg(_(e_cannot_mod));
+	    return;
+	}
+
 	/* existing variable, need to clear the value */
 	if (var_check_ro(v->di_flags, name, FALSE)
 			      || var_check_lock(v->di_tv.v_lock, name, FALSE))
@@ -8152,6 +8232,8 @@ set_var(
 	    return;
 	}
 	v->di_flags = DI_FLAGS_ALLOC;
+	if (is_const)
+	    v->di_flags |= DI_FLAGS_LOCK;
     }
 
     if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
@@ -8162,6 +8244,9 @@ set_var(
 	v->di_tv.v_lock = 0;
 	init_tv(tv);
     }
+
+    if (is_const)
+	v->di_tv.v_lock |= VAR_LOCKED;
 }
 
 /*
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -8,29 +8,29 @@ static const unsigned short cmdidxs1[26]
   /* a */ 0,
   /* b */ 19,
   /* c */ 42,
-  /* d */ 107,
-  /* e */ 129,
-  /* f */ 149,
-  /* g */ 165,
-  /* h */ 171,
-  /* i */ 180,
-  /* j */ 198,
-  /* k */ 200,
-  /* l */ 205,
-  /* m */ 267,
-  /* n */ 285,
-  /* o */ 305,
-  /* p */ 317,
-  /* q */ 356,
-  /* r */ 359,
-  /* s */ 379,
-  /* t */ 447,
-  /* u */ 492,
-  /* v */ 503,
-  /* w */ 521,
-  /* x */ 535,
-  /* y */ 545,
-  /* z */ 546
+  /* d */ 108,
+  /* e */ 130,
+  /* f */ 150,
+  /* g */ 166,
+  /* h */ 172,
+  /* i */ 181,
+  /* j */ 199,
+  /* k */ 201,
+  /* l */ 206,
+  /* m */ 268,
+  /* n */ 286,
+  /* o */ 306,
+  /* p */ 318,
+  /* q */ 357,
+  /* r */ 360,
+  /* s */ 380,
+  /* t */ 448,
+  /* u */ 493,
+  /* v */ 504,
+  /* w */ 522,
+  /* x */ 536,
+  /* y */ 546,
+  /* z */ 547
 };
 
 /*
@@ -43,7 +43,7 @@ static const unsigned char cmdidxs2[26][
 { /*         a   b   c   d   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s   t   u   v   w   x   y   z */
   /* a */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  4,  5,  6,  0,  0,  0,  7, 15,  0, 16,  0,  0,  0,  0,  0 },
   /* b */ {  2,  0,  0,  4,  5,  7,  0,  0,  0,  0,  0,  8,  9, 10, 11, 12,  0, 13,  0,  0,  0,  0, 22,  0,  0,  0 },
-  /* c */ {  3, 12, 16, 18, 20, 22, 25,  0,  0,  0,  0, 33, 37, 40, 46, 55, 57, 58, 59,  0, 61,  0, 64,  0,  0,  0 },
+  /* c */ {  3, 12, 16, 18, 20, 22, 25,  0,  0,  0,  0, 33, 37, 40, 46, 56, 58, 59, 60,  0, 62,  0, 65,  0,  0,  0 },
   /* d */ {  0,  0,  0,  0,  0,  0,  0,  0,  6, 15,  0, 16,  0,  0, 17,  0,  0, 19, 20,  0,  0,  0,  0,  0,  0,  0 },
   /* e */ {  1,  0,  2,  0,  0,  0,  0,  0,  0,  0,  0,  7,  9, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0, 16,  0,  0 },
   /* f */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  9,  0,  0,  0,  0,  0, 15,  0,  0,  0,  0,  0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };
 
-static const int command_count = 559;
+static const int command_count = 560;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -401,6 +401,9 @@ EX(CMD_continue,	"continue",	ex_continue
 EX(CMD_confirm,		"confirm",	ex_wrongmodifier,
 			NEEDARG|EXTRA|NOTRLCOM|CMDWIN,
 			ADDR_NONE),
+EX(CMD_const,		"const",	ex_const,
+			EXTRA|NOTRLCOM|SBOXOK|CMDWIN,
+			ADDR_NONE),
 EX(CMD_copen,		"copen",	ex_copen,
 			RANGE|COUNT|TRLBAR,
 			ADDR_OTHER),
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -28,6 +28,7 @@ void *call_func_retstr(char_u *func, int
 void *call_func_retlist(char_u *func, int argc, typval_T *argv);
 int eval_foldexpr(char_u *arg, int *cp);
 void ex_let(exarg_T *eap);
+void ex_const(exarg_T *eap);
 void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first);
 char_u *get_lval(char_u *name, typval_T *rettv, lval_T *lp, int unlet, int skip, int flags, int fne_flags);
 void clear_lval(lval_T *lp);
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -87,6 +87,7 @@ NEW_TESTS = \
 	test_comparators \
 	test_compiler \
 	test_conceal \
+	test_const \
 	test_crypt \
 	test_cscope \
 	test_cursor_func \
@@ -312,6 +313,7 @@ NEW_TESTS_RES = \
 	test_command_count.res \
 	test_comparators.res \
 	test_conceal.res \
+	test_const.res \
 	test_crypt.res \
 	test_cscope.res \
 	test_curswant.res \
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_const.vim
@@ -0,0 +1,228 @@
+" Test for :const
+
+func s:noop()
+endfunc
+
+func Test_define_var_with_lock()
+    const i = 1
+    const f = 1.1
+    const s = 'vim'
+    const F = funcref('s:noop')
+    const l = [1, 2, 3]
+    const d = {'foo': 10}
+    if has('channel')
+      const j = test_null_job()
+      const c = test_null_channel()
+    endif
+    const b = v:true
+    const n = v:null
+    const bl = 0zC0FFEE
+    const here =<< trim EOS
+    hello
+    EOS
+
+    call assert_fails('let i = 1', 'E741:')
+    call assert_fails('let f = 1.1', 'E741:')
+    call assert_fails('let s = "vim"', 'E741:')
+    call assert_fails('let F = funcref("s:noop")', 'E741:')
+    call assert_fails('let l = [1, 2, 3]', 'E741:')
+    call assert_fails('let d = {"foo": 10}', 'E741:')
+    if has('channel')
+      call assert_fails('let j = test_null_job()', 'E741:')
+      call assert_fails('let c = test_null_channel()', 'E741:')
+    endif
+    call assert_fails('let b = v:true', 'E741:')
+    call assert_fails('let n = v:null', 'E741:')
+    call assert_fails('let bl = 0zC0FFEE', 'E741:')
+    call assert_fails('let here = "xxx"', 'E741:')
+
+    " Unlet
+    unlet i
+    unlet f
+    unlet s
+    unlet F
+    unlet l
+    unlet d
+    unlet j
+    unlet c
+    unlet b
+    unlet n
+    unlet bl
+    unlet here
+endfunc
+
+func Test_define_l_var_with_lock()
+    " With l: prefix
+    const l:i = 1
+    const l:f = 1.1
+    const l:s = 'vim'
+    const l:F = funcref('s:noop')
+    const l:l = [1, 2, 3]
+    const l:d = {'foo': 10}
+    if has('channel')
+      const l:j = test_null_job()
+      const l:c = test_null_channel()
+    endif
+    const l:b = v:true
+    const l:n = v:null
+    const l:bl = 0zC0FFEE
+    const l:here =<< trim EOS
+    hello
+    EOS
+
+    call assert_fails('let l:i = 1', 'E741:')
+    call assert_fails('let l:f = 1.1', 'E741:')
+    call assert_fails('let l:s = "vim"', 'E741:')
+    call assert_fails('let l:F = funcref("s:noop")', 'E741:')
+    call assert_fails('let l:l = [1, 2, 3]', 'E741:')
+    call assert_fails('let l:d = {"foo": 10}', 'E741:')
+    if has('channel')
+      call assert_fails('let l:j = test_null_job()', 'E741:')
+      call assert_fails('let l:c = test_null_channel()', 'E741:')
+    endif
+    call assert_fails('let l:b = v:true', 'E741:')
+    call assert_fails('let l:n = v:null', 'E741:')
+    call assert_fails('let l:bl = 0zC0FFEE', 'E741:')
+    call assert_fails('let l:here = "xxx"', 'E741:')
+
+    " Unlet
+    unlet l:i
+    unlet l:f
+    unlet l:s
+    unlet l:F
+    unlet l:l
+    unlet l:d
+    unlet l:j
+    unlet l:c
+    unlet l:b
+    unlet l:n
+    unlet l:bl
+    unlet l:here
+endfunc
+
+func Test_define_script_var_with_lock()
+    const s:x = 0
+    call assert_fails('let s:x = 1', 'E741:')
+    unlet s:x
+endfunc
+
+func Test_descructuring_with_lock()
+    const [a, b, c] = [1, 1.1, 'vim']
+
+    call assert_fails('let a = 1', 'E741:')
+    call assert_fails('let b = 1.1', 'E741:')
+    call assert_fails('let c = "vim"', 'E741:')
+
+    const [d; e] = [1, 1.1, 'vim']
+    call assert_fails('let d = 1', 'E741:')
+    call assert_fails('let e = [2.2, "a"]', 'E741:')
+endfunc
+
+func Test_cannot_modify_existing_variable()
+    let i = 1
+    let f = 1.1
+    let s = 'vim'
+    let F = funcref('s:noop')
+    let l = [1, 2, 3]
+    let d = {'foo': 10}
+    if has('channel')
+      let j = test_null_job()
+      let c = test_null_channel()
+    endif
+    let b = v:true
+    let n = v:null
+    let bl = 0zC0FFEE
+
+    call assert_fails('const i = 1', 'E995:')
+    call assert_fails('const f = 1.1', 'E995:')
+    call assert_fails('const s = "vim"', 'E995:')
+    call assert_fails('const F = funcref("s:noop")', 'E995:')
+    call assert_fails('const l = [1, 2, 3]', 'E995:')
+    call assert_fails('const d = {"foo": 10}', 'E995:')
+    if has('channel')
+      call assert_fails('const j = test_null_job()', 'E995:')
+      call assert_fails('const c = test_null_channel()', 'E995:')
+    endif
+    call assert_fails('const b = v:true', 'E995:')
+    call assert_fails('const n = v:null', 'E995:')
+    call assert_fails('const bl = 0zC0FFEE', 'E995:')
+    call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:')
+
+    const i2 = 1
+    const f2 = 1.1
+    const s2 = 'vim'
+    const F2 = funcref('s:noop')
+    const l2 = [1, 2, 3]
+    const d2 = {'foo': 10}
+    if has('channel')
+      const j2 = test_null_job()
+      const c2 = test_null_channel()
+    endif
+    const b2 = v:true
+    const n2 = v:null
+    const bl2 = 0zC0FFEE
+
+    call assert_fails('const i2 = 1', 'E995:')
+    call assert_fails('const f2 = 1.1', 'E995:')
+    call assert_fails('const s2 = "vim"', 'E995:')
+    call assert_fails('const F2 = funcref("s:noop")', 'E995:')
+    call assert_fails('const l2 = [1, 2, 3]', 'E995:')
+    call assert_fails('const d2 = {"foo": 10}', 'E995:')
+    if has('channel')
+      call assert_fails('const j2 = test_null_job()', 'E995:')
+      call assert_fails('const c2 = test_null_channel()', 'E995:')
+    endif
+    call assert_fails('const b2 = v:true', 'E995:')
+    call assert_fails('const n2 = v:null', 'E995:')
+    call assert_fails('const bl2 = 0zC0FFEE', 'E995:')
+    call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
+endfunc
+
+func Test_const_with_index_access()
+    let l = [1, 2, 3]
+    call assert_fails('const l[0] = 4', 'E996:')
+    call assert_fails('const l[0:1] = [1, 2]', 'E996:')
+
+    let d = {'aaa': 0}
+    call assert_fails("const d['aaa'] = 4", 'E996:')
+    call assert_fails("const d.aaa = 4", 'E996:')
+endfunc
+
+func Test_const_with_compound_assign()
+    let i = 0
+    call assert_fails('const i += 4', 'E995:')
+    call assert_fails('const i -= 4', 'E995:')
+    call assert_fails('const i *= 4', 'E995:')
+    call assert_fails('const i /= 4', 'E995:')
+    call assert_fails('const i %= 4', 'E995:')
+
+    let s = 'a'
+    call assert_fails('const s .= "b"', 'E995:')
+
+    let [a, b, c] = [1, 2, 3]
+    call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:')
+
+    let [d; e] = [1, 2, 3]
+    call assert_fails('const [d; e] += [4, 5, 6]', 'E995:')
+endfunc
+
+func Test_const_with_special_variables()
+    call assert_fails('const $FOO = "hello"', 'E996:')
+    call assert_fails('const @a = "hello"', 'E996:')
+    call assert_fails('const &filetype = "vim"', 'E996:')
+    call assert_fails('const &l:filetype = "vim"', 'E996:')
+    call assert_fails('const &g:encoding = "utf-8"', 'E996:')
+endfunc
+
+func Test_lock_depth_is_1()
+    const l = [1, 2, 3]
+    const d = {'foo': 10}
+
+    " Modify list
+    call add(l, 4)
+    let l[0] = 42
+
+    " Modify dict
+    let d['bar'] = 'hello'
+    let d.foo = 44
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1539,
+/**/
     1538,
 /**/
     1537,