Skip to main content

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