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.
name: String,
// Syntax node at which this local was declared.
node: Option<rnix::SyntaxNode>,
// Scope depth of this local.
depth: usize,
// Phantom locals are not actually accessible by users (e.g.
// intermediate values used for `with`).
phantom: bool,
// Is this local known to have been used at all?
used: bool,
}
/// Represents a stack offset containing keys which are currently
@ -599,7 +605,10 @@ impl Compiler {
self.compile(from.expr().unwrap());
self.emit_literal_ident(&ident);
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.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.
@ -719,22 +731,21 @@ impl Compiler {
}
fn end_scope(&mut self) {
let mut scope = &mut self.scope;
debug_assert!(scope.scope_depth != 0, "can not end top scope");
debug_assert!(self.scope.scope_depth != 0, "can not end top scope");
// If this scope poisoned any builtins or special identifiers,
// they need to be reset.
if scope.poisoned_true == scope.scope_depth {
scope.poisoned_true = 0;
if self.scope.poisoned_true == self.scope.scope_depth {
self.scope.poisoned_true = 0;
}
if scope.poisoned_false == scope.scope_depth {
scope.poisoned_false = 0;
if self.scope.poisoned_false == self.scope.scope_depth {
self.scope.poisoned_false = 0;
}
if scope.poisoned_null == scope.scope_depth {
scope.poisoned_null = 0;
if self.scope.poisoned_null == self.scope.scope_depth {
self.scope.poisoned_null = 0;
}
scope.scope_depth -= 1;
self.scope.scope_depth -= 1;
// When ending a scope, all corresponding locals need to be
// 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
// ended scope still exist.
while !scope.locals.is_empty()
&& scope.locals[scope.locals.len() - 1].depth > scope.scope_depth
while !self.scope.locals.is_empty()
&& self.scope.locals[self.scope.locals.len() - 1].depth > self.scope.scope_depth
{
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 {
self.chunk.push_op(OpCode::OpCloseScope(pops));
}
while !scope.with_stack.is_empty()
&& scope.with_stack[scope.with_stack.len() - 1].depth > scope.scope_depth
while !self.scope.with_stack.is_empty()
&& self.scope.with_stack[self.scope.with_stack.len() - 1].depth > self.scope.scope_depth
{
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
/// compiled by pushing it to the locals. This is used to
/// 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.
let name = name.into();
match name.as_str() {
@ -786,24 +810,29 @@ impl Compiler {
self.scope.locals.push(Local {
name: name.into(),
node: Some(node),
depth: self.scope.scope_depth,
phantom: false,
used: false,
});
}
fn declare_phantom(&mut self) {
self.scope.locals.push(Local {
name: "".into(),
node: None,
depth: self.scope.scope_depth,
phantom: true,
used: true,
});
}
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 {
local.used = true;
return Some(idx);
}
}

View file

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