rustc_attr_parsing/attributes/
stability.rs

1use std::num::NonZero;
2
3use rustc_attr_data_structures::{
4    AttributeKind, DefaultBodyStability, PartialConstStability, Stability, StabilityLevel,
5    StableSince, UnstableReason, VERSION_PLACEHOLDER,
6};
7use rustc_errors::ErrorGuaranteed;
8use rustc_span::{Span, Symbol, kw, sym};
9
10use super::util::parse_version;
11use super::{AcceptMapping, AttributeParser, SingleAttributeParser};
12use crate::context::{AcceptContext, FinalizeContext};
13use crate::parser::{ArgParser, MetaItemParser};
14use crate::session_diagnostics::{self, UnsupportedLiteralReason};
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
26#[derive(Default)]
27pub(crate) struct StabilityParser {
28    allowed_through_unstable_modules: Option<Symbol>,
29    stability: Option<(Stability, Span)>,
30}
31
32impl StabilityParser {
33    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
34    fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool {
35        if let Some((_, _)) = self.stability {
36            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
37            true
38        } else {
39            false
40        }
41    }
42}
43
44impl AttributeParser for StabilityParser {
45    const ATTRIBUTES: AcceptMapping<Self> = &[
46        (&[sym::stable], |this, cx, args| {
47            reject_outside_std!(cx);
48            if !this.check_duplicate(cx)
49                && let Some((feature, level)) = parse_stability(cx, args)
50            {
51                this.stability = Some((Stability { level, feature }, cx.attr_span));
52            }
53        }),
54        (&[sym::unstable], |this, cx, args| {
55            reject_outside_std!(cx);
56            if !this.check_duplicate(cx)
57                && let Some((feature, level)) = parse_unstability(cx, args)
58            {
59                this.stability = Some((Stability { level, feature }, cx.attr_span));
60            }
61        }),
62        (&[sym::rustc_allowed_through_unstable_modules], |this, cx, args| {
63            reject_outside_std!(cx);
64            this.allowed_through_unstable_modules =
65                Some(match args.name_value().and_then(|i| i.value_as_str()) {
66                    Some(msg) => msg,
67                    None => kw::Empty,
68                });
69        }),
70    ];
71
72    fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
73        if let Some(atum) = self.allowed_through_unstable_modules {
74            if let Some((
75                Stability {
76                    level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. },
77                    ..
78                },
79                _,
80            )) = self.stability
81            {
82                *allowed_through_unstable_modules = Some(atum);
83            } else {
84                cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing {
85                    span: cx.target_span,
86                });
87            }
88        }
89
90        let (stability, span) = self.stability?;
91
92        Some(AttributeKind::Stability { stability, span })
93    }
94}
95
96// FIXME(jdonszelmann) change to Single
97#[derive(Default)]
98pub(crate) struct BodyStabilityParser {
99    stability: Option<(DefaultBodyStability, Span)>,
100}
101
102impl AttributeParser for BodyStabilityParser {
103    const ATTRIBUTES: AcceptMapping<Self> =
104        &[(&[sym::rustc_default_body_unstable], |this, cx, args| {
105            reject_outside_std!(cx);
106            if this.stability.is_some() {
107                cx.dcx()
108                    .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
109            } else if let Some((feature, level)) = parse_unstability(cx, args) {
110                this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
111            }
112        })];
113
114    fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
115        let (stability, span) = self.stability?;
116
117        Some(AttributeKind::BodyStability { stability, span })
118    }
119}
120
121pub(crate) struct ConstStabilityIndirectParser;
122// FIXME(jdonszelmann): single word attribute group when we have these
123impl SingleAttributeParser for ConstStabilityIndirectParser {
124    const PATH: &'static [rustc_span::Symbol] = &[sym::rustc_const_stable_indirect];
125
126    // ignore
127    fn on_duplicate(_cx: &AcceptContext<'_>, _first_span: Span) {}
128
129    fn convert(_cx: &AcceptContext<'_>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
130        Some(AttributeKind::ConstStabilityIndirect)
131    }
132}
133
134#[derive(Default)]
135pub(crate) struct ConstStabilityParser {
136    promotable: bool,
137    stability: Option<(PartialConstStability, Span)>,
138}
139
140impl ConstStabilityParser {
141    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
142    fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool {
143        if let Some((_, _)) = self.stability {
144            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
145            true
146        } else {
147            false
148        }
149    }
150}
151
152impl AttributeParser for ConstStabilityParser {
153    const ATTRIBUTES: AcceptMapping<Self> = &[
154        (&[sym::rustc_const_stable], |this, cx, args| {
155            reject_outside_std!(cx);
156
157            if !this.check_duplicate(cx)
158                && let Some((feature, level)) = parse_stability(cx, args)
159            {
160                this.stability = Some((
161                    PartialConstStability { level, feature, promotable: false },
162                    cx.attr_span,
163                ));
164            }
165        }),
166        (&[sym::rustc_const_unstable], |this, cx, args| {
167            reject_outside_std!(cx);
168            if !this.check_duplicate(cx)
169                && let Some((feature, level)) = parse_unstability(cx, args)
170            {
171                this.stability = Some((
172                    PartialConstStability { level, feature, promotable: false },
173                    cx.attr_span,
174                ));
175            }
176        }),
177        (&[sym::rustc_promotable], |this, cx, _| {
178            reject_outside_std!(cx);
179            this.promotable = true;
180        }),
181    ];
182
183    fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
184        if self.promotable {
185            if let Some((ref mut stab, _)) = self.stability {
186                stab.promotable = true;
187            } else {
188                cx.dcx()
189                    .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
190            }
191        }
192
193        let (stability, span) = self.stability?;
194
195        Some(AttributeKind::ConstStability { stability, span })
196    }
197}
198
199/// Tries to insert the value of a `key = value` meta item into an option.
200///
201/// Emits an error when either the option was already Some, or the arguments weren't of form
202/// `name = value`
203fn insert_value_into_option_or_error(
204    cx: &AcceptContext<'_>,
205    param: &MetaItemParser<'_>,
206    item: &mut Option<Symbol>,
207) -> Option<()> {
208    if item.is_some() {
209        cx.emit_err(session_diagnostics::MultipleItem {
210            span: param.span(),
211            item: param.path_without_args().to_string(),
212        });
213        None
214    } else if let Some(v) = param.args().name_value()
215        && let Some(s) = v.value_as_str()
216    {
217        *item = Some(s);
218        Some(())
219    } else {
220        cx.emit_err(session_diagnostics::IncorrectMetaItem {
221            span: param.span(),
222            suggestion: None,
223        });
224        None
225    }
226}
227
228/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
229/// its stability information.
230pub(crate) fn parse_stability(
231    cx: &AcceptContext<'_>,
232    args: &ArgParser<'_>,
233) -> Option<(Symbol, StabilityLevel)> {
234    let mut feature = None;
235    let mut since = None;
236
237    for param in args.list()?.mixed() {
238        let param_span = param.span();
239        let Some(param) = param.meta_item() else {
240            cx.emit_err(session_diagnostics::UnsupportedLiteral {
241                span: param_span,
242                reason: UnsupportedLiteralReason::Generic,
243                is_bytestr: false,
244                start_point_span: cx.sess().source_map().start_point(param_span),
245            });
246            return None;
247        };
248
249        match param.word_or_empty_without_args().name {
250            sym::feature => insert_value_into_option_or_error(cx, &param, &mut feature)?,
251            sym::since => insert_value_into_option_or_error(cx, &param, &mut since)?,
252            _ => {
253                cx.emit_err(session_diagnostics::UnknownMetaItem {
254                    span: param_span,
255                    item: param.path_without_args().to_string(),
256                    expected: &["feature", "since"],
257                });
258                return None;
259            }
260        }
261    }
262
263    let feature = match feature {
264        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
265        Some(_bad_feature) => {
266            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
267        }
268        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
269    };
270
271    let since = if let Some(since) = since {
272        if since.as_str() == VERSION_PLACEHOLDER {
273            StableSince::Current
274        } else if let Some(version) = parse_version(since) {
275            StableSince::Version(version)
276        } else {
277            cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
278            StableSince::Err
279        }
280    } else {
281        cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
282        StableSince::Err
283    };
284
285    match feature {
286        Ok(feature) => {
287            let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
288            Some((feature, level))
289        }
290        Err(ErrorGuaranteed { .. }) => None,
291    }
292}
293
294// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
295/// attribute, and return the feature name and its stability information.
296pub(crate) fn parse_unstability(
297    cx: &AcceptContext<'_>,
298    args: &ArgParser<'_>,
299) -> Option<(Symbol, StabilityLevel)> {
300    let mut feature = None;
301    let mut reason = None;
302    let mut issue = None;
303    let mut issue_num = None;
304    let mut is_soft = false;
305    let mut implied_by = None;
306    for param in args.list()?.mixed() {
307        let Some(param) = param.meta_item() else {
308            cx.emit_err(session_diagnostics::UnsupportedLiteral {
309                span: param.span(),
310                reason: UnsupportedLiteralReason::Generic,
311                is_bytestr: false,
312                start_point_span: cx.sess().source_map().start_point(param.span()),
313            });
314            return None;
315        };
316
317        let (word, args) = param.word_or_empty();
318        match word.name {
319            sym::feature => insert_value_into_option_or_error(cx, &param, &mut feature)?,
320            sym::reason => insert_value_into_option_or_error(cx, &param, &mut reason)?,
321            sym::issue => {
322                insert_value_into_option_or_error(cx, &param, &mut issue)?;
323
324                // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
325                // is a name/value pair string literal.
326                issue_num = match issue.unwrap().as_str() {
327                    "none" => None,
328                    issue_str => match issue_str.parse::<NonZero<u32>>() {
329                        Ok(num) => Some(num),
330                        Err(err) => {
331                            cx.emit_err(
332                                session_diagnostics::InvalidIssueString {
333                                    span: param.span(),
334                                    cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
335                                        args.name_value().unwrap().value_span,
336                                        err.kind(),
337                                    ),
338                                },
339                            );
340                            return None;
341                        }
342                    },
343                };
344            }
345            sym::soft => {
346                if !args.no_args() {
347                    cx.emit_err(session_diagnostics::SoftNoArgs { span: param.span() });
348                }
349                is_soft = true;
350            }
351            sym::implied_by => insert_value_into_option_or_error(cx, &param, &mut implied_by)?,
352            _ => {
353                cx.emit_err(session_diagnostics::UnknownMetaItem {
354                    span: param.span(),
355                    item: param.path_without_args().to_string(),
356                    expected: &["feature", "reason", "issue", "soft", "implied_by"],
357                });
358                return None;
359            }
360        }
361    }
362
363    let feature = match feature {
364        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
365        Some(_bad_feature) => {
366            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
367        }
368        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
369    };
370
371    let issue =
372        issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
373
374    match (feature, issue) {
375        (Ok(feature), Ok(_)) => {
376            let level = StabilityLevel::Unstable {
377                reason: UnstableReason::from_opt_reason(reason),
378                issue: issue_num,
379                is_soft,
380                implied_by,
381            };
382            Some((feature, level))
383        }
384        (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
385    }
386}