mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-04 19:29:07 +00:00
Add test for giving attribute proc macros valid syntax
This commit is contained in:
parent
9b1978a3ed
commit
212e82fd41
@ -43,6 +43,17 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
|||||||
db
|
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) {
|
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
|
||||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||||
let offset = range_or_offset.expect_offset();
|
let offset = range_or_offset.expect_offset();
|
||||||
@ -84,7 +95,14 @@ pub struct ChangeFixture {
|
|||||||
|
|
||||||
impl ChangeFixture {
|
impl ChangeFixture {
|
||||||
pub fn parse(ra_fixture: &str) -> 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 change = Change::new();
|
||||||
|
|
||||||
let mut files = Vec::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;
|
let proc_lib_file = file_id;
|
||||||
file_id.0 += 1;
|
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();
|
let mut fs = FileSet::default();
|
||||||
fs.insert(
|
fs.insert(
|
||||||
proc_lib_file,
|
proc_lib_file,
|
||||||
@ -272,52 +291,84 @@ impl ChangeFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_proc_macros(proc_macros: &[String]) -> (Vec<ProcMacro>, String) {
|
fn default_test_proc_macros() -> [(String, ProcMacro); 4] {
|
||||||
// The source here is only required so that paths to the macros exist and are resolvable.
|
[
|
||||||
let source = r#"
|
(
|
||||||
|
r#"
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
ProcMacro {
|
||||||
|
name: "identity".into(),
|
||||||
|
kind: crate::ProcMacroKind::Attr,
|
||||||
|
expander: Arc::new(IdentityProcMacroExpander),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
#[proc_macro_derive(DeriveIdentity)]
|
#[proc_macro_derive(DeriveIdentity)]
|
||||||
pub fn derive_identity(item: TokenStream) -> TokenStream {
|
pub fn derive_identity(item: TokenStream) -> TokenStream {
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
ProcMacro {
|
||||||
|
name: "DeriveIdentity".into(),
|
||||||
|
kind: crate::ProcMacroKind::CustomDerive,
|
||||||
|
expander: Arc::new(IdentityProcMacroExpander),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
|
pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
|
||||||
attr
|
attr
|
||||||
}
|
}
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
ProcMacro {
|
||||||
|
name: "input_replace".into(),
|
||||||
|
kind: crate::ProcMacroKind::Attr,
|
||||||
|
expander: Arc::new(AttributeInputReplaceProcMacroExpander),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn mirror(input: TokenStream) -> TokenStream {
|
pub fn mirror(input: TokenStream) -> TokenStream {
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
"#;
|
"#
|
||||||
let proc_macros = [
|
.into(),
|
||||||
ProcMacro {
|
ProcMacro {
|
||||||
name: "identity".into(),
|
name: "mirror".into(),
|
||||||
kind: crate::ProcMacroKind::Attr,
|
kind: crate::ProcMacroKind::FuncLike,
|
||||||
expander: Arc::new(IdentityProcMacroExpander),
|
expander: Arc::new(MirrorProcMacroExpander),
|
||||||
},
|
},
|
||||||
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_iter()
|
}
|
||||||
.filter(|pm| proc_macros.iter().any(|name| name == &stdx::to_lower_snake_case(&pm.name)))
|
|
||||||
.collect();
|
fn filter_test_proc_macros(
|
||||||
(proc_macros, source.into())
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -14,10 +14,10 @@ mod builtin_fn_macro;
|
|||||||
mod builtin_derive_macro;
|
mod builtin_derive_macro;
|
||||||
mod proc_macros;
|
mod proc_macros;
|
||||||
|
|
||||||
use std::{iter, ops::Range};
|
use std::{iter, ops::Range, sync::Arc};
|
||||||
|
|
||||||
use ::mbe::TokenMap;
|
use ::mbe::TokenMap;
|
||||||
use base_db::{fixture::WithFixture, SourceDatabase};
|
use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase};
|
||||||
use expect_test::Expect;
|
use expect_test::Expect;
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
db::{AstDatabase, TokenExpander},
|
db::{AstDatabase, TokenExpander},
|
||||||
@ -39,7 +39,21 @@ use crate::{
|
|||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check(ra_fixture: &str, mut expect: Expect) {
|
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 krate = db.crate_graph().iter().next().unwrap();
|
||||||
let def_map = db.crate_def_map(krate);
|
let def_map = db.crate_def_map(krate);
|
||||||
let local_id = def_map.root();
|
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() {
|
for decl_id in def_map[local_id].scope.declarations() {
|
||||||
if let ModuleDefId::AdtId(AdtId::StructId(struct_id)) = decl_id {
|
// FIXME: I'm sure there's already better way to do this
|
||||||
let src = struct_id.lookup(&db).source(&db);
|
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) {
|
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)
|
format_to!(expanded_text, "\n{}", pp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,3 +327,40 @@ fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> Str
|
|||||||
}
|
}
|
||||||
res
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -52,3 +52,40 @@ struct S;
|
|||||||
#[attr2] 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 }
|
||||||
|
"##]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user