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