0
0
mirror of https://github.com/vim/vim.git synced 2025-08-31 20:53:42 -04:00

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.
This commit is contained in:
Bram Moolenaar 2022-03-08 13:18:55 +00:00
parent 08238045e7
commit 8acb9cc620
16 changed files with 346 additions and 51 deletions

View 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.

View 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,

View 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"

View 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);

View 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()

View 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 []

View 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

View 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()

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
4526,
/**/
4525,
/**/

View 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

View 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);

View 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:

View 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;

View 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:

View 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
};

View 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)
{