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