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 {
48 let ExprKind::Call(callee, ..) = &expr.peel_parens().kind else { return false };
49 let Some(TyKind::Fn(function_ty)) = gcx.type_of_expr(callee.peel_parens().id).map(|ty| ty.kind)
50 else {
51 return false;
52 };
53 if !matches!(function_ty.kind(), TyFnKind::External | TyFnKind::DelegateCall)
54 || function_ty.returns.is_empty()
55 {
56 return false;
57 }
58
59 let ExprKind::Member(_, name) = &callee.peel_parens().kind else { return true };
60 let Some(fid) = gcx.resolved_function(callee) else { return false };
61 let f = gcx.hir.function(fid);
62
63 let sig = |vars: &[_], expected: &[&str]| {
64 vars.len() == expected.len()
65 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(&gcx.hir, id, ty))
66 };
67 let is_erc20_transfer = sig(f.returns, &["bool"])
68 && match name.as_str() {
69 "transfer" => sig(f.parameters, &["address", "uint256"]),
70 "transferFrom" => sig(f.parameters, &["address", "address", "uint256"]),
71 _ => false,
72 };
73 !is_erc20_transfer
74}