aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2021-05-21 22:45:27 +0100
committerJonas Schievink <[email protected]>2021-05-21 22:45:27 +0100
commit8d13864440ba8b6ede1097c79b28e4981caf714a (patch)
tree0213e81d565c20a14800a97033af0caa1c7a5703
parent01df4c04d12be89d53addca2885221419e56bf31 (diff)
Add an ItemTree pretty-printer
-rw-r--r--crates/hir_def/src/item_tree.rs11
-rw-r--r--crates/hir_def/src/item_tree/pretty.rs525
-rw-r--r--crates/hir_def/src/item_tree/tests.rs244
3 files changed, 780 insertions, 0 deletions
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 7440e7d29..528270d49 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -1,6 +1,9 @@
1//! A simplified AST that only contains items. 1//! A simplified AST that only contains items.
2 2
3mod lower; 3mod lower;
4mod pretty;
5#[cfg(test)]
6mod tests;
4 7
5use std::{ 8use std::{
6 any::type_name, 9 any::type_name,
@@ -205,6 +208,10 @@ impl ItemTree {
205 } 208 }
206 } 209 }
207 210
211 pub fn pretty_print(&self) -> String {
212 pretty::print_item_tree(self)
213 }
214
208 fn data(&self) -> &ItemTreeData { 215 fn data(&self) -> &ItemTreeData {
209 self.data.as_ref().expect("attempted to access data of empty ItemTree") 216 self.data.as_ref().expect("attempted to access data of empty ItemTree")
210 } 217 }
@@ -776,6 +783,10 @@ impl<T> IdRange<T> {
776 fn new(range: Range<Idx<T>>) -> Self { 783 fn new(range: Range<Idx<T>>) -> Self {
777 Self { range: range.start.into_raw().into()..range.end.into_raw().into(), _p: PhantomData } 784 Self { range: range.start.into_raw().into()..range.end.into_raw().into(), _p: PhantomData }
778 } 785 }
786
787 fn is_empty(&self) -> bool {
788 self.range.is_empty()
789 }
779} 790}
780 791
781impl<T> Iterator for IdRange<T> { 792impl<T> Iterator for IdRange<T> {
diff --git a/crates/hir_def/src/item_tree/pretty.rs b/crates/hir_def/src/item_tree/pretty.rs
new file mode 100644
index 000000000..5ec02d1be
--- /dev/null
+++ b/crates/hir_def/src/item_tree/pretty.rs
@@ -0,0 +1,525 @@
1//! `ItemTree` debug printer.
2
3use std::fmt::{self, Write};
4
5use crate::{attr::RawAttrs, visibility::RawVisibility};
6
7use super::*;
8
9pub(super) fn print_item_tree(tree: &ItemTree) -> String {
10 let mut p = Printer { tree, buf: String::new(), indent_level: 0, needs_indent: true };
11
12 if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) {
13 p.print_attrs(attrs, true);
14 }
15 p.blank();
16
17 for item in tree.top_level_items() {
18 p.print_mod_item(*item);
19 }
20
21 let mut s = p.buf.trim_end_matches('\n').to_string();
22 s.push('\n');
23 s
24}
25
26macro_rules! w {
27 ($dst:expr, $($arg:tt)*) => {
28 drop(write!($dst, $($arg)*))
29 };
30}
31
32macro_rules! wln {
33 ($dst:expr) => {
34 drop(writeln!($dst))
35 };
36 ($dst:expr, $($arg:tt)*) => {
37 drop(writeln!($dst, $($arg)*))
38 };
39}
40
41struct Printer<'a> {
42 tree: &'a ItemTree,
43 buf: String,
44 indent_level: usize,
45 needs_indent: bool,
46}
47
48impl<'a> Printer<'a> {
49 fn indented(&mut self, f: impl FnOnce(&mut Self)) {
50 self.indent_level += 1;
51 wln!(self);
52 f(self);
53 self.indent_level -= 1;
54 self.buf = self.buf.trim_end_matches('\n').to_string();
55 }
56
57 /// Ensures that a blank line is output before the next text.
58 fn blank(&mut self) {
59 let mut iter = self.buf.chars().rev().fuse();
60 match (iter.next(), iter.next()) {
61 (Some('\n'), Some('\n')) | (Some('\n'), None) | (None, None) => {}
62 (Some('\n'), Some(_)) => {
63 self.buf.push('\n');
64 }
65 (Some(_), _) => {
66 self.buf.push('\n');
67 self.buf.push('\n');
68 }
69 (None, Some(_)) => unreachable!(),
70 }
71 }
72
73 fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) {
74 let inner = if inner { "!" } else { "" };
75 for attr in &**attrs {
76 wln!(
77 self,
78 "#{}[{}{}] // {:?}",
79 inner,
80 attr.path,
81 attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(),
82 attr.id,
83 );
84 }
85 }
86
87 fn print_attrs_of(&mut self, of: impl Into<AttrOwner>) {
88 if let Some(attrs) = self.tree.attrs.get(&of.into()) {
89 self.print_attrs(attrs, false);
90 }
91 }
92
93 fn print_visibility(&mut self, vis: RawVisibilityId) {
94 match &self.tree[vis] {
95 RawVisibility::Module(path) => w!(self, "pub({}) ", path),
96 RawVisibility::Public => w!(self, "pub "),
97 };
98 }
99
100 fn print_fields(&mut self, fields: &Fields) {
101 match fields {
102 Fields::Record(fields) => {
103 w!(self, " {{");
104 self.indented(|this| {
105 for field in fields.clone() {
106 let Field { visibility, name, type_ref } = &this.tree[field];
107 this.print_attrs_of(field);
108 this.print_visibility(*visibility);
109 w!(this, "{}: ", name);
110 this.print_type_ref(type_ref);
111 wln!(this, ",");
112 }
113 });
114 w!(self, "}}");
115 }
116 Fields::Tuple(fields) => {
117 w!(self, "(");
118 self.indented(|this| {
119 for field in fields.clone() {
120 let Field { visibility, name, type_ref } = &this.tree[field];
121 this.print_attrs_of(field);
122 this.print_visibility(*visibility);
123 w!(this, "{}: ", name);
124 this.print_type_ref(type_ref);
125 wln!(this, ",");
126 }
127 });
128 w!(self, ")");
129 }
130 Fields::Unit => {}
131 }
132 }
133
134 fn print_mod_item(&mut self, item: ModItem) {
135 self.print_attrs_of(item);
136
137 match item {
138 ModItem::Import(it) => {
139 let Import { visibility, path, is_glob, alias, ast_id: _, index } = &self.tree[it];
140 self.print_visibility(*visibility);
141 w!(self, "use {}", path);
142 if *is_glob {
143 w!(self, "::*");
144 }
145 if let Some(alias) = alias {
146 w!(self, " as {}", alias);
147 }
148 wln!(self, "; // {}", index);
149 }
150 ModItem::ExternCrate(it) => {
151 let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it];
152 self.print_visibility(*visibility);
153 w!(self, "extern crate {}", name);
154 if let Some(alias) = alias {
155 w!(self, " as {}", alias);
156 }
157 wln!(self, ";");
158 }
159 ModItem::ExternBlock(it) => {
160 let ExternBlock { abi, ast_id: _, children } = &self.tree[it];
161 w!(self, "extern ");
162 if let Some(abi) = abi {
163 w!(self, "\"{}\" ", abi);
164 }
165 w!(self, "{{");
166 self.indented(|this| {
167 for child in &**children {
168 this.print_mod_item(*child);
169 }
170 });
171 wln!(self, "}}");
172 }
173 ModItem::Function(it) => {
174 let Function {
175 name,
176 visibility,
177 generic_params: _, // FIXME print these somehow
178 abi,
179 params,
180 ret_type,
181 ast_id: _,
182 flags,
183 } = &self.tree[it];
184 if flags.bits != 0 {
185 wln!(self, "// flags = 0x{:X}", flags.bits);
186 }
187 self.print_visibility(*visibility);
188 if let Some(abi) = abi {
189 w!(self, "extern \"{}\" ", abi);
190 }
191 w!(self, "fn {}(", name);
192 if !params.is_empty() {
193 self.indented(|this| {
194 for param in params.clone() {
195 this.print_attrs_of(param);
196 match &this.tree[param] {
197 Param::Normal(ty) => {
198 w!(this, "_: ");
199 this.print_type_ref(ty);
200 wln!(this, ",");
201 }
202 Param::Varargs => {
203 wln!(this, "...");
204 }
205 };
206 }
207 });
208 }
209 w!(self, ") -> ");
210 self.print_type_ref(ret_type);
211 wln!(self, ";");
212 }
213 ModItem::Struct(it) => {
214 let Struct { visibility, name, fields, generic_params: _, ast_id: _ } =
215 &self.tree[it];
216 self.print_visibility(*visibility);
217 w!(self, "struct {}", name);
218 self.print_fields(fields);
219 if matches!(fields, Fields::Record(_)) {
220 wln!(self);
221 } else {
222 wln!(self, ";");
223 }
224 }
225 ModItem::Union(it) => {
226 let Union { name, visibility, fields, generic_params: _, ast_id: _ } =
227 &self.tree[it];
228 self.print_visibility(*visibility);
229 w!(self, "union {}", name);
230 self.print_fields(fields);
231 if matches!(fields, Fields::Record(_)) {
232 wln!(self);
233 } else {
234 wln!(self, ";");
235 }
236 }
237 ModItem::Enum(it) => {
238 let Enum { name, visibility, variants, generic_params: _, ast_id: _ } =
239 &self.tree[it];
240 self.print_visibility(*visibility);
241 w!(self, "enum {} {{", name);
242 self.indented(|this| {
243 for variant in variants.clone() {
244 let Variant { name, fields } = &this.tree[variant];
245 this.print_attrs_of(variant);
246 w!(this, "{}", name);
247 this.print_fields(fields);
248 wln!(this, ",");
249 }
250 });
251 wln!(self, "}}");
252 }
253 ModItem::Const(it) => {
254 let Const { name, visibility, type_ref, ast_id: _ } = &self.tree[it];
255 self.print_visibility(*visibility);
256 w!(self, "const ");
257 match name {
258 Some(name) => w!(self, "{}", name),
259 None => w!(self, "_"),
260 }
261 w!(self, ": ");
262 self.print_type_ref(type_ref);
263 wln!(self, " = _;");
264 }
265 ModItem::Static(it) => {
266 let Static { name, visibility, mutable, is_extern, type_ref, ast_id: _ } =
267 &self.tree[it];
268 self.print_visibility(*visibility);
269 w!(self, "static ");
270 if *mutable {
271 w!(self, "mut ");
272 }
273 w!(self, "{}: ", name);
274 self.print_type_ref(type_ref);
275 w!(self, " = _;");
276 if *is_extern {
277 w!(self, " // extern");
278 }
279 wln!(self);
280 }
281 ModItem::Trait(it) => {
282 let Trait {
283 name,
284 visibility,
285 is_auto,
286 is_unsafe,
287 bounds,
288 items,
289 generic_params: _,
290 ast_id: _,
291 } = &self.tree[it];
292 self.print_visibility(*visibility);
293 if *is_unsafe {
294 w!(self, "unsafe ");
295 }
296 if *is_auto {
297 w!(self, "auto ");
298 }
299 w!(self, "trait {}", name);
300 if !bounds.is_empty() {
301 w!(self, ": ");
302 self.print_type_bounds(bounds);
303 }
304 w!(self, " {{");
305 self.indented(|this| {
306 for item in &**items {
307 this.print_mod_item((*item).into());
308 }
309 });
310 wln!(self, "}}");
311 }
312 ModItem::Impl(it) => {
313 let Impl {
314 target_trait,
315 self_ty,
316 is_negative,
317 items,
318 generic_params: _,
319 ast_id: _,
320 } = &self.tree[it];
321 w!(self, "impl ");
322 if *is_negative {
323 w!(self, "!");
324 }
325 if let Some(tr) = target_trait {
326 self.print_path(&tr.path);
327 w!(self, " for ");
328 }
329 self.print_type_ref(self_ty);
330 w!(self, " {{");
331 self.indented(|this| {
332 for item in &**items {
333 this.print_mod_item((*item).into());
334 }
335 });
336 wln!(self, "}}");
337 }
338 ModItem::TypeAlias(it) => {
339 let TypeAlias {
340 name,
341 visibility,
342 bounds,
343 type_ref,
344 is_extern,
345 generic_params: _,
346 ast_id: _,
347 } = &self.tree[it];
348 self.print_visibility(*visibility);
349 w!(self, "type {}", name);
350 if !bounds.is_empty() {
351 w!(self, ": ");
352 self.print_type_bounds(bounds);
353 }
354 if let Some(ty) = type_ref {
355 w!(self, " = ");
356 self.print_type_ref(ty);
357 }
358 w!(self, ";");
359 if *is_extern {
360 w!(self, " // extern");
361 }
362 wln!(self);
363 }
364 ModItem::Mod(it) => {
365 let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it];
366 self.print_visibility(*visibility);
367 w!(self, "mod {}", name);
368 match kind {
369 ModKind::Inline { items } => {
370 w!(self, " {{");
371 self.indented(|this| {
372 for item in &**items {
373 this.print_mod_item((*item).into());
374 }
375 });
376 wln!(self, "}}");
377 }
378 ModKind::Outline {} => {
379 wln!(self, ";");
380 }
381 }
382 }
383 ModItem::MacroCall(it) => {
384 let MacroCall { path, ast_id: _, fragment: _ } = &self.tree[it];
385 wln!(self, "{}!(...);", path);
386 }
387 ModItem::MacroRules(it) => {
388 let MacroRules { name, ast_id: _ } = &self.tree[it];
389 wln!(self, "macro_rules! {} {{ ... }}", name);
390 }
391 ModItem::MacroDef(it) => {
392 let MacroDef { name, visibility, ast_id: _ } = &self.tree[it];
393 self.print_visibility(*visibility);
394 wln!(self, "macro {} {{ ... }}", name);
395 }
396 }
397
398 self.blank();
399 }
400
401 fn print_type_ref(&mut self, type_ref: &TypeRef) {
402 // FIXME: deduplicate with `HirDisplay` impl
403 match type_ref {
404 TypeRef::Never => w!(self, "!"),
405 TypeRef::Placeholder => w!(self, "_"),
406 TypeRef::Tuple(fields) => {
407 w!(self, "(");
408 for (i, field) in fields.iter().enumerate() {
409 if i != 0 {
410 w!(self, ", ");
411 }
412 self.print_type_ref(field);
413 }
414 w!(self, ")");
415 }
416 TypeRef::Path(path) => self.print_path(path),
417 TypeRef::RawPtr(pointee, mtbl) => {
418 let mtbl = match mtbl {
419 Mutability::Shared => "*const",
420 Mutability::Mut => "*mut",
421 };
422 w!(self, "{} ", mtbl);
423 self.print_type_ref(pointee);
424 }
425 TypeRef::Reference(pointee, lt, mtbl) => {
426 let mtbl = match mtbl {
427 Mutability::Shared => "",
428 Mutability::Mut => "mut ",
429 };
430 w!(self, "&");
431 if let Some(lt) = lt {
432 w!(self, "{} ", lt.name);
433 }
434 w!(self, "{}", mtbl);
435 self.print_type_ref(pointee);
436 }
437 TypeRef::Array(elem, len) => {
438 w!(self, "[");
439 self.print_type_ref(elem);
440 w!(self, "; {}]", len);
441 }
442 TypeRef::Slice(elem) => {
443 w!(self, "[");
444 self.print_type_ref(elem);
445 w!(self, "]");
446 }
447 TypeRef::Fn(args_and_ret, varargs) => {
448 let (ret, args) =
449 args_and_ret.split_last().expect("TypeRef::Fn is missing return type");
450 w!(self, "fn(");
451 for (i, arg) in args.iter().enumerate() {
452 if i != 0 {
453 w!(self, ", ");
454 }
455 self.print_type_ref(arg);
456 }
457 if *varargs {
458 if !args.is_empty() {
459 w!(self, ", ");
460 }
461 w!(self, "...");
462 }
463 w!(self, ") -> ");
464 self.print_type_ref(ret);
465 }
466 TypeRef::Macro(_ast_id) => {
467 w!(self, "<macro>");
468 }
469 TypeRef::Error => drop(write!(self, "{{unknown}}")),
470 TypeRef::ImplTrait(bounds) => {
471 w!(self, "impl ");
472 self.print_type_bounds(bounds);
473 }
474 TypeRef::DynTrait(bounds) => {
475 w!(self, "dyn ");
476 self.print_type_bounds(bounds);
477 }
478 }
479 }
480
481 fn print_type_bounds(&mut self, bounds: &[TypeBound]) {
482 for (i, bound) in bounds.iter().enumerate() {
483 if i != 0 {
484 w!(self, " + ");
485 }
486
487 match bound {
488 TypeBound::Path(path) => self.print_path(path),
489 TypeBound::Lifetime(lt) => w!(self, "{}", lt.name),
490 TypeBound::Error => w!(self, "{{unknown}}"),
491 }
492 }
493 }
494
495 fn print_path(&mut self, path: &Path) {
496 if path.type_anchor().is_none()
497 && path.segments().iter().all(|seg| seg.args_and_bindings.is_none())
498 {
499 w!(self, "{}", path.mod_path());
500 } else {
501 // too complicated, just use `Debug`
502 w!(self, "{:?}", path);
503 }
504 }
505}
506
507impl<'a> Write for Printer<'a> {
508 fn write_str(&mut self, s: &str) -> fmt::Result {
509 for line in s.split_inclusive('\n') {
510 if self.needs_indent {
511 match self.buf.chars().last() {
512 Some('\n') | None => {}
513 _ => self.buf.push('\n'),
514 }
515 self.buf.push_str(&" ".repeat(self.indent_level));
516 self.needs_indent = false;
517 }
518
519 self.buf.push_str(line);
520 self.needs_indent = line.ends_with('\n');
521 }
522
523 Ok(())
524 }
525}
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
new file mode 100644
index 000000000..100ae9b97
--- /dev/null
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -0,0 +1,244 @@
1use base_db::fixture::WithFixture;
2use expect_test::{expect, Expect};
3
4use crate::{db::DefDatabase, test_db::TestDB};
5
6fn check(ra_fixture: &str, expect: Expect) {
7 let (db, file_id) = TestDB::with_single_file(ra_fixture);
8 let item_tree = db.file_item_tree(file_id.into());
9 let pretty = item_tree.pretty_print();
10 expect.assert_eq(&pretty);
11}
12
13#[test]
14fn imports() {
15 check(
16 r#"
17//! file comment
18#![no_std]
19//! another file comment
20
21extern crate self as renamed;
22pub(super) extern crate bli;
23
24pub use crate::path::{nested, items as renamed, Trait as _};
25use globs::*;
26
27/// docs on import
28use crate::{A, B};
29 "#,
30 expect![[r##"
31 #![doc = " file comment"] // AttrId { is_doc_comment: true, ast_index: 0 }
32 #![no_std] // AttrId { is_doc_comment: false, ast_index: 0 }
33 #![doc = " another file comment"] // AttrId { is_doc_comment: true, ast_index: 1 }
34
35 pub(self) extern crate self as renamed;
36
37 pub(super) extern crate bli;
38
39 pub use crate::path::nested; // 0
40
41 pub use crate::path::items as renamed; // 1
42
43 pub use crate::path::Trait as _; // 2
44
45 pub(self) use globs::*; // 0
46
47 #[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 }
48 pub(self) use crate::A; // 0
49
50 #[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 }
51 pub(self) use crate::B; // 1
52 "##]],
53 );
54}
55
56#[test]
57fn extern_blocks() {
58 check(
59 r#"
60#[on_extern_block]
61extern "C" {
62 #[on_extern_type]
63 type ExType;
64
65 #[on_extern_static]
66 static EX_STATIC: u8;
67
68 #[on_extern_fn]
69 fn ex_fn();
70}
71 "#,
72 expect![[r##"
73 #[on_extern_block] // AttrId { is_doc_comment: false, ast_index: 0 }
74 extern "C" {
75 #[on_extern_type] // AttrId { is_doc_comment: false, ast_index: 0 }
76 pub(self) type ExType; // extern
77
78 #[on_extern_static] // AttrId { is_doc_comment: false, ast_index: 0 }
79 pub(self) static EX_STATIC: u8 = _; // extern
80
81 #[on_extern_fn] // AttrId { is_doc_comment: false, ast_index: 0 }
82 // flags = 0x60
83 pub(self) fn ex_fn() -> ();
84 }
85 "##]],
86 );
87}
88
89#[test]
90fn adts() {
91 check(
92 r#"
93struct Unit;
94
95#[derive(Debug)]
96struct Struct {
97 /// fld docs
98 fld: (),
99}
100
101struct Tuple(#[attr] u8);
102
103union Ize {
104 a: (),
105 b: (),
106}
107
108enum E {
109 /// comment on Unit
110 Unit,
111 /// comment on Tuple
112 Tuple(u8),
113 Struct {
114 /// comment on a: u8
115 a: u8,
116 }
117}
118 "#,
119 expect![[r##"
120 pub(self) struct Unit;
121
122 #[derive(Debug)] // AttrId { is_doc_comment: false, ast_index: 0 }
123 pub(self) struct Struct {
124 #[doc = " fld docs"] // AttrId { is_doc_comment: true, ast_index: 0 }
125 pub(self) fld: (),
126 }
127
128 pub(self) struct Tuple(
129 #[attr] // AttrId { is_doc_comment: false, ast_index: 0 }
130 pub(self) 0: u8,
131 );
132
133 pub(self) union Ize {
134 pub(self) a: (),
135 pub(self) b: (),
136 }
137
138 pub(self) enum E {
139 #[doc = " comment on Unit"] // AttrId { is_doc_comment: true, ast_index: 0 }
140 Unit,
141 #[doc = " comment on Tuple"] // AttrId { is_doc_comment: true, ast_index: 0 }
142 Tuple(
143 pub(self) 0: u8,
144 ),
145 Struct {
146 #[doc = " comment on a: u8"] // AttrId { is_doc_comment: true, ast_index: 0 }
147 pub(self) a: u8,
148 },
149 }
150 "##]],
151 );
152}
153
154#[test]
155fn misc() {
156 check(
157 r#"
158pub static mut ST: () = ();
159
160const _: Anon = ();
161
162#[attr]
163fn f(#[attr] arg: u8, _: ()) {
164 #![inner_attr_in_fn]
165}
166
167trait Tr: SuperTrait + 'lifetime {
168 type Assoc: AssocBound = Default;
169 fn method(&self);
170}
171 "#,
172 expect![[r##"
173 pub static mut ST: () = _;
174
175 pub(self) const _: Anon = _;
176
177 #[attr] // AttrId { is_doc_comment: false, ast_index: 0 }
178 #[inner_attr_in_fn] // AttrId { is_doc_comment: false, ast_index: 1 }
179 // flags = 0x2
180 pub(self) fn f(
181 #[attr] // AttrId { is_doc_comment: false, ast_index: 0 }
182 _: u8,
183 _: (),
184 ) -> ();
185
186 pub(self) trait Tr: SuperTrait + 'lifetime {
187 pub(self) type Assoc: AssocBound = Default;
188
189 // flags = 0x1
190 pub(self) fn method(
191 _: &Self,
192 ) -> ();
193 }
194 "##]],
195 );
196}
197
198#[test]
199fn modules() {
200 check(
201 r#"
202/// outer
203mod inline {
204 //! inner
205
206 use super::*;
207
208 fn fn_in_module() {}
209}
210 "#,
211 expect![[r##"
212 #[doc = " outer"] // AttrId { is_doc_comment: true, ast_index: 0 }
213 #[doc = " inner"] // AttrId { is_doc_comment: true, ast_index: 1 }
214 pub(self) mod inline {
215 pub(self) use super::*; // 0
216
217 // flags = 0x2
218 pub(self) fn fn_in_module() -> ();
219 }
220 "##]],
221 );
222}
223
224#[test]
225fn macros() {
226 check(
227 r#"
228macro_rules! m {
229 () => {};
230}
231
232pub macro m2() {}
233
234m!();
235 "#,
236 expect![[r#"
237 macro_rules! m { ... }
238
239 pub macro m2 { ... }
240
241 m!(...);
242 "#]],
243 );
244}