about summary refs log tree commit diff
path: root/tvix/eval/src/compiler/bindings.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/compiler/bindings.rs')
-rw-r--r--tvix/eval/src/compiler/bindings.rs131
1 files changed, 63 insertions, 68 deletions
diff --git a/tvix/eval/src/compiler/bindings.rs b/tvix/eval/src/compiler/bindings.rs
index 31ab76aee8..634cc54022 100644
--- a/tvix/eval/src/compiler/bindings.rs
+++ b/tvix/eval/src/compiler/bindings.rs
@@ -226,7 +226,7 @@ impl TrackedBindings {
 
         // If the first element of the path is not statically known, the entry
         // can not be merged.
-        let name = match c.expr_static_attr_str(name) {
+        let name = match expr_static_attr_str(name) {
             Some(name) => name,
             None => return false,
         };
@@ -261,10 +261,10 @@ impl TrackedBindings {
 trait HasEntryProxy {
     fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>>;
 
-    fn attributes(
+    fn attributes<'a>(
         &self,
-        file: Arc<codemap::File>,
-    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)>>;
+        file: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a>;
 }
 
 impl<N: HasEntry> HasEntryProxy for N {
@@ -272,13 +272,13 @@ impl<N: HasEntry> HasEntryProxy for N {
         Box::new(ast::HasEntry::inherits(self))
     }
 
-    fn attributes(
+    fn attributes<'a>(
         &self,
-        file: Arc<codemap::File>,
-    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)>> {
+        file: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
         Box::new(ast::HasEntry::attrpath_values(self).map(move |entry| {
             (
-                entry.span_for(&file),
+                entry.span_for(file),
                 entry.attrpath().unwrap().attrs().peekable(),
                 entry.value().unwrap(),
             )
@@ -291,16 +291,16 @@ impl HasEntryProxy for AttributeSet {
         Box::new(self.inherits.clone().into_iter())
     }
 
-    fn attributes(
+    fn attributes<'a>(
         &self,
-        _: Arc<codemap::File>,
-    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)>> {
+        _: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
         Box::new(self.entries.clone().into_iter())
     }
 }
 
 /// AST-traversing functions related to bindings.
-impl Compiler<'_> {
+impl Compiler<'_, '_> {
     /// Compile all inherits of a node with entries that do *not* have a
     /// namespace to inherit from, and return the remaining ones that do.
     fn compile_plain_inherits<N>(
@@ -321,6 +321,11 @@ impl Compiler<'_> {
         let mut inherit_froms: Vec<(ast::Expr, SmolStr, Span)> = vec![];
 
         for inherit in node.inherits() {
+            if inherit.attrs().peekable().peek().is_none() {
+                self.emit_warning(&inherit, WarningKind::EmptyInherit);
+                continue;
+            }
+
             match inherit.from() {
                 // Within a `let` binding, inheriting from the outer scope is a
                 // no-op *if* there are no dynamic bindings.
@@ -331,7 +336,7 @@ impl Compiler<'_> {
 
                 None => {
                     for attr in inherit.attrs() {
-                        let name = match self.expr_static_attr_str(&attr) {
+                        let name = match expr_static_attr_str(&attr) {
                             Some(name) => name,
                             None => {
                                 self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
@@ -356,7 +361,7 @@ impl Compiler<'_> {
 
                         // Place key on the stack when compiling attribute sets.
                         if kind.is_attrs() {
-                            self.emit_constant(Value::String(name.clone().into()), &attr);
+                            self.emit_constant(name.as_str().into(), &attr);
                             let span = self.span_for(&attr);
                             self.scope_mut().declare_phantom(span, true);
                         }
@@ -382,7 +387,7 @@ impl Compiler<'_> {
 
                 Some(from) => {
                     for attr in inherit.attrs() {
-                        let name = match self.expr_static_attr_str(&attr) {
+                        let name = match expr_static_attr_str(&attr) {
                             Some(name) => name,
                             None => {
                                 self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
@@ -460,7 +465,7 @@ impl Compiler<'_> {
     ) where
         N: ToSpan + HasEntryProxy,
     {
-        for (span, mut path, value) in node.attributes(self.file.clone()) {
+        for (span, mut path, value) in node.attributes(self.file) {
             let key = path.next().unwrap();
 
             if bindings.try_merge(self, span, &key, path.clone(), value.clone()) {
@@ -471,7 +476,7 @@ impl Compiler<'_> {
             *count += 1;
 
             let key_span = self.span_for(&key);
-            let key_slot = match self.expr_static_attr_str(&key) {
+            let key_slot = match expr_static_attr_str(&key) {
                 Some(name) if kind.is_attrs() => KeySlot::Static {
                     name,
                     slot: self.scope_mut().declare_phantom(key_span, false),
@@ -564,7 +569,7 @@ impl Compiler<'_> {
 
                 KeySlot::Static { slot, name } => {
                     let span = self.scope()[slot].span;
-                    self.emit_constant(Value::String(name.into()), &span);
+                    self.emit_constant(name.as_str().into(), &span);
                     self.scope_mut().mark_initialised(slot);
                 }
 
@@ -585,17 +590,17 @@ impl Compiler<'_> {
                     // Create a thunk wrapping value (which may be one as well)
                     // to avoid forcing the from expr too early.
                     self.thunk(binding.value_slot, &namespace, |c, s| {
-                        c.compile(s, &namespace);
+                        c.compile(s, namespace.clone());
                         c.emit_force(&namespace);
 
-                        c.emit_constant(Value::String(name.into()), &span);
+                        c.emit_constant(name.as_str().into(), &span);
                         c.push_op(OpCode::OpAttrsSelect, &span);
                     })
                 }
 
                 // Binding is "just" a plain expression that needs to be
                 // compiled.
-                Binding::Plain { expr } => self.compile(binding.value_slot, &expr),
+                Binding::Plain { expr } => self.compile(binding.value_slot, expr),
 
                 // Binding is a merged or nested attribute set, and needs to be
                 // recursively compiled as another binding.
@@ -635,12 +640,28 @@ impl Compiler<'_> {
         self.declare_namespaced_inherits(kind, inherit_froms, &mut bindings);
         self.declare_bindings(kind, &mut count, &mut bindings, node);
 
+        // Check if we can bail out on empty bindings
+        if count == 0 {
+            // still need an attrset to exist, but it is empty.
+            if kind.is_attrs() {
+                self.emit_constant(Value::Attrs(Box::new(NixAttrs::empty())), node);
+                return;
+            }
+
+            self.emit_warning(node, WarningKind::EmptyLet);
+            return;
+        }
+
         // Actually bind values and ensure they are on the stack.
         self.bind_values(bindings);
 
         if kind.is_attrs() {
             self.push_op(OpCode::OpAttrs(Count(count)), node);
         }
+
+        if count == 0 {
+            self.unthunk();
+        }
     }
 
     /// Compile a standard `let ...; in ...` expression.
@@ -651,7 +672,7 @@ impl Compiler<'_> {
         self.compile_bindings(slot, BindingsKind::LetIn, node);
 
         // Deal with the body, then clean up the locals afterwards.
-        self.compile(slot, &node.body().unwrap());
+        self.compile(slot, node.body().unwrap());
         self.cleanup_scope(node);
     }
 
@@ -664,10 +685,18 @@ impl Compiler<'_> {
         // (OpAttrs consumes all of these locals).
         self.scope_mut().end_scope();
 
-        self.emit_constant(Value::String(SmolStr::new_inline("body").into()), node);
+        self.emit_constant("body".into(), node);
         self.push_op(OpCode::OpAttrsSelect, node);
     }
 
+    /// Is the given identifier defined *by the user* in any current scope?
+    pub(super) fn is_user_defined(&mut self, ident: &str) -> bool {
+        matches!(
+            self.scope_mut().resolve_local(ident),
+            LocalPosition::Known(_) | LocalPosition::Recursive(_)
+        )
+    }
+
     /// Resolve and compile access to an identifier in the scope.
     fn compile_identifier_access<N: ToSpan + Clone>(
         &mut self,
@@ -675,15 +704,6 @@ impl Compiler<'_> {
         ident: &str,
         node: &N,
     ) {
-        // If the identifier is a global, and it is not poisoned, emit the
-        // global directly.
-        if let Some(global) = self.globals.get(ident) {
-            if !self.scope().is_poisoned(ident) {
-                global.clone()(self, self.span_for(node));
-                return;
-            }
-        }
-
         match self.scope_mut().resolve_local(ident) {
             LocalPosition::Unknown => {
                 // Are we possibly dealing with an upvalue?
@@ -692,6 +712,15 @@ impl Compiler<'_> {
                     return;
                 }
 
+                // Globals are the "upmost upvalues": they behave
+                // exactly like a `let ... in` prepended to the
+                // program's text, and the global scope is nothing
+                // more than the parent scope of the root scope.
+                if let Some(global) = self.globals.get(ident) {
+                    self.emit_constant(global.clone(), &self.span_for(node));
+                    return;
+                }
+
                 // If there is a non-empty `with`-stack (or a parent context
                 // with one), emit a runtime dynamic resolution instruction.
                 //
@@ -701,7 +730,7 @@ impl Compiler<'_> {
                 if self.has_dynamic_ancestor() {
                     self.thunk(slot, node, |c, _| {
                         c.context_mut().captures_with_stack = true;
-                        c.emit_constant(Value::String(SmolStr::new(ident).into()), node);
+                        c.emit_constant(ident.into(), node);
                         c.push_op(OpCode::OpResolveWith, node);
                     });
                     return;
@@ -736,7 +765,7 @@ impl Compiler<'_> {
 }
 
 /// Private compiler helpers related to bindings.
-impl Compiler<'_> {
+impl Compiler<'_, '_> {
     fn resolve_upvalue<N: ToSpan>(
         &mut self,
         ctx_idx: usize,
@@ -794,38 +823,4 @@ impl Compiler<'_> {
         self.contexts[ctx_idx].lambda.upvalue_count += 1;
         idx
     }
-
-    /// Convert a non-dynamic string expression to a string if possible.
-    fn expr_static_str(&self, node: &ast::Str) -> Option<SmolStr> {
-        let mut parts = node.normalized_parts();
-
-        if parts.len() != 1 {
-            return None;
-        }
-
-        if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
-            return Some(SmolStr::new(&lit));
-        }
-
-        None
-    }
-
-    /// Convert the provided `ast::Attr` into a statically known string if
-    /// possible.
-    // TODO(tazjin): these should probably be SmolStr
-    fn expr_static_attr_str(&self, node: &ast::Attr) -> Option<SmolStr> {
-        match node {
-            ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
-            ast::Attr::Str(s) => self.expr_static_str(s),
-
-            // The dynamic node type is just a wrapper. C++ Nix does not care
-            // about the dynamic wrapper when determining whether the node
-            // itself is dynamic, it depends solely on the expression inside
-            // (i.e. `let ${"a"} = 1; in a` is valid).
-            ast::Attr::Dynamic(ref dynamic) => match dynamic.expr().unwrap() {
-                ast::Expr::Str(s) => self.expr_static_str(&s),
-                _ => None,
-            },
-        }
-    }
 }