summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/vim9.txt56
-rw-r--r--src/eval.c120
-rw-r--r--src/evalvars.c5
-rw-r--r--src/proto/eval.pro1
-rw-r--r--src/testdir/test_expr.vim20
-rw-r--r--src/testdir/test_vim9_assign.vim34
-rw-r--r--src/testdir/test_vim9_disassemble.vim52
-rw-r--r--src/testdir/test_vim9_func.vim2
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h1
-rw-r--r--src/vim9compile.c6
-rw-r--r--src/vim9execute.c14
-rw-r--r--src/vim9expr.c18
-rw-r--r--src/vim9instr.c37
-rw-r--r--src/vim9script.c8
-rw-r--r--src/vim9type.c21
16 files changed, 346 insertions, 51 deletions
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index 385238bfb1..e650b13dba 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -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.
diff --git a/src/eval.c b/src/eval.c
index c4c403c19d..a48b8f8abb 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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);
}
@@ -3483,6 +3484,100 @@ eval_leader(char_u **arg, int vim9)
}
/*
+ * 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 @@ 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,
diff --git a/src/evalvars.c b/src/evalvars.c
index 44882cae2f..4f7252c5c6 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -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"
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 8be58855d7..49dc65b8a8 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -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);
diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim
index 40c1701df7..5f2020db42 100644
--- a/src/testdir/test_expr.vim
+++ b/src/testdir/test_expr.vim
@@ -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()
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 6f6433493b..abc3a15b9a 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -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 []
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 028e1a760e..26ecddedb2 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -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
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 866cf4cc7f..a47dbae27b 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -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()
diff --git a/src/version.c b/src/version.c
index 9693ad35cf..c089b02aa3 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
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 07dd6392f3..a4fcc9b32a 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -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
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 634f24e1ec..6a3c32aad0 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -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);
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 56220f862e..9f6cc5d91c 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -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:
diff --git a/src/vim9expr.c b/src/vim9expr.c
index 1485bd93cf..19cd55d3c8 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -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;
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 2c76fadc3a..7fbf529a64 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -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:
diff --git a/src/vim9script.c b/src/vim9script.c
index 74f9325891..6dce5722ae 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -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
};
diff --git a/src/vim9type.c b/src/vim9type.c
index dcfc998c57..fe4dec59f0 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -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)
{