feat(tvix/eval): emit warnings for unused local bindings

Change-Id: I6e876a8f4d062297abae812b14ed8ec17a502f2c
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6237
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
This commit is contained in:
Vincent Ambo 2022-08-23 11:26:00 +03:00 committed by tazjin
parent f7305eed47
commit 3ed40b4eea
2 changed files with 50 additions and 20 deletions

View file

@ -40,12 +40,18 @@ struct Local {
// of `let`-expressions. // of `let`-expressions.
name: String, name: String,
// Syntax node at which this local was declared.
node: Option<rnix::SyntaxNode>,
// Scope depth of this local. // Scope depth of this local.
depth: usize, depth: usize,
// Phantom locals are not actually accessible by users (e.g. // Phantom locals are not actually accessible by users (e.g.
// intermediate values used for `with`). // intermediate values used for `with`).
phantom: bool, phantom: bool,
// Is this local known to have been used at all?
used: bool,
} }
/// Represents a stack offset containing keys which are currently /// Represents a stack offset containing keys which are currently
@ -599,7 +605,10 @@ impl Compiler {
self.compile(from.expr().unwrap()); self.compile(from.expr().unwrap());
self.emit_literal_ident(&ident); self.emit_literal_ident(&ident);
self.chunk.push_op(OpCode::OpAttrsSelect); self.chunk.push_op(OpCode::OpAttrsSelect);
self.declare_local(ident.ident_token().unwrap().text()); self.declare_local(
ident.syntax().clone(),
ident.ident_token().unwrap().text(),
);
} }
} }
} }
@ -619,7 +628,10 @@ impl Compiler {
} }
self.compile(entry.value().unwrap()); self.compile(entry.value().unwrap());
self.declare_local(path.pop().unwrap()); self.declare_local(
entry.attrpath().unwrap().syntax().clone(),
path.pop().unwrap(),
);
} }
// Deal with the body, then clean up the locals afterwards. // Deal with the body, then clean up the locals afterwards.
@ -719,22 +731,21 @@ impl Compiler {
} }
fn end_scope(&mut self) { fn end_scope(&mut self) {
let mut scope = &mut self.scope; debug_assert!(self.scope.scope_depth != 0, "can not end top scope");
debug_assert!(scope.scope_depth != 0, "can not end top scope");
// If this scope poisoned any builtins or special identifiers, // If this scope poisoned any builtins or special identifiers,
// they need to be reset. // they need to be reset.
if scope.poisoned_true == scope.scope_depth { if self.scope.poisoned_true == self.scope.scope_depth {
scope.poisoned_true = 0; self.scope.poisoned_true = 0;
} }
if scope.poisoned_false == scope.scope_depth { if self.scope.poisoned_false == self.scope.scope_depth {
scope.poisoned_false = 0; self.scope.poisoned_false = 0;
} }
if scope.poisoned_null == scope.scope_depth { if self.scope.poisoned_null == self.scope.scope_depth {
scope.poisoned_null = 0; self.scope.poisoned_null = 0;
} }
scope.scope_depth -= 1; self.scope.scope_depth -= 1;
// When ending a scope, all corresponding locals need to be // When ending a scope, all corresponding locals need to be
// removed, but the value of the body needs to remain on the // removed, but the value of the body needs to remain on the
@ -743,29 +754,42 @@ impl Compiler {
// TL;DR - iterate from the back while things belonging to the // TL;DR - iterate from the back while things belonging to the
// ended scope still exist. // ended scope still exist.
while !scope.locals.is_empty() while !self.scope.locals.is_empty()
&& scope.locals[scope.locals.len() - 1].depth > scope.scope_depth && self.scope.locals[self.scope.locals.len() - 1].depth > self.scope.scope_depth
{ {
pops += 1; pops += 1;
scope.locals.pop();
// While removing the local, analyse whether it has been
// accessed while it existed and emit a warning to the
// user otherwise.
if let Some(Local {
node: Some(node),
used,
..
}) = self.scope.locals.pop()
{
if !used {
self.emit_warning(node, WarningKind::UnusedBinding);
}
}
} }
if pops > 0 { if pops > 0 {
self.chunk.push_op(OpCode::OpCloseScope(pops)); self.chunk.push_op(OpCode::OpCloseScope(pops));
} }
while !scope.with_stack.is_empty() while !self.scope.with_stack.is_empty()
&& scope.with_stack[scope.with_stack.len() - 1].depth > scope.scope_depth && self.scope.with_stack[self.scope.with_stack.len() - 1].depth > self.scope.scope_depth
{ {
self.chunk.push_op(OpCode::OpPopWith); self.chunk.push_op(OpCode::OpPopWith);
scope.with_stack.pop(); self.scope.with_stack.pop();
} }
} }
/// 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.
fn declare_local<S: Into<String>>(&mut self, name: S) { fn declare_local<S: Into<String>>(&mut self, node: rnix::SyntaxNode, name: S) {
// Set up scope poisoning if required. // Set up scope poisoning if required.
let name = name.into(); let name = name.into();
match name.as_str() { match name.as_str() {
@ -786,24 +810,29 @@ impl Compiler {
self.scope.locals.push(Local { self.scope.locals.push(Local {
name: name.into(), name: name.into(),
node: Some(node),
depth: self.scope.scope_depth, depth: self.scope.scope_depth,
phantom: false, phantom: false,
used: false,
}); });
} }
fn declare_phantom(&mut self) { fn declare_phantom(&mut self) {
self.scope.locals.push(Local { self.scope.locals.push(Local {
name: "".into(), name: "".into(),
node: None,
depth: self.scope.scope_depth, depth: self.scope.scope_depth,
phantom: true, phantom: true,
used: true,
}); });
} }
fn resolve_local(&mut self, name: &str) -> Option<usize> { fn resolve_local(&mut self, name: &str) -> Option<usize> {
let scope = &self.scope; let scope = &mut self.scope;
for (idx, local) in scope.locals.iter().enumerate().rev() { for (idx, local) in scope.locals.iter_mut().enumerate().rev() {
if !local.phantom && local.name == name { if !local.phantom && local.name == name {
local.used = true;
return Some(idx); return Some(idx);
} }
} }

View file

@ -5,6 +5,7 @@
pub enum WarningKind { pub enum WarningKind {
DeprecatedLiteralURL, DeprecatedLiteralURL,
UselessInherit, UselessInherit,
UnusedBinding,
} }
#[derive(Debug)] #[derive(Debug)]