-
-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: improve item tree display (#547)
A refactor to format `ItemTree`s properly. I need this for testing `impl`s.
- Loading branch information
1 parent
5b87d1a
commit 99e5217
Showing
8 changed files
with
339 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
use crate::item_tree::LocalItemTreeId; | ||
use crate::{ | ||
item_tree::{Fields, Function, Import, ItemTree, ModItem, RawVisibilityId, Struct, TypeAlias}, | ||
path::ImportAlias, | ||
pretty::{print_path, print_type_ref}, | ||
type_ref::{LocalTypeRefId, TypeRefMap}, | ||
visibility::RawVisibility, | ||
DefDatabase, | ||
}; | ||
use std::{fmt, fmt::Write}; | ||
|
||
/// A helper method to print an `ItemTree` to a string. | ||
pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> Result<String, fmt::Error> { | ||
let mut p = Printer { | ||
db, | ||
tree, | ||
buf: String::new(), | ||
indent_level: 0, | ||
needs_indent: true, | ||
}; | ||
|
||
for item in tree.top_level_items() { | ||
p.print_mod_item(*item)?; | ||
} | ||
|
||
let mut s = p.buf.trim_end_matches('\n').to_string(); | ||
s.push('\n'); | ||
Ok(s) | ||
} | ||
|
||
/// A helper struct for [`print_item_tree`] that keeps track of the current indentation level. | ||
struct Printer<'a> { | ||
db: &'a dyn DefDatabase, | ||
tree: &'a ItemTree, | ||
buf: String, | ||
indent_level: usize, | ||
needs_indent: bool, | ||
} | ||
|
||
impl Printer<'_> { | ||
/// Run the specified closure with an increased indentation level. | ||
fn indented(&mut self, f: impl FnOnce(&mut Self) -> fmt::Result) -> fmt::Result { | ||
self.indent_level += 1; | ||
writeln!(self)?; | ||
f(self)?; | ||
self.indent_level -= 1; | ||
self.buf = self.buf.trim_end_matches('\n').to_string(); | ||
Ok(()) | ||
} | ||
|
||
// Add a whitespace to the end of the buffer if the last character is not a newline or space. | ||
fn whitespace(&mut self) -> fmt::Result { | ||
match self.buf.chars().next_back() { | ||
None | Some('\n' | ' ') => {} | ||
_ => self.buf.push(' '), | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Print a module item to the buffer. | ||
fn print_mod_item(&mut self, item: ModItem) -> fmt::Result { | ||
match item { | ||
ModItem::Function(it) => self.print_function(it), | ||
ModItem::Struct(it) => self.print_struct(it), | ||
ModItem::TypeAlias(it) => self.print_type_alias(it), | ||
ModItem::Import(it) => self.print_use(it), | ||
} | ||
} | ||
|
||
/// Prints a use statement to the buffer. | ||
fn print_use(&mut self, it: LocalItemTreeId<Import>) -> fmt::Result { | ||
let Import { | ||
path, | ||
alias, | ||
visibility, | ||
is_glob, | ||
ast_id: _, | ||
index: _, | ||
} = &self.tree[it]; | ||
self.print_visibility(*visibility)?; | ||
write!(self, "use ")?; | ||
print_path(self.db, path, self)?; | ||
if *is_glob { | ||
write!(self, "::*")?; | ||
} | ||
match alias { | ||
Some(ImportAlias::Alias(name)) => write!(self, " as {name}")?, | ||
Some(ImportAlias::Underscore) => write!(self, " as _")?, | ||
None => {} | ||
} | ||
writeln!(self, ";") | ||
} | ||
|
||
/// Prints a type alias to the buffer. | ||
fn print_type_alias(&mut self, it: LocalItemTreeId<TypeAlias>) -> fmt::Result { | ||
let TypeAlias { | ||
name, | ||
visibility, | ||
types, | ||
type_ref, | ||
ast_id: _, | ||
} = &self.tree[it]; | ||
self.print_visibility(*visibility)?; | ||
write!(self, "type {name}")?; | ||
if let Some(ty) = type_ref { | ||
write!(self, " = ")?; | ||
self.print_type_ref(*ty, types)?; | ||
} | ||
writeln!(self, ";") | ||
} | ||
|
||
/// Prints a struct to the buffer. | ||
fn print_struct(&mut self, it: LocalItemTreeId<Struct>) -> fmt::Result { | ||
let Struct { | ||
visibility, | ||
name, | ||
types, | ||
fields, | ||
ast_id: _, | ||
} = &self.tree[it]; | ||
self.print_visibility(*visibility)?; | ||
write!(self, "struct {name}")?; | ||
match fields { | ||
Fields::Record(fields) => { | ||
self.whitespace()?; | ||
write!(self, "{{")?; | ||
self.indented(|this| { | ||
for field in fields.clone() { | ||
let field = &this.tree[field]; | ||
write!(this, "{}: ", field.name)?; | ||
this.print_type_ref(field.type_ref, types)?; | ||
writeln!(this, ",")?; | ||
} | ||
Ok(()) | ||
})?; | ||
write!(self, "}}")?; | ||
} | ||
Fields::Tuple(fields) => { | ||
write!(self, "(")?; | ||
self.indented(|this| { | ||
for field in fields.clone() { | ||
let field = &this.tree[field]; | ||
this.print_type_ref(field.type_ref, types)?; | ||
writeln!(this, ",")?; | ||
} | ||
Ok(()) | ||
})?; | ||
write!(self, ")")?; | ||
} | ||
Fields::Unit => {} | ||
}; | ||
if matches!(fields, Fields::Record(_)) { | ||
writeln!(self) | ||
} else { | ||
writeln!(self, ";") | ||
} | ||
} | ||
|
||
/// Prints a function to the buffer. | ||
fn print_function(&mut self, it: LocalItemTreeId<Function>) -> fmt::Result { | ||
let Function { | ||
name, | ||
visibility, | ||
is_extern, | ||
types, | ||
params, | ||
ret_type, | ||
ast_id: _, | ||
} = &self.tree[it]; | ||
self.print_visibility(*visibility)?; | ||
if *is_extern { | ||
write!(self, "extern ")?; | ||
} | ||
write!(self, "fn {name}")?; | ||
write!(self, "(")?; | ||
if !params.is_empty() { | ||
self.indented(|this| { | ||
for param in params.iter().copied() { | ||
this.print_type_ref(param, types)?; | ||
writeln!(this, ",")?; | ||
} | ||
Ok(()) | ||
})?; | ||
} | ||
write!(self, ") -> ")?; | ||
self.print_type_ref(*ret_type, types)?; | ||
writeln!(self, ";") | ||
} | ||
|
||
/// Prints a [`RawVisibilityId`] to the buffer. | ||
fn print_visibility(&mut self, vis: RawVisibilityId) -> fmt::Result { | ||
match &self.tree[vis] { | ||
RawVisibility::This => Ok(()), | ||
RawVisibility::Super => write!(self, "pub(super) "), | ||
RawVisibility::Package => write!(self, "pub(package) "), | ||
RawVisibility::Public => write!(self, "pub "), | ||
} | ||
} | ||
|
||
/// Prints a type reference to the buffer. | ||
fn print_type_ref(&mut self, type_ref: LocalTypeRefId, map: &TypeRefMap) -> fmt::Result { | ||
print_type_ref(self.db, map, type_ref, self) | ||
} | ||
} | ||
|
||
impl Write for Printer<'_> { | ||
fn write_str(&mut self, s: &str) -> fmt::Result { | ||
for line in s.split_inclusive('\n') { | ||
if self.needs_indent { | ||
match self.buf.chars().last() { | ||
Some('\n') | None => {} | ||
_ => self.buf.push('\n'), | ||
} | ||
self.buf.push_str(&" ".repeat(self.indent_level)); | ||
self.needs_indent = false; | ||
} | ||
|
||
self.buf.push_str(line); | ||
self.needs_indent = line.ends_with('\n'); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
70 changes: 47 additions & 23 deletions
70
crates/mun_hir/src/item_tree/snapshots/mun_hir__item_tree__tests__top_level_items.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,51 @@ | ||
--- | ||
source: crates/mun_hir/src/item_tree/tests.rs | ||
assertion_line: 61 | ||
expression: "print_item_tree(r#\"\n fn foo(a:i32, b:u8, c:String) -> i32 {}\n pub fn bar(a:i32, b:u8, c:String) -> {}\n pub(super) fn bar(a:i32, b:u8, c:String) -> {}\n pub(package) fn baz(a:i32, b:, c:String) -> {}\n extern fn eval(a:String) -> bool;\n\n struct Foo {\n a: i32,\n b: u8,\n c: String,\n }\n struct Foo2 {\n a: i32,\n b: ,\n c: String,\n }\n struct Bar (i32, u32, String)\n struct Baz;\n\n type FooBar = Foo;\n type FooBar = package::Foo;\n \"#).unwrap()" | ||
expression: "print_item_tree(r#\"\n fn foo(a:i32, b:u8, c:String) -> i32 {}\n pub fn bar(a:i32, b:u8, c:String) -> {}\n pub(super) fn bar(a:i32, b:u8, c:String) -> {}\n pub(package) fn baz(a:i32, b:, c:String) -> {}\n extern fn eval(a:String) -> bool;\n\n struct Foo {\n a: i32,\n b: u8,\n c: String,\n }\n struct Foo2 {\n a: i32,\n b: ,\n c: String,\n }\n struct Bar (i32, u32, String)\n struct Baz;\n\n type FooBar = Foo;\n type FooBar = package::Foo;\n\n pub use foo;\n use super::bar;\n use super::*;\n use foo::{bar as _, baz::hello as world};\n \"#).unwrap()" | ||
--- | ||
top-level items: | ||
Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), is_extern: false, types: TypeRefMap { type_refs: Arena { len: 4, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] }), Path(Path { kind: Plain, segments: [Name(Text("i32"))] })] } }, params: [Idx::<TypeRef>(0), Idx::<TypeRef>(1), Idx::<TypeRef>(2)], ret_type: Idx::<TypeRef>(3), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(0), _ty: PhantomData<fn() -> mun_syntax::ast::generated::FunctionDef> } } | ||
Function { name: Name(Text("bar")), visibility: RawVisibilityId("pub"), is_extern: false, types: TypeRefMap { type_refs: Arena { len: 4, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] }), Tuple([])] } }, params: [Idx::<TypeRef>(0), Idx::<TypeRef>(1), Idx::<TypeRef>(2)], ret_type: Idx::<TypeRef>(3), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(1), _ty: PhantomData<fn() -> mun_syntax::ast::generated::FunctionDef> } } | ||
Function { name: Name(Text("bar")), visibility: RawVisibilityId("pub(super)"), is_extern: false, types: TypeRefMap { type_refs: Arena { len: 4, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] }), Tuple([])] } }, params: [Idx::<TypeRef>(0), Idx::<TypeRef>(1), Idx::<TypeRef>(2)], ret_type: Idx::<TypeRef>(3), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(2), _ty: PhantomData<fn() -> mun_syntax::ast::generated::FunctionDef> } } | ||
Function { name: Name(Text("baz")), visibility: RawVisibilityId("pub(package)"), is_extern: false, types: TypeRefMap { type_refs: Arena { len: 4, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Error, Path(Path { kind: Plain, segments: [Name(Text("String"))] }), Tuple([])] } }, params: [Idx::<TypeRef>(0), Idx::<TypeRef>(1), Idx::<TypeRef>(2)], ret_type: Idx::<TypeRef>(3), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(3), _ty: PhantomData<fn() -> mun_syntax::ast::generated::FunctionDef> } } | ||
Function { name: Name(Text("eval")), visibility: RawVisibilityId("pub(self)"), is_extern: true, types: TypeRefMap { type_refs: Arena { len: 2, data: [Path(Path { kind: Plain, segments: [Name(Text("String"))] }), Path(Path { kind: Plain, segments: [Name(Text("bool"))] })] } }, params: [Idx::<TypeRef>(0)], ret_type: Idx::<TypeRef>(1), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(4), _ty: PhantomData<fn() -> mun_syntax::ast::generated::FunctionDef> } } | ||
Struct { name: Name(Text("Foo")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 3, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] })] } }, fields: Record(IdRange::<mun_hir::item_tree::Field>(0..3)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(5), _ty: PhantomData<fn() -> mun_syntax::ast::generated::StructDef> }, kind: Record } | ||
> Field { name: Name(Text("a")), type_ref: Idx::<TypeRef>(0) } | ||
> Field { name: Name(Text("b")), type_ref: Idx::<TypeRef>(1) } | ||
> Field { name: Name(Text("c")), type_ref: Idx::<TypeRef>(2) } | ||
Struct { name: Name(Text("Foo2")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 3, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Error, Path(Path { kind: Plain, segments: [Name(Text("String"))] })] } }, fields: Record(IdRange::<mun_hir::item_tree::Field>(3..6)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(6), _ty: PhantomData<fn() -> mun_syntax::ast::generated::StructDef> }, kind: Record } | ||
> Field { name: Name(Text("a")), type_ref: Idx::<TypeRef>(0) } | ||
> Field { name: Name(Text("b")), type_ref: Idx::<TypeRef>(1) } | ||
> Field { name: Name(Text("c")), type_ref: Idx::<TypeRef>(2) } | ||
Struct { name: Name(Text("Bar")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 3, data: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u32"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] })] } }, fields: Tuple(IdRange::<mun_hir::item_tree::Field>(6..9)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(7), _ty: PhantomData<fn() -> mun_syntax::ast::generated::StructDef> }, kind: Tuple } | ||
> Field { name: Name(TupleField(0)), type_ref: Idx::<TypeRef>(0) } | ||
> Field { name: Name(TupleField(1)), type_ref: Idx::<TypeRef>(1) } | ||
> Field { name: Name(TupleField(2)), type_ref: Idx::<TypeRef>(2) } | ||
Struct { name: Name(Text("Baz")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 0, data: [] } }, fields: Unit, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(8), _ty: PhantomData<fn() -> mun_syntax::ast::generated::StructDef> }, kind: Unit } | ||
TypeAlias { name: Name(Text("FooBar")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 1, data: [Path(Path { kind: Plain, segments: [Name(Text("Foo"))] })] } }, type_ref: Some(Idx::<TypeRef>(0)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(9), _ty: PhantomData<fn() -> mun_syntax::ast::generated::TypeAliasDef> } } | ||
TypeAlias { name: Name(Text("FooBar")), visibility: RawVisibilityId("pub(self)"), types: TypeRefMap { type_refs: Arena { len: 1, data: [Path(Path { kind: Package, segments: [Name(Text("Foo"))] })] } }, type_ref: Some(Idx::<TypeRef>(0)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(10), _ty: PhantomData<fn() -> mun_syntax::ast::generated::TypeAliasDef> } } | ||
fn foo( | ||
i32, | ||
u8, | ||
String, | ||
) -> i32; | ||
pub fn bar( | ||
i32, | ||
u8, | ||
String, | ||
) -> (); | ||
pub(super) fn bar( | ||
i32, | ||
u8, | ||
String, | ||
) -> (); | ||
pub(package) fn baz( | ||
i32, | ||
{unknown}, | ||
String, | ||
) -> (); | ||
extern fn eval( | ||
String, | ||
) -> bool; | ||
struct Foo { | ||
a: i32, | ||
b: u8, | ||
c: String, | ||
} | ||
struct Foo2 { | ||
a: i32, | ||
b: {unknown}, | ||
c: String, | ||
} | ||
struct Bar( | ||
i32, | ||
u32, | ||
String, | ||
); | ||
struct Baz; | ||
type FooBar = Foo; | ||
type FooBar = package::Foo; | ||
pub use foo; | ||
use super::bar; | ||
use super::*; | ||
use foo::bar as _; | ||
use foo::baz::hello as world; | ||
|
Oops, something went wrong.