Skip to content

Commit 7a17fb9

Browse files
Merge #11444
11444: feat: Fix up syntax errors in attribute macro inputs to make completion work more often r=flodiebold a=flodiebold This implements the "fix up syntax nodes" workaround mentioned in #11014. It isn't much more than a proof of concept; I have only implemented a few cases, but it already helps quite a bit. Some notes: - I'm not super happy about how much the fixup procedure needs to interact with the syntax node -> token tree conversion code (e.g. needing to share the token map). This could maybe be simplified with some refactoring of that code. - It would maybe be nice to have the fixup procedure reuse or share information with the parser, though I'm not really sure how much that would actually help. Co-authored-by: Florian Diebold <[email protected]>
2 parents 4449a33 + ccb789b commit 7a17fb9

File tree

14 files changed

+646
-95
lines changed

14 files changed

+646
-95
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/base_db/src/fixture.rs

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
4343
db
4444
}
4545

46+
fn with_files_extra_proc_macros(
47+
ra_fixture: &str,
48+
proc_macros: Vec<(String, ProcMacro)>,
49+
) -> Self {
50+
let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, proc_macros);
51+
let mut db = Self::default();
52+
fixture.change.apply(&mut db);
53+
assert!(fixture.file_position.is_none());
54+
db
55+
}
56+
4657
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
4758
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
4859
let offset = range_or_offset.expect_offset();
@@ -84,7 +95,14 @@ pub struct ChangeFixture {
8495

8596
impl ChangeFixture {
8697
pub fn parse(ra_fixture: &str) -> ChangeFixture {
87-
let (mini_core, proc_macros, fixture) = Fixture::parse(ra_fixture);
98+
Self::parse_with_proc_macros(ra_fixture, Vec::new())
99+
}
100+
101+
pub fn parse_with_proc_macros(
102+
ra_fixture: &str,
103+
mut proc_macros: Vec<(String, ProcMacro)>,
104+
) -> ChangeFixture {
105+
let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture);
88106
let mut change = Change::new();
89107

90108
let mut files = Vec::new();
@@ -222,11 +240,12 @@ impl ChangeFixture {
222240
}
223241
}
224242

225-
if !proc_macros.is_empty() {
243+
if !proc_macro_names.is_empty() {
226244
let proc_lib_file = file_id;
227245
file_id.0 += 1;
228246

229-
let (proc_macro, source) = test_proc_macros(&proc_macros);
247+
proc_macros.extend(default_test_proc_macros());
248+
let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros);
230249
let mut fs = FileSet::default();
231250
fs.insert(
232251
proc_lib_file,
@@ -272,52 +291,84 @@ impl ChangeFixture {
272291
}
273292
}
274293

275-
fn test_proc_macros(proc_macros: &[String]) -> (Vec<ProcMacro>, String) {
276-
// The source here is only required so that paths to the macros exist and are resolvable.
277-
let source = r#"
294+
fn default_test_proc_macros() -> [(String, ProcMacro); 4] {
295+
[
296+
(
297+
r#"
278298
#[proc_macro_attribute]
279299
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
280300
item
281301
}
302+
"#
303+
.into(),
304+
ProcMacro {
305+
name: "identity".into(),
306+
kind: crate::ProcMacroKind::Attr,
307+
expander: Arc::new(IdentityProcMacroExpander),
308+
},
309+
),
310+
(
311+
r#"
282312
#[proc_macro_derive(DeriveIdentity)]
283313
pub fn derive_identity(item: TokenStream) -> TokenStream {
284314
item
285315
}
316+
"#
317+
.into(),
318+
ProcMacro {
319+
name: "DeriveIdentity".into(),
320+
kind: crate::ProcMacroKind::CustomDerive,
321+
expander: Arc::new(IdentityProcMacroExpander),
322+
},
323+
),
324+
(
325+
r#"
286326
#[proc_macro_attribute]
287327
pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
288328
attr
289329
}
330+
"#
331+
.into(),
332+
ProcMacro {
333+
name: "input_replace".into(),
334+
kind: crate::ProcMacroKind::Attr,
335+
expander: Arc::new(AttributeInputReplaceProcMacroExpander),
336+
},
337+
),
338+
(
339+
r#"
290340
#[proc_macro]
291341
pub fn mirror(input: TokenStream) -> TokenStream {
292342
input
293343
}
294-
"#;
295-
let proc_macros = [
296-
ProcMacro {
297-
name: "identity".into(),
298-
kind: crate::ProcMacroKind::Attr,
299-
expander: Arc::new(IdentityProcMacroExpander),
300-
},
301-
ProcMacro {
302-
name: "DeriveIdentity".into(),
303-
kind: crate::ProcMacroKind::CustomDerive,
304-
expander: Arc::new(IdentityProcMacroExpander),
305-
},
306-
ProcMacro {
307-
name: "input_replace".into(),
308-
kind: crate::ProcMacroKind::Attr,
309-
expander: Arc::new(AttributeInputReplaceProcMacroExpander),
310-
},
311-
ProcMacro {
312-
name: "mirror".into(),
313-
kind: crate::ProcMacroKind::FuncLike,
314-
expander: Arc::new(MirrorProcMacroExpander),
315-
},
344+
"#
345+
.into(),
346+
ProcMacro {
347+
name: "mirror".into(),
348+
kind: crate::ProcMacroKind::FuncLike,
349+
expander: Arc::new(MirrorProcMacroExpander),
350+
},
351+
),
316352
]
317-
.into_iter()
318-
.filter(|pm| proc_macros.iter().any(|name| name == &stdx::to_lower_snake_case(&pm.name)))
319-
.collect();
320-
(proc_macros, source.into())
353+
}
354+
355+
fn filter_test_proc_macros(
356+
proc_macro_names: &[String],
357+
proc_macro_defs: Vec<(String, ProcMacro)>,
358+
) -> (Vec<ProcMacro>, String) {
359+
// The source here is only required so that paths to the macros exist and are resolvable.
360+
let mut source = String::new();
361+
let mut proc_macros = Vec::new();
362+
363+
for (c, p) in proc_macro_defs {
364+
if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(&p.name)) {
365+
continue;
366+
}
367+
proc_macros.push(p);
368+
source += &c;
369+
}
370+
371+
(proc_macros, source)
321372
}
322373

323374
#[derive(Debug, Clone, Copy)]

crates/hir_def/src/macro_expansion_tests.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ mod builtin_fn_macro;
1414
mod builtin_derive_macro;
1515
mod proc_macros;
1616

17-
use std::{iter, ops::Range};
17+
use std::{iter, ops::Range, sync::Arc};
1818

1919
use ::mbe::TokenMap;
20-
use base_db::{fixture::WithFixture, SourceDatabase};
20+
use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase};
2121
use expect_test::Expect;
2222
use hir_expand::{
2323
db::{AstDatabase, TokenExpander},
@@ -39,7 +39,21 @@ use crate::{
3939

4040
#[track_caller]
4141
fn check(ra_fixture: &str, mut expect: Expect) {
42-
let db = TestDB::with_files(ra_fixture);
42+
let extra_proc_macros = vec![(
43+
r#"
44+
#[proc_macro_attribute]
45+
pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream {
46+
item
47+
}
48+
"#
49+
.into(),
50+
ProcMacro {
51+
name: "identity_when_valid".into(),
52+
kind: base_db::ProcMacroKind::Attr,
53+
expander: Arc::new(IdentityWhenValidProcMacroExpander),
54+
},
55+
)];
56+
let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
4357
let krate = db.crate_graph().iter().next().unwrap();
4458
let def_map = db.crate_def_map(krate);
4559
let local_id = def_map.root();
@@ -172,7 +186,7 @@ fn check(ra_fixture: &str, mut expect: Expect) {
172186
let range: Range<usize> = range.into();
173187

174188
if show_token_ids {
175-
if let Some((tree, map)) = arg.as_deref() {
189+
if let Some((tree, map, _)) = arg.as_deref() {
176190
let tt_range = call.token_tree().unwrap().syntax().text_range();
177191
let mut ranges = Vec::new();
178192
extract_id_ranges(&mut ranges, &map, &tree);
@@ -201,10 +215,19 @@ fn check(ra_fixture: &str, mut expect: Expect) {
201215
}
202216

203217
for decl_id in def_map[local_id].scope.declarations() {
204-
if let ModuleDefId::AdtId(AdtId::StructId(struct_id)) = decl_id {
205-
let src = struct_id.lookup(&db).source(&db);
218+
// FIXME: I'm sure there's already better way to do this
219+
let src = match decl_id {
220+
ModuleDefId::AdtId(AdtId::StructId(struct_id)) => {
221+
Some(struct_id.lookup(&db).source(&db).syntax().cloned())
222+
}
223+
ModuleDefId::FunctionId(function_id) => {
224+
Some(function_id.lookup(&db).source(&db).syntax().cloned())
225+
}
226+
_ => None,
227+
};
228+
if let Some(src) = src {
206229
if src.file_id.is_attr_macro(&db) || src.file_id.is_custom_derive(&db) {
207-
let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None);
230+
let pp = pretty_print_macro_expansion(src.value, None);
208231
format_to!(expanded_text, "\n{}", pp)
209232
}
210233
}
@@ -304,3 +327,25 @@ fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> Str
304327
}
305328
res
306329
}
330+
331+
// Identity mapping, but only works when the input is syntactically valid. This
332+
// simulates common proc macros that unnecessarily parse their input and return
333+
// compile errors.
334+
#[derive(Debug)]
335+
struct IdentityWhenValidProcMacroExpander;
336+
impl base_db::ProcMacroExpander for IdentityWhenValidProcMacroExpander {
337+
fn expand(
338+
&self,
339+
subtree: &Subtree,
340+
_: Option<&Subtree>,
341+
_: &base_db::Env,
342+
) -> Result<Subtree, base_db::ProcMacroExpansionError> {
343+
let (parse, _) =
344+
::mbe::token_tree_to_syntax_node(subtree, ::mbe::TopEntryPoint::MacroItems);
345+
if parse.errors().is_empty() {
346+
Ok(subtree.clone())
347+
} else {
348+
panic!("got invalid macro input: {:?}", parse.errors());
349+
}
350+
}
351+
}

crates/hir_def/src/macro_expansion_tests/proc_macros.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,43 @@ struct S;
5252
#[attr2] struct S;"##]],
5353
);
5454
}
55+
56+
#[test]
57+
fn attribute_macro_syntax_completion_1() {
58+
// this is just the case where the input is actually valid
59+
check(
60+
r#"
61+
//- proc_macros: identity_when_valid
62+
#[proc_macros::identity_when_valid]
63+
fn foo() { bar.baz(); blub }
64+
"#,
65+
expect![[r##"
66+
#[proc_macros::identity_when_valid]
67+
fn foo() { bar.baz(); blub }
68+
69+
fn foo() {
70+
bar.baz();
71+
blub
72+
}"##]],
73+
);
74+
}
75+
76+
#[test]
77+
fn attribute_macro_syntax_completion_2() {
78+
// common case of dot completion while typing
79+
check(
80+
r#"
81+
//- proc_macros: identity_when_valid
82+
#[proc_macros::identity_when_valid]
83+
fn foo() { bar.; blub }
84+
"#,
85+
expect![[r##"
86+
#[proc_macros::identity_when_valid]
87+
fn foo() { bar.; blub }
88+
89+
fn foo() {
90+
bar. ;
91+
blub
92+
}"##]],
93+
);
94+
}

crates/hir_expand/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ profile = { path = "../profile", version = "0.0.0" }
2727
tt = { path = "../tt", version = "0.0.0" }
2828
mbe = { path = "../mbe", version = "0.0.0" }
2929
limit = { path = "../limit", version = "0.0.0" }
30+
31+
[dev-dependencies]
32+
expect-test = "1.2.0-pre.1"

0 commit comments

Comments
 (0)