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