Skip to content

Commit c1846d4

Browse files
committed
special case loop iterator variables to weakly :=
1 parent 3dd4b1a commit c1846d4

2 files changed

Lines changed: 110 additions & 55 deletions

File tree

src/lang/compiler.c

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -355,22 +355,42 @@ vm_comp_resolve_id(struct workspace *wk, obj id, enum vm_comp_resolve_mode mode)
355355
}
356356

357357
static struct local_binding *
358-
vm_comp_declare_local(struct workspace *wk, struct node *n, obj id)
358+
vm_comp_lookup_local(struct workspace *wk, obj id, bool upvalues)
359359
{
360+
// Try to find a local variable defined in the current scope or any higher
361+
// scope.
362+
//
363+
// If a matching variable is found at the current scope, then do not
364+
// declare a new local since it has already been declared.
365+
//
366+
// If a matching variable is found at a higher scope, only declare a new
367+
// local variable if it has been bound. This fixes behavior like:
368+
//
369+
// func foo()
370+
// name = 'foo'
371+
// endfunc
372+
//
373+
// name = 'bar'
374+
// foo()
375+
// assert(name == 'bar')
376+
//
377+
// Without this, `name` in foo will incorrectly resolve to the outer
378+
// scope's name.
360379
const uint32_t depth = vm_comp_current_depth(wk);
361380
for (int32_t i = wk->vm.compiler_state.locals.len - 1; i >= 0; --i) {
362381
struct local_binding *l = arr_get(&wk->vm.compiler_state.locals, i);
363382
if (l->depth != depth) {
364-
break;
383+
if (!upvalues) {
384+
break;
385+
} else if (!l->bound) {
386+
continue;
387+
}
365388
}
366389
if (obj_equal(wk, id, l->id)) {
367-
vm_comp_error(wk, n, "duplicate variable name %s", get_cstr(wk, id));
368-
vm_comp_note(wk, l->n, "already declared here");
369-
return 0;
390+
return l;
370391
}
371392
}
372-
373-
return vm_comp_declare_local_unchecked(wk, n, id);
393+
return 0;
374394
}
375395

376396
static void
@@ -408,42 +428,51 @@ vm_comp_reserve_local_stack_slot(struct workspace *wk, obj id)
408428
}
409429

410430
static void
411-
vm_comp_try_declare_block_local(struct workspace *wk, struct node *n, obj id)
431+
vm_comp_duplicate_local_error(struct workspace *wk, struct node *n, obj id, struct local_binding *prev)
412432
{
413-
// Try to find a local variable defined in the current scope or any higher
414-
// scope.
415-
//
416-
// If a matching variable is found at the current scope, then do not
417-
// declare a new local since it has already been declared.
418-
//
419-
// If a matching variable is found at a higher scope, only declare a new
420-
// local variable if it has been bound. This fixes behavior like:
421-
//
422-
// func foo()
423-
// name = 'foo'
424-
// endfunc
425-
//
426-
// name = 'bar'
427-
// foo()
428-
// assert(name == 'bar')
429-
//
430-
// Without this, `name` in foo will incorrectly resolve to the outer
431-
// scope's name.
432-
const uint32_t depth = vm_comp_current_depth(wk);
433-
for (int32_t i = wk->vm.compiler_state.locals.len - 1; i >= 0; --i) {
434-
struct local_binding *l = arr_get(&wk->vm.compiler_state.locals, i);
435-
if (obj_equal(wk, id, l->id)) {
436-
if (depth != l->depth && !l->bound) {
437-
continue;
438-
}
433+
vm_comp_error(wk, n, "duplicate variable name %s", get_cstr(wk, id));
434+
vm_comp_note(wk, prev->n, "already declared here");
435+
}
436+
437+
static void
438+
vm_comp_declare_local(struct workspace *wk, struct node *n, obj id)
439+
{
440+
struct local_binding *prev;
441+
if ((prev = vm_comp_lookup_local(wk, id, false))) {
442+
vm_comp_duplicate_local_error(wk, n, id, prev);
443+
}
444+
445+
vm_comp_declare_local_unchecked(wk, n, id);
446+
}
447+
448+
static void
449+
vm_comp_force_declare_local(struct workspace *wk, struct node *n, obj id, bool reuse)
450+
{
451+
struct local_binding *prev;
452+
if ((prev = vm_comp_lookup_local(wk, id, false))) {
453+
if (reuse) {
439454
return;
455+
} else {
456+
vm_comp_duplicate_local_error(wk, n, id, prev);
440457
}
441458
}
442459

443-
vm_comp_declare_local_unchecked(wk, n, id);
460+
struct local_binding *l = vm_comp_declare_local_unchecked(wk, n, id);
461+
l->forced_declaration = true;
444462
vm_comp_reserve_local_stack_slot(wk, id);
445463
}
446464

465+
static void
466+
vm_comp_try_declare_local(struct workspace *wk,
467+
struct node *n,
468+
obj id)
469+
{
470+
if (!vm_comp_lookup_local(wk, id, true)) {
471+
vm_comp_declare_local_unchecked(wk, n, id);
472+
vm_comp_reserve_local_stack_slot(wk, id);
473+
}
474+
}
475+
447476
static void
448477
vm_comp_block_locals_visitor(struct workspace *wk, struct node *n)
449478
{
@@ -454,29 +483,25 @@ vm_comp_block_locals_visitor(struct workspace *wk, struct node *n)
454483
assert(n->l->type == node_type_id_lit);
455484
obj id = n->l->data.str;
456485
if (n->data.type & node_assign_flag_force_declaration) {
457-
struct local_binding *l = vm_comp_declare_local(wk, n->l, id);
458-
vm_comp_reserve_local_stack_slot(wk, id);
459-
if (l) {
460-
l->forced_declaration = true;
461-
}
486+
vm_comp_force_declare_local(wk, n->l, id, false);
462487
} else {
463-
vm_comp_try_declare_block_local(wk, n->l, id);
488+
vm_comp_try_declare_local(wk, n->l, id);
464489
}
465490
}
466491
break;
467492
}
468493
case node_type_foreach: {
469494
struct node *ida = n->l->l->l, *idb = n->l->l->r;
470-
vm_comp_try_declare_block_local(wk, ida, ida->data.str);
495+
vm_comp_force_declare_local(wk, ida, ida->data.str, true);
471496
if (idb) {
472-
vm_comp_try_declare_block_local(wk, idb, idb->data.str);
497+
vm_comp_force_declare_local(wk, idb, idb->data.str, true);
473498
}
474499
break;
475500
}
476501
case node_type_func_def: {
477502
struct node *id = n->l->l->l;
478503
if (id) {
479-
vm_comp_try_declare_block_local(wk, id, id->data.str);
504+
vm_comp_try_declare_local(wk, id, id->data.str);
480505
}
481506
break;
482507
}
@@ -870,15 +895,15 @@ vm_comp_node(struct workspace *wk, struct node *n)
870895
push_constant(wk, 0);
871896

872897
if (wk->vm.compiler_state.mode & vm_compile_mode_locals) {
873-
vm_comp_assign_local(wk, ida, ida->data.str, 0);
898+
vm_comp_assign_local(wk, ida, ida->data.str, node_assign_flag_force_declaration);
874899
} else {
875900
vm_comp_assign_global(wk, ida->data.str, 0);
876901
}
877902
push_code(wk, op_pop);
878903

879904
if (idb) {
880905
if (wk->vm.compiler_state.mode & vm_compile_mode_locals) {
881-
vm_comp_assign_local(wk, idb, idb->data.str, 0);
906+
vm_comp_assign_local(wk, idb, idb->data.str, node_assign_flag_force_declaration);
882907
} else {
883908
vm_comp_assign_global(wk, idb->data.str, 0);
884909
}

tests/lang/scope.meson

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,50 @@ add_test(
147147
)
148148

149149
add_test(
150-
'foreach does not create scopes',
150+
'foreach does not create scopes, but iterator variables do not bind to upvalues',
151151
func()
152-
k = 'not set'
152+
foo = 'set at outer scope'
153+
154+
func a()
155+
assert(foo == 'set at outer scope')
156+
157+
foo = 'set inside a()' # This mutates the foo upvalue declared above
158+
assert(foo == 'set inside a()')
159+
160+
# Iterator variables are like :=, they force declare new block
161+
# locals. They are special in that they reuse existing locals
162+
# instead of throwing a duplicate error.
163+
foreach foo : ['set in iterator']
164+
endforeach
165+
assert(foo == 'set in iterator')
166+
endfunc
167+
168+
a()
169+
assert(foo == 'set inside a()')
170+
171+
func b()
172+
assert(foo == 'set inside a()')
173+
174+
foo = 'set inside b()' # This mutates the foo upvalue declared above
175+
assert(foo == 'set inside b()')
176+
177+
foo := 'local to b'
178+
assert(foo == 'local to b')
179+
180+
# Iterator variables are like :=, they force declare new block
181+
# locals. They are special in that they reuse existing locals
182+
# instead of throwing a duplicate error.
183+
foreach foo : ['set in iterator']
184+
endforeach
185+
assert(foo == 'set in iterator')
153186

154-
func foo() -> int
155-
foreach k, v : {'a': 1}
156-
c = 1
187+
foreach foo : ['reset in iterator']
157188
endforeach
158-
assert(c == 1)
159-
return v
189+
assert(foo == 'reset in iterator')
160190
endfunc
161191

162-
assert(foo() == 1, 'v should be equal to the last iteration')
163-
assert(k == 'a', 'k should have been reused as an upvalue')
192+
b()
193+
assert(foo == 'set inside b()')
164194
endfunc,
165195
)
166196

0 commit comments

Comments
 (0)