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