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