rustc_attr_parsing/attributes/
stability.rs

1use std::num::NonZero;
2
3use rustc_errors::ErrorGuaranteed;
4use rustc_feature::template;
5use rustc_hir::attrs::AttributeKind;
6use rustc_hir::{
7    DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel,
8    StableSince, Target, UnstableReason, VERSION_PLACEHOLDER,
9};
10use rustc_span::{Ident, Span, Symbol, sym};
11
12use super::util::parse_version;
13use super::{AcceptMapping, AttributeParser, OnDuplicate};
14use crate::attributes::NoArgsAttributeParser;
15use crate::context::MaybeWarn::Allow;
16use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
17use crate::parser::{ArgParser, MetaItemParser};
18use crate::session_diagnostics::{self, UnsupportedLiteralReason};
19
20macro_rules! reject_outside_std {
21    ($cx: ident) => {
22        // Emit errors for non-staged-api crates.
23        if !$cx.features().staged_api() {
24            $cx.emit_err(session_diagnostics::StabilityOutsideStd { span: $cx.attr_span });
25            return;
26        }
27    };
28}
29
30const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
31    Allow(Target::Fn),
32    Allow(Target::Struct),
33    Allow(Target::Enum),
34    Allow(Target::Union),
35    Allow(Target::Method(MethodKind::Inherent)),
36    Allow(Target::Method(MethodKind::Trait { body: false })),
37    Allow(Target::Method(MethodKind::Trait { body: true })),
38    Allow(Target::Method(MethodKind::TraitImpl)),
39    Allow(Target::Impl { of_trait: false }),
40    Allow(Target::Impl { of_trait: true }),
41    Allow(Target::MacroDef),
42    Allow(Target::Crate),
43    Allow(Target::Mod),
44    Allow(Target::Use), // FIXME I don't think this does anything?
45    Allow(Target::Const),
46    Allow(Target::AssocConst),
47    Allow(Target::AssocTy),
48    Allow(Target::Trait),
49    Allow(Target::TraitAlias),
50    Allow(Target::TyAlias),
51    Allow(Target::Variant),
52    Allow(Target::Field),
53    Allow(Target::Param),
54    Allow(Target::Static),
55    Allow(Target::ForeignFn),
56    Allow(Target::ForeignStatic),
57]);
58
59#[derive(Default)]
60pub(crate) struct StabilityParser {
61    allowed_through_unstable_modules: Option<Symbol>,
62    stability: Option<(Stability, Span)>,
63}
64
65impl StabilityParser {
66    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
67    fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
68        if let Some((_, _)) = self.stability {
69            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
70            true
71        } else {
72            false
73        }
74    }
75}
76
77impl<S: Stage> AttributeParser<S> for StabilityParser {
78    const ATTRIBUTES: AcceptMapping<Self, S> = &[
79        (
80            &[sym::stable],
81            template!(List: &[r#"feature = "name", since = "version""#]),
82            |this, cx, args| {
83                reject_outside_std!(cx);
84                if !this.check_duplicate(cx)
85                    && let Some((feature, level)) = parse_stability(cx, args)
86                {
87                    this.stability = Some((Stability { level, feature }, cx.attr_span));
88                }
89            },
90        ),
91        (
92            &[sym::unstable],
93            template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
94            |this, cx, args| {
95                reject_outside_std!(cx);
96                if !this.check_duplicate(cx)
97                    && let Some((feature, level)) = parse_unstability(cx, args)
98                {
99                    this.stability = Some((Stability { level, feature }, cx.attr_span));
100                }
101            },
102        ),
103        (
104            &[sym::rustc_allowed_through_unstable_modules],
105            template!(NameValueStr: "deprecation message"),
106            |this, cx, args| {
107                reject_outside_std!(cx);
108                let Some(nv) = args.name_value() else {
109                    cx.expected_name_value(cx.attr_span, None);
110                    return;
111                };
112                let Some(value_str) = nv.value_as_str() else {
113                    cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
114                    return;
115                };
116                this.allowed_through_unstable_modules = Some(value_str);
117            },
118        ),
119    ];
120    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
121
122    fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
123        if let Some(atum) = self.allowed_through_unstable_modules {
124            if let Some((
125                Stability {
126                    level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. },
127                    ..
128                },
129                _,
130            )) = self.stability
131            {
132                *allowed_through_unstable_modules = Some(atum);
133            } else {
134                cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing {
135                    span: cx.target_span,
136                });
137            }
138        }
139
140        if let Some((Stability { level: StabilityLevel::Stable { .. }, .. }, _)) = self.stability {
141            for other_attr in cx.all_attrs {
142                if other_attr.word_is(sym::unstable_feature_bound) {
143                    cx.emit_err(session_diagnostics::UnstableFeatureBoundIncompatibleStability {
144                        span: cx.target_span,
145                    });
146                }
147            }
148        }
149
150        let (stability, span) = self.stability?;
151
152        Some(AttributeKind::Stability { stability, span })
153    }
154}
155
156// FIXME(jdonszelmann) change to Single
157#[derive(Default)]
158pub(crate) struct BodyStabilityParser {
159    stability: Option<(DefaultBodyStability, Span)>,
160}
161
162impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
163    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
164        &[sym::rustc_default_body_unstable],
165        template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
166        |this, cx, args| {
167            reject_outside_std!(cx);
168            if this.stability.is_some() {
169                cx.dcx()
170                    .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
171            } else if let Some((feature, level)) = parse_unstability(cx, args) {
172                this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
173            }
174        },
175    )];
176    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
177
178    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
179        let (stability, span) = self.stability?;
180
181        Some(AttributeKind::BodyStability { stability, span })
182    }
183}
184
185pub(crate) struct ConstStabilityIndirectParser;
186impl<S: Stage> NoArgsAttributeParser<S> for ConstStabilityIndirectParser {
187    const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect];
188    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
189    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
190        Allow(Target::Fn),
191        Allow(Target::Method(MethodKind::Inherent)),
192    ]);
193    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ConstStabilityIndirect;
194}
195
196#[derive(Default)]
197pub(crate) struct ConstStabilityParser {
198    promotable: bool,
199    stability: Option<(PartialConstStability, Span)>,
200}
201
202impl ConstStabilityParser {
203    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
204    fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
205        if let Some((_, _)) = self.stability {
206            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
207            true
208        } else {
209            false
210        }
211    }
212}
213
214impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
215    const ATTRIBUTES: AcceptMapping<Self, S> = &[
216        (
217            &[sym::rustc_const_stable],
218            template!(List: &[r#"feature = "name""#]),
219            |this, cx, args| {
220                reject_outside_std!(cx);
221
222                if !this.check_duplicate(cx)
223                    && let Some((feature, level)) = parse_stability(cx, args)
224                {
225                    this.stability = Some((
226                        PartialConstStability { level, feature, promotable: false },
227                        cx.attr_span,
228                    ));
229                }
230            },
231        ),
232        (
233            &[sym::rustc_const_unstable],
234            template!(List: &[r#"feature = "name""#]),
235            |this, cx, args| {
236                reject_outside_std!(cx);
237                if !this.check_duplicate(cx)
238                    && let Some((feature, level)) = parse_unstability(cx, args)
239                {
240                    this.stability = Some((
241                        PartialConstStability { level, feature, promotable: false },
242                        cx.attr_span,
243                    ));
244                }
245            },
246        ),
247        (&[sym::rustc_promotable], template!(Word), |this, cx, _| {
248            reject_outside_std!(cx);
249            this.promotable = true;
250        }),
251    ];
252    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
253
254    fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
255        if self.promotable {
256            if let Some((ref mut stab, _)) = self.stability {
257                stab.promotable = true;
258            } else {
259                cx.dcx()
260                    .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
261            }
262        }
263
264        let (stability, span) = self.stability?;
265
266        Some(AttributeKind::ConstStability { stability, span })
267    }
268}
269
270/// Tries to insert the value of a `key = value` meta item into an option.
271///
272/// Emits an error when either the option was already Some, or the arguments weren't of form
273/// `name = value`
274fn insert_value_into_option_or_error<S: Stage>(
275    cx: &AcceptContext<'_, '_, S>,
276    param: &MetaItemParser<'_>,
277    item: &mut Option<Symbol>,
278    name: Ident,
279) -> Option<()> {
280    if item.is_some() {
281        cx.duplicate_key(name.span, name.name);
282        None
283    } else if let Some(v) = param.args().name_value()
284        && let Some(s) = v.value_as_str()
285    {
286        *item = Some(s);
287        Some(())
288    } else {
289        cx.expected_name_value(param.span(), Some(name.name));
290        None
291    }
292}
293
294/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
295/// its stability information.
296pub(crate) fn parse_stability<S: Stage>(
297    cx: &AcceptContext<'_, '_, S>,
298    args: &ArgParser<'_>,
299) -> Option<(Symbol, StabilityLevel)> {
300    let mut feature = None;
301    let mut since = None;
302
303    let ArgParser::List(list) = args else {
304        cx.expected_list(cx.attr_span);
305        return None;
306    };
307
308    for param in list.mixed() {
309        let param_span = param.span();
310        let Some(param) = param.meta_item() else {
311            cx.emit_err(session_diagnostics::UnsupportedLiteral {
312                span: param_span,
313                reason: UnsupportedLiteralReason::Generic,
314                is_bytestr: false,
315                start_point_span: cx.sess().source_map().start_point(param_span),
316            });
317            return None;
318        };
319
320        let word = param.path().word();
321        match word.map(|i| i.name) {
322            Some(sym::feature) => {
323                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
324            }
325            Some(sym::since) => {
326                insert_value_into_option_or_error(cx, &param, &mut since, word.unwrap())?
327            }
328            _ => {
329                cx.emit_err(session_diagnostics::UnknownMetaItem {
330                    span: param_span,
331                    item: param.path().to_string(),
332                    expected: &["feature", "since"],
333                });
334                return None;
335            }
336        }
337    }
338
339    let feature = match feature {
340        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
341        Some(_bad_feature) => {
342            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
343        }
344        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
345    };
346
347    let since = if let Some(since) = since {
348        if since.as_str() == VERSION_PLACEHOLDER {
349            StableSince::Current
350        } else if let Some(version) = parse_version(since) {
351            StableSince::Version(version)
352        } else {
353            let err = cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
354            StableSince::Err(err)
355        }
356    } else {
357        let err = cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
358        StableSince::Err(err)
359    };
360
361    match feature {
362        Ok(feature) => {
363            let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
364            Some((feature, level))
365        }
366        Err(ErrorGuaranteed { .. }) => None,
367    }
368}
369
370// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
371/// attribute, and return the feature name and its stability information.
372pub(crate) fn parse_unstability<S: Stage>(
373    cx: &AcceptContext<'_, '_, S>,
374    args: &ArgParser<'_>,
375) -> Option<(Symbol, StabilityLevel)> {
376    let mut feature = None;
377    let mut reason = None;
378    let mut issue = None;
379    let mut issue_num = None;
380    let mut is_soft = false;
381    let mut implied_by = None;
382    let mut old_name = None;
383
384    let ArgParser::List(list) = args else {
385        cx.expected_list(cx.attr_span);
386        return None;
387    };
388
389    for param in list.mixed() {
390        let Some(param) = param.meta_item() else {
391            cx.emit_err(session_diagnostics::UnsupportedLiteral {
392                span: param.span(),
393                reason: UnsupportedLiteralReason::Generic,
394                is_bytestr: false,
395                start_point_span: cx.sess().source_map().start_point(param.span()),
396            });
397            return None;
398        };
399
400        let word = param.path().word();
401        match word.map(|i| i.name) {
402            Some(sym::feature) => {
403                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
404            }
405            Some(sym::reason) => {
406                insert_value_into_option_or_error(cx, &param, &mut reason, word.unwrap())?
407            }
408            Some(sym::issue) => {
409                insert_value_into_option_or_error(cx, &param, &mut issue, word.unwrap())?;
410
411                // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
412                // is a name/value pair string literal.
413                issue_num = match issue.unwrap().as_str() {
414                    "none" => None,
415                    issue_str => match issue_str.parse::<NonZero<u32>>() {
416                        Ok(num) => Some(num),
417                        Err(err) => {
418                            cx.emit_err(
419                                session_diagnostics::InvalidIssueString {
420                                    span: param.span(),
421                                    cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
422                                        param.args().name_value().unwrap().value_span,
423                                        err.kind(),
424                                    ),
425                                },
426                            );
427                            return None;
428                        }
429                    },
430                };
431            }
432            Some(sym::soft) => {
433                if let Err(span) = args.no_args() {
434                    cx.emit_err(session_diagnostics::SoftNoArgs { span });
435                }
436                is_soft = true;
437            }
438            Some(sym::implied_by) => {
439                insert_value_into_option_or_error(cx, &param, &mut implied_by, word.unwrap())?
440            }
441            Some(sym::old_name) => {
442                insert_value_into_option_or_error(cx, &param, &mut old_name, word.unwrap())?
443            }
444            _ => {
445                cx.emit_err(session_diagnostics::UnknownMetaItem {
446                    span: param.span(),
447                    item: param.path().to_string(),
448                    expected: &["feature", "reason", "issue", "soft", "implied_by", "old_name"],
449                });
450                return None;
451            }
452        }
453    }
454
455    let feature = match feature {
456        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
457        Some(_bad_feature) => {
458            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
459        }
460        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
461    };
462
463    let issue =
464        issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
465
466    match (feature, issue) {
467        (Ok(feature), Ok(_)) => {
468            let level = StabilityLevel::Unstable {
469                reason: UnstableReason::from_opt_reason(reason),
470                issue: issue_num,
471                is_soft,
472                implied_by,
473                old_name,
474            };
475            Some((feature, level))
476        }
477        (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
478    }
479}