Skip to content

Commit

Permalink
refactor: improve item tree display (#547)
Browse files Browse the repository at this point in the history
A refactor to format `ItemTree`s properly. I need this for testing
`impl`s.
  • Loading branch information
baszalmstra authored Dec 28, 2023
1 parent 5b87d1a commit 99e5217
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 88 deletions.
2 changes: 1 addition & 1 deletion crates/mun_hir/src/item_tree.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod lower;
mod pretty;
#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -306,7 +307,6 @@ pub struct Struct {
pub types: TypeRefMap,
pub fields: Fields,
pub ast_id: FileAstId<ast::StructDef>,
pub kind: StructDefKind,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
8 changes: 1 addition & 7 deletions crates/mun_hir/src/item_tree/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use super::{
diagnostics, Field, Fields, Function, IdRange, ItemTree, ItemTreeData, ItemTreeNode,
ItemVisibilities, LocalItemTreeId, ModItem, RawVisibilityId, Struct, StructDefKind, TypeAlias,
ItemVisibilities, LocalItemTreeId, ModItem, RawVisibilityId, Struct, TypeAlias,
};
use crate::item_tree::Import;
use crate::type_ref::{TypeRefMap, TypeRefMapBuilder};
Expand Down Expand Up @@ -183,11 +183,6 @@ impl Context {
let mut types = TypeRefMap::builder();
let fields = self.lower_fields(&strukt.kind(), &mut types);
let ast_id = self.source_ast_id_map.ast_id(strukt);
let kind = match strukt.kind() {
StructKind::Record(_) => StructDefKind::Record,
StructKind::Tuple(_) => StructDefKind::Tuple,
StructKind::Unit => StructDefKind::Unit,
};

let (types, _types_source_map) = types.finish();
let res = Struct {
Expand All @@ -196,7 +191,6 @@ impl Context {
types,
fields,
ast_id,
kind,
};
Some(self.data.structs.alloc(res).into())
}
Expand Down
224 changes: 224 additions & 0 deletions crates/mun_hir/src/item_tree/pretty.rs
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(())
}
}
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;

Loading

0 comments on commit 99e5217

Please sign in to comment.