feat(natu): lint redundant parameter forwarding
This commit was merged in pull request #114.
This commit is contained in:
244
src/natu/rules/redundant_parameter_forwarding.rs
Normal file
244
src/natu/rules/redundant_parameter_forwarding.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
use crate::ast;
|
||||
|
||||
pub struct Finding {
|
||||
pub range: ast::SourceRange,
|
||||
}
|
||||
|
||||
pub fn check(expr: &ast::Expr) -> Vec<Finding> {
|
||||
let mut findings = Vec::new();
|
||||
check_expr(expr, &[], &mut findings);
|
||||
findings
|
||||
}
|
||||
|
||||
fn check_expr(expr: &ast::Expr, outer_parameters: &[ast::Ident], findings: &mut Vec<Finding>) {
|
||||
match expr {
|
||||
ast::Expr::Let { bindings, body, .. } => {
|
||||
check_let(bindings, body, outer_parameters, findings);
|
||||
for binding in bindings {
|
||||
check_expr(&binding.value, outer_parameters, findings);
|
||||
}
|
||||
check_expr(body, outer_parameters, findings);
|
||||
}
|
||||
ast::Expr::AttrSet(bindings) => {
|
||||
for binding in bindings {
|
||||
check_expr(&binding.value, outer_parameters, findings);
|
||||
}
|
||||
}
|
||||
ast::Expr::Lambda { parameters, body } => check_expr(body, parameters, findings),
|
||||
ast::Expr::Apply(application) => {
|
||||
check_expr(&application.lambda, outer_parameters, findings);
|
||||
check_expr(&application.argument, outer_parameters, findings);
|
||||
}
|
||||
ast::Expr::Other(expressions) => {
|
||||
for expression in expressions {
|
||||
check_expr(expression, outer_parameters, findings);
|
||||
}
|
||||
}
|
||||
ast::Expr::Ident(_) | ast::Expr::Select(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn check_let(
|
||||
bindings: &[ast::Binding],
|
||||
let_body: &ast::Expr,
|
||||
outer_parameters: &[ast::Ident],
|
||||
findings: &mut Vec<Finding>,
|
||||
) {
|
||||
for binding in bindings {
|
||||
let parameters = curried_parameters(&binding.value);
|
||||
for (parameter_index, parameter) in parameters.iter().enumerate() {
|
||||
if !outer_parameters
|
||||
.iter()
|
||||
.any(|outer_parameter| outer_parameter.name == parameter.name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let usage = helper_usage(
|
||||
bindings,
|
||||
let_body,
|
||||
&binding.key.name,
|
||||
¶meter.name,
|
||||
parameter_index,
|
||||
);
|
||||
if usage.calls > 0 && !usage.invalid {
|
||||
findings.push(Finding {
|
||||
range: binding.range,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Usage {
|
||||
calls: usize,
|
||||
invalid: bool,
|
||||
}
|
||||
|
||||
fn helper_usage(
|
||||
bindings: &[ast::Binding],
|
||||
body: &ast::Expr,
|
||||
helper_name: &str,
|
||||
parameter_name: &str,
|
||||
parameter_index: usize,
|
||||
) -> Usage {
|
||||
let mut usage = Usage::default();
|
||||
for binding in bindings {
|
||||
inspect_usage(
|
||||
&binding.value,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
&mut usage,
|
||||
);
|
||||
}
|
||||
inspect_usage(body, helper_name, parameter_name, parameter_index, &mut usage);
|
||||
usage
|
||||
}
|
||||
|
||||
fn inspect_usage(
|
||||
expr: &ast::Expr,
|
||||
helper_name: &str,
|
||||
parameter_name: &str,
|
||||
parameter_index: usize,
|
||||
usage: &mut Usage,
|
||||
) {
|
||||
match expr {
|
||||
ast::Expr::Ident(ident) => {
|
||||
if ident.name == helper_name {
|
||||
usage.invalid = true;
|
||||
}
|
||||
}
|
||||
ast::Expr::Select(_) => { }
|
||||
ast::Expr::Apply(_) => {
|
||||
let (lambda, arguments) = application_parts(expr);
|
||||
if is_ident(lambda, helper_name) {
|
||||
let Some(argument) = arguments.get(parameter_index) else {
|
||||
usage.invalid = true;
|
||||
return;
|
||||
};
|
||||
if is_forwarded_parameter(argument, parameter_name) {
|
||||
usage.calls += 1;
|
||||
} else {
|
||||
usage.invalid = true;
|
||||
}
|
||||
for argument in arguments {
|
||||
inspect_usage(
|
||||
argument,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
usage,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for argument in arguments {
|
||||
inspect_usage(
|
||||
argument,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
usage,
|
||||
);
|
||||
}
|
||||
inspect_usage(lambda, helper_name, parameter_name, parameter_index, usage);
|
||||
}
|
||||
}
|
||||
ast::Expr::Let { bindings, body, .. } => {
|
||||
if bindings.iter().any(|binding| {
|
||||
binding.key.name == helper_name || binding.key.name == parameter_name
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
for binding in bindings {
|
||||
inspect_usage(
|
||||
&binding.value,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
usage,
|
||||
);
|
||||
}
|
||||
inspect_usage(body, helper_name, parameter_name, parameter_index, usage);
|
||||
}
|
||||
ast::Expr::AttrSet(bindings) => {
|
||||
for binding in bindings {
|
||||
inspect_usage(
|
||||
&binding.value,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
usage,
|
||||
);
|
||||
}
|
||||
}
|
||||
ast::Expr::Lambda { parameters, body } => {
|
||||
if parameters.iter().any(|parameter| {
|
||||
parameter.name == helper_name || parameter.name == parameter_name
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
inspect_usage(body, helper_name, parameter_name, parameter_index, usage);
|
||||
}
|
||||
ast::Expr::Other(expressions) => {
|
||||
for expression in expressions {
|
||||
inspect_usage(
|
||||
expression,
|
||||
helper_name,
|
||||
parameter_name,
|
||||
parameter_index,
|
||||
usage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn curried_parameters(expr: &ast::Expr) -> Vec<&ast::Ident> {
|
||||
let mut parameters = Vec::new();
|
||||
let mut body = expr;
|
||||
while let ast::Expr::Lambda {
|
||||
parameters: lambda_parameters,
|
||||
body: lambda_body,
|
||||
} = body
|
||||
{
|
||||
parameters.extend(lambda_parameters);
|
||||
body = lambda_body;
|
||||
}
|
||||
parameters
|
||||
}
|
||||
|
||||
fn application_parts(expr: &ast::Expr) -> (&ast::Expr, Vec<&ast::Expr>) {
|
||||
let mut arguments = Vec::new();
|
||||
let mut lambda = unwrap_expr(expr);
|
||||
while let ast::Expr::Apply(application) = lambda {
|
||||
arguments.push(&*application.argument);
|
||||
lambda = unwrap_expr(&application.lambda);
|
||||
}
|
||||
arguments.reverse();
|
||||
(lambda, arguments)
|
||||
}
|
||||
|
||||
fn is_ident(expr: &ast::Expr, name: &str) -> bool {
|
||||
matches!(unwrap_expr(expr), ast::Expr::Ident(ident) if ident.name == name)
|
||||
}
|
||||
|
||||
fn is_forwarded_parameter(expr: &ast::Expr, parameter_name: &str) -> bool {
|
||||
match unwrap_expr(expr) {
|
||||
ast::Expr::Ident(ident) => ident.name == parameter_name,
|
||||
ast::Expr::AttrSet(bindings) => matches!(
|
||||
bindings.as_slice(),
|
||||
[binding] if binding.key.name == parameter_name && is_ident(&binding.value, parameter_name)
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_expr(expr: &ast::Expr) -> &ast::Expr {
|
||||
match expr {
|
||||
ast::Expr::Other(expressions) if expressions.len() == 1 => unwrap_expr(&expressions[0]),
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user