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