]>
Commit | Line | Data |
---|---|---|
ed00b5ec | 1 | use clippy_config::msrvs::{self, Msrv}; |
cdc7bbd5 XL |
2 | use clippy_utils::diagnostics::span_lint; |
3 | use clippy_utils::qualify_min_const_fn::is_min_const_fn; | |
4 | use clippy_utils::ty::has_drop; | |
487cf647 | 5 | use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method}; |
f20569fa | 6 | use rustc_hir as hir; |
5e7ed085 | 7 | use rustc_hir::def_id::CRATE_DEF_ID; |
f20569fa | 8 | use rustc_hir::intravisit::FnKind; |
9ffffee4 | 9 | use rustc_hir::{Body, Constness, FnDecl, GenericParamKind}; |
5099ac24 | 10 | use rustc_lint::{LateContext, LateLintPass}; |
f20569fa | 11 | use rustc_middle::lint::in_external_macro; |
4b012472 | 12 | use rustc_session::impl_lint_pass; |
9ffffee4 | 13 | use rustc_span::def_id::LocalDefId; |
f20569fa | 14 | use rustc_span::Span; |
f20569fa | 15 | |
f20569fa | 16 | declare_clippy_lint! { |
94222f64 | 17 | /// ### What it does |
f20569fa XL |
18 | /// Suggests the use of `const` in functions and methods where possible. |
19 | /// | |
94222f64 | 20 | /// ### Why is this bad? |
f20569fa XL |
21 | /// Not having the function const prevents callers of the function from being const as well. |
22 | /// | |
94222f64 | 23 | /// ### Known problems |
f20569fa XL |
24 | /// Const functions are currently still being worked on, with some features only being available |
25 | /// on nightly. This lint does not consider all edge cases currently and the suggestions may be | |
26 | /// incorrect if you are using this lint on stable. | |
27 | /// | |
28 | /// Also, the lint only runs one pass over the code. Consider these two non-const functions: | |
29 | /// | |
ed00b5ec | 30 | /// ```no_run |
f20569fa XL |
31 | /// fn a() -> i32 { |
32 | /// 0 | |
33 | /// } | |
34 | /// fn b() -> i32 { | |
35 | /// a() | |
36 | /// } | |
37 | /// ``` | |
38 | /// | |
39 | /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time | |
40 | /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, | |
41 | /// will suggest to make `b` const, too. | |
42 | /// | |
353b0b11 | 43 | /// If you are marking a public function with `const`, removing it again will break API compatibility. |
94222f64 | 44 | /// ### Example |
ed00b5ec | 45 | /// ```no_run |
f20569fa XL |
46 | /// # struct Foo { |
47 | /// # random_number: usize, | |
48 | /// # } | |
49 | /// # impl Foo { | |
50 | /// fn new() -> Self { | |
51 | /// Self { random_number: 42 } | |
52 | /// } | |
53 | /// # } | |
54 | /// ``` | |
55 | /// | |
56 | /// Could be a const fn: | |
57 | /// | |
ed00b5ec | 58 | /// ```no_run |
f20569fa XL |
59 | /// # struct Foo { |
60 | /// # random_number: usize, | |
61 | /// # } | |
62 | /// # impl Foo { | |
63 | /// const fn new() -> Self { | |
64 | /// Self { random_number: 42 } | |
65 | /// } | |
66 | /// # } | |
67 | /// ``` | |
a2a8927a | 68 | #[clippy::version = "1.34.0"] |
f20569fa XL |
69 | pub MISSING_CONST_FOR_FN, |
70 | nursery, | |
71 | "Lint functions definitions that could be made `const fn`" | |
72 | } | |
73 | ||
74 | impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); | |
75 | ||
76 | pub struct MissingConstForFn { | |
487cf647 | 77 | msrv: Msrv, |
f20569fa XL |
78 | } |
79 | ||
80 | impl MissingConstForFn { | |
81 | #[must_use] | |
487cf647 | 82 | pub fn new(msrv: Msrv) -> Self { |
f20569fa XL |
83 | Self { msrv } |
84 | } | |
85 | } | |
86 | ||
87 | impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { | |
88 | fn check_fn( | |
89 | &mut self, | |
f2b60f7d FG |
90 | cx: &LateContext<'tcx>, |
91 | kind: FnKind<'tcx>, | |
f20569fa | 92 | _: &FnDecl<'_>, |
f2b60f7d | 93 | body: &Body<'tcx>, |
f20569fa | 94 | span: Span, |
9ffffee4 | 95 | def_id: LocalDefId, |
f20569fa | 96 | ) { |
487cf647 | 97 | if !self.msrv.meets(msrvs::CONST_IF_MATCH) { |
f20569fa XL |
98 | return; |
99 | } | |
100 | ||
f20569fa XL |
101 | if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) { |
102 | return; | |
103 | } | |
104 | ||
105 | // Building MIR for `fn`s with unsatisfiable preds results in ICE. | |
106 | if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { | |
107 | return; | |
108 | } | |
109 | ||
110 | // Perform some preliminary checks that rule out constness on the Clippy side. This way we | |
111 | // can skip the actual const check and return early. | |
112 | match kind { | |
113 | FnKind::ItemFn(_, generics, header, ..) => { | |
114 | let has_const_generic_params = generics | |
115 | .params | |
116 | .iter() | |
117 | .any(|param| matches!(param.kind, GenericParamKind::Const { .. })); | |
118 | ||
119 | if already_const(header) || has_const_generic_params { | |
120 | return; | |
121 | } | |
122 | }, | |
123 | FnKind::Method(_, sig, ..) => { | |
5099ac24 | 124 | if trait_ref_of_method(cx, def_id).is_some() |
f20569fa | 125 | || already_const(sig.header) |
781aab86 | 126 | || method_accepts_droppable(cx, def_id) |
f20569fa XL |
127 | { |
128 | return; | |
129 | } | |
130 | }, | |
131 | FnKind::Closure => return, | |
5e7ed085 FG |
132 | } |
133 | ||
4b012472 | 134 | let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); |
9ffffee4 | 135 | |
5e7ed085 FG |
136 | // Const fns are not allowed as methods in a trait. |
137 | { | |
2b03887a | 138 | let parent = cx.tcx.hir().get_parent_item(hir_id).def_id; |
5e7ed085 | 139 | if parent != CRATE_DEF_ID { |
4b012472 | 140 | if let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) { |
5e7ed085 FG |
141 | if let hir::ItemKind::Trait(..) = &item.kind { |
142 | return; | |
143 | } | |
144 | } | |
145 | } | |
f20569fa XL |
146 | } |
147 | ||
f2b60f7d FG |
148 | if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { |
149 | return; | |
150 | } | |
151 | ||
f20569fa XL |
152 | let mir = cx.tcx.optimized_mir(def_id); |
153 | ||
487cf647 | 154 | if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, &self.msrv) { |
cdc7bbd5 | 155 | if cx.tcx.is_const_fn_raw(def_id.to_def_id()) { |
c0240ec0 | 156 | cx.tcx.dcx().span_err(span, err); |
f20569fa XL |
157 | } |
158 | } else { | |
159 | span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); | |
160 | } | |
161 | } | |
162 | extract_msrv_attr!(LateContext); | |
163 | } | |
164 | ||
165 | /// Returns true if any of the method parameters is a type that implements `Drop`. The method | |
166 | /// can't be made const then, because `drop` can't be const-evaluated. | |
781aab86 FG |
167 | fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { |
168 | let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); | |
169 | ||
f20569fa | 170 | // If any of the params are droppable, return true |
781aab86 | 171 | sig.inputs().iter().any(|&ty| has_drop(cx, ty)) |
f20569fa XL |
172 | } |
173 | ||
174 | // We don't have to lint on something that's already `const` | |
175 | #[must_use] | |
176 | fn already_const(header: hir::FnHeader) -> bool { | |
177 | header.constness == Constness::Const | |
178 | } |