about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs33
-rw-r--r--compiler/rustc_hir_typeck/src/lib.rs6
-rw-r--r--compiler/rustc_interface/src/passes.rs22
-rw-r--r--compiler/rustc_middle/src/query/mod.rs3
-rw-r--r--compiler/rustc_mir_build/messages.ftl12
-rw-r--r--compiler/rustc_mir_build/src/builder/mod.rs7
-rw-r--r--compiler/rustc_mir_build/src/errors.rs22
-rw-r--r--compiler/rustc_mir_build/src/lib.rs4
-rw-r--r--compiler/rustc_mir_transform/messages.ftl12
-rw-r--r--compiler/rustc_mir_transform/src/check_call_recursion.rs (renamed from compiler/rustc_mir_build/src/lints.rs)81
-rw-r--r--compiler/rustc_mir_transform/src/check_inline.rs (renamed from compiler/rustc_mir_build/src/check_inline.rs)52
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs22
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs6
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs10
14 files changed, 161 insertions, 131 deletions
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 3bb518e7f97..dc6a2adf622 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -1991,18 +1991,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         adt_ty: Ty<'tcx>,
         expected: Expectation<'tcx>,
         expr: &hir::Expr<'_>,
-        span: Span,
+        path_span: Span,
         variant: &'tcx ty::VariantDef,
         hir_fields: &'tcx [hir::ExprField<'tcx>],
         base_expr: &'tcx hir::StructTailExpr<'tcx>,
     ) {
         let tcx = self.tcx;
 
-        let adt_ty = self.try_structurally_resolve_type(span, adt_ty);
+        let adt_ty = self.try_structurally_resolve_type(path_span, adt_ty);
         let adt_ty_hint = expected.only_has_type(self).and_then(|expected| {
             self.fudge_inference_if_ok(|| {
                 let ocx = ObligationCtxt::new(self);
-                ocx.sup(&self.misc(span), self.param_env, expected, adt_ty)?;
+                ocx.sup(&self.misc(path_span), self.param_env, expected, adt_ty)?;
                 if !ocx.select_where_possible().is_empty() {
                     return Err(TypeError::Mismatch);
                 }
@@ -2012,11 +2012,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         });
         if let Some(adt_ty_hint) = adt_ty_hint {
             // re-link the variables that the fudging above can create.
-            self.demand_eqtype(span, adt_ty_hint, adt_ty);
+            self.demand_eqtype(path_span, adt_ty_hint, adt_ty);
         }
 
         let ty::Adt(adt, args) = adt_ty.kind() else {
-            span_bug!(span, "non-ADT passed to check_expr_struct_fields");
+            span_bug!(path_span, "non-ADT passed to check_expr_struct_fields");
         };
         let adt_kind = adt.adt_kind();
 
@@ -2107,7 +2107,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         if adt_kind == AdtKind::Union && hir_fields.len() != 1 {
             struct_span_code_err!(
                 self.dcx(),
-                span,
+                path_span,
                 E0784,
                 "union expressions should have exactly one field",
             )
@@ -2167,6 +2167,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 });
                 return;
             }
+            if variant.fields.is_empty() {
+                let mut err = self.dcx().struct_span_err(
+                    span,
+                    format!(
+                        "`{adt_ty}` has no fields, `..` needs at least one default field in the \
+                         struct definition",
+                    ),
+                );
+                err.span_label(path_span, "this type has no fields");
+                err.emit();
+            }
             if !missing_mandatory_fields.is_empty() {
                 let s = pluralize!(missing_mandatory_fields.len());
                 let fields: Vec<_> =
@@ -2316,11 +2327,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 .collect();
 
             if !private_fields.is_empty() {
-                self.report_private_fields(adt_ty, span, expr.span, private_fields, hir_fields);
+                self.report_private_fields(
+                    adt_ty,
+                    path_span,
+                    expr.span,
+                    private_fields,
+                    hir_fields,
+                );
             } else {
                 self.report_missing_fields(
                     adt_ty,
-                    span,
+                    path_span,
                     remaining_fields,
                     variant,
                     hir_fields,
diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs
index cb21961f36b..9cd9ca040ce 100644
--- a/compiler/rustc_hir_typeck/src/lib.rs
+++ b/compiler/rustc_hir_typeck/src/lib.rs
@@ -87,7 +87,7 @@ fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet<LocalDef
 }
 
 fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx ty::TypeckResults<'tcx> {
-    typeck_with_fallback(tcx, def_id, None)
+    typeck_with_inspect(tcx, def_id, None)
 }
 
 /// Same as `typeck` but `inspect` is invoked on evaluation of each root obligation.
@@ -99,11 +99,11 @@ pub fn inspect_typeck<'tcx>(
     def_id: LocalDefId,
     inspect: ObligationInspector<'tcx>,
 ) -> &'tcx ty::TypeckResults<'tcx> {
-    typeck_with_fallback(tcx, def_id, Some(inspect))
+    typeck_with_inspect(tcx, def_id, Some(inspect))
 }
 
 #[instrument(level = "debug", skip(tcx, inspector), ret)]
-fn typeck_with_fallback<'tcx>(
+fn typeck_with_inspect<'tcx>(
     tcx: TyCtxt<'tcx>,
     def_id: LocalDefId,
     inspector: Option<ObligationInspector<'tcx>>,
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index aff66e48fbb..241bc35857a 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -875,6 +875,8 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
     });
     // Freeze definitions as we don't add new ones at this point.
     // We need to wait until now since we synthesize a by-move body
+    // for all coroutine-closures.
+    //
     // This improves performance by allowing lock-free access to them.
     tcx.untracked().definitions.freeze();
 
@@ -887,7 +889,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
         });
     });
     sess.time("MIR_effect_checking", || {
-        for def_id in tcx.hir().body_owners() {
+        tcx.hir().par_body_owners(|def_id| {
             tcx.ensure().has_ffi_unwind_calls(def_id);
 
             // If we need to codegen, ensure that we emit all errors from
@@ -898,15 +900,17 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
             {
                 tcx.ensure().mir_drops_elaborated_and_const_checked(def_id);
             }
-        }
+        });
     });
-    tcx.hir().par_body_owners(|def_id| {
-        if tcx.is_coroutine(def_id.to_def_id()) {
-            tcx.ensure().mir_coroutine_witnesses(def_id);
-            tcx.ensure().check_coroutine_obligations(
-                tcx.typeck_root_def_id(def_id.to_def_id()).expect_local(),
-            );
-        }
+    sess.time("coroutine_obligations", || {
+        tcx.hir().par_body_owners(|def_id| {
+            if tcx.is_coroutine(def_id.to_def_id()) {
+                tcx.ensure().mir_coroutine_witnesses(def_id);
+                tcx.ensure().check_coroutine_obligations(
+                    tcx.typeck_root_def_id(def_id.to_def_id()).expect_local(),
+                );
+            }
+        });
     });
 
     sess.time("layout_testing", || layout_test::test_layout(tcx));
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index bfbcb0532c1..65e93c3a1cc 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1164,8 +1164,7 @@ rustc_queries! {
     }
 
     /// Check whether the function has any recursion that could cause the inliner to trigger
-    /// a cycle. Returns the call stack causing the cycle. The call stack does not contain the
-    /// current function, just all intermediate functions.
+    /// a cycle.
     query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool {
         fatal_cycle
         desc { |tcx|
diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl
index 5203c33c968..ffdb721fb18 100644
--- a/compiler/rustc_mir_build/messages.ftl
+++ b/compiler/rustc_mir_build/messages.ftl
@@ -118,12 +118,6 @@ mir_build_extern_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
     .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior
     .label = use of extern static
 
-mir_build_force_inline =
-    `{$callee}` is incompatible with `#[rustc_force_inline]`
-    .attr = annotation here
-    .callee = `{$callee}` defined here
-    .note = incompatible due to: {$reason}
-
 mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
 
 mir_build_initializing_type_with_requires_unsafe =
@@ -330,12 +324,6 @@ mir_build_type_not_structural_more_info = see https://doc.rust-lang.org/stable/s
 mir_build_type_not_structural_tip =
     the `PartialEq` trait must be derived, manual `impl`s are not sufficient; see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html for details
 
-mir_build_unconditional_recursion = function cannot return without recursing
-    .label = cannot return without recursing
-    .help = a `loop` may express intention better if this is on purpose
-
-mir_build_unconditional_recursion_call_site_label = recursive call site
-
 mir_build_union_field_requires_unsafe =
     access to union field is unsafe and requires unsafe block
     .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior
diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs
index 8b01ec0d06a..9fa431f7d5f 100644
--- a/compiler/rustc_mir_build/src/builder/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/mod.rs
@@ -26,10 +26,8 @@ use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode
 use rustc_middle::{bug, span_bug};
 use rustc_span::{Span, Symbol, sym};
 
-use super::lints;
 use crate::builder::expr::as_place::PlaceBuilder;
 use crate::builder::scope::DropKind;
-use crate::check_inline;
 
 pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
     tcx: TyCtxt<'tcx>,
@@ -48,7 +46,7 @@ pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
 }
 
 /// Construct the MIR for a given `DefId`.
-pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx> {
+pub(crate) fn build_mir<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx> {
     let tcx = tcx.tcx;
     tcx.ensure_with_value().thir_abstract_const(def);
     if let Err(e) = tcx.check_match(def) {
@@ -80,9 +78,6 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx
         }
     };
 
-    lints::check(tcx, &body);
-    check_inline::check_force_inline(tcx, &body);
-
     // The borrow checker will replace all the regions here with its own
     // inference variables. There's no point having non-erased regions here.
     // The exception is `body.user_type_annotations`, which is used unmodified
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 90c31a2caa3..83aec9ccdef 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -12,16 +12,6 @@ use rustc_span::{Span, Symbol};
 use crate::fluent_generated as fluent;
 
 #[derive(LintDiagnostic)]
-#[diag(mir_build_unconditional_recursion)]
-#[help]
-pub(crate) struct UnconditionalRecursion {
-    #[label]
-    pub(crate) span: Span,
-    #[label(mir_build_unconditional_recursion_call_site_label)]
-    pub(crate) call_sites: Vec<Span>,
-}
-
-#[derive(LintDiagnostic)]
 #[diag(mir_build_call_to_deprecated_safe_fn_requires_unsafe)]
 pub(crate) struct CallToDeprecatedSafeFnRequiresUnsafe {
     #[label]
@@ -1107,15 +1097,3 @@ impl<'a> Subdiagnostic for Rust2024IncompatiblePatSugg<'a> {
         );
     }
 }
-
-#[derive(Diagnostic)]
-#[diag(mir_build_force_inline)]
-#[note]
-pub(crate) struct InvalidForceInline {
-    #[primary_span]
-    pub attr_span: Span,
-    #[label(mir_build_callee)]
-    pub callee_span: Span,
-    pub callee: String,
-    pub reason: &'static str,
-}
diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs
index 76a35355de7..8e786733ee0 100644
--- a/compiler/rustc_mir_build/src/lib.rs
+++ b/compiler/rustc_mir_build/src/lib.rs
@@ -15,11 +15,9 @@
 // "Go to file" feature to silently ignore all files in the module, probably
 // because it assumes that "build" is a build-output directory. See #134365.
 mod builder;
-pub mod check_inline;
 mod check_tail_calls;
 mod check_unsafety;
 mod errors;
-pub mod lints;
 mod thir;
 
 use rustc_middle::util::Providers;
@@ -29,7 +27,7 @@ rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 pub fn provide(providers: &mut Providers) {
     providers.check_match = thir::pattern::check_match;
     providers.lit_to_const = thir::constant::lit_to_const;
-    providers.hooks.build_mir = builder::mir_build;
+    providers.hooks.build_mir = builder::build_mir;
     providers.closure_saved_names_of_captured_variables =
         builder::closure_saved_names_of_captured_variables;
     providers.check_unsafety = check_unsafety::check_unsafety;
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl
index b0c023cca82..5628f4c9381 100644
--- a/compiler/rustc_mir_transform/messages.ftl
+++ b/compiler/rustc_mir_transform/messages.ftl
@@ -27,6 +27,12 @@ mir_transform_force_inline =
     .callee = `{$callee}` defined here
     .note = could not be inlined due to: {$reason}
 
+mir_transform_force_inline_attr =
+    `{$callee}` is incompatible with `#[rustc_force_inline]`
+    .attr = annotation here
+    .callee = `{$callee}` defined here
+    .note = incompatible due to: {$reason}
+
 mir_transform_force_inline_justification =
     `{$callee}` is required to be inlined to: {$sym}
 
@@ -66,6 +72,12 @@ mir_transform_unaligned_packed_ref = reference to packed field is unaligned
     .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
     .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
 
+mir_transform_unconditional_recursion = function cannot return without recursing
+    .label = cannot return without recursing
+    .help = a `loop` may express intention better if this is on purpose
+
+mir_transform_unconditional_recursion_call_site_label = recursive call site
+
 mir_transform_undefined_transmute = pointers cannot be transmuted to integers during const eval
     .note = at compile-time, pointers do not have an integer value
     .note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_transform/src/check_call_recursion.rs
index 5cf33868ade..51fd3c6512e 100644
--- a/compiler/rustc_mir_build/src/lints.rs
+++ b/compiler/rustc_mir_transform/src/check_call_recursion.rs
@@ -10,25 +10,54 @@ use rustc_session::lint::builtin::UNCONDITIONAL_RECURSION;
 use rustc_span::Span;
 
 use crate::errors::UnconditionalRecursion;
+use crate::pass_manager::MirLint;
 
-pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
-    check_call_recursion(tcx, body);
+pub(super) struct CheckCallRecursion;
+
+impl<'tcx> MirLint<'tcx> for CheckCallRecursion {
+    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
+        let def_id = body.source.def_id().expect_local();
+
+        if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) {
+            // If this is trait/impl method, extract the trait's args.
+            let trait_args = match tcx.trait_of_item(def_id.to_def_id()) {
+                Some(trait_def_id) => {
+                    let trait_args_count = tcx.generics_of(trait_def_id).count();
+                    &GenericArgs::identity_for_item(tcx, def_id)[..trait_args_count]
+                }
+                _ => &[],
+            };
+
+            check_recursion(tcx, body, CallRecursion { trait_args })
+        }
+    }
 }
 
-fn check_call_recursion<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
-    let def_id = body.source.def_id().expect_local();
+/// Requires drop elaboration to have been performed.
+pub(super) struct CheckDropRecursion;
 
-    if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) {
-        // If this is trait/impl method, extract the trait's args.
-        let trait_args = match tcx.trait_of_item(def_id.to_def_id()) {
-            Some(trait_def_id) => {
-                let trait_args_count = tcx.generics_of(trait_def_id).count();
-                &GenericArgs::identity_for_item(tcx, def_id)[..trait_args_count]
-            }
-            _ => &[],
-        };
+impl<'tcx> MirLint<'tcx> for CheckDropRecursion {
+    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
+        let def_id = body.source.def_id().expect_local();
 
-        check_recursion(tcx, body, CallRecursion { trait_args })
+        // First check if `body` is an `fn drop()` of `Drop`
+        if let DefKind::AssocFn = tcx.def_kind(def_id)
+        && let Some(trait_ref) =
+            tcx.impl_of_method(def_id.to_def_id()).and_then(|def_id| tcx.impl_trait_ref(def_id))
+        && let Some(drop_trait) = tcx.lang_items().drop_trait()
+        && drop_trait == trait_ref.instantiate_identity().def_id
+        // avoid erroneous `Drop` impls from causing ICEs below
+        && let sig = tcx.fn_sig(def_id).instantiate_identity()
+        && sig.inputs().skip_binder().len() == 1
+        {
+            // It was. Now figure out for what type `Drop` is implemented and then
+            // check for recursion.
+            if let ty::Ref(_, dropped_ty, _) =
+                tcx.liberate_late_bound_regions(def_id.to_def_id(), sig.input(0)).kind()
+            {
+                check_recursion(tcx, body, RecursiveDrop { drop_for: *dropped_ty });
+            }
+        }
     }
 }
 
@@ -61,30 +90,6 @@ fn check_recursion<'tcx>(
     }
 }
 
-/// Requires drop elaboration to have been performed first.
-pub fn check_drop_recursion<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
-    let def_id = body.source.def_id().expect_local();
-
-    // First check if `body` is an `fn drop()` of `Drop`
-    if let DefKind::AssocFn = tcx.def_kind(def_id)
-        && let Some(trait_ref) =
-            tcx.impl_of_method(def_id.to_def_id()).and_then(|def_id| tcx.impl_trait_ref(def_id))
-        && let Some(drop_trait) = tcx.lang_items().drop_trait()
-        && drop_trait == trait_ref.instantiate_identity().def_id
-        // avoid erroneous `Drop` impls from causing ICEs below
-        && let sig = tcx.fn_sig(def_id).instantiate_identity()
-        && sig.inputs().skip_binder().len() == 1
-    {
-        // It was. Now figure out for what type `Drop` is implemented and then
-        // check for recursion.
-        if let ty::Ref(_, dropped_ty, _) =
-            tcx.liberate_late_bound_regions(def_id.to_def_id(), sig.input(0)).kind()
-        {
-            check_recursion(tcx, body, RecursiveDrop { drop_for: *dropped_ty });
-        }
-    }
-}
-
 trait TerminatorClassifier<'tcx> {
     fn is_recursive_terminator(
         &self,
diff --git a/compiler/rustc_mir_build/src/check_inline.rs b/compiler/rustc_mir_transform/src/check_inline.rs
index 1af3b3e2c13..497f4a660ea 100644
--- a/compiler/rustc_mir_build/src/check_inline.rs
+++ b/compiler/rustc_mir_transform/src/check_inline.rs
@@ -1,3 +1,6 @@
+//! Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
+//! definition alone (irrespective of any specific caller).
+
 use rustc_attr_parsing::InlineAttr;
 use rustc_hir::def_id::DefId;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
@@ -6,30 +9,37 @@ use rustc_middle::ty;
 use rustc_middle::ty::TyCtxt;
 use rustc_span::sym;
 
-/// Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
-/// definition alone (irrespective of any specific caller).
-pub(crate) fn check_force_inline<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
-    let def_id = body.source.def_id();
-    if !tcx.hir().body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
-        return;
-    }
-    let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
-        return;
-    };
+use crate::pass_manager::MirLint;
 
-    if let Err(reason) =
-        is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
-    {
-        tcx.dcx().emit_err(crate::errors::InvalidForceInline {
-            attr_span,
-            callee_span: tcx.def_span(def_id),
-            callee: tcx.def_path_str(def_id),
-            reason,
-        });
+pub(super) struct CheckForceInline;
+
+impl<'tcx> MirLint<'tcx> for CheckForceInline {
+    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
+        let def_id = body.source.def_id();
+        if !tcx.hir().body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
+            return;
+        }
+        let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
+            return;
+        };
+
+        if let Err(reason) =
+            is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
+        {
+            tcx.dcx().emit_err(crate::errors::InvalidForceInline {
+                attr_span,
+                callee_span: tcx.def_span(def_id),
+                callee: tcx.def_path_str(def_id),
+                reason,
+            });
+        }
     }
 }
 
-pub fn is_inline_valid_on_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<(), &'static str> {
+pub(super) fn is_inline_valid_on_fn<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    def_id: DefId,
+) -> Result<(), &'static str> {
     let codegen_attrs = tcx.codegen_fn_attrs(def_id);
     if tcx.has_attr(def_id, sym::rustc_no_mir_inline) {
         return Err("#[rustc_no_mir_inline]");
@@ -65,7 +75,7 @@ pub fn is_inline_valid_on_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<(
     Ok(())
 }
 
-pub fn is_inline_valid_on_body<'tcx>(
+pub(super) fn is_inline_valid_on_body<'tcx>(
     _: TyCtxt<'tcx>,
     body: &Body<'tcx>,
 ) -> Result<(), &'static str> {
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 015633d145f..a2fd46043ca 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -10,6 +10,28 @@ use rustc_span::{Span, Symbol};
 use crate::fluent_generated as fluent;
 
 #[derive(LintDiagnostic)]
+#[diag(mir_transform_unconditional_recursion)]
+#[help]
+pub(crate) struct UnconditionalRecursion {
+    #[label]
+    pub(crate) span: Span,
+    #[label(mir_transform_unconditional_recursion_call_site_label)]
+    pub(crate) call_sites: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(mir_transform_force_inline_attr)]
+#[note]
+pub(crate) struct InvalidForceInline {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label(mir_transform_callee)]
+    pub callee_span: Span,
+    pub callee: String,
+    pub reason: &'static str,
+}
+
+#[derive(LintDiagnostic)]
 pub(crate) enum ConstMutate {
     #[diag(mir_transform_const_modify)]
     #[note]
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index 470393c9ae1..2052e28325c 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -21,8 +21,8 @@ use tracing::{debug, instrument, trace, trace_span};
 use crate::cost_checker::CostChecker;
 use crate::deref_separator::deref_finder;
 use crate::simplify::simplify_cfg;
-use crate::util;
 use crate::validate::validate_types;
+use crate::{check_inline, util};
 
 pub(crate) mod cycle;
 
@@ -575,7 +575,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
     check_mir_is_available(inliner, caller_body, callsite.callee)?;
 
     let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
-    rustc_mir_build::check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
+    check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
     check_codegen_attributes(inliner, callsite, callee_attrs)?;
 
     let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
@@ -590,7 +590,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
     }
 
     let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
-    rustc_mir_build::check_inline::is_inline_valid_on_body(tcx, callee_body)?;
+    check_inline::is_inline_valid_on_body(tcx, callee_body)?;
     inliner.check_callee_mir_body(callsite, callee_body, callee_attrs)?;
 
     let Ok(callee_body) = callsite.callee.try_instantiate_mir_and_normalize_erasing_regions(
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index db999bea986..d1bacf1f598 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -114,6 +114,8 @@ declare_passes! {
     mod add_moves_for_packed_drops : AddMovesForPackedDrops;
     mod add_retag : AddRetag;
     mod add_subtyping_projections : Subtyper;
+    mod check_inline : CheckForceInline;
+    mod check_call_recursion : CheckCallRecursion, CheckDropRecursion;
     mod check_alignment : CheckAlignment;
     mod check_const_item_mutation : CheckConstItemMutation;
     mod check_packed_ref : CheckPackedRef;
@@ -375,6 +377,8 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
         &mut body,
         &[
             // MIR-level lints.
+            &Lint(check_inline::CheckForceInline),
+            &Lint(check_call_recursion::CheckCallRecursion),
             &Lint(check_packed_ref::CheckPackedRef),
             &Lint(check_const_item_mutation::CheckConstItemMutation),
             &Lint(function_item_references::FunctionItemReferences),
@@ -505,10 +509,6 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
 
     run_analysis_to_runtime_passes(tcx, &mut body);
 
-    // Now that drop elaboration has been performed, we can check for
-    // unconditional drop recursion.
-    rustc_mir_build::lints::check_drop_recursion(tcx, &body);
-
     tcx.alloc_steal_mir(body)
 }
 
@@ -570,6 +570,8 @@ fn run_runtime_lowering_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
         // Calling this after `PostAnalysisNormalize` ensures that we don't deal with opaque types.
         &add_subtyping_projections::Subtyper,
         &elaborate_drops::ElaborateDrops,
+        // Needs to happen after drop elaboration.
+        &Lint(check_call_recursion::CheckDropRecursion),
         // This will remove extraneous landing pads which are no longer
         // necessary as well as forcing any call in a non-unwinding
         // function calling a possibly-unwinding function to abort the process.