Skip to content

feat: Fix up syntax errors in attribute macro inputs to make completion work more often #11444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 12, 2022
Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 83 additions & 32 deletions crates/base_db/src/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down
59 changes: 52 additions & 7 deletions crates/hir_def/src/macro_expansion_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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());
}
}
}
40 changes: 40 additions & 0 deletions crates/hir_def/src/macro_expansion_tests/proc_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}"##]],
);
}
3 changes: 3 additions & 0 deletions crates/hir_expand/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ profile = { path = "../profile", version = "0.0.0" }
tt = { path = "../tt", version = "0.0.0" }
mbe = { path = "../mbe", version = "0.0.0" }
limit = { path = "../limit", version = "0.0.0" }

[dev-dependencies]
expect-test = "1.2.0-pre.1"
Loading