Problem: Vim9: cannot set variables to a null value.
Solution: Add null_list, null_job, etc.
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
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
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.
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.
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);
}
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
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,
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"
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);
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()
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 []
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
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()
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 4526,
/**/
4525,
/**/
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
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);
}
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;
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:
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;
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;
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;
case ISN_NEGATENR:
case ISN_NEWDICT:
case ISN_NEWLIST:
+ case ISN_NEWPARTIAL:
case ISN_OPANY:
case ISN_OPFLOAT:
case ISN_OPNR:
"true",
"false",
"null",
+ "null_blob",
+ "null_dict",
+ "null_function",
+ "null_list",
+ "null_partial",
+ "null_string",
+ "null_channel",
+ "null_job",
"this",
NULL
};
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)
{