diff options
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r-- | crates/hir_def/src/attr.rs | 199 |
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 | ||
3 | use std::{ops, sync::Arc}; | 3 | use std::{ |
4 | convert::{TryFrom, TryInto}, | ||
5 | ops, | ||
6 | sync::Arc, | ||
7 | }; | ||
4 | 8 | ||
5 | use base_db::CrateId; | 9 | use base_db::CrateId; |
6 | use cfg::{CfgExpr, CfgOptions}; | 10 | use cfg::{CfgExpr, CfgOptions}; |
7 | use either::Either; | 11 | use either::Either; |
8 | use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile}; | 12 | use hir_expand::{hygiene::Hygiene, name::AsName, AstId, AttrId, InFile}; |
9 | use itertools::Itertools; | 13 | use itertools::Itertools; |
10 | use la_arena::ArenaMap; | 14 | use la_arena::ArenaMap; |
11 | use mbe::ast_to_token_tree; | 15 | use mbe::ast_to_token_tree; |
12 | use smallvec::{smallvec, SmallVec}; | 16 | use smallvec::{smallvec, SmallVec}; |
13 | use syntax::{ | 17 | use 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 | }; |
17 | use tt::Subtree; | 21 | use tt::Subtree; |
18 | 22 | ||
19 | use crate::{ | 23 | use 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 | ||
456 | fn inner_attributes( | 518 | fn 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. | ||
573 | pub 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 | |||
581 | impl 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)] |
511 | pub struct Attr { | 611 | pub 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 | ||
525 | impl Attr { | 625 | impl 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 | |||
757 | pub(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 | |||
771 | pub(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 | } |