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