Skip to main content

rustc_attr_parsing/attributes/
stability.rs

1use std::num::NonZero;
2
3use rustc_errors::ErrorGuaranteed;
4use rustc_feature::ACCEPTED_LANG_FEATURES;
5use rustc_hir::attrs::UnstableRemovedFeature;
6use rustc_hir::target::GenericParamKind;
7use rustc_hir::{
8    DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel,
9    StableSince, Target, UnstableReason, VERSION_PLACEHOLDER,
10};
11
12use super::prelude::*;
13use super::util::parse_version;
14use crate::session_diagnostics;
15
16macro_rules! reject_outside_std {
17    ($cx: ident) => {
18        // Emit errors for non-staged-api crates.
19        if !$cx.features().staged_api() {
20            $cx.emit_err(session_diagnostics::StabilityOutsideStd { span: $cx.attr_span });
21            return;
22        }
23    };
24}
25
26const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
27    Allow(Target::Fn),
28    Allow(Target::Struct),
29    Allow(Target::Enum),
30    Allow(Target::Union),
31    Allow(Target::Method(MethodKind::Inherent)),
32    Allow(Target::Method(MethodKind::Trait { body: false })),
33    Allow(Target::Method(MethodKind::Trait { body: true })),
34    Allow(Target::Method(MethodKind::TraitImpl)),
35    Allow(Target::Impl { of_trait: false }),
36    Allow(Target::Impl { of_trait: true }),
37    Allow(Target::MacroDef),
38    Allow(Target::Crate),
39    Allow(Target::Mod),
40    Allow(Target::Use), // FIXME I don't think this does anything?
41    Allow(Target::Const),
42    Allow(Target::AssocConst),
43    Allow(Target::AssocTy),
44    Allow(Target::Trait),
45    Allow(Target::TraitAlias),
46    Allow(Target::TyAlias),
47    Allow(Target::Variant),
48    Allow(Target::Field),
49    Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }),
50    Allow(Target::Static),
51    Allow(Target::ForeignFn),
52    Allow(Target::ForeignStatic),
53    Allow(Target::ExternCrate),
54]);
55
56#[derive(#[automatically_derived]
impl ::core::default::Default for StabilityParser {
    #[inline]
    fn default() -> StabilityParser {
        StabilityParser {
            allowed_through_unstable_modules: ::core::default::Default::default(),
            stability: ::core::default::Default::default(),
        }
    }
}Default)]
57pub(crate) struct StabilityParser {
58    allowed_through_unstable_modules: Option<Symbol>,
59    stability: Option<(Stability, Span)>,
60}
61
62impl StabilityParser {
63    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
64    fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
65        if let Some((_, _)) = self.stability {
66            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
67            true
68        } else {
69            false
70        }
71    }
72}
73
74impl<S: Stage> AttributeParser<S> for StabilityParser {
75    const ATTRIBUTES: AcceptMapping<Self, S> = &[
76        (
77            &[sym::stable],
78            ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name", since = "version""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name", since = "version""#]),
79            |this, cx, args| {
80                if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
81                if !this.check_duplicate(cx)
82                    && let Some((feature, level)) = parse_stability(cx, args)
83                {
84                    this.stability = Some((Stability { level, feature }, cx.attr_span));
85                }
86            },
87        ),
88        (
89            &[sym::unstable],
90            ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name", reason = "...", issue = "N""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
91            |this, cx, args| {
92                if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
93                if !this.check_duplicate(cx)
94                    && let Some((feature, level)) = parse_unstability(cx, args)
95                {
96                    this.stability = Some((Stability { level, feature }, cx.attr_span));
97                }
98            },
99        ),
100        (
101            &[sym::rustc_allowed_through_unstable_modules],
102            ::rustc_feature::AttributeTemplate {
    word: false,
    list: None,
    one_of: &[],
    name_value_str: Some(&["deprecation message"]),
    docs: None,
}template!(NameValueStr: "deprecation message"),
103            |this, cx, args| {
104                if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
105                let Some(nv) = args.name_value() else {
106                    let attr_span = cx.attr_span;
107                    cx.adcx().expected_name_value(attr_span, None);
108                    return;
109                };
110                let Some(value_str) = nv.value_as_str() else {
111                    cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
112                    return;
113                };
114                this.allowed_through_unstable_modules = Some(value_str);
115            },
116        ),
117    ];
118    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
119
120    fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
121        if let Some(atum) = self.allowed_through_unstable_modules {
122            if let Some((
123                Stability {
124                    level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. },
125                    ..
126                },
127                _,
128            )) = self.stability
129            {
130                *allowed_through_unstable_modules = Some(atum);
131            } else {
132                cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing {
133                    span: cx.target_span,
134                });
135            }
136        }
137
138        if let Some((Stability { level: StabilityLevel::Stable { .. }, .. }, _)) = self.stability {
139            for other_attr in cx.all_attrs {
140                if other_attr.word_is(sym::unstable_feature_bound) {
141                    cx.emit_err(session_diagnostics::UnstableFeatureBoundIncompatibleStability {
142                        span: cx.target_span,
143                    });
144                }
145            }
146        }
147
148        let (stability, span) = self.stability?;
149
150        Some(AttributeKind::Stability { stability, span })
151    }
152}
153
154// FIXME(jdonszelmann) change to Single
155#[derive(#[automatically_derived]
impl ::core::default::Default for BodyStabilityParser {
    #[inline]
    fn default() -> BodyStabilityParser {
        BodyStabilityParser { stability: ::core::default::Default::default() }
    }
}Default)]
156pub(crate) struct BodyStabilityParser {
157    stability: Option<(DefaultBodyStability, Span)>,
158}
159
160impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
161    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
162        &[sym::rustc_default_body_unstable],
163        ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name", reason = "...", issue = "N""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
164        |this, cx, args| {
165            if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
166            if this.stability.is_some() {
167                cx.dcx()
168                    .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
169            } else if let Some((feature, level)) = parse_unstability(cx, args) {
170                this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
171            }
172        },
173    )];
174    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
175
176    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
177        let (stability, span) = self.stability?;
178
179        Some(AttributeKind::RustcBodyStability { stability, span })
180    }
181}
182
183pub(crate) struct RustcConstStableIndirectParser;
184impl<S: Stage> NoArgsAttributeParser<S> for RustcConstStableIndirectParser {
185    const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect];
186    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
187    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
188        Allow(Target::Fn),
189        Allow(Target::Method(MethodKind::Inherent)),
190    ]);
191    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConstStableIndirect;
192}
193
194#[derive(#[automatically_derived]
impl ::core::default::Default for ConstStabilityParser {
    #[inline]
    fn default() -> ConstStabilityParser {
        ConstStabilityParser {
            promotable: ::core::default::Default::default(),
            stability: ::core::default::Default::default(),
        }
    }
}Default)]
195pub(crate) struct ConstStabilityParser {
196    promotable: bool,
197    stability: Option<(PartialConstStability, Span)>,
198}
199
200impl ConstStabilityParser {
201    /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate.
202    fn check_duplicate<S: Stage>(&self, cx: &AcceptContext<'_, '_, S>) -> bool {
203        if let Some((_, _)) = self.stability {
204            cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span });
205            true
206        } else {
207            false
208        }
209    }
210}
211
212impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
213    const ATTRIBUTES: AcceptMapping<Self, S> = &[
214        (
215            &[sym::rustc_const_stable],
216            ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name""#]),
217            |this, cx, args| {
218                if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
219
220                if !this.check_duplicate(cx)
221                    && let Some((feature, level)) = parse_stability(cx, args)
222                {
223                    this.stability = Some((
224                        PartialConstStability { level, feature, promotable: false },
225                        cx.attr_span,
226                    ));
227                }
228            },
229        ),
230        (
231            &[sym::rustc_const_unstable],
232            ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name""#]),
233            |this, cx, args| {
234                if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
235                if !this.check_duplicate(cx)
236                    && let Some((feature, level)) = parse_unstability(cx, args)
237                {
238                    this.stability = Some((
239                        PartialConstStability { level, feature, promotable: false },
240                        cx.attr_span,
241                    ));
242                }
243            },
244        ),
245        (&[sym::rustc_promotable], ::rustc_feature::AttributeTemplate {
    word: true,
    list: None,
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(Word), |this, cx, _| {
246            if !cx.features().staged_api() {
    cx.emit_err(session_diagnostics::StabilityOutsideStd {
            span: cx.attr_span,
        });
    return;
};reject_outside_std!(cx);
247            this.promotable = true;
248        }),
249    ];
250    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
251        Allow(Target::Fn),
252        Allow(Target::Method(MethodKind::Inherent)),
253        Allow(Target::Method(MethodKind::TraitImpl)),
254        Allow(Target::Method(MethodKind::Trait { body: true })),
255        Allow(Target::Impl { of_trait: false }),
256        Allow(Target::Impl { of_trait: true }),
257        Allow(Target::Use), // FIXME I don't think this does anything?
258        Allow(Target::Const),
259        Allow(Target::AssocConst),
260        Allow(Target::Trait),
261        Allow(Target::Static),
262        Allow(Target::Crate),
263    ]);
264
265    fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
266        if self.promotable {
267            if let Some((ref mut stab, _)) = self.stability {
268                stab.promotable = true;
269            } else {
270                cx.dcx()
271                    .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span });
272            }
273        }
274
275        let (stability, span) = self.stability?;
276
277        Some(AttributeKind::RustcConstStability { stability, span })
278    }
279}
280
281/// Tries to insert the value of a `key = value` meta item into an option.
282///
283/// Emits an error when either the option was already Some, or the arguments weren't of form
284/// `name = value`
285fn insert_value_into_option_or_error<S: Stage>(
286    cx: &mut AcceptContext<'_, '_, S>,
287    param: &MetaItemParser,
288    item: &mut Option<Symbol>,
289    name: Ident,
290) -> Option<()> {
291    if item.is_some() {
292        cx.adcx().duplicate_key(name.span, name.name);
293        None
294    } else if let Some(v) = param.args().name_value()
295        && let Some(s) = v.value_as_str()
296    {
297        *item = Some(s);
298        Some(())
299    } else {
300        cx.adcx().expected_name_value(param.span(), Some(name.name));
301        None
302    }
303}
304
305/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
306/// its stability information.
307pub(crate) fn parse_stability<S: Stage>(
308    cx: &mut AcceptContext<'_, '_, S>,
309    args: &ArgParser,
310) -> Option<(Symbol, StabilityLevel)> {
311    let mut feature = None;
312    let mut since = None;
313
314    let ArgParser::List(list) = args else {
315        let attr_span = cx.attr_span;
316        cx.adcx().expected_list(attr_span, args);
317        return None;
318    };
319
320    for param in list.mixed() {
321        let param_span = param.span();
322        let Some(param) = param.meta_item() else {
323            cx.adcx().expected_not_literal(param.span());
324            return None;
325        };
326
327        let word = param.path().word();
328        match word.map(|i| i.name) {
329            Some(sym::feature) => {
330                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
331            }
332            Some(sym::since) => {
333                insert_value_into_option_or_error(cx, &param, &mut since, word.unwrap())?
334            }
335            _ => {
336                cx.adcx().expected_specific_argument(param_span, &[sym::feature, sym::since]);
337                return None;
338            }
339        }
340    }
341
342    let feature = match feature {
343        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
344        Some(_bad_feature) => {
345            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
346        }
347        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
348    };
349
350    let since = if let Some(since) = since {
351        if since.as_str() == VERSION_PLACEHOLDER {
352            StableSince::Current
353        } else if let Some(version) = parse_version(since) {
354            StableSince::Version(version)
355        } else {
356            let err = cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
357            StableSince::Err(err)
358        }
359    } else {
360        let err = cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
361        StableSince::Err(err)
362    };
363
364    match feature {
365        Ok(feature) => {
366            let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
367            Some((feature, level))
368        }
369        Err(ErrorGuaranteed { .. }) => None,
370    }
371}
372
373/// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
374/// attribute, and return the feature name and its stability information.
375pub(crate) fn parse_unstability<S: Stage>(
376    cx: &mut AcceptContext<'_, '_, S>,
377    args: &ArgParser,
378) -> Option<(Symbol, StabilityLevel)> {
379    let mut feature = None;
380    let mut reason = None;
381    let mut issue = None;
382    let mut issue_num = None;
383    let mut implied_by = None;
384    let mut old_name = None;
385
386    let ArgParser::List(list) = args else {
387        let attr_span = cx.attr_span;
388        cx.adcx().expected_list(attr_span, args);
389        return None;
390    };
391
392    for param in list.mixed() {
393        let Some(param) = param.meta_item() else {
394            cx.adcx().expected_not_literal(param.span());
395            return None;
396        };
397
398        let word = param.path().word();
399        match word.map(|i| i.name) {
400            Some(sym::feature) => {
401                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
402            }
403            Some(sym::reason) => {
404                insert_value_into_option_or_error(cx, &param, &mut reason, word.unwrap())?
405            }
406            Some(sym::issue) => {
407                insert_value_into_option_or_error(cx, &param, &mut issue, word.unwrap())?;
408
409                // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
410                // is a name/value pair string literal.
411                issue_num = match issue.unwrap().as_str() {
412                    "none" => None,
413                    issue_str => match issue_str.parse::<NonZero<u32>>() {
414                        Ok(num) => Some(num),
415                        Err(err) => {
416                            cx.emit_err(
417                                session_diagnostics::InvalidIssueString {
418                                    span: param.span(),
419                                    cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind(
420                                        param.args().name_value().unwrap().value_span,
421                                        err.kind(),
422                                    ),
423                                },
424                            );
425                            return None;
426                        }
427                    },
428                };
429            }
430            Some(sym::implied_by) => {
431                insert_value_into_option_or_error(cx, &param, &mut implied_by, word.unwrap())?
432            }
433            Some(sym::old_name) => {
434                insert_value_into_option_or_error(cx, &param, &mut old_name, word.unwrap())?
435            }
436            _ => {
437                cx.adcx().expected_specific_argument(
438                    param.span(),
439                    &[sym::feature, sym::reason, sym::issue, sym::implied_by, sym::old_name],
440                );
441                return None;
442            }
443        }
444    }
445
446    let feature = match feature {
447        Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature),
448        Some(_bad_feature) => {
449            Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span }))
450        }
451        None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })),
452    };
453
454    let issue =
455        issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span }));
456
457    match (feature, issue) {
458        (Ok(feature), Ok(_)) => {
459            // Stable *language* features shouldn't be used as unstable library features.
460            // (Not doing this for stable library features is checked by tidy.)
461            if ACCEPTED_LANG_FEATURES.iter().any(|f| f.name == feature) {
462                cx.emit_err(session_diagnostics::UnstableAttrForAlreadyStableFeature {
463                    attr_span: cx.attr_span,
464                    item_span: cx.target_span,
465                });
466                return None;
467            }
468
469            let level = StabilityLevel::Unstable {
470                reason: UnstableReason::from_opt_reason(reason),
471                issue: issue_num,
472                implied_by,
473                old_name,
474            };
475            Some((feature, level))
476        }
477        (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
478    }
479}
480
481pub(crate) struct UnstableRemovedParser;
482
483impl<S: Stage> CombineAttributeParser<S> for UnstableRemovedParser {
484    type Item = UnstableRemovedFeature;
485    const PATH: &[Symbol] = &[sym::unstable_removed];
486    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]);
487    const TEMPLATE: AttributeTemplate =
488        ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&[r#"feature = "name", reason = "...", link = "...", since = "version""#]),
    one_of: &[],
    name_value_str: None,
    docs: None,
}template!(List: &[r#"feature = "name", reason = "...", link = "...", since = "version""#]);
489
490    const CONVERT: ConvertFn<Self::Item> = |items, _| AttributeKind::UnstableRemoved(items);
491
492    fn extend(
493        cx: &mut AcceptContext<'_, '_, S>,
494        args: &ArgParser,
495    ) -> impl IntoIterator<Item = Self::Item> {
496        let mut feature = None;
497        let mut reason = None;
498        let mut link = None;
499        let mut since = None;
500
501        if !cx.features().staged_api() {
502            cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span });
503            return None;
504        }
505
506        let ArgParser::List(list) = args else {
507            let attr_span = cx.attr_span;
508            cx.adcx().expected_list(attr_span, args);
509            return None;
510        };
511
512        for param in list.mixed() {
513            let Some(param) = param.meta_item() else {
514                cx.adcx().expected_not_literal(param.span());
515                return None;
516            };
517
518            let Some(word) = param.path().word() else {
519                cx.adcx().expected_specific_argument(
520                    param.span(),
521                    &[sym::feature, sym::reason, sym::link, sym::since],
522                );
523                return None;
524            };
525            match word.name {
526                sym::feature => insert_value_into_option_or_error(cx, &param, &mut feature, word)?,
527                sym::since => insert_value_into_option_or_error(cx, &param, &mut since, word)?,
528                sym::reason => insert_value_into_option_or_error(cx, &param, &mut reason, word)?,
529                sym::link => insert_value_into_option_or_error(cx, &param, &mut link, word)?,
530                _ => {
531                    cx.adcx().expected_specific_argument(
532                        param.span(),
533                        &[sym::feature, sym::reason, sym::link, sym::since],
534                    );
535                    return None;
536                }
537            }
538        }
539
540        // Check all the arguments are present
541        let Some(feature) = feature else {
542            cx.adcx().missing_name_value(list.span, sym::feature);
543            return None;
544        };
545        let Some(reason) = reason else {
546            cx.adcx().missing_name_value(list.span, sym::reason);
547            return None;
548        };
549        let Some(link) = link else {
550            cx.adcx().missing_name_value(list.span, sym::link);
551            return None;
552        };
553        let Some(since) = since else {
554            cx.adcx().missing_name_value(list.span, sym::since);
555            return None;
556        };
557
558        let Some(version) = parse_version(since) else {
559            cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
560            return None;
561        };
562
563        Some(UnstableRemovedFeature { feature, reason, link, since: version })
564    }
565}