aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_proc_macro_srv
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_proc_macro_srv')
-rw-r--r--crates/ra_proc_macro_srv/Cargo.toml5
-rw-r--r--crates/ra_proc_macro_srv/src/dylib.rs211
-rw-r--r--crates/ra_proc_macro_srv/src/lib.rs36
-rw-r--r--crates/ra_proc_macro_srv/src/rustc_server.rs4
-rw-r--r--crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt188
-rw-r--r--crates/ra_proc_macro_srv/src/tests/mod.rs47
-rw-r--r--crates/ra_proc_macro_srv/src/tests/utils.rs65
7 files changed, 551 insertions, 5 deletions
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index f08de5fc7..1e0f50339 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -12,9 +12,12 @@ doctest = false
12ra_tt = { path = "../ra_tt" } 12ra_tt = { path = "../ra_tt" }
13ra_mbe = { path = "../ra_mbe" } 13ra_mbe = { path = "../ra_mbe" }
14ra_proc_macro = { path = "../ra_proc_macro" } 14ra_proc_macro = { path = "../ra_proc_macro" }
15goblin = "0.2.1"
16libloading = "0.6.0"
17test_utils = { path = "../test_utils" }
15 18
16[dev-dependencies] 19[dev-dependencies]
17cargo_metadata = "0.9.1" 20cargo_metadata = "0.9.1"
18difference = "2.0.0" 21difference = "2.0.0"
19# used as proc macro test target 22# used as proc macro test target
20serde_derive = "=1.0.104" \ No newline at end of file 23serde_derive = "=1.0.104"
diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs
new file mode 100644
index 000000000..ec63d587b
--- /dev/null
+++ b/crates/ra_proc_macro_srv/src/dylib.rs
@@ -0,0 +1,211 @@
1//! Handles dynamic library loading for proc macro
2
3use crate::{proc_macro::bridge, rustc_server::TokenStream};
4use std::path::Path;
5
6use goblin::{mach::Mach, Object};
7use libloading::Library;
8use ra_proc_macro::ProcMacroKind;
9
10use std::io::Error as IoError;
11use std::io::ErrorKind as IoErrorKind;
12
13const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
14
15fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> IoError {
16 IoError::new(IoErrorKind::InvalidData, e)
17}
18
19fn get_symbols_from_lib(file: &Path) -> Result<Vec<String>, IoError> {
20 let buffer = std::fs::read(file)?;
21 let object = Object::parse(&buffer).map_err(invalid_data_err)?;
22
23 match object {
24 Object::Elf(elf) => {
25 let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?;
26 let names = symbols.iter().map(|s| s.to_string()).collect();
27 Ok(names)
28 }
29 Object::PE(pe) => {
30 let symbol_names =
31 pe.exports.iter().flat_map(|s| s.name).map(|n| n.to_string()).collect();
32 Ok(symbol_names)
33 }
34 Object::Mach(mach) => match mach {
35 Mach::Binary(binary) => {
36 let exports = binary.exports().map_err(invalid_data_err)?;
37 let names = exports
38 .into_iter()
39 .map(|s| {
40 // In macos doc:
41 // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html
42 // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be
43 // prepended with an underscore.
44 if s.name.starts_with("_") {
45 s.name[1..].to_string()
46 } else {
47 s.name
48 }
49 })
50 .collect();
51 Ok(names)
52 }
53 Mach::Fat(_) => Ok(vec![]),
54 },
55 Object::Archive(_) | Object::Unknown(_) => Ok(vec![]),
56 }
57}
58
59fn is_derive_registrar_symbol(symbol: &str) -> bool {
60 symbol.contains(NEW_REGISTRAR_SYMBOL)
61}
62
63fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> {
64 let symbols = get_symbols_from_lib(file)?;
65 Ok(symbols.into_iter().find(|s| is_derive_registrar_symbol(s)))
66}
67
68/// Loads dynamic library in platform dependent manner.
69///
70/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
71/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
72/// and [here](https://github.com/rust-lang/rust/issues/60593).
73///
74/// Usage of RTLD_DEEPBIND
75/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
76///
77/// It seems that on Windows that behaviour is default, so we do nothing in that case.
78#[cfg(windows)]
79fn load_library(file: &Path) -> Result<Library, libloading::Error> {
80 Library::new(file)
81}
82
83#[cfg(unix)]
84fn load_library(file: &Path) -> Result<Library, libloading::Error> {
85 use libloading::os::unix::Library as UnixLibrary;
86 use std::os::raw::c_int;
87
88 const RTLD_NOW: c_int = 0x00002;
89 const RTLD_DEEPBIND: c_int = 0x00008;
90
91 UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into())
92}
93
94struct ProcMacroLibraryLibloading {
95 // Hold the dylib to prevent it for unloadeding
96 _lib: Library,
97 exported_macros: Vec<bridge::client::ProcMacro>,
98}
99
100impl ProcMacroLibraryLibloading {
101 fn open(file: &Path) -> Result<Self, IoError> {
102 let symbol_name = find_registrar_symbol(file)?
103 .ok_or(invalid_data_err(format!("Cannot find registrar symbol in file {:?}", file)))?;
104
105 let lib = load_library(file).map_err(invalid_data_err)?;
106 let exported_macros = {
107 let macros: libloading::Symbol<&&[bridge::client::ProcMacro]> =
108 unsafe { lib.get(symbol_name.as_bytes()) }.map_err(invalid_data_err)?;
109 macros.to_vec()
110 };
111
112 Ok(ProcMacroLibraryLibloading { _lib: lib, exported_macros })
113 }
114}
115
116type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
117
118pub struct Expander {
119 libs: Vec<ProcMacroLibraryImpl>,
120}
121
122impl Expander {
123 pub fn new<P: AsRef<Path>>(lib: &P) -> Result<Expander, String> {
124 let mut libs = vec![];
125 /* Some libraries for dynamic loading require canonicalized path (even when it is
126 already absolute
127 */
128 let lib =
129 lib.as_ref().canonicalize().expect(&format!("Cannot canonicalize {:?}", lib.as_ref()));
130
131 let library = ProcMacroLibraryImpl::open(&lib).map_err(|e| e.to_string())?;
132 libs.push(library);
133
134 Ok(Expander { libs })
135 }
136
137 pub fn expand(
138 &self,
139 macro_name: &str,
140 macro_body: &ra_tt::Subtree,
141 attributes: Option<&ra_tt::Subtree>,
142 ) -> Result<ra_tt::Subtree, bridge::PanicMessage> {
143 let parsed_body = TokenStream::with_subtree(macro_body.clone());
144
145 let parsed_attributes = attributes
146 .map_or(crate::rustc_server::TokenStream::new(), |attr| {
147 TokenStream::with_subtree(attr.clone())
148 });
149
150 for lib in &self.libs {
151 for proc_macro in &lib.exported_macros {
152 match proc_macro {
153 bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
154 if *trait_name == macro_name =>
155 {
156 let res = client.run(
157 &crate::proc_macro::bridge::server::SameThread,
158 crate::rustc_server::Rustc::default(),
159 parsed_body,
160 );
161 return res.map(|it| it.subtree);
162 }
163 bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => {
164 let res = client.run(
165 &crate::proc_macro::bridge::server::SameThread,
166 crate::rustc_server::Rustc::default(),
167 parsed_body,
168 );
169 return res.map(|it| it.subtree);
170 }
171 bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => {
172 let res = client.run(
173 &crate::proc_macro::bridge::server::SameThread,
174 crate::rustc_server::Rustc::default(),
175 parsed_attributes,
176 parsed_body,
177 );
178
179 return res.map(|it| it.subtree);
180 }
181 _ => continue,
182 }
183 }
184 }
185
186 Err(bridge::PanicMessage::String("Nothing to expand".to_string()))
187 }
188
189 pub fn list_macros(&self) -> Result<Vec<(String, ProcMacroKind)>, bridge::PanicMessage> {
190 let mut result = vec![];
191
192 for lib in &self.libs {
193 for proc_macro in &lib.exported_macros {
194 let res = match proc_macro {
195 bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
196 (trait_name.to_string(), ProcMacroKind::CustomDerive)
197 }
198 bridge::client::ProcMacro::Bang { name, .. } => {
199 (name.to_string(), ProcMacroKind::FuncLike)
200 }
201 bridge::client::ProcMacro::Attr { name, .. } => {
202 (name.to_string(), ProcMacroKind::Attr)
203 }
204 };
205 result.push(res);
206 }
207 }
208
209 Ok(result)
210 }
211}
diff --git a/crates/ra_proc_macro_srv/src/lib.rs b/crates/ra_proc_macro_srv/src/lib.rs
index f376df236..59716cbb3 100644
--- a/crates/ra_proc_macro_srv/src/lib.rs
+++ b/crates/ra_proc_macro_srv/src/lib.rs
@@ -17,13 +17,41 @@ mod proc_macro;
17#[doc(hidden)] 17#[doc(hidden)]
18mod rustc_server; 18mod rustc_server;
19 19
20mod dylib;
21
20use proc_macro::bridge::client::TokenStream; 22use proc_macro::bridge::client::TokenStream;
21use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask}; 23use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
22 24
23pub fn expand_task(_task: &ExpansionTask) -> Result<ExpansionResult, String> { 25pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
24 unimplemented!() 26 let expander = dylib::Expander::new(&task.lib)
27 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
28
29 match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) {
30 Ok(expansion) => Ok(ExpansionResult { expansion }),
31 Err(msg) => {
32 let reason = format!(
33 "Cannot perform expansion for {}: error {:?}!",
34 &task.macro_name,
35 msg.as_str()
36 );
37 Err(reason)
38 }
39 }
25} 40}
26 41
27pub fn list_macros(_task: &ListMacrosTask) -> Result<ListMacrosResult, String> { 42pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
28 unimplemented!() 43 let expander = dylib::Expander::new(&task.lib)
44 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
45
46 match expander.list_macros() {
47 Ok(macros) => Ok(ListMacrosResult { macros }),
48 Err(msg) => {
49 let reason =
50 format!("Cannot perform expansion for {:?}: error {:?}!", &task.lib, msg.as_str());
51 Err(reason)
52 }
53 }
29} 54}
55
56#[cfg(test)]
57mod tests;
diff --git a/crates/ra_proc_macro_srv/src/rustc_server.rs b/crates/ra_proc_macro_srv/src/rustc_server.rs
index 92d1fd989..ec0d35692 100644
--- a/crates/ra_proc_macro_srv/src/rustc_server.rs
+++ b/crates/ra_proc_macro_srv/src/rustc_server.rs
@@ -34,6 +34,10 @@ impl TokenStream {
34 TokenStream { subtree: Default::default() } 34 TokenStream { subtree: Default::default() }
35 } 35 }
36 36
37 pub fn with_subtree(subtree: tt::Subtree) -> Self {
38 TokenStream { subtree }
39 }
40
37 pub fn is_empty(&self) -> bool { 41 pub fn is_empty(&self) -> bool {
38 self.subtree.token_trees.is_empty() 42 self.subtree.token_trees.is_empty()
39 } 43 }
diff --git a/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt b/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt
new file mode 100644
index 000000000..24507d98d
--- /dev/null
+++ b/crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt
@@ -0,0 +1,188 @@
1SUBTREE $
2 PUNCH # [alone] 4294967295
3 SUBTREE [] 4294967295
4 IDENT allow 4294967295
5 SUBTREE () 4294967295
6 IDENT non_upper_case_globals 4294967295
7 PUNCH , [alone] 4294967295
8 IDENT unused_attributes 4294967295
9 PUNCH , [alone] 4294967295
10 IDENT unused_qualifications 4294967295
11 IDENT const 4294967295
12 IDENT _IMPL_SERIALIZE_FOR_Foo 4294967295
13 PUNCH : [alone] 4294967295
14 SUBTREE () 4294967295
15 PUNCH = [alone] 4294967295
16 SUBTREE {} 4294967295
17 PUNCH # [alone] 4294967295
18 SUBTREE [] 4294967295
19 IDENT allow 4294967295
20 SUBTREE () 4294967295
21 IDENT unknown_lints 4294967295
22 PUNCH # [alone] 4294967295
23 SUBTREE [] 4294967295
24 IDENT cfg_attr 4294967295
25 SUBTREE () 4294967295
26 IDENT feature 4294967295
27 PUNCH = [alone] 4294967295
28 SUBTREE $
29 LITERAL "cargo-clippy" 0
30 PUNCH , [alone] 4294967295
31 IDENT allow 4294967295
32 SUBTREE () 4294967295
33 IDENT useless_attribute 4294967295
34 PUNCH # [alone] 4294967295
35 SUBTREE [] 4294967295
36 IDENT allow 4294967295
37 SUBTREE () 4294967295
38 IDENT rust_2018_idioms 4294967295
39 IDENT extern 4294967295
40 IDENT crate 4294967295
41 IDENT serde 4294967295
42 IDENT as 4294967295
43 IDENT _serde 4294967295
44 PUNCH ; [alone] 4294967295
45 PUNCH # [alone] 4294967295
46 SUBTREE [] 4294967295
47 IDENT allow 4294967295
48 SUBTREE () 4294967295
49 IDENT unused_macros 4294967295
50 IDENT macro_rules 4294967295
51 PUNCH ! [alone] 4294967295
52 IDENT try 4294967295
53 SUBTREE {} 4294967295
54 SUBTREE () 4294967295
55 PUNCH $ [alone] 4294967295
56 IDENT __expr 4294967295
57 PUNCH : [alone] 4294967295
58 IDENT expr 4294967295
59 PUNCH = [joint] 4294967295
60 PUNCH > [alone] 4294967295
61 SUBTREE {} 4294967295
62 IDENT match 4294967295
63 PUNCH $ [alone] 4294967295
64 IDENT __expr 4294967295
65 SUBTREE {} 4294967295
66 IDENT _serde 4294967295
67 PUNCH : [joint] 4294967295
68 PUNCH : [alone] 4294967295
69 IDENT export 4294967295
70 PUNCH : [joint] 4294967295
71 PUNCH : [alone] 4294967295
72 IDENT Ok 4294967295
73 SUBTREE () 4294967295
74 IDENT __val 4294967295
75 PUNCH = [joint] 4294967295
76 PUNCH > [alone] 4294967295
77 IDENT __val 4294967295
78 PUNCH , [alone] 4294967295
79 IDENT _serde 4294967295
80 PUNCH : [joint] 4294967295
81 PUNCH : [alone] 4294967295
82 IDENT export 4294967295
83 PUNCH : [joint] 4294967295
84 PUNCH : [alone] 4294967295
85 IDENT Err 4294967295
86 SUBTREE () 4294967295
87 IDENT __err 4294967295
88 PUNCH = [joint] 4294967295
89 PUNCH > [alone] 4294967295
90 SUBTREE {} 4294967295
91 IDENT return 4294967295
92 IDENT _serde 4294967295
93 PUNCH : [joint] 4294967295
94 PUNCH : [alone] 4294967295
95 IDENT export 4294967295
96 PUNCH : [joint] 4294967295
97 PUNCH : [alone] 4294967295
98 IDENT Err 4294967295
99 SUBTREE () 4294967295
100 IDENT __err 4294967295
101 PUNCH ; [alone] 4294967295
102 PUNCH # [alone] 4294967295
103 SUBTREE [] 4294967295
104 IDENT automatically_derived 4294967295
105 IDENT impl 4294967295
106 IDENT _serde 4294967295
107 PUNCH : [joint] 4294967295
108 PUNCH : [alone] 4294967295
109 IDENT Serialize 4294967295
110 IDENT for 4294967295
111 IDENT Foo 1
112 SUBTREE {} 4294967295
113 IDENT fn 4294967295
114 IDENT serialize 4294967295
115 PUNCH < [alone] 4294967295
116 IDENT __S 4294967295
117 PUNCH > [alone] 4294967295
118 SUBTREE () 4294967295
119 PUNCH & [alone] 4294967295
120 IDENT self 4294967295
121 PUNCH , [alone] 4294967295
122 IDENT __serializer 4294967295
123 PUNCH : [alone] 4294967295
124 IDENT __S 4294967295
125 PUNCH - [joint] 4294967295
126 PUNCH > [alone] 4294967295
127 IDENT _serde 4294967295
128 PUNCH : [joint] 4294967295
129 PUNCH : [alone] 4294967295
130 IDENT export 4294967295
131 PUNCH : [joint] 4294967295
132 PUNCH : [alone] 4294967295
133 IDENT Result 4294967295
134 PUNCH < [alone] 4294967295
135 IDENT __S 4294967295
136 PUNCH : [joint] 4294967295
137 PUNCH : [alone] 4294967295
138 IDENT Ok 4294967295
139 PUNCH , [alone] 4294967295
140 IDENT __S 4294967295
141 PUNCH : [joint] 4294967295
142 PUNCH : [alone] 4294967295
143 IDENT Error 4294967295
144 PUNCH > [alone] 4294967295
145 IDENT where 4294967295
146 IDENT __S 4294967295
147 PUNCH : [alone] 4294967295
148 IDENT _serde 4294967295
149 PUNCH : [joint] 4294967295
150 PUNCH : [alone] 4294967295
151 IDENT Serializer 4294967295
152 PUNCH , [alone] 4294967295
153 SUBTREE {} 4294967295
154 IDENT let 4294967295
155 IDENT __serde_state 4294967295
156 PUNCH = [alone] 4294967295
157 IDENT try 4294967295
158 PUNCH ! [alone] 4294967295
159 SUBTREE () 4294967295
160 IDENT _serde 4294967295
161 PUNCH : [joint] 4294967295
162 PUNCH : [alone] 4294967295
163 IDENT Serializer 4294967295
164 PUNCH : [joint] 4294967295
165 PUNCH : [alone] 4294967295
166 IDENT serialize_struct 4294967295
167 SUBTREE () 4294967295
168 IDENT __serializer 4294967295
169 PUNCH , [alone] 4294967295
170 LITERAL "Foo" 4294967295
171 PUNCH , [alone] 4294967295
172 IDENT false 4294967295
173 IDENT as 4294967295
174 IDENT usize 4294967295
175 PUNCH ; [alone] 4294967295
176 IDENT _serde 4294967295
177 PUNCH : [joint] 4294967295
178 PUNCH : [alone] 4294967295
179 IDENT ser 4294967295
180 PUNCH : [joint] 4294967295
181 PUNCH : [alone] 4294967295
182 IDENT SerializeStruct 4294967295
183 PUNCH : [joint] 4294967295
184 PUNCH : [alone] 4294967295
185 IDENT end 4294967295
186 SUBTREE () 4294967295
187 IDENT __serde_state 4294967295
188 PUNCH ; [alone] 4294967295 \ No newline at end of file
diff --git a/crates/ra_proc_macro_srv/src/tests/mod.rs b/crates/ra_proc_macro_srv/src/tests/mod.rs
new file mode 100644
index 000000000..03f79bc5d
--- /dev/null
+++ b/crates/ra_proc_macro_srv/src/tests/mod.rs
@@ -0,0 +1,47 @@
1//! proc-macro tests
2
3#[macro_use]
4mod utils;
5use test_utils::assert_eq_text;
6use utils::*;
7
8#[test]
9fn test_derive_serialize_proc_macro() {
10 assert_expand(
11 "serde_derive",
12 "Serialize",
13 "1.0.104",
14 r##"struct Foo {}"##,
15 include_str!("fixtures/test_serialize_proc_macro.txt"),
16 );
17}
18
19#[test]
20fn test_derive_serialize_proc_macro_failed() {
21 assert_expand(
22 "serde_derive",
23 "Serialize",
24 "1.0.104",
25 r##"
26 struct {}
27"##,
28 r##"
29SUBTREE $
30 IDENT compile_error 4294967295
31 PUNCH ! [alone] 4294967295
32 SUBTREE {} 4294967295
33 LITERAL "expected identifier" 4294967295
34"##,
35 );
36}
37
38#[test]
39fn test_derive_proc_macro_list() {
40 let res = list("serde_derive", "1.0.104").join("\n");
41
42 assert_eq_text!(
43 &res,
44 r#"Serialize [CustomDerive]
45Deserialize [CustomDerive]"#
46 );
47}
diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs
new file mode 100644
index 000000000..1ee409449
--- /dev/null
+++ b/crates/ra_proc_macro_srv/src/tests/utils.rs
@@ -0,0 +1,65 @@
1//! utils used in proc-macro tests
2
3use crate::dylib;
4use crate::list_macros;
5pub use difference::Changeset as __Changeset;
6use ra_proc_macro::ListMacrosTask;
7use std::str::FromStr;
8use test_utils::assert_eq_text;
9
10mod fixtures {
11 use cargo_metadata::{parse_messages, Message};
12 use std::process::Command;
13
14 // Use current project metadata to get the proc-macro dylib path
15 pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf {
16 let command = Command::new("cargo")
17 .args(&["check", "--message-format", "json"])
18 .output()
19 .unwrap()
20 .stdout;
21
22 for message in parse_messages(command.as_slice()) {
23 match message.unwrap() {
24 Message::CompilerArtifact(artifact) => {
25 if artifact.target.kind.contains(&"proc-macro".to_string()) {
26 let repr = format!("{} {}", crate_name, version);
27 if artifact.package_id.repr.starts_with(&repr) {
28 return artifact.filenames[0].clone();
29 }
30 }
31 }
32 _ => (), // Unknown message
33 }
34 }
35
36 panic!("No proc-macro dylib for {} found!", crate_name);
37 }
38}
39
40fn parse_string(code: &str) -> Option<crate::rustc_server::TokenStream> {
41 Some(crate::rustc_server::TokenStream::from_str(code).unwrap())
42}
43
44pub fn assert_expand(
45 crate_name: &str,
46 macro_name: &str,
47 version: &str,
48 fixture: &str,
49 expect: &str,
50) {
51 let path = fixtures::dylib_path(crate_name, version);
52 let expander = dylib::Expander::new(&path).unwrap();
53 let fixture = parse_string(fixture).unwrap();
54
55 let res = expander.expand(macro_name, &fixture.subtree, None).unwrap();
56 assert_eq_text!(&format!("{:?}", res), &expect.trim());
57}
58
59pub fn list(crate_name: &str, version: &str) -> Vec<String> {
60 let path = fixtures::dylib_path(crate_name, version);
61 let task = ListMacrosTask { lib: path };
62
63 let res = list_macros(&task).unwrap();
64 res.macros.into_iter().map(|(name, kind)| format!("{} [{:?}]", name, kind)).collect()
65}