patch 8.2.4698: Vim9: script variable has no flag that it was set

Problem:    Vim9: script variable has no flag that it was set.
Solution:   Add a flag that it was set, to avoid giving it a value when used.
            (closes #10088)
diff --git a/src/evalvars.c b/src/evalvars.c
index c1ec5b0..0ee4b08 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -2828,13 +2828,18 @@
 	}
 	else if (rettv != NULL)
 	{
+	    svar_T  *sv = NULL;
+	    int	    was_assigned = FALSE;
+
 	    if (ht != NULL && ht == get_script_local_ht()
 		    && tv != &SCRIPT_SV(current_sctx.sc_sid)->sv_var.di_tv)
 	    {
-		svar_T *sv = find_typval_in_script(tv, 0, TRUE);
-
+		sv = find_typval_in_script(tv, 0, TRUE);
 		if (sv != NULL)
+		{
 		    type = sv->sv_type;
+		    was_assigned = sv->sv_flags & SVFLAG_ASSIGNED;
+		}
 	    }
 
 	    // If a list or dict variable wasn't initialized and has meaningful
@@ -2843,7 +2848,7 @@
 	    if (ht != &globvarht)
 	    {
 		if (tv->v_type == VAR_DICT && tv->vval.v_dict == NULL
-			  && ((type != NULL && type != &t_dict_empty)
+					    && ((type != NULL && !was_assigned)
 							  || !in_vim9script()))
 		{
 		    tv->vval.v_dict = dict_alloc();
@@ -2851,10 +2856,12 @@
 		    {
 			++tv->vval.v_dict->dv_refcount;
 			tv->vval.v_dict->dv_type = alloc_type(type);
+			if (sv != NULL)
+			    sv->sv_flags |= SVFLAG_ASSIGNED;
 		    }
 		}
 		else if (tv->v_type == VAR_LIST && tv->vval.v_list == NULL
-				    && ((type != NULL && type != &t_list_empty)
+					    && ((type != NULL && !was_assigned)
 							  || !in_vim9script()))
 		{
 		    tv->vval.v_list = list_alloc();
@@ -2862,15 +2869,21 @@
 		    {
 			++tv->vval.v_list->lv_refcount;
 			tv->vval.v_list->lv_type = alloc_type(type);
+			if (sv != NULL)
+			    sv->sv_flags |= SVFLAG_ASSIGNED;
 		    }
 		}
 		else if (tv->v_type == VAR_BLOB && tv->vval.v_blob == NULL
-				    && ((type != NULL && type != &t_blob_null)
+					    && ((type != NULL && !was_assigned)
 							  || !in_vim9script()))
 		{
 		    tv->vval.v_blob = blob_alloc();
 		    if (tv->vval.v_blob != NULL)
+		    {
 			++tv->vval.v_blob->bv_refcount;
+			if (sv != NULL)
+			    sv->sv_flags |= SVFLAG_ASSIGNED;
+		    }
 		}
 	    }
 	    copy_tv(tv, rettv);
@@ -3587,6 +3600,7 @@
 			goto failed;
 		    if (type == NULL)
 			type = sv->sv_type;
+		    sv->sv_flags |= SVFLAG_ASSIGNED;
 		}
 	    }
 
diff --git a/src/structs.h b/src/structs.h
index 9f1e11d..5a76a59 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1807,6 +1807,10 @@
 #define HIKEY2SAV(p)  ((sallvar_T *)(p - offsetof(sallvar_T, sav_key)))
 #define HI2SAV(hi)     HIKEY2SAV((hi)->hi_key)
 
+#define SVFLAG_TYPE_ALLOCATED	1  // call free_type() for "sv_type"
+#define SVFLAG_EXPORTED		2  // "export let var = val"
+#define SVFLAG_ASSIGNED		4  // assigned a value
+
 /*
  * Entry for "sn_var_vals".  Used for script-local variables.
  */
@@ -1814,9 +1818,8 @@
     char_u	*sv_name;	// points into "sn_all_vars" di_key
     typval_T	*sv_tv;		// points into "sn_vars" or "sn_all_vars" di_tv
     type_T	*sv_type;
-    int		sv_type_allocated;  // call free_type() for sv_type
+    int		sv_flags;	// SVFLAG_ values above
     int		sv_const;	// 0, ASSIGN_CONST or ASSIGN_FINAL
-    int		sv_export;	// "export let var = val"
 };
 
 typedef struct {
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 57d205c..7e382c2 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -740,6 +740,7 @@
 enddef
 
 def Test_extend_list()
+  # using uninitilaized list assigns empty list
   var lines =<< trim END
       var l1: list<number>
       var l2 = l1
@@ -757,7 +758,7 @@
   END
   v9.CheckDefAndScriptSuccess(lines)
 
-  # appending to NULL list from a function
+  # appending to uninitialzed list from a function works
   lines =<< trim END
       vim9script
       var list: list<string>
@@ -779,13 +780,30 @@
   END
   v9.CheckScriptSuccess(lines)
 
+  # initialized to null, with type, does not default to empty list
   lines =<< trim END
       vim9script
       var l: list<string> = test_null_list()
       extend(l, ['x'])
-      assert_equal(['x'], l)
   END
-  v9.CheckScriptSuccess(lines)
+  v9.CheckScriptFailure(lines, 'E1134:', 3)
+
+  # initialized to null, without type, does not default to empty list
+  lines =<< trim END
+      vim9script
+      var l = null_list
+      extend(l, ['x'])
+  END
+  v9.CheckScriptFailure(lines, 'E1134:', 3)
+
+  # assigned null, does not default to empty list
+  lines =<< trim END
+      vim9script
+      var l: list<string>
+      l = null_list
+      extend(l, ['x'])
+  END
+  v9.CheckScriptFailure(lines, 'E1134:', 4)
 
   lines =<< trim END
       vim9script
@@ -838,9 +856,8 @@
       vim9script
       var d: dict<string> = test_null_dict()
       extend(d, {a: 'x'})
-      assert_equal({a: 'x'}, d)
   END
-  v9.CheckScriptSuccess(lines)
+  v9.CheckScriptFailure(lines, 'E1133:', 3)
 
   lines =<< trim END
       vim9script
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index b20ff14..9e60666 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -153,13 +153,21 @@
   END
   v9.CheckDefExecFailure(lines, 'E1130:', 2)
 
-  # Getting variable with NULL list allocates a new list at script level
+  # Getting an uninitialized variable allocates a new list at script level
+  lines =<< trim END
+      vim9script
+      var l: list<number>
+      add(l, 123)
+  END
+  v9.CheckScriptSuccess(lines)
+
+  # Adding to a variable set to a NULL list fails
   lines =<< trim END
       vim9script
       var l: list<number> = test_null_list()
       add(l, 123)
   END
-  v9.CheckScriptSuccess(lines)
+  v9.CheckScriptFailure(lines, 'E1130:', 3)
 
   lines =<< trim END
       vim9script
diff --git a/src/version.c b/src/version.c
index 9e1b5c7..23b2a1e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4698,
+/**/
     4697,
 /**/
     4696,
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 99135ea..0e8a1dd 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1514,7 +1514,8 @@
 	return NULL;
     }
 
-    if (!sv->sv_export && sref->sref_sid != current_sctx.sc_sid)
+    if ((sv->sv_flags & SVFLAG_EXPORTED) == 0
+				      && sref->sref_sid != current_sctx.sc_sid)
     {
 	if (dfunc != NULL)
 	    semsg(_(e_item_not_exported_in_script_str), sv->sv_name);
@@ -2952,7 +2953,7 @@
 			    {
 				sv = ((svar_T *)SCRIPT_ITEM(sid)
 						  ->sn_var_vals.ga_data) + idx;
-				if (!sv->sv_export)
+				if ((sv->sv_flags & SVFLAG_EXPORTED) == 0)
 				{
 				    SOURCING_LNUM = iptr->isn_lnum;
 				    semsg(_(e_item_not_exported_in_script_str),
@@ -3117,7 +3118,7 @@
 				svar_T	*sv = ((svar_T *)SCRIPT_ITEM(sid)
 						  ->sn_var_vals.ga_data) + idx;
 
-				if (!sv->sv_export)
+				if ((sv->sv_flags & SVFLAG_EXPORTED) == 0)
 				{
 				    semsg(_(e_item_not_exported_in_script_str),
 									 name);
diff --git a/src/vim9script.c b/src/vim9script.c
index adb01e8..68b8388 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -334,7 +334,7 @@
     {
 	svar_T    *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
 
-	if (sv->sv_type_allocated)
+	if (sv->sv_flags & SVFLAG_TYPE_ALLOCATED)
 	    free_type(sv->sv_type);
     }
     ga_clear(&si->sn_var_vals);
@@ -721,7 +721,7 @@
     {
 	sv = ((svar_T *)script->sn_var_vals.ga_data) + idx;
 	*ufunc = NULL;
-	if (!sv->sv_export)
+	if ((sv->sv_flags & SVFLAG_EXPORTED) == 0)
 	{
 	    if (verbose)
 		semsg(_(e_item_not_exported_in_script_str), name);
@@ -871,7 +871,7 @@
  * with a hashtable) and sn_var_vals (lookup by index).
  * When "create" is TRUE this is a new variable, otherwise find and update an
  * existing variable.
- * "flags" can have ASSIGN_FINAL or ASSIGN_CONST.
+ * "flags" can have ASSIGN_FINAL, ASSIGN_CONST or ASSIGN_INIT.
  * When "*type" is NULL use "tv" for the type and update "*type".  If
  * "do_member" is TRUE also use the member type, otherwise use "any".
  */
@@ -938,7 +938,9 @@
 	    sv->sv_tv = &di->di_tv;
 	    sv->sv_const = (flags & ASSIGN_FINAL) ? ASSIGN_FINAL
 				   : (flags & ASSIGN_CONST) ? ASSIGN_CONST : 0;
-	    sv->sv_export = is_export;
+	    sv->sv_flags = is_export ? SVFLAG_EXPORTED : 0;
+	    if ((flags & ASSIGN_INIT) == 0)
+		sv->sv_flags |= SVFLAG_ASSIGNED;
 	    newsav->sav_var_vals_idx = si->sn_var_vals.ga_len;
 	    ++si->sn_var_vals.ga_len;
 	    STRCPY(&newsav->sav_key, name);
@@ -970,7 +972,7 @@
 	    // "var b: blob = null_blob" has a different type.
 	    *type = &t_blob_null;
 	}
-	if (sv->sv_type_allocated)
+	if (sv->sv_flags & SVFLAG_TYPE_ALLOCATED)
 	    free_type(sv->sv_type);
 	if (*type != NULL && ((*type)->tt_type == VAR_FUNC
 					   || (*type)->tt_type == VAR_PARTIAL))
@@ -979,12 +981,12 @@
 	    // function is freed, but the script variable may keep the type.
 	    // Make a copy to avoid using freed memory.
 	    sv->sv_type = alloc_type(*type);
-	    sv->sv_type_allocated = TRUE;
+	    sv->sv_flags |= SVFLAG_TYPE_ALLOCATED;
 	}
 	else
 	{
 	    sv->sv_type = *type;
-	    sv->sv_type_allocated = FALSE;
+	    sv->sv_flags &= ~SVFLAG_TYPE_ALLOCATED;
 	}
     }