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