diff --git a/src/main.rs b/src/main.rs index 110a249f..b9d8c92d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ mod options; mod outcome; mod output; mod path; +mod pretty; mod process; mod scenario; mod source; diff --git a/src/pretty.rs b/src/pretty.rs new file mode 100644 index 00000000..0a35ed42 --- /dev/null +++ b/src/pretty.rs @@ -0,0 +1,69 @@ +// Copyright 2021-2023 Martin Pool + +//! Convert a token stream back to (reasonably) pretty Rust code in a string. + +use proc_macro2::{Delimiter, TokenTree}; +use quote::ToTokens; + +/// Convert a TokenStream representing some code to a reasonably formatted +/// string of Rust code. +/// +/// [TokenStream] has a `to_string`, but it adds spaces in places that don't +/// look idiomatic, so this reimplements it in a way that looks better. +/// +/// This is probably not correctly formatted for all Rust syntax, and only tries +/// to cover cases that can emerge from the code we generate. +pub(crate) fn tokens_to_pretty_string(t: T) -> String { + use TokenTree::*; + let mut b = String::with_capacity(200); + let mut ts = t.to_token_stream().into_iter().peekable(); + while let Some(tt) = ts.next() { + match tt { + Punct(p) => { + let pc = p.as_char(); + b.push(pc); + if ts.peek().is_some() && (b.ends_with("->") || pc == ',' || pc == ';') { + b.push(' '); + } + } + Ident(_) | Literal(_) => { + match tt { + Literal(l) => b.push_str(&l.to_string()), + Ident(i) => b.push_str(&i.to_string()), + _ => unreachable!(), + }; + if let Some(next) = ts.peek() { + match next { + Ident(_) | Literal(_) => b.push(' '), + Punct(p) => match p.as_char() { + ',' | ';' | '<' | '>' | ':' | '.' | '!' => (), + _ => b.push(' '), + }, + Group(_) => (), + } + } + } + Group(g) => { + match g.delimiter() { + Delimiter::Brace => b.push('{'), + Delimiter::Bracket => b.push('['), + Delimiter::Parenthesis => b.push('('), + Delimiter::None => (), + } + b.push_str(&tokens_to_pretty_string(g.stream())); + match g.delimiter() { + Delimiter::Brace => b.push('}'), + Delimiter::Bracket => b.push(']'), + Delimiter::Parenthesis => b.push(')'), + Delimiter::None => (), + } + } + } + } + debug_assert!( + !b.ends_with(' '), + "generated a trailing space: ts={ts:?}, b={b:?}", + ts = t.to_token_stream(), + ); + b +} diff --git a/src/visit.rs b/src/visit.rs index 56ac4494..b451572d 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use anyhow::Context; use itertools::Itertools; -use proc_macro2::{Delimiter, TokenStream, TokenTree}; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::ext::IdentExt; use syn::visit::Visit; @@ -23,6 +23,7 @@ use syn::{ }; use tracing::{debug, debug_span, trace, trace_span, warn}; +use crate::pretty::tokens_to_pretty_string; use crate::source::SourceFile; use crate::*; @@ -625,69 +626,6 @@ fn path_is_nonzero_unsigned(path: &Path) -> bool { } } -/// Convert a TokenStream representing some code to a reasonably formatted -/// string of Rust code. -/// -/// [TokenStream] has a `to_string`, but it adds spaces in places that don't -/// look idiomatic, so this reimplements it in a way that looks better. -/// -/// This is probably not correctly formatted for all Rust syntax, and only tries -/// to cover cases that can emerge from the code we generate. -fn tokens_to_pretty_string(t: T) -> String { - use TokenTree::*; - let mut b = String::with_capacity(200); - let mut ts = t.to_token_stream().into_iter().peekable(); - while let Some(tt) = ts.next() { - match tt { - Punct(p) => { - let pc = p.as_char(); - b.push(pc); - if ts.peek().is_some() && (b.ends_with("->") || pc == ',' || pc == ';') { - b.push(' '); - } - } - Ident(_) | Literal(_) => { - match tt { - Literal(l) => b.push_str(&l.to_string()), - Ident(i) => b.push_str(&i.to_string()), - _ => unreachable!(), - }; - if let Some(next) = ts.peek() { - match next { - Ident(_) | Literal(_) => b.push(' '), - Punct(p) => match p.as_char() { - ',' | ';' | '<' | '>' | ':' | '.' | '!' => (), - _ => b.push(' '), - }, - Group(_) => (), - } - } - } - Group(g) => { - match g.delimiter() { - Delimiter::Brace => b.push('{'), - Delimiter::Bracket => b.push('['), - Delimiter::Parenthesis => b.push('('), - Delimiter::None => (), - } - b.push_str(&tokens_to_pretty_string(g.stream())); - match g.delimiter() { - Delimiter::Brace => b.push('}'), - Delimiter::Bracket => b.push(']'), - Delimiter::Parenthesis => b.push(')'), - Delimiter::None => (), - } - } - } - } - debug_assert!( - !b.ends_with(' '), - "generated a trailing space: ts={ts:?}, b={b:?}", - ts = t.to_token_stream(), - ); - b -} - /// If this looks like `Result` (optionally with `Result` in some module), return `T`. fn result_ok_type(path: &Path) -> Option<&Type> { match_first_type_arg(path, "Result")