patch 8.2.4526: Vim9: cannot set variables to a null value

Problem:    Vim9: cannot set variables to a null value.
Solution:   Add null_list, null_job, etc.
diff --git a/src/eval.c b/src/eval.c
index c4c403c..a48b8f8 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -943,6 +943,7 @@
 		    type_list = &SCRIPT_ITEM(current_sctx.sc_sid)->sn_type_list;
 		else
 		{
+		    // TODO: should we give an error here?
 		    type_list = &tmp_type_list;
 		    ga_init2(type_list, sizeof(type_T), 10);
 		}
@@ -3483,6 +3484,100 @@
 }
 
 /*
+ * Check for a predefined value "true", "false" and "null.*".
+ * Return OK when recognized.
+ */
+    int
+handle_predefined(char_u *s, int len, typval_T *rettv)
+{
+    switch (len)
+    {
+	case 4: if (STRNCMP(s, "true", 4) == 0)
+		{
+		    rettv->v_type = VAR_BOOL;
+		    rettv->vval.v_number = VVAL_TRUE;
+		    return OK;
+		}
+		if (STRNCMP(s, "null", 4) == 0)
+		{
+		    rettv->v_type = VAR_SPECIAL;
+		    rettv->vval.v_number = VVAL_NULL;
+		    return OK;
+		}
+		break;
+	case 5: if (STRNCMP(s, "false", 5) == 0)
+		{
+		    rettv->v_type = VAR_BOOL;
+		    rettv->vval.v_number = VVAL_FALSE;
+		    return OK;
+		}
+		break;
+#ifdef FEAT_JOB_CHANNEL
+	case 8: if (STRNCMP(s, "null_job", 8) == 0)
+		{
+		    rettv->v_type = VAR_JOB;
+		    rettv->vval.v_job = NULL;
+		    return OK;
+		}
+		break;
+#endif
+	case 9:
+		if (STRNCMP(s, "null_", 5) != 0)
+		    break;
+		if (STRNCMP(s + 5, "list", 4) == 0)
+		{
+		    rettv->v_type = VAR_LIST;
+		    rettv->vval.v_list = NULL;
+		    return OK;
+		}
+		if (STRNCMP(s + 5, "dict", 4) == 0)
+		{
+		    rettv->v_type = VAR_DICT;
+		    rettv->vval.v_dict = NULL;
+		    return OK;
+		}
+		if (STRNCMP(s + 5, "blob", 4) == 0)
+		{
+		    rettv->v_type = VAR_BLOB;
+		    rettv->vval.v_blob = NULL;
+		    return OK;
+		}
+		break;
+	case 11: if (STRNCMP(s, "null_string", 11) == 0)
+		{
+		    rettv->v_type = VAR_STRING;
+		    rettv->vval.v_string = NULL;
+		    return OK;
+		}
+		break;
+	case 12:
+#ifdef FEAT_JOB_CHANNEL
+		if (STRNCMP(s, "null_channel", 12) == 0)
+		{
+		    rettv->v_type = VAR_CHANNEL;
+		    rettv->vval.v_channel = NULL;
+		    return OK;
+		}
+#endif
+		if (STRNCMP(s, "null_partial", 12) == 0)
+		{
+		    rettv->v_type = VAR_PARTIAL;
+		    rettv->vval.v_partial = NULL;
+		    return OK;
+		}
+		break;
+	case 13: if (STRNCMP(s, "null_function", 13) == 0)
+		{
+		    rettv->v_type = VAR_FUNC;
+		    rettv->vval.v_string = NULL;
+		    return OK;
+		}
+		break;
+    }
+    return FAIL;
+}
+
+/*
  * Handle sixth level expression:
  *  number		number constant
  *  0zFFFFFFFF		Blob constant
@@ -3757,26 +3852,11 @@
 		ret = FAIL;
 	    else if (evaluate)
 	    {
-		// get the value of "true", "false" or a variable
-		if (len == 4 && vim9script && STRNCMP(s, "true", 4) == 0)
-		{
-		    rettv->v_type = VAR_BOOL;
-		    rettv->vval.v_number = VVAL_TRUE;
-		    ret = OK;
-		}
-		else if (len == 5 && vim9script && STRNCMP(s, "false", 5) == 0)
-		{
-		    rettv->v_type = VAR_BOOL;
-		    rettv->vval.v_number = VVAL_FALSE;
-		    ret = OK;
-		}
-		else if (len == 4 && vim9script && STRNCMP(s, "null", 4) == 0)
-		{
-		    rettv->v_type = VAR_SPECIAL;
-		    rettv->vval.v_number = VVAL_NULL;
-		    ret = OK;
-		}
-		else
+		// get the value of "true", "false", etc. or a variable
+		ret = FAIL;
+		if (vim9script)
+		    ret = handle_predefined(s, len, rettv);
+		if (ret == FAIL)
 		{
 		    name_start = s;
 		    ret = eval_variable(s, len, 0, rettv, NULL,
diff --git a/src/evalvars.c b/src/evalvars.c
index 44882ca..4f7252c 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -999,6 +999,11 @@
     listitem_T	*item;
     typval_T	ltv;
 
+    if (tv->v_type == VAR_VOID)
+    {
+	emsg(_(e_cannot_use_void_value));
+	return FAIL;
+    }
     if (*arg != '[')
     {
 	// ":let var = expr" or ":for var in list"
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 8be5885..49dc65b 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -42,6 +42,7 @@
 void eval_addblob(typval_T *tv1, typval_T *tv2);
 int eval_addlist(typval_T *tv1, typval_T *tv2);
 int eval_leader(char_u **arg, int vim9);
+int handle_predefined(char_u *s, int len, typval_T *rettv);
 int check_can_index(typval_T *rettv, int evaluate, int verbose);
 void f_slice(typval_T *argvars, typval_T *rettv);
 int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, int exclusive, char_u *key, int keylen, int verbose);
diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim
index 40c1701..5f2020d 100644
--- a/src/testdir/test_expr.vim
+++ b/src/testdir/test_expr.vim
@@ -157,12 +157,28 @@
 
 func Test_loop_over_null_list()
   let lines =<< trim END
-      VAR null_list = test_null_list()
-      for i in null_list
+      VAR nulllist = test_null_list()
+      for i in nulllist
         call assert_report('should not get here')
       endfor
   END
   call v9.CheckLegacyAndVim9Success(lines)
+
+  let lines =<< trim END
+      var nulllist = null_list
+      for i in nulllist
+        call assert_report('should not get here')
+      endfor
+  END
+  call v9.CheckDefAndScriptSuccess(lines)
+
+  let lines =<< trim END
+      let nulllist = null_list
+      for i in nulllist
+        call assert_report('should not get here')
+      endfor
+  END
+  call v9.CheckScriptFailure(lines, 'E121: Undefined variable: null_list')
 endfunc
 
 func Test_setreg_null_list()
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 6f64334..abc3a15 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -306,12 +306,44 @@
 enddef
 
 def Test_reserved_name()
-  for name in ['true', 'false', 'null']
+  var more_names = ['null_job', 'null_channel']
+  if !has('job')
+    more_names = []
+  endif
+
+  for name in ['true',
+               'false',
+               'null',
+               'null_blob',
+               'null_dict',
+               'null_function',
+               'null_list',
+               'null_partial',
+               'null_string',
+               ] + more_names
     v9.CheckDefExecAndScriptFailure(['var ' .. name .. ' =  0'], 'E1034:')
     v9.CheckDefExecAndScriptFailure(['var ' .. name .. ': bool'], 'E1034:')
   endfor
 enddef
 
+def Test_null_values()
+  var lines =<< trim END
+      var b: blob = null_blob
+      var dn: dict<number> = null_dict
+      var ds: dict<string> = null_dict
+      var ln: list<number> = null_list
+      var ls: list<string> = null_list
+      var Ff: func(string): string = null_function
+      var Fp: func(number): number = null_partial
+      var s: string = null_string
+      if has('job')
+        var j: job = null_job
+        var c: channel = null_channel
+      endif
+  END
+  v9.CheckDefAndScriptSuccess(lines)
+enddef
+
 def Test_skipped_assignment()
   var lines =<< trim END
       for x in []
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 028e1a7..26ecdde 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -415,6 +415,58 @@
         res)
 enddef
 
+if has('job')
+  def s:StoreNull()
+    var ss = null_string
+    var bb = null_blob
+    var dd = null_dict
+    var ll = null_list
+    var Ff = null_function
+    var Pp = null_partial
+    var jj = null_job
+    var cc = null_channel
+  enddef
+
+  def Test_disassemble_assign_null()
+    var res = execute('disass s:StoreNull')
+    assert_match('<SNR>\d*_StoreNull\_s*' ..
+          'var ss = null_string\_s*' ..
+          '\d\+ PUSHS "\[NULL\]"\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var bb = null_blob\_s*' ..
+          '\d\+ PUSHBLOB 0z\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var dd = null_dict\_s*' ..
+          '\d\+ NEWDICT size 0\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var ll = null_list\_s*' ..
+          '\d\+ NEWLIST size 0\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var Ff = null_function\_s*' ..
+          '\d\+ PUSHFUNC "\[none\]"\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var Pp = null_partial\_s*' ..
+          '\d\+ NEWPARTIAL\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var jj = null_job\_s*' ..
+          '\d\+ PUSHJOB "no process"\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          'var cc = null_channel\_s*' ..
+          '\d\+ PUSHCHANNEL 0\_s*' ..
+          '\d\+ STORE $\d\_s*' ..
+
+          '\d\+ RETURN void',
+          res)
+  enddef
+endif
+
 def s:ScriptFuncStoreIndex()
   var d = {dd: {}}
   d.dd[0] = 0
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 866cf4c..a47dbae 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -3326,7 +3326,7 @@
       var Expr: func(dict<any>): dict<any>
       const Call = Foo(Expr)
   END
-  v9.CheckScriptFailure(lines, 'E1235:')
+  v9.CheckScriptFailure(lines, 'E1031:')
 enddef
 
 def Test_partial_double_nested()
diff --git a/src/version.c b/src/version.c
index 9693ad3..c089b02 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4526,
+/**/
     4525,
 /**/
     4524,
diff --git a/src/vim9.h b/src/vim9.h
index 07dd639..a4fcc9b 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -91,6 +91,7 @@
     ISN_PUSHJOB,	// push channel isn_arg.job
     ISN_NEWLIST,	// push list from stack items, size is isn_arg.number
     ISN_NEWDICT,	// push dict from stack items, size is isn_arg.number
+    ISN_NEWPARTIAL,	// push NULL partial
 
     ISN_AUTOLOAD,	// get item from autoload import, function or variable
 
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 634f24e..6a3c32a 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -403,8 +403,12 @@
     if (ret == OK)
 	return OK;
 
+    // If actual a constant a runtime check makes no sense.  If it's
+    // null_function it is OK.
+    if (actual_is_const && ret == MAYBE && actual == &t_func_unknown)
+	return OK;
+
     // If the actual type can be the expected type add a runtime check.
-    // If it's a constant a runtime check makes no sense.
     if (!actual_is_const && ret == MAYBE && use_typecheck(actual, expected))
     {
 	generate_TYPECHECK(cctx, expected, offset, where.wt_index);
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 56220f8..9f6cc5d 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3440,6 +3440,17 @@
 		}
 		break;
 
+	    // create a partial with NULL value
+	    case ISN_NEWPARTIAL:
+		if (GA_GROW_FAILS(&ectx->ec_stack, 1))
+		    goto theend;
+		++ectx->ec_stack.ga_len;
+		tv = STACK_TV_BOT(-1);
+		tv->v_type = VAR_PARTIAL;
+		tv->v_lock = 0;
+		tv->vval.v_partial = NULL;
+		break;
+
 	    // call a :def function
 	    case ISN_DCALL:
 		SOURCING_LNUM = iptr->isn_lnum;
@@ -5720,6 +5731,9 @@
 		smsg("%s%4d NEWDICT size %lld", pfx, current,
 					    (varnumber_T)(iptr->isn_arg.number));
 		break;
+	    case ISN_NEWPARTIAL:
+		smsg("%s%4d NEWPARTIAL", pfx, current);
+		break;
 
 	    // function call
 	    case ISN_BCALL:
diff --git a/src/vim9expr.c b/src/vim9expr.c
index 1485bd9..19cd55d 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -2107,14 +2107,20 @@
 		    break;
 
 	/*
-	 * "null" constant
+	 * "null" or "null_*" constant
 	 */
-	case 'n':   if (STRNCMP(*arg, "null", 4) == 0
-						   && !eval_isnamec((*arg)[4]))
+	case 'n':   if (STRNCMP(*arg, "null", 4) == 0)
 		    {
-			*arg += 4;
-			rettv->v_type = VAR_SPECIAL;
-			rettv->vval.v_number = VVAL_NULL;
+			char_u  *p = *arg + 4;
+			int	len;
+
+			for (len = 0; eval_isnamec(p[len]); ++len)
+			    ;
+			ret = handle_predefined(*arg, len + 4, rettv);
+			if (ret == FAIL)
+			    ret = NOTDONE;
+			else
+			    *arg += len + 4;
 		    }
 		    else
 			ret = NOTDONE;
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 2c76fad..7fbf529 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -570,6 +570,40 @@
 		generate_PUSHBLOB(cctx, tv->vval.v_blob);
 		tv->vval.v_blob = NULL;
 		break;
+	    case VAR_LIST:
+		if (tv->vval.v_list != NULL)
+		    iemsg("non-empty list constant not supported");
+		generate_NEWLIST(cctx, 0);
+		break;
+	    case VAR_DICT:
+		if (tv->vval.v_dict != NULL)
+		    iemsg("non-empty dict constant not supported");
+		generate_NEWDICT(cctx, 0);
+		break;
+#ifdef FEAT_JOB_CHANNEL
+	    case VAR_JOB:
+		if (tv->vval.v_job != NULL)
+		    iemsg("non-null job constant not supported");
+		generate_PUSHJOB(cctx, NULL);
+		break;
+	    case VAR_CHANNEL:
+		if (tv->vval.v_channel != NULL)
+		    iemsg("non-null channel constant not supported");
+		generate_PUSHCHANNEL(cctx, NULL);
+		break;
+#endif
+	    case VAR_FUNC:
+		if (tv->vval.v_string != NULL)
+		    iemsg("non-null function constant not supported");
+		generate_PUSHFUNC(cctx, NULL, &t_func_unknown);
+		break;
+	    case VAR_PARTIAL:
+		if (tv->vval.v_partial != NULL)
+		    iemsg("non-null partial constant not supported");
+		if (generate_instr_type(cctx, ISN_NEWPARTIAL, &t_func_unknown)
+								       == NULL)
+		    return FAIL;
+		break;
 	    case VAR_STRING:
 		generate_PUSHS(cctx, &tv->vval.v_string);
 		tv->vval.v_string = NULL;
@@ -706,7 +740,7 @@
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if ((isn = generate_instr_type(cctx, ISN_PUSHJOB, &t_channel)) == NULL)
+    if ((isn = generate_instr_type(cctx, ISN_PUSHJOB, &t_job)) == NULL)
 	return FAIL;
     isn->isn_arg.job = job;
 
@@ -2185,6 +2219,7 @@
 	case ISN_NEGATENR:
 	case ISN_NEWDICT:
 	case ISN_NEWLIST:
+	case ISN_NEWPARTIAL:
 	case ISN_OPANY:
 	case ISN_OPFLOAT:
 	case ISN_OPNR:
diff --git a/src/vim9script.c b/src/vim9script.c
index 74f9325..6dce572 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -1062,6 +1062,14 @@
     "true",
     "false",
     "null",
+    "null_blob",
+    "null_dict",
+    "null_function",
+    "null_list",
+    "null_partial",
+    "null_string",
+    "null_channel",
+    "null_job",
     "this",
     NULL
 };
diff --git a/src/vim9type.c b/src/vim9type.c
index dcfc998..fe4dec5 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -567,22 +567,19 @@
 
     if (expected == NULL)
 	return OK;  // didn't expect anything.
+		    //
+    ga_init2(&type_list, sizeof(type_T *), 10);
 
-    // For some values there is no type, assume an error will be given later
-    // for an invalid value.
+    // A null_function and null_partial are special cases, they can be used to
+    // clear a variable.
     if ((actual_tv->v_type == VAR_FUNC && actual_tv->vval.v_string == NULL)
 	    || (actual_tv->v_type == VAR_PARTIAL
 					 && actual_tv->vval.v_partial == NULL))
-    {
-	emsg(_(e_function_reference_is_not_set));
-	return FAIL;
-    }
-
-    ga_init2(&type_list, sizeof(type_T *), 10);
-
-    // When the actual type is list<any> or dict<any> go through the values to
-    // possibly get a more specific type.
-    actual_type = typval2type(actual_tv, get_copyID(), &type_list,
+	actual_type = &t_func_unknown;
+    else
+	// When the actual type is list<any> or dict<any> go through the values
+	// to possibly get a more specific type.
+	actual_type = typval2type(actual_tv, get_copyID(), &type_list,
 					  TVTT_DO_MEMBER | TVTT_MORE_SPECIFIC);
     if (actual_type != NULL)
     {