diff options
Diffstat (limited to 'tvix/eval')
-rw-r--r-- | tvix/eval/src/compiler/bindings.rs | 55 |
1 files changed, 39 insertions, 16 deletions
diff --git a/tvix/eval/src/compiler/bindings.rs b/tvix/eval/src/compiler/bindings.rs index 7ee8ef8a84f0..659dce299a1a 100644 --- a/tvix/eval/src/compiler/bindings.rs +++ b/tvix/eval/src/compiler/bindings.rs @@ -63,19 +63,15 @@ impl Compiler<'_> { ) where N: ToSpan + ast::HasEntry, { - // First pass to find all plain inherits (if they are not useless). - // Since they always resolve to a higher scope, we can just compile and - // declare them immediately. This needs to happen *before* we declare - // any other locals in the scope or the stack order gets messed up. - // While we are looping through the inherits, already note all inherit - // (from) expressions, that may very well resolve recursively and need - // to be compiled like normal let in bindings. + // First pass over all inherits resolves only those without + // namespaces. Since they always resolve to a higher scope, we + // can just compile and declare them immediately. let mut inherit_froms: Vec<(ast::Expr, String, Span)> = vec![]; + for inherit in node.inherits() { match inherit.from() { - // Within a `let` binding, inheriting from the outer - // scope is a no-op *if* the identifier can be - // statically resolved. + // Within a `let` binding, inheriting from the outer scope is a + // no-op *if* the scope is fully static. None if !kind.is_attrs() && !self.scope().has_with() => { self.emit_warning(&inherit, WarningKind::UselessInherit); continue; @@ -91,8 +87,6 @@ impl Compiler<'_> { } }; - *count += 1; - // If the identifier resolves statically in a // `let`, it has precedence over dynamic // bindings, and the inherit is useless. @@ -106,14 +100,30 @@ impl Compiler<'_> { continue; } - if kind == BindingsKind::RecAttrs { + *count += 1; + + // Place key on the stack when compiling attribute sets. + if kind.is_attrs() { self.emit_constant(Value::String(SmolStr::new(&name).into()), &attr); let span = self.span_for(&attr); self.scope_mut().declare_phantom(span, true); } + // Place the value on the stack. Note that because plain + // inherits are always in the outer scope, the slot of + // *this* scope itself is used. self.compile_identifier_access(slot, &name, &attr); - let idx = self.declare_local(&attr, &name); + + // In non-recursive attribute sets, the key slot must be + // a phantom (i.e. the identifier can not be resolved in + // this scope). + let idx = if kind == BindingsKind::Attrs { + let span = self.span_for(&attr); + self.scope_mut().declare_phantom(span, false) + } else { + self.declare_local(&attr, &name) + }; + self.scope_mut().mark_initialised(idx); } } @@ -135,9 +145,14 @@ impl Compiler<'_> { } } - // Begin with the inherit (from)s since they all become a thunk anyway + // Second pass over the inherits that have a namespace, to declare them + // in the tracked bindings. Compiling the values into their slots is the + // job of the caller. for (from, name, span) in inherit_froms { let key_slot = if kind.is_attrs() { + // In an attribute set, the keys themselves are placed + // on the stack but their stack slot is inaccessible + // (it is only consumed by `OpAttrs`). Some(KeySlot { slot: self.scope_mut().declare_phantom(span, false), name: SmolStr::new(&name), @@ -146,7 +161,15 @@ impl Compiler<'_> { None }; - let value_slot = self.declare_local(&span, &name); + let value_slot = match kind { + // In recursive scopes, the value needs to be + // accessible on the stack. + BindingsKind::LetIn | BindingsKind::RecAttrs => self.declare_local(&span, &name), + + // In non-recursive attribute sets, the value is + // inaccessible (only consumed by `OpAttrs`). + BindingsKind::Attrs => self.scope_mut().declare_phantom(span, false), + }; bindings.push(TrackedBinding { key_slot, |