#![forbid(unsafe_code)]
#![doc(html_logo_url = "https://github.com/mxxo/plutonium/raw/master/pluto.png")]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
fold::Fold, parse_macro_input, parse_quote, Block, Expr, ExprUnsafe, ItemFn, Stmt, Token,
};
#[proc_macro_attribute]
pub fn safe(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let mut safe_fn = input_fn.clone();
if input_fn.sig.unsafety.is_some() {
safe_fn.sig.unsafety = None;
}
safe_fn.block = Box::new(MakeFnBodyUnsafe.fold_block(*input_fn.block));
quote!(#safe_fn).into()
}
struct MakeFnBodyUnsafe;
impl Fold for MakeFnBodyUnsafe {
fn fold_block(&mut self, block: Block) -> Block {
Block {
brace_token: block.brace_token,
stmts: vec![Stmt::Expr(Expr::Unsafe(ExprUnsafe {
attrs: vec![parse_quote! { #[allow(unused_unsafe)] }],
unsafe_token: Token!(unsafe)(block.brace_token.span),
block,
}))],
}
}
}
#[proc_macro]
pub fn optimize(_tokens: TokenStream) -> TokenStream {
TokenStream::new()
}
#[proc_macro_attribute]
pub fn unby(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut unby_fn = parse_macro_input!(item as ItemFn);
unby_fn.block = Box::new(parse_quote! {{
#[allow(invalid_value)]
unsafe { std::mem::MaybeUninit::uninit().assume_init() }
}});
quote!(#unby_fn).into()
}
#[proc_macro_attribute]
pub fn fallout(_attr: TokenStream, item: TokenStream) -> TokenStream {
if let Ok(mut fallout_fn) = syn::parse::<ItemFn>(item.clone()) {
fallout_fn.block.stmts = fallthrough_stmts(&fallout_fn.block.stmts);
return quote!(#fallout_fn).into()
}
item
}
fn fallthrough_stmts(stmts: &Vec<Stmt>) -> Vec<Stmt> {
let mut fallthru_stmts = Vec::with_capacity(stmts.len());
for stmt in stmts {
match stmt {
Stmt::Local(_) | Stmt::Item(_) => fallthru_stmts.push(stmt.clone()),
Stmt::Expr(expr) => fallthru_stmts.push(Stmt::Expr(fallthrough_expr(expr))),
Stmt::Semi(expr, semi) => fallthru_stmts.push(Stmt::Semi(fallthrough_expr(expr), *semi)),
}
};
fallthru_stmts
}
fn fallthrough_expr(expr: &syn::Expr) -> syn::Expr {
match expr {
Expr::Match(m) => {
let mut arm_masher = FallThru { arm_exprs: Vec::new() };
let mut mashed_arms: Vec<_> = m.arms.iter().rev().map(|arm| arm_masher.fold_arm(arm.clone())).collect();
Expr::Match(syn::ExprMatch {
arms: { mashed_arms.reverse(); mashed_arms },
..m.clone()
})
},
_ => expr.clone()
}
}
struct FallThru {
arm_exprs: Vec<syn::Expr>,
}
impl Fold for FallThru {
fn fold_arm(&mut self, mut arm: syn::Arm) -> syn::Arm {
let (breakless_body, arm_ending) = FallThru::parse_arm(arm.body);
if let ArmEnd::Break = arm_ending {
self.arm_exprs.clear();
}
self.arm_exprs.push(*breakless_body);
arm.body = self.as_arm_body();
arm
}
}
#[derive(Debug, Clone, Copy)]
enum ArmEnd { Break, FallThru }
impl FallThru {
fn as_arm_body(&self) -> Box<syn::Expr> {
if self.arm_exprs.len() == 0 {
panic!("arm exprs is empty");
}
let mut stmts: Vec<syn::Stmt> = Vec::with_capacity(self.arm_exprs.len());
for i in 0..self.arm_exprs.len() {
if i == 0 {
stmts.push(syn::Stmt::Expr(self.arm_exprs[i].clone()));
} else {
stmts.push(syn::Stmt::Semi(
self.arm_exprs[i].clone(),
parse_quote!(;),
));
}
}
stmts.reverse();
Box::new(syn::Expr::Block (
syn::ExprBlock {
attrs: Vec::new(),
label: None,
block: Block {
brace_token: syn::token::Brace { span: Span::call_site() },
stmts
},
}
))
}
fn parse_arm(expr: Box<syn::Expr>) -> (Box<syn::Expr>, ArmEnd) {
match *expr {
Expr::Break(_) => (Box::new(parse_quote!{()}), ArmEnd::Break),
Expr::Block(mut block_expr) => {
match block_expr.block.stmts.last() {
Some(syn::Stmt::Expr(Expr::Break(_)))
| Some(syn::Stmt::Semi(Expr::Break(_), _)) => {
let _ = block_expr.block.stmts.pop();
match block_expr.block.stmts.pop() {
Some(syn::Stmt::Semi(expr, _)) => {
block_expr.block.stmts.push(syn::Stmt::Expr(expr));
},
Some(other) => block_expr.block.stmts.push(other),
None => {},
}
(Box::new(Expr::Block(block_expr)), ArmEnd::Break)
},
_ => (Box::new(Expr::Block(block_expr)), ArmEnd::FallThru),
}
},
other => (Box::new(other), ArmEnd::FallThru),
}
}
}