forge_lint/sol/med/
unused_return.rs1use super::UnusedReturn;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{
5 Severity, SolLint,
6 analysis::{is_elementary, tuple_elems},
7 },
8};
9use solar::sema::{
10 Gcx,
11 hir::{Expr, ExprKind, Stmt, StmtKind},
12 ty::{TyFnKind, TyKind},
13};
14
15declare_forge_lint!(
16 UNUSED_RETURN,
17 Severity::Med,
18 "unused-return",
19 "return value of an external call is not used"
20);
21
22impl<'gcx> LateLintPass<'gcx> for UnusedReturn {
23 fn check_stmt(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, stmt: &'gcx Stmt<'gcx>) {
24 let (call, span) = match &stmt.kind {
25 StmtKind::Expr(expr) => match &expr.peel_parens().kind {
26 ExprKind::Assign(lhs, None, rhs)
28 if tuple_elems(lhs).is_some_and(|e| e.iter().any(Option::is_none)) =>
29 {
30 (rhs, expr.span)
31 }
32 _ => (expr, expr.span),
33 },
34 StmtKind::DeclMulti(vars, expr) if vars.iter().any(Option::is_none) => {
35 (expr, expr.span)
36 }
37 _ => return,
38 };
39 if is_unused_return_call(gcx, call) {
40 ctx.emit(&UNUSED_RETURN, span);
41 }
42 }
43}
44
45fn is_unused_return_call<'gcx>(gcx: Gcx<'gcx>, expr: &Expr<'gcx>) -> bool {
49 let ExprKind::Call(callee, ..) = &expr.peel_parens().kind else { return false };
50 let ExprKind::Member(_, name) = &callee.peel_parens().kind else { return false };
51 let Some(ty) = gcx.type_of_expr(callee.peel_parens().id) else { return false };
52 if !matches!(ty.kind, TyKind::Fn(f) if matches!(f.kind(), TyFnKind::External | TyFnKind::DelegateCall))
53 {
54 return false;
55 }
56 let Some(fid) = gcx.resolved_function(callee) else { return false };
57 let f = gcx.hir.function(fid);
58
59 let sig = |vars: &[_], expected: &[&str]| {
60 vars.len() == expected.len()
61 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(&gcx.hir, id, ty))
62 };
63 let is_erc20_transfer = sig(f.returns, &["bool"])
64 && match name.as_str() {
65 "transfer" => sig(f.parameters, &["address", "uint256"]),
66 "transferFrom" => sig(f.parameters, &["address", "address", "uint256"]),
67 _ => false,
68 };
69 !f.returns.is_empty() && !is_erc20_transfer
70}