From 8acb9cc6209768ca7ec75c9f7af8c389312ea8d6 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 8 Mar 2022 13:18:55 +0000 Subject: 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. --- src/eval.c | 120 ++++++++++++++++++++++++++++------ src/evalvars.c | 5 ++ src/proto/eval.pro | 1 + src/testdir/test_expr.vim | 20 +++++- src/testdir/test_vim9_assign.vim | 34 +++++++++- src/testdir/test_vim9_disassemble.vim | 52 +++++++++++++++ src/testdir/test_vim9_func.vim | 2 +- src/version.c | 2 + src/vim9.h | 1 + src/vim9compile.c | 6 +- src/vim9execute.c | 14 ++++ src/vim9expr.c | 18 +++-- src/vim9instr.c | 37 ++++++++++- src/vim9script.c | 8 +++ src/vim9type.c | 21 +++--- 15 files changed, 297 insertions(+), 44 deletions(-) (limited to 'src') 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); } @@ -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, 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 = null_dict + var ds: dict = null_dict + var ln: list = null_list + var ls: list = 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('\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): dict 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 @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4526, /**/ 4525, /**/ 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 or dict 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 or dict 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) { -- cgit v1.2.3