Port rustc_never_type_options to the new attribute parser

This commit is contained in:
Jana Dönszelmann 2026-02-07 17:57:43 +01:00
parent a17eb934db
commit 954f483557
No known key found for this signature in database
10 changed files with 148 additions and 99 deletions

View file

@ -420,6 +420,88 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcCaptureAnalysisParser {
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcCaptureAnalysis;
}
pub(crate) struct RustcNeverTypeOptionsParser;
impl<S: Stage> SingleAttributeParser<S> for RustcNeverTypeOptionsParser {
const PATH: &[Symbol] = &[sym::rustc_never_type_options];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]);
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
const TEMPLATE: AttributeTemplate = template!(List: &[
r#"fallback = "unit", "never", "no""#,
r#"diverging_block_default = "unit", "never""#,
]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span, args);
return None;
};
let mut fallback = None::<Ident>;
let mut diverging_block_default = None::<Ident>;
for arg in list.mixed() {
let Some(meta) = arg.meta_item() else {
cx.expected_name_value(arg.span(), None);
continue;
};
let res = match meta.ident().map(|i| i.name) {
Some(sym::fallback) => &mut fallback,
Some(sym::diverging_block_default) => &mut diverging_block_default,
_ => {
cx.expected_specific_argument(
meta.path().span(),
&[sym::fallback, sym::diverging_block_default],
);
continue;
}
};
let Some(nv) = meta.args().name_value() else {
cx.expected_name_value(meta.span(), None);
continue;
};
let Some(field) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
continue;
};
if res.is_some() {
cx.duplicate_key(meta.span(), meta.ident().unwrap().name);
continue;
}
*res = Some(Ident { name: field, span: nv.value_span });
}
let fallback = match fallback {
None => None,
Some(Ident { name: sym::unit, .. }) => Some(DivergingFallbackBehavior::ToUnit),
Some(Ident { name: sym::never, .. }) => Some(DivergingFallbackBehavior::ToNever),
Some(Ident { name: sym::no, .. }) => Some(DivergingFallbackBehavior::NoFallback),
Some(Ident { span, .. }) => {
cx.expected_specific_argument_strings(span, &[sym::unit, sym::never, sym::no]);
return None;
}
};
let diverging_block_default = match diverging_block_default {
None => None,
Some(Ident { name: sym::unit, .. }) => Some(DivergingBlockBehavior::Unit),
Some(Ident { name: sym::never, .. }) => Some(DivergingBlockBehavior::Never),
Some(Ident { span, .. }) => {
cx.expected_specific_argument_strings(span, &[sym::unit, sym::no]);
return None;
}
};
Some(AttributeKind::RustcNeverTypeOptions { fallback, diverging_block_default })
}
}
pub(crate) struct RustcLintQueryInstabilityParser;
impl<S: Stage> NoArgsAttributeParser<S> for RustcLintQueryInstabilityParser {

View file

@ -147,9 +147,9 @@ attribute_parsers!(
DocParser,
MacroUseParser,
NakedParser,
RustcCguTestAttributeParser,
StabilityParser,
UsedParser,
RustcCguTestAttributeParser,
// tidy-alphabetical-end
// tidy-alphabetical-start
@ -210,6 +210,7 @@ attribute_parsers!(
Single<RustcLegacyConstGenericsParser>,
Single<RustcLintOptDenyFieldAccessParser>,
Single<RustcMustImplementOneOfParser>,
Single<RustcNeverTypeOptionsParser>,
Single<RustcObjectLifetimeDefaultParser>,
Single<RustcReservationImplParser>,
Single<RustcScalableVectorParser>,

View file

@ -63,6 +63,44 @@ pub enum CguFields {
PartitionCodegened { cfg: Symbol, module: Symbol },
ExpectedCguReuse { cfg: Symbol, module: Symbol, kind: CguKind },
}
#[derive(Copy, Clone, PartialEq, Debug, PrintAttribute)]
#[derive(HashStable_Generic, Encodable, Decodable)]
pub enum DivergingFallbackBehavior {
/// Always fallback to `()` (aka "always spontaneous decay")
ToUnit,
/// Always fallback to `!` (which should be equivalent to never falling back + not making
/// never-to-any coercions unless necessary)
ToNever,
/// Don't fallback at all
NoFallback,
}
#[derive(Copy, Clone, PartialEq, Debug, PrintAttribute, Default)]
#[derive(HashStable_Generic, Encodable, Decodable)]
pub enum DivergingBlockBehavior {
/// This is the current stable behavior:
///
/// ```rust
/// {
/// return;
/// } // block has type = !, even though we are supposedly dropping it with `;`
/// ```
#[default]
Never,
/// Alternative behavior:
///
/// ```ignore (very-unstable-new-attribute)
/// #![rustc_never_type_options(diverging_block_default = "unit")]
/// {
/// return;
/// } // block has type = (), since we are dropping `!` from `return` with `;`
/// ```
Unit,
}
#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic, PrintAttribute)]
pub enum InlineAttr {
None,
Hint,
@ -1069,6 +1107,9 @@ pub enum AttributeKind {
/// Represents `#[rustc_capture_analysis]`
RustcCaptureAnalysis,
/// Represents `#[rustc_expected_cgu_reuse]`, `#[rustc_partition_codegened]` and `#[rustc_partition_reused]`.
RustcCguTestAttr(ThinVec<(Span, CguFields)>),
/// Represents `#[rustc_clean]`
RustcClean(ThinVec<RustcCleanAttribute>),
@ -1139,9 +1180,6 @@ pub enum AttributeKind {
/// Represents `#[rustc_evaluate_where_clauses]`
RustcEvaluateWhereClauses,
/// Represents `#[rustc_expected_cgu_reuse]`, `#[rustc_partition_codegened]` and `#[rustc_partition_reused]`.
RustcCguTestAttr(ThinVec<(Span, CguFields)>),
/// Represents `#[rustc_has_incoherent_inherent_impls]`
RustcHasIncoherentInherentImpls,
@ -1199,6 +1237,12 @@ pub enum AttributeKind {
/// Represents `#[rustc_never_returns_null_ptr]`
RustcNeverReturnsNullPointer,
/// Represents `#[rustc_never_type_options]`.
RustcNeverTypeOptions {
fallback: Option<DivergingFallbackBehavior>,
diverging_block_default: Option<DivergingBlockBehavior>,
},
/// Represents `#[rustc_no_implicit_autorefs]`
RustcNoImplicitAutorefs,

View file

@ -99,6 +99,7 @@ impl AttributeKind {
RustcBodyStability { .. } => No,
RustcBuiltinMacro { .. } => Yes,
RustcCaptureAnalysis => No,
RustcCguTestAttr { .. } => No,
RustcClean { .. } => No,
RustcCoherenceIsCore(..) => No,
RustcCoinductive(..) => No,
@ -139,6 +140,7 @@ impl AttributeKind {
RustcMir(..) => Yes,
RustcMustImplementOneOf { .. } => No,
RustcNeverReturnsNullPointer => Yes,
RustcNeverTypeOptions { .. } => No,
RustcNoImplicitAutorefs => Yes,
RustcNoImplicitBounds => No,
RustcNonConstTraitMethod => No, // should be reported via other queries like `constness`
@ -149,7 +151,6 @@ impl AttributeKind {
RustcOffloadKernel => Yes,
RustcOutlives => No,
RustcParenSugar(..) => No,
RustcCguTestAttr { .. } => No,
RustcPassByValue(..) => Yes,
RustcPassIndirectlyInNonRusticAbis(..) => No,
RustcPreserveUbChecks => No,

View file

@ -7,6 +7,7 @@ use rustc_data_structures::graph::{self};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_hir as hir;
use rustc_hir::HirId;
use rustc_hir::attrs::DivergingFallbackBehavior;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{InferKind, Visitor};
@ -19,17 +20,6 @@ use tracing::debug;
use crate::{FnCtxt, errors};
#[derive(Copy, Clone)]
pub(crate) enum DivergingFallbackBehavior {
/// Always fallback to `()` (aka "always spontaneous decay")
ToUnit,
/// Always fallback to `!` (which should be equivalent to never falling back + not making
/// never-to-any coercions unless necessary)
ToNever,
/// Don't fallback at all
NoFallback,
}
impl<'tcx> FnCtxt<'_, 'tcx> {
/// Performs type inference fallback, setting [`FnCtxt::diverging_fallback_has_occurred`]
/// if the never type fallback has occurred.

View file

@ -5,6 +5,7 @@ use itertools::Itertools;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::codes::*;
use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan, a_or_an, listify, pluralize};
use rustc_hir::attrs::DivergingBlockBehavior;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
@ -47,29 +48,6 @@ rustc_index::newtype_index! {
pub(crate) struct GenericIdx {}
}
#[derive(Clone, Copy, Default)]
pub(crate) enum DivergingBlockBehavior {
/// This is the current stable behavior:
///
/// ```rust
/// {
/// return;
/// } // block has type = !, even though we are supposedly dropping it with `;`
/// ```
#[default]
Never,
/// Alternative behavior:
///
/// ```ignore (very-unstable-new-attribute)
/// #![rustc_never_type_options(diverging_block_default = "unit")]
/// {
/// return;
/// } // block has type = (), since we are dropping `!` from `return` with `;`
/// ```
Unit,
}
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(in super::super) fn check_casts(&mut self) {
let mut deferred_cast_checks = self.root_ctxt.deferred_cast_checks.borrow_mut();

View file

@ -10,8 +10,9 @@ use std::ops::Deref;
use hir::def_id::CRATE_DEF_ID;
use rustc_errors::DiagCtxtHandle;
use rustc_hir::attrs::{AttributeKind, DivergingBlockBehavior, DivergingFallbackBehavior};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, HirId, ItemLocalMap};
use rustc_hir::{self as hir, HirId, ItemLocalMap, find_attr};
use rustc_hir_analysis::hir_ty_lowering::{
HirTyLowerer, InherentAssocCandidate, RegionInferReason,
};
@ -19,15 +20,13 @@ use rustc_infer::infer::{self, RegionVariableOrigin};
use rustc_infer::traits::{DynCompatibilityViolation, Obligation};
use rustc_middle::ty::{self, Const, Ty, TyCtxt, TypeVisitableExt};
use rustc_session::Session;
use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span, sym};
use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span};
use rustc_trait_selection::error_reporting::TypeErrCtxt;
use rustc_trait_selection::traits::{
self, FulfillmentError, ObligationCause, ObligationCauseCode, ObligationCtxt,
};
use crate::coercion::CoerceMany;
use crate::fallback::DivergingFallbackBehavior;
use crate::fn_ctxt::checks::DivergingBlockBehavior;
use crate::{CoroutineTypes, Diverges, EnclosingBreakables, TypeckRootCtxt};
/// The `FnCtxt` stores type-checking context needed to type-check bodies of
@ -517,51 +516,5 @@ fn parse_never_type_options_attr(
// Error handling is dubious here (unwraps), but that's probably fine for an internal attribute.
// Just don't write incorrect attributes <3
let mut fallback = None;
let mut block = None;
let items = if tcx.features().rustc_attrs() {
tcx.get_attr(CRATE_DEF_ID, sym::rustc_never_type_options)
.map(|attr| attr.meta_item_list().unwrap())
} else {
None
};
let items = items.unwrap_or_default();
for item in items {
if item.has_name(sym::fallback) && fallback.is_none() {
let mode = item.value_str().unwrap();
match mode {
sym::unit => fallback = Some(DivergingFallbackBehavior::ToUnit),
sym::never => fallback = Some(DivergingFallbackBehavior::ToNever),
sym::no => fallback = Some(DivergingFallbackBehavior::NoFallback),
_ => {
tcx.dcx().span_err(item.span(), format!("unknown never type fallback mode: `{mode}` (supported: `unit`, `niko`, `never` and `no`)"));
}
};
continue;
}
if item.has_name(sym::diverging_block_default) && block.is_none() {
let default = item.value_str().unwrap();
match default {
sym::unit => block = Some(DivergingBlockBehavior::Unit),
sym::never => block = Some(DivergingBlockBehavior::Never),
_ => {
tcx.dcx().span_err(item.span(), format!("unknown diverging block default: `{default}` (supported: `unit` and `never`)"));
}
};
continue;
}
tcx.dcx().span_err(
item.span(),
format!(
"unknown or duplicate never type option: `{}` (supported: `fallback`, `diverging_block_default`)",
item.name().unwrap()
),
);
}
(fallback, block)
find_attr!(tcx.get_all_attrs(CRATE_DEF_ID), AttributeKind::RustcNeverTypeOptions {fallback, diverging_block_default} => (*fallback, *diverging_block_default)).unwrap_or_default()
}

View file

@ -295,6 +295,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcBodyStability { .. }
| AttributeKind::RustcBuiltinMacro { .. }
| AttributeKind::RustcCaptureAnalysis
| AttributeKind::RustcCguTestAttr(..)
| AttributeKind::RustcClean(..)
| AttributeKind::RustcCoherenceIsCore(..)
| AttributeKind::RustcCoinductive(..)
@ -332,6 +333,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcMain
| AttributeKind::RustcMir(_)
| AttributeKind::RustcNeverReturnsNullPointer
| AttributeKind::RustcNeverTypeOptions {..}
| AttributeKind::RustcNoImplicitAutorefs
| AttributeKind::RustcNoImplicitBounds
| AttributeKind::RustcNonConstTraitMethod
@ -341,7 +343,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcOffloadKernel
| AttributeKind::RustcOutlives
| AttributeKind::RustcParenSugar(..)
| AttributeKind::RustcCguTestAttr(..)
| AttributeKind::RustcPassByValue (..)
| AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)
| AttributeKind::RustcPreserveUbChecks
@ -405,7 +406,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| sym::rustc_test_marker
| sym::rustc_layout
| sym::rustc_proc_macro_decls
| sym::rustc_never_type_options
| sym::rustc_autodiff
| sym::rustc_capture_analysis
| sym::rustc_mir

View file

@ -2,7 +2,7 @@
//! The `rustc_*` attribute is malformed, but ICEing without a `feature(rustc_attrs)` is still bad.
#![rustc_never_type_options(: Unsize<U> = "hi")]
//~^ ERROR expected unsuffixed literal, found `:`
//~^ ERROR expected a literal
//~| ERROR use of an internal attribute
fn main() {}

View file

@ -1,9 +1,3 @@
error: expected unsuffixed literal, found `:`
--> $DIR/malformed-never-type-options.rs:4:29
|
LL | #![rustc_never_type_options(: Unsize<U> = "hi")]
| ^
error[E0658]: use of an internal attribute
--> $DIR/malformed-never-type-options.rs:4:1
|
@ -14,6 +8,12 @@ LL | #![rustc_never_type_options(: Unsize<U> = "hi")]
= note: the `#[rustc_never_type_options]` attribute is an internal implementation detail that will never be stable
= note: `rustc_never_type_options` is used to experiment with never type fallback and work on never type stabilization
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `:`
--> $DIR/malformed-never-type-options.rs:4:29
|
LL | #![rustc_never_type_options(: Unsize<U> = "hi")]
| ^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.