Skip to main content

rustc_attr_parsing/attributes/
stability.rs

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