fix(tvix/eval): inherit scope poisoning data in nested contexts

Scope poisoning must be inherited across lambda context boundaries,
e.g. if an outer scope has a poisoned `null`, any lambdas defined on
the same level must reference that poisoned identifier correctly.

Change-Id: I1aac64e1c048a6f3bacadb6d78ed295fa439e8b4
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6410
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2022-09-02 01:19:53 +03:00 committed by tazjin
parent 0a13d267f0
commit 5ee89bcf5c
4 changed files with 36 additions and 4 deletions

View file

@ -53,6 +53,13 @@ impl LambdaCtx {
scope: Default::default(), scope: Default::default(),
} }
} }
fn inherit(&self) -> Self {
LambdaCtx {
lambda: Lambda::new_anonymous(),
scope: self.scope.inherit(),
}
}
} }
/// Alias for the map of globally available functions that should /// Alias for the map of globally available functions that should
@ -836,9 +843,7 @@ impl Compiler<'_> {
} }
fn compile_lambda(&mut self, slot: Option<LocalIdx>, node: ast::Lambda) { fn compile_lambda(&mut self, slot: Option<LocalIdx>, node: ast::Lambda) {
// Open new lambda context in compiler, which has its own self.new_context();
// scope etc.
self.contexts.push(LambdaCtx::new());
self.begin_scope(); self.begin_scope();
// Compile the function itself // Compile the function itself
@ -912,7 +917,7 @@ impl Compiler<'_> {
N: AstNode + Clone, N: AstNode + Clone,
F: FnOnce(&mut Compiler, &N, Option<LocalIdx>), F: FnOnce(&mut Compiler, &N, Option<LocalIdx>),
{ {
self.contexts.push(LambdaCtx::new()); self.new_context();
self.begin_scope(); self.begin_scope();
content(self, node, slot); content(self, node, slot);
self.end_scope(node); self.end_scope(node);
@ -1011,10 +1016,14 @@ impl Compiler<'_> {
} }
} }
/// Increase the scope depth of the current function (e.g. within
/// a new bindings block, or `with`-scope).
fn begin_scope(&mut self) { fn begin_scope(&mut self) {
self.scope_mut().scope_depth += 1; self.scope_mut().scope_depth += 1;
} }
/// Decrease scope depth of the current function and emit
/// instructions to clean up the stack at runtime.
fn end_scope<N: AstNode>(&mut self, node: &N) { fn end_scope<N: AstNode>(&mut self, node: &N) {
debug_assert!(self.scope().scope_depth != 0, "can not end top scope"); debug_assert!(self.scope().scope_depth != 0, "can not end top scope");
@ -1055,6 +1064,15 @@ impl Compiler<'_> {
} }
} }
/// Open a new lambda context within which to compile a function,
/// closure or thunk.
fn new_context(&mut self) {
// This must inherit the scope-poisoning status of the parent
// in order for upvalue resolution to work correctly with
// poisoned identifiers.
self.contexts.push(self.context().inherit());
}
/// Declare a local variable known in the scope that is being /// Declare a local variable known in the scope that is being
/// compiled by pushing it to the locals. This is used to /// compiled by pushing it to the locals. This is used to
/// determine the stack offset of variables. /// determine the stack offset of variables.

View file

@ -152,6 +152,15 @@ impl Scope {
} }
} }
/// Inherit scope details from a parent scope (required for
/// correctly nesting scopes in lambdas and thunks when special
/// scope features like poisoning are present).
pub fn inherit(&self) -> Self {
let mut scope = Self::default();
scope.poisoned_tokens = self.poisoned_tokens.clone();
scope
}
/// Check whether a given token is poisoned. /// Check whether a given token is poisoned.
pub fn is_poisoned(&self, name: &str) -> bool { pub fn is_poisoned(&self, name: &str) -> bool {
self.poisoned_tokens.contains_key(name) self.poisoned_tokens.contains_key(name)

View file

@ -0,0 +1 @@
42

View file

@ -0,0 +1,4 @@
let
null = 1;
f = n: n + null;
in f 41