| 
 | 1 | +use clippy_config::Conf;  | 
 | 2 | +use clippy_utils::diagnostics::span_lint_and_sugg;  | 
 | 3 | +use clippy_utils::source::snippet_opt;  | 
 | 4 | +use clippy_utils::{ReturnType, ReturnVisitor, is_entrypoint_fn, visit_returns};  | 
 | 5 | +use rustc_errors::Applicability;  | 
 | 6 | +use rustc_hir::def_id::LocalDefId;  | 
 | 7 | +use rustc_hir::{BodyId, FnRetTy, FnSig, ImplItemKind, Item, ItemKind, TyKind};  | 
 | 8 | +use rustc_lint::{LateContext, LateLintPass};  | 
 | 9 | +use rustc_middle::ty::TypeckResults;  | 
 | 10 | +use rustc_middle::ty::adjustment::{Adjust, Adjustment};  | 
 | 11 | +use rustc_session::impl_lint_pass;  | 
 | 12 | + | 
 | 13 | +declare_clippy_lint! {  | 
 | 14 | +    /// ### What it does  | 
 | 15 | +    ///  | 
 | 16 | +    /// Detects functions that do not return, but do not have `!` as their return type.  | 
 | 17 | +    ///  | 
 | 18 | +    /// ### Why is this bad?  | 
 | 19 | +    ///  | 
 | 20 | +    /// Returning `!` is a more accurate API for your callers, and allows for optimisations/further linting.  | 
 | 21 | +    ///  | 
 | 22 | +    /// ### Example  | 
 | 23 | +    /// ```no_run  | 
 | 24 | +    /// # fn do_thing() {}  | 
 | 25 | +    /// fn run() {  | 
 | 26 | +    ///     loop {  | 
 | 27 | +    ///         do_thing();  | 
 | 28 | +    ///     }  | 
 | 29 | +    /// }  | 
 | 30 | +    /// ```  | 
 | 31 | +    /// Use instead:  | 
 | 32 | +    /// ```no_run  | 
 | 33 | +    /// # fn do_thing() {}  | 
 | 34 | +    /// fn run() -> ! {  | 
 | 35 | +    ///     loop {  | 
 | 36 | +    ///         do_thing();  | 
 | 37 | +    ///     }  | 
 | 38 | +    /// }  | 
 | 39 | +    /// ```  | 
 | 40 | +    #[clippy::version = "1.83.0"]  | 
 | 41 | +    pub NEVER_RETURNS,  | 
 | 42 | +    pedantic,  | 
 | 43 | +    "functions that never return, but are typed to"  | 
 | 44 | +}  | 
 | 45 | + | 
 | 46 | +#[derive(Clone, Copy)]  | 
 | 47 | +pub(crate) struct NeverReturns {  | 
 | 48 | +    avoid_breaking_exported_api: bool,  | 
 | 49 | +}  | 
 | 50 | + | 
 | 51 | +impl_lint_pass!(NeverReturns => [NEVER_RETURNS]);  | 
 | 52 | + | 
 | 53 | +impl NeverReturns {  | 
 | 54 | +    pub fn new(conf: &Conf) -> Self {  | 
 | 55 | +        Self {  | 
 | 56 | +            avoid_breaking_exported_api: conf.avoid_breaking_exported_api,  | 
 | 57 | +        }  | 
 | 58 | +    }  | 
 | 59 | + | 
 | 60 | +    fn check_item_fn(self, cx: &LateContext<'_>, sig: FnSig<'_>, def_id: LocalDefId, body_id: BodyId) {  | 
 | 61 | +        let returns_unit = if let FnRetTy::Return(ret_ty) = sig.decl.output {  | 
 | 62 | +            if let TyKind::Never = ret_ty.kind {  | 
 | 63 | +                return;  | 
 | 64 | +            }  | 
 | 65 | + | 
 | 66 | +            matches!(ret_ty.kind, TyKind::Tup([]))  | 
 | 67 | +        } else {  | 
 | 68 | +            true  | 
 | 69 | +        };  | 
 | 70 | + | 
 | 71 | +        if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {  | 
 | 72 | +            return;  | 
 | 73 | +        }  | 
 | 74 | + | 
 | 75 | +        // We shouldn't try to change the signature of a lang item!  | 
 | 76 | +        if cx.tcx.lang_items().from_def_id(def_id.to_def_id()).is_some() {  | 
 | 77 | +            return;  | 
 | 78 | +        }  | 
 | 79 | + | 
 | 80 | +        let body = cx.tcx.hir().body(body_id);  | 
 | 81 | +        let typeck_results = cx.tcx.typeck_body(body_id);  | 
 | 82 | +        let mut visitor = NeverReturnVisitor {  | 
 | 83 | +            typeck_results,  | 
 | 84 | +            returns_unit,  | 
 | 85 | +            found_implicit_return: false,  | 
 | 86 | +        };  | 
 | 87 | + | 
 | 88 | +        if visit_returns(&mut visitor, body.value).is_continue() && visitor.found_implicit_return {  | 
 | 89 | +            let mut applicability = Applicability::MachineApplicable;  | 
 | 90 | +            let (lint_span, mut snippet, sugg) = match sig.decl.output {  | 
 | 91 | +                FnRetTy::DefaultReturn(span) => (span, String::new(), " -> !"),  | 
 | 92 | +                FnRetTy::Return(ret_ty) => {  | 
 | 93 | +                    let snippet = if let Some(snippet) = snippet_opt(cx, ret_ty.span) {  | 
 | 94 | +                        format!(" a `{snippet}`")  | 
 | 95 | +                    } else {  | 
 | 96 | +                        applicability = Applicability::HasPlaceholders;  | 
 | 97 | +                        String::new()  | 
 | 98 | +                    };  | 
 | 99 | + | 
 | 100 | +                    (ret_ty.span, snippet, "!")  | 
 | 101 | +                },  | 
 | 102 | +            };  | 
 | 103 | + | 
 | 104 | +            snippet.insert_str(0, "function never returns, but is typed to return");  | 
 | 105 | +            span_lint_and_sugg(  | 
 | 106 | +                cx,  | 
 | 107 | +                NEVER_RETURNS,  | 
 | 108 | +                lint_span,  | 
 | 109 | +                snippet,  | 
 | 110 | +                "replace with",  | 
 | 111 | +                sugg.into(),  | 
 | 112 | +                applicability,  | 
 | 113 | +            );  | 
 | 114 | +        }  | 
 | 115 | +    }  | 
 | 116 | +}  | 
 | 117 | + | 
 | 118 | +impl LateLintPass<'_> for NeverReturns {  | 
 | 119 | +    fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {  | 
 | 120 | +        if let ItemKind::Fn(sig, _, body_id) = item.kind {  | 
 | 121 | +            let local_def_id = item.owner_id.def_id;  | 
 | 122 | +            if is_entrypoint_fn(cx, local_def_id.to_def_id()) {  | 
 | 123 | +                return;  | 
 | 124 | +            }  | 
 | 125 | + | 
 | 126 | +            self.check_item_fn(cx, sig, local_def_id, body_id);  | 
 | 127 | +        } else if let ItemKind::Impl(impl_) = item.kind {  | 
 | 128 | +            // Do not lint trait impls  | 
 | 129 | +            if impl_.of_trait.is_some() {  | 
 | 130 | +                return;  | 
 | 131 | +            }  | 
 | 132 | + | 
 | 133 | +            for impl_item in impl_.items {  | 
 | 134 | +                let ImplItemKind::Fn(sig, body_id) = cx.tcx.hir().impl_item(impl_item.id).kind else {  | 
 | 135 | +                    continue;  | 
 | 136 | +                };  | 
 | 137 | + | 
 | 138 | +                let local_def_id = item.owner_id.def_id;  | 
 | 139 | +                self.check_item_fn(cx, sig, local_def_id, body_id);  | 
 | 140 | +            }  | 
 | 141 | +        }  | 
 | 142 | +    }  | 
 | 143 | +}  | 
 | 144 | + | 
 | 145 | +struct NeverReturnVisitor<'tcx> {  | 
 | 146 | +    typeck_results: &'tcx TypeckResults<'tcx>,  | 
 | 147 | +    found_implicit_return: bool,  | 
 | 148 | +    returns_unit: bool,  | 
 | 149 | +}  | 
 | 150 | + | 
 | 151 | +impl ReturnVisitor for &mut NeverReturnVisitor<'_> {  | 
 | 152 | +    type Result = std::ops::ControlFlow<()>;  | 
 | 153 | + | 
 | 154 | +    fn visit_return(&mut self, kind: ReturnType<'_>) -> Self::Result {  | 
 | 155 | +        let expression = match kind {  | 
 | 156 | +            ReturnType::Explicit(expr) => expr,  | 
 | 157 | +            ReturnType::UnitReturnExplicit(_) => {  | 
 | 158 | +                return Self::Result::Break(());  | 
 | 159 | +            },  | 
 | 160 | +            ReturnType::Implicit(expr) | ReturnType::MissingElseImplicit(expr) => {  | 
 | 161 | +                self.found_implicit_return = true;  | 
 | 162 | +                expr  | 
 | 163 | +            },  | 
 | 164 | +            ReturnType::DivergingImplicit(_) => {  | 
 | 165 | +                // If this function returns unit, a diverging implicit may just  | 
 | 166 | +                // be an implicit unit return, in which case we should not lint.  | 
 | 167 | +                return if self.returns_unit {  | 
 | 168 | +                    Self::Result::Break(())  | 
 | 169 | +                } else {  | 
 | 170 | +                    Self::Result::Continue(())  | 
 | 171 | +                };  | 
 | 172 | +            },  | 
 | 173 | +        };  | 
 | 174 | + | 
 | 175 | +        if expression.span.from_expansion() {  | 
 | 176 | +            return Self::Result::Break(());  | 
 | 177 | +        }  | 
 | 178 | + | 
 | 179 | +        let adjustments = self.typeck_results.expr_adjustments(expression);  | 
 | 180 | +        if adjustments.iter().any(is_never_to_any) {  | 
 | 181 | +            Self::Result::Continue(())  | 
 | 182 | +        } else {  | 
 | 183 | +            Self::Result::Break(())  | 
 | 184 | +        }  | 
 | 185 | +    }  | 
 | 186 | +}  | 
 | 187 | + | 
 | 188 | +fn is_never_to_any(adjustment: &Adjustment<'_>) -> bool {  | 
 | 189 | +    matches!(adjustment.kind, Adjust::NeverToAny)  | 
 | 190 | +}  | 
0 commit comments