rustc_attr_parsing/attributes/
stability.rs

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