rustc_attr_parsing/attributes/
util.rs

1use std::num::IntErrorKind;
2
3use rustc_ast::LitKind;
4use rustc_ast::attr::AttributeExt;
5use rustc_feature::is_builtin_attr_name;
6use rustc_hir::RustcVersion;
7use rustc_hir::limit::Limit;
8use rustc_span::{Symbol, sym};
9
10use crate::context::{AcceptContext, Stage};
11use crate::parser::{ArgParser, NameValueParser};
12use crate::session_diagnostics::LimitInvalid;
13
14/// Parse a rustc version number written inside string literal in an attribute,
15/// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are
16/// not accepted in this position, unlike when parsing CFG_RELEASE.
17pub fn parse_version(s: Symbol) -> Option<RustcVersion> {
18    let mut components = s.as_str().split('-');
19    let d = components.next()?;
20    if components.next().is_some() {
21        return None;
22    }
23    let mut digits = d.splitn(3, '.');
24    let major = digits.next()?.parse().ok()?;
25    let minor = digits.next()?.parse().ok()?;
26    let patch = digits.next().unwrap_or("0").parse().ok()?;
27    Some(RustcVersion { major, minor, patch })
28}
29
30pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
31    attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
32}
33
34pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
35    attrs: impl Iterator<Item = &'tcx T>,
36    symbol: Symbol,
37) -> bool {
38    let doc_attrs = attrs.filter(|attr| attr.has_name(sym::doc));
39    for attr in doc_attrs {
40        let Some(values) = attr.meta_item_list() else {
41            continue;
42        };
43        let alias_values = values.iter().filter(|v| v.has_name(sym::alias));
44        for v in alias_values {
45            if let Some(nested) = v.meta_item_list() {
46                // #[doc(alias("foo", "bar"))]
47                let mut iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol);
48                if iter.any(|s| s == symbol) {
49                    return true;
50                }
51            } else if let Some(meta) = v.meta_item()
52                && let Some(lit) = meta.name_value_literal()
53            {
54                // #[doc(alias = "foo")]
55                if lit.symbol == symbol {
56                    return true;
57                }
58            }
59        }
60    }
61    false
62}
63
64/// Parse a single integer.
65///
66/// Used by attributes that take a single integer as argument, such as
67/// `#[link_ordinal]` and `#[rustc_layout_scalar_valid_range_start]`.
68/// `cx` is the context given to the attribute.
69/// `args` is the parser for the attribute arguments.
70pub(crate) fn parse_single_integer<S: Stage>(
71    cx: &mut AcceptContext<'_, '_, S>,
72    args: &ArgParser<'_>,
73) -> Option<u128> {
74    let Some(list) = args.list() else {
75        cx.expected_list(cx.attr_span);
76        return None;
77    };
78    let Some(single) = list.single() else {
79        cx.expected_single_argument(list.span);
80        return None;
81    };
82    let Some(lit) = single.lit() else {
83        cx.expected_integer_literal(single.span());
84        return None;
85    };
86    let LitKind::Int(num, _ty) = lit.kind else {
87        cx.expected_integer_literal(single.span());
88        return None;
89    };
90    Some(num.0)
91}
92
93impl<S: Stage> AcceptContext<'_, '_, S> {
94    pub(crate) fn parse_limit_int(&self, nv: &NameValueParser) -> Option<Limit> {
95        let Some(limit) = nv.value_as_str() else {
96            self.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
97            return None;
98        };
99
100        let error_str = match limit.as_str().parse() {
101            Ok(i) => return Some(Limit::new(i)),
102            Err(e) => match e.kind() {
103                IntErrorKind::PosOverflow => "`limit` is too large",
104                IntErrorKind::Empty => "`limit` must be a non-negative integer",
105                IntErrorKind::InvalidDigit => "not a valid integer",
106                IntErrorKind::NegOverflow => {
107                    panic!(
108                        "`limit` should never negatively overflow since we're parsing into a usize and we'd get Empty instead"
109                    )
110                }
111                IntErrorKind::Zero => {
112                    panic!("zero is a valid `limit` so should have returned Ok() when parsing")
113                }
114                kind => panic!("unimplemented IntErrorKind variant: {:?}", kind),
115            },
116        };
117
118        self.emit_err(LimitInvalid { span: self.attr_span, value_span: nv.value_span, error_str });
119
120        None
121    }
122}