Skip to main content

hir_def/item_tree/
attrs.rs

1//! Defines attribute helpers for name resolution.
2//!
3//! Notice we don't preserve all attributes for name resolution, to save space:
4//! for example, we skip doc comments (desugared to `#[doc = "..."]` attributes)
5//! and `#[inline]`. The filtered attributes are listed in [`hir_expand::attrs`].
6
7use std::{
8    borrow::Cow,
9    convert::Infallible,
10    ops::{self, ControlFlow},
11};
12
13use base_db::SourceDatabase;
14use cfg::{CfgExpr, CfgOptions};
15use either::Either;
16use hir_expand::{
17    attrs::{Attr, AttrId, AttrInput, collect_item_tree_attrs},
18    mod_path::ModPath,
19    name::Name,
20};
21use intern::{Interned, Symbol, sym};
22use syntax::{AstNode, ast};
23use syntax_bridge::DocCommentDesugarMode;
24use tt::token_to_literal;
25
26use crate::item_tree::lower::Ctx;
27
28#[derive(Debug, PartialEq, Eq)]
29pub(crate) enum AttrsOrCfg {
30    Enabled {
31        attrs: AttrsOwned,
32    },
33    /// This only collects the attributes up to the disabled `cfg` (this is what needed for crate-level attributes.)
34    CfgDisabled(Box<(CfgExpr, AttrsOwned)>),
35}
36
37impl Default for AttrsOrCfg {
38    #[inline]
39    fn default() -> Self {
40        AttrsOrCfg::Enabled { attrs: AttrsOwned(Box::new([])) }
41    }
42}
43
44impl AttrsOrCfg {
45    pub(crate) fn lower<'a, S>(
46        db: &dyn SourceDatabase,
47        owner: &dyn ast::HasAttrs,
48        cfg_options: &dyn Fn() -> &'a CfgOptions,
49        span_map: S,
50    ) -> AttrsOrCfg
51    where
52        S: syntax_bridge::SpanMapper + Copy,
53    {
54        let mut attrs = Vec::new();
55        let result = collect_item_tree_attrs::<Infallible>(owner, cfg_options, |meta, _| {
56            // NOTE: We cannot early return from this function, *every* attribute must be pushed, otherwise we'll mess the `AttrId`
57            // tracking.
58            let path = meta.path();
59            let path_range = path
60                .as_ref()
61                .map(|path| path.syntax().text_range())
62                .unwrap_or_else(|| meta.syntax().text_range());
63            let (span, input) = match &meta {
64                ast::Meta::KeyValueMeta(meta) => {
65                    let span = span_map.span_for(path_range);
66                    let input = meta.expr().and_then(|value| {
67                        if let ast::Expr::Literal(value) = value {
68                            Some(Box::new(AttrInput::Literal(token_to_literal(
69                                value.token().text(),
70                                span_map.span_for(value.syntax().text_range()),
71                            ))))
72                        } else {
73                            None
74                        }
75                    });
76                    (span, input)
77                }
78                ast::Meta::TokenTreeMeta(meta) => {
79                    let span = span_map.span_for(path_range);
80                    let tt = syntax_bridge::syntax_node_to_token_tree(
81                        &meta
82                            .token_tree()
83                            .map(|it| it.syntax().clone())
84                            .unwrap_or_else(|| meta.syntax().clone()),
85                        span_map,
86                        span,
87                        DocCommentDesugarMode::ProcMacro,
88                    );
89                    let input = Some(Box::new(AttrInput::TokenTree(tt)));
90                    (span, input)
91                }
92                ast::Meta::PathMeta(_) => {
93                    let span = span_map.span_for(path_range);
94                    (span, None)
95                }
96                ast::Meta::CfgMeta(_) | ast::Meta::CfgAttrMeta(_) | ast::Meta::UnsafeMeta(_) => {
97                    unreachable!(
98                        "`cfg`, `cfg_attr` and `unsafe(...)` are handled in `collect_item_tree_attrs()`"
99                    )
100                }
101            };
102
103            let path = path.and_then(|path| {
104                ModPath::from_src(db, path, &mut |range| span_map.span_for(range).ctx)
105            });
106            let path = path.unwrap_or_else(|| Name::missing().into());
107
108            attrs.push(Attr { path: Interned::new(path), input, ctxt: span.ctx });
109            ControlFlow::Continue(())
110        });
111        let attrs = AttrsOwned(attrs.into_boxed_slice());
112        match result {
113            Some(Either::Right(cfg)) => AttrsOrCfg::CfgDisabled(Box::new((cfg, attrs))),
114            None => AttrsOrCfg::Enabled { attrs },
115        }
116    }
117
118    // Merges two `AttrsOrCfg`s, assuming `self` is placed before `other` in the source code.
119    // The operation follows these rules:
120    //
121    //   - If `self` and `other` are both `AttrsOrCfg::Enabled`, the result is a new
122    //     `AttrsOrCfg::Enabled`. It contains the concatenation of `self`'s attributes followed by
123    //     `other`'s.
124    //   - If `self` is `AttrsOrCfg::Enabled` but `other` is `AttrsOrCfg::CfgDisabled`, the result
125    //     is a new `AttrsOrCfg::CfgDisabled`. It contains the concatenation of `self`'s attributes
126    //     followed by `other`'s.
127    //   - If `self` is `AttrsOrCfg::CfgDisabled`, return `self` as-is.
128    //
129    // The rationale is that attribute collection is sequential and order-sensitive. This operation
130    // preserves those semantics when combining attributes from two different sources.
131    // `AttrsOrCfg::CfgDisabled` marks a point where collection stops due to a false `#![cfg(...)]`
132    // condition. It acts as a "breakpoint": attributes beyond it are not collected. Therefore,
133    // when merging, an `AttrsOrCfg::CfgDisabled` on the left-hand side short-circuits the
134    // operation, while an `AttrsOrCfg::CfgDisabled` on the right-hand side preserves all
135    // attributes collected up to that point.
136    //
137    // Note that this operation is neither commutative nor associative.
138    pub(crate) fn merge(self, other: AttrsOrCfg) -> AttrsOrCfg {
139        match (self, other) {
140            (AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::Enabled { attrs: other_attrs }) => {
141                let mut v = attrs.0.into_vec();
142                v.extend(other_attrs.0);
143                AttrsOrCfg::Enabled { attrs: AttrsOwned(v.into_boxed_slice()) }
144            }
145            (AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::CfgDisabled(mut other)) => {
146                let other_attrs = &mut other.1;
147                let mut v = attrs.0.into_vec();
148                v.extend(std::mem::take(&mut other_attrs.0));
149                other_attrs.0 = v.into_boxed_slice();
150                AttrsOrCfg::CfgDisabled(other)
151            }
152            (this @ AttrsOrCfg::CfgDisabled(_), _) => this,
153        }
154    }
155}
156
157#[derive(Debug, PartialEq, Eq)]
158pub(crate) struct AttrsOwned(Box<[Attr]>);
159
160#[derive(Debug, Clone, Copy)]
161pub(crate) struct Attrs<'a>(&'a [Attr]);
162
163impl ops::Deref for Attrs<'_> {
164    type Target = [Attr];
165
166    #[inline]
167    fn deref(&self) -> &Self::Target {
168        self.0
169    }
170}
171
172impl Ctx<'_> {
173    #[inline]
174    pub(super) fn lower_attrs(&self, owner: &dyn ast::HasAttrs) -> AttrsOrCfg {
175        AttrsOrCfg::lower(self.db, owner, &|| self.cfg_options(), self.span_map())
176    }
177}
178
179impl AttrsOwned {
180    #[inline]
181    pub(crate) fn as_ref(&self) -> Attrs<'_> {
182        Attrs(&self.0)
183    }
184}
185
186impl<'a> Attrs<'a> {
187    pub(crate) const EMPTY: Self = Attrs(&[]);
188
189    #[inline]
190    pub(crate) fn by_key(self, key: Symbol) -> AttrQuery<'a> {
191        AttrQuery { attrs: self, key }
192    }
193
194    #[inline]
195    pub(crate) fn iter(self) -> impl Iterator<Item = (AttrId, &'a Attr)> {
196        self.0.iter().enumerate().map(|(id, attr)| (AttrId::from_item_tree_index(id as u32), attr))
197    }
198
199    #[inline]
200    pub(crate) fn iter_after(
201        self,
202        after: Option<AttrId>,
203    ) -> impl Iterator<Item = (AttrId, &'a Attr)> {
204        let skip = after.map_or(0, |after| after.item_tree_index() + 1);
205        self.0[skip as usize..]
206            .iter()
207            .enumerate()
208            .map(move |(id, attr)| (AttrId::from_item_tree_index(id as u32 + skip), attr))
209    }
210
211    #[inline]
212    pub(crate) fn is_proc_macro(&self) -> bool {
213        self.by_key(sym::proc_macro).exists()
214    }
215
216    #[inline]
217    pub(crate) fn is_proc_macro_attribute(&self) -> bool {
218        self.by_key(sym::proc_macro_attribute).exists()
219    }
220}
221#[derive(Debug, Clone)]
222pub(crate) struct AttrQuery<'attr> {
223    attrs: Attrs<'attr>,
224    key: Symbol,
225}
226
227impl<'attr> AttrQuery<'attr> {
228    #[inline]
229    pub(crate) fn tt_values(self) -> impl Iterator<Item = &'attr crate::tt::TopSubtree> {
230        self.attrs().filter_map(|attr| attr.token_tree_value())
231    }
232
233    #[inline]
234    pub(crate) fn string_value_with_span(self) -> Option<(&'attr str, span::Span)> {
235        self.attrs().find_map(|attr| attr.string_value_with_span())
236    }
237
238    #[inline]
239    pub(crate) fn string_value_unescape(self) -> Option<Cow<'attr, str>> {
240        self.attrs().find_map(|attr| attr.string_value_unescape())
241    }
242
243    #[inline]
244    pub(crate) fn exists(self) -> bool {
245        self.attrs().next().is_some()
246    }
247
248    #[inline]
249    pub(crate) fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
250        let key = self.key;
251        self.attrs.0.iter().filter(move |attr| attr.path.as_ident().is_some_and(|s| *s == key))
252    }
253}
254
255impl AttrsOrCfg {
256    #[inline]
257    pub(super) fn is_empty(&self) -> bool {
258        matches!(self, AttrsOrCfg::Enabled { attrs } if attrs.0.is_empty())
259    }
260}