From 8ea9d939d2c44feb71e2d1c8ec390a9471b75e57 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Mon, 10 May 2021 22:54:17 +0200 Subject: Rewrite `#[derive]` removal to be based on AST --- crates/hir_expand/src/db.rs | 7 +-- crates/hir_expand/src/input.rs | 94 +++++++++++++++++++++++++++++++++ crates/hir_expand/src/lib.rs | 16 +++++- crates/hir_expand/src/proc_macro.rs | 102 ------------------------------------ 4 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 crates/hir_expand/src/input.rs diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 6647e57e7..9fa419fcf 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 { }; 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 000000000..112216859 --- /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 57e08eeb0..5df11856e 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,8 +293,19 @@ pub struct MacroCallLoc { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MacroCallKind { - FnLike { ast_id: AstId, fragment: FragmentKind }, - Derive { ast_id: AstId, derive_name: String, derive_attr_index: u32 }, + FnLike { + ast_id: AstId, + fragment: FragmentKind, + }, + Derive { + ast_id: AstId, + 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, + }, } impl MacroCallKind { diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs index 75e950816..d5643393a 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 { - 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 - ); - } -} -- cgit v1.2.3