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