• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! A higher level attributes based on TokenTree, with also some shortcuts.
2 
3 pub mod builtin;
4 
5 #[cfg(test)]
6 mod tests;
7 
8 use std::{hash::Hash, ops};
9 
10 use base_db::CrateId;
11 use cfg::{CfgExpr, CfgOptions};
12 use either::Either;
13 use hir_expand::{
14     attrs::{collect_attrs, Attr, AttrId, RawAttrs},
15     HirFileId, InFile,
16 };
17 use itertools::Itertools;
18 use la_arena::{ArenaMap, Idx, RawIdx};
19 use mbe::DelimiterKind;
20 use syntax::{
21     ast::{self, HasAttrs, IsString},
22     AstPtr, AstToken, SmolStr, TextRange, TextSize,
23 };
24 use triomphe::Arc;
25 
26 use crate::{
27     db::DefDatabase,
28     item_tree::{AttrOwner, Fields, ItemTreeId, ItemTreeNode},
29     lang_item::LangItem,
30     nameres::{ModuleOrigin, ModuleSource},
31     src::{HasChildSource, HasSource},
32     AdtId, AssocItemLoc, AttrDefId, EnumId, GenericParamId, ItemLoc, LocalEnumVariantId,
33     LocalFieldId, Lookup, MacroId, VariantId,
34 };
35 
36 /// Holds documentation
37 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
38 pub struct Documentation(String);
39 
40 impl Documentation {
new(s: String) -> Self41     pub fn new(s: String) -> Self {
42         Documentation(s)
43     }
44 
as_str(&self) -> &str45     pub fn as_str(&self) -> &str {
46         &self.0
47     }
48 }
49 
50 impl From<Documentation> for String {
from(Documentation(string): Documentation) -> Self51     fn from(Documentation(string): Documentation) -> Self {
52         string
53     }
54 }
55 
56 #[derive(Default, Debug, Clone, PartialEq, Eq)]
57 pub struct Attrs(RawAttrs);
58 
59 #[derive(Debug, Clone, PartialEq, Eq)]
60 pub struct AttrsWithOwner {
61     attrs: Attrs,
62     owner: AttrDefId,
63 }
64 
65 impl Attrs {
get(&self, id: AttrId) -> Option<&Attr>66     pub fn get(&self, id: AttrId) -> Option<&Attr> {
67         (**self).iter().find(|attr| attr.id == id)
68     }
69 
filter(db: &dyn DefDatabase, krate: CrateId, raw_attrs: RawAttrs) -> Attrs70     pub(crate) fn filter(db: &dyn DefDatabase, krate: CrateId, raw_attrs: RawAttrs) -> Attrs {
71         Attrs(raw_attrs.filter(db.upcast(), krate))
72     }
73 }
74 
75 impl ops::Deref for Attrs {
76     type Target = [Attr];
77 
deref(&self) -> &[Attr]78     fn deref(&self) -> &[Attr] {
79         &self.0
80     }
81 }
82 
83 impl ops::Deref for AttrsWithOwner {
84     type Target = Attrs;
85 
deref(&self) -> &Attrs86     fn deref(&self) -> &Attrs {
87         &self.attrs
88     }
89 }
90 
91 impl Attrs {
92     pub const EMPTY: Self = Self(RawAttrs::EMPTY);
93 
variants_attrs_query( db: &dyn DefDatabase, e: EnumId, ) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>>94     pub(crate) fn variants_attrs_query(
95         db: &dyn DefDatabase,
96         e: EnumId,
97     ) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>> {
98         let _p = profile::span("variants_attrs_query");
99         // FIXME: There should be some proper form of mapping between item tree enum variant ids and hir enum variant ids
100         let mut res = ArenaMap::default();
101 
102         let loc = e.lookup(db);
103         let krate = loc.container.krate;
104         let item_tree = loc.id.item_tree(db);
105         let enum_ = &item_tree[loc.id.value];
106         let crate_graph = db.crate_graph();
107         let cfg_options = &crate_graph[krate].cfg_options;
108 
109         let mut idx = 0;
110         for variant in enum_.variants.clone() {
111             let attrs = item_tree.attrs(db, krate, variant.into());
112             if attrs.is_cfg_enabled(cfg_options) {
113                 res.insert(Idx::from_raw(RawIdx::from(idx)), attrs);
114                 idx += 1;
115             }
116         }
117 
118         Arc::new(res)
119     }
120 
fields_attrs_query( db: &dyn DefDatabase, v: VariantId, ) -> Arc<ArenaMap<LocalFieldId, Attrs>>121     pub(crate) fn fields_attrs_query(
122         db: &dyn DefDatabase,
123         v: VariantId,
124     ) -> Arc<ArenaMap<LocalFieldId, Attrs>> {
125         let _p = profile::span("fields_attrs_query");
126         // FIXME: There should be some proper form of mapping between item tree field ids and hir field ids
127         let mut res = ArenaMap::default();
128 
129         let crate_graph = db.crate_graph();
130         let (fields, item_tree, krate) = match v {
131             VariantId::EnumVariantId(it) => {
132                 let e = it.parent;
133                 let loc = e.lookup(db);
134                 let krate = loc.container.krate;
135                 let item_tree = loc.id.item_tree(db);
136                 let enum_ = &item_tree[loc.id.value];
137 
138                 let cfg_options = &crate_graph[krate].cfg_options;
139 
140                 let Some(variant) = enum_.variants.clone().filter(|variant| {
141                     let attrs = item_tree.attrs(db, krate, (*variant).into());
142                     attrs.is_cfg_enabled(cfg_options)
143                 })
144                 .zip(0u32..)
145                 .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx)))
146                 .map(|(variant, _idx)| variant)
147                 else {
148                     return Arc::new(res);
149                 };
150 
151                 (item_tree[variant].fields.clone(), item_tree, krate)
152             }
153             VariantId::StructId(it) => {
154                 let loc = it.lookup(db);
155                 let krate = loc.container.krate;
156                 let item_tree = loc.id.item_tree(db);
157                 let struct_ = &item_tree[loc.id.value];
158                 (struct_.fields.clone(), item_tree, krate)
159             }
160             VariantId::UnionId(it) => {
161                 let loc = it.lookup(db);
162                 let krate = loc.container.krate;
163                 let item_tree = loc.id.item_tree(db);
164                 let union_ = &item_tree[loc.id.value];
165                 (union_.fields.clone(), item_tree, krate)
166             }
167         };
168 
169         let fields = match fields {
170             Fields::Record(fields) | Fields::Tuple(fields) => fields,
171             Fields::Unit => return Arc::new(res),
172         };
173 
174         let cfg_options = &crate_graph[krate].cfg_options;
175 
176         let mut idx = 0;
177         for field in fields {
178             let attrs = item_tree.attrs(db, krate, field.into());
179             if attrs.is_cfg_enabled(cfg_options) {
180                 res.insert(Idx::from_raw(RawIdx::from(idx)), attrs);
181                 idx += 1;
182             }
183         }
184 
185         Arc::new(res)
186     }
187 }
188 
189 impl Attrs {
by_key(&self, key: &'static str) -> AttrQuery<'_>190     pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
191         AttrQuery { attrs: self, key }
192     }
193 
cfg(&self) -> Option<CfgExpr>194     pub fn cfg(&self) -> Option<CfgExpr> {
195         let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse);
196         let first = cfgs.next()?;
197         match cfgs.next() {
198             Some(second) => {
199                 let cfgs = [first, second].into_iter().chain(cfgs);
200                 Some(CfgExpr::All(cfgs.collect()))
201             }
202             None => Some(first),
203         }
204     }
205 
is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool206     pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
207         match self.cfg() {
208             None => true,
209             Some(cfg) => cfg_options.check(&cfg) != Some(false),
210         }
211     }
212 
lang(&self) -> Option<&SmolStr>213     pub fn lang(&self) -> Option<&SmolStr> {
214         self.by_key("lang").string_value()
215     }
216 
lang_item(&self) -> Option<LangItem>217     pub fn lang_item(&self) -> Option<LangItem> {
218         self.by_key("lang").string_value().and_then(|it| LangItem::from_str(it))
219     }
220 
docs(&self) -> Option<Documentation>221     pub fn docs(&self) -> Option<Documentation> {
222         let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value());
223         let indent = doc_indent(self);
224         let mut buf = String::new();
225         for doc in docs {
226             // str::lines doesn't yield anything for the empty string
227             if !doc.is_empty() {
228                 buf.extend(Itertools::intersperse(
229                     doc.lines().map(|line| {
230                         line.char_indices()
231                             .nth(indent)
232                             .map_or(line, |(offset, _)| &line[offset..])
233                             .trim_end()
234                     }),
235                     "\n",
236                 ));
237             }
238             buf.push('\n');
239         }
240         buf.pop();
241         if buf.is_empty() {
242             None
243         } else {
244             Some(Documentation(buf))
245         }
246     }
247 
has_doc_hidden(&self) -> bool248     pub fn has_doc_hidden(&self) -> bool {
249         self.by_key("doc").tt_values().any(|tt| {
250             tt.delimiter.kind == DelimiterKind::Parenthesis &&
251                 matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden")
252         })
253     }
254 
doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_255     pub fn doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_ {
256         self.by_key("doc").tt_values().map(DocExpr::parse)
257     }
258 
doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_259     pub fn doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_ {
260         self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
261     }
262 
is_proc_macro(&self) -> bool263     pub fn is_proc_macro(&self) -> bool {
264         self.by_key("proc_macro").exists()
265     }
266 
is_proc_macro_attribute(&self) -> bool267     pub fn is_proc_macro_attribute(&self) -> bool {
268         self.by_key("proc_macro_attribute").exists()
269     }
270 
is_proc_macro_derive(&self) -> bool271     pub fn is_proc_macro_derive(&self) -> bool {
272         self.by_key("proc_macro_derive").exists()
273     }
274 
is_unstable(&self) -> bool275     pub fn is_unstable(&self) -> bool {
276         self.by_key("unstable").exists()
277     }
278 }
279 
280 use std::slice::Iter as SliceIter;
281 #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
282 pub enum DocAtom {
283     /// eg. `#[doc(hidden)]`
284     Flag(SmolStr),
285     /// eg. `#[doc(alias = "x")]`
286     ///
287     /// Note that a key can have multiple values that are all considered "active" at the same time.
288     /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
289     KeyValue { key: SmolStr, value: SmolStr },
290 }
291 
292 // Adapted from `CfgExpr` parsing code
293 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
294 // #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
295 pub enum DocExpr {
296     Invalid,
297     /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]`
298     Atom(DocAtom),
299     /// eg. `#[doc(alias("x", "y"))]`
300     Alias(Vec<SmolStr>),
301 }
302 
303 impl From<DocAtom> for DocExpr {
from(atom: DocAtom) -> Self304     fn from(atom: DocAtom) -> Self {
305         DocExpr::Atom(atom)
306     }
307 }
308 
309 impl DocExpr {
parse<S>(tt: &tt::Subtree<S>) -> DocExpr310     fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
311         next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
312     }
313 
aliases(&self) -> &[SmolStr]314     pub fn aliases(&self) -> &[SmolStr] {
315         match self {
316             DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
317                 std::slice::from_ref(value)
318             }
319             DocExpr::Alias(aliases) => aliases,
320             _ => &[],
321         }
322     }
323 }
324 
next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr>325 fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> {
326     let name = match it.next() {
327         None => return None,
328         Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
329         Some(_) => return Some(DocExpr::Invalid),
330     };
331 
332     // Peek
333     let ret = match it.as_slice().first() {
334         Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
335             match it.as_slice().get(1) {
336                 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
337                     it.next();
338                     it.next();
339                     // FIXME: escape? raw string?
340                     let value =
341                         SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
342                     DocAtom::KeyValue { key: name, value }.into()
343                 }
344                 _ => return Some(DocExpr::Invalid),
345             }
346         }
347         Some(tt::TokenTree::Subtree(subtree)) => {
348             it.next();
349             let subs = parse_comma_sep(subtree);
350             match name.as_str() {
351                 "alias" => DocExpr::Alias(subs),
352                 _ => DocExpr::Invalid,
353             }
354         }
355         _ => DocAtom::Flag(name).into(),
356     };
357 
358     // Eat comma separator
359     if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
360         if punct.char == ',' {
361             it.next();
362         }
363     }
364     Some(ret)
365 }
366 
parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr>367 fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> {
368     subtree
369         .token_trees
370         .iter()
371         .filter_map(|tt| match tt {
372             tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
373                 // FIXME: escape? raw string?
374                 Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"')))
375             }
376             _ => None,
377         })
378         .collect()
379 }
380 
381 impl AttrsWithOwner {
attrs_with_owner(db: &dyn DefDatabase, owner: AttrDefId) -> Self382     pub(crate) fn attrs_with_owner(db: &dyn DefDatabase, owner: AttrDefId) -> Self {
383         Self { attrs: db.attrs(owner), owner }
384     }
385 
attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs386     pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
387         let _p = profile::span("attrs_query");
388         // FIXME: this should use `Trace` to avoid duplication in `source_map` below
389         let raw_attrs = match def {
390             AttrDefId::ModuleId(module) => {
391                 let def_map = module.def_map(db);
392                 let mod_data = &def_map[module.local_id];
393 
394                 match mod_data.origin {
395                     ModuleOrigin::File { definition, declaration_tree_id, .. } => {
396                         let decl_attrs = declaration_tree_id
397                             .item_tree(db)
398                             .raw_attrs(AttrOwner::ModItem(declaration_tree_id.value.into()))
399                             .clone();
400                         let tree = db.file_item_tree(definition.into());
401                         let def_attrs = tree.raw_attrs(AttrOwner::TopLevel).clone();
402                         decl_attrs.merge(def_attrs)
403                     }
404                     ModuleOrigin::CrateRoot { definition } => {
405                         let tree = db.file_item_tree(definition.into());
406                         tree.raw_attrs(AttrOwner::TopLevel).clone()
407                     }
408                     ModuleOrigin::Inline { definition_tree_id, .. } => definition_tree_id
409                         .item_tree(db)
410                         .raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into()))
411                         .clone(),
412                     ModuleOrigin::BlockExpr { block } => RawAttrs::from_attrs_owner(
413                         db.upcast(),
414                         InFile::new(block.file_id, block.to_node(db.upcast()))
415                             .as_ref()
416                             .map(|it| it as &dyn ast::HasAttrs),
417                     ),
418                 }
419             }
420             AttrDefId::FieldId(it) => {
421                 return db.fields_attrs(it.parent)[it.local_id].clone();
422             }
423             AttrDefId::EnumVariantId(it) => {
424                 return db.variants_attrs(it.parent)[it.local_id].clone();
425             }
426             // FIXME: DRY this up
427             AttrDefId::AdtId(it) => match it {
428                 AdtId::StructId(it) => attrs_from_item_tree_loc(db, it),
429                 AdtId::EnumId(it) => attrs_from_item_tree_loc(db, it),
430                 AdtId::UnionId(it) => attrs_from_item_tree_loc(db, it),
431             },
432             AttrDefId::TraitId(it) => attrs_from_item_tree_loc(db, it),
433             AttrDefId::TraitAliasId(it) => attrs_from_item_tree_loc(db, it),
434             AttrDefId::MacroId(it) => match it {
435                 MacroId::Macro2Id(it) => attrs_from_item_tree(db, it.lookup(db).id),
436                 MacroId::MacroRulesId(it) => attrs_from_item_tree(db, it.lookup(db).id),
437                 MacroId::ProcMacroId(it) => attrs_from_item_tree(db, it.lookup(db).id),
438             },
439             AttrDefId::ImplId(it) => attrs_from_item_tree_loc(db, it),
440             AttrDefId::ConstId(it) => attrs_from_item_tree_assoc(db, it),
441             AttrDefId::StaticId(it) => attrs_from_item_tree_assoc(db, it),
442             AttrDefId::FunctionId(it) => attrs_from_item_tree_assoc(db, it),
443             AttrDefId::TypeAliasId(it) => attrs_from_item_tree_assoc(db, it),
444             AttrDefId::GenericParamId(it) => match it {
445                 GenericParamId::ConstParamId(it) => {
446                     let src = it.parent().child_source(db);
447                     RawAttrs::from_attrs_owner(
448                         db.upcast(),
449                         src.with_value(&src.value[it.local_id()]),
450                     )
451                 }
452                 GenericParamId::TypeParamId(it) => {
453                     let src = it.parent().child_source(db);
454                     RawAttrs::from_attrs_owner(
455                         db.upcast(),
456                         src.with_value(&src.value[it.local_id()]),
457                     )
458                 }
459                 GenericParamId::LifetimeParamId(it) => {
460                     let src = it.parent.child_source(db);
461                     RawAttrs::from_attrs_owner(db.upcast(), src.with_value(&src.value[it.local_id]))
462                 }
463             },
464             AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it),
465         };
466 
467         let attrs = raw_attrs.filter(db.upcast(), def.krate(db));
468         Attrs(attrs)
469     }
470 
source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap471     pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap {
472         let owner = match self.owner {
473             AttrDefId::ModuleId(module) => {
474                 // Modules can have 2 attribute owners (the `mod x;` item, and the module file itself).
475 
476                 let def_map = module.def_map(db);
477                 let mod_data = &def_map[module.local_id];
478                 match mod_data.declaration_source(db) {
479                     Some(it) => {
480                         let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value));
481                         if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
482                             mod_data.definition_source(db)
483                         {
484                             map.append_module_inline_attrs(AttrSourceMap::new(InFile::new(
485                                 file_id, &file,
486                             )));
487                         }
488                         return map;
489                     }
490                     None => {
491                         let InFile { file_id, value } = mod_data.definition_source(db);
492                         let attrs_owner = match &value {
493                             ModuleSource::SourceFile(file) => file as &dyn ast::HasAttrs,
494                             ModuleSource::Module(module) => module as &dyn ast::HasAttrs,
495                             ModuleSource::BlockExpr(block) => block as &dyn ast::HasAttrs,
496                         };
497                         return AttrSourceMap::new(InFile::new(file_id, attrs_owner));
498                     }
499                 }
500             }
501             AttrDefId::FieldId(id) => {
502                 let map = db.fields_attrs_source_map(id.parent);
503                 let file_id = id.parent.file_id(db);
504                 let root = db.parse_or_expand(file_id);
505                 let owner = match &map[id.local_id] {
506                     Either::Left(it) => ast::AnyHasAttrs::new(it.to_node(&root)),
507                     Either::Right(it) => ast::AnyHasAttrs::new(it.to_node(&root)),
508                 };
509                 InFile::new(file_id, owner)
510             }
511             AttrDefId::AdtId(adt) => match adt {
512                 AdtId::StructId(id) => any_has_attrs(db, id),
513                 AdtId::UnionId(id) => any_has_attrs(db, id),
514                 AdtId::EnumId(id) => any_has_attrs(db, id),
515             },
516             AttrDefId::FunctionId(id) => any_has_attrs(db, id),
517             AttrDefId::EnumVariantId(id) => {
518                 let map = db.variants_attrs_source_map(id.parent);
519                 let file_id = id.parent.lookup(db).id.file_id();
520                 let root = db.parse_or_expand(file_id);
521                 InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root)))
522             }
523             AttrDefId::StaticId(id) => any_has_attrs(db, id),
524             AttrDefId::ConstId(id) => any_has_attrs(db, id),
525             AttrDefId::TraitId(id) => any_has_attrs(db, id),
526             AttrDefId::TraitAliasId(id) => any_has_attrs(db, id),
527             AttrDefId::TypeAliasId(id) => any_has_attrs(db, id),
528             AttrDefId::MacroId(id) => match id {
529                 MacroId::Macro2Id(id) => any_has_attrs(db, id),
530                 MacroId::MacroRulesId(id) => any_has_attrs(db, id),
531                 MacroId::ProcMacroId(id) => any_has_attrs(db, id),
532             },
533             AttrDefId::ImplId(id) => any_has_attrs(db, id),
534             AttrDefId::GenericParamId(id) => match id {
535                 GenericParamId::ConstParamId(id) => id
536                     .parent()
537                     .child_source(db)
538                     .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())),
539                 GenericParamId::TypeParamId(id) => id
540                     .parent()
541                     .child_source(db)
542                     .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())),
543                 GenericParamId::LifetimeParamId(id) => id
544                     .parent
545                     .child_source(db)
546                     .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
547             },
548             AttrDefId::ExternBlockId(id) => any_has_attrs(db, id),
549         };
550 
551         AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs))
552     }
553 
docs_with_rangemap( &self, db: &dyn DefDatabase, ) -> Option<(Documentation, DocsRangeMap)>554     pub fn docs_with_rangemap(
555         &self,
556         db: &dyn DefDatabase,
557     ) -> Option<(Documentation, DocsRangeMap)> {
558         let docs =
559             self.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id)));
560         let indent = doc_indent(self);
561         let mut buf = String::new();
562         let mut mapping = Vec::new();
563         for (doc, idx) in docs {
564             if !doc.is_empty() {
565                 let mut base_offset = 0;
566                 for raw_line in doc.split('\n') {
567                     let line = raw_line.trim_end();
568                     let line_len = line.len();
569                     let (offset, line) = match line.char_indices().nth(indent) {
570                         Some((offset, _)) => (offset, &line[offset..]),
571                         None => (0, line),
572                     };
573                     let buf_offset = buf.len();
574                     buf.push_str(line);
575                     mapping.push((
576                         TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?),
577                         idx,
578                         TextRange::at(
579                             (base_offset + offset).try_into().ok()?,
580                             line_len.try_into().ok()?,
581                         ),
582                     ));
583                     buf.push('\n');
584                     base_offset += raw_line.len() + 1;
585                 }
586             } else {
587                 buf.push('\n');
588             }
589         }
590         buf.pop();
591         if buf.is_empty() {
592             None
593         } else {
594             Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) }))
595         }
596     }
597 }
598 
doc_indent(attrs: &Attrs) -> usize599 fn doc_indent(attrs: &Attrs) -> usize {
600     attrs
601         .by_key("doc")
602         .attrs()
603         .filter_map(|attr| attr.string_value())
604         .flat_map(|s| s.lines())
605         .filter(|line| !line.chars().all(|c| c.is_whitespace()))
606         .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
607         .min()
608         .unwrap_or(0)
609 }
610 
611 #[derive(Debug)]
612 pub struct AttrSourceMap {
613     source: Vec<Either<ast::Attr, ast::Comment>>,
614     file_id: HirFileId,
615     /// If this map is for a module, this will be the [`HirFileId`] of the module's definition site,
616     /// while `file_id` will be the one of the module declaration site.
617     /// The usize is the index into `source` from which point on the entries reside in the def site
618     /// file.
619     mod_def_site_file_id: Option<(HirFileId, usize)>,
620 }
621 
622 impl AttrSourceMap {
new(owner: InFile<&dyn ast::HasAttrs>) -> Self623     fn new(owner: InFile<&dyn ast::HasAttrs>) -> Self {
624         Self {
625             source: collect_attrs(owner.value).map(|(_, it)| it).collect(),
626             file_id: owner.file_id,
627             mod_def_site_file_id: None,
628         }
629     }
630 
631     /// Append a second source map to this one, this is required for modules, whose outline and inline
632     /// attributes can reside in different files
append_module_inline_attrs(&mut self, other: Self)633     fn append_module_inline_attrs(&mut self, other: Self) {
634         assert!(self.mod_def_site_file_id.is_none() && other.mod_def_site_file_id.is_none());
635         let len = self.source.len();
636         self.source.extend(other.source);
637         if other.file_id != self.file_id {
638             self.mod_def_site_file_id = Some((other.file_id, len));
639         }
640     }
641 
642     /// Maps the lowered `Attr` back to its original syntax node.
643     ///
644     /// `attr` must come from the `owner` used for AttrSourceMap
645     ///
646     /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
647     /// the attribute represented by `Attr`.
source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>>648     pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> {
649         self.source_of_id(attr.id)
650     }
651 
source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>>652     fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> {
653         let ast_idx = id.ast_index();
654         let file_id = match self.mod_def_site_file_id {
655             Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id,
656             _ => self.file_id,
657         };
658 
659         self.source
660             .get(ast_idx)
661             .map(|it| InFile::new(file_id, it))
662             .unwrap_or_else(|| panic!("cannot find attr at index {id:?}"))
663     }
664 }
665 
666 /// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
667 #[derive(Debug)]
668 pub struct DocsRangeMap {
669     source_map: AttrSourceMap,
670     // (docstring-line-range, attr_index, attr-string-range)
671     // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
672     // the original (untrimmed) syntax doc line
673     mapping: Vec<(TextRange, AttrId, TextRange)>,
674 }
675 
676 impl DocsRangeMap {
677     /// Maps a [`TextRange`] relative to the documentation string back to its AST range
map(&self, range: TextRange) -> Option<InFile<TextRange>>678     pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
679         let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
680         let (line_docs_range, idx, original_line_src_range) = self.mapping[found];
681         if !line_docs_range.contains_range(range) {
682             return None;
683         }
684 
685         let relative_range = range - line_docs_range.start();
686 
687         let InFile { file_id, value: source } = self.source_map.source_of_id(idx);
688         match source {
689             Either::Left(attr) => {
690                 let string = get_doc_string_in_attr(attr)?;
691                 let text_range = string.open_quote_text_range()?;
692                 let range = TextRange::at(
693                     text_range.end() + original_line_src_range.start() + relative_range.start(),
694                     string.syntax().text_range().len().min(range.len()),
695                 );
696                 Some(InFile { file_id, value: range })
697             }
698             Either::Right(comment) => {
699                 let text_range = comment.syntax().text_range();
700                 let range = TextRange::at(
701                     text_range.start()
702                         + TextSize::try_from(comment.prefix().len()).ok()?
703                         + original_line_src_range.start()
704                         + relative_range.start(),
705                     text_range.len().min(range.len()),
706                 );
707                 Some(InFile { file_id, value: range })
708             }
709         }
710     }
711 }
712 
get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String>713 fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> {
714     match it.expr() {
715         // #[doc = lit]
716         Some(ast::Expr::Literal(lit)) => match lit.kind() {
717             ast::LiteralKind::String(it) => Some(it),
718             _ => None,
719         },
720         // #[cfg_attr(..., doc = "", ...)]
721         None => {
722             // FIXME: See highlight injection for what to do here
723             None
724         }
725         _ => None,
726     }
727 }
728 
729 #[derive(Debug, Clone, Copy)]
730 pub struct AttrQuery<'attr> {
731     attrs: &'attr Attrs,
732     key: &'static str,
733 }
734 
735 impl<'attr> AttrQuery<'attr> {
tt_values(self) -> impl Iterator<Item = &'attr crate::tt::Subtree>736     pub fn tt_values(self) -> impl Iterator<Item = &'attr crate::tt::Subtree> {
737         self.attrs().filter_map(|attr| attr.token_tree_value())
738     }
739 
string_value(self) -> Option<&'attr SmolStr>740     pub fn string_value(self) -> Option<&'attr SmolStr> {
741         self.attrs().find_map(|attr| attr.string_value())
742     }
743 
exists(self) -> bool744     pub fn exists(self) -> bool {
745         self.attrs().next().is_some()
746     }
747 
attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone748     pub fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
749         let key = self.key;
750         self.attrs
751             .iter()
752             .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key))
753     }
754 
755     /// Find string value for a specific key inside token tree
756     ///
757     /// ```ignore
758     /// #[doc(html_root_url = "url")]
759     ///       ^^^^^^^^^^^^^ key
760     /// ```
find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr>761     pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> {
762         self.tt_values().find_map(|tt| {
763             let name = tt.token_trees.iter()
764                 .skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key))
765                 .nth(2);
766 
767             match name {
768                 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ ref text, ..}))) => Some(text),
769                 _ => None
770             }
771         })
772     }
773 }
774 
any_has_attrs( db: &dyn DefDatabase, id: impl Lookup<Data = impl HasSource<Value = impl ast::HasAttrs>>, ) -> InFile<ast::AnyHasAttrs>775 fn any_has_attrs(
776     db: &dyn DefDatabase,
777     id: impl Lookup<Data = impl HasSource<Value = impl ast::HasAttrs>>,
778 ) -> InFile<ast::AnyHasAttrs> {
779     id.lookup(db).source(db).map(ast::AnyHasAttrs::new)
780 }
781 
attrs_from_item_tree<N: ItemTreeNode>(db: &dyn DefDatabase, id: ItemTreeId<N>) -> RawAttrs782 fn attrs_from_item_tree<N: ItemTreeNode>(db: &dyn DefDatabase, id: ItemTreeId<N>) -> RawAttrs {
783     let tree = id.item_tree(db);
784     let mod_item = N::id_to_mod_item(id.value);
785     tree.raw_attrs(mod_item.into()).clone()
786 }
787 
attrs_from_item_tree_loc<N: ItemTreeNode>( db: &dyn DefDatabase, lookup: impl Lookup<Data = ItemLoc<N>>, ) -> RawAttrs788 fn attrs_from_item_tree_loc<N: ItemTreeNode>(
789     db: &dyn DefDatabase,
790     lookup: impl Lookup<Data = ItemLoc<N>>,
791 ) -> RawAttrs {
792     let id = lookup.lookup(db).id;
793     attrs_from_item_tree(db, id)
794 }
795 
attrs_from_item_tree_assoc<N: ItemTreeNode>( db: &dyn DefDatabase, lookup: impl Lookup<Data = AssocItemLoc<N>>, ) -> RawAttrs796 fn attrs_from_item_tree_assoc<N: ItemTreeNode>(
797     db: &dyn DefDatabase,
798     lookup: impl Lookup<Data = AssocItemLoc<N>>,
799 ) -> RawAttrs {
800     let id = lookup.lookup(db).id;
801     attrs_from_item_tree(db, id)
802 }
803 
variants_attrs_source_map( db: &dyn DefDatabase, def: EnumId, ) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>>804 pub(crate) fn variants_attrs_source_map(
805     db: &dyn DefDatabase,
806     def: EnumId,
807 ) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>> {
808     let mut res = ArenaMap::default();
809     let child_source = def.child_source(db);
810 
811     for (idx, variant) in child_source.value.iter() {
812         res.insert(idx, AstPtr::new(variant));
813     }
814 
815     Arc::new(res)
816 }
817 
fields_attrs_source_map( db: &dyn DefDatabase, def: VariantId, ) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>>818 pub(crate) fn fields_attrs_source_map(
819     db: &dyn DefDatabase,
820     def: VariantId,
821 ) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>> {
822     let mut res = ArenaMap::default();
823     let child_source = def.child_source(db);
824 
825     for (idx, variant) in child_source.value.iter() {
826         res.insert(
827             idx,
828             variant
829                 .as_ref()
830                 .either(|l| Either::Left(AstPtr::new(l)), |r| Either::Right(AstPtr::new(r))),
831         );
832     }
833 
834     Arc::new(res)
835 }
836