Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/attr.rs')
| -rw-r--r-- | crates/hir-def/src/attr.rs | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 200072c172..45da0c8af5 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ attrs::{collect_attrs, Attr, AttrId, RawAttrs}, + name::{AsName, Name}, HirFileId, InFile, }; use itertools::Itertools; @@ -238,6 +239,17 @@ impl Attrs { }) } + pub fn doc_exprs(&self) -> Vec<DocExpr> { + self.by_key("doc").tt_values().map(DocExpr::parse).collect() + } + + pub fn doc_aliases(&self) -> Vec<SmolStr> { + self.doc_exprs() + .into_iter() + .flat_map(|doc_expr| doc_expr.aliases()) + .collect() + } + pub fn is_proc_macro(&self) -> bool { self.by_key("proc_macro").exists() } @@ -251,6 +263,106 @@ impl Attrs { } } +use std::slice::Iter as SliceIter; +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum DocAtom { + /// eg. `#[doc(hidden)]` + Flag(SmolStr), + /// eg. `#[doc(alias = "x")]` + /// + /// Note that a key can have multiple values that are all considered "active" at the same time. + /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`. + KeyValue { key: SmolStr, value: SmolStr }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))] +pub enum DocExpr { + Invalid, + /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` + Atom(DocAtom), + /// eg. `#[doc(alias("x", "y"))]` + Alias(Vec<SmolStr>), +} + +impl From<DocAtom> for DocExpr { + fn from(atom: DocAtom) -> Self { + DocExpr::Atom(atom) + } +} + +impl DocExpr { + pub fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr { + next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid) + } + + pub fn aliases(self) -> Vec<SmolStr> { + match self { + DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => { + vec![value] + } + DocExpr::Alias(aliases) => aliases, + _ => vec![], + } + } +} + +fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> { + let name = match it.next() { + None => return None, + Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(), + Some(_) => return Some(DocExpr::Invalid), + }; + + // Peek + let ret = match it.as_slice().first() { + Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => { + match it.as_slice().get(1) { + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => { + it.next(); + it.next(); + // FIXME: escape? raw string? + let value = + SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); + DocAtom::KeyValue { key: name, value }.into() + } + _ => return Some(DocExpr::Invalid), + } + } + Some(tt::TokenTree::Subtree(subtree)) => { + it.next(); + let subs = parse_comma_sep(subtree); + match name.as_str() { + "alias" => DocExpr::Alias(subs), + _ => DocExpr::Invalid, + } + } + _ => DocAtom::Flag(name).into(), + }; + + // Eat comma separator + if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() { + if punct.char == ',' { + it.next(); + } + } + Some(ret) +} + +fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> { + subtree + .token_trees + .iter() + .filter_map(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { + // FIXME: escape? raw string? + Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"'))) + } + _ => None, + }) + .collect() +} + impl AttrsWithOwner { pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { // FIXME: this should use `Trace` to avoid duplication in `source_map` below |