//! `ItemTree` debug printer. use std::fmt::{self, Write}; use span::{Edition, ErasedFileAstId}; use crate::{ item_tree::{ Const, DefDatabase, Enum, ExternBlock, ExternCrate, FieldsShape, Function, Impl, ItemTree, Macro2, MacroCall, MacroRules, Mod, ModItemId, ModKind, RawVisibilityId, Static, Struct, Trait, TypeAlias, Union, Use, UseTree, UseTreeKind, attrs::AttrsOrCfg, }, visibility::RawVisibility, }; pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree, edition: Edition) -> String { let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true, edition }; p.print_attrs(&tree.top_attrs, true, "\n"); p.blank(); for item in tree.top_level_items() { p.print_mod_item(*item); } let mut s = p.buf.trim_end_matches('\n').to_owned(); s.push('\n'); s } macro_rules! w { ($dst:expr, $($arg:tt)*) => { { let _ = write!($dst, $($arg)*); } }; } macro_rules! wln { ($dst:expr) => { { let _ = writeln!($dst); } }; ($dst:expr, $($arg:tt)*) => { { let _ = writeln!($dst, $($arg)*); } }; } struct Printer<'a> { db: &'a dyn DefDatabase, tree: &'a ItemTree, buf: String, indent_level: usize, needs_indent: bool, edition: Edition, } impl Printer<'_> { fn indented(&mut self, f: impl FnOnce(&mut Self)) { self.indent_level += 1; wln!(self); f(self); self.indent_level -= 1; self.buf = self.buf.trim_end_matches('\n').to_owned(); } /// Ensures that a blank line is output before the next text. fn blank(&mut self) { let mut iter = self.buf.chars().rev().fuse(); match (iter.next(), iter.next()) { (Some('\n'), Some('\n') | None) | (None, None) => {} (Some('\n'), Some(_)) => { self.buf.push('\n'); } (Some(_), _) => { self.buf.push('\n'); self.buf.push('\n'); } (None, Some(_)) => unreachable!(), } } fn whitespace(&mut self) { match self.buf.chars().next_back() { None | Some('\n' | ' ') => {} _ => self.buf.push(' '), } } fn print_attrs(&mut self, attrs: &AttrsOrCfg, inner: bool, separated_by: &str) { let (cfg_disabled_expr, attrs) = match attrs { AttrsOrCfg::Enabled { attrs } => (None, attrs), AttrsOrCfg::CfgDisabled(inner_box) => (Some(&inner_box.0), &inner_box.1), }; let inner = if inner { "!" } else { "" }; for attr in &*attrs.as_ref() { w!( self, "#{}[{}{}]{}", inner, attr.path.display(self.db, self.edition), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), separated_by, ); } if let Some(expr) = cfg_disabled_expr { w!(self, "#{inner}[cfg({expr})]{separated_by}"); } } fn print_attrs_of(&mut self, of: ModItemId, separated_by: &str) { if let Some(attrs) = self.tree.attrs.get(&of.ast_id()) { self.print_attrs(attrs, false, separated_by); } } fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { RawVisibility::Module(path, _expl) => { w!(self, "pub(in {}) ", path.display(self.db, self.edition)) } RawVisibility::Public => w!(self, "pub "), RawVisibility::PubCrate => w!(self, "pub(crate) "), RawVisibility::PubSelf(_) => w!(self, "pub(self) "), }; } fn print_fields(&mut self, kind: FieldsShape) { match kind { FieldsShape::Record => { self.whitespace(); w!(self, "{{ ... }}"); } FieldsShape::Tuple => { w!(self, "(...)"); } FieldsShape::Unit => {} } } fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { w!(self, "{}", path.display(self.db, self.edition)); if let Some(alias) = alias { w!(self, " as {}", alias.display(self.edition)); } } UseTreeKind::Glob { path } => { if let Some(path) = path { w!(self, "{}::", path.display(self.db, self.edition)); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { w!(self, "{}::", prefix.display(self.db, self.edition)); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { if i != 0 { w!(self, ", "); } self.print_use_tree(tree); } w!(self, "}}"); } } } fn print_mod_item(&mut self, item: ModItemId) { self.print_attrs_of(item, "\n"); match item { ModItemId::Use(ast_id) => { let Use { visibility, use_tree } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "use "); self.print_use_tree(use_tree); wln!(self, ";"); } ModItemId::ExternCrate(ast_id) => { let ExternCrate { name, alias, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "extern crate {}", name.display(self.db, self.edition)); if let Some(alias) = alias { w!(self, " as {}", alias.display(self.edition)); } wln!(self, ";"); } ModItemId::ExternBlock(ast_id) => { let ExternBlock { children } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); w!(self, "extern {{"); self.indented(|this| { for child in &**children { this.print_mod_item(*child); } }); wln!(self, "}}"); } ModItemId::Function(ast_id) => { let Function { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); wln!(self, "fn {};", name.display(self.db, self.edition)); } ModItemId::Struct(ast_id) => { let Struct { visibility, name, shape: kind } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "struct {}", name.display(self.db, self.edition)); self.print_fields(*kind); if matches!(kind, FieldsShape::Record) { wln!(self); } else { wln!(self, ";"); } } ModItemId::Union(ast_id) => { let Union { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "union {}", name.display(self.db, self.edition)); self.print_fields(FieldsShape::Record); wln!(self); } ModItemId::Enum(ast_id) => { let Enum { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "enum {} {{ ... }}", name.display(self.db, self.edition)); } ModItemId::Const(ast_id) => { let Const { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "const "); match name { Some(name) => w!(self, "{}", name.display(self.db, self.edition)), None => w!(self, "_"), } wln!(self, " = _;"); } ModItemId::Static(ast_id) => { let Static { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "static "); w!(self, "{}", name.display(self.db, self.edition)); w!(self, " = _;"); wln!(self); } ModItemId::Trait(ast_id) => { let Trait { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "trait {} {{ ... }}", name.display(self.db, self.edition)); } ModItemId::Impl(ast_id) => { let Impl { is_trait_impl: _ } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); w!(self, "impl {{ ... }}"); } ModItemId::TypeAlias(ast_id) => { let TypeAlias { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "type {}", name.display(self.db, self.edition)); w!(self, ";"); wln!(self); } ModItemId::Mod(ast_id) => { let Mod { name, visibility, kind } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); w!(self, "mod {}", name.display(self.db, self.edition)); match kind { ModKind::Inline { items } => { w!(self, " {{"); self.indented(|this| { for item in &**items { this.print_mod_item(*item); } }); wln!(self, "}}"); } ModKind::Outline => { wln!(self, ";"); } } } ModItemId::MacroCall(ast_id) => { let MacroCall { path, expand_to, ctxt } = &self.tree[ast_id]; let _ = writeln!( self, "// AstId: {:#?}, SyntaxContextId: {}, ExpandTo: {:?}", ast_id.erase(), ctxt, expand_to ); wln!(self, "{}!(...);", path.display(self.db, self.edition)); } ModItemId::MacroRules(ast_id) => { let MacroRules { name } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db, self.edition)); } ModItemId::Macro2(ast_id) => { let Macro2 { name, visibility } = &self.tree[ast_id]; self.print_ast_id(ast_id.erase()); self.print_visibility(*visibility); wln!(self, "macro {} {{ ... }}", name.display(self.db, self.edition)); } } self.blank(); } fn print_ast_id(&mut self, ast_id: ErasedFileAstId) { wln!(self, "// AstId: {ast_id:#?}"); } } 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(()) } }