use crate::linter::diagnostic::Diagnostic;
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::semantic::{Binding, BindingId, BindingKind, ScopeId, ScopeKind, SemanticModel};
pub struct LoopVariableShadow;
impl Rule for LoopVariableShadow {
fn id(&self) -> &'static str {
"loop-variable-shadow"
}
fn description(&self) -> &'static str {
"Flag a `for` loop whose index variable is already an enclosing `for` \
loop's index. The nested `for` binds a fresh variable, so the outer \
index is unreachable inside it — usually a copy-pasted inner loop whose index \
was never renamed. Assignments to loop variables are allowed, since \
normalization and other updates within an iteration are valid. \
Comprehension and generator clauses are left alone, as is reuse \
across a function body, a closure, or a `do` block. No fix: renaming \
an index requires choosing the intended binding."
}
fn examples(&self) -> &'static [Example] {
&[Example {
caption: "A nested loop reusing the enclosing loop's index:",
source: "for i in 1:3\n for i in 1:2\n println(i)\n end\nend\n",
}]
}
fn check_file(&self, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let model = ctx.model;
for binding in model.bindings() {
if !is_loop_variable(model, binding) {
continue;
}
if let Some(outer) = enclosing_loop_variable(model, binding) {
sink.push(Diagnostic::new(
self.id(),
binding.def_range,
format!(
"loop variable `{}` shadows the enclosing loop's variable",
model.binding(outer).name
),
));
}
}
}
}
fn is_loop_variable(model: &SemanticModel, binding: &Binding) -> bool {
binding.kind == BindingKind::ForVar && model.scope(binding.scope).kind == ScopeKind::For
}
fn enclosing_loop_variable(model: &SemanticModel, binding: &Binding) -> Option<BindingId> {
let mut cursor = model.scope(binding.scope).parent;
while let Some(id) = cursor {
let scope = model.scope(id);
if scope.kind.is_global() || scope.kind == ScopeKind::Function {
return None;
}
if scope.kind == ScopeKind::For
&& let Some(outer) = same_named_loop_variable(model, id, binding)
{
return Some(outer);
}
cursor = scope.parent;
}
None
}
fn same_named_loop_variable(
model: &SemanticModel,
scope: ScopeId,
binding: &Binding,
) -> Option<BindingId> {
model
.scope(scope)
.bindings
.iter()
.copied()
.find(|&candidate| {
let outer = model.binding(candidate);
outer.kind == BindingKind::ForVar && outer.name == binding.name
})
}