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