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, Hir, UsingDirective, UsingEntryKind},
11        ty::TyKind,
12    },
13};
14
15declare_forge_lint!(
16    INCORRECT_USING_FOR,
17    Severity::Info,
18    "incorrect-using-for",
19    "`using ... for` names a library with no function applicable to the type, so the directive attaches nothing"
20);
21
22impl<'hir> LateLintPass<'hir> for IncorrectUsingFor {
23    fn check_nested_source(
24        &mut self,
25        ctx: &LintContext,
26        gcx: Gcx<'hir>,
27        hir: &'hir Hir<'hir>,
28        id: hir::SourceId,
29    ) {
30        // The file-level directives.
31        for directive in hir.source(id).usings {
32            self.check_directive(ctx, gcx, hir, directive);
33        }
34    }
35
36    fn check_nested_contract(
37        &mut self,
38        ctx: &LintContext,
39        gcx: Gcx<'hir>,
40        hir: &'hir Hir<'hir>,
41        id: hir::ContractId,
42    ) {
43        // The contract-level directives.
44        for directive in hir.contract(id).usings {
45            self.check_directive(ctx, gcx, hir, directive);
46        }
47    }
48}
49
50impl IncorrectUsingFor {
51    /// Judges one `using ... for` directive: a library entry that contributes no member to the
52    /// target type attaches nothing, which means no function of the library accepts the type
53    /// as its bound first parameter.
54    fn check_directive<'hir>(
55        &self,
56        ctx: &LintContext,
57        gcx: Gcx<'hir>,
58        hir: &'hir Hir<'hir>,
59        directive: &'hir UsingDirective<'hir>,
60    ) {
61        // `using L for *` attaches every function of the library: nothing to validate.
62        let Some(hir_ty) = &directive.ty else { return };
63        // `members_of` expects reference types wrapped in their data location. Storage
64        // converts implicitly to memory but not to calldata, so a library function whose
65        // bound first parameter is `calldata` only shows up under the calldata form: each
66        // location is probed and the directive only flags when none of them attaches.
67        let base_ty = gcx.type_of_hir_ty(hir_ty);
68        let tys = [
69            base_ty.with_loc_if_ref(gcx, DataLocation::Storage),
70            base_ty.with_loc_if_ref(gcx, DataLocation::Memory),
71            base_ty.with_loc_if_ref(gcx, DataLocation::Calldata),
72        ];
73        for entry in directive.entries {
74            // The braced form `using {f} for T` is already type-checked: the compiler rejects
75            // a function that cannot attach to `T`.
76            let UsingEntryKind::Library(library_id) = entry.kind else { continue };
77            // The directive is useful when at least one member the type gains in this scope
78            // comes from the named library. Whether the bound value converts to the first
79            // parameter is the type checker's business: `members_of` already reflects it.
80            let mut attaches = false;
81            // Each data location the bound value may live in is probed separately.
82            for ty in tys {
83                for member in gcx.members_of(ty, directive.source, directive.contract) {
84                    // A member counts when it is an attached function declared in the library,
85                    // excluding private ones: the library form skips private functions, so a
86                    // private member attached by a braced directive in scope does not make
87                    // this entry live.
88                    if member.attached
89                        && let TyKind::Fn(function_ty) = member.ty.kind
90                        && let Some(function_id) = function_ty.function_id
91                        && hir.function(function_id).contract == Some(library_id)
92                        && hir.function(function_id).visibility != hir::Visibility::Private
93                    {
94                        attaches = true;
95                    }
96                }
97            }
98            // No function of the library accepts the type: the directive is a no-op.
99            if !attaches {
100                ctx.emit(&INCORRECT_USING_FOR, entry.span);
101            }
102        }
103    }
104}