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}