snix_eval/compiler/
bindings.rs

1//! This module implements compiler logic related to name/value binding
2//! definitions (that is, attribute sets and let-expressions).
3//!
4//! In the case of recursive scopes these cases share almost all of their
5//! (fairly complex) logic.
6
7use std::iter::Peekable;
8
9use rnix::ast::HasEntry;
10use rowan::ast::AstChildren;
11
12use crate::spans::{EntireFile, OrEntireFile};
13
14use super::*;
15
16type PeekableAttrs = Peekable<AstChildren<ast::Attr>>;
17
18/// What kind of bindings scope is being compiled?
19#[derive(Clone, Copy, PartialEq)]
20enum BindingsKind {
21    /// Standard `let ... in ...`-expression.
22    LetIn,
23
24    /// Non-recursive attribute set.
25    Attrs,
26
27    /// Recursive attribute set.
28    RecAttrs,
29}
30
31impl BindingsKind {
32    fn is_attrs(&self) -> bool {
33        matches!(self, BindingsKind::Attrs | BindingsKind::RecAttrs)
34    }
35}
36
37// Internal representation of an attribute set used for merging sets, or
38// inserting nested keys.
39#[derive(Clone)]
40struct AttributeSet {
41    /// Original span at which this set was first encountered.
42    span: Span,
43
44    /// Tracks the kind of set (rec or not).
45    kind: BindingsKind,
46
47    /// All inherited entries
48    inherits: Vec<ast::Inherit>,
49
50    /// All internal entries
51    entries: Vec<(Span, PeekableAttrs, ast::Expr)>,
52}
53
54impl ToSpan for AttributeSet {
55    fn span_for(&self, _: &codemap::File) -> Span {
56        self.span
57    }
58}
59
60impl AttributeSet {
61    fn from_ast(c: &Compiler, node: &ast::AttrSet) -> Self {
62        AttributeSet {
63            span: c.span_for(node),
64
65            // Kind of the attrs depends on the first time it is
66            // encountered. We actually believe this to be a Nix
67            // bug: https://github.com/NixOS/nix/issues/7111
68            kind: if node.rec_token().is_some() {
69                BindingsKind::RecAttrs
70            } else {
71                BindingsKind::Attrs
72            },
73
74            inherits: ast::HasEntry::inherits(node).collect(),
75
76            entries: ast::HasEntry::attrpath_values(node)
77                .map(|entry| {
78                    let span = c.span_for(&entry);
79                    (
80                        span,
81                        entry.attrpath().unwrap().attrs().peekable(),
82                        entry.value().unwrap(),
83                    )
84                })
85                .collect(),
86        }
87    }
88}
89
90// Data structures to track the bindings observed in the second pass, and
91// forward the information needed to compile their value.
92enum Binding {
93    InheritFrom {
94        namespace: ast::Expr,
95        name: SmolStr,
96        span: Span,
97    },
98
99    Plain {
100        expr: ast::Expr,
101    },
102
103    Set(AttributeSet),
104}
105
106impl Binding {
107    /// Merge the provided value into the current binding, or emit an
108    /// error if this turns out to be impossible.
109    fn merge(
110        &mut self,
111        c: &mut Compiler,
112        span: Span,
113        mut remaining_path: PeekableAttrs,
114        value: ast::Expr,
115    ) {
116        match self {
117            Binding::InheritFrom { name, ref span, .. } => {
118                c.emit_error(span, ErrorKind::UnmergeableInherit { name: name.clone() })
119            }
120
121            // If the value is not yet a nested binding, flip the representation
122            // and recurse.
123            Binding::Plain { expr } => match expr {
124                ast::Expr::AttrSet(existing) => {
125                    let nested = AttributeSet::from_ast(c, existing);
126                    *self = Binding::Set(nested);
127                    self.merge(c, span, remaining_path, value);
128                }
129
130                _ => c.emit_error(&value, ErrorKind::UnmergeableValue),
131            },
132
133            // If the value is nested further, it is simply inserted into the
134            // bindings with its full path and resolved recursively further
135            // down.
136            Binding::Set(existing) if remaining_path.peek().is_some() => {
137                existing.entries.push((span, remaining_path, value))
138            }
139
140            Binding::Set(existing) => {
141                if let ast::Expr::AttrSet(new) = value {
142                    existing.inherits.extend(ast::HasEntry::inherits(&new));
143                    existing
144                        .entries
145                        .extend(ast::HasEntry::attrpath_values(&new).map(|entry| {
146                            let span = c.span_for(&entry);
147                            (
148                                span,
149                                entry.attrpath().unwrap().attrs().peekable(),
150                                entry.value().unwrap(),
151                            )
152                        }));
153                } else {
154                    // This branch is unreachable because in cases where the
155                    // path is empty (i.e. there is no further nesting), the
156                    // previous try_merge function already verified that the
157                    // expression is an attribute set.
158
159                    // TODO(tazjin): Consider making this branch live by
160                    // shuffling that check around and emitting a static error
161                    // here instead of a runtime error.
162                    unreachable!()
163                }
164            }
165        }
166    }
167}
168
169enum KeySlot {
170    /// There is no key slot (`let`-expressions do not emit their key).
171    None { name: SmolStr },
172
173    /// The key is statically known and has a slot.
174    Static { slot: LocalIdx, name: SmolStr },
175
176    /// The key is dynamic, i.e. only known at runtime, and must be compiled
177    /// into its slot.
178    Dynamic { slot: LocalIdx, attr: ast::Attr },
179}
180
181struct TrackedBinding {
182    key_slot: KeySlot,
183    value_slot: LocalIdx,
184    binding: Binding,
185}
186
187impl TrackedBinding {
188    /// Does this binding match the given key?
189    ///
190    /// Used to determine which binding to merge another one into.
191    fn matches(&self, key: &str) -> bool {
192        match &self.key_slot {
193            KeySlot::None { name } => name == key,
194            KeySlot::Static { name, .. } => name == key,
195            KeySlot::Dynamic { .. } => false,
196        }
197    }
198}
199
200struct TrackedBindings {
201    bindings: Vec<TrackedBinding>,
202}
203
204impl TrackedBindings {
205    fn new() -> Self {
206        TrackedBindings { bindings: vec![] }
207    }
208
209    /// Attempt to merge an entry into an existing matching binding, assuming
210    /// that the provided binding is mergable (i.e. either a nested key or an
211    /// attribute set literal).
212    ///
213    /// Returns true if the binding was merged, false if it needs to be compiled
214    /// separately as a new binding.
215    fn try_merge(
216        &mut self,
217        c: &mut Compiler,
218        span: Span,
219        name: &ast::Attr,
220        mut remaining_path: PeekableAttrs,
221        value: ast::Expr,
222    ) -> bool {
223        // If the path has no more entries, and if the entry is not an
224        // attribute set literal, the entry can not be merged.
225        if remaining_path.peek().is_none() && !matches!(value, ast::Expr::AttrSet(_)) {
226            return false;
227        }
228
229        // If the first element of the path is not statically known, the entry
230        // can not be merged.
231        let name = match expr_static_attr_str(name) {
232            Some(name) => name,
233            None => return false,
234        };
235
236        // If there is no existing binding with this key, the entry can not be
237        // merged.
238        // TODO: benchmark whether using a map or something is useful over the
239        // `find` here
240        let binding = match self.bindings.iter_mut().find(|b| b.matches(&name)) {
241            Some(b) => b,
242            None => return false,
243        };
244
245        // No more excuses ... the binding can be merged!
246        binding.binding.merge(c, span, remaining_path, value);
247
248        true
249    }
250
251    /// Add a completely new binding to the tracked bindings.
252    fn track_new(&mut self, key_slot: KeySlot, value_slot: LocalIdx, binding: Binding) {
253        self.bindings.push(TrackedBinding {
254            key_slot,
255            value_slot,
256            binding,
257        });
258    }
259}
260
261/// Wrapper around the `ast::HasEntry` trait as that trait can not be
262/// implemented for custom types.
263trait HasEntryProxy {
264    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>>;
265
266    fn attributes<'a>(
267        &self,
268        file: &'a codemap::File,
269    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a>;
270}
271
272impl<N: HasEntry> HasEntryProxy for N {
273    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
274        Box::new(ast::HasEntry::inherits(self))
275    }
276
277    fn attributes<'a>(
278        &self,
279        file: &'a codemap::File,
280    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
281        Box::new(ast::HasEntry::attrpath_values(self).map(move |entry| {
282            (
283                entry.span_for(file),
284                entry.attrpath().unwrap().attrs().peekable(),
285                entry.value().unwrap(),
286            )
287        }))
288    }
289}
290
291impl HasEntryProxy for AttributeSet {
292    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
293        Box::new(self.inherits.clone().into_iter())
294    }
295
296    fn attributes<'a>(
297        &self,
298        _: &'a codemap::File,
299    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
300        Box::new(self.entries.clone().into_iter())
301    }
302}
303
304/// AST-traversing functions related to bindings.
305impl Compiler<'_, '_> {
306    /// Compile all inherits of a node with entries that do *not* have a
307    /// namespace to inherit from, and return the remaining ones that do.
308    fn compile_plain_inherits<N>(
309        &mut self,
310        slot: LocalIdx,
311        kind: BindingsKind,
312        count: &mut usize,
313        node: &N,
314    ) -> Vec<(ast::Expr, SmolStr, Span)>
315    where
316        N: ToSpan + HasEntryProxy,
317    {
318        // Pass over all inherits, resolving only those without namespaces.
319        // Since they always resolve in a higher scope, we can just compile and
320        // declare them immediately.
321        //
322        // Inherits with namespaces are returned to the caller.
323        let mut inherit_froms: Vec<(ast::Expr, SmolStr, Span)> = vec![];
324
325        for inherit in node.inherits() {
326            if inherit.attrs().peekable().peek().is_none() {
327                self.emit_warning(&inherit, WarningKind::EmptyInherit);
328                continue;
329            }
330
331            match inherit.from() {
332                // Within a `let` binding, inheriting from the outer scope is a
333                // no-op *if* there are no dynamic bindings.
334                None if !kind.is_attrs() && !self.has_dynamic_ancestor() => {
335                    self.emit_warning(&inherit, WarningKind::UselessInherit);
336                    continue;
337                }
338
339                None => {
340                    for attr in inherit.attrs() {
341                        let name = match expr_static_attr_str(&attr) {
342                            Some(name) => name,
343                            None => {
344                                self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
345                                continue;
346                            }
347                        };
348
349                        // If the identifier resolves statically in a `let`, it
350                        // has precedence over dynamic bindings, and the inherit
351                        // is useless.
352                        if kind == BindingsKind::LetIn
353                            && matches!(
354                                self.scope_mut().resolve_local(&name),
355                                LocalPosition::Known(_)
356                            )
357                        {
358                            self.emit_warning(&attr, WarningKind::UselessInherit);
359                            continue;
360                        }
361
362                        *count += 1;
363
364                        // Place key on the stack when compiling attribute sets.
365                        if kind.is_attrs() {
366                            self.emit_constant(name.as_str().into(), &attr);
367                            let span = self.span_for(&attr);
368                            self.scope_mut().declare_phantom(span, true);
369                        }
370
371                        // Place the value on the stack. Note that because plain
372                        // inherits are always in the outer scope, the slot of
373                        // *this* scope itself is used.
374                        self.compile_identifier_access(slot, &name, &attr);
375
376                        // In non-recursive attribute sets, the key slot must be
377                        // a phantom (i.e. the identifier can not be resolved in
378                        // this scope).
379                        let idx = if kind == BindingsKind::Attrs {
380                            let span = self.span_for(&attr);
381                            self.scope_mut().declare_phantom(span, false)
382                        } else {
383                            self.declare_local(&attr, name)
384                        };
385
386                        self.scope_mut().mark_initialised(idx);
387                    }
388                }
389
390                Some(from) => {
391                    for attr in inherit.attrs() {
392                        let name = match expr_static_attr_str(&attr) {
393                            Some(name) => name,
394                            None => {
395                                self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
396                                continue;
397                            }
398                        };
399
400                        *count += 1;
401                        inherit_froms.push((from.expr().unwrap(), name, self.span_for(&attr)));
402                    }
403                }
404            }
405        }
406
407        inherit_froms
408    }
409
410    /// Declare all namespaced inherits, that is inherits which are inheriting
411    /// values from an attribute set.
412    ///
413    /// This only ensures that the locals stack is aware of the inherits, it
414    /// does not yet emit bytecode that places them on the stack. This is up to
415    /// the owner of the `bindings` vector, which this function will populate.
416    fn declare_namespaced_inherits(
417        &mut self,
418        kind: BindingsKind,
419        inherit_froms: Vec<(ast::Expr, SmolStr, Span)>,
420        bindings: &mut TrackedBindings,
421    ) {
422        for (from, name, span) in inherit_froms {
423            let key_slot = if kind.is_attrs() {
424                // In an attribute set, the keys themselves are placed on the
425                // stack but their stack slot is inaccessible (it is only
426                // consumed by `OpAttrs`).
427                KeySlot::Static {
428                    slot: self.scope_mut().declare_phantom(span, false),
429                    name: name.clone(),
430                }
431            } else {
432                KeySlot::None { name: name.clone() }
433            };
434
435            let value_slot = match kind {
436                // In recursive scopes, the value needs to be accessible on the
437                // stack.
438                BindingsKind::LetIn | BindingsKind::RecAttrs => {
439                    self.declare_local(&span, name.clone())
440                }
441
442                // In non-recursive attribute sets, the value is inaccessible
443                // (only consumed by `OpAttrs`).
444                BindingsKind::Attrs => self.scope_mut().declare_phantom(span, false),
445            };
446
447            bindings.track_new(
448                key_slot,
449                value_slot,
450                Binding::InheritFrom {
451                    namespace: from,
452                    name,
453                    span,
454                },
455            );
456        }
457    }
458
459    /// Declare all regular bindings (i.e. `key = value;`) in a bindings scope,
460    /// but do not yet compile their values.
461    fn declare_bindings<N>(
462        &mut self,
463        kind: BindingsKind,
464        count: &mut usize,
465        bindings: &mut TrackedBindings,
466        node: &N,
467    ) where
468        N: ToSpan + HasEntryProxy,
469    {
470        for (span, mut path, value) in node.attributes(self.file) {
471            let key = path.next().unwrap();
472
473            if bindings.try_merge(self, span, &key, path.clone(), value.clone()) {
474                // Binding is nested, or already exists and was merged, move on.
475                continue;
476            }
477
478            *count += 1;
479
480            let key_span = self.span_for(&key);
481            let key_slot = match expr_static_attr_str(&key) {
482                Some(name) if kind.is_attrs() => KeySlot::Static {
483                    name,
484                    slot: self.scope_mut().declare_phantom(key_span, false),
485                },
486
487                Some(name) => KeySlot::None { name },
488
489                None if kind.is_attrs() => KeySlot::Dynamic {
490                    attr: key,
491                    slot: self.scope_mut().declare_phantom(key_span, false),
492                },
493
494                None => {
495                    self.emit_error(&key, ErrorKind::DynamicKeyInScope("let-expression"));
496                    continue;
497                }
498            };
499
500            let value_slot = match kind {
501                BindingsKind::LetIn | BindingsKind::RecAttrs => match &key_slot {
502                    // In recursive scopes, the value needs to be accessible on the
503                    // stack if it is statically known
504                    KeySlot::None { name } | KeySlot::Static { name, .. } => {
505                        self.declare_local(&key_span, name.as_str())
506                    }
507
508                    // Dynamic values are never resolvable (as their names are
509                    // of course only known at runtime).
510                    //
511                    // Note: This branch is unreachable in `let`-expressions.
512                    KeySlot::Dynamic { .. } => self.scope_mut().declare_phantom(key_span, false),
513                },
514
515                // In non-recursive attribute sets, the value is inaccessible
516                // (only consumed by `OpAttrs`).
517                BindingsKind::Attrs => self.scope_mut().declare_phantom(key_span, false),
518            };
519
520            let binding = if path.peek().is_some() {
521                Binding::Set(AttributeSet {
522                    span,
523                    kind: BindingsKind::Attrs,
524                    inherits: vec![],
525                    entries: vec![(span, path, value)],
526                })
527            } else {
528                Binding::Plain { expr: value }
529            };
530
531            bindings.track_new(key_slot, value_slot, binding);
532        }
533    }
534
535    /// Compile attribute set literals into equivalent bytecode.
536    ///
537    /// This is complicated by a number of features specific to Nix attribute
538    /// sets, most importantly:
539    ///
540    /// 1. Keys can be dynamically constructed through interpolation.
541    /// 2. Keys can refer to nested attribute sets.
542    /// 3. Attribute sets can (optionally) be recursive.
543    pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &ast::AttrSet) {
544        // Open a scope to track the positions of the temporaries used by the
545        // `OpAttrs` instruction.
546        self.scope_mut().begin_scope();
547
548        let kind = if node.rec_token().is_some() {
549            BindingsKind::RecAttrs
550        } else {
551            BindingsKind::Attrs
552        };
553
554        self.compile_bindings(slot, kind, node);
555
556        // Remove the temporary scope, but do not emit any additional cleanup
557        // (OpAttrs consumes all of these locals).
558        self.scope_mut().end_scope();
559    }
560
561    /// Emit definitions for all variables in the top-level global env passed to the evaluation (eg
562    /// local variables in the REPL)
563    pub(super) fn compile_env(&mut self, env: &FxHashMap<SmolStr, Value>) {
564        for (name, value) in env {
565            self.scope_mut().declare_constant(name.to_string());
566            self.emit_constant(value.clone(), &EntireFile);
567        }
568    }
569
570    /// Actually binds all tracked bindings by emitting the bytecode that places
571    /// them in their stack slots.
572    fn bind_values(&mut self, bindings: TrackedBindings) {
573        let mut value_indices: Vec<LocalIdx> = vec![];
574
575        for binding in bindings.bindings.into_iter() {
576            value_indices.push(binding.value_slot);
577
578            match binding.key_slot {
579                KeySlot::None { .. } => {} // nothing to do here
580
581                KeySlot::Static { slot, name } => {
582                    let span = self.scope()[slot].span;
583                    self.emit_constant(name.as_str().into(), &OrEntireFile(span));
584                    self.scope_mut().mark_initialised(slot);
585                }
586
587                KeySlot::Dynamic { slot, attr } => {
588                    self.compile_attr(slot, &attr);
589                    self.scope_mut().mark_initialised(slot);
590                }
591            }
592
593            match binding.binding {
594                // This entry is an inherit (from) expr. The value is placed on
595                // the stack by selecting an attribute.
596                Binding::InheritFrom {
597                    namespace,
598                    name,
599                    span,
600                } => {
601                    // Create a thunk wrapping value (which may be one as well)
602                    // to avoid forcing the from expr too early.
603                    self.thunk(binding.value_slot, &namespace, |c, s| {
604                        c.compile(s, namespace.clone());
605                        c.emit_force(&namespace);
606
607                        c.emit_constant(name.as_str().into(), &span);
608                        c.push_op(Op::AttrsSelect, &span);
609                    })
610                }
611
612                // Binding is "just" a plain expression that needs to be
613                // compiled.
614                Binding::Plain { expr } => self.compile(binding.value_slot, expr),
615
616                // Binding is a merged or nested attribute set, and needs to be
617                // recursively compiled as another binding.
618                Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, _| {
619                    c.scope_mut().begin_scope();
620                    c.compile_bindings(binding.value_slot, set.kind, &set);
621                    c.scope_mut().end_scope();
622                }),
623            }
624
625            // Any code after this point will observe the value in the right
626            // stack slot, so mark it as initialised.
627            self.scope_mut().mark_initialised(binding.value_slot);
628        }
629
630        // Final pass to emit finaliser instructions if necessary.
631        for idx in value_indices {
632            if self.scope()[idx].needs_finaliser {
633                let stack_idx = self.scope().stack_index(idx);
634                let span = self.scope()[idx].span;
635                self.push_op(Op::Finalise, &OrEntireFile(span));
636                self.push_uvarint(stack_idx.0 as u64)
637            }
638        }
639    }
640
641    fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
642    where
643        N: ToSpan + HasEntryProxy,
644    {
645        let mut count = 0;
646        self.scope_mut().begin_scope();
647
648        // Vector to track all observed bindings.
649        let mut bindings = TrackedBindings::new();
650
651        let inherit_froms = self.compile_plain_inherits(slot, kind, &mut count, node);
652        self.declare_namespaced_inherits(kind, inherit_froms, &mut bindings);
653        self.declare_bindings(kind, &mut count, &mut bindings, node);
654
655        // Check if we can bail out on empty bindings
656        if count == 0 {
657            // still need an attrset to exist, but it is empty.
658            if kind.is_attrs() {
659                self.emit_constant(Value::Attrs(Box::new(NixAttrs::empty())), node);
660                return;
661            }
662
663            self.emit_warning(node, WarningKind::EmptyLet);
664            return;
665        }
666
667        // Actually bind values and ensure they are on the stack.
668        self.bind_values(bindings);
669
670        if kind.is_attrs() {
671            self.push_op(Op::Attrs, node);
672            self.push_uvarint(count as u64);
673        }
674    }
675
676    /// Compile a standard `let ...; in ...` expression.
677    ///
678    /// Unless in a non-standard scope, the encountered values are simply pushed
679    /// on the stack and their indices noted in the entries vector.
680    pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &ast::LetIn) {
681        self.compile_bindings(slot, BindingsKind::LetIn, node);
682
683        // Deal with the body, then clean up the locals afterwards.
684        self.compile(slot, node.body().unwrap());
685        self.cleanup_scope(node);
686    }
687
688    pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &ast::LegacyLet) {
689        self.emit_warning(node, WarningKind::DeprecatedLegacyLet);
690        self.scope_mut().begin_scope();
691        self.compile_bindings(slot, BindingsKind::RecAttrs, node);
692
693        // Remove the temporary scope, but do not emit any additional cleanup
694        // (OpAttrs consumes all of these locals).
695        self.scope_mut().end_scope();
696
697        self.emit_constant("body".into(), node);
698        self.push_op(Op::AttrsSelect, node);
699    }
700
701    /// Is the given identifier defined *by the user* in any current scope?
702    pub(super) fn is_user_defined(&mut self, ident: &str) -> bool {
703        matches!(
704            self.scope_mut().resolve_local(ident),
705            LocalPosition::Known(_) | LocalPosition::Recursive(_)
706        )
707    }
708
709    /// Resolve and compile access to an identifier in the scope.
710    fn compile_identifier_access<N: ToSpan + Clone>(
711        &mut self,
712        slot: LocalIdx,
713        ident: &str,
714        node: &N,
715    ) {
716        match self.scope_mut().resolve_local(ident) {
717            LocalPosition::Unknown => {
718                // Are we possibly dealing with an upvalue?
719                if let Some(idx) = self.resolve_upvalue_for_use(self.contexts.len() - 1, ident) {
720                    self.push_op(Op::GetUpvalue, node);
721                    self.push_uvarint(idx.0 as u64);
722                    return;
723                }
724
725                // Globals are the "upmost upvalues": they behave
726                // exactly like a `let ... in` prepended to the
727                // program's text, and the global scope is nothing
728                // more than the parent scope of the root scope.
729                if let Some(global) = self.globals.get(ident) {
730                    self.emit_constant(global.clone(), &self.span_for(node));
731                    return;
732                }
733
734                // If there is a non-empty `with`-stack (or a parent context
735                // with one), emit a runtime dynamic resolution instruction.
736                //
737                // Since it is possible for users to e.g. assign a variable to a
738                // dynamic resolution without actually using it, this operation
739                // is wrapped in an extra thunk.
740                if self.has_dynamic_ancestor() {
741                    self.thunk(slot, node, |c, _| {
742                        c.context_mut().captures_with_stack = true;
743                        c.emit_constant(ident.into(), node);
744                        c.push_op(Op::ResolveWith, node);
745                    });
746                    return;
747                }
748
749                // Otherwise, this variable is missing.
750                self.emit_error(node, ErrorKind::UnknownStaticVariable);
751            }
752
753            LocalPosition::Known(idx) => {
754                self.scope_mut().mark_used(idx);
755
756                let stack_idx = self.scope().stack_index(idx);
757                self.push_op(Op::GetLocal, node);
758                self.push_uvarint(stack_idx.0 as u64);
759            }
760
761            // This identifier is referring to a value from the same scope which
762            // is not yet defined. This identifier access must be thunked.
763            LocalPosition::Recursive(idx) => {
764                self.scope_mut().mark_used(idx);
765                self.thunk(slot, node, move |compiler, _| {
766                    let upvalue_idx =
767                        compiler.add_upvalue(compiler.contexts.len() - 1, UpvalueKind::Local(idx));
768                    compiler.push_op(Op::GetUpvalue, node);
769                    compiler.push_uvarint(upvalue_idx.0 as u64);
770                })
771            }
772        };
773    }
774
775    pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &ast::Ident) {
776        let ident = node.ident_token().unwrap();
777        self.compile_identifier_access(slot, ident.text(), node);
778    }
779}
780
781/// Private compiler helpers related to bindings.
782impl Compiler<'_, '_> {
783    // ATTN: Also marks local backing the upvalue as used if any
784    fn resolve_upvalue_for_use(&mut self, ctx_idx: usize, name: &str) -> Option<UpvalueIdx> {
785        if ctx_idx == 0 {
786            // There can not be any upvalue at the outermost context.
787            return None;
788        }
789
790        // Determine whether the upvalue is a local in the enclosing context.
791        match self.contexts[ctx_idx - 1].scope.resolve_local(name) {
792            // recursive upvalues are dealt with the same way as standard known
793            // ones, as thunks and closures are guaranteed to be placed on the
794            // stack (i.e. in the right position) *during* their runtime
795            // construction
796            LocalPosition::Known(idx) | LocalPosition::Recursive(idx) => {
797                self.contexts[ctx_idx - 1].scope.mark_used(idx);
798                return Some(self.add_upvalue(ctx_idx, UpvalueKind::Local(idx)));
799            }
800
801            LocalPosition::Unknown => { /* continue below */ }
802        };
803
804        // If the upvalue comes from even further up, we need to recurse to make
805        // sure that the upvalues are created at each level.
806        if let Some(idx) = self.resolve_upvalue_for_use(ctx_idx - 1, name) {
807            return Some(self.add_upvalue(ctx_idx, UpvalueKind::Upvalue(idx)));
808        }
809
810        None
811    }
812
813    fn add_upvalue(&mut self, ctx_idx: usize, kind: UpvalueKind) -> UpvalueIdx {
814        // If there is already an upvalue closing over the specified index,
815        // retrieve that instead.
816        for (idx, existing) in self.contexts[ctx_idx].scope.upvalues.iter().enumerate() {
817            if existing.kind == kind {
818                return UpvalueIdx(idx);
819            }
820        }
821
822        self.contexts[ctx_idx].scope.upvalues.push(Upvalue { kind });
823
824        let idx = UpvalueIdx(self.contexts[ctx_idx].lambda.upvalue_count);
825        self.contexts[ctx_idx].lambda.upvalue_count += 1;
826        idx
827    }
828}