Skip to main content

rustc_attr_parsing/attributes/
stability.rs

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