rustc_attr_parsing/attributes/
stability.rs

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