diff options
Diffstat (limited to 'tvix/eval/src/compiler/bindings.rs')
-rw-r--r-- | tvix/eval/src/compiler/bindings.rs | 131 |
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, - }, - } - } } |