diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index 417c081c6b..9eea5b9b34 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs @@ -210,6 +210,7 @@ impl ChangeFixture { let proc_lib_file = file_id; file_id.0 += 1; + let (proc_macro, source) = test_proc_macros(&proc_macros); let mut fs = FileSet::default(); fs.insert( proc_lib_file, @@ -217,7 +218,7 @@ impl ChangeFixture { ); roots.push(SourceRoot::new_library(fs)); - change.change_file(proc_lib_file, Some(Arc::new(String::new()))); + change.change_file(proc_lib_file, Some(Arc::new(String::from(source)))); let all_crates = crate_graph.crates_in_topological_order(); @@ -228,7 +229,7 @@ impl ChangeFixture { CfgOptions::default(), CfgOptions::default(), Env::default(), - test_proc_macros(&proc_macros), + proc_macro, ); for krate in all_crates { @@ -250,14 +251,22 @@ impl ChangeFixture { } } -fn test_proc_macros(proc_macros: &[String]) -> Vec { - std::array::IntoIter::new([ProcMacro { +fn test_proc_macros(proc_macros: &[String]) -> (Vec, String) { + // The source here is only required so that paths to the macros exist and are resolvable. + let source = r#" +#[proc_macro_attribute] +pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} +"#; + let proc_macros = std::array::IntoIter::new([ProcMacro { name: "identity".into(), kind: crate::ProcMacroKind::Attr, expander: Arc::new(IdentityProcMacroExpander), }]) .filter(|pm| proc_macros.iter().any(|name| name == &pm.name)) - .collect() + .collect(); + (proc_macros, source.into()) } #[derive(Debug, Clone, Copy)] diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index b81332c68f..b9b94fd22a 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -166,6 +166,15 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.speculative_expand(actual_macro_call, speculative_args, token_to_map) } + pub fn speculative_expand_attr_macro( + &self, + actual_macro_call: &ast::Item, + speculative_args: &ast::Item, + token_to_map: SyntaxToken, + ) -> Option<(SyntaxNode, SyntaxToken)> { + self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map) + } + // FIXME: Rename to descend_into_macros_single pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken { self.imp.descend_into_macros(token).pop().unwrap() @@ -452,7 +461,24 @@ impl<'db> SemanticsImpl<'db> { hir_expand::db::expand_speculative( self.db.upcast(), macro_call_id, - speculative_args, + speculative_args.syntax(), + token_to_map, + ) + } + + fn speculative_expand_attr( + &self, + actual_macro_call: &ast::Item, + speculative_args: &ast::Item, + token_to_map: SyntaxToken, + ) -> Option<(SyntaxNode, SyntaxToken)> { + let sa = self.analyze(actual_macro_call.syntax()); + let macro_call = InFile::new(sa.file_id, actual_macro_call.clone()); + let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call))?; + hir_expand::db::expand_speculative( + self.db.upcast(), + macro_call_id, + speculative_args.syntax(), token_to_map, ) } diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 14fe5cd3bb..80ba7a8d14 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -141,27 +141,40 @@ pub trait AstDatabase: SourceDatabase { pub fn expand_speculative( db: &dyn AstDatabase, actual_macro_call: MacroCallId, - speculative_args: &ast::TokenTree, + speculative_args: &SyntaxNode, token_to_map: SyntaxToken, ) -> Option<(SyntaxNode, SyntaxToken)> { - let (tt, tmap_1) = mbe::syntax_node_to_token_tree(speculative_args.syntax()); - let range = - token_to_map.text_range().checked_sub(speculative_args.syntax().text_range().start())?; - let token_id = tmap_1.token_by_range(range)?; + let loc = db.lookup_intern_macro(actual_macro_call); + let macro_def = db.macro_def(loc.def)?; - let macro_def = { - let loc: MacroCallLoc = db.lookup_intern_macro(actual_macro_call); - db.macro_def(loc.def)? + // Fetch token id in the speculative args + let censor = censor_for_macro_input(&loc, &speculative_args); + let (tt, args_tmap) = mbe::syntax_node_to_token_tree_censored(&speculative_args, censor); + let range = token_to_map.text_range().checked_sub(speculative_args.text_range().start())?; + let token_id = args_tmap.token_by_range(range)?; + + let speculative_expansion = if let MacroDefKind::ProcMacro(expander, ..) = loc.def.kind { + let attr_arg = match &loc.kind { + // FIXME make attr arg speculative as well + MacroCallKind::Attr { attr_args, .. } => { + let mut attr_args = attr_args.0.clone(); + mbe::Shift::new(&tt).shift_all(&mut attr_args); + Some(attr_args) + } + _ => None, + }; + + expander.expand(db, loc.krate, &tt, attr_arg.as_ref()) + } else { + macro_def.expand(db, actual_macro_call, &tt) }; - let speculative_expansion = macro_def.expand(db, actual_macro_call, &tt); - let expand_to = macro_expand_to(db, actual_macro_call); - - let (node, tmap_2) = token_tree_to_syntax_node(&speculative_expansion.value, expand_to).ok()?; + let (node, rev_tmap) = + token_tree_to_syntax_node(&speculative_expansion.value, expand_to).ok()?; let token_id = macro_def.map_id_down(token_id); - let range = tmap_2.first_range_by_token(token_id, token_to_map.kind())?; + let range = rev_tmap.first_range_by_token(token_id, token_to_map.kind())?; let token = node.syntax_node().covering_element(range).into_token()?; Some((node.syntax_node(), token)) } @@ -259,7 +272,19 @@ fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option Option { + match loc.kind { MacroCallKind::FnLike { .. } => None, MacroCallKind::Derive { derive_attr_index, .. } => match ast::Item::cast(node.clone()) { Some(item) => item @@ -275,15 +300,7 @@ fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option None, }, - }; - let (mut tt, tmap) = mbe::syntax_node_to_token_tree_censored(&node, censor); - - if loc.def.is_proc_macro() { - // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.delimiter = None; } - - Some(Arc::new((tt, tmap))) } fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option { @@ -367,11 +384,11 @@ fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult return ExpandResult::str_err("Failed to lower macro args to token tree".into()), }; - let macro_rules = match db.macro_def(loc.def) { + let expander = match db.macro_def(loc.def) { Some(it) => it, None => return ExpandResult::str_err("Failed to find macro definition".into()), }; - let ExpandResult { value: tt, err } = macro_rules.expand(db, id, ¯o_arg.0); + let ExpandResult { value: tt, err } = expander.expand(db, id, ¯o_arg.0); // Set a hard limit for the expanded tt let count = tt.count(); // XXX: Make ExpandResult a real error and use .map_err instead? diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index a814a2886d..871cccb830 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -370,6 +370,7 @@ impl ExpansionInfo { ) -> Option> + '_> { assert_eq!(token.file_id, self.arg.file_id); let token_id = if let Some(item) = item { + // check if we are mapping down in an attribute input let call_id = match self.expanded.file_id.0 { HirFileIdRepr::FileId(_) => return None, HirFileIdRepr::MacroFile(macro_file) => macro_file.macro_call_id, diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 4273129dff..b2e78bcfcf 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -175,48 +175,90 @@ impl<'a> CompletionContext<'a> { incomplete_let: false, no_completion_required: false, }; - - let mut original_file = original_file.syntax().clone(); - let mut speculative_file = file_with_fake_ident.syntax().clone(); - let mut offset = position.offset; - let mut fake_ident_token = fake_ident_token; - - // Are we inside a macro call? - while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( - find_node_at_offset::(&original_file, offset), - find_node_at_offset::(&speculative_file, offset), - ) { - if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) - != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) - { - break; - } - let speculative_args = match macro_call_with_fake_ident.token_tree() { - Some(tt) => tt, - None => break, - }; - if let (Some(actual_expansion), Some(speculative_expansion)) = ( - ctx.sema.expand(&actual_macro_call), - ctx.sema.speculative_expand( - &actual_macro_call, - &speculative_args, - fake_ident_token, - ), + ctx.expand_and_fill( + original_file.syntax().clone(), + file_with_fake_ident.syntax().clone(), + position.offset, + fake_ident_token, + ); + Some(ctx) + } + fn expand_and_fill( + &mut self, + mut original_file: SyntaxNode, + mut speculative_file: SyntaxNode, + mut offset: TextSize, + mut fake_ident_token: SyntaxToken, + ) { + loop { + if let (Some(actual_item), Some(item_with_fake_ident)) = ( + find_node_at_offset::(&original_file, offset), + find_node_at_offset::(&speculative_file, offset), ) { - let new_offset = speculative_expansion.1.text_range().start(); - if new_offset > actual_expansion.text_range().end() { + match ( + self.sema.expand_attr_macro(&actual_item), + self.sema.speculative_expand_attr_macro( + &actual_item, + &item_with_fake_ident, + fake_ident_token.clone(), + ), + ) { + (Some(actual_expansion), Some(speculative_expansion)) => { + let new_offset = speculative_expansion.1.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + break; + } + original_file = actual_expansion; + speculative_file = speculative_expansion.0; + fake_ident_token = speculative_expansion.1; + offset = new_offset; + continue; + } + (None, None) => (), + _ => break, + } + } + + if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( + find_node_at_offset::(&original_file, offset), + find_node_at_offset::(&speculative_file, offset), + ) { + let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text()); + let mac_call_path1 = + macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()); + if mac_call_path0 != mac_call_path1 { + break; + } + let speculative_args = match macro_call_with_fake_ident.token_tree() { + Some(tt) => tt, + None => break, + }; + + if let (Some(actual_expansion), Some(speculative_expansion)) = ( + self.sema.expand(&actual_macro_call), + self.sema.speculative_expand( + &actual_macro_call, + &speculative_args, + fake_ident_token, + ), + ) { + let new_offset = speculative_expansion.1.text_range().start(); + if new_offset > actual_expansion.text_range().end() { + break; + } + original_file = actual_expansion; + speculative_file = speculative_expansion.0; + fake_ident_token = speculative_expansion.1; + offset = new_offset; + } else { break; } - original_file = actual_expansion; - speculative_file = speculative_expansion.0; - fake_ident_token = speculative_expansion.1; - offset = new_offset; } else { break; } } - ctx.fill(&original_file, speculative_file, offset); - Some(ctx) + + self.fill(&original_file, speculative_file, offset); } /// Checks whether completions in that particular case don't make much sense. diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs index 5ef6829a05..6872e3b8dc 100644 --- a/crates/ide_completion/src/tests.rs +++ b/crates/ide_completion/src/tests.rs @@ -15,6 +15,7 @@ mod item_list; mod item; mod pattern; mod predicate; +mod proc_macros; mod record; mod sourcegen; mod type_pos; @@ -23,7 +24,7 @@ mod visibility; use std::mem; -use hir::{PrefixKind, Semantics}; +use hir::{db::DefDatabase, PrefixKind, Semantics}; use ide_db::{ base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, helpers::{ @@ -96,6 +97,7 @@ fn completion_list_with_config(config: CompletionConfig, ra_fixture: &str) -> St pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) { let change_fixture = ChangeFixture::parse(ra_fixture); let mut database = RootDatabase::default(); + database.set_enable_proc_attr_macros(true); database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); diff --git a/crates/ide_completion/src/tests/proc_macros.rs b/crates/ide_completion/src/tests/proc_macros.rs new file mode 100644 index 0000000000..6a8133a448 --- /dev/null +++ b/crates/ide_completion/src/tests/proc_macros.rs @@ -0,0 +1,75 @@ +//! Completion tests for expressions. +use expect_test::{expect, Expect}; + +use crate::tests::completion_list; + +fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture); + expect.assert_eq(&actual) +} + +#[test] +fn complete_dot_in_attr() { + check( + r#" +//- proc_macros: identity +pub struct Foo; +impl Foo { + fn foo(&self) {} +} + +#[proc_macros::identity] +fn main() { + Foo.$0 +} +"#, + expect![[r#" + me foo() fn(&self) + sn ref &expr + sn refm &mut expr + sn match match expr {} + sn box Box::new(expr) + sn ok Ok(expr) + sn err Err(expr) + sn some Some(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn call function(expr) + sn let let + sn letm let mut + "#]], + ) +} + +#[test] +fn complete_dot_in_attr2() { + check( + r#" +//- proc_macros: identity +pub struct Foo; +impl Foo { + fn foo(&self) {} +} + +#[proc_macros::identity] +fn main() { + Foo.f$0 +} +"#, + expect![[r#" + me foo() fn(&self) + sn ref &expr + sn refm &mut expr + sn match match expr {} + sn box Box::new(expr) + sn ok Ok(expr) + sn err Err(expr) + sn some Some(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn call function(expr) + sn let let + sn letm let mut + "#]], + ) +}