pub fn render(tokens: proc_macro2::TokenStream) -> Result<String, syn::Error> {
let source = tokens.to_string();
consortium_log::trace!(
"rendering {} bytes of generated tokens to source",
source.len()
);
let file = syn::parse_file(&source).map_err(|err| {
consortium_log::trace!("generated tokens failed to parse as a Rust file: {}", err);
err
})?;
Ok(restore_safety_comments(&prettyplease::unparse(&file)))
}
pub fn restore_safety_comments(src: &str) -> String {
let mut out = String::with_capacity(src.len());
let mut in_safety = false;
for line in src.split_inclusive('\n') {
let (content, newline) = match line.strip_suffix('\n') {
Some(rest) => (rest, "\n"),
None => (line, ""),
};
let indent_len = content.len() - content.trim_start().len();
let (indent, body) = content.split_at(indent_len);
if let Some(doc) = body.strip_prefix("///") {
if doc.trim_start().starts_with("SAFETY") {
in_safety = true;
}
if in_safety {
out.push_str(indent);
out.push_str("//");
out.push_str(doc);
out.push_str(newline);
continue;
}
} else {
in_safety = false;
}
out.push_str(line);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rewrites_safety_doc_into_line_comment() {
let src = "fn f() {\n /// SAFETY: valid handle\n unsafe { g() }\n}\n";
let out = restore_safety_comments(src);
assert!(out.contains(" // SAFETY: valid handle\n"));
assert!(!out.contains("///"));
}
#[test]
fn rewrites_multi_line_safety_run() {
let src = "fn f() {\n /// SAFETY: base covers the descriptor and\n /// the channels are this side's.\n unsafe { g() }\n}\n";
let out = restore_safety_comments(src);
assert!(out.contains(" // SAFETY: base covers the descriptor and\n"));
assert!(out.contains(" // the channels are this side's.\n"));
}
#[test]
fn leaves_ordinary_doc_comments_untouched() {
let src = "/// Ordinary item docs.\nstruct S;\n";
assert_eq!(restore_safety_comments(src), src);
}
#[test]
fn run_ends_at_non_doc_line() {
let src = "/// SAFETY: one\nunsafe { a() }\n/// Item docs\nstruct S;\n";
let out = restore_safety_comments(src);
assert!(out.contains("// SAFETY: one\n"));
assert!(out.contains("/// Item docs\n"));
}
#[test]
fn render_restores_safety_from_tokens() {
let tokens = quote::quote! {
fn f() {
unsafe { g() }
}
};
let out = render(tokens).expect("valid Rust");
assert!(out.contains("// SAFETY: verified in config"));
assert!(!out.contains("///"));
}
}