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