2024-12-07 15:27:17 +01:00
|
|
|
use std::num::NonZero;
|
|
|
|
|
|
2024-12-13 14:47:11 +01:00
|
|
|
use rustc_attr_data_structures::{
|
2025-02-09 22:49:33 +01:00
|
|
|
AttributeKind, DefaultBodyStability, PartialConstStability, Stability, StabilityLevel,
|
|
|
|
|
StableSince, UnstableReason, VERSION_PLACEHOLDER,
|
2024-12-07 15:27:17 +01:00
|
|
|
};
|
2024-12-13 14:47:11 +01:00
|
|
|
use rustc_errors::ErrorGuaranteed;
|
2025-03-25 15:50:31 +11:00
|
|
|
use rustc_span::{Span, Symbol, sym};
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
use super::util::parse_version;
|
|
|
|
|
use super::{AcceptMapping, AttributeParser, SingleAttributeParser};
|
|
|
|
|
use crate::context::{AcceptContext, FinalizeContext};
|
|
|
|
|
use crate::parser::{ArgParser, MetaItemParser};
|
|
|
|
|
use crate::session_diagnostics::{self, UnsupportedLiteralReason};
|
|
|
|
|
|
|
|
|
|
macro_rules! reject_outside_std {
|
|
|
|
|
($cx: ident) => {
|
|
|
|
|
// Emit errors for non-staged-api crates.
|
|
|
|
|
if !$cx.features().staged_api() {
|
|
|
|
|
$cx.emit_err(session_diagnostics::StabilityOutsideStd { span: $cx.attr_span });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub(crate) struct StabilityParser {
|
|
|
|
|
allowed_through_unstable_modules: Option<Symbol>,
|
|
|
|
|
stability: Option<(Stability, Span)>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl StabilityParser {
|
|
|
|
|
/// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
|
|
|
|
|
fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool {
|
|
|
|
|
if let Some((_, _)) = self.stability {
|
|
|
|
|
cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
impl AttributeParser for StabilityParser {
|
|
|
|
|
const ATTRIBUTES: AcceptMapping<Self> = &[
|
|
|
|
|
(&[sym::stable], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
if !this.check_duplicate(cx)
|
|
|
|
|
&& let Some((feature, level)) = parse_stability(cx, args)
|
|
|
|
|
{
|
|
|
|
|
this.stability = Some((Stability { level, feature }, cx.attr_span));
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
(&[sym::unstable], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
if !this.check_duplicate(cx)
|
|
|
|
|
&& let Some((feature, level)) = parse_unstability(cx, args)
|
|
|
|
|
{
|
|
|
|
|
this.stability = Some((Stability { level, feature }, cx.attr_span));
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
(&[sym::rustc_allowed_through_unstable_modules], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
2025-03-25 15:50:31 +11:00
|
|
|
this.allowed_through_unstable_modules = args.name_value().and_then(|i| i.value_as_str())
|
2025-02-09 22:49:33 +01:00
|
|
|
}),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
|
|
|
|
|
if let Some(atum) = self.allowed_through_unstable_modules {
|
|
|
|
|
if let Some((
|
2024-12-07 15:27:17 +01:00
|
|
|
Stability {
|
2025-02-09 22:49:33 +01:00
|
|
|
level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. },
|
2024-12-07 15:27:17 +01:00
|
|
|
..
|
|
|
|
|
},
|
|
|
|
|
_,
|
2025-02-09 22:49:33 +01:00
|
|
|
)) = self.stability
|
|
|
|
|
{
|
|
|
|
|
*allowed_through_unstable_modules = Some(atum);
|
|
|
|
|
} else {
|
|
|
|
|
cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing {
|
|
|
|
|
span: cx.target_span,
|
|
|
|
|
});
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
let (stability, span) = self.stability?;
|
|
|
|
|
|
|
|
|
|
Some(AttributeKind::Stability { stability, span })
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
// FIXME(jdonszelmann) change to Single
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub(crate) struct BodyStabilityParser {
|
|
|
|
|
stability: Option<(DefaultBodyStability, Span)>,
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
impl AttributeParser for BodyStabilityParser {
|
|
|
|
|
const ATTRIBUTES: AcceptMapping<Self> =
|
|
|
|
|
&[(&[sym::rustc_default_body_unstable], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
if this.stability.is_some() {
|
|
|
|
|
cx.dcx()
|
|
|
|
|
.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
|
|
|
|
|
} else if let Some((feature, level)) = parse_unstability(cx, args) {
|
|
|
|
|
this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
})];
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
|
|
|
|
|
let (stability, span) = self.stability?;
|
|
|
|
|
|
|
|
|
|
Some(AttributeKind::BodyStability { stability, span })
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) struct ConstStabilityIndirectParser;
|
|
|
|
|
// FIXME(jdonszelmann): single word attribute group when we have these
|
|
|
|
|
impl SingleAttributeParser for ConstStabilityIndirectParser {
|
|
|
|
|
const PATH: &'static [rustc_span::Symbol] = &[sym::rustc_const_stable_indirect];
|
|
|
|
|
|
|
|
|
|
// ignore
|
|
|
|
|
fn on_duplicate(_cx: &AcceptContext<'_>, _first_span: Span) {}
|
|
|
|
|
|
|
|
|
|
fn convert(_cx: &AcceptContext<'_>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
|
|
|
|
|
Some(AttributeKind::ConstStabilityIndirect)
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub(crate) struct ConstStabilityParser {
|
|
|
|
|
promotable: bool,
|
|
|
|
|
stability: Option<(PartialConstStability, Span)>,
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
impl ConstStabilityParser {
|
|
|
|
|
/// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
|
|
|
|
|
fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool {
|
|
|
|
|
if let Some((_, _)) = self.stability {
|
|
|
|
|
cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
impl AttributeParser for ConstStabilityParser {
|
|
|
|
|
const ATTRIBUTES: AcceptMapping<Self> = &[
|
|
|
|
|
(&[sym::rustc_const_stable], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
|
|
|
|
|
if !this.check_duplicate(cx)
|
|
|
|
|
&& let Some((feature, level)) = parse_stability(cx, args)
|
|
|
|
|
{
|
|
|
|
|
this.stability = Some((
|
|
|
|
|
PartialConstStability { level, feature, promotable: false },
|
|
|
|
|
cx.attr_span,
|
|
|
|
|
));
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
}),
|
|
|
|
|
(&[sym::rustc_const_unstable], |this, cx, args| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
if !this.check_duplicate(cx)
|
|
|
|
|
&& let Some((feature, level)) = parse_unstability(cx, args)
|
|
|
|
|
{
|
|
|
|
|
this.stability = Some((
|
|
|
|
|
PartialConstStability { level, feature, promotable: false },
|
|
|
|
|
cx.attr_span,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
(&[sym::rustc_promotable], |this, cx, _| {
|
|
|
|
|
reject_outside_std!(cx);
|
|
|
|
|
this.promotable = true;
|
|
|
|
|
}),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
|
|
|
|
|
if self.promotable {
|
|
|
|
|
if let Some((ref mut stab, _)) = self.stability {
|
|
|
|
|
stab.promotable = true;
|
|
|
|
|
} else {
|
|
|
|
|
cx.dcx()
|
|
|
|
|
.emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
let (stability, span) = self.stability?;
|
|
|
|
|
|
|
|
|
|
Some(AttributeKind::ConstStability { stability, span })
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
/// Tries to insert the value of a `key = value` meta item into an option.
|
|
|
|
|
///
|
|
|
|
|
/// Emits an error when either the option was already Some, or the arguments weren't of form
|
|
|
|
|
/// `name = value`
|
|
|
|
|
fn insert_value_into_option_or_error(
|
|
|
|
|
cx: &AcceptContext<'_>,
|
|
|
|
|
param: &MetaItemParser<'_>,
|
|
|
|
|
item: &mut Option<Symbol>,
|
|
|
|
|
) -> Option<()> {
|
2024-12-07 15:27:17 +01:00
|
|
|
if item.is_some() {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::MultipleItem {
|
|
|
|
|
span: param.span(),
|
|
|
|
|
item: param.path_without_args().to_string(),
|
2024-12-07 15:27:17 +01:00
|
|
|
});
|
|
|
|
|
None
|
2025-02-09 22:49:33 +01:00
|
|
|
} else if let Some(v) = param.args().name_value()
|
|
|
|
|
&& let Some(s) = v.value_as_str()
|
|
|
|
|
{
|
|
|
|
|
*item = Some(s);
|
2024-12-07 15:27:17 +01:00
|
|
|
Some(())
|
|
|
|
|
} else {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::IncorrectMetaItem {
|
|
|
|
|
span: param.span(),
|
|
|
|
|
suggestion: None,
|
|
|
|
|
});
|
2024-12-07 15:27:17 +01:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
|
|
|
|
|
/// its stability information.
|
2025-02-09 22:49:33 +01:00
|
|
|
pub(crate) fn parse_stability(
|
|
|
|
|
cx: &AcceptContext<'_>,
|
|
|
|
|
args: &ArgParser<'_>,
|
|
|
|
|
) -> Option<(Symbol, StabilityLevel)> {
|
2024-12-07 15:27:17 +01:00
|
|
|
let mut feature = None;
|
|
|
|
|
let mut since = None;
|
2025-02-09 22:49:33 +01:00
|
|
|
|
|
|
|
|
for param in args.list()?.mixed() {
|
|
|
|
|
let param_span = param.span();
|
|
|
|
|
let Some(param) = param.meta_item() else {
|
|
|
|
|
cx.emit_err(session_diagnostics::UnsupportedLiteral {
|
|
|
|
|
span: param_span,
|
2024-12-07 15:27:17 +01:00
|
|
|
reason: UnsupportedLiteralReason::Generic,
|
|
|
|
|
is_bytestr: false,
|
2025-02-09 22:49:33 +01:00
|
|
|
start_point_span: cx.sess().source_map().start_point(param_span),
|
2024-12-07 15:27:17 +01:00
|
|
|
});
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-21 07:52:30 +10:00
|
|
|
match param.path_without_args().word_sym() {
|
|
|
|
|
Some(sym::feature) => insert_value_into_option_or_error(cx, ¶m, &mut feature)?,
|
|
|
|
|
Some(sym::since) => insert_value_into_option_or_error(cx, ¶m, &mut since)?,
|
2024-12-07 15:27:17 +01:00
|
|
|
_ => {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::UnknownMetaItem {
|
|
|
|
|
span: param_span,
|
|
|
|
|
item: param.path_without_args().to_string(),
|
2024-12-07 15:27:17 +01:00
|
|
|
expected: &["feature", "since"],
|
|
|
|
|
});
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let feature = match feature {
|
|
|
|
|
Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
|
|
|
|
|
Some(_bad_feature) => {
|
2025-02-09 22:49:33 +01:00
|
|
|
Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
|
2024-12-07 15:27:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let since = if let Some(since) = since {
|
|
|
|
|
if since.as_str() == VERSION_PLACEHOLDER {
|
|
|
|
|
StableSince::Current
|
|
|
|
|
} else if let Some(version) = parse_version(since) {
|
|
|
|
|
StableSince::Version(version)
|
|
|
|
|
} else {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
|
2024-12-07 15:27:17 +01:00
|
|
|
StableSince::Err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
|
2024-12-07 15:27:17 +01:00
|
|
|
StableSince::Err
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match feature {
|
|
|
|
|
Ok(feature) => {
|
2025-01-01 19:09:01 +01:00
|
|
|
let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
|
2024-12-07 15:27:17 +01:00
|
|
|
Some((feature, level))
|
|
|
|
|
}
|
|
|
|
|
Err(ErrorGuaranteed { .. }) => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
|
2024-12-07 15:27:17 +01:00
|
|
|
/// attribute, and return the feature name and its stability information.
|
2025-02-09 22:49:33 +01:00
|
|
|
pub(crate) fn parse_unstability(
|
|
|
|
|
cx: &AcceptContext<'_>,
|
|
|
|
|
args: &ArgParser<'_>,
|
|
|
|
|
) -> Option<(Symbol, StabilityLevel)> {
|
2024-12-07 15:27:17 +01:00
|
|
|
let mut feature = None;
|
|
|
|
|
let mut reason = None;
|
|
|
|
|
let mut issue = None;
|
|
|
|
|
let mut issue_num = None;
|
|
|
|
|
let mut is_soft = false;
|
|
|
|
|
let mut implied_by = None;
|
2025-02-09 22:49:33 +01:00
|
|
|
for param in args.list()?.mixed() {
|
|
|
|
|
let Some(param) = param.meta_item() else {
|
|
|
|
|
cx.emit_err(session_diagnostics::UnsupportedLiteral {
|
|
|
|
|
span: param.span(),
|
2024-12-07 15:27:17 +01:00
|
|
|
reason: UnsupportedLiteralReason::Generic,
|
|
|
|
|
is_bytestr: false,
|
2025-02-09 22:49:33 +01:00
|
|
|
start_point_span: cx.sess().source_map().start_point(param.span()),
|
2024-12-07 15:27:17 +01:00
|
|
|
});
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-21 07:52:30 +10:00
|
|
|
match param.path_without_args().word_sym() {
|
|
|
|
|
Some(sym::feature) => insert_value_into_option_or_error(cx, ¶m, &mut feature)?,
|
|
|
|
|
Some(sym::reason) => insert_value_into_option_or_error(cx, ¶m, &mut reason)?,
|
|
|
|
|
Some(sym::issue) => {
|
2025-02-09 22:49:33 +01:00
|
|
|
insert_value_into_option_or_error(cx, ¶m, &mut issue)?;
|
2024-12-07 15:27:17 +01:00
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
// These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
|
2024-12-07 15:27:17 +01:00
|
|
|
// is a name/value pair string literal.
|
|
|
|
|
issue_num = match issue.unwrap().as_str() {
|
|
|
|
|
"none" => None,
|
2025-02-09 22:49:33 +01:00
|
|
|
issue_str => match issue_str.parse::<NonZero<u32>>() {
|
2024-12-07 15:27:17 +01:00
|
|
|
Ok(num) => Some(num),
|
|
|
|
|
Err(err) => {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(
|
2024-12-07 15:27:17 +01:00
|
|
|
session_diagnostics::InvalidIssueString {
|
2025-02-09 22:49:33 +01:00
|
|
|
span: param.span(),
|
2024-12-07 15:27:17 +01:00
|
|
|
cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
|
2025-04-21 07:52:30 +10:00
|
|
|
param.args().name_value().unwrap().value_span,
|
2024-12-07 15:27:17 +01:00
|
|
|
err.kind(),
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-04-21 07:52:30 +10:00
|
|
|
Some(sym::soft) => {
|
|
|
|
|
if !param.args().no_args() {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::SoftNoArgs { span: param.span() });
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
|
|
|
|
is_soft = true;
|
|
|
|
|
}
|
2025-04-21 07:52:30 +10:00
|
|
|
Some(sym::implied_by) => {
|
|
|
|
|
insert_value_into_option_or_error(cx, ¶m, &mut implied_by)?
|
|
|
|
|
}
|
2024-12-07 15:27:17 +01:00
|
|
|
_ => {
|
2025-02-09 22:49:33 +01:00
|
|
|
cx.emit_err(session_diagnostics::UnknownMetaItem {
|
|
|
|
|
span: param.span(),
|
|
|
|
|
item: param.path_without_args().to_string(),
|
2024-12-07 15:27:17 +01:00
|
|
|
expected: &["feature", "reason", "issue", "soft", "implied_by"],
|
|
|
|
|
});
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let feature = match feature {
|
|
|
|
|
Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
|
|
|
|
|
Some(_bad_feature) => {
|
2025-02-09 22:49:33 +01:00
|
|
|
Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
|
2024-12-07 15:27:17 +01:00
|
|
|
}
|
2025-02-09 22:49:33 +01:00
|
|
|
None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
|
2024-12-07 15:27:17 +01:00
|
|
|
};
|
|
|
|
|
|
2025-02-09 22:49:33 +01:00
|
|
|
let issue =
|
|
|
|
|
issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
|
2024-12-07 15:27:17 +01:00
|
|
|
|
|
|
|
|
match (feature, issue) {
|
|
|
|
|
(Ok(feature), Ok(_)) => {
|
|
|
|
|
let level = StabilityLevel::Unstable {
|
|
|
|
|
reason: UnstableReason::from_opt_reason(reason),
|
|
|
|
|
issue: issue_num,
|
|
|
|
|
is_soft,
|
|
|
|
|
implied_by,
|
|
|
|
|
};
|
|
|
|
|
Some((feature, level))
|
|
|
|
|
}
|
|
|
|
|
(Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
|
|
|
|
|
}
|
|
|
|
|
}
|