about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir_typeck/messages.ftl4
-rw-r--r--compiler/rustc_hir_typeck/src/_match.rs139
-rw-r--r--compiler/rustc_hir_typeck/src/coercion.rs141
-rw-r--r--compiler/rustc_hir_typeck/src/errors.rs18
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs5
-rw-r--r--compiler/rustc_infer/messages.ftl3
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs18
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs38
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/suggest.rs27
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs9
-rw-r--r--compiler/rustc_infer/src/infer/opaque_types/mod.rs2
-rw-r--r--compiler/rustc_infer/src/infer/type_variable.rs1
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs6
-rw-r--r--src/tools/run-make-support/src/lib.rs189
-rw-r--r--src/tools/run-make-support/src/run.rs67
-rw-r--r--src/tools/run-make-support/src/rustc.rs147
-rw-r--r--src/tools/run-make-support/src/rustdoc.rs80
-rw-r--r--tests/run-make/CURRENT_RUSTC_VERSION/rmake.rs21
-rw-r--r--tests/run-make/a-b-a-linker-guard/rmake.rs44
-rw-r--r--tests/run-make/compiler-builtins/rmake.rs8
-rw-r--r--tests/run-make/rustdoc-test-args/rmake.rs9
-rw-r--r--tests/run-make/wasm-abi/rmake.rs7
-rw-r--r--tests/run-make/wasm-custom-section/rmake.rs8
-rw-r--r--tests/run-make/wasm-custom-sections-opt/rmake.rs7
-rw-r--r--tests/run-make/wasm-export-all-symbols/rmake.rs13
-rw-r--r--tests/run-make/wasm-import-module/rmake.rs13
-rw-r--r--tests/run-make/wasm-panic-small/rmake.rs14
-rw-r--r--tests/run-make/wasm-spurious-import/rmake.rs11
-rw-r--r--tests/run-make/wasm-stringify-ints-small/rmake.rs6
-rw-r--r--tests/run-make/wasm-symbols-different-module/rmake.rs6
-rw-r--r--tests/run-make/wasm-symbols-not-exported/rmake.rs18
-rw-r--r--tests/run-make/wasm-symbols-not-imported/rmake.rs18
32 files changed, 622 insertions, 475 deletions
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index 0ca958302f7..f7af438ad16 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -122,6 +122,10 @@ hir_typeck_return_stmt_outside_of_fn_body =
     .encl_body_label = the {$statement_kind} is part of this body...
     .encl_fn_label = ...not the enclosing function body
 
+hir_typeck_rpit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions
+
+hir_typeck_rpit_change_return_type = you could change the return type to be a boxed trait object
+
 hir_typeck_rustcall_incorrect_args =
     functions with the "rust-call" ABI must take a single non-self tuple argument
 
diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs
index e42db342f14..2a2fd0a41a6 100644
--- a/compiler/rustc_hir_typeck/src/_match.rs
+++ b/compiler/rustc_hir_typeck/src/_match.rs
@@ -1,17 +1,13 @@
 use crate::coercion::{AsCoercionSite, CoerceMany};
 use crate::{Diverges, Expectation, FnCtxt, Needs};
 use rustc_errors::{Applicability, Diag};
-use rustc_hir::{
-    self as hir,
-    def::{CtorOf, DefKind, Res},
-    ExprKind, PatKind,
-};
+use rustc_hir::def::{CtorOf, DefKind, Res};
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::{self as hir, ExprKind, PatKind};
 use rustc_hir_pretty::ty_to_string;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
-use rustc_infer::traits::Obligation;
 use rustc_middle::ty::{self, Ty};
 use rustc_span::Span;
-use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
 use rustc_trait_selection::traits::{
     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
 };
@@ -91,10 +87,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
             let arm_ty = self.check_expr_with_expectation(arm.body, expected);
             all_arms_diverge &= self.diverges.get();
-
-            let opt_suggest_box_span = prior_arm.and_then(|(_, prior_arm_ty, _)| {
-                self.opt_suggest_box_span(prior_arm_ty, arm_ty, orig_expected)
-            });
+            let tail_defines_return_position_impl_trait =
+                self.return_position_impl_trait_from_match_expectation(orig_expected);
 
             let (arm_block_id, arm_span) = if let hir::ExprKind::Block(blk, _) = arm.body.kind {
                 (Some(blk.hir_id), self.find_block_span(blk))
@@ -120,7 +114,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         scrut_span: scrut.span,
                         source: match_src,
                         prior_non_diverging_arms: prior_non_diverging_arms.clone(),
-                        opt_suggest_box_span,
+                        tail_defines_return_position_impl_trait,
                     })),
                 ),
             };
@@ -422,7 +416,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         else_expr: &'tcx hir::Expr<'tcx>,
         then_ty: Ty<'tcx>,
         else_ty: Ty<'tcx>,
-        opt_suggest_box_span: Option<Span>,
+        tail_defines_return_position_impl_trait: Option<LocalDefId>,
     ) -> ObligationCause<'tcx> {
         let mut outer_span = if self.tcx.sess.source_map().is_multiline(span) {
             // The `if`/`else` isn't in one line in the output, include some context to make it
@@ -513,7 +507,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 then_ty,
                 else_ty,
                 outer_span,
-                opt_suggest_box_span,
+                tail_defines_return_position_impl_trait,
             })),
         )
     }
@@ -593,96 +587,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
-    /// When we have a `match` as a tail expression in a `fn` with a returned `impl Trait`
-    /// we check if the different arms would work with boxed trait objects instead and
-    /// provide a structured suggestion in that case.
-    pub(crate) fn opt_suggest_box_span(
+    // Does the expectation of the match define an RPIT?
+    // (e.g. we're in the tail of a function body)
+    //
+    // Returns the `LocalDefId` of the RPIT, which is always identity-substituted.
+    pub fn return_position_impl_trait_from_match_expectation(
         &self,
-        first_ty: Ty<'tcx>,
-        second_ty: Ty<'tcx>,
-        orig_expected: Expectation<'tcx>,
-    ) -> Option<Span> {
-        // FIXME(compiler-errors): This really shouldn't need to be done during the
-        // "good" path of typeck, but here we are.
-        match orig_expected {
-            Expectation::ExpectHasType(expected) => {
-                let TypeVariableOrigin {
-                    span,
-                    kind: TypeVariableOriginKind::OpaqueTypeInference(rpit_def_id),
-                    ..
-                } = self.type_var_origin(expected)?
-                else {
-                    return None;
-                };
-
-                let Some(rpit_local_def_id) = rpit_def_id.as_local() else {
-                    return None;
-                };
-                if !matches!(
-                    self.tcx.hir().expect_item(rpit_local_def_id).expect_opaque_ty().origin,
-                    hir::OpaqueTyOrigin::FnReturn(..)
-                ) {
-                    return None;
-                }
-
-                let sig = self.body_fn_sig()?;
-
-                let args = sig.output().walk().find_map(|arg| {
-                    if let ty::GenericArgKind::Type(ty) = arg.unpack()
-                        && let ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) = *ty.kind()
-                        && def_id == rpit_def_id
-                    {
-                        Some(args)
-                    } else {
-                        None
-                    }
-                })?;
-
-                if !self.can_coerce(first_ty, expected) || !self.can_coerce(second_ty, expected) {
-                    return None;
-                }
-
-                for ty in [first_ty, second_ty] {
-                    for (clause, _) in self
-                        .tcx
-                        .explicit_item_super_predicates(rpit_def_id)
-                        .iter_instantiated_copied(self.tcx, args)
-                    {
-                        let pred = clause.kind().rebind(match clause.kind().skip_binder() {
-                            ty::ClauseKind::Trait(trait_pred) => {
-                                assert!(matches!(
-                                    *trait_pred.trait_ref.self_ty().kind(),
-                                    ty::Alias(ty::Opaque, ty::AliasTy { def_id, args: alias_args, .. })
-                                    if def_id == rpit_def_id && args == alias_args
-                                ));
-                                ty::ClauseKind::Trait(trait_pred.with_self_ty(self.tcx, ty))
-                            }
-                            ty::ClauseKind::Projection(mut proj_pred) => {
-                                assert!(matches!(
-                                    *proj_pred.projection_ty.self_ty().kind(),
-                                    ty::Alias(ty::Opaque, ty::AliasTy { def_id, args: alias_args, .. })
-                                    if def_id == rpit_def_id && args == alias_args
-                                ));
-                                proj_pred = proj_pred.with_self_ty(self.tcx, ty);
-                                ty::ClauseKind::Projection(proj_pred)
-                            }
-                            _ => continue,
-                        });
-                        if !self.predicate_must_hold_modulo_regions(&Obligation::new(
-                            self.tcx,
-                            ObligationCause::misc(span, self.body_id),
-                            self.param_env,
-                            pred,
-                        )) {
-                            return None;
-                        }
-                    }
-                }
-
-                Some(span)
-            }
-            _ => None,
+        expectation: Expectation<'tcx>,
+    ) -> Option<LocalDefId> {
+        let expected_ty = expectation.to_option(self)?;
+        let (def_id, args) = match *expected_ty.kind() {
+            // FIXME: Could also check that the RPIT is not defined
+            ty::Alias(ty::Opaque, alias_ty) => (alias_ty.def_id.as_local()?, alias_ty.args),
+            // FIXME(-Znext-solver): Remove this branch once `replace_opaque_types_with_infer` is gone.
+            ty::Infer(ty::TyVar(_)) => self
+                .inner
+                .borrow()
+                .iter_opaque_types()
+                .find(|(_, v)| v.ty == expected_ty)
+                .map(|(k, _)| (k.def_id, k.args))?,
+            _ => return None,
+        };
+        let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = self.tcx.opaque_type_origin(def_id)
+        else {
+            return None;
+        };
+        if &args[0..self.tcx.generics_of(parent_def_id).count()]
+            != ty::GenericArgs::identity_for_item(self.tcx, parent_def_id).as_slice()
+        {
+            return None;
         }
+        Some(def_id)
     }
 }
 
diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs
index 3328177634b..44b19318d5d 100644
--- a/compiler/rustc_hir_typeck/src/coercion.rs
+++ b/compiler/rustc_hir_typeck/src/coercion.rs
@@ -35,17 +35,18 @@
 //! // and are then unable to coerce `&7i32` to `&mut i32`.
 //! ```
 
+use crate::errors::SuggestBoxingForReturnImplTrait;
 use crate::FnCtxt;
 use rustc_errors::{codes::*, struct_span_code_err, Applicability, Diag, MultiSpan};
 use rustc_hir as hir;
-use rustc_hir::def_id::DefId;
+use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::Expr;
 use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::infer::{Coercion, DefineOpaqueTypes, InferOk, InferResult};
-use rustc_infer::traits::TraitEngine;
 use rustc_infer::traits::TraitEngineExt as _;
+use rustc_infer::traits::{IfExpressionCause, MatchExpressionArmCause, TraitEngine};
 use rustc_infer::traits::{Obligation, PredicateObligation};
 use rustc_middle::lint::in_external_macro;
 use rustc_middle::traits::BuiltinImplSource;
@@ -59,6 +60,7 @@ use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::sym;
 use rustc_span::DesugaringKind;
+use rustc_span::{BytePos, Span};
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::infer::InferCtxtExt as _;
 use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
@@ -1638,6 +1640,77 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
                             unsized_return = self.is_return_ty_definitely_unsized(fcx);
                         }
                     }
+                    ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
+                        arm_span,
+                        arm_ty,
+                        prior_arm_ty,
+                        ref prior_non_diverging_arms,
+                        tail_defines_return_position_impl_trait: Some(rpit_def_id),
+                        ..
+                    }) => {
+                        err = fcx.err_ctxt().report_mismatched_types(
+                            cause,
+                            expected,
+                            found,
+                            coercion_error,
+                        );
+                        // Check that we're actually in the second or later arm
+                        if prior_non_diverging_arms.len() > 0 {
+                            self.suggest_boxing_tail_for_return_position_impl_trait(
+                                fcx,
+                                &mut err,
+                                rpit_def_id,
+                                arm_ty,
+                                prior_arm_ty,
+                                prior_non_diverging_arms
+                                    .iter()
+                                    .chain(std::iter::once(&arm_span))
+                                    .copied(),
+                            );
+                        }
+                    }
+                    ObligationCauseCode::IfExpression(box IfExpressionCause {
+                        then_id,
+                        else_id,
+                        then_ty,
+                        else_ty,
+                        tail_defines_return_position_impl_trait: Some(rpit_def_id),
+                        ..
+                    }) => {
+                        err = fcx.err_ctxt().report_mismatched_types(
+                            cause,
+                            expected,
+                            found,
+                            coercion_error,
+                        );
+                        let then_span = fcx.find_block_span_from_hir_id(then_id);
+                        let else_span = fcx.find_block_span_from_hir_id(else_id);
+                        // don't suggest wrapping either blocks in `if .. {} else {}`
+                        let is_empty_arm = |id| {
+                            let hir::Node::Block(blk) = fcx.tcx.hir_node(id) else {
+                                return false;
+                            };
+                            if blk.expr.is_some() || !blk.stmts.is_empty() {
+                                return false;
+                            }
+                            let Some((_, hir::Node::Expr(expr))) =
+                                fcx.tcx.hir().parent_iter(id).nth(1)
+                            else {
+                                return false;
+                            };
+                            matches!(expr.kind, hir::ExprKind::If(..))
+                        };
+                        if !is_empty_arm(then_id) && !is_empty_arm(else_id) {
+                            self.suggest_boxing_tail_for_return_position_impl_trait(
+                                fcx,
+                                &mut err,
+                                rpit_def_id,
+                                then_ty,
+                                else_ty,
+                                [then_span, else_span].into_iter(),
+                            );
+                        }
+                    }
                     _ => {
                         err = fcx.err_ctxt().report_mismatched_types(
                             cause,
@@ -1677,6 +1750,70 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
         }
     }
 
+    fn suggest_boxing_tail_for_return_position_impl_trait(
+        &self,
+        fcx: &FnCtxt<'_, 'tcx>,
+        err: &mut Diag<'_>,
+        rpit_def_id: LocalDefId,
+        a_ty: Ty<'tcx>,
+        b_ty: Ty<'tcx>,
+        arm_spans: impl Iterator<Item = Span>,
+    ) {
+        let compatible = |ty: Ty<'tcx>| {
+            fcx.probe(|_| {
+                let ocx = ObligationCtxt::new(fcx);
+                ocx.register_obligations(
+                    fcx.tcx
+                        .item_super_predicates(rpit_def_id)
+                        .instantiate_identity_iter()
+                        .filter_map(|clause| {
+                            let predicate = clause
+                                .kind()
+                                .map_bound(|clause| match clause {
+                                    ty::ClauseKind::Trait(trait_pred) => Some(
+                                        ty::ClauseKind::Trait(trait_pred.with_self_ty(fcx.tcx, ty)),
+                                    ),
+                                    ty::ClauseKind::Projection(proj_pred) => {
+                                        Some(ty::ClauseKind::Projection(
+                                            proj_pred.with_self_ty(fcx.tcx, ty),
+                                        ))
+                                    }
+                                    _ => None,
+                                })
+                                .transpose()?;
+                            Some(Obligation::new(
+                                fcx.tcx,
+                                ObligationCause::dummy(),
+                                fcx.param_env,
+                                predicate,
+                            ))
+                        }),
+                );
+                ocx.select_where_possible().is_empty()
+            })
+        };
+
+        if !compatible(a_ty) || !compatible(b_ty) {
+            return;
+        }
+
+        let rpid_def_span = fcx.tcx.def_span(rpit_def_id);
+        err.subdiagnostic(
+            fcx.tcx.dcx(),
+            SuggestBoxingForReturnImplTrait::ChangeReturnType {
+                start_sp: rpid_def_span.with_hi(rpid_def_span.lo() + BytePos(4)),
+                end_sp: rpid_def_span.shrink_to_hi(),
+            },
+        );
+
+        let (starts, ends) =
+            arm_spans.map(|span| (span.shrink_to_lo(), span.shrink_to_hi())).unzip();
+        err.subdiagnostic(
+            fcx.tcx.dcx(),
+            SuggestBoxingForReturnImplTrait::BoxReturnExpr { starts, ends },
+        );
+    }
+
     fn note_unreachable_loop_return(
         &self,
         err: &mut Diag<'_>,
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index eee4ac5ad23..f9b2ec69730 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -621,3 +621,21 @@ pub struct NoteCallerChoosesTyForTyParam<'tcx> {
     pub ty_param_name: Symbol,
     pub found_ty: Ty<'tcx>,
 }
+
+#[derive(Subdiagnostic)]
+pub enum SuggestBoxingForReturnImplTrait {
+    #[multipart_suggestion(hir_typeck_rpit_change_return_type, applicability = "maybe-incorrect")]
+    ChangeReturnType {
+        #[suggestion_part(code = "Box<dyn")]
+        start_sp: Span,
+        #[suggestion_part(code = ">")]
+        end_sp: Span,
+    },
+    #[multipart_suggestion(hir_typeck_rpit_box_return_expr, applicability = "maybe-incorrect")]
+    BoxReturnExpr {
+        #[suggestion_part(code = "Box::new(")]
+        starts: Vec<Span>,
+        #[suggestion_part(code = ")")]
+        ends: Vec<Span>,
+    },
+}
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index f38f04fce43..d3e6eb124f7 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -1088,7 +1088,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let else_ty = self.check_expr_with_expectation(else_expr, expected);
             let else_diverges = self.diverges.get();
 
-            let opt_suggest_box_span = self.opt_suggest_box_span(then_ty, else_ty, orig_expected);
+            let tail_defines_return_position_impl_trait =
+                self.return_position_impl_trait_from_match_expectation(orig_expected);
             let if_cause = self.if_cause(
                 sp,
                 cond_expr.span,
@@ -1096,7 +1097,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 else_expr,
                 then_ty,
                 else_ty,
-                opt_suggest_box_span,
+                tail_defines_return_position_impl_trait,
             );
 
             coerce.coerce(self, &if_cause, else_expr, else_ty);
diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index 521c65c6009..fdb6ab8f59b 100644
--- a/compiler/rustc_infer/messages.ftl
+++ b/compiler/rustc_infer/messages.ftl
@@ -270,9 +270,6 @@ infer_ril_introduced_by = requirement introduced by this return type
 infer_ril_introduced_here = `'static` requirement introduced here
 infer_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type
 
-infer_sbfrit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions
-
-infer_sbfrit_change_return_type = you could change the return type to be a boxed trait object
 infer_source_kind_closure_return =
     try giving this closure an explicit return type
 
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index d0b1f2848ff..6192eaf3c3a 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -1263,24 +1263,6 @@ pub enum SuggestAccessingField<'a> {
 }
 
 #[derive(Subdiagnostic)]
-pub enum SuggestBoxingForReturnImplTrait {
-    #[multipart_suggestion(infer_sbfrit_change_return_type, applicability = "maybe-incorrect")]
-    ChangeReturnType {
-        #[suggestion_part(code = "Box<dyn")]
-        start_sp: Span,
-        #[suggestion_part(code = ">")]
-        end_sp: Span,
-    },
-    #[multipart_suggestion(infer_sbfrit_box_return_expr, applicability = "maybe-incorrect")]
-    BoxReturnExpr {
-        #[suggestion_part(code = "Box::new(")]
-        starts: Vec<Span>,
-        #[suggestion_part(code = ")")]
-        ends: Vec<Span>,
-    },
-}
-
-#[derive(Subdiagnostic)]
 #[multipart_suggestion(infer_stp_wrap_one, applicability = "maybe-incorrect")]
 pub struct SuggestTuplePatternOne {
     pub variant: String,
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 82634f7308d..dd438736baa 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -784,7 +784,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 prior_arm_ty,
                 source,
                 ref prior_non_diverging_arms,
-                opt_suggest_box_span,
                 scrut_span,
                 ..
             }) => match source {
@@ -853,17 +852,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     ) {
                         err.subdiagnostic(self.dcx(), subdiag);
                     }
-                    if let Some(ret_sp) = opt_suggest_box_span {
-                        // Get return type span and point to it.
-                        self.suggest_boxing_for_return_impl_trait(
-                            err,
-                            ret_sp,
-                            prior_non_diverging_arms
-                                .iter()
-                                .chain(std::iter::once(&arm_span))
-                                .copied(),
-                        );
-                    }
                 }
             },
             ObligationCauseCode::IfExpression(box IfExpressionCause {
@@ -872,7 +860,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 then_ty,
                 else_ty,
                 outer_span,
-                opt_suggest_box_span,
+                ..
             }) => {
                 let then_span = self.find_block_span_from_hir_id(then_id);
                 let else_span = self.find_block_span_from_hir_id(else_id);
@@ -890,30 +878,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 ) {
                     err.subdiagnostic(self.dcx(), subdiag);
                 }
-                // don't suggest wrapping either blocks in `if .. {} else {}`
-                let is_empty_arm = |id| {
-                    let hir::Node::Block(blk) = self.tcx.hir_node(id) else {
-                        return false;
-                    };
-                    if blk.expr.is_some() || !blk.stmts.is_empty() {
-                        return false;
-                    }
-                    let Some((_, hir::Node::Expr(expr))) = self.tcx.hir().parent_iter(id).nth(1)
-                    else {
-                        return false;
-                    };
-                    matches!(expr.kind, hir::ExprKind::If(..))
-                };
-                if let Some(ret_sp) = opt_suggest_box_span
-                    && !is_empty_arm(then_id)
-                    && !is_empty_arm(else_id)
-                {
-                    self.suggest_boxing_for_return_impl_trait(
-                        err,
-                        ret_sp,
-                        [then_span, else_span].into_iter(),
-                    );
-                }
             }
             ObligationCauseCode::LetElse => {
                 err.help("try adding a diverging expression, such as `return` or `panic!(..)`");
diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
index e220c4d02a2..9a05fb1c30f 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
@@ -19,9 +19,8 @@ use rustc_span::{sym, BytePos, Span};
 
 use crate::errors::{
     ConsiderAddingAwait, FnConsiderCasting, FnItemsAreDistinct, FnUniqTypes,
-    FunctionPointerSuggestion, SuggestAccessingField, SuggestBoxingForReturnImplTrait,
-    SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany, SuggestTuplePatternOne,
-    TypeErrorAdditionalDiags,
+    FunctionPointerSuggestion, SuggestAccessingField, SuggestRemoveSemiOrReturnBinding,
+    SuggestTuplePatternMany, SuggestTuplePatternOne, TypeErrorAdditionalDiags,
 };
 
 use super::TypeErrCtxt;
@@ -80,28 +79,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         }
     }
 
-    pub(super) fn suggest_boxing_for_return_impl_trait(
-        &self,
-        err: &mut Diag<'_>,
-        return_sp: Span,
-        arm_spans: impl Iterator<Item = Span>,
-    ) {
-        let sugg = SuggestBoxingForReturnImplTrait::ChangeReturnType {
-            start_sp: return_sp.with_hi(return_sp.lo() + BytePos(4)),
-            end_sp: return_sp.shrink_to_hi(),
-        };
-        err.subdiagnostic(self.dcx(), sugg);
-
-        let mut starts = Vec::new();
-        let mut ends = Vec::new();
-        for span in arm_spans {
-            starts.push(span.shrink_to_lo());
-            ends.push(span.shrink_to_hi());
-        }
-        let sugg = SuggestBoxingForReturnImplTrait::BoxReturnExpr { starts, ends };
-        err.subdiagnostic(self.dcx(), sugg);
-    }
-
     pub(super) fn suggest_tuple_pattern(
         &self,
         cause: &ObligationCause<'tcx>,
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index a3ff655b609..339c8ac10b3 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -229,6 +229,15 @@ impl<'tcx> InferCtxtInner<'tcx> {
             .expect("region constraints already solved")
             .with_log(&mut self.undo_log)
     }
+
+    // Iterates through the opaque type definitions without taking them; this holds the
+    // `InferCtxtInner` lock, so make sure to not do anything with `InferCtxt` side-effects
+    // while looping through this.
+    pub fn iter_opaque_types(
+        &self,
+    ) -> impl Iterator<Item = (ty::OpaqueTypeKey<'tcx>, ty::OpaqueHiddenType<'tcx>)> + '_ {
+        self.opaque_type_storage.opaque_types.iter().map(|(&k, v)| (k, v.hidden_type))
+    }
 }
 
 pub struct InferCtxt<'tcx> {
diff --git a/compiler/rustc_infer/src/infer/opaque_types/mod.rs b/compiler/rustc_infer/src/infer/opaque_types/mod.rs
index 02b8ded285f..01430e830e5 100644
--- a/compiler/rustc_infer/src/infer/opaque_types/mod.rs
+++ b/compiler/rustc_infer/src/infer/opaque_types/mod.rs
@@ -73,7 +73,7 @@ impl<'tcx> InferCtxt<'tcx> {
                     // for opaque types, and then use that kind to fix the spans for type errors
                     // that we see later on.
                     let ty_var = self.next_ty_var(TypeVariableOrigin {
-                        kind: TypeVariableOriginKind::OpaqueTypeInference(def_id),
+                        kind: TypeVariableOriginKind::MiscVariable,
                         span,
                     });
                     obligations.extend(
diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs
index 3630b0f439f..55c6c92a584 100644
--- a/compiler/rustc_infer/src/infer/type_variable.rs
+++ b/compiler/rustc_infer/src/infer/type_variable.rs
@@ -47,7 +47,6 @@ pub enum TypeVariableOriginKind {
     MiscVariable,
     NormalizeProjectionType,
     TypeInference,
-    OpaqueTypeInference(DefId),
     TypeParameterDefinition(Symbol, DefId),
 
     /// One of the upvars or closure kind parameters in a `ClosureArgs`
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index a04bd636622..efea2a66bb2 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -571,7 +571,8 @@ pub struct MatchExpressionArmCause<'tcx> {
     pub scrut_span: Span,
     pub source: hir::MatchSource,
     pub prior_non_diverging_arms: Vec<Span>,
-    pub opt_suggest_box_span: Option<Span>,
+    // Is the expectation of this match expression an RPIT?
+    pub tail_defines_return_position_impl_trait: Option<LocalDefId>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -582,7 +583,8 @@ pub struct IfExpressionCause<'tcx> {
     pub then_ty: Ty<'tcx>,
     pub else_ty: Ty<'tcx>,
     pub outer_span: Option<Span>,
-    pub opt_suggest_box_span: Option<Span>,
+    // Is the expectation of this match expression an RPIT?
+    pub tail_defines_return_position_impl_trait: Option<LocalDefId>,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 419b04231b5..7975677286d 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -1,19 +1,21 @@
+pub mod run;
+pub mod rustc;
+pub mod rustdoc;
+
 use std::env;
-use std::path::{Path, PathBuf};
-use std::process::{Command, Output};
+use std::path::PathBuf;
+use std::process::Output;
 
 pub use object;
 pub use wasmparser;
 
-pub fn out_dir() -> PathBuf {
-    env::var_os("TMPDIR").unwrap().into()
-}
+pub use run::{run, run_fail};
+pub use rustc::{aux_build, rustc, Rustc};
+pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
 
-fn setup_common_build_cmd(command: &str) -> Command {
-    let rustc = env::var(command).unwrap();
-    let mut cmd = Command::new(rustc);
-    cmd.arg("--out-dir").arg(out_dir()).arg("-L").arg(out_dir());
-    cmd
+/// Path of `TMPDIR` (a temporary build directory, not under `/tmp`).
+pub fn tmp_dir() -> PathBuf {
+    env::var_os("TMPDIR").unwrap().into()
 }
 
 fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> ! {
@@ -24,170 +26,3 @@ fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> !
     eprintln!("=== STDERR ===\n{}\n\n", String::from_utf8(output.stderr).unwrap());
     std::process::exit(1)
 }
-
-pub fn rustc() -> RustcInvocationBuilder {
-    RustcInvocationBuilder::new()
-}
-
-pub fn aux_build() -> AuxBuildInvocationBuilder {
-    AuxBuildInvocationBuilder::new()
-}
-
-pub fn rustdoc() -> Rustdoc {
-    Rustdoc::new()
-}
-
-#[derive(Debug)]
-pub struct RustcInvocationBuilder {
-    cmd: Command,
-}
-
-impl RustcInvocationBuilder {
-    fn new() -> Self {
-        let cmd = setup_common_build_cmd("RUSTC");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut RustcInvocationBuilder {
-        self.cmd.arg(arg);
-        self
-    }
-
-    pub fn args(&mut self, args: &[&str]) -> &mut RustcInvocationBuilder {
-        self.cmd.args(args);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-#[derive(Debug)]
-pub struct AuxBuildInvocationBuilder {
-    cmd: Command,
-}
-
-impl AuxBuildInvocationBuilder {
-    fn new() -> Self {
-        let mut cmd = setup_common_build_cmd("RUSTC");
-        cmd.arg("--crate-type=lib");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut AuxBuildInvocationBuilder {
-        self.cmd.arg(arg);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-#[derive(Debug)]
-pub struct Rustdoc {
-    cmd: Command,
-}
-
-impl Rustdoc {
-    fn new() -> Self {
-        let cmd = setup_common_build_cmd("RUSTDOC");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut Self {
-        self.cmd.arg(arg);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-fn run_common(bin_name: &str) -> (Command, Output) {
-    let target = env::var("TARGET").unwrap();
-
-    let bin_name =
-        if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
-
-    let mut bin_path = PathBuf::new();
-    bin_path.push(env::var("TMPDIR").unwrap());
-    bin_path.push(&bin_name);
-    let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
-    let mut cmd = Command::new(bin_path);
-    cmd.env(&ld_lib_path_envvar, {
-        let mut paths = vec![];
-        paths.push(PathBuf::from(env::var("TMPDIR").unwrap()));
-        for p in env::split_paths(&env::var("TARGET_RPATH_ENV").unwrap()) {
-            paths.push(p.to_path_buf());
-        }
-        for p in env::split_paths(&env::var(&ld_lib_path_envvar).unwrap()) {
-            paths.push(p.to_path_buf());
-        }
-        env::join_paths(paths.iter()).unwrap()
-    });
-
-    if target.contains("windows") {
-        let mut paths = vec![];
-        for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
-            paths.push(p.to_path_buf());
-        }
-        paths.push(Path::new(&std::env::var("TARGET_RPATH_DIR").unwrap()).to_path_buf());
-        cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
-    }
-
-    let output = cmd.output().unwrap();
-    (cmd, output)
-}
-
-/// Run a built binary and make sure it succeeds.
-#[track_caller]
-pub fn run(bin_name: &str) -> Output {
-    let caller_location = std::panic::Location::caller();
-    let caller_line_number = caller_location.line();
-
-    let (cmd, output) = run_common(bin_name);
-    if !output.status.success() {
-        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
-    }
-    output
-}
-
-/// Run a built binary and make sure it fails.
-#[track_caller]
-pub fn run_fail(bin_name: &str) -> Output {
-    let caller_location = std::panic::Location::caller();
-    let caller_line_number = caller_location.line();
-
-    let (cmd, output) = run_common(bin_name);
-    if output.status.success() {
-        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
-    }
-    output
-}
diff --git a/src/tools/run-make-support/src/run.rs b/src/tools/run-make-support/src/run.rs
new file mode 100644
index 00000000000..42dcf54da22
--- /dev/null
+++ b/src/tools/run-make-support/src/run.rs
@@ -0,0 +1,67 @@
+use std::env;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Output};
+
+use super::handle_failed_output;
+
+fn run_common(bin_name: &str) -> (Command, Output) {
+    let target = env::var("TARGET").unwrap();
+
+    let bin_name =
+        if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
+
+    let mut bin_path = PathBuf::new();
+    bin_path.push(env::var("TMPDIR").unwrap());
+    bin_path.push(&bin_name);
+    let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
+    let mut cmd = Command::new(bin_path);
+    cmd.env(&ld_lib_path_envvar, {
+        let mut paths = vec![];
+        paths.push(PathBuf::from(env::var("TMPDIR").unwrap()));
+        for p in env::split_paths(&env::var("TARGET_RPATH_ENV").unwrap()) {
+            paths.push(p.to_path_buf());
+        }
+        for p in env::split_paths(&env::var(&ld_lib_path_envvar).unwrap()) {
+            paths.push(p.to_path_buf());
+        }
+        env::join_paths(paths.iter()).unwrap()
+    });
+
+    if target.contains("windows") {
+        let mut paths = vec![];
+        for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
+            paths.push(p.to_path_buf());
+        }
+        paths.push(Path::new(&std::env::var("TARGET_RPATH_DIR").unwrap()).to_path_buf());
+        cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
+    }
+
+    let output = cmd.output().unwrap();
+    (cmd, output)
+}
+
+/// Run a built binary and make sure it succeeds.
+#[track_caller]
+pub fn run(bin_name: &str) -> Output {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let (cmd, output) = run_common(bin_name);
+    if !output.status.success() {
+        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
+    }
+    output
+}
+
+/// Run a built binary and make sure it fails.
+#[track_caller]
+pub fn run_fail(bin_name: &str) -> Output {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let (cmd, output) = run_common(bin_name);
+    if output.status.success() {
+        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
+    }
+    output
+}
diff --git a/src/tools/run-make-support/src/rustc.rs b/src/tools/run-make-support/src/rustc.rs
new file mode 100644
index 00000000000..d0ab8df42d2
--- /dev/null
+++ b/src/tools/run-make-support/src/rustc.rs
@@ -0,0 +1,147 @@
+use std::env;
+use std::path::Path;
+use std::process::{Command, Output};
+
+use crate::{handle_failed_output, tmp_dir};
+
+/// Construct a new `rustc` invocation.
+pub fn rustc() -> Rustc {
+    Rustc::new()
+}
+
+/// Construct a new `rustc` aux-build invocation.
+pub fn aux_build() -> Rustc {
+    Rustc::new_aux_build()
+}
+
+/// A `rustc` invocation builder.
+#[derive(Debug)]
+pub struct Rustc {
+    cmd: Command,
+}
+
+fn setup_common() -> Command {
+    let rustc = env::var("RUSTC").unwrap();
+    let mut cmd = Command::new(rustc);
+    cmd.arg("--out-dir").arg(tmp_dir()).arg("-L").arg(tmp_dir());
+    cmd
+}
+
+impl Rustc {
+    // `rustc` invocation constructor methods
+
+    /// Construct a new `rustc` invocation.
+    pub fn new() -> Self {
+        let cmd = setup_common();
+        Self { cmd }
+    }
+
+    /// Construct a new `rustc` invocation with `aux_build` preset (setting `--crate-type=lib`).
+    pub fn new_aux_build() -> Self {
+        let mut cmd = setup_common();
+        cmd.arg("--crate-type=lib");
+        Self { cmd }
+    }
+
+    // Argument provider methods
+
+    /// Configure the compilation environment.
+    pub fn cfg(&mut self, s: &str) -> &mut Self {
+        self.cmd.arg("--cfg");
+        self.cmd.arg(s);
+        self
+    }
+
+    /// Specify default optimization level `-O` (alias for `-C opt-level=2`).
+    pub fn opt(&mut self) -> &mut Self {
+        self.cmd.arg("-O");
+        self
+    }
+
+    /// Specify type(s) of output files to generate.
+    pub fn emit(&mut self, kinds: &str) -> &mut Self {
+        self.cmd.arg(format!("--emit={kinds}"));
+        self
+    }
+
+    /// Specify where an external library is located.
+    pub fn extern_<P: AsRef<Path>>(&mut self, crate_name: &str, path: P) -> &mut Self {
+        assert!(
+            !crate_name.contains(|c: char| c.is_whitespace() || c == '\\' || c == '/'),
+            "crate name cannot contain whitespace or path separators"
+        );
+
+        let path = path.as_ref().to_string_lossy();
+
+        self.cmd.arg("--extern");
+        self.cmd.arg(format!("{crate_name}={path}"));
+
+        self
+    }
+
+    /// Specify path to the input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+
+    /// Specify target triple.
+    pub fn target(&mut self, target: &str) -> &mut Self {
+        assert!(!target.contains(char::is_whitespace), "target triple cannot contain spaces");
+        self.cmd.arg(format!("--target={target}"));
+        self
+    }
+
+    /// Generic command argument provider. Use `.arg("-Zname")` over `.arg("-Z").arg("arg")`.
+    /// This method will panic if a plain `-Z` or `-C` is passed, or if `-Z <name>` or `-C <name>`
+    /// is passed (note the space).
+    pub fn arg(&mut self, arg: &str) -> &mut Self {
+        assert!(
+            !(["-Z", "-C"].contains(&arg) || arg.starts_with("-Z ") || arg.starts_with("-C ")),
+            "use `-Zarg` or `-Carg` over split `-Z` `arg` or `-C` `arg`"
+        );
+        self.cmd.arg(arg);
+        self
+    }
+
+    /// Generic command arguments provider. Use `.arg("-Zname")` over `.arg("-Z").arg("arg")`.
+    /// This method will panic if a plain `-Z` or `-C` is passed, or if `-Z <name>` or `-C <name>`
+    /// is passed (note the space).
+    pub fn args(&mut self, args: &[&str]) -> &mut Self {
+        for arg in args {
+            assert!(
+                !(["-Z", "-C"].contains(&arg) || arg.starts_with("-Z ") || arg.starts_with("-C ")),
+                "use `-Zarg` or `-Carg` over split `-Z` `arg` or `-C` `arg`"
+            );
+        }
+
+        self.cmd.args(args);
+        self
+    }
+
+    // Command inspection, output and running helper methods
+
+    /// Get the [`Output`][std::process::Output] of the finished `rustc` process.
+    pub fn output(&mut self) -> Output {
+        self.cmd.output().unwrap()
+    }
+
+    /// Run the constructed `rustc` command and assert that it is successfully run.
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+
+    /// Inspect what the underlying [`Command`] is up to the current construction.
+    pub fn inspect(&mut self, f: impl FnOnce(&Command)) -> &mut Self {
+        f(&self.cmd);
+        self
+    }
+}
diff --git a/src/tools/run-make-support/src/rustdoc.rs b/src/tools/run-make-support/src/rustdoc.rs
new file mode 100644
index 00000000000..9607ff02f96
--- /dev/null
+++ b/src/tools/run-make-support/src/rustdoc.rs
@@ -0,0 +1,80 @@
+use std::env;
+use std::path::Path;
+use std::process::{Command, Output};
+
+use crate::handle_failed_output;
+
+/// Construct a plain `rustdoc` invocation with no flags set.
+pub fn bare_rustdoc() -> Rustdoc {
+    Rustdoc::bare()
+}
+
+/// Construct a new `rustdoc` invocation with `-L $(TARGET_RPATH_DIR)` set.
+pub fn rustdoc() -> Rustdoc {
+    Rustdoc::new()
+}
+
+#[derive(Debug)]
+pub struct Rustdoc {
+    cmd: Command,
+}
+
+fn setup_common() -> Command {
+    let rustdoc = env::var("RUSTDOC").unwrap();
+    Command::new(rustdoc)
+}
+
+impl Rustdoc {
+    /// Construct a bare `rustdoc` invocation.
+    pub fn bare() -> Self {
+        let cmd = setup_common();
+        Self { cmd }
+    }
+
+    /// Construct a `rustdoc` invocation with `-L $(TARGET_RPATH_DIR)` set.
+    pub fn new() -> Self {
+        let mut cmd = setup_common();
+        let target_rpath_dir = env::var_os("TARGET_RPATH_DIR").unwrap();
+        cmd.arg(format!("-L{}", target_rpath_dir.to_string_lossy()));
+        Self { cmd }
+    }
+
+    /// Specify path to the input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+
+    /// Specify output directory.
+    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg("--out-dir").arg(path.as_ref());
+        self
+    }
+
+    /// Given a `path`, pass `@{path}` to `rustdoc` as an
+    /// [arg file](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path).
+    pub fn arg_file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(format!("@{}", path.as_ref().display()));
+        self
+    }
+
+    /// Fallback argument provider. Consider adding meaningfully named methods instead of using
+    /// this method.
+    pub fn arg(&mut self, arg: &str) -> &mut Self {
+        self.cmd.arg(arg);
+        self
+    }
+
+    /// Run the build `rustdoc` command and assert that the run is successful.
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+}
diff --git a/tests/run-make/CURRENT_RUSTC_VERSION/rmake.rs b/tests/run-make/CURRENT_RUSTC_VERSION/rmake.rs
index 586f4e4095f..1204260a2f4 100644
--- a/tests/run-make/CURRENT_RUSTC_VERSION/rmake.rs
+++ b/tests/run-make/CURRENT_RUSTC_VERSION/rmake.rs
@@ -1,24 +1,25 @@
 // ignore-tidy-linelength
 
+// Check that the `CURRENT_RUSTC_VERSION` placeholder is correctly replaced by the current
+// `rustc` version and the `since` property in feature stability gating is properly respected.
+
 extern crate run_make_support;
 
 use std::path::PathBuf;
 
-use run_make_support::{aux_build, rustc};
+use run_make_support::{rustc, aux_build};
 
 fn main() {
-    aux_build()
-        .arg("--emit=metadata")
-        .arg("stable.rs")
-        .run();
+    aux_build().input("stable.rs").emit("metadata").run();
+
     let mut stable_path = PathBuf::from(env!("TMPDIR"));
     stable_path.push("libstable.rmeta");
+
     let output = rustc()
-        .arg("--emit=metadata")
-        .arg("--extern")
-        .arg(&format!("stable={}", &stable_path.to_string_lossy()))
-        .arg("main.rs")
-        .run();
+        .input("main.rs")
+        .emit("metadata")
+        .extern_("stable", &stable_path)
+        .output();
 
     let stderr = String::from_utf8_lossy(&output.stderr);
     let version = include_str!(concat!(env!("S"), "/src/version"));
diff --git a/tests/run-make/a-b-a-linker-guard/rmake.rs b/tests/run-make/a-b-a-linker-guard/rmake.rs
index ef4813e1214..ffc1b2000b9 100644
--- a/tests/run-make/a-b-a-linker-guard/rmake.rs
+++ b/tests/run-make/a-b-a-linker-guard/rmake.rs
@@ -1,44 +1,36 @@
 // ignore-tidy-linelength
 
+// Test that if we build `b` against a version of `a` that has one set of types, it will not run
+// with a dylib that has a different set of types.
+
 extern crate run_make_support;
 
 use run_make_support::{run, run_fail, rustc};
 
 fn main() {
     rustc()
-        .arg("a.rs")
-        .arg("--cfg")
-        .arg("x")
-        .arg("-C")
-        .arg("prefer-dynamic")
-        .arg("-Z")
-        .arg("unstable-options")
-        .arg("-C")
-        .arg("symbol-mangling-version=legacy")
+        .input("a.rs")
+        .cfg("x")
+        .arg("-Zunstable-options")
+        .arg("-Cprefer-dynamic")
+        .arg("-Csymbol-mangling-version=legacy")
         .run();
 
     rustc()
-       .arg("b.rs")
-       .arg("-C")
-       .arg("prefer-dynamic")
-       .arg("-Z")
-       .arg("unstable-options")
-       .arg("-C")
-       .arg("symbol-mangling-version=legacy")
-       .run();
+        .input("b.rs")
+        .arg("-Zunstable-options")
+        .arg("-Cprefer-dynamic")
+        .arg("-Csymbol-mangling-version=legacy")
+        .run();
 
     run("b");
 
     rustc()
-        .arg("a.rs")
-        .arg("--cfg")
-        .arg("y")
-        .arg("-C")
-        .arg("prefer-dynamic")
-        .arg("-Z")
-        .arg("unstable-options")
-        .arg("-C")
-        .arg("symbol-mangling-version=legacy")
+        .input("a.rs")
+        .cfg("y")
+        .arg("-Zunstable-options")
+        .arg("-Cprefer-dynamic")
+        .arg("-Csymbol-mangling-version=legacy")
         .run();
 
     run_fail("b");
diff --git a/tests/run-make/compiler-builtins/rmake.rs b/tests/run-make/compiler-builtins/rmake.rs
index e7a5e8addbe..b546a095c51 100644
--- a/tests/run-make/compiler-builtins/rmake.rs
+++ b/tests/run-make/compiler-builtins/rmake.rs
@@ -18,7 +18,7 @@ use run_make_support::object::read::Object;
 use run_make_support::object::ObjectSection;
 use run_make_support::object::ObjectSymbol;
 use run_make_support::object::RelocationTarget;
-use run_make_support::out_dir;
+use run_make_support::tmp_dir;
 use std::collections::HashSet;
 
 const MANIFEST: &str = r#"
@@ -31,7 +31,7 @@ edition = "2021"
 path = "lib.rs""#;
 
 fn main() {
-    let target_dir = out_dir().join("target");
+    let target_dir = tmp_dir().join("target");
     let target = std::env::var("TARGET").unwrap();
     if target.starts_with("wasm") || target.starts_with("nvptx") {
         // wasm and nvptx targets don't produce rlib files that object can parse.
@@ -41,9 +41,9 @@ fn main() {
     println!("Testing compiler_builtins for {}", target);
 
     // Set up the tiniest Cargo project: An empty no_std library. Just enough to run -Zbuild-std.
-    let manifest_path = out_dir().join("Cargo.toml");
+    let manifest_path = tmp_dir().join("Cargo.toml");
     std::fs::write(&manifest_path, MANIFEST.as_bytes()).unwrap();
-    std::fs::write(out_dir().join("lib.rs"), b"#![no_std]").unwrap();
+    std::fs::write(tmp_dir().join("lib.rs"), b"#![no_std]").unwrap();
 
     let path = std::env::var("PATH").unwrap();
     let rustc = std::env::var("RUSTC").unwrap();
diff --git a/tests/run-make/rustdoc-test-args/rmake.rs b/tests/run-make/rustdoc-test-args/rmake.rs
index 808d13928eb..c8edfb6370e 100644
--- a/tests/run-make/rustdoc-test-args/rmake.rs
+++ b/tests/run-make/rustdoc-test-args/rmake.rs
@@ -1,8 +1,8 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustdoc};
-use std::{fs, iter};
+use run_make_support::{rustdoc, tmp_dir};
 use std::path::Path;
+use std::{fs, iter};
 
 fn generate_a_lot_of_cfgs(path: &Path) {
     let content = iter::repeat("--cfg=a\n").take(100_000).collect::<String>();
@@ -10,9 +10,8 @@ fn generate_a_lot_of_cfgs(path: &Path) {
 }
 
 fn main() {
-    let arg_file = out_dir().join("args");
+    let arg_file = tmp_dir().join("args");
     generate_a_lot_of_cfgs(&arg_file);
 
-    let arg_file = format!("@{}", arg_file.display());
-    rustdoc().arg("--test").arg(&arg_file).arg("foo.rs").run();
+    rustdoc().out_dir(tmp_dir()).input("foo.rs").arg_file(&arg_file).arg("--test").run();
 }
diff --git a/tests/run-make/wasm-abi/rmake.rs b/tests/run-make/wasm-abi/rmake.rs
index 07b826ae6fe..f4d0027798a 100644
--- a/tests/run-make/wasm-abi/rmake.rs
+++ b/tests/run-make/wasm-abi/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc};
+use run_make_support::{rustc, tmp_dir};
 use std::path::Path;
 use std::process::Command;
 
@@ -9,8 +9,9 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").run();
-    let file = out_dir().join("foo.wasm");
+    rustc().input("foo.rs").target("wasm32-wasip1").run();
+
+    let file = tmp_dir().join("foo.wasm");
 
     let has_wasmtime = match Command::new("wasmtime").arg("--version").output() {
         Ok(s) => s.status.success(),
diff --git a/tests/run-make/wasm-custom-section/rmake.rs b/tests/run-make/wasm-custom-section/rmake.rs
index 9ad152695ec..31fb5fb3230 100644
--- a/tests/run-make/wasm-custom-section/rmake.rs
+++ b/tests/run-make/wasm-custom-section/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{rustc, tmp_dir, wasmparser};
 use std::collections::HashMap;
 
 fn main() {
@@ -8,10 +8,10 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").run();
-    rustc().arg("bar.rs").arg("--target=wasm32-wasip1").arg("-Clto").arg("-O").run();
+    rustc().input("foo.rs").target("wasm32-wasip1").run();
+    rustc().input("bar.rs").target("wasm32-wasip1").arg("-Clto").opt().run();
 
-    let file = std::fs::read(&out_dir().join("bar.wasm")).unwrap();
+    let file = std::fs::read(&tmp_dir().join("bar.wasm")).unwrap();
 
     let mut custom = HashMap::new();
     for payload in wasmparser::Parser::new(0).parse_all(&file) {
diff --git a/tests/run-make/wasm-custom-sections-opt/rmake.rs b/tests/run-make/wasm-custom-sections-opt/rmake.rs
index db31d6b7163..3164de1b4c9 100644
--- a/tests/run-make/wasm-custom-sections-opt/rmake.rs
+++ b/tests/run-make/wasm-custom-sections-opt/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{tmp_dir, wasmparser, rustc};
 use std::collections::HashMap;
 use std::path::Path;
 
@@ -9,8 +9,9 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-O").run();
-    verify(&out_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").opt().run();
+
+    verify(&tmp_dir().join("foo.wasm"));
 }
 
 fn verify(path: &Path) {
diff --git a/tests/run-make/wasm-export-all-symbols/rmake.rs b/tests/run-make/wasm-export-all-symbols/rmake.rs
index e3b118279b7..13101a97444 100644
--- a/tests/run-make/wasm-export-all-symbols/rmake.rs
+++ b/tests/run-make/wasm-export-all-symbols/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{tmp_dir, wasmparser, rustc};
 use std::collections::HashMap;
 use std::path::Path;
 use wasmparser::ExternalKind::*;
@@ -17,16 +17,17 @@ fn main() {
 
 fn test(args: &[&str]) {
     eprintln!("running with {args:?}");
-    rustc().arg("bar.rs").arg("--target=wasm32-wasip1").args(args).run();
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").args(args).run();
-    rustc().arg("main.rs").arg("--target=wasm32-wasip1").args(args).run();
+
+    rustc().input("bar.rs").target("wasm32-wasip1").args(args).run();
+    rustc().input("foo.rs").target("wasm32-wasip1").args(args).run();
+    rustc().input("main.rs").target("wasm32-wasip1").args(args).run();
 
     verify_exports(
-        &out_dir().join("foo.wasm"),
+        &tmp_dir().join("foo.wasm"),
         &[("foo", Func), ("FOO", Global), ("memory", Memory)],
     );
     verify_exports(
-        &out_dir().join("main.wasm"),
+        &tmp_dir().join("main.wasm"),
         &[
             ("foo", Func),
             ("FOO", Global),
diff --git a/tests/run-make/wasm-import-module/rmake.rs b/tests/run-make/wasm-import-module/rmake.rs
index e521b5b0983..3962bd80a89 100644
--- a/tests/run-make/wasm-import-module/rmake.rs
+++ b/tests/run-make/wasm-import-module/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{tmp_dir, wasmparser, rustc};
 use std::collections::HashMap;
 use wasmparser::TypeRef::Func;
 
@@ -9,10 +9,15 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").run();
-    rustc().arg("bar.rs").arg("--target=wasm32-wasip1").arg("-Clto").arg("-O").run();
+    rustc().input("foo.rs").target("wasm32-wasip1").run();
+    rustc()
+        .input("bar.rs")
+        .target("wasm32-wasip1")
+        .arg("-Clto")
+        .opt()
+        .run();
 
-    let file = std::fs::read(&out_dir().join("bar.wasm")).unwrap();
+    let file = std::fs::read(&tmp_dir().join("bar.wasm")).unwrap();
 
     let mut imports = HashMap::new();
     for payload in wasmparser::Parser::new(0).parse_all(&file) {
diff --git a/tests/run-make/wasm-panic-small/rmake.rs b/tests/run-make/wasm-panic-small/rmake.rs
index 0260485f744..7941b503994 100644
--- a/tests/run-make/wasm-panic-small/rmake.rs
+++ b/tests/run-make/wasm-panic-small/rmake.rs
@@ -2,7 +2,7 @@
 
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc};
+use run_make_support::{rustc, tmp_dir};
 
 fn main() {
     if std::env::var("TARGET").unwrap() != "wasm32-wasip1" {
@@ -17,16 +17,10 @@ fn main() {
 
 fn test(cfg: &str) {
     eprintln!("running cfg {cfg:?}");
-    rustc()
-        .arg("foo.rs")
-        .arg("--target=wasm32-wasip1")
-        .arg("-Clto")
-        .arg("-O")
-        .arg("--cfg")
-        .arg(cfg)
-        .run();
 
-    let bytes = std::fs::read(&out_dir().join("foo.wasm")).unwrap();
+    rustc().input("foo.rs").target("wasm32-wasip1").arg("-Clto").opt().cfg(cfg).run();
+
+    let bytes = std::fs::read(&tmp_dir().join("foo.wasm")).unwrap();
     println!("{}", bytes.len());
     assert!(bytes.len() < 40_000);
 }
diff --git a/tests/run-make/wasm-spurious-import/rmake.rs b/tests/run-make/wasm-spurious-import/rmake.rs
index 0ac9104bfb4..9dafa6f594a 100644
--- a/tests/run-make/wasm-spurious-import/rmake.rs
+++ b/tests/run-make/wasm-spurious-import/rmake.rs
@@ -1,8 +1,7 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{rustc, tmp_dir, wasmparser};
 use std::collections::HashMap;
-use wasmparser::TypeRef::Func;
 
 fn main() {
     if std::env::var("TARGET").unwrap() != "wasm32-wasip1" {
@@ -10,15 +9,15 @@ fn main() {
     }
 
     rustc()
-        .arg("main.rs")
-        .arg("--target=wasm32-wasip1")
-        .arg("-Coverflow-checks=yes")
+        .input("main.rs")
+        .target("wasm32-wasip1")
+        .arg("-Coverflow-checks")
         .arg("-Cpanic=abort")
         .arg("-Clto")
         .arg("-Copt-level=z")
         .run();
 
-    let file = std::fs::read(&out_dir().join("main.wasm")).unwrap();
+    let file = std::fs::read(&tmp_dir().join("main.wasm")).unwrap();
 
     let mut imports = HashMap::new();
     for payload in wasmparser::Parser::new(0).parse_all(&file) {
diff --git a/tests/run-make/wasm-stringify-ints-small/rmake.rs b/tests/run-make/wasm-stringify-ints-small/rmake.rs
index 80cff7acdf4..6b3ad871a70 100644
--- a/tests/run-make/wasm-stringify-ints-small/rmake.rs
+++ b/tests/run-make/wasm-stringify-ints-small/rmake.rs
@@ -2,16 +2,16 @@
 
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc};
+use run_make_support::{rustc, tmp_dir};
 
 fn main() {
     if std::env::var("TARGET").unwrap() != "wasm32-wasip1" {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-Clto").arg("-O").run();
+    rustc().input("foo.rs").target("wasm32-wasip1").arg("-Clto").opt().run();
 
-    let bytes = std::fs::read(&out_dir().join("foo.wasm")).unwrap();
+    let bytes = std::fs::read(&tmp_dir().join("foo.wasm")).unwrap();
     println!("{}", bytes.len());
     assert!(bytes.len() < 50_000);
 }
diff --git a/tests/run-make/wasm-symbols-different-module/rmake.rs b/tests/run-make/wasm-symbols-different-module/rmake.rs
index c3cc1e0c32b..a27da446baa 100644
--- a/tests/run-make/wasm-symbols-different-module/rmake.rs
+++ b/tests/run-make/wasm-symbols-different-module/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{rustc, tmp_dir, wasmparser};
 use std::collections::{HashMap, HashSet};
 
 fn main() {
@@ -24,9 +24,9 @@ fn test_file(file: &str, expected_imports: &[(&str, &[&str])]) {
 fn test(file: &str, args: &[&str], expected_imports: &[(&str, &[&str])]) {
     println!("test {file:?} {args:?} for {expected_imports:?}");
 
-    rustc().arg(file).arg("--target=wasm32-wasip1").args(args).run();
+    rustc().input(file).target("wasm32-wasip1").args(args).run();
 
-    let file = std::fs::read(&out_dir().join(file).with_extension("wasm")).unwrap();
+    let file = std::fs::read(&tmp_dir().join(file).with_extension("wasm")).unwrap();
 
     let mut imports = HashMap::new();
     for payload in wasmparser::Parser::new(0).parse_all(&file) {
diff --git a/tests/run-make/wasm-symbols-not-exported/rmake.rs b/tests/run-make/wasm-symbols-not-exported/rmake.rs
index 5ff0dc578b3..da536f2af71 100644
--- a/tests/run-make/wasm-symbols-not-exported/rmake.rs
+++ b/tests/run-make/wasm-symbols-not-exported/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{rustc, tmp_dir, wasmparser};
 use std::path::Path;
 
 fn main() {
@@ -8,15 +8,15 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-O").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").opt().run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
 
-    rustc().arg("bar.rs").arg("--target=wasm32-wasip1").run();
-    verify_symbols(&out_dir().join("bar.wasm"));
-    rustc().arg("bar.rs").arg("--target=wasm32-wasip1").arg("-O").run();
-    verify_symbols(&out_dir().join("bar.wasm"));
+    rustc().input("bar.rs").target("wasm32-wasip1").run();
+    verify_symbols(&tmp_dir().join("bar.wasm"));
+    rustc().input("bar.rs").target("wasm32-wasip1").opt().run();
+    verify_symbols(&tmp_dir().join("bar.wasm"));
 }
 
 fn verify_symbols(path: &Path) {
diff --git a/tests/run-make/wasm-symbols-not-imported/rmake.rs b/tests/run-make/wasm-symbols-not-imported/rmake.rs
index 974f415166b..b784b6aff6a 100644
--- a/tests/run-make/wasm-symbols-not-imported/rmake.rs
+++ b/tests/run-make/wasm-symbols-not-imported/rmake.rs
@@ -1,6 +1,6 @@
 extern crate run_make_support;
 
-use run_make_support::{out_dir, rustc, wasmparser};
+use run_make_support::{rustc, tmp_dir, wasmparser};
 use std::path::Path;
 
 fn main() {
@@ -8,14 +8,14 @@ fn main() {
         return;
     }
 
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-Clto").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-O").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
-    rustc().arg("foo.rs").arg("--target=wasm32-wasip1").arg("-Clto").arg("-O").run();
-    verify_symbols(&out_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").arg("-Clto").run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").opt().run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
+    rustc().input("foo.rs").target("wasm32-wasip1").arg("-Clto").opt().run();
+    verify_symbols(&tmp_dir().join("foo.wasm"));
 }
 
 fn verify_symbols(path: &Path) {