| //! AST pretty printing. | |
| //! | |
| //! Note that HIR pretty printing is layered on top of this crate. | |
| mod expr; | |
| mod fixup; | |
| mod item; | |
| use std::borrow::Cow; | |
| use std::sync::Arc; | |
| use rustc_ast::attr::AttrIdGenerator; | |
| use rustc_ast::token::{self, CommentKind, Delimiter, DocFragmentKind, Token, TokenKind}; | |
| use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree}; | |
| use rustc_ast::util::classify; | |
| use rustc_ast::util::comments::{Comment, CommentStyle}; | |
| use rustc_ast::{ | |
| self as ast, AttrArgs, BindingMode, BlockCheckMode, ByRef, DelimArgs, GenericArg, GenericBound, | |
| InlineAsmOperand, InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece, PatKind, | |
| RangeEnd, RangeSyntax, Safety, SelfKind, Term, attr, | |
| }; | |
| use rustc_span::edition::Edition; | |
| use rustc_span::source_map::SourceMap; | |
| use rustc_span::symbol::IdentPrinter; | |
| use rustc_span::{ | |
| BytePos, CharPos, DUMMY_SP, FileName, Ident, Pos, Span, Spanned, Symbol, kw, sym, | |
| }; | |
| use crate::pp::Breaks::{Consistent, Inconsistent}; | |
| use crate::pp::{self, BoxMarker, Breaks}; | |
| use crate::pprust::state::fixup::FixupContext; | |
| pub enum MacHeader<'a> { | |
| Path(&'a ast::Path), | |
| Keyword(&'static str), | |
| } | |
| pub enum AnnNode<'a> { | |
| Ident(&'a Ident), | |
| Name(&'a Symbol), | |
| Block(&'a ast::Block), | |
| Item(&'a ast::Item), | |
| SubItem(ast::NodeId), | |
| Expr(&'a ast::Expr), | |
| Pat(&'a ast::Pat), | |
| Crate(&'a ast::Crate), | |
| } | |
| pub trait PpAnn { | |
| fn pre(&self, _state: &mut State<'_>, _node: AnnNode<'_>) {} | |
| fn post(&self, _state: &mut State<'_>, _node: AnnNode<'_>) {} | |
| } | |
| struct NoAnn; | |
| impl PpAnn for NoAnn {} | |
| pub struct Comments<'a> { | |
| sm: &'a SourceMap, | |
| // Stored in reverse order so we can consume them by popping. | |
| reversed_comments: Vec<Comment>, | |
| } | |
| /// Returns `None` if the first `col` chars of `s` contain a non-whitespace char. | |
| /// Otherwise returns `Some(k)` where `k` is first char offset after that leading | |
| /// whitespace. Note that `k` may be outside bounds of `s`. | |
| fn all_whitespace(s: &str, col: CharPos) -> Option<usize> { | |
| let mut idx = 0; | |
| for (i, ch) in s.char_indices().take(col.to_usize()) { | |
| if !ch.is_whitespace() { | |
| return None; | |
| } | |
| idx = i + ch.len_utf8(); | |
| } | |
| Some(idx) | |
| } | |
| fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str { | |
| let len = s.len(); | |
| match all_whitespace(s, col) { | |
| Some(col) => { | |
| if col < len { | |
| &s[col..] | |
| } else { | |
| "" | |
| } | |
| } | |
| None => s, | |
| } | |
| } | |
| fn split_block_comment_into_lines(text: &str, col: CharPos) -> Vec<String> { | |
| let mut res: Vec<String> = vec![]; | |
| let mut lines = text.lines(); | |
| // just push the first line | |
| res.extend(lines.next().map(|it| it.to_string())); | |
| // for other lines, strip common whitespace prefix | |
| for line in lines { | |
| res.push(trim_whitespace_prefix(line, col).to_string()) | |
| } | |
| res | |
| } | |
| fn gather_comments(sm: &SourceMap, path: FileName, src: String) -> Vec<Comment> { | |
| let sm = SourceMap::new(sm.path_mapping().clone()); | |
| let source_file = sm.new_source_file(path, src); | |
| let text = Arc::clone(&(*source_file.src.as_ref().unwrap())); | |
| let text: &str = text.as_str(); | |
| let start_bpos = source_file.start_pos; | |
| let mut pos = 0; | |
| let mut comments: Vec<Comment> = Vec::new(); | |
| let mut code_to_the_left = false; | |
| if let Some(shebang_len) = rustc_lexer::strip_shebang(text) { | |
| comments.push(Comment { | |
| style: CommentStyle::Isolated, | |
| lines: vec![text[..shebang_len].to_string()], | |
| pos: start_bpos, | |
| }); | |
| pos += shebang_len; | |
| } | |
| for token in rustc_lexer::tokenize(&text[pos..], rustc_lexer::FrontmatterAllowed::Yes) { | |
| let token_text = &text[pos..pos + token.len as usize]; | |
| match token.kind { | |
| rustc_lexer::TokenKind::Whitespace => { | |
| if let Some(mut idx) = token_text.find('\n') { | |
| code_to_the_left = false; | |
| while let Some(next_newline) = &token_text[idx + 1..].find('\n') { | |
| idx += 1 + next_newline; | |
| comments.push(Comment { | |
| style: CommentStyle::BlankLine, | |
| lines: vec![], | |
| pos: start_bpos + BytePos((pos + idx) as u32), | |
| }); | |
| } | |
| } | |
| } | |
| rustc_lexer::TokenKind::BlockComment { doc_style, .. } => { | |
| if doc_style.is_none() { | |
| let code_to_the_right = !matches!( | |
| text[pos + token.len as usize..].chars().next(), | |
| Some('\r' | '\n') | |
| ); | |
| let style = match (code_to_the_left, code_to_the_right) { | |
| (_, true) => CommentStyle::Mixed, | |
| (false, false) => CommentStyle::Isolated, | |
| (true, false) => CommentStyle::Trailing, | |
| }; | |
| // Count the number of chars since the start of the line by rescanning. | |
| let pos_in_file = start_bpos + BytePos(pos as u32); | |
| let line_begin_in_file = source_file.line_begin_pos(pos_in_file); | |
| let line_begin_pos = (line_begin_in_file - start_bpos).to_usize(); | |
| let col = CharPos(text[line_begin_pos..pos].chars().count()); | |
| let lines = split_block_comment_into_lines(token_text, col); | |
| comments.push(Comment { style, lines, pos: pos_in_file }) | |
| } | |
| } | |
| rustc_lexer::TokenKind::LineComment { doc_style } => { | |
| if doc_style.is_none() { | |
| comments.push(Comment { | |
| style: if code_to_the_left { | |
| CommentStyle::Trailing | |
| } else { | |
| CommentStyle::Isolated | |
| }, | |
| lines: vec![token_text.to_string()], | |
| pos: start_bpos + BytePos(pos as u32), | |
| }) | |
| } | |
| } | |
| rustc_lexer::TokenKind::Frontmatter { .. } => { | |
| code_to_the_left = false; | |
| comments.push(Comment { | |
| style: CommentStyle::Isolated, | |
| lines: vec![token_text.to_string()], | |
| pos: start_bpos + BytePos(pos as u32), | |
| }); | |
| } | |
| _ => { | |
| code_to_the_left = true; | |
| } | |
| } | |
| pos += token.len as usize; | |
| } | |
| comments | |
| } | |
| impl<'a> Comments<'a> { | |
| pub fn new(sm: &'a SourceMap, filename: FileName, input: String) -> Comments<'a> { | |
| let mut comments = gather_comments(sm, filename, input); | |
| comments.reverse(); | |
| Comments { sm, reversed_comments: comments } | |
| } | |
| fn peek(&self) -> Option<&Comment> { | |
| self.reversed_comments.last() | |
| } | |
| fn next(&mut self) -> Option<Comment> { | |
| self.reversed_comments.pop() | |
| } | |
| fn trailing_comment( | |
| &mut self, | |
| span: rustc_span::Span, | |
| next_pos: Option<BytePos>, | |
| ) -> Option<Comment> { | |
| if let Some(cmnt) = self.peek() { | |
| if cmnt.style != CommentStyle::Trailing { | |
| return None; | |
| } | |
| let span_line = self.sm.lookup_char_pos(span.hi()); | |
| let comment_line = self.sm.lookup_char_pos(cmnt.pos); | |
| let next = next_pos.unwrap_or_else(|| cmnt.pos + BytePos(1)); | |
| if span.hi() < cmnt.pos && cmnt.pos < next && span_line.line == comment_line.line { | |
| return Some(self.next().unwrap()); | |
| } | |
| } | |
| None | |
| } | |
| } | |
| pub struct State<'a> { | |
| pub s: pp::Printer, | |
| comments: Option<Comments<'a>>, | |
| ann: &'a (dyn PpAnn + 'a), | |
| is_sdylib_interface: bool, | |
| } | |
| const INDENT_UNIT: isize = 4; | |
| /// Requires you to pass an input filename and reader so that | |
| /// it can scan the input text for comments to copy forward. | |
| pub fn print_crate<'a>( | |
| sm: &'a SourceMap, | |
| krate: &ast::Crate, | |
| filename: FileName, | |
| input: String, | |
| ann: &'a dyn PpAnn, | |
| is_expanded: bool, | |
| edition: Edition, | |
| g: &AttrIdGenerator, | |
| ) -> String { | |
| let mut s = State { | |
| s: pp::Printer::new(), | |
| comments: Some(Comments::new(sm, filename, input)), | |
| ann, | |
| is_sdylib_interface: false, | |
| }; | |
| print_crate_inner(&mut s, krate, is_expanded, edition, g); | |
| s.s.eof() | |
| } | |
| pub fn print_crate_as_interface( | |
| krate: &ast::Crate, | |
| edition: Edition, | |
| g: &AttrIdGenerator, | |
| ) -> String { | |
| let mut s = | |
| State { s: pp::Printer::new(), comments: None, ann: &NoAnn, is_sdylib_interface: true }; | |
| print_crate_inner(&mut s, krate, false, edition, g); | |
| s.s.eof() | |
| } | |
| fn print_crate_inner<'a>( | |
| s: &mut State<'a>, | |
| krate: &ast::Crate, | |
| is_expanded: bool, | |
| edition: Edition, | |
| g: &AttrIdGenerator, | |
| ) { | |
| // We need to print shebang before anything else | |
| // otherwise the resulting code will not compile | |
| // and shebang will be useless. | |
| s.maybe_print_shebang(); | |
| if is_expanded && !krate.attrs.iter().any(|attr| attr.has_name(sym::no_core)) { | |
| // We need to print `#![no_std]` (and its feature gate) so that | |
| // compiling pretty-printed source won't inject libstd again. | |
| // However, we don't want these attributes in the AST because | |
| // of the feature gate, so we fake them up here. | |
| // `#![feature(prelude_import)]` | |
| let fake_attr = attr::mk_attr_nested_word( | |
| g, | |
| ast::AttrStyle::Inner, | |
| Safety::Default, | |
| sym::feature, | |
| sym::prelude_import, | |
| DUMMY_SP, | |
| ); | |
| s.print_attribute(&fake_attr); | |
| // Currently, in Rust 2018 we don't have `extern crate std;` at the crate | |
| // root, so this is not needed, and actually breaks things. | |
| if edition.is_rust_2015() { | |
| // `#![no_std]` | |
| let fake_attr = attr::mk_attr_word( | |
| g, | |
| ast::AttrStyle::Inner, | |
| Safety::Default, | |
| sym::no_std, | |
| DUMMY_SP, | |
| ); | |
| s.print_attribute(&fake_attr); | |
| } | |
| } | |
| s.print_inner_attributes(&krate.attrs); | |
| for item in &krate.items { | |
| s.print_item(item); | |
| } | |
| s.print_remaining_comments(); | |
| s.ann.post(s, AnnNode::Crate(krate)); | |
| } | |
| /// Should two consecutive tokens be printed with a space between them? | |
| /// | |
| /// Note: some old proc macros parse pretty-printed output, so changes here can | |
| /// break old code. For example: | |
| /// - #63896: `#[allow(unused,` must be printed rather than `#[allow(unused ,` | |
| /// - #73345: `#[allow(unused)]` must be printed rather than `# [allow(unused)]` | |
| /// | |
| /// Returns `true` if both token trees are identifier-like tokens that would | |
| /// merge into a single token if printed without a space between them. | |
| /// E.g. `ident` + `where` would merge into `identwhere`. | |
| fn idents_would_merge(tt1: &TokenTree, tt2: &TokenTree) -> bool { | |
| fn is_ident_like(tt: &TokenTree) -> bool { | |
| matches!( | |
| tt, | |
| TokenTree::Token(Token { kind: token::Ident(..) | token::NtIdent(..), .. }, _,) | |
| ) | |
| } | |
| is_ident_like(tt1) && is_ident_like(tt2) | |
| } | |
| fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { | |
| use Delimiter::*; | |
| use TokenTree::{Delimited as Del, Token as Tok}; | |
| use token::*; | |
| fn is_punct(tt: &TokenTree) -> bool { | |
| matches!(tt, TokenTree::Token(tok, _) if tok.is_punct()) | |
| } | |
| // Each match arm has one or more examples in comments. The default is to | |
| // insert space between adjacent tokens, except for the cases listed in | |
| // this match. | |
| match (tt1, tt2) { | |
| // No space after line doc comments. | |
| (Tok(Token { kind: DocComment(CommentKind::Line, ..), .. }, _), _) => false, | |
| // `.` + NON-PUNCT: `x.y`, `tup.0` | |
| (Tok(Token { kind: Dot, .. }, _), tt2) if !is_punct(tt2) => false, | |
| // `$` + IDENT: `$e` | |
| (Tok(Token { kind: Dollar, .. }, _), Tok(Token { kind: Ident(..), .. }, _)) => false, | |
| // NON-PUNCT + `,`: `foo,` | |
| // NON-PUNCT + `;`: `x = 3;`, `[T; 3]` | |
| // NON-PUNCT + `.`: `x.y`, `tup.0` | |
| (tt1, Tok(Token { kind: Comma | Semi | Dot, .. }, _)) if !is_punct(tt1) => false, | |
| // IDENT + `!`: `println!()`, but `if !x { ... }` needs a space after the `if` | |
| (Tok(Token { kind: Ident(sym, is_raw), span }, _), Tok(Token { kind: Bang, .. }, _)) | |
| if !Ident::new(*sym, *span).is_reserved() || matches!(is_raw, IdentIsRaw::Yes) => | |
| { | |
| false | |
| } | |
| // IDENT|`fn`|`Self`|`pub` + `(`: `f(3)`, `fn(x: u8)`, `Self()`, `pub(crate)`, | |
| // but `let (a, b) = (1, 2)` needs a space after the `let` | |
| (Tok(Token { kind: Ident(sym, is_raw), span }, _), Del(_, _, Parenthesis, _)) | |
| if !Ident::new(*sym, *span).is_reserved() | |
| || *sym == kw::Fn | |
| || *sym == kw::SelfUpper | |
| || *sym == kw::Pub | |
| || matches!(is_raw, IdentIsRaw::Yes) => | |
| { | |
| false | |
| } | |
| // `#` + `[`: `#[attr]` | |
| (Tok(Token { kind: Pound, .. }, _), Del(_, _, Bracket, _)) => false, | |
| _ => true, | |
| } | |
| } | |
| pub fn doc_comment_to_string( | |
| fragment_kind: DocFragmentKind, | |
| attr_style: ast::AttrStyle, | |
| data: Symbol, | |
| ) -> String { | |
| match fragment_kind { | |
| DocFragmentKind::Sugared(comment_kind) => match (comment_kind, attr_style) { | |
| (CommentKind::Line, ast::AttrStyle::Outer) => format!("///{data}"), | |
| (CommentKind::Line, ast::AttrStyle::Inner) => format!("//!{data}"), | |
| (CommentKind::Block, ast::AttrStyle::Outer) => format!("/**{data}*/"), | |
| (CommentKind::Block, ast::AttrStyle::Inner) => format!("/*!{data}*/"), | |
| }, | |
| DocFragmentKind::Raw(_) => { | |
| format!( | |
| "#{}[doc = {:?}]", | |
| if attr_style == ast::AttrStyle::Inner { "!" } else { "" }, | |
| data.to_string(), | |
| ) | |
| } | |
| } | |
| } | |
| fn literal_to_string(lit: token::Lit) -> String { | |
| let token::Lit { kind, symbol, suffix } = lit; | |
| let mut out = match kind { | |
| token::Byte => format!("b'{symbol}'"), | |
| token::Char => format!("'{symbol}'"), | |
| token::Str => format!("\"{symbol}\""), | |
| token::StrRaw(n) => { | |
| format!("r{delim}\"{string}\"{delim}", delim = "#".repeat(n as usize), string = symbol) | |
| } | |
| token::ByteStr => format!("b\"{symbol}\""), | |
| token::ByteStrRaw(n) => { | |
| format!("br{delim}\"{string}\"{delim}", delim = "#".repeat(n as usize), string = symbol) | |
| } | |
| token::CStr => format!("c\"{symbol}\""), | |
| token::CStrRaw(n) => { | |
| format!("cr{delim}\"{symbol}\"{delim}", delim = "#".repeat(n as usize)) | |
| } | |
| token::Integer | token::Float | token::Bool | token::Err(_) => symbol.to_string(), | |
| }; | |
| if let Some(suffix) = suffix { | |
| out.push_str(suffix.as_str()) | |
| } | |
| out | |
| } | |
| impl std::ops::Deref for State<'_> { | |
| type Target = pp::Printer; | |
| fn deref(&self) -> &Self::Target { | |
| &self.s | |
| } | |
| } | |
| impl std::ops::DerefMut for State<'_> { | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| &mut self.s | |
| } | |
| } | |
| /// This trait is used for both AST and HIR pretty-printing. | |
| pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::DerefMut { | |
| fn comments(&self) -> Option<&Comments<'a>>; | |
| fn comments_mut(&mut self) -> Option<&mut Comments<'a>>; | |
| fn ann_post(&mut self, ident: Ident); | |
| fn print_generic_args(&mut self, args: &ast::GenericArgs, colons_before_params: bool); | |
| fn print_ident(&mut self, ident: Ident) { | |
| self.word(IdentPrinter::for_ast_ident(ident, ident.guess_print_mode()).to_string()); | |
| self.ann_post(ident) | |
| } | |
| fn strsep<'x, T: 'x, F, I>( | |
| &mut self, | |
| sep: &'static str, | |
| space_before: bool, | |
| b: Breaks, | |
| elts: I, | |
| mut op: F, | |
| ) where | |
| F: FnMut(&mut Self, &T), | |
| I: IntoIterator<Item = &'x T>, | |
| { | |
| let mut it = elts.into_iter(); | |
| let rb = self.rbox(0, b); | |
| if let Some(first) = it.next() { | |
| op(self, first); | |
| for elt in it { | |
| if space_before { | |
| self.space(); | |
| } | |
| self.word_space(sep); | |
| op(self, elt); | |
| } | |
| } | |
| self.end(rb); | |
| } | |
| fn commasep<'x, T: 'x, F, I>(&mut self, b: Breaks, elts: I, op: F) | |
| where | |
| F: FnMut(&mut Self, &T), | |
| I: IntoIterator<Item = &'x T>, | |
| { | |
| self.strsep(",", false, b, elts, op) | |
| } | |
| fn maybe_print_comment(&mut self, pos: BytePos) -> bool { | |
| let mut has_comment = false; | |
| while let Some(cmnt) = self.peek_comment() { | |
| if cmnt.pos >= pos { | |
| break; | |
| } | |
| has_comment = true; | |
| let cmnt = self.next_comment().unwrap(); | |
| self.print_comment(cmnt); | |
| } | |
| has_comment | |
| } | |
| fn print_comment(&mut self, cmnt: Comment) { | |
| match cmnt.style { | |
| CommentStyle::Mixed => { | |
| if !self.is_beginning_of_line() { | |
| self.zerobreak(); | |
| } | |
| if let Some((last, lines)) = cmnt.lines.split_last() { | |
| let ib = self.ibox(0); | |
| for line in lines { | |
| self.word(line.clone()); | |
| self.hardbreak() | |
| } | |
| self.word(last.clone()); | |
| self.space(); | |
| self.end(ib); | |
| } | |
| self.zerobreak() | |
| } | |
| CommentStyle::Isolated => { | |
| self.hardbreak_if_not_bol(); | |
| for line in &cmnt.lines { | |
| // Don't print empty lines because they will end up as trailing | |
| // whitespace. | |
| if !line.is_empty() { | |
| self.word(line.clone()); | |
| } | |
| self.hardbreak(); | |
| } | |
| } | |
| CommentStyle::Trailing => { | |
| if !self.is_beginning_of_line() { | |
| self.word(" "); | |
| } | |
| if let [line] = cmnt.lines.as_slice() { | |
| self.word(line.clone()); | |
| self.hardbreak() | |
| } else { | |
| let vb = self.visual_align(); | |
| for line in &cmnt.lines { | |
| if !line.is_empty() { | |
| self.word(line.clone()); | |
| } | |
| self.hardbreak(); | |
| } | |
| self.end(vb); | |
| } | |
| } | |
| CommentStyle::BlankLine => { | |
| // We need to do at least one, possibly two hardbreaks. | |
| let twice = match self.last_token() { | |
| Some(pp::Token::String(s)) => ";" == s, | |
| Some(pp::Token::Begin(_)) => true, | |
| Some(pp::Token::End) => true, | |
| _ => false, | |
| }; | |
| if twice { | |
| self.hardbreak(); | |
| } | |
| self.hardbreak(); | |
| } | |
| } | |
| } | |
| fn peek_comment<'b>(&'b self) -> Option<&'b Comment> | |
| where | |
| 'a: 'b, | |
| { | |
| self.comments().and_then(|c| c.peek()) | |
| } | |
| fn next_comment(&mut self) -> Option<Comment> { | |
| self.comments_mut().and_then(|c| c.next()) | |
| } | |
| fn maybe_print_trailing_comment(&mut self, span: rustc_span::Span, next_pos: Option<BytePos>) { | |
| if let Some(cmnts) = self.comments_mut() | |
| && let Some(cmnt) = cmnts.trailing_comment(span, next_pos) | |
| { | |
| self.print_comment(cmnt); | |
| } | |
| } | |
| fn print_remaining_comments(&mut self) { | |
| // If there aren't any remaining comments, then we need to manually | |
| // make sure there is a line break at the end. | |
| if self.peek_comment().is_none() { | |
| self.hardbreak(); | |
| } | |
| while let Some(cmnt) = self.next_comment() { | |
| self.print_comment(cmnt) | |
| } | |
| } | |
| fn print_string(&mut self, st: &str, style: ast::StrStyle) { | |
| let st = match style { | |
| ast::StrStyle::Cooked => format!("\"{}\"", st.escape_debug()), | |
| ast::StrStyle::Raw(n) => { | |
| format!("r{delim}\"{string}\"{delim}", delim = "#".repeat(n as usize), string = st) | |
| } | |
| }; | |
| self.word(st) | |
| } | |
| fn maybe_print_shebang(&mut self) { | |
| if let Some(cmnt) = self.peek_comment() { | |
| // Comment is a shebang if it's: | |
| // Isolated, starts with #! and doesn't continue with `[` | |
| // See [rustc_lexer::strip_shebang] and [gather_comments] from pprust/state.rs for details | |
| if cmnt.style == CommentStyle::Isolated | |
| && cmnt.lines.first().map_or(false, |l| l.starts_with("#!")) | |
| { | |
| let cmnt = self.next_comment().unwrap(); | |
| self.print_comment(cmnt); | |
| } | |
| } | |
| } | |
| fn print_inner_attributes(&mut self, attrs: &[ast::Attribute]) -> bool { | |
| self.print_either_attributes(attrs, ast::AttrStyle::Inner, false, true) | |
| } | |
| fn print_outer_attributes(&mut self, attrs: &[ast::Attribute]) -> bool { | |
| self.print_either_attributes(attrs, ast::AttrStyle::Outer, false, true) | |
| } | |
| fn print_either_attributes( | |
| &mut self, | |
| attrs: &[ast::Attribute], | |
| kind: ast::AttrStyle, | |
| is_inline: bool, | |
| trailing_hardbreak: bool, | |
| ) -> bool { | |
| let mut printed = false; | |
| for attr in attrs { | |
| if attr.style == kind { | |
| if self.print_attribute_inline(attr, is_inline) { | |
| if is_inline { | |
| self.nbsp(); | |
| } | |
| printed = true; | |
| } | |
| } | |
| } | |
| if printed && trailing_hardbreak && !is_inline { | |
| self.hardbreak_if_not_bol(); | |
| } | |
| printed | |
| } | |
| fn print_attribute_inline(&mut self, attr: &ast::Attribute, is_inline: bool) -> bool { | |
| if attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace) { | |
| // It's not a valid identifier, so avoid printing it | |
| // to keep the printed code reasonably parse-able. | |
| return false; | |
| } | |
| if !is_inline { | |
| self.hardbreak_if_not_bol(); | |
| } | |
| self.maybe_print_comment(attr.span.lo()); | |
| match &attr.kind { | |
| ast::AttrKind::Normal(normal) => { | |
| match attr.style { | |
| ast::AttrStyle::Inner => self.word("#!["), | |
| ast::AttrStyle::Outer => self.word("#["), | |
| } | |
| self.print_attr_item(&normal.item, attr.span); | |
| self.word("]"); | |
| } | |
| ast::AttrKind::DocComment(comment_kind, data) => { | |
| self.word(doc_comment_to_string( | |
| DocFragmentKind::Sugared(*comment_kind), | |
| attr.style, | |
| *data, | |
| )); | |
| self.hardbreak() | |
| } | |
| } | |
| true | |
| } | |
| fn print_attr_item(&mut self, item: &ast::AttrItem, span: Span) { | |
| let ib = self.ibox(0); | |
| match item.unsafety { | |
| ast::Safety::Unsafe(_) => { | |
| self.word("unsafe"); | |
| self.popen(); | |
| } | |
| ast::Safety::Default | ast::Safety::Safe(_) => {} | |
| } | |
| match &item.args.unparsed_ref().expect("Parsed attributes are never printed") { | |
| AttrArgs::Delimited(DelimArgs { dspan: _, delim, tokens }) => self.print_mac_common( | |
| Some(MacHeader::Path(&item.path)), | |
| false, | |
| None, | |
| *delim, | |
| None, | |
| tokens, | |
| true, | |
| span, | |
| ), | |
| AttrArgs::Empty => { | |
| self.print_path(&item.path, false, 0); | |
| } | |
| AttrArgs::Eq { expr, .. } => { | |
| self.print_path(&item.path, false, 0); | |
| self.space(); | |
| self.word_space("="); | |
| let token_str = self.expr_to_string(expr); | |
| self.word(token_str); | |
| } | |
| } | |
| match item.unsafety { | |
| ast::Safety::Unsafe(_) => self.pclose(), | |
| ast::Safety::Default | ast::Safety::Safe(_) => {} | |
| } | |
| self.end(ib); | |
| } | |
| /// This doesn't deserve to be called "pretty" printing, but it should be | |
| /// meaning-preserving. A quick hack that might help would be to look at the | |
| /// spans embedded in the TTs to decide where to put spaces and newlines. | |
| /// But it'd be better to parse these according to the grammar of the | |
| /// appropriate macro, transcribe back into the grammar we just parsed from, | |
| /// and then pretty-print the resulting AST nodes (so, e.g., we print | |
| /// expression arguments as expressions). It can be done! I think. | |
| fn print_tt(&mut self, tt: &TokenTree, convert_dollar_crate: bool) -> Spacing { | |
| match tt { | |
| TokenTree::Token(token, spacing) => { | |
| let token_str = self.token_to_string_ext(token, convert_dollar_crate); | |
| self.word(token_str); | |
| // Emit hygiene annotations for identity-bearing tokens, | |
| // matching how print_ident() and print_lifetime() call ann_post(). | |
| match token.kind { | |
| token::Ident(name, _) => { | |
| self.ann_post(Ident::new(name, token.span)); | |
| } | |
| token::NtIdent(ident, _) => { | |
| self.ann_post(ident); | |
| } | |
| token::Lifetime(name, _) => { | |
| self.ann_post(Ident::new(name, token.span)); | |
| } | |
| token::NtLifetime(ident, _) => { | |
| self.ann_post(ident); | |
| } | |
| _ => {} | |
| } | |
| if let token::DocComment(..) = token.kind { | |
| self.hardbreak() | |
| } | |
| *spacing | |
| } | |
| TokenTree::Delimited(dspan, spacing, delim, tts) => { | |
| self.print_mac_common( | |
| None, | |
| false, | |
| None, | |
| *delim, | |
| Some(spacing.open), | |
| tts, | |
| convert_dollar_crate, | |
| dspan.entire(), | |
| ); | |
| spacing.close | |
| } | |
| } | |
| } | |
| // The easiest way to implement token stream pretty printing would be to | |
| // print each token followed by a single space. But that would produce ugly | |
| // output, so we go to some effort to do better. | |
| // | |
| // First, we track whether each token that appears in source code is | |
| // followed by a space, with `Spacing`, and reproduce that in the output. | |
| // This works well in a lot of cases. E.g. `stringify!(x + y)` produces | |
| // "x + y" and `stringify!(x+y)` produces "x+y". | |
| // | |
| // But this doesn't work for code produced by proc macros (which have no | |
| // original source text representation) nor for code produced by decl | |
| // macros (which are tricky because the whitespace after tokens appearing | |
| // in macro rules isn't always what you want in the produced output). For | |
| // these we mostly use `Spacing::Alone`, which is the conservative choice. | |
| // | |
| // So we have a backup mechanism for when `Spacing::Alone` occurs between a | |
| // pair of tokens: we check if that pair of tokens can obviously go | |
| // together without a space between them. E.g. token `x` followed by token | |
| // `,` is better printed as `x,` than `x ,`. (Even if the original source | |
| // code was `x ,`.) | |
| // | |
| // Finally, we must be careful about changing the output. Token pretty | |
| // printing is used by `stringify!` and `impl Display for | |
| // proc_macro::TokenStream`, and some programs rely on the output having a | |
| // particular form, even though they shouldn't. In particular, some proc | |
| // macros do `format!({stream})` on a token stream and then "parse" the | |
| // output with simple string matching that can't handle whitespace changes. | |
| // E.g. we have seen cases where a proc macro can handle `a :: b` but not | |
| // `a::b`. See #117433 for some examples. | |
| fn print_tts(&mut self, tts: &TokenStream, convert_dollar_crate: bool) { | |
| let mut iter = tts.iter().peekable(); | |
| while let Some(tt) = iter.next() { | |
| let spacing = self.print_tt(tt, convert_dollar_crate); | |
| if let Some(next) = iter.peek() { | |
| if spacing == Spacing::Alone && space_between(tt, next) { | |
| self.space(); | |
| } else if spacing != Spacing::Alone && idents_would_merge(tt, next) { | |
| // When tokens from macro `tt` captures preserve their | |
| // original `Joint`/`JointHidden` spacing, adjacent | |
| // identifier-like tokens can be concatenated without a | |
| // space (e.g. `$x:identwhere`). Insert a space to | |
| // prevent this. | |
| self.space(); | |
| } | |
| } | |
| } | |
| } | |
| fn print_mac_common( | |
| &mut self, | |
| header: Option<MacHeader<'_>>, | |
| has_bang: bool, | |
| ident: Option<Ident>, | |
| delim: Delimiter, | |
| open_spacing: Option<Spacing>, | |
| tts: &TokenStream, | |
| convert_dollar_crate: bool, | |
| span: Span, | |
| ) { | |
| let cb = (delim == Delimiter::Brace).then(|| self.cbox(INDENT_UNIT)); | |
| match header { | |
| Some(MacHeader::Path(path)) => self.print_path(path, false, 0), | |
| Some(MacHeader::Keyword(kw)) => self.word(kw), | |
| None => {} | |
| } | |
| if has_bang { | |
| self.word("!"); | |
| } | |
| if let Some(ident) = ident { | |
| self.nbsp(); | |
| self.print_ident(ident); | |
| } | |
| match delim { | |
| Delimiter::Brace => { | |
| if header.is_some() || has_bang || ident.is_some() { | |
| self.nbsp(); | |
| } | |
| self.word("{"); | |
| // Respect `Alone`, if provided, and print a space. Unless the list is empty. | |
| let open_space = (open_spacing == None || open_spacing == Some(Spacing::Alone)) | |
| && !tts.is_empty(); | |
| if open_space { | |
| self.space(); | |
| } | |
| let ib = self.ibox(0); | |
| self.print_tts(tts, convert_dollar_crate); | |
| self.end(ib); | |
| // Use `open_space` for the spacing *before* the closing delim. | |
| // Because spacing on delimiters is lost when going through | |
| // proc macros, and otherwise we can end up with ugly cases | |
| // like `{ x}`. Symmetry is better. | |
| self.bclose(span, !open_space, cb.unwrap()); | |
| } | |
| delim => { | |
| // `open_spacing` is ignored. We never print spaces after | |
| // non-brace opening delims or before non-brace closing delims. | |
| let token_str = self.token_kind_to_string(&delim.as_open_token_kind()); | |
| self.word(token_str); | |
| let ib = self.ibox(0); | |
| self.print_tts(tts, convert_dollar_crate); | |
| self.end(ib); | |
| let token_str = self.token_kind_to_string(&delim.as_close_token_kind()); | |
| self.word(token_str); | |
| } | |
| } | |
| } | |
| fn print_mac_def( | |
| &mut self, | |
| macro_def: &ast::MacroDef, | |
| ident: &Ident, | |
| sp: Span, | |
| print_visibility: impl FnOnce(&mut Self), | |
| ) { | |
| if let Some(eii_decl) = ¯o_def.eii_declaration { | |
| self.word("#[eii_declaration("); | |
| self.print_path(&eii_decl.foreign_item, false, 0); | |
| if eii_decl.impl_unsafe { | |
| self.word(","); | |
| self.space(); | |
| self.word("unsafe"); | |
| } | |
| self.word(")]"); | |
| self.hardbreak(); | |
| } | |
| let (kw, has_bang) = if macro_def.macro_rules { | |
| ("macro_rules", true) | |
| } else { | |
| print_visibility(self); | |
| ("macro", false) | |
| }; | |
| self.print_mac_common( | |
| Some(MacHeader::Keyword(kw)), | |
| has_bang, | |
| Some(*ident), | |
| macro_def.body.delim, | |
| None, | |
| ¯o_def.body.tokens, | |
| true, | |
| sp, | |
| ); | |
| if macro_def.body.need_semicolon() { | |
| self.word(";"); | |
| } | |
| } | |
| fn print_path(&mut self, path: &ast::Path, colons_before_params: bool, depth: usize) { | |
| self.maybe_print_comment(path.span.lo()); | |
| for (i, segment) in path.segments[..path.segments.len() - depth].iter().enumerate() { | |
| if i > 0 { | |
| self.word("::") | |
| } | |
| self.print_path_segment(segment, colons_before_params); | |
| } | |
| } | |
| fn print_path_segment(&mut self, segment: &ast::PathSegment, colons_before_params: bool) { | |
| if segment.ident.name != kw::PathRoot { | |
| self.print_ident(segment.ident); | |
| if let Some(args) = &segment.args { | |
| self.print_generic_args(args, colons_before_params); | |
| } | |
| } | |
| } | |
| fn head<S: Into<Cow<'static, str>>>(&mut self, w: S) -> (BoxMarker, BoxMarker) { | |
| let w = w.into(); | |
| // Outer-box is consistent. | |
| let cb = self.cbox(INDENT_UNIT); | |
| // Head-box is inconsistent. | |
| let ib = self.ibox(0); | |
| // Keyword that starts the head. | |
| if !w.is_empty() { | |
| self.word_nbsp(w); | |
| } | |
| (cb, ib) | |
| } | |
| fn bopen(&mut self, ib: BoxMarker) { | |
| self.word("{"); | |
| self.end(ib); | |
| } | |
| fn bclose_maybe_open(&mut self, span: rustc_span::Span, no_space: bool, cb: Option<BoxMarker>) { | |
| let has_comment = self.maybe_print_comment(span.hi()); | |
| if !no_space || has_comment { | |
| self.break_offset_if_not_bol(1, -INDENT_UNIT); | |
| } | |
| self.word("}"); | |
| if let Some(cb) = cb { | |
| self.end(cb); | |
| } | |
| } | |
| fn bclose(&mut self, span: rustc_span::Span, no_space: bool, cb: BoxMarker) { | |
| let cb = Some(cb); | |
| self.bclose_maybe_open(span, no_space, cb) | |
| } | |
| fn break_offset_if_not_bol(&mut self, n: usize, off: isize) { | |
| if !self.is_beginning_of_line() { | |
| self.break_offset(n, off) | |
| } else if off != 0 { | |
| if let Some(last_token) = self.last_token_still_buffered() { | |
| if last_token.is_hardbreak_tok() { | |
| // We do something pretty sketchy here: tuck the nonzero | |
| // offset-adjustment we were going to deposit along with the | |
| // break into the previous hardbreak. | |
| self.replace_last_token_still_buffered(pp::Printer::hardbreak_tok_offset(off)); | |
| } | |
| } | |
| } | |
| } | |
| /// Print the token kind precisely, without converting `$crate` into its respective crate name. | |
| fn token_kind_to_string(&self, tok: &TokenKind) -> Cow<'static, str> { | |
| self.token_kind_to_string_ext(tok, None) | |
| } | |
| fn token_kind_to_string_ext( | |
| &self, | |
| tok: &TokenKind, | |
| convert_dollar_crate: Option<Span>, | |
| ) -> Cow<'static, str> { | |
| match *tok { | |
| token::Eq => "=".into(), | |
| token::Lt => "<".into(), | |
| token::Le => "<=".into(), | |
| token::EqEq => "==".into(), | |
| token::Ne => "!=".into(), | |
| token::Ge => ">=".into(), | |
| token::Gt => ">".into(), | |
| token::Bang => "!".into(), | |
| token::Tilde => "~".into(), | |
| token::OrOr => "||".into(), | |
| token::AndAnd => "&&".into(), | |
| token::Plus => "+".into(), | |
| token::Minus => "-".into(), | |
| token::Star => "*".into(), | |
| token::Slash => "/".into(), | |
| token::Percent => "%".into(), | |
| token::Caret => "^".into(), | |
| token::And => "&".into(), | |
| token::Or => "|".into(), | |
| token::Shl => "<<".into(), | |
| token::Shr => ">>".into(), | |
| token::PlusEq => "+=".into(), | |
| token::MinusEq => "-=".into(), | |
| token::StarEq => "*=".into(), | |
| token::SlashEq => "/=".into(), | |
| token::PercentEq => "%=".into(), | |
| token::CaretEq => "^=".into(), | |
| token::AndEq => "&=".into(), | |
| token::OrEq => "|=".into(), | |
| token::ShlEq => "<<=".into(), | |
| token::ShrEq => ">>=".into(), | |
| /* Structural symbols */ | |
| token::At => "@".into(), | |
| token::Dot => ".".into(), | |
| token::DotDot => "..".into(), | |
| token::DotDotDot => "...".into(), | |
| token::DotDotEq => "..=".into(), | |
| token::Comma => ",".into(), | |
| token::Semi => ";".into(), | |
| token::Colon => ":".into(), | |
| token::PathSep => "::".into(), | |
| token::RArrow => "->".into(), | |
| token::LArrow => "<-".into(), | |
| token::FatArrow => "=>".into(), | |
| token::OpenParen => "(".into(), | |
| token::CloseParen => ")".into(), | |
| token::OpenBracket => "[".into(), | |
| token::CloseBracket => "]".into(), | |
| token::OpenBrace => "{".into(), | |
| token::CloseBrace => "}".into(), | |
| token::OpenInvisible(_) | token::CloseInvisible(_) => "".into(), | |
| token::Pound => "#".into(), | |
| token::Dollar => "$".into(), | |
| token::Question => "?".into(), | |
| token::SingleQuote => "'".into(), | |
| /* Literals */ | |
| token::Literal(lit) => literal_to_string(lit).into(), | |
| /* Name components */ | |
| token::Ident(name, is_raw) => { | |
| IdentPrinter::new(name, is_raw.to_print_mode_ident(), convert_dollar_crate) | |
| .to_string() | |
| .into() | |
| } | |
| token::NtIdent(ident, is_raw) => { | |
| IdentPrinter::for_ast_ident(ident, is_raw.to_print_mode_ident()).to_string().into() | |
| } | |
| token::Lifetime(name, is_raw) | token::NtLifetime(Ident { name, .. }, is_raw) => { | |
| IdentPrinter::new(name, is_raw.to_print_mode_lifetime(), None).to_string().into() | |
| } | |
| /* Other */ | |
| token::DocComment(comment_kind, attr_style, data) => { | |
| doc_comment_to_string(DocFragmentKind::Sugared(comment_kind), attr_style, data) | |
| .into() | |
| } | |
| token::Eof => "<eof>".into(), | |
| } | |
| } | |
| /// Print the token precisely, without converting `$crate` into its respective crate name. | |
| fn token_to_string(&self, token: &Token) -> Cow<'static, str> { | |
| self.token_to_string_ext(token, false) | |
| } | |
| fn token_to_string_ext(&self, token: &Token, convert_dollar_crate: bool) -> Cow<'static, str> { | |
| let convert_dollar_crate = convert_dollar_crate.then_some(token.span); | |
| self.token_kind_to_string_ext(&token.kind, convert_dollar_crate) | |
| } | |
| fn ty_to_string(&self, ty: &ast::Ty) -> String { | |
| Self::to_string(|s| s.print_type(ty)) | |
| } | |
| fn pat_to_string(&self, pat: &ast::Pat) -> String { | |
| Self::to_string(|s| s.print_pat(pat)) | |
| } | |
| fn expr_to_string(&self, e: &ast::Expr) -> String { | |
| Self::to_string(|s| s.print_expr(e, FixupContext::default())) | |
| } | |
| fn meta_item_lit_to_string(&self, lit: &ast::MetaItemLit) -> String { | |
| Self::to_string(|s| s.print_meta_item_lit(lit)) | |
| } | |
| fn stmt_to_string(&self, stmt: &ast::Stmt) -> String { | |
| Self::to_string(|s| s.print_stmt(stmt)) | |
| } | |
| fn item_to_string(&self, i: &ast::Item) -> String { | |
| Self::to_string(|s| s.print_item(i)) | |
| } | |
| fn assoc_item_to_string(&self, i: &ast::AssocItem) -> String { | |
| Self::to_string(|s| s.print_assoc_item(i)) | |
| } | |
| fn foreign_item_to_string(&self, i: &ast::ForeignItem) -> String { | |
| Self::to_string(|s| s.print_foreign_item(i)) | |
| } | |
| fn path_to_string(&self, p: &ast::Path) -> String { | |
| Self::to_string(|s| s.print_path(p, false, 0)) | |
| } | |
| fn vis_to_string(&self, v: &ast::Visibility) -> String { | |
| Self::to_string(|s| s.print_visibility(v)) | |
| } | |
| fn impl_restriction_to_string(&self, r: &ast::ImplRestriction) -> String { | |
| Self::to_string(|s| s.print_impl_restriction(r)) | |
| } | |
| fn block_to_string(&self, blk: &ast::Block) -> String { | |
| Self::to_string(|s| { | |
| let (cb, ib) = s.head(""); | |
| s.print_block(blk, cb, ib) | |
| }) | |
| } | |
| fn attr_item_to_string(&self, ai: &ast::AttrItem) -> String { | |
| Self::to_string(|s| s.print_attr_item(ai, ai.path.span)) | |
| } | |
| fn tts_to_string(&self, tokens: &TokenStream) -> String { | |
| Self::to_string(|s| s.print_tts(tokens, false)) | |
| } | |
| fn to_string(f: impl FnOnce(&mut State<'_>)) -> String { | |
| let mut printer = State::new(); | |
| f(&mut printer); | |
| printer.s.eof() | |
| } | |
| } | |
| impl<'a> PrintState<'a> for State<'a> { | |
| fn comments(&self) -> Option<&Comments<'a>> { | |
| self.comments.as_ref() | |
| } | |
| fn comments_mut(&mut self) -> Option<&mut Comments<'a>> { | |
| self.comments.as_mut() | |
| } | |
| fn ann_post(&mut self, ident: Ident) { | |
| self.ann.post(self, AnnNode::Ident(&ident)); | |
| } | |
| fn print_generic_args(&mut self, args: &ast::GenericArgs, colons_before_params: bool) { | |
| if colons_before_params { | |
| self.word("::") | |
| } | |
| match args { | |
| ast::GenericArgs::AngleBracketed(data) => { | |
| self.word("<"); | |
| self.commasep(Inconsistent, &data.args, |s, arg| match arg { | |
| ast::AngleBracketedArg::Arg(a) => s.print_generic_arg(a), | |
| ast::AngleBracketedArg::Constraint(c) => s.print_assoc_item_constraint(c), | |
| }); | |
| self.word(">") | |
| } | |
| ast::GenericArgs::Parenthesized(data) => { | |
| self.word("("); | |
| self.commasep(Inconsistent, &data.inputs, |s, ty| s.print_type(ty)); | |
| self.word(")"); | |
| self.print_fn_ret_ty(&data.output); | |
| } | |
| ast::GenericArgs::ParenthesizedElided(_) => { | |
| self.word("("); | |
| self.word(".."); | |
| self.word(")"); | |
| } | |
| } | |
| } | |
| } | |
| impl<'a> State<'a> { | |
| pub fn new() -> State<'a> { | |
| State { s: pp::Printer::new(), comments: None, ann: &NoAnn, is_sdylib_interface: false } | |
| } | |
| fn commasep_cmnt<T, F, G>(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G) | |
| where | |
| F: FnMut(&mut State<'_>, &T), | |
| G: FnMut(&T) -> rustc_span::Span, | |
| { | |
| let rb = self.rbox(0, b); | |
| let len = elts.len(); | |
| let mut i = 0; | |
| for elt in elts { | |
| self.maybe_print_comment(get_span(elt).hi()); | |
| op(self, elt); | |
| i += 1; | |
| if i < len { | |
| self.word(","); | |
| self.maybe_print_trailing_comment(get_span(elt), Some(get_span(&elts[i]).hi())); | |
| self.space_if_not_bol(); | |
| } | |
| } | |
| self.end(rb); | |
| } | |
| fn commasep_exprs(&mut self, b: Breaks, exprs: &[Box<ast::Expr>]) { | |
| self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e, FixupContext::default()), |e| e.span) | |
| } | |
| pub fn print_opt_lifetime(&mut self, lifetime: &Option<ast::Lifetime>) { | |
| if let Some(lt) = *lifetime { | |
| self.print_lifetime(lt); | |
| self.nbsp(); | |
| } | |
| } | |
| pub fn print_assoc_item_constraint(&mut self, constraint: &ast::AssocItemConstraint) { | |
| self.print_ident(constraint.ident); | |
| if let Some(args) = constraint.gen_args.as_ref() { | |
| self.print_generic_args(args, false) | |
| } | |
| self.space(); | |
| match &constraint.kind { | |
| ast::AssocItemConstraintKind::Equality { term } => { | |
| self.word_space("="); | |
| match term { | |
| Term::Ty(ty) => self.print_type(ty), | |
| Term::Const(c) => self.print_expr_anon_const(c, &[]), | |
| } | |
| } | |
| ast::AssocItemConstraintKind::Bound { bounds } => { | |
| if !bounds.is_empty() { | |
| self.word_nbsp(":"); | |
| self.print_type_bounds(bounds); | |
| } | |
| } | |
| } | |
| } | |
| pub fn print_generic_arg(&mut self, generic_arg: &GenericArg) { | |
| match generic_arg { | |
| GenericArg::Lifetime(lt) => self.print_lifetime(*lt), | |
| GenericArg::Type(ty) => self.print_type(ty), | |
| GenericArg::Const(ct) => self.print_expr(&ct.value, FixupContext::default()), | |
| } | |
| } | |
| pub fn print_ty_pat(&mut self, pat: &ast::TyPat) { | |
| match &pat.kind { | |
| rustc_ast::TyPatKind::Range(start, end, include_end) => { | |
| if let Some(start) = start { | |
| self.print_expr_anon_const(start, &[]); | |
| } | |
| self.word(".."); | |
| if let Some(end) = end { | |
| if let RangeEnd::Included(_) = include_end.node { | |
| self.word("="); | |
| } | |
| self.print_expr_anon_const(end, &[]); | |
| } | |
| } | |
| rustc_ast::TyPatKind::NotNull => self.word("!null"), | |
| rustc_ast::TyPatKind::Or(variants) => { | |
| let mut first = true; | |
| for pat in variants { | |
| if first { | |
| first = false | |
| } else { | |
| self.word(" | "); | |
| } | |
| self.print_ty_pat(pat); | |
| } | |
| } | |
| rustc_ast::TyPatKind::Err(_) => { | |
| self.popen(); | |
| self.word("/*ERROR*/"); | |
| self.pclose(); | |
| } | |
| } | |
| } | |
| pub fn print_type(&mut self, ty: &ast::Ty) { | |
| self.maybe_print_comment(ty.span.lo()); | |
| let ib = self.ibox(0); | |
| match &ty.kind { | |
| ast::TyKind::Slice(ty) => { | |
| self.word("["); | |
| self.print_type(ty); | |
| self.word("]"); | |
| } | |
| ast::TyKind::Ptr(mt) => { | |
| self.word("*"); | |
| self.print_mt(mt, true); | |
| } | |
| ast::TyKind::Ref(lifetime, mt) => { | |
| self.word("&"); | |
| self.print_opt_lifetime(lifetime); | |
| self.print_mt(mt, false); | |
| } | |
| ast::TyKind::PinnedRef(lifetime, mt) => { | |
| self.word("&"); | |
| self.print_opt_lifetime(lifetime); | |
| self.word("pin "); | |
| self.print_mt(mt, true); | |
| } | |
| ast::TyKind::Never => { | |
| self.word("!"); | |
| } | |
| ast::TyKind::Tup(elts) => { | |
| self.popen(); | |
| self.commasep(Inconsistent, elts, |s, ty| s.print_type(ty)); | |
| if elts.len() == 1 { | |
| self.word(","); | |
| } | |
| self.pclose(); | |
| } | |
| ast::TyKind::Paren(typ) => { | |
| self.popen(); | |
| self.print_type(typ); | |
| self.pclose(); | |
| } | |
| ast::TyKind::FnPtr(f) => { | |
| self.print_ty_fn(f.ext, f.safety, &f.decl, None, &f.generic_params); | |
| } | |
| ast::TyKind::UnsafeBinder(f) => { | |
| let ib = self.ibox(INDENT_UNIT); | |
| self.word("unsafe"); | |
| self.print_generic_params(&f.generic_params); | |
| self.nbsp(); | |
| self.print_type(&f.inner_ty); | |
| self.end(ib); | |
| } | |
| ast::TyKind::Path(None, path) => { | |
| self.print_path(path, false, 0); | |
| } | |
| ast::TyKind::Path(Some(qself), path) => self.print_qpath(path, qself, false), | |
| ast::TyKind::TraitObject(bounds, syntax) => { | |
| match syntax { | |
| ast::TraitObjectSyntax::Dyn => self.word_nbsp("dyn"), | |
| ast::TraitObjectSyntax::None => {} | |
| } | |
| self.print_type_bounds(bounds); | |
| } | |
| ast::TyKind::ImplTrait(_, bounds) => { | |
| self.word_nbsp("impl"); | |
| self.print_type_bounds(bounds); | |
| } | |
| ast::TyKind::Array(ty, length) => { | |
| self.word("["); | |
| self.print_type(ty); | |
| self.word("; "); | |
| self.print_expr(&length.value, FixupContext::default()); | |
| self.word("]"); | |
| } | |
| ast::TyKind::Infer => { | |
| self.word("_"); | |
| } | |
| ast::TyKind::Err(_) => { | |
| self.popen(); | |
| self.word("/*ERROR*/"); | |
| self.pclose(); | |
| } | |
| ast::TyKind::Dummy => { | |
| self.popen(); | |
| self.word("/*DUMMY*/"); | |
| self.pclose(); | |
| } | |
| ast::TyKind::ImplicitSelf => { | |
| self.word("Self"); | |
| } | |
| ast::TyKind::MacCall(m) => { | |
| self.print_mac(m); | |
| } | |
| ast::TyKind::CVarArgs => { | |
| self.word("..."); | |
| } | |
| ast::TyKind::Pat(ty, pat) => { | |
| self.print_type(ty); | |
| self.word(" is "); | |
| self.print_ty_pat(pat); | |
| } | |
| ast::TyKind::FieldOf(ty, variant, field) => { | |
| self.word("builtin # field_of"); | |
| self.popen(); | |
| let ib = self.ibox(0); | |
| self.print_type(ty); | |
| self.word(","); | |
| self.space(); | |
| if let Some(variant) = variant { | |
| self.print_ident(*variant); | |
| self.word("."); | |
| } | |
| self.print_ident(*field); | |
| self.end(ib); | |
| self.pclose(); | |
| } | |
| } | |
| self.end(ib); | |
| } | |
| fn print_trait_ref(&mut self, t: &ast::TraitRef) { | |
| self.print_path(&t.path, false, 0) | |
| } | |
| fn print_formal_generic_params(&mut self, generic_params: &[ast::GenericParam]) { | |
| if !generic_params.is_empty() { | |
| self.word("for"); | |
| self.print_generic_params(generic_params); | |
| self.nbsp(); | |
| } | |
| } | |
| fn print_poly_trait_ref(&mut self, t: &ast::PolyTraitRef) { | |
| if let ast::Parens::Yes = t.parens { | |
| self.popen(); | |
| } | |
| self.print_formal_generic_params(&t.bound_generic_params); | |
| let ast::TraitBoundModifiers { constness, asyncness, polarity } = t.modifiers; | |
| match constness { | |
| ast::BoundConstness::Never => {} | |
| ast::BoundConstness::Always(_) | ast::BoundConstness::Maybe(_) => { | |
| self.word_space(constness.as_str()); | |
| } | |
| } | |
| match asyncness { | |
| ast::BoundAsyncness::Normal => {} | |
| ast::BoundAsyncness::Async(_) => { | |
| self.word_space(asyncness.as_str()); | |
| } | |
| } | |
| match polarity { | |
| ast::BoundPolarity::Positive => {} | |
| ast::BoundPolarity::Negative(_) | ast::BoundPolarity::Maybe(_) => { | |
| self.word(polarity.as_str()); | |
| } | |
| } | |
| self.print_trait_ref(&t.trait_ref); | |
| if let ast::Parens::Yes = t.parens { | |
| self.pclose(); | |
| } | |
| } | |
| fn print_stmt(&mut self, st: &ast::Stmt) { | |
| self.maybe_print_comment(st.span.lo()); | |
| match &st.kind { | |
| ast::StmtKind::Let(loc) => { | |
| self.print_outer_attributes(&loc.attrs); | |
| self.space_if_not_bol(); | |
| let ib1 = self.ibox(INDENT_UNIT); | |
| if loc.super_.is_some() { | |
| self.word_nbsp("super"); | |
| } | |
| self.word_nbsp("let"); | |
| let ib2 = self.ibox(INDENT_UNIT); | |
| self.print_local_decl(loc); | |
| self.end(ib2); | |
| if let Some((init, els)) = loc.kind.init_else_opt() { | |
| self.nbsp(); | |
| self.word_space("="); | |
| self.print_expr_cond_paren( | |
| init, | |
| els.is_some() && classify::expr_trailing_brace(init).is_some(), | |
| FixupContext::default(), | |
| ); | |
| if let Some(els) = els { | |
| let cb = self.cbox(INDENT_UNIT); | |
| let ib = self.ibox(INDENT_UNIT); | |
| self.word(" else "); | |
| self.print_block(els, cb, ib); | |
| } | |
| } | |
| self.word(";"); | |
| self.end(ib1); | |
| } | |
| ast::StmtKind::Item(item) => self.print_item(item), | |
| ast::StmtKind::Expr(expr) => { | |
| self.space_if_not_bol(); | |
| self.print_expr_outer_attr_style(expr, false, FixupContext::new_stmt()); | |
| if classify::expr_requires_semi_to_be_stmt(expr) { | |
| self.word(";"); | |
| } | |
| } | |
| ast::StmtKind::Semi(expr) => { | |
| self.space_if_not_bol(); | |
| self.print_expr_outer_attr_style(expr, false, FixupContext::new_stmt()); | |
| self.word(";"); | |
| } | |
| ast::StmtKind::Empty => { | |
| self.space_if_not_bol(); | |
| self.word(";"); | |
| } | |
| ast::StmtKind::MacCall(mac) => { | |
| self.space_if_not_bol(); | |
| self.print_outer_attributes(&mac.attrs); | |
| self.print_mac(&mac.mac); | |
| if mac.style == ast::MacStmtStyle::Semicolon { | |
| self.word(";"); | |
| } | |
| } | |
| } | |
| self.maybe_print_trailing_comment(st.span, None) | |
| } | |
| fn print_block(&mut self, blk: &ast::Block, cb: BoxMarker, ib: BoxMarker) { | |
| self.print_block_with_attrs(blk, &[], cb, ib) | |
| } | |
| fn print_block_unclosed_indent(&mut self, blk: &ast::Block, ib: BoxMarker) { | |
| self.print_block_maybe_unclosed(blk, &[], None, ib) | |
| } | |
| fn print_block_with_attrs( | |
| &mut self, | |
| blk: &ast::Block, | |
| attrs: &[ast::Attribute], | |
| cb: BoxMarker, | |
| ib: BoxMarker, | |
| ) { | |
| self.print_block_maybe_unclosed(blk, attrs, Some(cb), ib) | |
| } | |
| fn print_block_maybe_unclosed( | |
| &mut self, | |
| blk: &ast::Block, | |
| attrs: &[ast::Attribute], | |
| cb: Option<BoxMarker>, | |
| ib: BoxMarker, | |
| ) { | |
| match blk.rules { | |
| BlockCheckMode::Unsafe(..) => self.word_space("unsafe"), | |
| BlockCheckMode::Default => (), | |
| } | |
| self.maybe_print_comment(blk.span.lo()); | |
| self.ann.pre(self, AnnNode::Block(blk)); | |
| self.bopen(ib); | |
| let has_attrs = self.print_inner_attributes(attrs); | |
| for (i, st) in blk.stmts.iter().enumerate() { | |
| match &st.kind { | |
| ast::StmtKind::Expr(expr) if i == blk.stmts.len() - 1 => { | |
| self.maybe_print_comment(st.span.lo()); | |
| self.space_if_not_bol(); | |
| self.print_expr_outer_attr_style(expr, false, FixupContext::new_stmt()); | |
| self.maybe_print_trailing_comment(expr.span, Some(blk.span.hi())); | |
| } | |
| _ => self.print_stmt(st), | |
| } | |
| } | |
| let no_space = !has_attrs && blk.stmts.is_empty(); | |
| self.bclose_maybe_open(blk.span, no_space, cb); | |
| self.ann.post(self, AnnNode::Block(blk)) | |
| } | |
| /// Print a `let pat = expr` expression. | |
| /// | |
| /// Parentheses are inserted surrounding `expr` if a round-trip through the | |
| /// parser would otherwise work out the wrong way in a condition position. | |
| /// | |
| /// For example each of the following would mean the wrong thing without | |
| /// parentheses. | |
| /// | |
| /// ```ignore (illustrative) | |
| /// if let _ = (Struct {}) {} | |
| /// | |
| /// if let _ = (true && false) {} | |
| /// ``` | |
| /// | |
| /// In a match guard, the second case still requires parens, but the first | |
| /// case no longer does because anything until `=>` is considered part of | |
| /// the match guard expression. Parsing of the expression is not terminated | |
| /// by `{` in that position. | |
| /// | |
| /// ```ignore (illustrative) | |
| /// match () { | |
| /// () if let _ = Struct {} => {} | |
| /// () if let _ = (true && false) => {} | |
| /// } | |
| /// ``` | |
| fn print_let(&mut self, pat: &ast::Pat, expr: &ast::Expr, fixup: FixupContext) { | |
| self.word("let "); | |
| self.print_pat(pat); | |
| self.space(); | |
| self.word_space("="); | |
| self.print_expr_cond_paren( | |
| expr, | |
| fixup.needs_par_as_let_scrutinee(expr), | |
| FixupContext::default(), | |
| ); | |
| } | |
| fn print_mac(&mut self, m: &ast::MacCall) { | |
| self.print_mac_common( | |
| Some(MacHeader::Path(&m.path)), | |
| true, | |
| None, | |
| m.args.delim, | |
| None, | |
| &m.args.tokens, | |
| true, | |
| m.span(), | |
| ); | |
| } | |
| fn print_inline_asm(&mut self, asm: &ast::InlineAsm) { | |
| enum AsmArg<'a> { | |
| Template(String), | |
| Operand(&'a InlineAsmOperand), | |
| ClobberAbi(Symbol), | |
| Options(InlineAsmOptions), | |
| } | |
| let mut args = vec![AsmArg::Template(InlineAsmTemplatePiece::to_string(&asm.template))]; | |
| args.extend(asm.operands.iter().map(|(o, _)| AsmArg::Operand(o))); | |
| for (abi, _) in &asm.clobber_abis { | |
| args.push(AsmArg::ClobberAbi(*abi)); | |
| } | |
| if !asm.options.is_empty() { | |
| args.push(AsmArg::Options(asm.options)); | |
| } | |
| self.popen(); | |
| self.commasep(Consistent, &args, |s, arg| match arg { | |
| AsmArg::Template(template) => s.print_string(template, ast::StrStyle::Cooked), | |
| AsmArg::Operand(op) => { | |
| let print_reg_or_class = |s: &mut Self, r: &InlineAsmRegOrRegClass| match r { | |
| InlineAsmRegOrRegClass::Reg(r) => s.print_symbol(*r, ast::StrStyle::Cooked), | |
| InlineAsmRegOrRegClass::RegClass(r) => s.word(r.to_string()), | |
| }; | |
| match op { | |
| InlineAsmOperand::In { reg, expr } => { | |
| s.word("in"); | |
| s.popen(); | |
| print_reg_or_class(s, reg); | |
| s.pclose(); | |
| s.space(); | |
| s.print_expr(expr, FixupContext::default()); | |
| } | |
| InlineAsmOperand::Out { reg, late, expr } => { | |
| s.word(if *late { "lateout" } else { "out" }); | |
| s.popen(); | |
| print_reg_or_class(s, reg); | |
| s.pclose(); | |
| s.space(); | |
| match expr { | |
| Some(expr) => s.print_expr(expr, FixupContext::default()), | |
| None => s.word("_"), | |
| } | |
| } | |
| InlineAsmOperand::InOut { reg, late, expr } => { | |
| s.word(if *late { "inlateout" } else { "inout" }); | |
| s.popen(); | |
| print_reg_or_class(s, reg); | |
| s.pclose(); | |
| s.space(); | |
| s.print_expr(expr, FixupContext::default()); | |
| } | |
| InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { | |
| s.word(if *late { "inlateout" } else { "inout" }); | |
| s.popen(); | |
| print_reg_or_class(s, reg); | |
| s.pclose(); | |
| s.space(); | |
| s.print_expr(in_expr, FixupContext::default()); | |
| s.space(); | |
| s.word_space("=>"); | |
| match out_expr { | |
| Some(out_expr) => s.print_expr(out_expr, FixupContext::default()), | |
| None => s.word("_"), | |
| } | |
| } | |
| InlineAsmOperand::Const { anon_const } => { | |
| s.word("const"); | |
| s.space(); | |
| s.print_expr(&anon_const.value, FixupContext::default()); | |
| } | |
| InlineAsmOperand::Sym { sym } => { | |
| s.word("sym"); | |
| s.space(); | |
| if let Some(qself) = &sym.qself { | |
| s.print_qpath(&sym.path, qself, true); | |
| } else { | |
| s.print_path(&sym.path, true, 0); | |
| } | |
| } | |
| InlineAsmOperand::Label { block } => { | |
| let (cb, ib) = s.head("label"); | |
| s.print_block(block, cb, ib); | |
| } | |
| } | |
| } | |
| AsmArg::ClobberAbi(abi) => { | |
| s.word("clobber_abi"); | |
| s.popen(); | |
| s.print_symbol(*abi, ast::StrStyle::Cooked); | |
| s.pclose(); | |
| } | |
| AsmArg::Options(opts) => { | |
| s.word("options"); | |
| s.popen(); | |
| s.commasep(Inconsistent, &opts.human_readable_names(), |s, &opt| { | |
| s.word(opt); | |
| }); | |
| s.pclose(); | |
| } | |
| }); | |
| self.pclose(); | |
| } | |
| fn print_local_decl(&mut self, loc: &ast::Local) { | |
| self.print_pat(&loc.pat); | |
| if let Some(ty) = &loc.ty { | |
| self.word_space(":"); | |
| self.print_type(ty); | |
| } | |
| } | |
| fn print_name(&mut self, name: Symbol) { | |
| self.word(name.to_string()); | |
| self.ann.post(self, AnnNode::Name(&name)) | |
| } | |
| fn print_qpath(&mut self, path: &ast::Path, qself: &ast::QSelf, colons_before_params: bool) { | |
| self.word("<"); | |
| self.print_type(&qself.ty); | |
| if qself.position > 0 { | |
| self.space(); | |
| self.word_space("as"); | |
| let depth = path.segments.len() - qself.position; | |
| self.print_path(path, false, depth); | |
| } | |
| self.word(">"); | |
| for item_segment in &path.segments[qself.position..] { | |
| self.word("::"); | |
| self.print_ident(item_segment.ident); | |
| if let Some(args) = &item_segment.args { | |
| self.print_generic_args(args, colons_before_params) | |
| } | |
| } | |
| } | |
| /// Print a pattern, parenthesizing it if it is an or-pattern (`A | B`). | |
| /// | |
| /// Or-patterns have the lowest precedence among patterns, so they need | |
| /// parentheses when nested inside `@` bindings, `&` references, or `box` | |
| /// patterns — otherwise `x @ A | B` parses as `(x @ A) | B`, `&A | B` | |
| /// parses as `(&A) | B`, etc. | |
| fn print_pat_paren_if_or(&mut self, pat: &ast::Pat) { | |
| let needs_paren = matches!(pat.kind, PatKind::Or(..)); | |
| if needs_paren { | |
| self.popen(); | |
| } | |
| self.print_pat(pat); | |
| if needs_paren { | |
| self.pclose(); | |
| } | |
| } | |
| fn print_pat(&mut self, pat: &ast::Pat) { | |
| self.maybe_print_comment(pat.span.lo()); | |
| self.ann.pre(self, AnnNode::Pat(pat)); | |
| /* Pat isn't normalized, but the beauty of it is that it doesn't matter */ | |
| match &pat.kind { | |
| PatKind::Missing => unreachable!(), | |
| PatKind::Wild => self.word("_"), | |
| PatKind::Never => self.word("!"), | |
| PatKind::Ident(BindingMode(by_ref, mutbl), ident, sub) => { | |
| if mutbl.is_mut() { | |
| self.word_nbsp("mut"); | |
| } | |
| if let ByRef::Yes(pinnedness, rmutbl) = by_ref { | |
| self.word_nbsp("ref"); | |
| if pinnedness.is_pinned() { | |
| self.word_nbsp("pin"); | |
| } | |
| if rmutbl.is_mut() { | |
| self.word_nbsp("mut"); | |
| } else if pinnedness.is_pinned() { | |
| self.word_nbsp("const"); | |
| } | |
| } | |
| self.print_ident(*ident); | |
| if let Some(p) = sub { | |
| self.space(); | |
| self.word_space("@"); | |
| self.print_pat_paren_if_or(p); | |
| } | |
| } | |
| PatKind::TupleStruct(qself, path, elts) => { | |
| if let Some(qself) = qself { | |
| self.print_qpath(path, qself, true); | |
| } else { | |
| self.print_path(path, true, 0); | |
| } | |
| self.popen(); | |
| self.commasep(Inconsistent, elts, |s, p| s.print_pat(p)); | |
| self.pclose(); | |
| } | |
| PatKind::Or(pats) => { | |
| self.strsep("|", true, Inconsistent, pats, |s, p| s.print_pat(p)); | |
| } | |
| PatKind::Path(None, path) => { | |
| self.print_path(path, true, 0); | |
| } | |
| PatKind::Path(Some(qself), path) => { | |
| self.print_qpath(path, qself, false); | |
| } | |
| PatKind::Struct(qself, path, fields, etc) => { | |
| if let Some(qself) = qself { | |
| self.print_qpath(path, qself, true); | |
| } else { | |
| self.print_path(path, true, 0); | |
| } | |
| self.nbsp(); | |
| self.word("{"); | |
| let empty = fields.is_empty() && *etc == ast::PatFieldsRest::None; | |
| if !empty { | |
| self.space(); | |
| } | |
| self.commasep_cmnt( | |
| Consistent, | |
| fields, | |
| |s, f| { | |
| let cb = s.cbox(INDENT_UNIT); | |
| if !f.is_shorthand { | |
| s.print_ident(f.ident); | |
| s.word_nbsp(":"); | |
| } | |
| s.print_pat(&f.pat); | |
| s.end(cb); | |
| }, | |
| |f| f.pat.span, | |
| ); | |
| if let ast::PatFieldsRest::Rest(_) | ast::PatFieldsRest::Recovered(_) = etc { | |
| if !fields.is_empty() { | |
| self.word_space(","); | |
| } | |
| self.word(".."); | |
| if let ast::PatFieldsRest::Recovered(_) = etc { | |
| self.word("/* recovered parse error */"); | |
| } | |
| } | |
| if !empty { | |
| self.space(); | |
| } | |
| self.word("}"); | |
| } | |
| PatKind::Tuple(elts) => { | |
| self.popen(); | |
| self.commasep(Inconsistent, elts, |s, p| s.print_pat(p)); | |
| if elts.len() == 1 { | |
| self.word(","); | |
| } | |
| self.pclose(); | |
| } | |
| PatKind::Box(inner) => { | |
| self.word("box "); | |
| self.print_pat_paren_if_or(inner); | |
| } | |
| PatKind::Deref(inner) => { | |
| self.word("deref!"); | |
| self.popen(); | |
| self.print_pat(inner); | |
| self.pclose(); | |
| } | |
| PatKind::Ref(inner, pinned, mutbl) => { | |
| self.word("&"); | |
| if pinned.is_pinned() { | |
| self.word("pin "); | |
| if mutbl.is_not() { | |
| self.word("const "); | |
| } | |
| } | |
| if mutbl.is_mut() { | |
| self.word("mut "); | |
| } | |
| if let PatKind::Ident(ast::BindingMode::MUT, ..) = inner.kind { | |
| self.popen(); | |
| self.print_pat(inner); | |
| self.pclose(); | |
| } else { | |
| self.print_pat_paren_if_or(inner); | |
| } | |
| } | |
| PatKind::Expr(e) => self.print_expr(e, FixupContext::default()), | |
| PatKind::Range(begin, end, Spanned { node: end_kind, .. }) => { | |
| if let Some(e) = begin { | |
| self.print_expr(e, FixupContext::default()); | |
| } | |
| match end_kind { | |
| RangeEnd::Included(RangeSyntax::DotDotDot) => self.word("..."), | |
| RangeEnd::Included(RangeSyntax::DotDotEq) => self.word("..="), | |
| RangeEnd::Excluded => self.word(".."), | |
| } | |
| if let Some(e) = end { | |
| self.print_expr(e, FixupContext::default()); | |
| } | |
| } | |
| PatKind::Guard(subpat, guard) => { | |
| self.popen(); | |
| self.print_pat(subpat); | |
| self.space(); | |
| self.word_space("if"); | |
| self.print_expr(&guard.cond, FixupContext::default()); | |
| self.pclose(); | |
| } | |
| PatKind::Slice(elts) => { | |
| self.word("["); | |
| self.commasep(Inconsistent, elts, |s, p| s.print_pat(p)); | |
| self.word("]"); | |
| } | |
| PatKind::Rest => self.word(".."), | |
| PatKind::Paren(inner) => { | |
| self.popen(); | |
| self.print_pat(inner); | |
| self.pclose(); | |
| } | |
| PatKind::MacCall(m) => self.print_mac(m), | |
| PatKind::Err(_) => { | |
| self.popen(); | |
| self.word("/*ERROR*/"); | |
| self.pclose(); | |
| } | |
| } | |
| self.ann.post(self, AnnNode::Pat(pat)) | |
| } | |
| fn print_explicit_self(&mut self, explicit_self: &ast::ExplicitSelf) { | |
| match &explicit_self.node { | |
| SelfKind::Value(m) => { | |
| self.print_mutability(*m, false); | |
| self.word("self") | |
| } | |
| SelfKind::Region(lt, m) => { | |
| self.word("&"); | |
| self.print_opt_lifetime(lt); | |
| self.print_mutability(*m, false); | |
| self.word("self") | |
| } | |
| SelfKind::Pinned(lt, m) => { | |
| self.word("&"); | |
| self.print_opt_lifetime(lt); | |
| self.word("pin "); | |
| self.print_mutability(*m, true); | |
| self.word("self") | |
| } | |
| SelfKind::Explicit(typ, m) => { | |
| self.print_mutability(*m, false); | |
| self.word("self"); | |
| self.word_space(":"); | |
| self.print_type(typ) | |
| } | |
| } | |
| } | |
| fn print_coroutine_kind(&mut self, coroutine_kind: ast::CoroutineKind) { | |
| match coroutine_kind { | |
| ast::CoroutineKind::Gen { .. } => { | |
| self.word_nbsp("gen"); | |
| } | |
| ast::CoroutineKind::Async { .. } => { | |
| self.word_nbsp("async"); | |
| } | |
| ast::CoroutineKind::AsyncGen { .. } => { | |
| self.word_nbsp("async"); | |
| self.word_nbsp("gen"); | |
| } | |
| } | |
| } | |
| pub fn print_type_bounds(&mut self, bounds: &[ast::GenericBound]) { | |
| let mut first = true; | |
| for bound in bounds { | |
| if first { | |
| first = false; | |
| } else { | |
| self.nbsp(); | |
| self.word_space("+"); | |
| } | |
| match bound { | |
| GenericBound::Trait(tref) => { | |
| self.print_poly_trait_ref(tref); | |
| } | |
| GenericBound::Outlives(lt) => self.print_lifetime(*lt), | |
| GenericBound::Use(args, _) => { | |
| self.word("use"); | |
| self.word("<"); | |
| self.commasep(Inconsistent, args, |s, arg| match arg { | |
| ast::PreciseCapturingArg::Arg(p, _) => s.print_path(p, false, 0), | |
| ast::PreciseCapturingArg::Lifetime(lt) => s.print_lifetime(*lt), | |
| }); | |
| self.word(">") | |
| } | |
| } | |
| } | |
| } | |
| fn print_lifetime(&mut self, lifetime: ast::Lifetime) { | |
| self.word(lifetime.ident.name.to_string()); | |
| self.ann_post(lifetime.ident) | |
| } | |
| fn print_lifetime_bounds(&mut self, bounds: &ast::GenericBounds) { | |
| for (i, bound) in bounds.iter().enumerate() { | |
| if i != 0 { | |
| self.word(" + "); | |
| } | |
| match bound { | |
| ast::GenericBound::Outlives(lt) => self.print_lifetime(*lt), | |
| _ => { | |
| panic!("expected a lifetime bound, found a trait bound") | |
| } | |
| } | |
| } | |
| } | |
| fn print_generic_params(&mut self, generic_params: &[ast::GenericParam]) { | |
| if generic_params.is_empty() { | |
| return; | |
| } | |
| self.word("<"); | |
| self.commasep(Inconsistent, generic_params, |s, param| { | |
| s.print_outer_attributes_inline(¶m.attrs); | |
| match ¶m.kind { | |
| ast::GenericParamKind::Lifetime => { | |
| let lt = ast::Lifetime { id: param.id, ident: param.ident }; | |
| s.print_lifetime(lt); | |
| if !param.bounds.is_empty() { | |
| s.word_nbsp(":"); | |
| s.print_lifetime_bounds(¶m.bounds) | |
| } | |
| } | |
| ast::GenericParamKind::Type { default } => { | |
| s.print_ident(param.ident); | |
| if !param.bounds.is_empty() { | |
| s.word_nbsp(":"); | |
| s.print_type_bounds(¶m.bounds); | |
| } | |
| if let Some(default) = default { | |
| s.space(); | |
| s.word_space("="); | |
| s.print_type(default) | |
| } | |
| } | |
| ast::GenericParamKind::Const { ty, default, .. } => { | |
| s.word_space("const"); | |
| s.print_ident(param.ident); | |
| s.space(); | |
| s.word_space(":"); | |
| s.print_type(ty); | |
| if !param.bounds.is_empty() { | |
| s.word_nbsp(":"); | |
| s.print_type_bounds(¶m.bounds); | |
| } | |
| if let Some(default) = default { | |
| s.space(); | |
| s.word_space("="); | |
| s.print_expr(&default.value, FixupContext::default()); | |
| } | |
| } | |
| } | |
| }); | |
| self.word(">"); | |
| } | |
| pub fn print_mutability(&mut self, mutbl: ast::Mutability, print_const: bool) { | |
| match mutbl { | |
| ast::Mutability::Mut => self.word_nbsp("mut"), | |
| ast::Mutability::Not => { | |
| if print_const { | |
| self.word_nbsp("const"); | |
| } | |
| } | |
| } | |
| } | |
| fn print_mt(&mut self, mt: &ast::MutTy, print_const: bool) { | |
| self.print_mutability(mt.mutbl, print_const); | |
| self.print_type(&mt.ty) | |
| } | |
| fn print_param(&mut self, input: &ast::Param, is_closure: bool) { | |
| let ib = self.ibox(INDENT_UNIT); | |
| self.print_outer_attributes_inline(&input.attrs); | |
| match input.ty.kind { | |
| ast::TyKind::Infer if is_closure => self.print_pat(&input.pat), | |
| _ => { | |
| if let Some(eself) = input.to_self() { | |
| self.print_explicit_self(&eself); | |
| } else { | |
| if !matches!(input.pat.kind, PatKind::Missing) { | |
| self.print_pat(&input.pat); | |
| self.word(":"); | |
| self.space(); | |
| } | |
| self.print_type(&input.ty); | |
| } | |
| } | |
| } | |
| self.end(ib); | |
| } | |
| fn print_fn_ret_ty(&mut self, fn_ret_ty: &ast::FnRetTy) { | |
| if let ast::FnRetTy::Ty(ty) = fn_ret_ty { | |
| self.space_if_not_bol(); | |
| let ib = self.ibox(INDENT_UNIT); | |
| self.word_space("->"); | |
| self.print_type(ty); | |
| self.end(ib); | |
| self.maybe_print_comment(ty.span.lo()); | |
| } | |
| } | |
| fn print_ty_fn( | |
| &mut self, | |
| ext: ast::Extern, | |
| safety: ast::Safety, | |
| decl: &ast::FnDecl, | |
| name: Option<Ident>, | |
| generic_params: &[ast::GenericParam], | |
| ) { | |
| let ib = self.ibox(INDENT_UNIT); | |
| self.print_formal_generic_params(generic_params); | |
| let generics = ast::Generics::default(); | |
| let header = ast::FnHeader { safety, ext, ..ast::FnHeader::default() }; | |
| self.print_fn(decl, header, name, &generics); | |
| self.end(ib); | |
| } | |
| fn print_fn_header_info(&mut self, header: ast::FnHeader) { | |
| self.print_constness(header.constness); | |
| header.coroutine_kind.map(|coroutine_kind| self.print_coroutine_kind(coroutine_kind)); | |
| self.print_safety(header.safety); | |
| match header.ext { | |
| ast::Extern::None => {} | |
| ast::Extern::Implicit(_) => { | |
| self.word_nbsp("extern"); | |
| } | |
| ast::Extern::Explicit(abi, _) => { | |
| self.word_nbsp("extern"); | |
| self.print_token_literal(abi.as_token_lit(), abi.span); | |
| self.nbsp(); | |
| } | |
| } | |
| self.word("fn") | |
| } | |
| fn print_safety(&mut self, s: ast::Safety) { | |
| match s { | |
| ast::Safety::Default => {} | |
| ast::Safety::Safe(_) => self.word_nbsp("safe"), | |
| ast::Safety::Unsafe(_) => self.word_nbsp("unsafe"), | |
| } | |
| } | |
| fn print_constness(&mut self, s: ast::Const) { | |
| match s { | |
| ast::Const::No => {} | |
| ast::Const::Yes(_) => self.word_nbsp("const"), | |
| } | |
| } | |
| fn print_is_auto(&mut self, s: ast::IsAuto) { | |
| match s { | |
| ast::IsAuto::Yes => self.word_nbsp("auto"), | |
| ast::IsAuto::No => {} | |
| } | |
| } | |
| fn print_meta_item_lit(&mut self, lit: &ast::MetaItemLit) { | |
| self.print_token_literal(lit.as_token_lit(), lit.span) | |
| } | |
| fn print_token_literal(&mut self, token_lit: token::Lit, span: Span) { | |
| self.maybe_print_comment(span.lo()); | |
| self.word(token_lit.to_string()) | |
| } | |
| fn print_symbol(&mut self, sym: Symbol, style: ast::StrStyle) { | |
| self.print_string(sym.as_str(), style); | |
| } | |
| fn print_inner_attributes_no_trailing_hardbreak(&mut self, attrs: &[ast::Attribute]) -> bool { | |
| self.print_either_attributes(attrs, ast::AttrStyle::Inner, false, false) | |
| } | |
| fn print_outer_attributes_inline(&mut self, attrs: &[ast::Attribute]) -> bool { | |
| self.print_either_attributes(attrs, ast::AttrStyle::Outer, true, true) | |
| } | |
| fn print_attribute(&mut self, attr: &ast::Attribute) { | |
| self.print_attribute_inline(attr, false); | |
| } | |
| fn print_meta_list_item(&mut self, item: &ast::MetaItemInner) { | |
| match item { | |
| ast::MetaItemInner::MetaItem(mi) => self.print_meta_item(mi), | |
| ast::MetaItemInner::Lit(lit) => self.print_meta_item_lit(lit), | |
| } | |
| } | |
| fn print_meta_item(&mut self, item: &ast::MetaItem) { | |
| let ib = self.ibox(INDENT_UNIT); | |
| match item.unsafety { | |
| ast::Safety::Unsafe(_) => { | |
| self.word("unsafe"); | |
| self.popen(); | |
| } | |
| ast::Safety::Default | ast::Safety::Safe(_) => {} | |
| } | |
| match &item.kind { | |
| ast::MetaItemKind::Word => self.print_path(&item.path, false, 0), | |
| ast::MetaItemKind::NameValue(value) => { | |
| self.print_path(&item.path, false, 0); | |
| self.space(); | |
| self.word_space("="); | |
| self.print_meta_item_lit(value); | |
| } | |
| ast::MetaItemKind::List(items) => { | |
| self.print_path(&item.path, false, 0); | |
| self.popen(); | |
| self.commasep(Consistent, items, |s, i| s.print_meta_list_item(i)); | |
| self.pclose(); | |
| } | |
| } | |
| match item.unsafety { | |
| ast::Safety::Unsafe(_) => self.pclose(), | |
| ast::Safety::Default | ast::Safety::Safe(_) => {} | |
| } | |
| self.end(ib); | |
| } | |
| pub(crate) fn bounds_to_string(&self, bounds: &[ast::GenericBound]) -> String { | |
| Self::to_string(|s| s.print_type_bounds(bounds)) | |
| } | |
| pub(crate) fn where_bound_predicate_to_string( | |
| &self, | |
| where_bound_predicate: &ast::WhereBoundPredicate, | |
| ) -> String { | |
| Self::to_string(|s| s.print_where_bound_predicate(where_bound_predicate)) | |
| } | |
| pub(crate) fn tt_to_string(&self, tt: &TokenTree) -> String { | |
| Self::to_string(|s| { | |
| s.print_tt(tt, false); | |
| }) | |
| } | |
| pub(crate) fn path_segment_to_string(&self, p: &ast::PathSegment) -> String { | |
| Self::to_string(|s| s.print_path_segment(p, false)) | |
| } | |
| pub(crate) fn meta_list_item_to_string(&self, li: &ast::MetaItemInner) -> String { | |
| Self::to_string(|s| s.print_meta_list_item(li)) | |
| } | |
| pub(crate) fn attribute_to_string(&self, attr: &ast::Attribute) -> String { | |
| Self::to_string(|s| s.print_attribute(attr)) | |
| } | |
| } |