aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-22 00:18:19 +0100
committerGitHub <[email protected]>2021-05-22 00:18:19 +0100
commitae24651e445444d4ed4275a717ac10980f2957a4 (patch)
tree9ae9115957ab35a3bc7d2d446bce72031328af1a
parent5b6c0c1af290996a407fb4be51e852317dfab7c2 (diff)
parent463ecefc64a48d80b2c4591fd4a1b82ae62b2897 (diff)
Merge #8916
8916: ItemTree pretty-printing r=jonas-schievink a=jonas-schievink This adds a printer for `ItemTree` contents, and a few tests to ensure that `ItemTree` lowering works like we expect it to. It also adds a new "Debug ItemTree" command that can be used to see the `ItemTree` of the currently open file. The pretty-printed output is usually close enough to Rust syntax that we can even use Rust syntax highlighting. This is similar to the old `ItemTree` tests we had, but produces significantly more readable output, so these should actually carry their weight. Co-authored-by: Jonas Schievink <[email protected]>
-rw-r--r--crates/hir_def/src/attr.rs11
-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
-rw-r--r--crates/hir_def/src/path.rs9
-rw-r--r--crates/ide/src/lib.rs5
-rw-r--r--crates/ide/src/view_item_tree.rs16
-rw-r--r--crates/rust-analyzer/src/handlers.rs10
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs14
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--docs/dev/lsp-extensions.md20
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/commands.ts50
-rw-r--r--editors/code/src/lsp_ext.ts6
-rw-r--r--editors/code/src/main.ts1
15 files changed, 925 insertions, 3 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index aadd4e44a..89a1ea770 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -2,7 +2,7 @@
2 2
3use std::{ 3use std::{
4 convert::{TryFrom, TryInto}, 4 convert::{TryFrom, TryInto},
5 ops, 5 fmt, ops,
6 sync::Arc, 6 sync::Arc,
7}; 7};
8 8
@@ -648,6 +648,15 @@ pub enum AttrInput {
648 TokenTree(Subtree), 648 TokenTree(Subtree),
649} 649}
650 650
651impl fmt::Display for AttrInput {
652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653 match self {
654 AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()),
655 AttrInput::TokenTree(subtree) => subtree.fmt(f),
656 }
657 }
658}
659
651impl Attr { 660impl Attr {
652 fn from_src( 661 fn from_src(
653 db: &dyn DefDatabase, 662 db: &dyn DefDatabase,
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}
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index a43441b1c..9b8873fd2 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -46,6 +46,15 @@ pub enum ImportAlias {
46 Alias(Name), 46 Alias(Name),
47} 47}
48 48
49impl Display for ImportAlias {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 ImportAlias::Underscore => f.write_str("_"),
53 ImportAlias::Alias(name) => f.write_str(&name.to_string()),
54 }
55 }
56}
57
49impl ModPath { 58impl ModPath {
50 pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> { 59 pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> {
51 let ctx = LowerCtx::with_hygiene(db, hygiene); 60 let ctx = LowerCtx::with_hygiene(db, hygiene);
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f4b90db3a..ff2a54117 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -50,6 +50,7 @@ mod typing;
50mod markdown_remove; 50mod markdown_remove;
51mod doc_links; 51mod doc_links;
52mod view_crate_graph; 52mod view_crate_graph;
53mod view_item_tree;
53 54
54use std::sync::Arc; 55use std::sync::Arc;
55 56
@@ -288,6 +289,10 @@ impl Analysis {
288 self.with_db(|db| view_hir::view_hir(&db, position)) 289 self.with_db(|db| view_hir::view_hir(&db, position))
289 } 290 }
290 291
292 pub fn view_item_tree(&self, file_id: FileId) -> Cancelable<String> {
293 self.with_db(|db| view_item_tree::view_item_tree(&db, file_id))
294 }
295
291 /// Renders the crate graph to GraphViz "dot" syntax. 296 /// Renders the crate graph to GraphViz "dot" syntax.
292 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> { 297 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
293 self.with_db(|db| view_crate_graph::view_crate_graph(&db)) 298 self.with_db(|db| view_crate_graph::view_crate_graph(&db))
diff --git a/crates/ide/src/view_item_tree.rs b/crates/ide/src/view_item_tree.rs
new file mode 100644
index 000000000..3dc03085d
--- /dev/null
+++ b/crates/ide/src/view_item_tree.rs
@@ -0,0 +1,16 @@
1use hir::db::DefDatabase;
2use ide_db::base_db::FileId;
3use ide_db::RootDatabase;
4
5// Feature: Debug ItemTree
6//
7// Displays the ItemTree of the currently open file, for debugging.
8//
9// |===
10// | Editor | Action Name
11//
12// | VS Code | **Rust Analyzer: Debug ItemTree**
13// |===
14pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String {
15 db.file_item_tree(file_id.into()).pretty_print()
16}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 51041d7a0..aa12fd94b 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -117,6 +117,16 @@ pub(crate) fn handle_view_hir(
117 Ok(res) 117 Ok(res)
118} 118}
119 119
120pub(crate) fn handle_view_item_tree(
121 snap: GlobalStateSnapshot,
122 params: lsp_ext::ViewItemTreeParams,
123) -> Result<String> {
124 let _p = profile::span("handle_view_item_tree");
125 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
126 let res = snap.analysis.view_item_tree(file_id)?;
127 Ok(res)
128}
129
120pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> { 130pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> {
121 let _p = profile::span("handle_view_crate_graph"); 131 let _p = profile::span("handle_view_crate_graph");
122 let dot = snap.analysis.view_crate_graph()??; 132 let dot = snap.analysis.view_crate_graph()??;
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 34b53a7a8..905048793 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -70,6 +70,20 @@ impl Request for ViewCrateGraph {
70 const METHOD: &'static str = "rust-analyzer/viewCrateGraph"; 70 const METHOD: &'static str = "rust-analyzer/viewCrateGraph";
71} 71}
72 72
73#[derive(Deserialize, Serialize, Debug)]
74#[serde(rename_all = "camelCase")]
75pub struct ViewItemTreeParams {
76 pub text_document: TextDocumentIdentifier,
77}
78
79pub enum ViewItemTree {}
80
81impl Request for ViewItemTree {
82 type Params = ViewItemTreeParams;
83 type Result = String;
84 const METHOD: &'static str = "rust-analyzer/viewItemTree";
85}
86
73pub enum ExpandMacro {} 87pub enum ExpandMacro {}
74 88
75impl Request for ExpandMacro { 89impl Request for ExpandMacro {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4e0791611..f837b89dd 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -514,6 +514,7 @@ impl GlobalState {
514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree) 514 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir) 515 .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
516 .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph) 516 .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
517 .on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
517 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) 518 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
518 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) 519 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
519 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 520 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 3c4eacfeb..fbe2ce1c9 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: 10a8988e6893e6b2 2lsp_ext.rs hash: 49f253e4a9307d4f
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -464,7 +464,7 @@ Clients are discouraged from but are allowed to use the `health` status to decid
464**Request:** 464**Request:**
465 465
466```typescript 466```typescript
467interface SyntaxTeeParams { 467interface SyntaxTreeParams {
468 textDocument: TextDocumentIdentifier, 468 textDocument: TextDocumentIdentifier,
469 range?: Range, 469 range?: Range,
470} 470}
@@ -486,6 +486,22 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
486Returns a textual representation of the HIR of the function containing the cursor. 486Returns a textual representation of the HIR of the function containing the cursor.
487For debugging or when working on rust-analyzer itself. 487For debugging or when working on rust-analyzer itself.
488 488
489## View ItemTree
490
491**Method:** `rust-analyzer/viewItemTree`
492
493**Request:**
494
495```typescript
496interface ViewItemTreeParams {
497 textDocument: TextDocumentIdentifier,
498}
499```
500
501**Response:** `string`
502
503Returns a textual representation of the `ItemTree` of the currently open file, for debugging.
504
489## View Crate Graph 505## View Crate Graph
490 506
491**Method:** `rust-analyzer/viewCrateGraph` 507**Method:** `rust-analyzer/viewCrateGraph`
diff --git a/editors/code/package.json b/editors/code/package.json
index 1743b374c..17d9281ff 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -110,6 +110,11 @@
110 "category": "Rust Analyzer" 110 "category": "Rust Analyzer"
111 }, 111 },
112 { 112 {
113 "command": "rust-analyzer.viewItemTree",
114 "title": "Debug ItemTree",
115 "category": "Rust Analyzer"
116 },
117 {
113 "command": "rust-analyzer.viewCrateGraph", 118 "command": "rust-analyzer.viewCrateGraph",
114 "title": "View Crate Graph", 119 "title": "View Crate Graph",
115 "category": "Rust Analyzer" 120 "category": "Rust Analyzer"
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 8ab259af2..0fdb9fe05 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -429,6 +429,56 @@ export function viewHir(ctx: Ctx): Cmd {
429 }; 429 };
430} 430}
431 431
432export function viewItemTree(ctx: Ctx): Cmd {
433 const tdcp = new class implements vscode.TextDocumentContentProvider {
434 readonly uri = vscode.Uri.parse('rust-analyzer://viewItemTree/itemtree.rs');
435 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
436 constructor() {
437 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
438 vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
439 }
440
441 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
442 if (isRustDocument(event.document)) {
443 // We need to order this after language server updates, but there's no API for that.
444 // Hence, good old sleep().
445 void sleep(10).then(() => this.eventEmitter.fire(this.uri));
446 }
447 }
448 private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
449 if (editor && isRustEditor(editor)) {
450 this.eventEmitter.fire(this.uri);
451 }
452 }
453
454 provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
455 const rustEditor = ctx.activeRustEditor;
456 const client = ctx.client;
457 if (!rustEditor || !client) return '';
458
459 const params = {
460 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
461 };
462 return client.sendRequest(ra.viewItemTree, params, ct);
463 }
464
465 get onDidChange(): vscode.Event<vscode.Uri> {
466 return this.eventEmitter.event;
467 }
468 };
469
470 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
471
472 return async () => {
473 const document = await vscode.workspace.openTextDocument(tdcp.uri);
474 tdcp.eventEmitter.fire(tdcp.uri);
475 void await vscode.window.showTextDocument(document, {
476 viewColumn: vscode.ViewColumn.Two,
477 preserveFocus: true
478 });
479 };
480}
481
432export function viewCrateGraph(ctx: Ctx): Cmd { 482export function viewCrateGraph(ctx: Ctx): Cmd {
433 return async () => { 483 return async () => {
434 const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two); 484 const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two);
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index aa745a65c..6d5c2ea72 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -27,6 +27,12 @@ export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("ru
27 27
28export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir"); 28export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir");
29 29
30export interface ViewItemTreeParams {
31 textDocument: lc.TextDocumentIdentifier;
32}
33
34export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>("rust-analyzer/viewItemTree");
35
30export const viewCrateGraph = new lc.RequestType0<string, void>("rust-analyzer/viewCrateGraph"); 36export const viewCrateGraph = new lc.RequestType0<string, void>("rust-analyzer/viewCrateGraph");
31 37
32export interface ExpandMacroParams { 38export interface ExpandMacroParams {
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 516322d03..92c797d47 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -106,6 +106,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
106 ctx.registerCommand('parentModule', commands.parentModule); 106 ctx.registerCommand('parentModule', commands.parentModule);
107 ctx.registerCommand('syntaxTree', commands.syntaxTree); 107 ctx.registerCommand('syntaxTree', commands.syntaxTree);
108 ctx.registerCommand('viewHir', commands.viewHir); 108 ctx.registerCommand('viewHir', commands.viewHir);
109 ctx.registerCommand('viewItemTree', commands.viewItemTree);
109 ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph); 110 ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph);
110 ctx.registerCommand('expandMacro', commands.expandMacro); 111 ctx.registerCommand('expandMacro', commands.expandMacro);
111 ctx.registerCommand('run', commands.run); 112 ctx.registerCommand('run', commands.run);