Skip to main content

rustc_attr_parsing/attributes/
stability.rs

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