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