rustc_attr_parsing/attributes/
stability.rs
1use 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
19pub 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 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
87pub 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 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 }
172 }
173 }
174
175 const_stab
176}
177
178pub 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 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
198pub 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
239fn 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
302fn 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 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}