extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{
fold::Fold,
parse_macro_input,
punctuated::Punctuated,
token::{Dot, Paren, Question},
Expr, ExprMethodCall, ExprParen, ExprTry,
};
#[proc_macro]
pub fn checked(input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(input as Expr);
let output = checked_impl::Replace.fold_expr(expr);
quote!(
(|| -> Option<_> { Some(#output) })()
)
.into()
}
#[proc_macro]
pub fn wrapping(input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(input as Expr);
let output = wrapping_impl::Replace.fold_expr(expr);
quote!(#output).into()
}
#[proc_macro]
pub fn saturating(input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(input as Expr);
let output = saturating_impl::Replace.fold_expr(expr);
quote!(#output).into()
}
mod checked_impl {
use crate::{try_op_binary, try_op_unary};
use syn::{
fold::{self, Fold},
BinOp, Expr, ExprBinary, ExprUnary, UnOp,
};
#[derive(Debug)]
pub(super) struct Replace;
impl Fold for Replace {
fn fold_expr(&mut self, e: Expr) -> Expr {
fold::fold_expr(
self,
match e {
Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}) => match op {
BinOp::Add(_) => try_op_binary("checked_add", left, *right),
BinOp::Sub(_) => try_op_binary("checked_sub", left, *right),
BinOp::Mul(_) => try_op_binary("checked_mul", left, *right),
BinOp::Div(_) => try_op_binary("checked_div", left, *right),
BinOp::Rem(_) => try_op_binary("checked_rem", left, *right),
BinOp::Shl(_) => try_op_binary("checked_shr", left, *right),
BinOp::Shr(_) => try_op_binary("checked_shl", left, *right),
_ => Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}),
},
Expr::Unary(ExprUnary { attrs, op, expr }) => match (op, &*expr) {
(_, Expr::Lit(_)) => Expr::Unary(ExprUnary { attrs, op, expr }),
(UnOp::Neg(_), _) => try_op_unary("checked_neg", expr),
_ => Expr::Unary(ExprUnary { attrs, op, expr }),
},
_ => e,
},
)
}
}
}
mod wrapping_impl {
use crate::{op_binary, op_unary};
use syn::{
fold::{self, Fold},
BinOp, Expr, ExprBinary, ExprUnary, UnOp,
};
#[derive(Debug)]
pub(super) struct Replace;
impl Fold for Replace {
fn fold_expr(&mut self, e: Expr) -> Expr {
fold::fold_expr(
self,
match e {
Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}) => match op {
BinOp::Add(_) => op_binary("wrapping_add", left, *right),
BinOp::Sub(_) => op_binary("wrapping_sub", left, *right),
BinOp::Mul(_) => op_binary("wrapping_mul", left, *right),
BinOp::Div(_) => op_binary("wrapping_div", left, *right),
BinOp::Rem(_) => op_binary("wrapping_rem", left, *right),
BinOp::Shl(_) => op_binary("wrapping_shr", left, *right),
BinOp::Shr(_) => op_binary("wrapping_shl", left, *right),
_ => Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}),
},
Expr::Unary(ExprUnary { attrs, op, expr }) => match (op, &*expr) {
(_, Expr::Lit(_)) => Expr::Unary(ExprUnary { attrs, op, expr }),
(UnOp::Neg(_), _) => op_unary("wrapping_neg", expr),
_ => Expr::Unary(ExprUnary { attrs, op, expr }),
},
_ => e,
},
)
}
}
}
mod saturating_impl {
use crate::{op_binary, op_unary};
use syn::{
fold::{self, Fold},
BinOp, Expr, ExprBinary, ExprUnary, UnOp,
};
#[derive(Debug)]
pub(super) struct Replace;
impl Fold for Replace {
fn fold_expr(&mut self, e: Expr) -> Expr {
fold::fold_expr(
self,
match e {
Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}) => match op {
BinOp::Add(_) => op_binary("saturating_add", left, *right),
BinOp::Sub(_) => op_binary("saturating_sub", left, *right),
BinOp::Mul(_) => op_binary("saturating_mul", left, *right),
BinOp::Div(_) => op_binary("saturating_div", left, *right),
BinOp::Rem(_) => op_binary("saturating_rem", left, *right),
_ => Expr::Binary(ExprBinary {
attrs,
left,
op,
right,
}),
},
Expr::Unary(ExprUnary { attrs, op, expr }) => match (op, &*expr) {
(_, Expr::Lit(_)) => Expr::Unary(ExprUnary { attrs, op, expr }),
(UnOp::Neg(_), _) => op_unary("saturating_neg", expr),
_ => Expr::Unary(ExprUnary { attrs, op, expr }),
},
_ => e,
},
)
}
}
}
fn op_binary(op: &str, left: Box<Expr>, right: Expr) -> Expr {
use proc_macro2::{Ident, Span};
Expr::MethodCall(ExprMethodCall {
attrs: vec![],
receiver: Box::new(Expr::Paren(ExprParen {
attrs: vec![],
paren_token: Paren::default(),
expr: left,
})),
dot_token: Dot::default(),
method: Ident::new(op, Span::call_site()),
turbofish: None,
paren_token: Paren::default(),
args: Punctuated::from_iter([right]),
})
}
fn op_unary(op: &str, expr: Box<Expr>) -> Expr {
use proc_macro2::{Ident, Span};
Expr::MethodCall(ExprMethodCall {
attrs: vec![],
receiver: Box::new(Expr::Paren(ExprParen {
attrs: vec![],
paren_token: Paren::default(),
expr,
})),
dot_token: Dot::default(),
method: Ident::new(op, Span::call_site()),
turbofish: None,
paren_token: Paren::default(),
args: Punctuated::default(),
})
}
fn try_op_binary(op: &str, left: Box<Expr>, right: Expr) -> Expr {
use proc_macro2::{Ident, Span};
Expr::Try(ExprTry {
attrs: vec![],
expr: Box::new(Expr::MethodCall(ExprMethodCall {
attrs: vec![],
receiver: Box::new(Expr::Paren(ExprParen {
attrs: vec![],
paren_token: Paren::default(),
expr: left,
})),
dot_token: Dot::default(),
method: Ident::new(op, Span::call_site()),
turbofish: None,
paren_token: Paren::default(),
args: Punctuated::from_iter([right]),
})),
question_token: Question::default(),
})
}
fn try_op_unary(op: &str, expr: Box<Expr>) -> Expr {
use proc_macro2::{Ident, Span};
Expr::Try(ExprTry {
attrs: vec![],
expr: Box::new(Expr::MethodCall(ExprMethodCall {
attrs: vec![],
receiver: Box::new(Expr::Paren(ExprParen {
attrs: vec![],
paren_token: Paren::default(),
expr,
})),
dot_token: Dot::default(),
method: Ident::new(op, Span::call_site()),
turbofish: None,
paren_token: Paren::default(),
args: Punctuated::default(),
})),
question_token: Question::default(),
})
}