Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/base_db/src/fixture.rs115
-rw-r--r--crates/hir_def/src/macro_expansion_tests.rs72
-rw-r--r--crates/hir_def/src/macro_expansion_tests/proc_macros.rs37
3 files changed, 186 insertions, 38 deletions
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 5326a5a440..af825a2e00 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -43,6 +43,17 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
db
}
+ fn with_files_extra_proc_macros(
+ ra_fixture: &str,
+ proc_macros: Vec<(String, ProcMacro)>,
+ ) -> Self {
+ let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, proc_macros);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ assert!(fixture.file_position.is_none());
+ db
+ }
+
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
let offset = range_or_offset.expect_offset();
@@ -84,7 +95,14 @@ pub struct ChangeFixture {
impl ChangeFixture {
pub fn parse(ra_fixture: &str) -> ChangeFixture {
- let (mini_core, proc_macros, fixture) = Fixture::parse(ra_fixture);
+ Self::parse_with_proc_macros(ra_fixture, Vec::new())
+ }
+
+ pub fn parse_with_proc_macros(
+ ra_fixture: &str,
+ mut proc_macros: Vec<(String, ProcMacro)>,
+ ) -> ChangeFixture {
+ let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture);
let mut change = Change::new();
let mut files = Vec::new();
@@ -222,11 +240,12 @@ impl ChangeFixture {
}
}
- if !proc_macros.is_empty() {
+ if !proc_macro_names.is_empty() {
let proc_lib_file = file_id;
file_id.0 += 1;
- let (proc_macro, source) = test_proc_macros(&proc_macros);
+ proc_macros.extend(default_test_proc_macros());
+ let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros);
let mut fs = FileSet::default();
fs.insert(
proc_lib_file,
@@ -272,52 +291,84 @@ impl ChangeFixture {
}
}
-fn test_proc_macros(proc_macros: &[String]) -> (Vec<ProcMacro>, String) {
- // The source here is only required so that paths to the macros exist and are resolvable.
- let source = r#"
+fn default_test_proc_macros() -> [(String, ProcMacro); 4] {
+ [
+ (
+ r#"
#[proc_macro_attribute]
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
+"#
+ .into(),
+ ProcMacro {
+ name: "identity".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(IdentityProcMacroExpander),
+ },
+ ),
+ (
+ r#"
#[proc_macro_derive(DeriveIdentity)]
pub fn derive_identity(item: TokenStream) -> TokenStream {
item
}
+"#
+ .into(),
+ ProcMacro {
+ name: "DeriveIdentity".into(),
+ kind: crate::ProcMacroKind::CustomDerive,
+ expander: Arc::new(IdentityProcMacroExpander),
+ },
+ ),
+ (
+ r#"
#[proc_macro_attribute]
pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
attr
}
+"#
+ .into(),
+ ProcMacro {
+ name: "input_replace".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(AttributeInputReplaceProcMacroExpander),
+ },
+ ),
+ (
+ r#"
#[proc_macro]
pub fn mirror(input: TokenStream) -> TokenStream {
input
}
-"#;
- let proc_macros = [
- ProcMacro {
- name: "identity".into(),
- kind: crate::ProcMacroKind::Attr,
- expander: Arc::new(IdentityProcMacroExpander),
- },
- ProcMacro {
- name: "DeriveIdentity".into(),
- kind: crate::ProcMacroKind::CustomDerive,
- expander: Arc::new(IdentityProcMacroExpander),
- },
- ProcMacro {
- name: "input_replace".into(),
- kind: crate::ProcMacroKind::Attr,
- expander: Arc::new(AttributeInputReplaceProcMacroExpander),
- },
- ProcMacro {
- name: "mirror".into(),
- kind: crate::ProcMacroKind::FuncLike,
- expander: Arc::new(MirrorProcMacroExpander),
- },
+"#
+ .into(),
+ ProcMacro {
+ name: "mirror".into(),
+ kind: crate::ProcMacroKind::FuncLike,
+ expander: Arc::new(MirrorProcMacroExpander),
+ },
+ ),
]
- .into_iter()
- .filter(|pm| proc_macros.iter().any(|name| name == &stdx::to_lower_snake_case(&pm.name)))
- .collect();
- (proc_macros, source.into())
+}
+
+fn filter_test_proc_macros(
+ proc_macro_names: &[String],
+ proc_macro_defs: Vec<(String, ProcMacro)>,
+) -> (Vec<ProcMacro>, String) {
+ // The source here is only required so that paths to the macros exist and are resolvable.
+ let mut source = String::new();
+ let mut proc_macros = Vec::new();
+
+ for (c, p) in proc_macro_defs {
+ if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(&p.name)) {
+ continue;
+ }
+ proc_macros.push(p);
+ source += &c;
+ }
+
+ (proc_macros, source)
}
#[derive(Debug, Clone, Copy)]
diff --git a/crates/hir_def/src/macro_expansion_tests.rs b/crates/hir_def/src/macro_expansion_tests.rs
index c3116edc88..80747f6c63 100644
--- a/crates/hir_def/src/macro_expansion_tests.rs
+++ b/crates/hir_def/src/macro_expansion_tests.rs
@@ -14,10 +14,10 @@ mod builtin_fn_macro;
mod builtin_derive_macro;
mod proc_macros;
-use std::{iter, ops::Range};
+use std::{iter, ops::Range, sync::Arc};
use ::mbe::TokenMap;
-use base_db::{fixture::WithFixture, SourceDatabase};
+use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase};
use expect_test::Expect;
use hir_expand::{
db::{AstDatabase, TokenExpander},
@@ -39,7 +39,21 @@ use crate::{
#[track_caller]
fn check(ra_fixture: &str, mut expect: Expect) {
- let db = TestDB::with_files(ra_fixture);
+ let extra_proc_macros = vec![(
+ r#"
+#[proc_macro_attribute]
+pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ item
+}
+"#
+ .into(),
+ ProcMacro {
+ name: "identity_when_valid".into(),
+ kind: base_db::ProcMacroKind::Attr,
+ expander: Arc::new(IdentityWhenValidProcMacroExpander),
+ },
+ )];
+ let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
let krate = db.crate_graph().iter().next().unwrap();
let def_map = db.crate_def_map(krate);
let local_id = def_map.root();
@@ -201,10 +215,19 @@ fn check(ra_fixture: &str, mut expect: Expect) {
}
for decl_id in def_map[local_id].scope.declarations() {
- if let ModuleDefId::AdtId(AdtId::StructId(struct_id)) = decl_id {
- let src = struct_id.lookup(&db).source(&db);
+ // FIXME: I'm sure there's already better way to do this
+ let src = match decl_id {
+ ModuleDefId::AdtId(AdtId::StructId(struct_id)) => {
+ Some(struct_id.lookup(&db).source(&db).syntax().cloned())
+ }
+ ModuleDefId::FunctionId(function_id) => {
+ Some(function_id.lookup(&db).source(&db).syntax().cloned())
+ }
+ _ => None,
+ };
+ if let Some(src) = src {
if src.file_id.is_attr_macro(&db) || src.file_id.is_custom_derive(&db) {
- let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None);
+ let pp = pretty_print_macro_expansion(src.value, None);
format_to!(expanded_text, "\n{}", pp)
}
}
@@ -304,3 +327,40 @@ fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> Str
}
res
}
+
+// Identity mapping, but only works when the input is syntactically valid. This
+// simulates common proc macros that unnecessarily parse their input and return
+// compile errors.
+#[derive(Debug)]
+struct IdentityWhenValidProcMacroExpander;
+impl base_db::ProcMacroExpander for IdentityWhenValidProcMacroExpander {
+ fn expand(
+ &self,
+ subtree: &Subtree,
+ _: Option<&Subtree>,
+ _: &base_db::Env,
+ ) -> Result<Subtree, base_db::ProcMacroExpansionError> {
+ let (parse, _) =
+ ::mbe::token_tree_to_syntax_node(subtree, ::mbe::TopEntryPoint::MacroItems);
+ if parse.errors().is_empty() {
+ Ok(subtree.clone())
+ } else {
+ use tt::{Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, TokenTree};
+ let mut subtree = Subtree::default();
+ subtree.token_trees.push(TokenTree::Leaf(
+ Ident { text: "compile_error!".into(), id: TokenId(0) }.into(),
+ ));
+ subtree.token_trees.push(TokenTree::Subtree(Subtree {
+ delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Parenthesis }),
+ token_trees: vec![TokenTree::Leaf(Leaf::Literal(Literal {
+ text: r#""parse error""#.into(),
+ id: TokenId::unspecified(),
+ }))],
+ }));
+ subtree.token_trees.push(TokenTree::Leaf(
+ Punct { char: ';', spacing: tt::Spacing::Alone, id: TokenId::unspecified() }.into(),
+ ));
+ Ok(subtree)
+ }
+ }
+}
diff --git a/crates/hir_def/src/macro_expansion_tests/proc_macros.rs b/crates/hir_def/src/macro_expansion_tests/proc_macros.rs
index 901872edda..dead99a40c 100644
--- a/crates/hir_def/src/macro_expansion_tests/proc_macros.rs
+++ b/crates/hir_def/src/macro_expansion_tests/proc_macros.rs
@@ -52,3 +52,40 @@ struct S;
#[attr2] struct S;"##]],
);
}
+
+#[test]
+fn attribute_macro_syntax_completion_1() {
+ // this is just the case where the input is actually valid
+ check(
+ r#"
+//- proc_macros: identity_when_valid
+#[proc_macros::identity_when_valid]
+fn foo() { bar.baz(); blub }
+"#,
+ expect![[r##"
+#[proc_macros::identity_when_valid]
+fn foo() { bar.baz(); blub }
+
+fn foo() {
+ bar.baz();
+ blub
+}"##]],
+ );
+}
+
+#[test]
+fn attribute_macro_syntax_completion_2() {
+ // common case of dot completion while typing
+ // right now not working
+ check(
+ r#"
+//- proc_macros: identity_when_valid
+#[proc_macros::identity_when_valid]
+fn foo() { bar.; blub }
+"#,
+ expect![[r##"
+#[proc_macros::identity_when_valid]
+fn foo() { bar.; blub }
+"##]],
+ );
+}