rustc_attr_parsing/attributes/
stability.rs

1//! Parsing and validation of builtin attributes
2
3use std::num::NonZero;
4
5use rustc_ast::MetaItem;
6use rustc_ast::attr::AttributeExt;
7use rustc_ast_pretty::pprust;
8use rustc_attr_data_structures::{
9    ConstStability, DefaultBodyStability, Stability, StabilityLevel, StableSince, UnstableReason,
10    VERSION_PLACEHOLDER,
11};
12use rustc_errors::ErrorGuaranteed;
13use rustc_session::Session;
14use rustc_span::{Span, Symbol, kw, sym};
15
16use crate::attributes::util::UnsupportedLiteralReason;
17use crate::{parse_version, session_diagnostics};
18
19/// Collects stability info from `stable`/`unstable`/`rustc_allowed_through_unstable_modules`
20/// attributes in `attrs`. Returns `None` if no stability attributes are found.
21pub fn find_stability(
22    sess: &Session,
23    attrs: &[impl AttributeExt],
24    item_sp: Span,
25) -> Option<(Stability, Span)> {
26    let mut stab: Option<(Stability, Span)> = None;
27    let mut allowed_through_unstable_modules = None;
28
29    for attr in attrs {
30        match attr.name_or_empty() {
31            sym::rustc_allowed_through_unstable_modules => {
32                // The value is mandatory, but avoid ICEs in case such code reaches this function.
33                allowed_through_unstable_modules = Some(attr.value_str().unwrap_or_else(|| {
34                    sess.dcx().span_delayed_bug(
35                        item_sp,
36                        "`#[rustc_allowed_through_unstable_modules]` without deprecation message",
37                    );
38                    kw::Empty
39                }))
40            }
41            sym::unstable => {
42                if stab.is_some() {
43                    sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
44                        span: attr.span(),
45                    });
46                    break;
47                }
48
49                if let Some((feature, level)) = parse_unstability(sess, attr) {
50                    stab = Some((Stability { level, feature }, attr.span()));
51                }
52            }
53            sym::stable => {
54                if stab.is_some() {
55                    sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
56                        span: attr.span(),
57                    });
58                    break;
59                }
60                if let Some((feature, level)) = parse_stability(sess, attr) {
61                    stab = Some((Stability { level, feature }, attr.span()));
62                }
63            }
64            _ => {}
65        }
66    }
67
68    if let Some(allowed_through_unstable_modules) = allowed_through_unstable_modules {
69        match &mut stab {
70            Some((
71                Stability {
72                    level: StabilityLevel::Stable { allowed_through_unstable_modules: in_stab, .. },
73                    ..
74                },
75                _,
76            )) => *in_stab = Some(allowed_through_unstable_modules),
77            _ => {
78                sess.dcx()
79                    .emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp });
80            }
81        }
82    }
83
84    stab
85}
86
87/// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
88/// attributes in `attrs`. Returns `None` if no stability attributes are found.
89pub fn find_const_stability(
90    sess: &Session,
91    attrs: &[impl AttributeExt],
92    item_sp: Span,
93) -> Option<(ConstStability, Span)> {
94    let mut const_stab: Option<(ConstStability, Span)> = None;
95    let mut promotable = false;
96    let mut const_stable_indirect = false;
97
98    for attr in attrs {
99        match attr.name_or_empty() {
100            sym::rustc_promotable => promotable = true,
101            sym::rustc_const_stable_indirect => const_stable_indirect = true,
102            sym::rustc_const_unstable => {
103                if const_stab.is_some() {
104                    sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
105                        span: attr.span(),
106                    });
107                    break;
108                }
109
110                if let Some((feature, level)) = parse_unstability(sess, attr) {
111                    const_stab = Some((
112                        ConstStability {
113                            level,
114                            feature,
115                            const_stable_indirect: false,
116                            promotable: false,
117                        },
118                        attr.span(),
119                    ));
120                }
121            }
122            sym::rustc_const_stable => {
123                if const_stab.is_some() {
124                    sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels {
125                        span: attr.span(),
126                    });
127                    break;
128                }
129                if let Some((feature, level)) = parse_stability(sess, attr) {
130                    const_stab = Some((
131                        ConstStability {
132                            level,
133                            feature,
134                            const_stable_indirect: false,
135                            promotable: false,
136                        },
137                        attr.span(),
138                    ));
139                }
140            }
141            _ => {}
142        }
143    }
144
145    // Merge promotable and const_stable_indirect into stability info
146    if promotable {
147        match &mut const_stab {
148            Some((stab, _)) => stab.promotable = promotable,
149            _ => {
150                _ = sess
151                    .dcx()
152                    .emit_err(session_diagnostics::RustcPromotablePairing { span: item_sp })
153            }
154        }
155    }
156    if const_stable_indirect {
157        match &mut const_stab {
158            Some((stab, _)) => {
159                if stab.is_const_unstable() {
160                    stab.const_stable_indirect = true;
161                } else {
162                    _ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing {
163                        span: item_sp,
164                    })
165                }
166            }
167            _ => {
168                // This function has no const stability attribute, but has `const_stable_indirect`.
169                // We ignore that; unmarked functions are subject to recursive const stability
170                // checks by default so we do carry out the user's intent.
171            }
172        }
173    }
174
175    const_stab
176}
177
178/// Calculates the const stability for a const function in a `-Zforce-unstable-if-unmarked` crate
179/// without the `staged_api` feature.
180pub fn unmarked_crate_const_stab(
181    _sess: &Session,
182    attrs: &[impl AttributeExt],
183    regular_stab: Stability,
184) -> ConstStability {
185    assert!(regular_stab.level.is_unstable());
186    // The only attribute that matters here is `rustc_const_stable_indirect`.
187    // We enforce recursive const stability rules for those functions.
188    let const_stable_indirect =
189        attrs.iter().any(|a| a.name_or_empty() == sym::rustc_const_stable_indirect);
190    ConstStability {
191        feature: regular_stab.feature,
192        const_stable_indirect,
193        promotable: false,
194        level: regular_stab.level,
195    }
196}
197
198/// Collects stability info from `rustc_default_body_unstable` attributes in `attrs`.
199/// Returns `None` if no stability attributes are found.
200pub fn find_body_stability(
201    sess: &Session,
202    attrs: &[impl AttributeExt],
203) -> Option<(DefaultBodyStability, Span)> {
204    let mut body_stab: Option<(DefaultBodyStability, Span)> = None;
205
206    for attr in attrs {
207        if attr.has_name(sym::rustc_default_body_unstable) {
208            if body_stab.is_some() {
209                sess.dcx()
210                    .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span() });
211                break;
212            }
213
214            if let Some((feature, level)) = parse_unstability(sess, attr) {
215                body_stab = Some((DefaultBodyStability { level, feature }, attr.span()));
216            }
217        }
218    }
219
220    body_stab
221}
222
223fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option<Symbol>) -> Option<()> {
224    if item.is_some() {
225        sess.dcx().emit_err(session_diagnostics::MultipleItem {
226            span: meta.span,
227            item: pprust::path_to_string(&meta.path),
228        });
229        None
230    } else if let Some(v) = meta.value_str() {
231        *item = Some(v);
232        Some(())
233    } else {
234        sess.dcx().emit_err(session_diagnostics::IncorrectMetaItem { span: meta.span });
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.
241fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, StabilityLevel)> {
242    let metas = attr.meta_item_list()?;
243
244    let mut feature = None;
245    let mut since = None;
246    for meta in metas {
247        let Some(mi) = meta.meta_item() else {
248            sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral {
249                span: meta.span(),
250                reason: UnsupportedLiteralReason::Generic,
251                is_bytestr: false,
252                start_point_span: sess.source_map().start_point(meta.span()),
253            });
254            return None;
255        };
256
257        match mi.name_or_empty() {
258            sym::feature => insert_or_error(sess, mi, &mut feature)?,
259            sym::since => insert_or_error(sess, mi, &mut since)?,
260            _ => {
261                sess.dcx().emit_err(session_diagnostics::UnknownMetaItem {
262                    span: meta.span(),
263                    item: pprust::path_to_string(&mi.path),
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(sess.dcx().emit_err(session_diagnostics::NonIdentFeature { span: attr.span() }))
275        }
276        None => Err(sess.dcx().emit_err(session_diagnostics::MissingFeature { span: 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            sess.dcx().emit_err(session_diagnostics::InvalidSince { span: attr.span() });
286            StableSince::Err
287        }
288    } else {
289        sess.dcx().emit_err(session_diagnostics::MissingSince { span: 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.
304fn parse_unstability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, StabilityLevel)> {
305    let metas = attr.meta_item_list()?;
306
307    let mut feature = None;
308    let mut reason = None;
309    let mut issue = None;
310    let mut issue_num = None;
311    let mut is_soft = false;
312    let mut implied_by = None;
313    for meta in metas {
314        let Some(mi) = meta.meta_item() else {
315            sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral {
316                span: meta.span(),
317                reason: UnsupportedLiteralReason::Generic,
318                is_bytestr: false,
319                start_point_span: sess.source_map().start_point(meta.span()),
320            });
321            return None;
322        };
323
324        match mi.name_or_empty() {
325            sym::feature => insert_or_error(sess, mi, &mut feature)?,
326            sym::reason => insert_or_error(sess, mi, &mut reason)?,
327            sym::issue => {
328                insert_or_error(sess, mi, &mut issue)?;
329
330                // These unwraps are safe because `insert_or_error` ensures the meta item
331                // is a name/value pair string literal.
332                issue_num = match issue.unwrap().as_str() {
333                    "none" => None,
334                    issue => match issue.parse::<NonZero<u32>>() {
335                        Ok(num) => Some(num),
336                        Err(err) => {
337                            sess.dcx().emit_err(
338                                session_diagnostics::InvalidIssueString {
339                                    span: mi.span,
340                                    cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
341                                        mi.name_value_literal_span().unwrap(),
342                                        err.kind(),
343                                    ),
344                                },
345                            );
346                            return None;
347                        }
348                    },
349                };
350            }
351            sym::soft => {
352                if !mi.is_word() {
353                    sess.dcx().emit_err(session_diagnostics::SoftNoArgs { span: mi.span });
354                }
355                is_soft = true;
356            }
357            sym::implied_by => insert_or_error(sess, mi, &mut implied_by)?,
358            _ => {
359                sess.dcx().emit_err(session_diagnostics::UnknownMetaItem {
360                    span: meta.span(),
361                    item: pprust::path_to_string(&mi.path),
362                    expected: &["feature", "reason", "issue", "soft", "implied_by"],
363                });
364                return None;
365            }
366        }
367    }
368
369    let feature = match feature {
370        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
371        Some(_bad_feature) => {
372            Err(sess.dcx().emit_err(session_diagnostics::NonIdentFeature { span: attr.span() }))
373        }
374        None => Err(sess.dcx().emit_err(session_diagnostics::MissingFeature { span: attr.span() })),
375    };
376
377    let issue = issue.ok_or_else(|| {
378        sess.dcx().emit_err(session_diagnostics::MissingIssue { span: attr.span() })
379    });
380
381    match (feature, issue) {
382        (Ok(feature), Ok(_)) => {
383            let level = StabilityLevel::Unstable {
384                reason: UnstableReason::from_opt_reason(reason),
385                issue: issue_num,
386                is_soft,
387                implied_by,
388            };
389            Some((feature, level))
390        }
391        (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
392    }
393}