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