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