Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/item_tree/attrs.rs')
-rw-r--r--crates/hir-def/src/item_tree/attrs.rs260
1 files changed, 260 insertions, 0 deletions
diff --git a/crates/hir-def/src/item_tree/attrs.rs b/crates/hir-def/src/item_tree/attrs.rs
new file mode 100644
index 0000000000..7907611284
--- /dev/null
+++ b/crates/hir-def/src/item_tree/attrs.rs
@@ -0,0 +1,260 @@
+//! Defines attribute helpers for name resolution.
+//!
+//! Notice we don't preserve all attributes for name resolution, to save space:
+//! for example, we skip doc comments (desugared to `#[doc = "..."]` attributes)
+//! and `#[inline]`. The filtered attributes are listed in [`hir_expand::attrs`].
+
+use std::{
+ borrow::Cow,
+ convert::Infallible,
+ ops::{self, ControlFlow},
+};
+
+use cfg::{CfgExpr, CfgOptions};
+use either::Either;
+use hir_expand::{
+ attrs::{Attr, AttrId, AttrInput, Meta, collect_item_tree_attrs},
+ mod_path::ModPath,
+ name::Name,
+};
+use intern::{Interned, Symbol, sym};
+use syntax::{AstNode, T, ast};
+use syntax_bridge::DocCommentDesugarMode;
+use tt::token_to_literal;
+
+use crate::{db::DefDatabase, item_tree::lower::Ctx};
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) enum AttrsOrCfg {
+ Enabled {
+ attrs: AttrsOwned,
+ },
+ /// This only collects the attributes up to the disabled `cfg` (this is what needed for crate-level attributes.)
+ CfgDisabled(Box<(CfgExpr, AttrsOwned)>),
+}
+
+impl Default for AttrsOrCfg {
+ #[inline]
+ fn default() -> Self {
+ AttrsOrCfg::Enabled { attrs: AttrsOwned(Box::new([])) }
+ }
+}
+
+impl AttrsOrCfg {
+ pub(crate) fn lower<'a, S>(
+ db: &dyn DefDatabase,
+ owner: &dyn ast::HasAttrs,
+ cfg_options: &dyn Fn() -> &'a CfgOptions,
+ span_map: S,
+ ) -> AttrsOrCfg
+ where
+ S: syntax_bridge::SpanMapper + Copy,
+ {
+ let mut attrs = Vec::new();
+ let result =
+ collect_item_tree_attrs::<Infallible>(owner, cfg_options, |meta, container, _, _| {
+ // NOTE: We cannot early return from this function, *every* attribute must be pushed, otherwise we'll mess the `AttrId`
+ // tracking.
+ let (span, path_range, input) = match meta {
+ Meta::NamedKeyValue { path_range, name: _, value } => {
+ let span = span_map.span_for(path_range);
+ let input = value.map(|value| {
+ Box::new(AttrInput::Literal(token_to_literal(
+ value.text(),
+ span_map.span_for(value.text_range()),
+ )))
+ });
+ (span, path_range, input)
+ }
+ Meta::TokenTree { path, tt } => {
+ let span = span_map.span_for(path.range);
+ let tt = syntax_bridge::syntax_node_to_token_tree(
+ tt.syntax(),
+ span_map,
+ span,
+ DocCommentDesugarMode::ProcMacro,
+ );
+ let input = Some(Box::new(AttrInput::TokenTree(tt)));
+ (span, path.range, input)
+ }
+ Meta::Path { path } => {
+ let span = span_map.span_for(path.range);
+ (span, path.range, None)
+ }
+ };
+
+ let path = container.token_at_offset(path_range.start()).right_biased().and_then(
+ |first_path_token| {
+ let is_abs = matches!(first_path_token.kind(), T![:] | T![::]);
+ let segments =
+ std::iter::successors(Some(first_path_token), |it| it.next_token())
+ .take_while(|it| it.text_range().end() <= path_range.end())
+ .filter(|it| it.kind().is_any_identifier());
+ ModPath::from_tokens(
+ db,
+ &mut |range| span_map.span_for(range).ctx,
+ is_abs,
+ segments,
+ )
+ },
+ );
+ let path = path.unwrap_or_else(|| Name::missing().into());
+
+ attrs.push(Attr { path: Interned::new(path), input, ctxt: span.ctx });
+ ControlFlow::Continue(())
+ });
+ let attrs = AttrsOwned(attrs.into_boxed_slice());
+ match result {
+ Some(Either::Right(cfg)) => AttrsOrCfg::CfgDisabled(Box::new((cfg, attrs))),
+ None => AttrsOrCfg::Enabled { attrs },
+ }
+ }
+
+ // Merges two `AttrsOrCfg`s, assuming `self` is placed before `other` in the source code.
+ // The operation follows these rules:
+ //
+ // - If `self` and `other` are both `AttrsOrCfg::Enabled`, the result is a new
+ // `AttrsOrCfg::Enabled`. It contains the concatenation of `self`'s attributes followed by
+ // `other`'s.
+ // - If `self` is `AttrsOrCfg::Enabled` but `other` is `AttrsOrCfg::CfgDisabled`, the result
+ // is a new `AttrsOrCfg::CfgDisabled`. It contains the concatenation of `self`'s attributes
+ // followed by `other`'s.
+ // - If `self` is `AttrsOrCfg::CfgDisabled`, return `self` as-is.
+ //
+ // The rationale is that attribute collection is sequential and order-sensitive. This operation
+ // preserves those semantics when combining attributes from two different sources.
+ // `AttrsOrCfg::CfgDisabled` marks a point where collection stops due to a false `#![cfg(...)]`
+ // condition. It acts as a "breakpoint": attributes beyond it are not collected. Therefore,
+ // when merging, an `AttrsOrCfg::CfgDisabled` on the left-hand side short-circuits the
+ // operation, while an `AttrsOrCfg::CfgDisabled` on the right-hand side preserves all
+ // attributes collected up to that point.
+ //
+ // Note that this operation is neither commutative nor associative.
+ pub(crate) fn merge(self, other: AttrsOrCfg) -> AttrsOrCfg {
+ match (self, other) {
+ (AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::Enabled { attrs: other_attrs }) => {
+ let mut v = attrs.0.into_vec();
+ v.extend(other_attrs.0);
+ AttrsOrCfg::Enabled { attrs: AttrsOwned(v.into_boxed_slice()) }
+ }
+ (AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::CfgDisabled(mut other)) => {
+ let other_attrs = &mut other.1;
+ let mut v = attrs.0.into_vec();
+ v.extend(std::mem::take(&mut other_attrs.0));
+ other_attrs.0 = v.into_boxed_slice();
+ AttrsOrCfg::CfgDisabled(other)
+ }
+ (this @ AttrsOrCfg::CfgDisabled(_), _) => this,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct AttrsOwned(Box<[Attr]>);
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct Attrs<'a>(&'a [Attr]);
+
+impl ops::Deref for Attrs<'_> {
+ type Target = [Attr];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.0
+ }
+}
+
+impl Ctx<'_> {
+ #[inline]
+ pub(super) fn lower_attrs(&self, owner: &dyn ast::HasAttrs) -> AttrsOrCfg {
+ AttrsOrCfg::lower(self.db, owner, &|| self.cfg_options(), self.span_map())
+ }
+}
+
+impl AttrsOwned {
+ #[inline]
+ pub(crate) fn as_ref(&self) -> Attrs<'_> {
+ Attrs(&self.0)
+ }
+}
+
+impl<'a> Attrs<'a> {
+ pub(crate) const EMPTY: Self = Attrs(&[]);
+
+ #[inline]
+ pub(crate) fn by_key(self, key: Symbol) -> AttrQuery<'a> {
+ AttrQuery { attrs: self, key }
+ }
+
+ #[inline]
+ pub(crate) fn iter(self) -> impl Iterator<Item = (AttrId, &'a Attr)> {
+ self.0.iter().enumerate().map(|(id, attr)| (AttrId::from_item_tree_index(id as u32), attr))
+ }
+
+ #[inline]
+ pub(crate) fn iter_after(
+ self,
+ after: Option<AttrId>,
+ ) -> impl Iterator<Item = (AttrId, &'a Attr)> {
+ let skip = after.map_or(0, |after| after.item_tree_index() + 1);
+ self.0[skip as usize..]
+ .iter()
+ .enumerate()
+ .map(move |(id, attr)| (AttrId::from_item_tree_index(id as u32 + skip), attr))
+ }
+
+ #[inline]
+ pub(crate) fn is_proc_macro(&self) -> bool {
+ self.by_key(sym::proc_macro).exists()
+ }
+
+ #[inline]
+ pub(crate) fn is_proc_macro_attribute(&self) -> bool {
+ self.by_key(sym::proc_macro_attribute).exists()
+ }
+}
+#[derive(Debug, Clone)]
+pub(crate) struct AttrQuery<'attr> {
+ attrs: Attrs<'attr>,
+ key: Symbol,
+}
+
+impl<'attr> AttrQuery<'attr> {
+ #[inline]
+ pub(crate) fn tt_values(self) -> impl Iterator<Item = &'attr crate::tt::TopSubtree> {
+ self.attrs().filter_map(|attr| attr.token_tree_value())
+ }
+
+ #[inline]
+ pub(crate) fn string_value_with_span(self) -> Option<(&'attr str, span::Span)> {
+ self.attrs().find_map(|attr| attr.string_value_with_span())
+ }
+
+ #[inline]
+ pub(crate) fn string_value_unescape(self) -> Option<Cow<'attr, str>> {
+ self.attrs().find_map(|attr| attr.string_value_unescape())
+ }
+
+ #[inline]
+ pub(crate) fn exists(self) -> bool {
+ self.attrs().next().is_some()
+ }
+
+ #[inline]
+ pub(crate) fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
+ let key = self.key;
+ self.attrs.0.iter().filter(move |attr| attr.path.as_ident().is_some_and(|s| *s == key))
+ }
+}
+
+impl AttrsOrCfg {
+ #[inline]
+ pub(super) fn empty() -> Self {
+ AttrsOrCfg::Enabled { attrs: AttrsOwned(Box::new([])) }
+ }
+
+ #[inline]
+ pub(super) fn is_empty(&self) -> bool {
+ matches!(self, AttrsOrCfg::Enabled { attrs } if attrs.as_ref().is_empty())
+ }
+}