Add rustc_never_type_mode crate-level attribute to allow experimenting

This commit is contained in:
Maybe Waffle 2024-03-15 12:27:54 +00:00
parent ee03c286cf
commit 19bc337063
3 changed files with 96 additions and 34 deletions

View File

@ -580,6 +580,13 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
"`may_dangle` has unstable semantics and may be removed in the future", "`may_dangle` has unstable semantics and may be removed in the future",
), ),
rustc_attr!(
rustc_never_type_mode, Normal, template!(NameValueStr: "fallback_to_unit|fallback_to_niko|no_fallback"), ErrorFollowing,
@only_local: true,
"`rustc_never_type_fallback` is used to experiment with never type fallback and work on \
never type stabilization, and will never be stable"
),
// ========================================================================== // ==========================================================================
// Internal attributes: Runtime related: // Internal attributes: Runtime related:
// ========================================================================== // ==========================================================================

View File

@ -4,8 +4,19 @@ use rustc_data_structures::{
graph::{iterate::DepthFirstSearch, vec_graph::VecGraph}, graph::{iterate::DepthFirstSearch, vec_graph::VecGraph},
unord::{UnordBag, UnordMap, UnordSet}, unord::{UnordBag, UnordMap, UnordSet},
}; };
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_infer::infer::{DefineOpaqueTypes, InferOk}; use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_span::sym;
enum DivergingFallbackBehavior {
/// Always fallback to `()` (aka "always spontaneous decay")
FallbackToUnit,
/// Sometimes fallback to `!`, but mainly fallback to `()` so that most of the crates are not broken.
FallbackToNiko,
/// Don't fallback at all
NoFallback,
}
impl<'tcx> FnCtxt<'_, 'tcx> { impl<'tcx> FnCtxt<'_, 'tcx> {
/// Performs type inference fallback, setting `FnCtxt::fallback_has_occurred` /// Performs type inference fallback, setting `FnCtxt::fallback_has_occurred`
@ -64,7 +75,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
return false; return false;
} }
let diverging_fallback = self.calculate_diverging_fallback(&unresolved_variables); let diverging_behavior = self.diverging_fallback_behavior();
let diverging_fallback =
self.calculate_diverging_fallback(&unresolved_variables, diverging_behavior);
// We do fallback in two passes, to try to generate // We do fallback in two passes, to try to generate
// better error messages. // better error messages.
@ -78,6 +91,31 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
fallback_occurred fallback_occurred
} }
fn diverging_fallback_behavior(&self) -> DivergingFallbackBehavior {
let Some((mode, span)) = self
.tcx
.get_attr(CRATE_DEF_ID, sym::rustc_never_type_mode)
.map(|attr| (attr.value_str().unwrap(), attr.span))
else {
if self.tcx.features().never_type_fallback {
return DivergingFallbackBehavior::FallbackToNiko;
}
return DivergingFallbackBehavior::FallbackToUnit;
};
match mode {
sym::fallback_to_unit => DivergingFallbackBehavior::FallbackToUnit,
sym::fallback_to_niko => DivergingFallbackBehavior::FallbackToNiko,
sym::no_fallback => DivergingFallbackBehavior::NoFallback,
_ => {
self.tcx.dcx().span_err(span, format!("unknown never type mode: `{mode}` (supported: `fallback_to_unit`, `fallback_to_niko`, and `no_fallback`)"));
DivergingFallbackBehavior::FallbackToUnit
}
}
}
fn fallback_effects(&self) -> bool { fn fallback_effects(&self) -> bool {
let unsolved_effects = self.unsolved_effects(); let unsolved_effects = self.unsolved_effects();
@ -232,6 +270,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
fn calculate_diverging_fallback( fn calculate_diverging_fallback(
&self, &self,
unresolved_variables: &[Ty<'tcx>], unresolved_variables: &[Ty<'tcx>],
behavior: DivergingFallbackBehavior,
) -> UnordMap<Ty<'tcx>, Ty<'tcx>> { ) -> UnordMap<Ty<'tcx>, Ty<'tcx>> {
debug!("calculate_diverging_fallback({:?})", unresolved_variables); debug!("calculate_diverging_fallback({:?})", unresolved_variables);
@ -345,39 +384,51 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
output: infer_var_infos.items().any(|info| info.output), output: infer_var_infos.items().any(|info| info.output),
}; };
if found_infer_var_info.self_in_trait && found_infer_var_info.output { use DivergingFallbackBehavior::*;
// This case falls back to () to ensure that the code pattern in match behavior {
// tests/ui/never_type/fallback-closure-ret.rs continues to FallbackToUnit => {
// compile when never_type_fallback is enabled. debug!("fallback to () - legacy: {:?}", diverging_vid);
// diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
// This rule is not readily explainable from first principles, }
// but is rather intended as a patchwork fix to ensure code FallbackToNiko => {
// which compiles before the stabilization of never type if found_infer_var_info.self_in_trait && found_infer_var_info.output {
// fallback continues to work. // This case falls back to () to ensure that the code pattern in
// // tests/ui/never_type/fallback-closure-ret.rs continues to
// Typically this pattern is encountered in a function taking a // compile when never_type_fallback is enabled.
// closure as a parameter, where the return type of that closure //
// (checked by `relationship.output`) is expected to implement // This rule is not readily explainable from first principles,
// some trait (checked by `relationship.self_in_trait`). This // but is rather intended as a patchwork fix to ensure code
// can come up in non-closure cases too, so we do not limit this // which compiles before the stabilization of never type
// rule to specifically `FnOnce`. // fallback continues to work.
// //
// When the closure's body is something like `panic!()`, the // Typically this pattern is encountered in a function taking a
// return type would normally be inferred to `!`. However, it // closure as a parameter, where the return type of that closure
// needs to fall back to `()` in order to still compile, as the // (checked by `relationship.output`) is expected to implement
// trait is specifically implemented for `()` but not `!`. // some trait (checked by `relationship.self_in_trait`). This
// // can come up in non-closure cases too, so we do not limit this
// For details on the requirements for these relationships to be // rule to specifically `FnOnce`.
// set, see the relationship finding module in //
// compiler/rustc_trait_selection/src/traits/relationships.rs. // When the closure's body is something like `panic!()`, the
debug!("fallback to () - found trait and projection: {:?}", diverging_vid); // return type would normally be inferred to `!`. However, it
diverging_fallback.insert(diverging_ty, self.tcx.types.unit); // needs to fall back to `()` in order to still compile, as the
} else if can_reach_non_diverging { // trait is specifically implemented for `()` but not `!`.
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid); //
diverging_fallback.insert(diverging_ty, self.tcx.types.unit); // For details on the requirements for these relationships to be
} else { // set, see the relationship finding module in
debug!("fallback to ! - all diverging: {:?}", diverging_vid); // compiler/rustc_trait_selection/src/traits/relationships.rs.
diverging_fallback.insert(diverging_ty, Ty::new_diverging_default(self.tcx)); debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
} else if can_reach_non_diverging {
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
} else {
debug!("fallback to ! - all diverging: {:?}", diverging_vid);
diverging_fallback.insert(diverging_ty, self.tcx.types.never);
}
}
NoFallback => {
debug!("no fallback - `rustc_never_type_mode = "no_fallback"`: {:?}", diverging_vid);
}
} }
} }

View File

@ -815,6 +815,8 @@ symbols! {
fadd_algebraic, fadd_algebraic,
fadd_fast, fadd_fast,
fake_variadic, fake_variadic,
fallback_to_niko,
fallback_to_unit,
fdiv_algebraic, fdiv_algebraic,
fdiv_fast, fdiv_fast,
feature, feature,
@ -1233,6 +1235,7 @@ symbols! {
no_crate_inject, no_crate_inject,
no_debug, no_debug,
no_default_passes, no_default_passes,
no_fallback,
no_implicit_prelude, no_implicit_prelude,
no_inline, no_inline,
no_link, no_link,
@ -1551,6 +1554,7 @@ symbols! {
rustc_mir, rustc_mir,
rustc_must_implement_one_of, rustc_must_implement_one_of,
rustc_never_returns_null_ptr, rustc_never_returns_null_ptr,
rustc_never_type_mode,
rustc_no_mir_inline, rustc_no_mir_inline,
rustc_nonnull_optimization_guaranteed, rustc_nonnull_optimization_guaranteed,
rustc_nounwind, rustc_nounwind,