Skip to main content

rustc_attr_parsing/attributes/
deprecation.rs

1use rustc_ast::LitKind;
2use rustc_hir::attrs::{DeprecatedSince, Deprecation};
3use rustc_hir::{RustcVersion, VERSION_PLACEHOLDER};
4
5use super::prelude::*;
6use super::util::parse_version;
7use crate::session_diagnostics::{
8    DeprecatedItemSuggestion, InvalidSince, MissingNote, MissingSince,
9};
10
11fn get(
12    cx: &mut AcceptContext<'_, '_>,
13    name: Symbol,
14    param_span: Span,
15    arg: &ArgParser,
16    item: Option<Symbol>,
17) -> Option<Ident> {
18    if item.is_some() {
19        cx.adcx().duplicate_key(param_span, name);
20        return None;
21    }
22    let v = cx.expect_name_value(arg, param_span, Some(name))?;
23    if let Some(value_str) = v.value_as_ident() {
24        Some(value_str)
25    } else {
26        cx.adcx().expected_string_literal(v.value_span, Some(&v.value_as_lit()));
27        None
28    }
29}
30
31pub(crate) struct DeprecatedParser;
32impl SingleAttributeParser for DeprecatedParser {
33    const PATH: &[Symbol] = &[sym::deprecated];
34    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
35        Allow(Target::Fn),
36        Allow(Target::Mod),
37        Allow(Target::Struct),
38        Allow(Target::Enum),
39        Allow(Target::Union),
40        Allow(Target::Const),
41        Allow(Target::Static),
42        Allow(Target::MacroDef),
43        Allow(Target::Method(MethodKind::Inherent)),
44        Allow(Target::Method(MethodKind::Trait { body: false })),
45        Allow(Target::Method(MethodKind::Trait { body: true })),
46        Allow(Target::TyAlias),
47        Allow(Target::Use),
48        Allow(Target::ForeignFn),
49        Allow(Target::ForeignStatic),
50        Allow(Target::ForeignTy),
51        Allow(Target::Field),
52        Allow(Target::Trait),
53        Allow(Target::AssocTy),
54        Allow(Target::AssocConst),
55        Allow(Target::Variant),
56        Allow(Target::Impl { of_trait: false }),
57        Allow(Target::Crate),
58        Error(Target::WherePredicate),
59    ]);
60    const TEMPLATE: AttributeTemplate = ::rustc_feature::AttributeTemplate {
    word: true,
    list: Some(&[r#"since = "version""#, r#"note = "reason""#,
                    r#"since = "version", note = "reason""#]),
    one_of: &[],
    name_value_str: Some(&["reason"]),
    docs: None,
}template!(
61        Word,
62        List: &[r#"since = "version""#, r#"note = "reason""#, r#"since = "version", note = "reason""#],
63        NameValueStr: "reason"
64    );
65
66    fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<AttributeKind> {
67        let features = cx.features();
68
69        let mut since = None;
70        let mut note: Option<Ident> = None;
71        let mut suggestion = None;
72
73        let is_rustc = features.staged_api();
74
75        match args {
76            ArgParser::NoArgs => {
77                // ok
78            }
79            ArgParser::List(list) => {
80                // If the argument list contains a single string literal:
81                // check whether it may be a version and suggest since field
82                // otherwise, suggest using NameValue syntax
83                if let Some(elem) = list.as_single()
84                    && let Some(lit) = elem.as_lit()
85                    && let LitKind::Str(text, _) = lit.kind
86                {
87                    let mut adcx = cx.adcx();
88
89                    match parse_since(text, true) {
90                        DeprecatedSince::Future | DeprecatedSince::RustcVersion(_) => {
91                            adcx.push_suggestion(
92                                String::from("try specifying a deprecated since version"),
93                                elem.span(),
94                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("since = {0}", lit.kind))
    })format!("since = {}", lit.kind),
95                            );
96                        }
97                        _ => {
98                            if let Some(span) = args.span() {
99                                adcx.push_suggestion(
100                                    String::from("try using `=` instead"),
101                                    span,
102                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" = {0}", lit.kind))
    })format!(" = {}", lit.kind),
103                                );
104                            }
105                        }
106                    };
107
108                    adcx.expected_not_literal(elem.span());
109                    return None;
110                }
111
112                for param in list.mixed() {
113                    let Some(param) = param.meta_item() else {
114                        cx.adcx().expected_not_literal(param.span());
115                        return None;
116                    };
117
118                    let ident_name = param.path().word_sym();
119
120                    match ident_name {
121                        Some(name @ sym::since) => {
122                            since = Some(get(cx, name, param.span(), param.args(), since)?.name);
123                        }
124                        Some(name @ sym::note) => {
125                            note = Some(get(
126                                cx,
127                                name,
128                                param.span(),
129                                param.args(),
130                                note.map(|ident| ident.name),
131                            )?);
132                        }
133                        Some(name @ sym::suggestion) => {
134                            if !features.deprecated_suggestion() {
135                                cx.emit_err(DeprecatedItemSuggestion {
136                                    span: param.span(),
137                                    is_nightly: cx.sess().is_nightly_build(),
138                                    details: (),
139                                });
140                            }
141
142                            suggestion =
143                                Some(get(cx, name, param.span(), param.args(), suggestion)?.name);
144                        }
145                        _ => {
146                            cx.adcx().expected_specific_argument(
147                                param.span(),
148                                if features.deprecated_suggestion() {
149                                    &[sym::since, sym::note, sym::suggestion]
150                                } else {
151                                    &[sym::since, sym::note]
152                                },
153                            );
154                            return None;
155                        }
156                    }
157                }
158            }
159            ArgParser::NameValue(v) => {
160                let Some(value) = v.value_as_ident() else {
161                    cx.adcx().expected_string_literal(v.value_span, Some(v.value_as_lit()));
162                    return None;
163                };
164                note = Some(value);
165            }
166        }
167
168        let since = if let Some(since) = since {
169            let since = parse_since(since, is_rustc);
170            if #[allow(non_exhaustive_omitted_patterns)] match since {
    DeprecatedSince::Err => true,
    _ => false,
}matches!(since, DeprecatedSince::Err) {
171                cx.emit_err(InvalidSince { span: cx.attr_span });
172            }
173            since
174        } else if is_rustc {
175            cx.emit_err(MissingSince { span: cx.attr_span });
176            DeprecatedSince::Err
177        } else {
178            DeprecatedSince::Unspecified
179        };
180
181        if is_rustc && note.is_none() {
182            cx.emit_err(MissingNote { span: cx.attr_span });
183            return None;
184        }
185
186        Some(AttributeKind::Deprecated {
187            deprecation: Deprecation { since, note, suggestion },
188            span: cx.attr_span,
189        })
190    }
191}
192
193fn parse_since(since: Symbol, is_rustc: bool) -> DeprecatedSince {
194    if since.as_str() == "TBD" {
195        DeprecatedSince::Future
196    } else if !is_rustc {
197        DeprecatedSince::NonStandard(since)
198    } else if since.as_str() == VERSION_PLACEHOLDER {
199        DeprecatedSince::RustcVersion(RustcVersion::CURRENT)
200    } else if let Some(version) = parse_version(since) {
201        DeprecatedSince::RustcVersion(version)
202    } else {
203        DeprecatedSince::Err
204    }
205}