]> granicus.if.org Git - vim/commitdiff
patch 8.2.4526: Vim9: cannot set variables to a null value v8.2.4526
authorBram Moolenaar <Bram@vim.org>
Tue, 8 Mar 2022 13:18:55 +0000 (13:18 +0000)
committerBram Moolenaar <Bram@vim.org>
Tue, 8 Mar 2022 13:18:55 +0000 (13:18 +0000)
Problem:    Vim9: cannot set variables to a null value.
Solution:   Add null_list, null_job, etc.

16 files changed:
runtime/doc/vim9.txt
src/eval.c
src/evalvars.c
src/proto/eval.pro
src/testdir/test_expr.vim
src/testdir/test_vim9_assign.vim
src/testdir/test_vim9_disassemble.vim
src/testdir/test_vim9_func.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c
src/vim9expr.c
src/vim9instr.c
src/vim9script.c
src/vim9type.c

index 385238bfb10fa07ae08dcbc2e16b8e65b26957d2..e650b13dba5c63191f8d11ab95a74ae8a766f75e 100644 (file)
@@ -94,8 +94,20 @@ script and `:def` functions; details are below:
        def CallMe(count: number, message: string): bool
 - Call functions without `:call`: >
        writefile(['done'], 'file.txt')
-- You cannot use old Ex commands `:xit`, `:t`, `:k`, `:append`, `:change`,
-  `:insert`, `:open`, and `:s` or `:d` with only flags.
+- You cannot use old Ex commands:
+       `:Print`
+       `:append`
+       `:change`
+       `:d`  directly followed by 'd' or 'p'.
+       `:insert`
+       `:k`
+       `:mode`
+       `:open`
+       `:s`  with only flags
+       `:t`
+       `:xit`
+- Some commands, especially those used for flow control, cannot be shortened.
+  E.g., `:throw` cannot be written as `:th`. *E839*
 - You cannot use curly-braces names.
 - A range before a command must be prefixed with a colon: >
        :%s/this/that
@@ -305,7 +317,7 @@ function, the function does not need to be defined more than once: >
 
 
 Variable declarations with :var, :final and :const ~
-                               *vim9-declaration* *:var*
+                               *vim9-declaration* *:var* *E1079*
                                *E1017* *E1020* *E1054* *E1087* *E1108* *E1124*
 Local variables need to be declared with `:var`.  Local constants need to be
 declared with `:final` or `:const`.  We refer to both as "variables" in this
@@ -375,6 +387,9 @@ In Vim9 script `:let` cannot be used.  An existing variable is assigned to
 without any command.  The same for global, window, tab, buffer and Vim
 variables, because they are not really declared.  Those can also be deleted
 with `:unlet`.
+                                                       *E1065*
+You cannot use `:va` to declare a variable, it must be written with the full
+name `:var`.  Just to make sure it is easy to read.
                                                        *E1178*
 `:lockvar` does not work on local variables.  Use `:const` and `:final`
 instead.
@@ -952,10 +967,37 @@ always converted to string: >
 Simple types are Number, Float, Special and Bool.  For other types |string()|
 should be used.
                                                *false* *true* *null* *E1034*
-In Vim9 script one can use "true" for v:true, "false" for v:false and "null"
-for v:null.  When converting a boolean to a string "false" and "true" are
-used, not "v:false" and "v:true" like in legacy script.  "v:none" is not
-changed, it is only used in JSON and has no equivalent in other languages.
+In Vim9 script one can use the following predefined values: >
+       true
+       false
+       null
+       null_blob
+       null_channel
+       null_dict
+       null_function
+       null_job
+       null_list
+       null_partial
+       null_string
+`true` is the same as `v:true`, `false` the same as `v:false`, `null` the same
+as `v:null`.
+
+While `null` has the type "special", the other "null_" types have the type
+indicated by their name.  Quite often a null value is handled the same as an
+empty value, but not always.  The values can be useful to clear a script-local
+variable, since they cannot be deleted with `:unlet`.  E.g.: >
+       var theJob = job_start(...)
+       # let the job do its work
+       theJob = null_job
+
+The values can also be useful as the default value for an argument: >
+       def MyFunc(b: blob = null_blob)
+          if b == null_blob
+             # b argument was not given
+
+When converting a boolean to a string `false` and `true` are used, not
+`v:false` and `v:true` like in legacy script.  `v:none` has no `none`
+replacement, it has no equivalent in other languages.
 
 Indexing a string with [idx] or taking a slice with [idx : idx] uses character
 indexes instead of byte indexes.  Composing characters are included.
index c4c403c19dcc4b58bbfc6cf24e789d9905054211..a48b8f8abbae1ddaf26b0574c522f5ccfed95692 100644 (file)
@@ -943,6 +943,7 @@ get_lval(
                    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);
                }
@@ -3482,6 +3483,100 @@ eval_leader(char_u **arg, int vim9)
     return OK;
 }
 
+/*
+ * 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
@@ -3757,26 +3852,11 @@ eval7(
                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,
index 44882cae2f5335f4f08176e4d97d0f5caf22e700..4f7252c5c68fdc0e36b61253e2471843ad594142 100644 (file)
@@ -999,6 +999,11 @@ ex_let_vars(
     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"
index 8be58855d7eca8c553f7cb4e15b30d88175103fa..49dc65b8a89db20cf8aefb3ec76e497da75b7612 100644 (file)
@@ -42,6 +42,7 @@ int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
 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);
index 40c1701df76a7ff73001a61d52e9d5d4dc40559a..5f2020db42dfb84c4a2d3f34c384f8074acbc3ec 100644 (file)
@@ -157,12 +157,28 @@ endfunc
 
 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()
index 6f6433493b0d533b3aa11df875c38f6a4d5dd948..abc3a15b9a3d894f06741f1b12a780aa216c214c 100644 (file)
@@ -306,12 +306,44 @@ def Test_assign_register()
 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 []
index 028e1a760ec5f3070bc8e18a9014859d1f12350a..26ecddedb2167809da3fd3241092f4c52d992dd9 100644 (file)
@@ -415,6 +415,58 @@ def Test_disassemble_store_member()
         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
index 866cf4cc7f65bb8d7f29d917fdaa14909d6bf222..a47dbae27b1f7db15b035753844f63eb465e402c 100644 (file)
@@ -3326,7 +3326,7 @@ def Test_partial_call()
       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()
index 9693ad35cf114bf86a360e0bbddb091c4ab9fb91..c089b02aa33fe83609a689247ed163f4ab77311c 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4526,
 /**/
     4525,
 /**/
index 07dd6392f3120c69d7e55b5e6408867ed21ec3a3..a4fcc9b32aee271a9a9d5d47603d9cc1532f670b 100644 (file)
@@ -91,6 +91,7 @@ typedef enum {
     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
 
index 634f24e1ecc84494ecd88a34f55ac51e140fedee..6a3c32aad0836a765432e390a7bed9a2b476284d 100644 (file)
@@ -403,8 +403,12 @@ need_type_where(
     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);
index 56220f862e7ab6052a71eba8ee4ed8a4b5264f37..9f6cc5d91c496ac45f571d8760c723737ee4c8c2 100644 (file)
@@ -3440,6 +3440,17 @@ exec_instructions(ectx_T *ectx)
                }
                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 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                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:
index 1485bd93cf37c89e8c4e414a2fd78d2f464d194c..19cd55d3c8caddd5252c706eb2b15e560c0117ff 100644 (file)
@@ -2107,14 +2107,20 @@ compile_expr8(
                    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;
index 2c76fadc3acb7414dafb9ad9511f3e679aa03f4a..7fbf529a6477e9e9acc8850a69e86cdf561f6613 100644 (file)
@@ -570,6 +570,40 @@ generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
                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 @@ generate_PUSHJOB(cctx_T *cctx, job_T *job)
     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 @@ delete_instr(isn_T *isn)
        case ISN_NEGATENR:
        case ISN_NEWDICT:
        case ISN_NEWLIST:
+       case ISN_NEWPARTIAL:
        case ISN_OPANY:
        case ISN_OPFLOAT:
        case ISN_OPNR:
index 74f93258918afad64efccf5241748bfa7746b290..6dce5722aece0c762daca43c75d8fd32cf7a26b7 100644 (file)
@@ -1062,6 +1062,14 @@ static char *reserved[] = {
     "true",
     "false",
     "null",
+    "null_blob",
+    "null_dict",
+    "null_function",
+    "null_list",
+    "null_partial",
+    "null_string",
+    "null_channel",
+    "null_job",
     "this",
     NULL
 };
index dcfc998c5701e4071fdc3673b77f11b0700d6f8a..fe4dec59f03d1ae8b3761b411f060909254742fa 100644 (file)
@@ -567,22 +567,19 @@ check_typval_type(type_T *expected, typval_T *actual_tv, where_T where)
 
     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)
     {