Skip to main content

forge_lint/sol/info/
incorrect_using_for.rs

1use super::IncorrectUsingFor;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::DataLocation,
8    sema::{
9        Gcx,
10        hir::{self, UsingDirective, UsingEntryKind},
11    },
12};
13
14declare_forge_lint!(
15    INCORRECT_USING_FOR,
16    Severity::Info,
17    "incorrect-using-for",
18    "`using ... for` names a library with no function applicable to the type, so the directive attaches nothing"
19);
20
21impl<'gcx> LateLintPass<'gcx> for IncorrectUsingFor {
22    fn check_nested_source(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, id: hir::SourceId) {
23        for directive in gcx.hir.source(id).usings {
24            check_directive(ctx, gcx, directive);
25        }
26    }
27
28    fn check_nested_contract(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, id: hir::ContractId) {
29        for directive in gcx.hir.contract(id).usings {
30            check_directive(ctx, gcx, directive);
31        }
32    }
33}
34
35/// Judges one `using ... for` directive: a library entry that contributes no member to the
36/// target type attaches nothing, which means no function of the library accepts the type as
37/// its bound first parameter.
38fn check_directive<'gcx>(ctx: &LintContext, gcx: Gcx<'gcx>, directive: &'gcx UsingDirective<'gcx>) {
39    // `using L for *` attaches every function of the library: nothing to validate.
40    let Some(hir_ty) = &directive.ty else { return };
41    // `members_of` expects reference types wrapped in their data location. Storage converts
42    // implicitly to memory but not to calldata, so a library function whose bound first
43    // parameter is `calldata` only shows up under the calldata form: each location is probed
44    // and the directive only flags when none of them attaches.
45    let base_ty = gcx.type_of_hir_ty(hir_ty);
46    let tys = [DataLocation::Storage, DataLocation::Memory, DataLocation::Calldata]
47        .map(|loc| base_ty.with_loc_if_ref(gcx, loc));
48    for entry in directive.entries {
49        // The braced form `using {f} for T` is already type-checked: the compiler rejects a
50        // function that cannot attach to `T`.
51        let UsingEntryKind::Library(library_id) = entry.kind else { continue };
52        // A member counts when it is an attached, non-private function declared in the library:
53        // the library form skips private functions, so a private member attached by a braced
54        // directive in scope does not make this entry live.
55        let attaches = tys
56            .iter()
57            .flat_map(|ty| gcx.members_of(*ty, directive.source, directive.contract))
58            .filter(|member| member.attached)
59            .filter_map(|member| member.ty.function_id())
60            .any(|function_id| {
61                let function = gcx.hir.function(function_id);
62                function.contract == Some(library_id)
63                    && function.visibility != hir::Visibility::Private
64            });
65        if !attaches {
66            ctx.emit(&INCORRECT_USING_FOR, entry.span);
67        }
68    }
69}