mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 22:54:58 +00:00
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 <flodiebold@gmail.com>
This commit is contained in:
commit
7a17fb9c43
14 changed files with 646 additions and 95 deletions
|
@ -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();
|
||||
|
@ -172,7 +186,7 @@ fn check(ra_fixture: &str, mut expect: Expect) {
|
|||
let range: Range<usize> = range.into();
|
||||
|
||||
if show_token_ids {
|
||||
if let Some((tree, map)) = arg.as_deref() {
|
||||
if let Some((tree, map, _)) = arg.as_deref() {
|
||||
let tt_range = call.token_tree().unwrap().syntax().text_range();
|
||||
let mut ranges = Vec::new();
|
||||
extract_id_ranges(&mut ranges, &map, &tree);
|
||||
|
@ -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,25 @@ 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 {
|
||||
panic!("got invalid macro input: {:?}", parse.errors());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,3 +52,43 @@ 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
|
||||
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 }
|
||||
|
||||
fn foo() {
|
||||
bar. ;
|
||||
blub
|
||||
}"##]],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue