| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::msrvs::{self, Msrv}; | |
| use clippy_utils::qualify_min_const_fn::is_min_const_fn; | |
| use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, is_in_test, trait_ref_of_method}; | |
| use rustc_abi::ExternAbi; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::def_id::CRATE_DEF_ID; | |
| use rustc_hir::intravisit::FnKind; | |
| use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind, OwnerId}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::ty; | |
| use rustc_session::impl_lint_pass; | |
| use rustc_span::Span; | |
| use rustc_span::def_id::LocalDefId; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Suggests the use of `const` in functions and methods where possible. | |
| /// | |
| /// ### Why is this bad? | |
| /// Not having the function const prevents callers of the function from being const as well. | |
| /// | |
| /// ### Known problems | |
| /// Const functions are currently still being worked on, with some features only being available | |
| /// on nightly. This lint does not consider all edge cases currently and the suggestions may be | |
| /// incorrect if you are using this lint on stable. | |
| /// | |
| /// Also, the lint only runs one pass over the code. Consider these two non-const functions: | |
| /// | |
| /// ```no_run | |
| /// fn a() -> i32 { | |
| /// 0 | |
| /// } | |
| /// fn b() -> i32 { | |
| /// a() | |
| /// } | |
| /// ``` | |
| /// | |
| /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time | |
| /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, | |
| /// will suggest to make `b` const, too. | |
| /// | |
| /// If you are marking a public function with `const`, removing it again will break API compatibility. | |
| /// ### Example | |
| /// ```no_run | |
| /// # struct Foo { | |
| /// # random_number: usize, | |
| /// # } | |
| /// # impl Foo { | |
| /// fn new() -> Self { | |
| /// Self { random_number: 42 } | |
| /// } | |
| /// # } | |
| /// ``` | |
| /// | |
| /// Could be a const fn: | |
| /// | |
| /// ```no_run | |
| /// # struct Foo { | |
| /// # random_number: usize, | |
| /// # } | |
| /// # impl Foo { | |
| /// const fn new() -> Self { | |
| /// Self { random_number: 42 } | |
| /// } | |
| /// # } | |
| /// ``` | |
| #[clippy::version = "1.34.0"] | |
| pub MISSING_CONST_FOR_FN, | |
| nursery, | |
| "Lint functions definitions that could be made `const fn`" | |
| } | |
| impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); | |
| pub struct MissingConstForFn { | |
| msrv: Msrv, | |
| } | |
| impl MissingConstForFn { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { msrv: conf.msrv } | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { | |
| fn check_fn( | |
| &mut self, | |
| cx: &LateContext<'tcx>, | |
| kind: FnKind<'tcx>, | |
| _: &FnDecl<'_>, | |
| body: &Body<'tcx>, | |
| span: Span, | |
| def_id: LocalDefId, | |
| ) { | |
| let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); | |
| if is_in_test(cx.tcx, hir_id) { | |
| return; | |
| } | |
| if span.in_external_macro(cx.tcx.sess.source_map()) || is_entrypoint_fn(cx, def_id.to_def_id()) { | |
| return; | |
| } | |
| // Building MIR for `fn`s with unsatisfiable preds results in ICE. | |
| if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { | |
| return; | |
| } | |
| // Perform some preliminary checks that rule out constness on the Clippy side. This way we | |
| // can skip the actual const check and return early. | |
| match kind { | |
| FnKind::ItemFn(_, generics, header, ..) => { | |
| let has_const_generic_params = generics | |
| .params | |
| .iter() | |
| .any(|param| matches!(param.kind, GenericParamKind::Const { .. })); | |
| if already_const(header) | |
| || has_const_generic_params | |
| || !could_be_const_with_abi(cx, self.msrv, header.abi) | |
| { | |
| return; | |
| } | |
| }, | |
| FnKind::Method(_, sig, ..) => { | |
| if already_const(sig.header) || trait_ref_of_method(cx, OwnerId { def_id }).is_some() { | |
| return; | |
| } | |
| }, | |
| FnKind::Closure => return, | |
| } | |
| if fn_inputs_has_impl_trait_ty(cx, def_id) { | |
| return; | |
| } | |
| // Const fns are not allowed as methods in a trait. | |
| { | |
| let parent = cx.tcx.hir_get_parent_item(hir_id).def_id; | |
| if parent != CRATE_DEF_ID | |
| && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) | |
| && let hir::ItemKind::Trait(..) = &item.kind | |
| { | |
| return; | |
| } | |
| } | |
| if !self.msrv.meets(cx, msrvs::CONST_IF_MATCH) { | |
| return; | |
| } | |
| if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { | |
| return; | |
| } | |
| let mir = cx.tcx.optimized_mir(def_id); | |
| if let Ok(()) = is_min_const_fn(cx, mir, self.msrv) | |
| && let node = cx.tcx.hir_node_by_def_id(def_id) | |
| && let Some((item_span, vis_span_opt)) = match node { | |
| hir::Node::Item(item) => Some((item.span, Some(item.vis_span))), | |
| hir::Node::ImplItem(impl_item) => Some((impl_item.span, impl_item.vis_span())), | |
| _ => None, | |
| } | |
| { | |
| let (sugg_span, suggestion) = if let Some(vis_span) = vis_span_opt | |
| && !vis_span.is_empty() | |
| { | |
| (vis_span.shrink_to_hi(), " const") | |
| } else { | |
| (item_span.shrink_to_lo(), "const ") | |
| }; | |
| span_lint_and_then(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`", |diag| { | |
| diag.span_suggestion_verbose( | |
| sugg_span, | |
| "make the function `const`", | |
| suggestion, | |
| Applicability::MachineApplicable, | |
| ); | |
| }); | |
| } | |
| } | |
| } | |
| // We don't have to lint on something that's already `const` | |
| #[must_use] | |
| fn already_const(header: hir::FnHeader) -> bool { | |
| header.constness == Constness::Const | |
| } | |
| fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: Msrv, abi: ExternAbi) -> bool { | |
| match abi { | |
| ExternAbi::Rust => true, | |
| // `const extern "C"` was stabilized after 1.62.0 | |
| ExternAbi::C { unwind: false } => msrv.meets(cx, msrvs::CONST_EXTERN_C_FN), | |
| // Rest ABIs are still unstable and need the `const_extern_fn` feature enabled. | |
| _ => msrv.meets(cx, msrvs::CONST_EXTERN_FN), | |
| } | |
| } | |
| /// Return `true` when the given `def_id` is a function that has `impl Trait` ty as one of | |
| /// its parameter types. | |
| fn fn_inputs_has_impl_trait_ty(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { | |
| let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().inputs().skip_binder(); | |
| inputs.iter().any(|input| { | |
| matches!( | |
| input.kind(), | |
| &ty::Alias(ty::AliasTy { kind: ty::Free{def_id} , ..}) if cx.tcx.type_of(def_id).skip_binder().is_opaque() | |
| ) | |
| }) | |
| } |