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, 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 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 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#[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 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
233fn 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
257pub(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, ¶m, &mut feature, word.unwrap())?
287 }
288 Some(sym::since) => {
289 insert_value_into_option_or_error(cx, ¶m, &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
333pub(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, ¶m, &mut feature, word.unwrap())?
367 }
368 Some(sym::reason) => {
369 insert_value_into_option_or_error(cx, ¶m, &mut reason, word.unwrap())?
370 }
371 Some(sym::issue) => {
372 insert_value_into_option_or_error(cx, ¶m, &mut issue, word.unwrap())?;
373
374 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, ¶m, &mut implied_by, word.unwrap())?
403 }
404 Some(sym::old_name) => {
405 insert_value_into_option_or_error(cx, ¶m, &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}