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