Skip to content

Commit

Permalink
patch 8.2.4526: Vim9: cannot set variables to a null value
Browse files Browse the repository at this point in the history
Problem:    Vim9: cannot set variables to a null value.
Solution:   Add null_list, null_job, etc.
  • Loading branch information
brammool committed Mar 8, 2022
1 parent 0823804 commit 8acb9cc
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 51 deletions.
56 changes: 49 additions & 7 deletions runtime/doc/vim9.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
120 changes: 100 additions & 20 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/evalvars.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/proto/eval.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 18 additions & 2 deletions src/testdir/test_expr.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
34 changes: 33 additions & 1 deletion src/testdir/test_vim9_assign.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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 []
Expand Down
52 changes: 52 additions & 0 deletions src/testdir/test_vim9_disassemble.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/testdir/test_vim9_func.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions src/version.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
4526,
/**/
4525,
/**/
Expand Down

0 comments on commit 8acb9cc

Please sign in to comment.