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 let (stability, span) = self.stability?;
102
103 Some(AttributeKind::Stability { stability, span })
104 }
105}
106
107#[derive(Default)]
109pub(crate) struct BodyStabilityParser {
110 stability: Option<(DefaultBodyStability, Span)>,
111}
112
113impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
114 const ATTRIBUTES: AcceptMapping<Self, S> = &[(
115 &[sym::rustc_default_body_unstable],
116 template!(List: r#"feature = "name", reason = "...", issue = "N""#),
117 |this, cx, args| {
118 reject_outside_std!(cx);
119 if this.stability.is_some() {
120 cx.dcx()
121 .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
122 } else if let Some((feature, level)) = parse_unstability(cx, args) {
123 this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
124 }
125 },
126 )];
127
128 fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
129 let (stability, span) = self.stability?;
130
131 Some(AttributeKind::BodyStability { stability, span })
132 }
133}
134
135pub(crate) struct ConstStabilityIndirectParser;
136impl<S: Stage> NoArgsAttributeParser<S> for ConstStabilityIndirectParser {
137 const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect];
138 const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
139 const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ConstStabilityIndirect;
140}
141
142#[derive(Default)]
143pub(crate) struct ConstStabilityParser {
144 promotable: bool,
145 stability: Option<(PartialConstStability, Span)>,
146}
147
148impl ConstStabilityParser {
149 fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
151 if let Some((_, _)) = self.stability {
152 cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
153 true
154 } else {
155 false
156 }
157 }
158}
159
160impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
161 const ATTRIBUTES: AcceptMapping<Self, S> = &[
162 (&[sym::rustc_const_stable], template!(List: r#"feature = "name""#), |this, cx, args| {
163 reject_outside_std!(cx);
164
165 if !this.check_duplicate(cx)
166 && let Some((feature, level)) = parse_stability(cx, args)
167 {
168 this.stability = Some((
169 PartialConstStability { level, feature, promotable: false },
170 cx.attr_span,
171 ));
172 }
173 }),
174 (&[sym::rustc_const_unstable], template!(List: r#"feature = "name""#), |this, cx, args| {
175 reject_outside_std!(cx);
176 if !this.check_duplicate(cx)
177 && let Some((feature, level)) = parse_unstability(cx, args)
178 {
179 this.stability = Some((
180 PartialConstStability { level, feature, promotable: false },
181 cx.attr_span,
182 ));
183 }
184 }),
185 (&[sym::rustc_promotable], template!(Word), |this, cx, _| {
186 reject_outside_std!(cx);
187 this.promotable = true;
188 }),
189 ];
190
191 fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
192 if self.promotable {
193 if let Some((ref mut stab, _)) = self.stability {
194 stab.promotable = true;
195 } else {
196 cx.dcx()
197 .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
198 }
199 }
200
201 let (stability, span) = self.stability?;
202
203 Some(AttributeKind::ConstStability { stability, span })
204 }
205}
206
207fn insert_value_into_option_or_error<S: Stage>(
212 cx: &AcceptContext<'_, '_, S>,
213 param: &MetaItemParser<'_>,
214 item: &mut Option<Symbol>,
215 name: Ident,
216) -> Option<()> {
217 if item.is_some() {
218 cx.duplicate_key(name.span, name.name);
219 None
220 } else if let Some(v) = param.args().name_value()
221 && let Some(s) = v.value_as_str()
222 {
223 *item = Some(s);
224 Some(())
225 } else {
226 cx.expected_name_value(param.span(), Some(name.name));
227 None
228 }
229}
230
231pub(crate) fn parse_stability<S: Stage>(
234 cx: &AcceptContext<'_, '_, S>,
235 args: &ArgParser<'_>,
236) -> Option<(Symbol, StabilityLevel)> {
237 let mut feature = None;
238 let mut since = None;
239
240 for param in args.list()?.mixed() {
241 let param_span = param.span();
242 let Some(param) = param.meta_item() else {
243 cx.emit_err(session_diagnostics::UnsupportedLiteral {
244 span: param_span,
245 reason: UnsupportedLiteralReason::Generic,
246 is_bytestr: false,
247 start_point_span: cx.sess().source_map().start_point(param_span),
248 });
249 return None;
250 };
251
252 let word = param.path().word();
253 match word.map(|i| i.name) {
254 Some(sym::feature) => {
255 insert_value_into_option_or_error(cx, ¶m, &mut feature, word.unwrap())?
256 }
257 Some(sym::since) => {
258 insert_value_into_option_or_error(cx, ¶m, &mut since, word.unwrap())?
259 }
260 _ => {
261 cx.emit_err(session_diagnostics::UnknownMetaItem {
262 span: param_span,
263 item: param.path().to_string(),
264 expected: &["feature", "since"],
265 });
266 return None;
267 }
268 }
269 }
270
271 let feature = match feature {
272 Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
273 Some(_bad_feature) => {
274 Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
275 }
276 None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
277 };
278
279 let since = if let Some(since) = since {
280 if since.as_str() == VERSION_PLACEHOLDER {
281 StableSince::Current
282 } else if let Some(version) = parse_version(since) {
283 StableSince::Version(version)
284 } else {
285 cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
286 StableSince::Err
287 }
288 } else {
289 cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
290 StableSince::Err
291 };
292
293 match feature {
294 Ok(feature) => {
295 let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
296 Some((feature, level))
297 }
298 Err(ErrorGuaranteed { .. }) => None,
299 }
300}
301
302pub(crate) fn parse_unstability<S: Stage>(
305 cx: &AcceptContext<'_, '_, S>,
306 args: &ArgParser<'_>,
307) -> Option<(Symbol, StabilityLevel)> {
308 let mut feature = None;
309 let mut reason = None;
310 let mut issue = None;
311 let mut issue_num = None;
312 let mut is_soft = false;
313 let mut implied_by = None;
314 let mut old_name = None;
315 for param in args.list()?.mixed() {
316 let Some(param) = param.meta_item() else {
317 cx.emit_err(session_diagnostics::UnsupportedLiteral {
318 span: param.span(),
319 reason: UnsupportedLiteralReason::Generic,
320 is_bytestr: false,
321 start_point_span: cx.sess().source_map().start_point(param.span()),
322 });
323 return None;
324 };
325
326 let word = param.path().word();
327 match word.map(|i| i.name) {
328 Some(sym::feature) => {
329 insert_value_into_option_or_error(cx, ¶m, &mut feature, word.unwrap())?
330 }
331 Some(sym::reason) => {
332 insert_value_into_option_or_error(cx, ¶m, &mut reason, word.unwrap())?
333 }
334 Some(sym::issue) => {
335 insert_value_into_option_or_error(cx, ¶m, &mut issue, word.unwrap())?;
336
337 issue_num = match issue.unwrap().as_str() {
340 "none" => None,
341 issue_str => match issue_str.parse::<NonZero<u32>>() {
342 Ok(num) => Some(num),
343 Err(err) => {
344 cx.emit_err(
345 session_diagnostics::InvalidIssueString {
346 span: param.span(),
347 cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
348 param.args().name_value().unwrap().value_span,
349 err.kind(),
350 ),
351 },
352 );
353 return None;
354 }
355 },
356 };
357 }
358 Some(sym::soft) => {
359 if let Err(span) = args.no_args() {
360 cx.emit_err(session_diagnostics::SoftNoArgs { span });
361 }
362 is_soft = true;
363 }
364 Some(sym::implied_by) => {
365 insert_value_into_option_or_error(cx, ¶m, &mut implied_by, word.unwrap())?
366 }
367 Some(sym::old_name) => {
368 insert_value_into_option_or_error(cx, ¶m, &mut old_name, word.unwrap())?
369 }
370 _ => {
371 cx.emit_err(session_diagnostics::UnknownMetaItem {
372 span: param.span(),
373 item: param.path().to_string(),
374 expected: &["feature", "reason", "issue", "soft", "implied_by", "old_name"],
375 });
376 return None;
377 }
378 }
379 }
380
381 let feature = match feature {
382 Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
383 Some(_bad_feature) => {
384 Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
385 }
386 None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
387 };
388
389 let issue =
390 issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
391
392 match (feature, issue) {
393 (Ok(feature), Ok(_)) => {
394 let level = StabilityLevel::Unstable {
395 reason: UnstableReason::from_opt_reason(reason),
396 issue: issue_num,
397 is_soft,
398 implied_by,
399 old_name,
400 };
401 Some((feature, level))
402 }
403 (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
404 }
405}