patch 8.2.1685: Vim9: cannot declare a constant value

Problem:    Vim9: cannot declare a constant value.
Solution:   Introduce ":const!".
diff --git a/src/errors.h b/src/errors.h
index c76afbc..badbabd 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -258,4 +258,12 @@
 	INIT(= N_("E1116: assert_fails() fifth argument must be a string"));
 EXTERN char e_cannot_use_bang_with_nested_def[]
 	INIT(= N_("E1117: Cannot use ! with nested :def"));
+EXTERN char e_cannot_change_list[]
+	INIT(= N_("E1118: Cannot change list"));
+EXTERN char e_cannot_change_list_item[]
+	INIT(= N_("E1119: Cannot change list item"));
+EXTERN char e_cannot_change_dict[]
+	INIT(= N_("E1120: Cannot change dict"));
+EXTERN char e_cannot_change_dict_item[]
+	INIT(= N_("E1121: Cannot change dict item"));
 #endif
diff --git a/src/eval.c b/src/eval.c
index 18f3a5b..79c5488 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1200,7 +1200,7 @@
     char_u	*endp,
     typval_T	*rettv,
     int		copy,
-    int		flags,    // LET_IS_CONST and/or LET_NO_COMMAND
+    int		flags,    // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
     char_u	*op)
 {
     int		cc;
diff --git a/src/evalvars.c b/src/evalvars.c
index bcc425d..1c40f41 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -173,7 +173,6 @@
 static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op);
 static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
 static int do_lock_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
-static void item_lock(typval_T *tv, int deep, int lock, int check_refcount);
 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);
@@ -709,6 +708,8 @@
     // detect Vim9 assignment without ":let" or ":const"
     if (eap->arg == eap->cmd)
 	flags |= LET_NO_COMMAND;
+    if (eap->forceit)
+	flags |= LET_FORCEIT;
 
     argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
     if (argend == NULL)
@@ -859,7 +860,7 @@
     int		copy,		// copy values from "tv", don't move
     int		semicolon,	// from skip_var_list()
     int		var_count,	// from skip_var_list()
-    int		flags,		// LET_IS_CONST and/or LET_NO_COMMAND
+    int		flags,		// LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
     char_u	*op)
 {
     char_u	*arg = arg_start;
@@ -1214,7 +1215,7 @@
     char_u	*arg,		// points to variable name
     typval_T	*tv,		// value to assign to variable
     int		copy,		// copy value from "tv"
-    int		flags,		// LET_IS_CONST and/or LET_NO_COMMAND
+    int		flags,		// LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
     char_u	*endchars,	// valid chars after variable name  or NULL
     char_u	*op)		// "+", "-", "."  or NULL
 {
@@ -1741,7 +1742,7 @@
  * When "check_refcount" is TRUE do not lock a list or dict with a reference
  * count larger than 1.
  */
-    static void
+    void
 item_lock(typval_T *tv, int deep, int lock, int check_refcount)
 {
     static int	recurse = 0;
@@ -2937,7 +2938,7 @@
     type_T	*type,
     typval_T	*tv_arg,
     int		copy,	    // make copy of value in "tv"
-    int		flags)	    // LET_IS_CONST and/or LET_NO_COMMAND
+    int		flags)	    // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
 {
     typval_T	*tv = tv_arg;
     typval_T	bool_tv;
@@ -3124,8 +3125,8 @@
 	init_tv(tv);
     }
 
-    // ":const var = val" locks the value, but not in Vim9 script
-    if ((flags & LET_IS_CONST) && !vim9script)
+    // ":const var = val" locks the value; in Vim9 script only with ":const!"
+    if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT)))
 	// Like :lockvar! name: lock the value and what it contains, but only
 	// if the reference count is up to one.  That locks only literal
 	// values.
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index 3977c4d..8ef9eb4 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -398,7 +398,7 @@
 	EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_NONE),
 EXCMD(CMD_const,	"const",	ex_let,
-	EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
+	EX_EXTRA|EX_BANG|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_NONE),
 EXCMD(CMD_copen,	"copen",	ex_copen,
 	EX_RANGE|EX_COUNT|EX_TRLBAR,
diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro
index f304872..010e630 100644
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -23,6 +23,7 @@
 void ex_lockvar(exarg_T *eap);
 void ex_unletlock(exarg_T *eap, char_u *argstart, int deep, int glv_flags, int (*callback)(lval_T *, char_u *, exarg_T *, int, void *), void *cookie);
 int do_unlet(char_u *name, int forceit);
+void item_lock(typval_T *tv, int deep, int lock, int check_refcount);
 void del_menutrans_vars(void);
 char_u *get_user_var_name(expand_T *xp, int idx);
 char *get_var_special_name(int nr);
@@ -65,7 +66,7 @@
 void vars_clear(hashtab_T *ht);
 void vars_clear_ext(hashtab_T *ht, int free_val);
 void set_var(char_u *name, typval_T *tv, int copy);
-void set_var_const(char_u *name, type_T *type, typval_T *tv, int copy, int flags);
+void set_var_const(char_u *name, type_T *type, typval_T *tv_arg, int copy, int flags);
 int var_check_ro(int flags, char_u *name, int use_gettext);
 int var_check_fixed(int flags, char_u *name, int use_gettext);
 int var_wrong_func_name(char_u *name, int new_var);
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index 6358181..904ee63 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -828,10 +828,50 @@
   let lines =<< trim END
     const list = [1, 2, 3]
     list[0] = 4
+    list->assert_equal([4, 2, 3])
+    const! other = [5, 6, 7]
+    other->assert_equal([5, 6, 7])
   END
   CheckDefAndScriptSuccess(lines)
 enddef
 
+def Test_const_bang()
+  let lines =<< trim END
+      const! var = 234
+      var = 99
+  END
+  CheckDefExecFailure(lines, 'E1018:', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E46:', 3)
+
+  lines =<< trim END
+      const! ll = [2, 3, 4]
+      ll[0] = 99
+  END
+  CheckDefExecFailure(lines, 'E1119:', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+
+  lines =<< trim END
+      const! ll = [2, 3, 4]
+      ll[3] = 99
+  END
+  CheckDefExecFailure(lines, 'E1118:', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E684:', 3)
+
+  lines =<< trim END
+      const! dd = #{one: 1, two: 2}
+      dd["one"] = 99
+  END
+  CheckDefExecFailure(lines, 'E1121:', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+
+  lines =<< trim END
+      const! dd = #{one: 1, two: 2}
+      dd["three"] = 99
+  END
+  CheckDefExecFailure(lines, 'E1120:')
+  CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+enddef
+
 def Test_range_no_colon()
   CheckDefFailure(['%s/a/b/'], 'E1050:')
   CheckDefFailure(['+ s/a/b/'], 'E1050:')
diff --git a/src/version.c b/src/version.c
index 67ccfb6..f958df9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1685,
+/**/
     1684,
 /**/
     1683,
diff --git a/src/vim.h b/src/vim.h
index 60a565b..208128e 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2136,7 +2136,8 @@
 
 // Flags for assignment functions.
 #define LET_IS_CONST	1   // ":const"
-#define LET_NO_COMMAND	2   // "var = expr" without ":let" or ":const"
+#define LET_FORCEIT	2   // ":const!" (LET_IS_CONST is also set)
+#define LET_NO_COMMAND	4   // "var = expr" without ":let" or ":const"
 
 #include "ex_cmds.h"	    // Ex command defines
 #include "spell.h"	    // spell checking stuff
diff --git a/src/vim9.h b/src/vim9.h
index 367c05c..310b59e 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -58,6 +58,8 @@
     ISN_UNLET,		// unlet variable isn_arg.unlet.ul_name
     ISN_UNLETENV,	// unlet environment variable isn_arg.unlet.ul_name
 
+    ISN_LOCKCONST,	// lock constant value
+
     // constants
     ISN_PUSHNR,		// push number isn_arg.number
     ISN_PUSHBOOL,	// push bool value isn_arg.number
diff --git a/src/vim9compile.c b/src/vim9compile.c
index dd3eff4..103e696 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1109,6 +1109,20 @@
 }
 
 /*
+ * Generate an ISN_LOCKCONST instruction.
+ */
+    static int
+generate_LOCKCONST(cctx_T *cctx)
+{
+    isn_T	*isn;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr(cctx, ISN_LOCKCONST)) == NULL)
+	return FAIL;
+    return OK;
+}
+
+/*
  * Generate an ISN_LOADS instruction.
  */
     static int
@@ -4342,7 +4356,7 @@
     ufunc_T	*ufunc;
     int		r;
 
-    if (*name_start == '!')
+    if (eap->forceit)
     {
 	emsg(_(e_cannot_use_bang_with_nested_def));
 	return NULL;
@@ -5232,6 +5246,11 @@
 	}
 	else
 	{
+	    if (is_decl && eap->forceit && cmdidx == CMD_const
+		    && (dest == dest_script || dest == dest_local))
+		// ":const! var": lock the value, but not referenced variables
+		generate_LOCKCONST(cctx);
+
 	    switch (dest)
 	    {
 		case dest_option:
@@ -6362,13 +6381,8 @@
     char_u	*line = arg;
     linenr_T	lnum;
     char	*errormsg;
-    int		above = FALSE;
+    int		above = eap->forceit;
 
-    if (*arg == '!')
-    {
-	above = TRUE;
-	line = skipwhite(arg + 1);
-    }
     eap->regname = *line;
 
     if (eap->regname == '=')
@@ -6411,7 +6425,7 @@
 
     if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE)
     {
-	long	argt = excmd_get_argt(eap->cmdidx);
+	long	argt = eap->argt;
 	int	usefilter = FALSE;
 
 	has_expr = argt & (EX_XFILE | EX_EXPAND);
@@ -6870,8 +6884,6 @@
 	    }
 	}
 
-	p = skipwhite(p);
-
 	if (cctx.ctx_had_return
 		&& ea.cmdidx != CMD_elseif
 		&& ea.cmdidx != CMD_else
@@ -6886,6 +6898,18 @@
 	    goto erret;
 	}
 
+	p = skipwhite(p);
+	if (ea.cmdidx != CMD_SIZE
+			    && ea.cmdidx != CMD_write && ea.cmdidx != CMD_read)
+	{
+	    ea.argt = excmd_get_argt(ea.cmdidx);
+	    if ((ea.argt & EX_BANG) && *p == '!')
+	    {
+		ea.forceit = TRUE;
+		p = skipwhite(p + 1);
+	    }
+	}
+
 	switch (ea.cmdidx)
 	{
 	    case CMD_def:
@@ -7309,6 +7333,7 @@
 	case ISN_LOADTDICT:
 	case ISN_LOADV:
 	case ISN_LOADWDICT:
+	case ISN_LOCKCONST:
 	case ISN_MEMBER:
 	case ISN_NEGATENR:
 	case ISN_NEWDICT:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index c2bd857..cd6990f 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -678,6 +678,21 @@
 }
 
 /*
+ * Check if "lock" is VAR_LOCKED or VAR_FIXED.  If so give an error and return
+ * TRUE.
+ */
+    static int
+error_if_locked(int lock, char *error)
+{
+    if (lock & (VAR_LOCKED | VAR_FIXED))
+    {
+	emsg(_(error));
+	return TRUE;
+    }
+    return FALSE;
+}
+
+/*
  * Store "tv" in variable "name".
  * This is for s: and g: variables.
  */
@@ -1455,12 +1470,12 @@
 		    typval_T	*tv_list = STACK_TV_BOT(-1);
 		    list_T	*list = tv_list->vval.v_list;
 
+		    SOURCING_LNUM = iptr->isn_lnum;
 		    if (lidx < 0 && list->lv_len + lidx >= 0)
 			// negative index is relative to the end
 			lidx = list->lv_len + lidx;
 		    if (lidx < 0 || lidx > list->lv_len)
 		    {
-			SOURCING_LNUM = iptr->isn_lnum;
 			semsg(_(e_listidx), lidx);
 			goto on_error;
 		    }
@@ -1469,12 +1484,18 @@
 		    {
 			listitem_T *li = list_find(list, lidx);
 
+			if (error_if_locked(li->li_tv.v_lock,
+						    e_cannot_change_list_item))
+			    goto failed;
 			// overwrite existing list item
 			clear_tv(&li->li_tv);
 			li->li_tv = *tv;
 		    }
 		    else
 		    {
+			if (error_if_locked(list->lv_lock,
+							 e_cannot_change_list))
+			    goto failed;
 			// append to list, only fails when out of memory
 			if (list_append_tv(list, tv) == FAIL)
 			    goto failed;
@@ -1495,9 +1516,9 @@
 		    dict_T	*dict = tv_dict->vval.v_dict;
 		    dictitem_T	*di;
 
+		    SOURCING_LNUM = iptr->isn_lnum;
 		    if (dict == NULL)
 		    {
-			SOURCING_LNUM = iptr->isn_lnum;
 			emsg(_(e_dictionary_not_set));
 			goto on_error;
 		    }
@@ -1507,12 +1528,18 @@
 		    di = dict_find(dict, key, -1);
 		    if (di != NULL)
 		    {
+			if (error_if_locked(di->di_tv.v_lock,
+						    e_cannot_change_dict_item))
+			    goto failed;
 			// overwrite existing value
 			clear_tv(&di->di_tv);
 			di->di_tv = *tv;
 		    }
 		    else
 		    {
+			if (error_if_locked(dict->dv_lock,
+							 e_cannot_change_dict))
+			    goto failed;
 			// add to dict, only fails when out of memory
 			if (dict_add_tv(dict, (char *)key, tv) == FAIL)
 			    goto failed;
@@ -1603,6 +1630,10 @@
 		vim_unsetenv(iptr->isn_arg.unlet.ul_name);
 		break;
 
+	    case ISN_LOCKCONST:
+		item_lock(STACK_TV_BOT(-1), 100, TRUE, TRUE);
+		break;
+
 	    // create a list from items on the stack; uses a single allocation
 	    // for the list header and the items
 	    case ISN_NEWLIST:
@@ -3025,6 +3056,9 @@
 			iptr->isn_arg.unlet.ul_forceit ? "!" : "",
 			iptr->isn_arg.unlet.ul_name);
 		break;
+	    case ISN_LOCKCONST:
+		smsg("%4d LOCKCONST", current);
+		break;
 	    case ISN_NEWLIST:
 		smsg("%4d NEWLIST size %lld", current,
 					    (long long)(iptr->isn_arg.number));