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