aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/attr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r--crates/hir_def/src/attr.rs199
1 files changed, 165 insertions, 34 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index 52a2bce9b..786fad6e1 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -1,23 +1,28 @@
1//! A higher level attributes based on TokenTree, with also some shortcuts. 1//! A higher level attributes based on TokenTree, with also some shortcuts.
2 2
3use std::{ops, sync::Arc}; 3use std::{
4 convert::{TryFrom, TryInto},
5 ops,
6 sync::Arc,
7};
4 8
5use base_db::CrateId; 9use base_db::CrateId;
6use cfg::{CfgExpr, CfgOptions}; 10use cfg::{CfgExpr, CfgOptions};
7use either::Either; 11use either::Either;
8use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile}; 12use hir_expand::{hygiene::Hygiene, name::AsName, AstId, AttrId, InFile};
9use itertools::Itertools; 13use itertools::Itertools;
10use la_arena::ArenaMap; 14use la_arena::ArenaMap;
11use mbe::ast_to_token_tree; 15use mbe::ast_to_token_tree;
12use smallvec::{smallvec, SmallVec}; 16use smallvec::{smallvec, SmallVec};
13use syntax::{ 17use syntax::{
14 ast::{self, AstNode, AttrsOwner}, 18 ast::{self, AstNode, AttrsOwner},
15 match_ast, AstToken, SmolStr, SyntaxNode, 19 match_ast, AstPtr, AstToken, SmolStr, SyntaxNode, TextRange, TextSize,
16}; 20};
17use tt::Subtree; 21use tt::Subtree;
18 22
19use crate::{ 23use crate::{
20 db::DefDatabase, 24 db::DefDatabase,
25 intern::Interned,
21 item_tree::{ItemTreeId, ItemTreeNode}, 26 item_tree::{ItemTreeId, ItemTreeNode},
22 nameres::ModuleSource, 27 nameres::ModuleSource,
23 path::{ModPath, PathKind}, 28 path::{ModPath, PathKind},
@@ -93,13 +98,16 @@ impl RawAttrs {
93 pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self { 98 pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self {
94 let entries = collect_attrs(owner) 99 let entries = collect_attrs(owner)
95 .enumerate() 100 .enumerate()
96 .flat_map(|(i, attr)| match attr { 101 .flat_map(|(i, attr)| {
97 Either::Left(attr) => Attr::from_src(attr, hygiene, i as u32), 102 let index = AttrId(i as u32);
98 Either::Right(comment) => comment.doc_comment().map(|doc| Attr { 103 match attr {
99 index: i as u32, 104 Either::Left(attr) => Attr::from_src(attr, hygiene, index),
100 input: Some(AttrInput::Literal(SmolStr::new(doc))), 105 Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
101 path: ModPath::from(hir_expand::name!(doc)), 106 id: index,
102 }), 107 input: Some(AttrInput::Literal(SmolStr::new(doc))),
108 path: Interned::new(ModPath::from(hir_expand::name!(doc))),
109 }),
110 }
103 }) 111 })
104 .collect::<Arc<_>>(); 112 .collect::<Arc<_>>();
105 113
@@ -156,7 +164,7 @@ impl RawAttrs {
156 let cfg = parts.next().unwrap(); 164 let cfg = parts.next().unwrap();
157 let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; 165 let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() };
158 let cfg = CfgExpr::parse(&cfg); 166 let cfg = CfgExpr::parse(&cfg);
159 let index = attr.index; 167 let index = attr.id;
160 let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| { 168 let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| {
161 let tree = Subtree { delimiter: None, token_trees: attr.to_vec() }; 169 let tree = Subtree { delimiter: None, token_trees: attr.to_vec() };
162 let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; 170 let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?;
@@ -210,12 +218,11 @@ impl Attrs {
210 let mut res = ArenaMap::default(); 218 let mut res = ArenaMap::default();
211 219
212 for (id, fld) in src.value.iter() { 220 for (id, fld) in src.value.iter() {
213 let attrs = match fld { 221 let owner: &dyn AttrsOwner = match fld {
214 Either::Left(_tuple) => Attrs::default(), 222 Either::Left(tuple) => tuple,
215 Either::Right(record) => { 223 Either::Right(record) => record,
216 RawAttrs::from_attrs_owner(db, src.with_value(record)).filter(db, krate)
217 }
218 }; 224 };
225 let attrs = RawAttrs::from_attrs_owner(db, src.with_value(owner)).filter(db, krate);
219 226
220 res.insert(id, attrs); 227 res.insert(id, attrs);
221 } 228 }
@@ -399,10 +406,14 @@ impl AttrsWithOwner {
399 return AttrSourceMap { attrs }; 406 return AttrSourceMap { attrs };
400 } 407 }
401 AttrDefId::FieldId(id) => { 408 AttrDefId::FieldId(id) => {
402 id.parent.child_source(db).map(|source| match &source[id.local_id] { 409 let map = db.fields_attrs_source_map(id.parent);
403 Either::Left(field) => ast::AttrsOwnerNode::new(field.clone()), 410 let file_id = id.parent.file_id(db);
404 Either::Right(field) => ast::AttrsOwnerNode::new(field.clone()), 411 let root = db.parse_or_expand(file_id).unwrap();
405 }) 412 let owner = match &map[id.local_id] {
413 Either::Left(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
414 Either::Right(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
415 };
416 InFile::new(file_id, owner)
406 } 417 }
407 AttrDefId::AdtId(adt) => match adt { 418 AttrDefId::AdtId(adt) => match adt {
408 AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 419 AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
@@ -410,10 +421,12 @@ impl AttrsWithOwner {
410 AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 421 AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
411 }, 422 },
412 AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 423 AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
413 AttrDefId::EnumVariantId(id) => id 424 AttrDefId::EnumVariantId(id) => {
414 .parent 425 let map = db.variants_attrs_source_map(id.parent);
415 .child_source(db) 426 let file_id = id.parent.lookup(db).id.file_id();
416 .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())), 427 let root = db.parse_or_expand(file_id).unwrap();
428 InFile::new(file_id, ast::AttrsOwnerNode::new(map[id.local_id].to_node(&root)))
429 }
417 AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 430 AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
418 AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 431 AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
419 AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new), 432 AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
@@ -451,6 +464,55 @@ impl AttrsWithOwner {
451 .collect(), 464 .collect(),
452 } 465 }
453 } 466 }
467
468 pub fn docs_with_rangemap(
469 &self,
470 db: &dyn DefDatabase,
471 ) -> Option<(Documentation, DocsRangeMap)> {
472 // FIXME: code duplication in `docs` above
473 let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_ref()? {
474 AttrInput::Literal(s) => Some((s, attr.id)),
475 AttrInput::TokenTree(_) => None,
476 });
477 let indent = docs
478 .clone()
479 .flat_map(|(s, _)| s.lines())
480 .filter(|line| !line.chars().all(|c| c.is_whitespace()))
481 .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
482 .min()
483 .unwrap_or(0);
484 let mut buf = String::new();
485 let mut mapping = Vec::new();
486 for (doc, idx) in docs {
487 // str::lines doesn't yield anything for the empty string
488 if !doc.is_empty() {
489 for line in doc.split('\n') {
490 let line = line.trim_end();
491 let line_len = line.len();
492 let (offset, line) = match line.char_indices().nth(indent) {
493 Some((offset, _)) => (offset, &line[offset..]),
494 None => (0, line),
495 };
496 let buf_offset = buf.len();
497 buf.push_str(line);
498 mapping.push((
499 TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?),
500 idx,
501 TextRange::new(offset.try_into().ok()?, line_len.try_into().ok()?),
502 ));
503 buf.push('\n');
504 }
505 } else {
506 buf.push('\n');
507 }
508 }
509 buf.pop();
510 if buf.is_empty() {
511 None
512 } else {
513 Some((Documentation(buf), DocsRangeMap { mapping, source: self.source_map(db).attrs }))
514 }
515 }
454} 516}
455 517
456fn inner_attributes( 518fn inner_attributes(
@@ -501,16 +563,54 @@ impl AttrSourceMap {
501 /// the attribute represented by `Attr`. 563 /// the attribute represented by `Attr`.
502 pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> { 564 pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> {
503 self.attrs 565 self.attrs
504 .get(attr.index as usize) 566 .get(attr.id.0 as usize)
505 .unwrap_or_else(|| panic!("cannot find `Attr` at index {}", attr.index)) 567 .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", attr.id))
506 .as_ref() 568 .as_ref()
507 } 569 }
508} 570}
509 571
572/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
573pub struct DocsRangeMap {
574 source: Vec<InFile<Either<ast::Attr, ast::Comment>>>,
575 // (docstring-line-range, attr_index, attr-string-range)
576 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
577 // the original (untrimmed) syntax doc line
578 mapping: Vec<(TextRange, AttrId, TextRange)>,
579}
580
581impl DocsRangeMap {
582 pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
583 let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
584 let (line_docs_range, idx, original_line_src_range) = self.mapping[found].clone();
585 if !line_docs_range.contains_range(range) {
586 return None;
587 }
588
589 let relative_range = range - line_docs_range.start();
590
591 let &InFile { file_id, value: ref source } = &self.source[idx.0 as usize];
592 match source {
593 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
594 // as well as for whats done in syntax highlight doc injection
595 Either::Right(comment) => {
596 let text_range = comment.syntax().text_range();
597 let range = TextRange::at(
598 text_range.start()
599 + TextSize::try_from(comment.prefix().len()).ok()?
600 + original_line_src_range.start()
601 + relative_range.start(),
602 text_range.len().min(range.len()),
603 );
604 Some(InFile { file_id, value: range })
605 }
606 }
607 }
608}
609
510#[derive(Debug, Clone, PartialEq, Eq)] 610#[derive(Debug, Clone, PartialEq, Eq)]
511pub struct Attr { 611pub struct Attr {
512 index: u32, 612 pub(crate) id: AttrId,
513 pub(crate) path: ModPath, 613 pub(crate) path: Interned<ModPath>,
514 pub(crate) input: Option<AttrInput>, 614 pub(crate) input: Option<AttrInput>,
515} 615}
516 616
@@ -523,8 +623,8 @@ pub enum AttrInput {
523} 623}
524 624
525impl Attr { 625impl Attr {
526 fn from_src(ast: ast::Attr, hygiene: &Hygiene, index: u32) -> Option<Attr> { 626 fn from_src(ast: ast::Attr, hygiene: &Hygiene, id: AttrId) -> Option<Attr> {
527 let path = ModPath::from_src(ast.path()?, hygiene)?; 627 let path = Interned::new(ModPath::from_src(ast.path()?, hygiene)?);
528 let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { 628 let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
529 let value = match lit.kind() { 629 let value = match lit.kind() {
530 ast::LiteralKind::String(string) => string.value()?.into(), 630 ast::LiteralKind::String(string) => string.value()?.into(),
@@ -532,11 +632,11 @@ impl Attr {
532 }; 632 };
533 Some(AttrInput::Literal(value)) 633 Some(AttrInput::Literal(value))
534 } else if let Some(tt) = ast.token_tree() { 634 } else if let Some(tt) = ast.token_tree() {
535 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) 635 Some(AttrInput::TokenTree(ast_to_token_tree(&tt).0))
536 } else { 636 } else {
537 None 637 None
538 }; 638 };
539 Some(Attr { index, path, input }) 639 Some(Attr { id, path, input })
540 } 640 }
541 641
542 /// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths 642 /// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
@@ -651,7 +751,38 @@ fn collect_attrs(
651 .chain(inner_docs.into_iter().flatten()) 751 .chain(inner_docs.into_iter().flatten())
652 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text))); 752 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text)));
653 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved 753 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
654 let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect(); 754 docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).map(|(_, attr)| attr)
755}
756
757pub(crate) fn variants_attrs_source_map(
758 db: &dyn DefDatabase,
759 def: EnumId,
760) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>> {
761 let mut res = ArenaMap::default();
762 let child_source = def.child_source(db);
763
764 for (idx, variant) in child_source.value.iter() {
765 res.insert(idx, AstPtr::new(variant));
766 }
767
768 Arc::new(res)
769}
770
771pub(crate) fn fields_attrs_source_map(
772 db: &dyn DefDatabase,
773 def: VariantId,
774) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>> {
775 let mut res = ArenaMap::default();
776 let child_source = def.child_source(db);
777
778 for (idx, variant) in child_source.value.iter() {
779 res.insert(
780 idx,
781 variant
782 .as_ref()
783 .either(|l| Either::Left(AstPtr::new(l)), |r| Either::Right(AstPtr::new(r))),
784 );
785 }
655 786
656 attrs.into_iter().map(|(_, attr)| attr) 787 Arc::new(res)
657} 788}