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        if let Err(span) = args.no_args() {
144            cx.expected_no_args(span);
145        }
146        Some(AttributeKind::ConstStabilityIndirect)
147    }
148}
149
150#[derive(Default)]
151pub(crate) struct ConstStabilityParser {
152    promotable: bool,
153    stability: Option<(PartialConstStability, Span)>,
154}
155
156impl ConstStabilityParser {
157    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
158    fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
159        if let Some((_, _)) = self.stability {
160            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
161            true
162        } else {
163            false
164        }
165    }
166}
167
168impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
169    const ATTRIBUTES: AcceptMapping<Self, S> = &[
170        (&[sym::rustc_const_stable], template!(List: r#"feature = "name""#), |this, cx, args| {
171            reject_outside_std!(cx);
172
173            if !this.check_duplicate(cx)
174                && let Some((feature, level)) = parse_stability(cx, args)
175            {
176                this.stability = Some((
177                    PartialConstStability { level, feature, promotable: false },
178                    cx.attr_span,
179                ));
180            }
181        }),
182        (&[sym::rustc_const_unstable], template!(List: r#"feature = "name""#), |this, cx, args| {
183            reject_outside_std!(cx);
184            if !this.check_duplicate(cx)
185                && let Some((feature, level)) = parse_unstability(cx, args)
186            {
187                this.stability = Some((
188                    PartialConstStability { level, feature, promotable: false },
189                    cx.attr_span,
190                ));
191            }
192        }),
193        (&[sym::rustc_promotable], template!(Word), |this, cx, _| {
194            reject_outside_std!(cx);
195            this.promotable = true;
196        }),
197    ];
198
199    fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
200        if self.promotable {
201            if let Some((ref mut stab, _)) = self.stability {
202                stab.promotable = true;
203            } else {
204                cx.dcx()
205                    .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
206            }
207        }
208
209        let (stability, span) = self.stability?;
210
211        Some(AttributeKind::ConstStability { stability, span })
212    }
213}
214
215/// Tries to insert the value of a `key = value` meta item into an option.
216///
217/// Emits an error when either the option was already Some, or the arguments weren't of form
218/// `name = value`
219fn insert_value_into_option_or_error<S: Stage>(
220    cx: &AcceptContext<'_, '_, S>,
221    param: &MetaItemParser<'_>,
222    item: &mut Option<Symbol>,
223    name: Ident,
224) -> Option<()> {
225    if item.is_some() {
226        cx.duplicate_key(name.span, name.name);
227        None
228    } else if let Some(v) = param.args().name_value()
229        && let Some(s) = v.value_as_str()
230    {
231        *item = Some(s);
232        Some(())
233    } else {
234        cx.expected_name_value(param.span(), Some(name.name));
235        None
236    }
237}
238
239/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
240/// its stability information.
241pub(crate) fn parse_stability<S: Stage>(
242    cx: &AcceptContext<'_, '_, S>,
243    args: &ArgParser<'_>,
244) -> Option<(Symbol, StabilityLevel)> {
245    let mut feature = None;
246    let mut since = None;
247
248    for param in args.list()?.mixed() {
249        let param_span = param.span();
250        let Some(param) = param.meta_item() else {
251            cx.emit_err(session_diagnostics::UnsupportedLiteral {
252                span: param_span,
253                reason: UnsupportedLiteralReason::Generic,
254                is_bytestr: false,
255                start_point_span: cx.sess().source_map().start_point(param_span),
256            });
257            return None;
258        };
259
260        let word = param.path().word();
261        match word.map(|i| i.name) {
262            Some(sym::feature) => {
263                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
264            }
265            Some(sym::since) => {
266                insert_value_into_option_or_error(cx, &param, &mut since, word.unwrap())?
267            }
268            _ => {
269                cx.emit_err(session_diagnostics::UnknownMetaItem {
270                    span: param_span,
271                    item: param.path().to_string(),
272                    expected: &["feature", "since"],
273                });
274                return None;
275            }
276        }
277    }
278
279    let feature = match feature {
280        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
281        Some(_bad_feature) => {
282            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
283        }
284        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
285    };
286
287    let since = if let Some(since) = since {
288        if since.as_str() == VERSION_PLACEHOLDER {
289            StableSince::Current
290        } else if let Some(version) = parse_version(since) {
291            StableSince::Version(version)
292        } else {
293            cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
294            StableSince::Err
295        }
296    } else {
297        cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
298        StableSince::Err
299    };
300
301    match feature {
302        Ok(feature) => {
303            let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
304            Some((feature, level))
305        }
306        Err(ErrorGuaranteed { .. }) => None,
307    }
308}
309
310// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
311/// attribute, and return the feature name and its stability information.
312pub(crate) fn parse_unstability<S: Stage>(
313    cx: &AcceptContext<'_, '_, S>,
314    args: &ArgParser<'_>,
315) -> Option<(Symbol, StabilityLevel)> {
316    let mut feature = None;
317    let mut reason = None;
318    let mut issue = None;
319    let mut issue_num = None;
320    let mut is_soft = false;
321    let mut implied_by = None;
322    let mut old_name = None;
323    for param in args.list()?.mixed() {
324        let Some(param) = param.meta_item() else {
325            cx.emit_err(session_diagnostics::UnsupportedLiteral {
326                span: param.span(),
327                reason: UnsupportedLiteralReason::Generic,
328                is_bytestr: false,
329                start_point_span: cx.sess().source_map().start_point(param.span()),
330            });
331            return None;
332        };
333
334        let word = param.path().word();
335        match word.map(|i| i.name) {
336            Some(sym::feature) => {
337                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
338            }
339            Some(sym::reason) => {
340                insert_value_into_option_or_error(cx, &param, &mut reason, word.unwrap())?
341            }
342            Some(sym::issue) => {
343                insert_value_into_option_or_error(cx, &param, &mut issue, word.unwrap())?;
344
345                // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
346                // is a name/value pair string literal.
347                issue_num = match issue.unwrap().as_str() {
348                    "none" => None,
349                    issue_str => match issue_str.parse::<NonZero<u32>>() {
350                        Ok(num) => Some(num),
351                        Err(err) => {
352                            cx.emit_err(
353                                session_diagnostics::InvalidIssueString {
354                                    span: param.span(),
355                                    cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
356                                        param.args().name_value().unwrap().value_span,
357                                        err.kind(),
358                                    ),
359                                },
360                            );
361                            return None;
362                        }
363                    },
364                };
365            }
366            Some(sym::soft) => {
367                if let Err(span) = args.no_args() {
368                    cx.emit_err(session_diagnostics::SoftNoArgs { span });
369                }
370                is_soft = true;
371            }
372            Some(sym::implied_by) => {
373                insert_value_into_option_or_error(cx, &param, &mut implied_by, word.unwrap())?
374            }
375            Some(sym::old_name) => {
376                insert_value_into_option_or_error(cx, &param, &mut old_name, word.unwrap())?
377            }
378            _ => {
379                cx.emit_err(session_diagnostics::UnknownMetaItem {
380                    span: param.span(),
381                    item: param.path().to_string(),
382                    expected: &["feature", "reason", "issue", "soft", "implied_by", "old_name"],
383                });
384                return None;
385            }
386        }
387    }
388
389    let feature = match feature {
390        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
391        Some(_bad_feature) => {
392            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
393        }
394        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
395    };
396
397    let issue =
398        issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
399
400    match (feature, issue) {
401        (Ok(feature), Ok(_)) => {
402            let level = StabilityLevel::Unstable {
403                reason: UnstableReason::from_opt_reason(reason),
404                issue: issue_num,
405                is_soft,
406                implied_by,
407                old_name,
408            };
409            Some((feature, level))
410        }
411        (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
412    }
413}