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