xref: /vim-8.2.3635/src/vim9script.c (revision 864a28b6)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * vim9script.c: :vim9script, :import, :export and friends
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_EVAL)
17 # include "vim9.h"
18 #endif
19 
20     int
21 in_vim9script(void)
22 {
23     // Do not go up the stack, a ":function" inside vim9script uses legacy
24     // syntax.  "sc_version" is also set when compiling a ":def" function in
25     // legacy script.
26     return current_sctx.sc_version == SCRIPT_VERSION_VIM9;
27 }
28 
29 /*
30  * ":vim9script".
31  */
32     void
33 ex_vim9script(exarg_T *eap UNUSED)
34 {
35 #ifdef FEAT_EVAL
36     int		    sid = current_sctx.sc_sid;
37     scriptitem_T    *si;
38 
39     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
40     {
41 	emsg(_(e_vim9script_can_only_be_used_in_script));
42 	return;
43     }
44 
45     si = SCRIPT_ITEM(sid);
46     if (si->sn_state == SN_STATE_HAD_COMMAND)
47     {
48 	emsg(_(e_vim9script_must_be_first_command_in_script));
49 	return;
50     }
51     if (!IS_WHITE_OR_NUL(*eap->arg) && STRCMP(eap->arg, "noclear") != 0)
52     {
53 	semsg(_(e_invarg2), eap->arg);
54 	return;
55     }
56     if (si->sn_state == SN_STATE_RELOAD && IS_WHITE_OR_NUL(*eap->arg))
57     {
58 	hashtab_T	*ht = &SCRIPT_VARS(sid);
59 
60 	// Reloading a script without the "noclear" argument: clear
61 	// script-local variables and functions.
62 	hashtab_free_contents(ht);
63 	hash_init(ht);
64 	delete_script_functions(sid);
65 
66 	// old imports and script variables are no longer valid
67 	free_imports_and_script_vars(sid);
68     }
69     si->sn_state = SN_STATE_HAD_COMMAND;
70 
71     current_sctx.sc_version = SCRIPT_VERSION_VIM9;
72     si->sn_version = SCRIPT_VERSION_VIM9;
73 
74     if (STRCMP(p_cpo, CPO_VIM) != 0)
75     {
76 	si->sn_save_cpo = vim_strsave(p_cpo);
77 	set_option_value((char_u *)"cpo", 0L, (char_u *)CPO_VIM, 0);
78     }
79 #else
80     // No check for this being the first command, it doesn't matter.
81     current_sctx.sc_version = SCRIPT_VERSION_VIM9;
82 #endif
83 }
84 
85 /*
86  * When in Vim9 script give an error and return FAIL.
87  */
88     int
89 not_in_vim9(exarg_T *eap)
90 {
91     if (in_vim9script())
92 	switch (eap->cmdidx)
93 	{
94 	    case CMD_append:
95 	    case CMD_change:
96 	    case CMD_insert:
97 	    case CMD_t:
98 	    case CMD_xit:
99 		semsg(_(e_command_not_supported_in_vim9_script_missing_var_str), eap->cmd);
100 		return FAIL;
101 	    default: break;
102 	}
103     return OK;
104 }
105 
106 /*
107  * Return TRUE if "p" points at a "#".  Does not check for white space.
108  */
109     int
110 vim9_comment_start(char_u *p)
111 {
112     return p[0] == '#';
113 }
114 
115 #if defined(FEAT_EVAL) || defined(PROTO)
116 
117 /*
118  * ":export let Name: type"
119  * ":export const Name: type"
120  * ":export def Name(..."
121  * ":export class Name ..."
122  */
123     void
124 ex_export(exarg_T *eap)
125 {
126     if (!in_vim9script())
127     {
128 	emsg(_(e_export_can_only_be_used_in_vim9script));
129 	return;
130     }
131 
132     eap->cmd = eap->arg;
133     (void)find_ex_command(eap, NULL, lookup_scriptvar, NULL);
134     switch (eap->cmdidx)
135     {
136 	case CMD_let:
137 	case CMD_var:
138 	case CMD_final:
139 	case CMD_const:
140 	case CMD_def:
141 	// case CMD_class:
142 	    is_export = TRUE;
143 	    do_cmdline(eap->cmd, eap->getline, eap->cookie,
144 						DOCMD_VERBOSE + DOCMD_NOWAIT);
145 
146 	    // The command will reset "is_export" when exporting an item.
147 	    if (is_export)
148 	    {
149 		emsg(_(e_export_with_invalid_argument));
150 		is_export = FALSE;
151 	    }
152 	    break;
153 	default:
154 	    emsg(_(e_invalid_command_after_export));
155 	    break;
156     }
157 }
158 
159 /*
160  * Add a new imported item entry to the current script.
161  */
162     static imported_T *
163 new_imported(garray_T *gap)
164 {
165     if (ga_grow(gap, 1) == OK)
166 	return ((imported_T *)gap->ga_data + gap->ga_len++);
167     return NULL;
168 }
169 
170 /*
171  * Free all imported items in script "sid".
172  */
173     void
174 free_imports_and_script_vars(int sid)
175 {
176     scriptitem_T    *si = SCRIPT_ITEM(sid);
177     int		    idx;
178 
179     for (idx = 0; idx < si->sn_imports.ga_len; ++idx)
180     {
181 	imported_T *imp = ((imported_T *)si->sn_imports.ga_data) + idx;
182 
183 	vim_free(imp->imp_name);
184     }
185     ga_clear(&si->sn_imports);
186 
187     free_all_script_vars(si);
188 
189     clear_type_list(&si->sn_type_list);
190 }
191 
192 /*
193  * Mark all imports as possible to redefine.  Used when a script is loaded
194  * again but not cleared.
195  */
196     void
197 mark_imports_for_reload(int sid)
198 {
199     scriptitem_T    *si = SCRIPT_ITEM(sid);
200     int		    idx;
201 
202     for (idx = 0; idx < si->sn_imports.ga_len; ++idx)
203     {
204 	imported_T *imp = ((imported_T *)si->sn_imports.ga_data) + idx;
205 
206 	imp->imp_flags |= IMP_FLAGS_RELOAD;
207     }
208 }
209 
210 /*
211  * ":import Item from 'filename'"
212  * ":import Item as Alias from 'filename'"
213  * ":import {Item} from 'filename'".
214  * ":import {Item as Alias} from 'filename'"
215  * ":import {Item, Item} from 'filename'"
216  * ":import {Item, Item as Alias} from 'filename'"
217  *
218  * ":import * as Name from 'filename'"
219  */
220     void
221 ex_import(exarg_T *eap)
222 {
223     char_u	*cmd_end;
224     evalarg_T	evalarg;
225 
226     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
227     {
228 	emsg(_(e_import_can_only_be_used_in_script));
229 	return;
230     }
231     fill_evalarg_from_eap(&evalarg, eap, eap->skip);
232 
233     cmd_end = handle_import(eap->arg, NULL, current_sctx.sc_sid,
234 							       &evalarg, NULL);
235     if (cmd_end != NULL)
236 	eap->nextcmd = check_nextcmd(cmd_end);
237     clear_evalarg(&evalarg, eap);
238 }
239 
240 /*
241  * Find an exported item in "sid" matching the name at "*argp".
242  * When it is a variable return the index.
243  * When it is a user function return "*ufunc".
244  * When not found returns -1 and "*ufunc" is NULL.
245  */
246     int
247 find_exported(
248 	int	    sid,
249 	char_u	    *name,
250 	ufunc_T	    **ufunc,
251 	type_T	    **type,
252 	cctx_T	    *cctx)
253 {
254     int		idx = -1;
255     svar_T	*sv;
256     scriptitem_T *script = SCRIPT_ITEM(sid);
257 
258     // find name in "script"
259     // TODO: also find script-local user function
260     idx = get_script_item_idx(sid, name, FALSE, cctx);
261     if (idx >= 0)
262     {
263 	sv = ((svar_T *)script->sn_var_vals.ga_data) + idx;
264 	if (!sv->sv_export)
265 	{
266 	    semsg(_(e_item_not_exported_in_script_str), name);
267 	    return -1;
268 	}
269 	*type = sv->sv_type;
270 	*ufunc = NULL;
271     }
272     else
273     {
274 	char_u	buffer[200];
275 	char_u	*funcname;
276 
277 	// it could be a user function.
278 	if (STRLEN(name) < sizeof(buffer) - 15)
279 	    funcname = buffer;
280 	else
281 	{
282 	    funcname = alloc(STRLEN(name) + 15);
283 	    if (funcname == NULL)
284 		return -1;
285 	}
286 	funcname[0] = K_SPECIAL;
287 	funcname[1] = KS_EXTRA;
288 	funcname[2] = (int)KE_SNR;
289 	sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name);
290 	*ufunc = find_func(funcname, FALSE, NULL);
291 	if (funcname != buffer)
292 	    vim_free(funcname);
293 
294 	if (*ufunc == NULL)
295 	{
296 	    semsg(_(e_item_not_found_in_script_str), name);
297 	    return -1;
298 	}
299     }
300 
301     return idx;
302 }
303 
304 /*
305  * Handle an ":import" command and add the resulting imported_T to "gap", when
306  * not NULL, or script "import_sid" sn_imports.
307  * "cctx" is NULL at the script level.
308  * Returns a pointer to after the command or NULL in case of failure
309  */
310     char_u *
311 handle_import(
312 	char_u	    *arg_start,
313 	garray_T    *gap,
314 	int	    import_sid,
315 	evalarg_T   *evalarg,
316 	void	    *cctx)
317 {
318     char_u	*arg = arg_start;
319     char_u	*cmd_end = NULL;
320     char_u	*as_name = NULL;
321     int		ret = FAIL;
322     typval_T	tv;
323     int		sid = -1;
324     int		res;
325     garray_T	names;
326 
327     ga_init2(&names, sizeof(char_u *), 10);
328     if (*arg == '{')
329     {
330 	// "import {item, item} from ..."
331 	arg = skipwhite_and_linebreak(arg + 1, evalarg);
332 	for (;;)
333 	{
334 	    char_u  *p = arg;
335 	    int	    had_comma = FALSE;
336 
337 	    while (eval_isnamec(*arg))
338 		++arg;
339 	    if (p == arg)
340 		break;
341 	    if (ga_grow(&names, 1) == FAIL)
342 		goto erret;
343 	    ((char_u **)names.ga_data)[names.ga_len] =
344 						      vim_strnsave(p, arg - p);
345 	    ++names.ga_len;
346 	    if (*arg == ',')
347 	    {
348 		had_comma = TRUE;
349 		++arg;
350 	    }
351 	    arg = skipwhite_and_linebreak(arg, evalarg);
352 	    if (*arg == '}')
353 	    {
354 		arg = skipwhite_and_linebreak(arg + 1, evalarg);
355 		break;
356 	    }
357 	    if (!had_comma)
358 	    {
359 		emsg(_(e_missing_comma_in_import));
360 		goto erret;
361 	    }
362 	}
363 	if (names.ga_len == 0)
364 	{
365 	    emsg(_(e_syntax_error_in_import));
366 	    goto erret;
367 	}
368     }
369     else
370     {
371 	// "import Name from ..."
372 	// "import * as Name from ..."
373 	// "import item [as Name] from ..."
374 	arg = skipwhite_and_linebreak(arg, evalarg);
375 	if (arg[0] == '*' && IS_WHITE_OR_NUL(arg[1]))
376 	    arg = skipwhite_and_linebreak(arg + 1, evalarg);
377 	else if (eval_isnamec1(*arg))
378 	{
379 	    char_u  *p = arg;
380 
381 	    while (eval_isnamec(*arg))
382 		++arg;
383 	    if (ga_grow(&names, 1) == FAIL)
384 		goto erret;
385 	    ((char_u **)names.ga_data)[names.ga_len] =
386 						      vim_strnsave(p, arg - p);
387 	    ++names.ga_len;
388 	    arg = skipwhite_and_linebreak(arg, evalarg);
389 	}
390 	else
391 	{
392 	    emsg(_(e_syntax_error_in_import));
393 	    goto erret;
394 	}
395 
396 	if (STRNCMP("as", arg, 2) == 0 && IS_WHITE_OR_NUL(arg[2]))
397 	{
398 	    char_u *p;
399 
400 	    // skip over "as Name "; no line break allowed after "as"
401 	    arg = skipwhite(arg + 2);
402 	    p = arg;
403 	    if (eval_isnamec1(*arg))
404 		while (eval_isnamec(*arg))
405 		    ++arg;
406 	    if (check_defined(p, arg - p, cctx) == FAIL)
407 		goto erret;
408 	    as_name = vim_strnsave(p, arg - p);
409 	    arg = skipwhite_and_linebreak(arg, evalarg);
410 	}
411 	else if (*arg_start == '*')
412 	{
413 	    emsg(_(e_missing_as_after_star));
414 	    goto erret;
415 	}
416     }
417 
418     if (STRNCMP("from", arg, 4) != 0 || !IS_WHITE_OR_NUL(arg[4]))
419     {
420 	emsg(_(e_missing_from));
421 	goto erret;
422     }
423 
424     arg = skipwhite_and_linebreak(arg + 4, evalarg);
425     tv.v_type = VAR_UNKNOWN;
426     // TODO: should we accept any expression?
427     if (*arg == '\'')
428 	ret = eval_lit_string(&arg, &tv, TRUE);
429     else if (*arg == '"')
430 	ret = eval_string(&arg, &tv, TRUE);
431     if (ret == FAIL || tv.vval.v_string == NULL || *tv.vval.v_string == NUL)
432     {
433 	emsg(_(e_invalid_string_after_from));
434 	goto erret;
435     }
436     cmd_end = arg;
437 
438     /*
439      * find script file
440      */
441     if (*tv.vval.v_string == '.')
442     {
443 	size_t		len;
444 	scriptitem_T	*si = SCRIPT_ITEM(current_sctx.sc_sid);
445 	char_u		*tail = gettail(si->sn_name);
446 	char_u		*from_name;
447 
448 	// Relative to current script: "./name.vim", "../../name.vim".
449 	len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2;
450 	from_name = alloc((int)len);
451 	if (from_name == NULL)
452 	{
453 	    clear_tv(&tv);
454 	    goto erret;
455 	}
456 	vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
457 	add_pathsep(from_name);
458 	STRCAT(from_name, tv.vval.v_string);
459 	simplify_filename(from_name);
460 
461 	res = do_source(from_name, FALSE, DOSO_NONE, &sid);
462 	vim_free(from_name);
463     }
464     else if (mch_isFullName(tv.vval.v_string))
465     {
466 	// Absolute path: "/tmp/name.vim"
467 	res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid);
468     }
469     else
470     {
471 	size_t	    len = 7 + STRLEN(tv.vval.v_string) + 1;
472 	char_u	    *from_name;
473 
474 	// Find file in "import" subdirs in 'runtimepath'.
475 	from_name = alloc((int)len);
476 	if (from_name == NULL)
477 	{
478 	    clear_tv(&tv);
479 	    goto erret;
480 	}
481 	vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string);
482 	res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid);
483 	vim_free(from_name);
484     }
485 
486     if (res == FAIL || sid <= 0)
487     {
488 	semsg(_(e_could_not_import_str), tv.vval.v_string);
489 	clear_tv(&tv);
490 	goto erret;
491     }
492     clear_tv(&tv);
493 
494     if (*arg_start == '*')
495     {
496 	imported_T *imported;
497 
498 	imported = find_imported(as_name, STRLEN(as_name), cctx);
499 	if (imported != NULL && imported->imp_sid == sid)
500 	{
501 	    if (imported->imp_flags & IMP_FLAGS_RELOAD)
502 		// import already defined on a previous script load
503 		imported->imp_flags &= ~IMP_FLAGS_RELOAD;
504 	    else
505 	    {
506 		semsg(_(e_name_already_defined_str), as_name);
507 		goto erret;
508 	    }
509 	}
510 
511 	imported = new_imported(gap != NULL ? gap
512 					: &SCRIPT_ITEM(import_sid)->sn_imports);
513 	if (imported == NULL)
514 	    goto erret;
515 	imported->imp_name = as_name;
516 	as_name = NULL;
517 	imported->imp_sid = sid;
518 	imported->imp_flags = IMP_FLAGS_STAR;
519     }
520     else
521     {
522 	int i;
523 
524 	arg = arg_start;
525 	if (*arg == '{')
526 	    arg = skipwhite(arg + 1);
527 	for (i = 0; i < names.ga_len; ++i)
528 	{
529 	    char_u	*name = ((char_u **)names.ga_data)[i];
530 	    size_t	len = STRLEN(name);
531 	    int		idx;
532 	    imported_T	*imported;
533 	    ufunc_T	*ufunc = NULL;
534 	    type_T	*type;
535 
536 	    idx = find_exported(sid, name, &ufunc, &type, cctx);
537 
538 	    if (idx < 0 && ufunc == NULL)
539 		goto erret;
540 
541 	    // If already imported with the same propertis and the
542 	    // IMP_FLAGS_RELOAD set then we keep that entry.  Otherwise create
543 	    // a new one (and give an error for an existing import).
544 	    imported = find_imported(name, len, cctx);
545 	    if (imported != NULL
546 		    && (imported->imp_flags & IMP_FLAGS_RELOAD)
547 		    && imported->imp_sid == sid
548 		    && (idx >= 0
549 			? (equal_type(imported->imp_type, type)
550 			    && imported->imp_var_vals_idx == idx)
551 			: (equal_type(imported->imp_type, ufunc->uf_func_type)
552 			    && STRCMP(imported->imp_funcname,
553 							ufunc->uf_name) == 0)))
554 	    {
555 		imported->imp_flags &= ~IMP_FLAGS_RELOAD;
556 	    }
557 	    else
558 	    {
559 		if (check_defined(name, len, cctx) == FAIL)
560 		    goto erret;
561 
562 		imported = new_imported(gap != NULL ? gap
563 				       : &SCRIPT_ITEM(import_sid)->sn_imports);
564 		if (imported == NULL)
565 		    goto erret;
566 
567 		// TODO: check for "as" following
568 		// imported->imp_name = vim_strsave(as_name);
569 		imported->imp_name = name;
570 		((char_u **)names.ga_data)[i] = NULL;
571 		imported->imp_sid = sid;
572 		if (idx >= 0)
573 		{
574 		    imported->imp_type = type;
575 		    imported->imp_var_vals_idx = idx;
576 		}
577 		else
578 		{
579 		    imported->imp_type = ufunc->uf_func_type;
580 		    imported->imp_funcname = ufunc->uf_name;
581 		}
582 	    }
583 	}
584     }
585 erret:
586     ga_clear_strings(&names);
587     vim_free(as_name);
588     return cmd_end;
589 }
590 
591 /*
592  * Declare a script-local variable without init: "let var: type".
593  * "const" is an error since the value is missing.
594  * Returns a pointer to after the type.
595  */
596     char_u *
597 vim9_declare_scriptvar(exarg_T *eap, char_u *arg)
598 {
599     char_u	    *p;
600     char_u	    *name;
601     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
602     type_T	    *type;
603     typval_T	    init_tv;
604 
605     if (eap->cmdidx == CMD_final || eap->cmdidx == CMD_const)
606     {
607 	if (eap->cmdidx == CMD_final)
608 	    emsg(_(e_final_requires_a_value));
609 	else
610 	    emsg(_(e_const_requires_a_value));
611 	return arg + STRLEN(arg);
612     }
613 
614     // Check for valid starting character.
615     if (!eval_isnamec1(*arg))
616     {
617 	semsg(_(e_invarg2), arg);
618 	return arg + STRLEN(arg);
619     }
620 
621     for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p))
622 	if (*p == ':' && (VIM_ISWHITE(p[1]) || p != arg + 1))
623 	    break;
624 
625     if (*p != ':')
626     {
627 	emsg(_(e_type_or_initialization_required));
628 	return arg + STRLEN(arg);
629     }
630     if (!VIM_ISWHITE(p[1]))
631     {
632 	semsg(_(e_white_space_required_after_str), ":");
633 	return arg + STRLEN(arg);
634     }
635     name = vim_strnsave(arg, p - arg);
636 
637     // parse type
638     p = skipwhite(p + 1);
639     type = parse_type(&p, &si->sn_type_list, TRUE);
640     if (type == NULL)
641     {
642 	vim_free(name);
643 	return p;
644     }
645 
646     // Create the variable with 0/NULL value.
647     CLEAR_FIELD(init_tv);
648     if (type->tt_type == VAR_ANY)
649 	// A variable of type "any" is not possible, just use zero instead
650 	init_tv.v_type = VAR_NUMBER;
651     else
652 	init_tv.v_type = type->tt_type;
653     set_var_const(name, type, &init_tv, FALSE, 0);
654 
655     vim_free(name);
656     return p;
657 }
658 
659 /*
660  * Vim9 part of adding a script variable: add it to sn_all_vars (lookup by name
661  * with a hashtable) and sn_var_vals (lookup by index).
662  * When "create" is TRUE this is a new variable, otherwise find and update an
663  * existing variable.
664  * When "type" is NULL use "tv" for the type.
665  */
666     void
667 update_vim9_script_var(int create, dictitem_T *di, typval_T *tv, type_T *type)
668 {
669     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
670     hashitem_T	    *hi;
671     svar_T	    *sv;
672 
673     if (create)
674     {
675 	sallvar_T	    *newsav;
676 
677 	// Store a pointer to the typval_T, so that it can be found by index
678 	// instead of using a hastab lookup.
679 	if (ga_grow(&si->sn_var_vals, 1) == FAIL)
680 	    return;
681 
682 	sv = ((svar_T *)si->sn_var_vals.ga_data) + si->sn_var_vals.ga_len;
683 	newsav = (sallvar_T *)alloc_clear(
684 				       sizeof(sallvar_T) + STRLEN(di->di_key));
685 	if (newsav == NULL)
686 	    return;
687 
688 	sv->sv_tv = &di->di_tv;
689 	sv->sv_const = (di->di_flags & DI_FLAGS_LOCK) ? ASSIGN_CONST : 0;
690 	sv->sv_export = is_export;
691 	newsav->sav_var_vals_idx = si->sn_var_vals.ga_len;
692 	++si->sn_var_vals.ga_len;
693 	STRCPY(&newsav->sav_key, di->di_key);
694 	sv->sv_name = newsav->sav_key;
695 	newsav->sav_di = di;
696 	newsav->sav_block_id = si->sn_current_block_id;
697 
698 	hi = hash_find(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
699 	if (!HASHITEM_EMPTY(hi))
700 	{
701 	    sallvar_T *sav = HI2SAV(hi);
702 
703 	    // variable with this name exists in another block
704 	    while (sav->sav_next != NULL)
705 		sav = sav->sav_next;
706 	    sav->sav_next = newsav;
707 	}
708 	else
709 	    // new variable name
710 	    hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
711     }
712     else
713     {
714 	sv = find_typval_in_script(&di->di_tv);
715     }
716     if (sv != NULL)
717     {
718 	if (type == NULL)
719 	    sv->sv_type = typval2type(tv, &si->sn_type_list);
720 	else
721 	    sv->sv_type = type;
722     }
723 
724     // let ex_export() know the export worked.
725     is_export = FALSE;
726 }
727 
728 /*
729  * Hide a script variable when leaving a block.
730  * "idx" is de index in sn_var_vals.
731  * When "func_defined" is non-zero then a function was defined in this block,
732  * the variable may be accessed by it.  Otherwise the variable can be cleared.
733  */
734     void
735 hide_script_var(scriptitem_T *si, int idx, int func_defined)
736 {
737     svar_T	*sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
738     hashtab_T	*script_ht = get_script_local_ht();
739     hashtab_T	*all_ht = &si->sn_all_vars.dv_hashtab;
740     hashitem_T	*script_hi;
741     hashitem_T	*all_hi;
742 
743     // Remove a variable declared inside the block, if it still exists.
744     // If it was added in a nested block it will already have been removed.
745     // The typval is moved into the sallvar_T.
746     script_hi = hash_find(script_ht, sv->sv_name);
747     all_hi = hash_find(all_ht, sv->sv_name);
748     if (!HASHITEM_EMPTY(script_hi) && !HASHITEM_EMPTY(all_hi))
749     {
750 	dictitem_T	*di = HI2DI(script_hi);
751 	sallvar_T	*sav = HI2SAV(all_hi);
752 	sallvar_T	*sav_prev = NULL;
753 
754 	// There can be multiple entries with the same name in different
755 	// blocks, find the right one.
756 	while (sav != NULL && sav->sav_var_vals_idx != idx)
757 	{
758 	    sav_prev = sav;
759 	    sav = sav->sav_next;
760 	}
761 	if (sav != NULL)
762 	{
763 	    if (func_defined)
764 	    {
765 		// move the typval from the dictitem to the sallvar
766 		sav->sav_tv = di->di_tv;
767 		di->di_tv.v_type = VAR_UNKNOWN;
768 		sav->sav_flags = di->di_flags;
769 		sav->sav_di = NULL;
770 		sv->sv_tv = &sav->sav_tv;
771 	    }
772 	    else
773 	    {
774 		if (sav_prev == NULL)
775 		    hash_remove(all_ht, all_hi);
776 		else
777 		    sav_prev->sav_next = sav->sav_next;
778 		sv->sv_name = NULL;
779 		vim_free(sav);
780 	    }
781 	    delete_var(script_ht, script_hi);
782 	}
783     }
784 }
785 
786 /*
787  * Free the script variables from "sn_all_vars".
788  */
789     void
790 free_all_script_vars(scriptitem_T *si)
791 {
792     int		todo;
793     hashtab_T	*ht = &si->sn_all_vars.dv_hashtab;
794     hashitem_T	*hi;
795     sallvar_T	*sav;
796     sallvar_T	*sav_next;
797 
798     hash_lock(ht);
799     todo = (int)ht->ht_used;
800     for (hi = ht->ht_array; todo > 0; ++hi)
801     {
802 	if (!HASHITEM_EMPTY(hi))
803 	{
804 	    --todo;
805 
806 	    // Free the variable.  Don't remove it from the hashtab, ht_array
807 	    // might change then.  hash_clear() takes care of it later.
808 	    sav = HI2SAV(hi);
809 	    while (sav != NULL)
810 	    {
811 		sav_next = sav->sav_next;
812 		if (sav->sav_di == NULL)
813 		    clear_tv(&sav->sav_tv);
814 		vim_free(sav);
815 		sav = sav_next;
816 	    }
817 	}
818     }
819     hash_clear(ht);
820     hash_init(ht);
821 
822     ga_clear(&si->sn_var_vals);
823 
824     // existing commands using script variable indexes are no longer valid
825     si->sn_script_seq = current_sctx.sc_seq;
826 }
827 
828 /*
829  * Find the script-local variable that links to "dest".
830  * Returns NULL if not found.
831  */
832     svar_T *
833 find_typval_in_script(typval_T *dest)
834 {
835     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
836     int		    idx;
837 
838     if (si->sn_version != SCRIPT_VERSION_VIM9)
839 	// legacy script doesn't store variable types
840 	return NULL;
841 
842     // Find the svar_T in sn_var_vals.
843     for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx)
844     {
845 	svar_T    *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
846 
847 	// If "sv_name" is NULL the variable was hidden when leaving a block,
848 	// don't check "sv_tv" then, it might be used for another variable now.
849 	if (sv->sv_name != NULL && sv->sv_tv == dest)
850 	    return sv;
851     }
852     iemsg("check_script_var_type(): not found");
853     return NULL;
854 }
855 
856 /*
857  * Check if the type of script variable "dest" allows assigning "value".
858  * If needed convert "value" to a bool.
859  */
860     int
861 check_script_var_type(typval_T *dest, typval_T *value, char_u *name)
862 {
863     svar_T  *sv = find_typval_in_script(dest);
864     int	    ret;
865 
866     if (sv != NULL)
867     {
868 	if (sv->sv_const)
869 	{
870 	    semsg(_(e_readonlyvar), name);
871 	    return FAIL;
872 	}
873 	ret = check_typval_type(sv->sv_type, value, 0);
874 	if (ret == OK && need_convert_to_bool(sv->sv_type, value))
875 	{
876 	    int	val = tv2bool(value);
877 
878 	    clear_tv(value);
879 	    value->v_type = VAR_BOOL;
880 	    value->v_lock = 0;
881 	    value->vval.v_number = val ? VVAL_TRUE : VVAL_FALSE;
882 	}
883 	return ret;
884     }
885 
886     return OK; // not really
887 }
888 
889 #endif // FEAT_EVAL
890