diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-05-11 17:47:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-05-11 17:47:45 +0000 |
| commit | 6afd9b2b8dcfaa9338303f29d8fc6c90dbcdd6e7 (patch) | |
| tree | b1534743851c3805c64fe764f9d0bf8f7ab9f7ef | |
| parent | 9fa9d166d8141bb9ca4fcf0544c49b903fb85e09 (diff) | |
| parent | 8ea9d939d2c44feb71e2d1c8ec390a9471b75e57 (diff) | |
| download | rust-6afd9b2b8dcfaa9338303f29d8fc6c90dbcdd6e7.tar.gz rust-6afd9b2b8dcfaa9338303f29d8fc6c90dbcdd6e7.zip | |
Merge #8796
8796: internal: rewrite `#[derive]` removal to be based on AST (take 2) r=jonas-schievink a=jonas-schievink Second attempt of https://github.com/rust-analyzer/rust-analyzer/pull/8443, this uses syntactical attribute offsets in `hir_expand`, and changes `attr.rs` to make those easy to derive. This will make it easy to add similar attribute removal for attribute macros, unblocking them. Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
| -rw-r--r-- | crates/hir_def/src/attr.rs | 144 | ||||
| -rw-r--r-- | crates/hir_def/src/lib.rs | 6 | ||||
| -rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 4 | ||||
| -rw-r--r-- | crates/hir_expand/src/builtin_derive.rs | 4 | ||||
| -rw-r--r-- | crates/hir_expand/src/db.rs | 7 | ||||
| -rw-r--r-- | crates/hir_expand/src/input.rs | 94 | ||||
| -rw-r--r-- | crates/hir_expand/src/lib.rs | 19 | ||||
| -rw-r--r-- | crates/hir_expand/src/proc_macro.rs | 102 |
8 files changed, 205 insertions, 175 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index a2479016e57..aadd4e44ae7 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs @@ -9,7 +9,7 @@ use std::{ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_expand::{hygiene::Hygiene, name::AsName, AstId, AttrId, InFile}; +use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile}; use itertools::Itertools; use la_arena::ArenaMap; use mbe::ast_to_token_tree; @@ -101,17 +101,13 @@ impl RawAttrs { hygiene: &Hygiene, ) -> Self { let entries = collect_attrs(owner) - .enumerate() - .flat_map(|(i, attr)| { - let index = AttrId(i as u32); - match attr { - Either::Left(attr) => Attr::from_src(db, attr, hygiene, index), - Either::Right(comment) => comment.doc_comment().map(|doc| Attr { - id: index, - input: Some(AttrInput::Literal(SmolStr::new(doc))), - path: Interned::new(ModPath::from(hir_expand::name!(doc))), - }), - } + .flat_map(|(id, attr)| match attr { + Either::Left(attr) => Attr::from_src(db, attr, hygiene, id), + Either::Right(comment) => comment.doc_comment().map(|doc| Attr { + id, + input: Some(AttrInput::Literal(SmolStr::new(doc))), + path: Interned::new(ModPath::from(hir_expand::name!(doc))), + }), }) .collect::<Arc<_>>(); @@ -124,6 +120,7 @@ impl RawAttrs { } pub(crate) fn merge(&self, other: Self) -> Self { + // FIXME: This needs to fixup `AttrId`s match (&self.entries, &other.entries) { (None, None) => Self::EMPTY, (Some(entries), None) | (None, Some(entries)) => { @@ -375,39 +372,26 @@ impl AttrsWithOwner { let def_map = module.def_map(db); let mod_data = &def_map[module.local_id]; - let attrs = match mod_data.declaration_source(db) { + match mod_data.declaration_source(db) { Some(it) => { - let mut attrs: Vec<_> = collect_attrs(&it.value as &dyn ast::AttrsOwner) - .map(|attr| InFile::new(it.file_id, attr)) - .collect(); + let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value)); if let InFile { file_id, value: ModuleSource::SourceFile(file) } = mod_data.definition_source(db) { - attrs.extend( - collect_attrs(&file as &dyn ast::AttrsOwner) - .map(|attr| InFile::new(file_id, attr)), - ) + map.merge(AttrSourceMap::new(InFile::new(file_id, &file))); } - attrs + return map; } None => { let InFile { file_id, value } = mod_data.definition_source(db); - match &value { - ModuleSource::SourceFile(file) => { - collect_attrs(file as &dyn ast::AttrsOwner) - } - ModuleSource::Module(module) => { - collect_attrs(module as &dyn ast::AttrsOwner) - } - ModuleSource::BlockExpr(block) => { - collect_attrs(block as &dyn ast::AttrsOwner) - } - } - .map(|attr| InFile::new(file_id, attr)) - .collect() + let attrs_owner = match &value { + ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner, + ModuleSource::Module(module) => module as &dyn ast::AttrsOwner, + ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner, + }; + return AttrSourceMap::new(InFile::new(file_id, attrs_owner)); } - }; - return AttrSourceMap { attrs }; + } } AttrDefId::FieldId(id) => { let map = db.fields_attrs_source_map(id.parent); @@ -462,11 +446,7 @@ impl AttrsWithOwner { }, }; - AttrSourceMap { - attrs: collect_attrs(&owner.value) - .map(|attr| InFile::new(owner.file_id, attr)) - .collect(), - } + AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn AttrsOwner)) } pub fn docs_with_rangemap( @@ -518,7 +498,7 @@ impl AttrsWithOwner { if buf.is_empty() { None } else { - Some((Documentation(buf), DocsRangeMap { mapping, source: self.source_map(db).attrs })) + Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) })) } } } @@ -559,27 +539,59 @@ fn inner_attributes( } pub struct AttrSourceMap { - attrs: Vec<InFile<Either<ast::Attr, ast::Comment>>>, + attrs: Vec<InFile<ast::Attr>>, + doc_comments: Vec<InFile<ast::Comment>>, } impl AttrSourceMap { + fn new(owner: InFile<&dyn ast::AttrsOwner>) -> Self { + let mut attrs = Vec::new(); + let mut doc_comments = Vec::new(); + for (_, attr) in collect_attrs(owner.value) { + match attr { + Either::Left(attr) => attrs.push(owner.with_value(attr)), + Either::Right(comment) => doc_comments.push(owner.with_value(comment)), + } + } + + Self { attrs, doc_comments } + } + + fn merge(&mut self, other: Self) { + self.attrs.extend(other.attrs); + self.doc_comments.extend(other.doc_comments); + } + /// Maps the lowered `Attr` back to its original syntax node. /// /// `attr` must come from the `owner` used for AttrSourceMap /// /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of /// the attribute represented by `Attr`. - pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> { - self.attrs - .get(attr.id.0 as usize) - .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", attr.id)) - .as_ref() + pub fn source_of(&self, attr: &Attr) -> InFile<Either<ast::Attr, ast::Comment>> { + self.source_of_id(attr.id) + } + + fn source_of_id(&self, id: AttrId) -> InFile<Either<ast::Attr, ast::Comment>> { + if id.is_doc_comment { + self.doc_comments + .get(id.ast_index as usize) + .unwrap_or_else(|| panic!("cannot find doc comment at index {:?}", id)) + .clone() + .map(|attr| Either::Right(attr)) + } else { + self.attrs + .get(id.ast_index as usize) + .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", id)) + .clone() + .map(|attr| Either::Left(attr)) + } } } /// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. pub struct DocsRangeMap { - source: Vec<InFile<Either<ast::Attr, ast::Comment>>>, + source_map: AttrSourceMap, // (docstring-line-range, attr_index, attr-string-range) // a mapping from the text range of a line of the [`Documentation`] to the attribute index and // the original (untrimmed) syntax doc line @@ -596,7 +608,7 @@ impl DocsRangeMap { let relative_range = range - line_docs_range.start(); - let &InFile { file_id, value: ref source } = &self.source[idx.0 as usize]; + let &InFile { file_id, value: ref source } = &self.source_map.source_of_id(idx); match source { Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here // as well as for whats done in syntax highlight doc injection @@ -615,6 +627,12 @@ impl DocsRangeMap { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct AttrId { + is_doc_comment: bool, + pub(crate) ast_index: u32, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Attr { pub(crate) id: AttrId, @@ -749,22 +767,32 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase fn collect_attrs( owner: &dyn ast::AttrsOwner, -) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { +) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> { let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs))); let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer()); - let attrs = outer_attrs - .chain(inner_attrs.into_iter().flatten()) - .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr))); + let attrs = + outer_attrs.chain(inner_attrs.into_iter().flatten()).enumerate().map(|(idx, attr)| { + ( + AttrId { ast_index: idx as u32, is_doc_comment: false }, + attr.syntax().text_range().start(), + Either::Left(attr), + ) + }); let outer_docs = ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer); - let docs = outer_docs - .chain(inner_docs.into_iter().flatten()) - .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text))); + let docs = + outer_docs.chain(inner_docs.into_iter().flatten()).enumerate().map(|(idx, docs_text)| { + ( + AttrId { ast_index: idx as u32, is_doc_comment: true }, + docs_text.syntax().text_range().start(), + Either::Right(docs_text), + ) + }); // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved - docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).map(|(_, attr)| attr) + docs.chain(attrs).sorted_by_key(|&(_, offset, _)| offset).map(|(id, _, attr)| (id, attr)) } pub(crate) fn variants_attrs_source_map( diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index e96ca953ff7..a82ea5957e9 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs @@ -62,14 +62,14 @@ use hir_expand::{ ast_id_map::FileAstId, eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, hygiene::Hygiene, - AstId, AttrId, FragmentKind, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, - MacroDefKind, + AstId, FragmentKind, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, }; use la_arena::Idx; use nameres::DefMap; use path::ModPath; use syntax::ast; +use crate::attr::AttrId; use crate::builtin_type::BuiltinType; use item_tree::{ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, @@ -753,7 +753,7 @@ fn derive_macro_as_call_id( MacroCallKind::Derive { ast_id: item_attr.ast_id, derive_name: last_segment.to_string(), - derive_attr, + derive_attr_index: derive_attr.ast_index, }, ) .into(); diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index e89136ed1dc..adfb78c942f 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -13,14 +13,14 @@ use hir_expand::{ builtin_macro::find_builtin_macro, name::{AsName, Name}, proc_macro::ProcMacroExpander, - AttrId, FragmentKind, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, + FragmentKind, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, }; use hir_expand::{InFile, MacroCallLoc}; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::ast; use crate::{ - attr::Attrs, + attr::{AttrId, Attrs}, db::DefDatabase, derive_macro_as_call_id, intern::Interned, diff --git a/crates/hir_expand/src/builtin_derive.rs b/crates/hir_expand/src/builtin_derive.rs index 537c0302866..b6a6d602f7f 100644 --- a/crates/hir_expand/src/builtin_derive.rs +++ b/crates/hir_expand/src/builtin_derive.rs @@ -269,7 +269,7 @@ mod tests { use expect_test::{expect, Expect}; use name::AsName; - use crate::{test_db::TestDB, AstId, AttrId, MacroCallId, MacroCallKind, MacroCallLoc}; + use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc}; use super::*; @@ -320,7 +320,7 @@ $0 kind: MacroCallKind::Derive { ast_id, derive_name: name.to_string(), - derive_attr: AttrId(0), + derive_attr_index: 0, }, }; diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 6647e57e7dc..9fa419fcf14 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -12,9 +12,9 @@ use syntax::{ }; use crate::{ - ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander, - EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, - MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, + ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander, + BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, + MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, }; /// Total limit on the number of tokens produced by any macro invocation. @@ -281,6 +281,7 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { }; let loc = db.lookup_intern_macro(id); let arg = loc.kind.arg(db)?; + let arg = process_macro_input(db, arg, id); Some(arg.green().into()) } diff --git a/crates/hir_expand/src/input.rs b/crates/hir_expand/src/input.rs new file mode 100644 index 00000000000..112216859cf --- /dev/null +++ b/crates/hir_expand/src/input.rs @@ -0,0 +1,94 @@ +//! Macro input conditioning. + +use syntax::{ + ast::{self, AttrsOwner}, + AstNode, SyntaxNode, +}; + +use crate::{ + db::AstDatabase, + name::{name, AsName}, + LazyMacroId, MacroCallKind, MacroCallLoc, +}; + +pub(crate) fn process_macro_input( + db: &dyn AstDatabase, + node: SyntaxNode, + id: LazyMacroId, +) -> SyntaxNode { + let loc: MacroCallLoc = db.lookup_intern_macro(id); + + match loc.kind { + MacroCallKind::FnLike { .. } => node, + MacroCallKind::Derive { derive_attr_index, .. } => { + let item = match ast::Item::cast(node.clone()) { + Some(item) => item, + None => return node, + }; + + remove_derives_up_to(item, derive_attr_index as usize).syntax().clone() + } + } +} + +/// Removes `#[derive]` attributes from `item`, up to `attr_index`. +fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item { + let item = item.clone_for_update(); + for attr in item.attrs().take(attr_index + 1) { + if let Some(name) = + attr.path().and_then(|path| path.as_single_segment()).and_then(|seg| seg.name_ref()) + { + if name.as_name() == name![derive] { + attr.syntax().detach(); + } + } + } + item +} + +#[cfg(test)] +mod tests { + use base_db::fixture::WithFixture; + use base_db::SourceDatabase; + use expect_test::{expect, Expect}; + + use crate::test_db::TestDB; + + use super::*; + + fn test_remove_derives_up_to(attr: usize, ra_fixture: &str, expect: Expect) { + let (db, file_id) = TestDB::with_single_file(&ra_fixture); + let parsed = db.parse(file_id); + + let mut items: Vec<_> = + parsed.syntax_node().descendants().filter_map(ast::Item::cast).collect(); + assert_eq!(items.len(), 1); + + let item = remove_derives_up_to(items.pop().unwrap(), attr); + expect.assert_eq(&item.to_string()); + } + + #[test] + fn remove_derive() { + test_remove_derives_up_to( + 2, + r#" +#[allow(unused)] +#[derive(Copy)] +#[derive(Hello)] +#[derive(Clone)] +struct A { + bar: u32 +} + "#, + expect![[r#" +#[allow(unused)] + + +#[derive(Clone)] +struct A { + bar: u32 +}"#]], + ); + } +} diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 80ab3aeee7e..5df11856e90 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -14,6 +14,7 @@ pub mod builtin_macro; pub mod proc_macro; pub mod quote; pub mod eager; +mod input; use either::Either; @@ -292,13 +293,21 @@ pub struct MacroCallLoc { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MacroCallKind { - FnLike { ast_id: AstId<ast::MacroCall>, fragment: FragmentKind }, - Derive { ast_id: AstId<ast::Item>, derive_name: String, derive_attr: AttrId }, + FnLike { + ast_id: AstId<ast::MacroCall>, + fragment: FragmentKind, + }, + Derive { + ast_id: AstId<ast::Item>, + derive_name: String, + /// Syntactical index of the invoking `#[derive]` attribute. + /// + /// Outer attributes are counted first, then inner attributes. This does not support + /// out-of-line modules, which may have attributes spread across 2 files! + derive_attr_index: u32, + }, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AttrId(pub u32); - impl MacroCallKind { fn file_id(&self) -> HirFileId { match self { diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs index 75e95081609..d5643393ae2 100644 --- a/crates/hir_expand/src/proc_macro.rs +++ b/crates/hir_expand/src/proc_macro.rs @@ -2,7 +2,6 @@ use crate::db::AstDatabase; use base_db::{CrateId, ProcMacroId}; -use tt::buffer::{Cursor, TokenBuffer}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct ProcMacroExpander { @@ -44,9 +43,6 @@ impl ProcMacroExpander { .clone() .ok_or_else(|| err!("No derive macro found."))?; - let tt = remove_derive_attrs(tt) - .ok_or_else(|| err!("Fail to remove derive for custom derive"))?; - // Proc macros have access to the environment variables of the invoking crate. let env = &krate_graph[calling_crate].env; @@ -56,101 +52,3 @@ impl ProcMacroExpander { } } } - -fn eat_punct(cursor: &mut Cursor, c: char) -> bool { - if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(punct), _)) = cursor.token_tree() { - if punct.char == c { - *cursor = cursor.bump(); - return true; - } - } - false -} - -fn eat_subtree(cursor: &mut Cursor, kind: tt::DelimiterKind) -> bool { - if let Some(tt::buffer::TokenTreeRef::Subtree(subtree, _)) = cursor.token_tree() { - if Some(kind) == subtree.delimiter_kind() { - *cursor = cursor.bump_subtree(); - return true; - } - } - false -} - -fn eat_ident(cursor: &mut Cursor, t: &str) -> bool { - if let Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Ident(ident), _)) = cursor.token_tree() { - if t == ident.text.as_str() { - *cursor = cursor.bump(); - return true; - } - } - false -} - -fn remove_derive_attrs(tt: &tt::Subtree) -> Option<tt::Subtree> { - let buffer = TokenBuffer::from_tokens(&tt.token_trees); - let mut p = buffer.begin(); - let mut result = tt::Subtree::default(); - - while !p.eof() { - let curr = p; - - if eat_punct(&mut p, '#') { - eat_punct(&mut p, '!'); - let parent = p; - if eat_subtree(&mut p, tt::DelimiterKind::Bracket) { - if eat_ident(&mut p, "derive") { - p = parent.bump(); - continue; - } - } - } - - result.token_trees.push(curr.token_tree()?.cloned()); - p = curr.bump(); - } - - Some(result) -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::assert_eq_text; - - #[test] - fn test_remove_derive_attrs() { - let tt = mbe::parse_to_token_tree( - r#" - #[allow(unused)] - #[derive(Copy)] - #[derive(Hello)] - struct A { - bar: u32 - } -"#, - ) - .unwrap() - .0; - let result = format!("{:#?}", remove_derive_attrs(&tt).unwrap()); - - assert_eq_text!( - r#" -SUBTREE $ - PUNCH # [alone] 0 - SUBTREE [] 1 - IDENT allow 2 - SUBTREE () 3 - IDENT unused 4 - IDENT struct 15 - IDENT A 16 - SUBTREE {} 17 - IDENT bar 18 - PUNCH : [alone] 19 - IDENT u32 20 -"# - .trim(), - &result - ); - } -} |
