Skip to main content

rustc_attr_parsing/attributes/
stability.rs

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