rustc_attr_parsing/attributes/
stability.rs

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