use quote::ToTokens;
use super::ast::*;
use crate::SourceResult;
pub trait ToPure {
type Output;
fn to_pure(&self) -> Self::Output;
}
fn abi_to_string(abi: &syn::Abi) -> String {
abi.name
.as_ref()
.map(|lit| lit.value())
.unwrap_or_else(|| "C".to_string()) }
fn label_to_string(label: &syn::Label) -> String {
label.name.ident.to_string()
}
fn infer_async_from_return_type(ret: &syn::ReturnType) -> bool {
let ty = match ret {
syn::ReturnType::Default => return false,
syn::ReturnType::Type(_, ty) => ty,
};
let ty_str = ty.to_token_stream().to_string();
is_pinned_boxed_future(&ty_str)
}
fn is_pinned_boxed_future(ty_str: &str) -> bool {
let normalized: String = ty_str.chars().filter(|c| !c.is_whitespace()).collect();
normalized.starts_with("Pin<Box<dynFuture<")
|| normalized.starts_with("::core::pin::Pin<Box<dynFuture<")
|| normalized.starts_with("core::pin::Pin<Box<dynFuture<")
|| normalized.starts_with("std::pin::Pin<Box<dynFuture<")
|| normalized.starts_with("::std::pin::Pin<Box<dynFuture<")
}
impl PureFile {
pub fn from_source(source: &str) -> SourceResult<Self> {
let file = syn::parse_file(source)?;
Ok(file.to_pure())
}
}
impl ToPure for syn::File {
type Output = PureFile;
fn to_pure(&self) -> PureFile {
PureFile {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
items: self.items.iter().map(|i| i.to_pure()).collect(),
}
}
}
impl ToPure for syn::Attribute {
type Output = PureAttribute;
fn to_pure(&self) -> PureAttribute {
let meta = match &self.meta {
syn::Meta::Path(_) => PureAttrMeta::Path,
syn::Meta::List(list) => {
PureAttrMeta::List(list.tokens.to_string())
}
syn::Meta::NameValue(nv) => {
PureAttrMeta::NameValue(nv.value.to_token_stream().to_string())
}
};
PureAttribute {
path: path_to_string(self.path()),
meta,
is_inner: matches!(self.style, syn::AttrStyle::Inner(_)),
}
}
}
impl ToPure for syn::Item {
type Output = PureItem;
fn to_pure(&self) -> PureItem {
match self {
syn::Item::Use(u) => PureItem::Use(u.to_pure()),
syn::Item::Fn(f) => PureItem::Fn(f.to_pure()),
syn::Item::Struct(s) => PureItem::Struct(s.to_pure()),
syn::Item::Enum(e) => PureItem::Enum(e.to_pure()),
syn::Item::Impl(i) => PureItem::Impl(i.to_pure()),
syn::Item::Const(c) => PureItem::Const(c.to_pure()),
syn::Item::Static(s) => PureItem::Static(s.to_pure()),
syn::Item::Type(t) => PureItem::Type(t.to_pure()),
syn::Item::Mod(m) => PureItem::Mod(m.to_pure()),
syn::Item::Trait(t) => PureItem::Trait(t.to_pure()),
syn::Item::Macro(m) => PureItem::Macro(m.to_pure()),
other => PureItem::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::ItemUse {
type Output = PureUse;
fn to_pure(&self) -> PureUse {
PureUse {
vis: self.vis.to_pure(),
tree: self.tree.to_pure(),
}
}
}
impl ToPure for syn::UseTree {
type Output = PureUseTree;
fn to_pure(&self) -> PureUseTree {
match self {
syn::UseTree::Path(p) => PureUseTree::Path {
path: p.ident.to_string(),
tree: Box::new(p.tree.to_pure()),
},
syn::UseTree::Name(n) => PureUseTree::Name(n.ident.to_string()),
syn::UseTree::Rename(r) => PureUseTree::Rename {
name: r.ident.to_string(),
rename: r.rename.to_string(),
},
syn::UseTree::Glob(_) => PureUseTree::Glob,
syn::UseTree::Group(g) => {
PureUseTree::Group(g.items.iter().map(|t| t.to_pure()).collect())
}
}
}
}
impl ToPure for syn::Visibility {
type Output = PureVis;
fn to_pure(&self) -> PureVis {
match self {
syn::Visibility::Public(_) => PureVis::Public,
syn::Visibility::Restricted(r) => {
let path = path_to_string(&r.path);
match path.as_str() {
"crate" => PureVis::Crate,
"super" => PureVis::Super,
_ => PureVis::In(path),
}
}
syn::Visibility::Inherited => PureVis::Private,
}
}
}
impl ToPure for syn::ItemFn {
type Output = PureFn;
fn to_pure(&self) -> PureFn {
let is_async = self.sig.asyncness.is_some();
let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
PureFn {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
is_async,
is_async_inferred,
is_const: self.sig.constness.is_some(),
is_unsafe: self.sig.unsafety.is_some(),
abi: self.sig.abi.as_ref().map(abi_to_string),
name: self.sig.ident.to_string(),
generics: self.sig.generics.to_pure(),
params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
ret: match &self.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
},
body: self.block.to_pure(),
}
}
}
impl ToPure for syn::FnArg {
type Output = PureParam;
fn to_pure(&self) -> PureParam {
match self {
syn::FnArg::Receiver(r) => PureParam::SelfValue {
is_ref: r.reference.is_some(),
is_mut: r.mutability.is_some(),
},
syn::FnArg::Typed(t) => PureParam::Typed {
name: pat_to_name(&t.pat),
ty: t.ty.to_pure(),
},
}
}
}
impl ToPure for syn::Generics {
type Output = PureGenerics;
fn to_pure(&self) -> PureGenerics {
PureGenerics {
params: self.params.iter().map(|p| p.to_pure()).collect(),
where_clause: self
.where_clause
.as_ref()
.map(|w| {
w.predicates
.iter()
.map(|p| p.to_token_stream().to_string())
.collect()
})
.unwrap_or_default(),
}
}
}
impl ToPure for syn::GenericParam {
type Output = PureGenericParam;
fn to_pure(&self) -> PureGenericParam {
match self {
syn::GenericParam::Type(t) => PureGenericParam::Type {
name: t.ident.to_string(),
bounds: t
.bounds
.iter()
.map(|b| b.to_token_stream().to_string())
.collect(),
},
syn::GenericParam::Lifetime(l) => PureGenericParam::Lifetime {
name: l.lifetime.to_string(),
bounds: l.bounds.iter().map(|b| b.to_string()).collect(),
},
syn::GenericParam::Const(c) => PureGenericParam::Const {
name: c.ident.to_string(),
ty: c.ty.to_token_stream().to_string(),
},
}
}
}
impl ToPure for syn::Block {
type Output = PureBlock;
fn to_pure(&self) -> PureBlock {
PureBlock {
stmts: self.stmts.iter().map(|s| s.to_pure()).collect(),
}
}
}
impl ToPure for syn::Stmt {
type Output = PureStmt;
fn to_pure(&self) -> PureStmt {
match self {
syn::Stmt::Local(l) => {
let (pattern, ty) = match &l.pat {
syn::Pat::Type(pt) => (pt.pat.to_pure(), Some(pt.ty.to_pure())),
other => (other.to_pure(), None),
};
PureStmt::Local {
pattern,
ty,
init: l.init.as_ref().map(|i| i.expr.to_pure()),
}
}
syn::Stmt::Item(i) => PureStmt::Item(Box::new(i.to_pure())),
syn::Stmt::Expr(e, semi) => {
if semi.is_some() {
PureStmt::Semi(e.to_pure())
} else {
PureStmt::Expr(e.to_pure())
}
}
syn::Stmt::Macro(m) => PureStmt::Semi(PureExpr::Macro {
name: path_to_string(&m.mac.path),
delimiter: match m.mac.delimiter {
syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
},
tokens: m.mac.tokens.to_string(),
}),
}
}
}
impl ToPure for syn::Pat {
type Output = PurePattern;
fn to_pure(&self) -> PurePattern {
match self {
syn::Pat::Ident(i) => PurePattern::Ident {
name: i.ident.to_string(),
is_mut: i.mutability.is_some(),
},
syn::Pat::Wild(_) => PurePattern::Wild,
syn::Pat::Tuple(t) => PurePattern::Tuple(t.elems.iter().map(|p| p.to_pure()).collect()),
syn::Pat::TupleStruct(ts) => PurePattern::Struct {
path: path_to_string(&ts.path),
fields: ts
.elems
.iter()
.enumerate()
.map(|(i, p)| (i.to_string(), p.to_pure()))
.collect(),
rest: false,
},
syn::Pat::Struct(s) => PurePattern::Struct {
path: path_to_string(&s.path),
fields: s
.fields
.iter()
.map(|f| {
let name = f.member.to_token_stream().to_string();
(name, f.pat.to_pure())
})
.collect(),
rest: s.rest.is_some(),
},
syn::Pat::Reference(r) => PurePattern::Ref {
is_mut: r.mutability.is_some(),
pattern: Box::new(r.pat.to_pure()),
},
syn::Pat::Lit(l) => PurePattern::Lit(l.lit.to_token_stream().to_string()),
syn::Pat::Or(o) => PurePattern::Or(o.cases.iter().map(|p| p.to_pure()).collect()),
syn::Pat::Type(t) => t.pat.to_pure(),
syn::Pat::Path(p) => PurePattern::Path(path_to_string(&p.path)),
syn::Pat::Range(r) => PurePattern::Range {
start: r.start.as_ref().map(|e| e.to_token_stream().to_string()),
end: r.end.as_ref().map(|e| e.to_token_stream().to_string()),
inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
},
syn::Pat::Slice(s) => PurePattern::Slice(s.elems.iter().map(|p| p.to_pure()).collect()),
syn::Pat::Rest(_) => PurePattern::Rest,
syn::Pat::Paren(p) => p.pat.to_pure(),
other => PurePattern::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::Expr {
type Output = PureExpr;
fn to_pure(&self) -> PureExpr {
match self {
syn::Expr::Lit(l) => PureExpr::Lit(l.lit.to_token_stream().to_string()),
syn::Expr::Path(p) => PureExpr::Path(path_to_string(&p.path)),
syn::Expr::Binary(b) => PureExpr::Binary {
op: b.op.to_token_stream().to_string(),
left: Box::new(b.left.to_pure()),
right: Box::new(b.right.to_pure()),
},
syn::Expr::Unary(u) => PureExpr::Unary {
op: u.op.to_token_stream().to_string(),
expr: Box::new(u.expr.to_pure()),
},
syn::Expr::Call(c) => PureExpr::Call {
func: Box::new(c.func.to_pure()),
args: c.args.iter().map(|a| a.to_pure()).collect(),
},
syn::Expr::MethodCall(m) => PureExpr::MethodCall {
receiver: Box::new(m.receiver.to_pure()),
method: m.method.to_string(),
turbofish: m
.turbofish
.as_ref()
.map(|t| t.args.to_token_stream().to_string()),
args: m.args.iter().map(|a| a.to_pure()).collect(),
},
syn::Expr::Field(f) => PureExpr::Field {
expr: Box::new(f.base.to_pure()),
field: f.member.to_token_stream().to_string(),
},
syn::Expr::Index(i) => PureExpr::Index {
expr: Box::new(i.expr.to_pure()),
index: Box::new(i.index.to_pure()),
},
syn::Expr::Block(b) => PureExpr::Block {
label: b.label.as_ref().map(label_to_string),
block: b.block.to_pure(),
},
syn::Expr::If(i) => PureExpr::If {
cond: Box::new(i.cond.to_pure()),
then_branch: i.then_branch.to_pure(),
else_branch: i.else_branch.as_ref().map(|(_, e)| Box::new(e.to_pure())),
},
syn::Expr::Match(m) => PureExpr::Match {
expr: Box::new(m.expr.to_pure()),
arms: m.arms.iter().map(|a| a.to_pure()).collect(),
},
syn::Expr::Loop(l) => PureExpr::Loop {
label: l.label.as_ref().map(label_to_string),
body: l.body.to_pure(),
},
syn::Expr::While(w) => PureExpr::While {
label: w.label.as_ref().map(label_to_string),
cond: Box::new(w.cond.to_pure()),
body: w.body.to_pure(),
},
syn::Expr::ForLoop(f) => PureExpr::For {
label: f.label.as_ref().map(label_to_string),
pat: f.pat.to_pure(),
expr: Box::new(f.expr.to_pure()),
body: f.body.to_pure(),
},
syn::Expr::Return(r) => {
PureExpr::Return(r.expr.as_ref().map(|e| Box::new(e.to_pure())))
}
syn::Expr::Break(b) => PureExpr::Break {
label: b.label.as_ref().map(|l| l.ident.to_string()),
expr: b.expr.as_ref().map(|e| Box::new(e.to_pure())),
},
syn::Expr::Continue(c) => PureExpr::Continue {
label: c.label.as_ref().map(|l| l.ident.to_string()),
},
syn::Expr::Closure(c) => PureExpr::Closure {
is_async: c.asyncness.is_some(),
is_move: c.capture.is_some(),
params: c
.inputs
.iter()
.map(|p| match p {
syn::Pat::Type(pt) => {
PureClosureParam::typed(pt.pat.to_pure(), pt.ty.to_pure())
}
other => PureClosureParam::untyped(other.to_pure()),
})
.collect(),
ret: match &c.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
},
body: Box::new(c.body.to_pure()),
},
syn::Expr::Struct(s) => PureExpr::Struct {
path: path_to_string(&s.path),
fields: s
.fields
.iter()
.map(|f| {
let name = f.member.to_token_stream().to_string();
(name, f.expr.to_pure())
})
.collect(),
},
syn::Expr::Tuple(t) => PureExpr::Tuple(t.elems.iter().map(|e| e.to_pure()).collect()),
syn::Expr::Array(a) => PureExpr::Array(a.elems.iter().map(|e| e.to_pure()).collect()),
syn::Expr::Reference(r) => PureExpr::Ref {
is_mut: r.mutability.is_some(),
expr: Box::new(r.expr.to_pure()),
},
syn::Expr::Macro(m) => PureExpr::Macro {
name: path_to_string(&m.mac.path),
delimiter: match m.mac.delimiter {
syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
},
tokens: m.mac.tokens.to_string(),
},
syn::Expr::Await(a) => PureExpr::Await(Box::new(a.base.to_pure())),
syn::Expr::Try(t) => PureExpr::Try(Box::new(t.expr.to_pure())),
syn::Expr::Paren(p) => p.expr.to_pure(),
syn::Expr::Assign(a) => PureExpr::Binary {
op: "=".to_string(),
left: Box::new(a.left.to_pure()),
right: Box::new(a.right.to_pure()),
},
syn::Expr::Range(r) => PureExpr::Range {
start: r.start.as_ref().map(|e| Box::new(e.to_pure())),
end: r.end.as_ref().map(|e| Box::new(e.to_pure())),
inclusive: matches!(r.limits, syn::RangeLimits::Closed(_)),
},
syn::Expr::Cast(c) => PureExpr::Cast {
expr: Box::new(c.expr.to_pure()),
ty: c.ty.to_pure(),
},
syn::Expr::Let(l) => PureExpr::Let {
pattern: l.pat.to_pure(),
expr: Box::new(l.expr.to_pure()),
},
syn::Expr::Async(a) => PureExpr::Async {
is_move: a.capture.is_some(),
body: a.block.to_pure(),
},
syn::Expr::Unsafe(u) => PureExpr::Unsafe(u.block.to_pure()),
syn::Expr::Repeat(r) => PureExpr::Repeat {
expr: Box::new(r.expr.to_pure()),
len: Box::new(r.len.to_pure()),
},
other => PureExpr::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::Arm {
type Output = PureMatchArm;
fn to_pure(&self) -> PureMatchArm {
PureMatchArm {
pattern: self.pat.to_pure(),
guard: self.guard.as_ref().map(|(_, e)| e.to_pure()),
body: self.body.to_pure(),
}
}
}
impl ToPure for syn::Type {
type Output = PureType;
fn to_pure(&self) -> PureType {
match self {
syn::Type::Path(p) => PureType::Path(path_to_string(&p.path)),
syn::Type::Reference(r) => PureType::Ref {
lifetime: r.lifetime.as_ref().map(|l| l.to_string()),
is_mut: r.mutability.is_some(),
ty: Box::new(r.elem.to_pure()),
},
syn::Type::Tuple(t) => PureType::Tuple(t.elems.iter().map(|t| t.to_pure()).collect()),
syn::Type::Array(a) => PureType::Array {
ty: Box::new(a.elem.to_pure()),
len: a.len.to_token_stream().to_string(),
},
syn::Type::Slice(s) => PureType::Slice(Box::new(s.elem.to_pure())),
syn::Type::BareFn(f) => PureType::Fn {
params: f.inputs.iter().map(|i| i.ty.to_pure()).collect(),
ret: match &f.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(Box::new(ty.to_pure())),
},
},
syn::Type::ImplTrait(i) => PureType::ImplTrait(
i.bounds
.iter()
.map(|b| b.to_token_stream().to_string())
.collect(),
),
syn::Type::TraitObject(t) => PureType::TraitObject(
t.bounds
.iter()
.map(|b| b.to_token_stream().to_string())
.collect(),
),
syn::Type::Infer(_) => PureType::Infer,
syn::Type::Never(_) => PureType::Never,
syn::Type::Paren(p) => p.elem.to_pure(),
other => PureType::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::ItemStruct {
type Output = PureStruct;
fn to_pure(&self) -> PureStruct {
PureStruct {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
generics: self.generics.to_pure(),
fields: self.fields.to_pure(),
}
}
}
impl ToPure for syn::Fields {
type Output = PureFields;
fn to_pure(&self) -> PureFields {
match self {
syn::Fields::Named(n) => {
PureFields::Named(n.named.iter().map(|f| f.to_pure()).collect())
}
syn::Fields::Unnamed(u) => {
PureFields::Tuple(u.unnamed.iter().map(|f| f.ty.to_pure()).collect())
}
syn::Fields::Unit => PureFields::Unit,
}
}
}
impl ToPure for syn::Field {
type Output = PureField;
fn to_pure(&self) -> PureField {
PureField {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_default(),
ty: self.ty.to_pure(),
}
}
}
impl ToPure for syn::ItemEnum {
type Output = PureEnum;
fn to_pure(&self) -> PureEnum {
PureEnum {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
generics: self.generics.to_pure(),
variants: self.variants.iter().map(|v| v.to_pure()).collect(),
}
}
}
impl ToPure for syn::Variant {
type Output = PureVariant;
fn to_pure(&self) -> PureVariant {
PureVariant {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
name: self.ident.to_string(),
fields: self.fields.to_pure(),
discriminant: self
.discriminant
.as_ref()
.map(|(_, e)| e.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::ItemImpl {
type Output = PureImpl;
fn to_pure(&self) -> PureImpl {
PureImpl {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
generics: self.generics.to_pure(),
is_unsafe: self.unsafety.is_some(),
trait_: self.trait_.as_ref().map(|(_, p, _)| path_to_string(p)),
self_ty: self.self_ty.to_token_stream().to_string(),
items: self.items.iter().map(|i| i.to_pure()).collect(),
}
}
}
impl ToPure for syn::ImplItem {
type Output = PureImplItem;
fn to_pure(&self) -> PureImplItem {
match self {
syn::ImplItem::Fn(f) => PureImplItem::Fn(f.to_pure()),
syn::ImplItem::Const(c) => PureImplItem::Const(c.to_pure()),
syn::ImplItem::Type(t) => PureImplItem::Type(t.to_pure()),
other => PureImplItem::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::ImplItemFn {
type Output = PureFn;
fn to_pure(&self) -> PureFn {
let is_async = self.sig.asyncness.is_some();
let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
PureFn {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
is_async,
is_async_inferred,
is_const: self.sig.constness.is_some(),
is_unsafe: self.sig.unsafety.is_some(),
abi: self.sig.abi.as_ref().map(abi_to_string),
name: self.sig.ident.to_string(),
generics: self.sig.generics.to_pure(),
params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
ret: match &self.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
},
body: self.block.to_pure(),
}
}
}
impl ToPure for syn::ImplItemConst {
type Output = PureConst;
fn to_pure(&self) -> PureConst {
PureConst {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
ty: self.ty.to_pure(),
value: Some(self.expr.to_pure()),
}
}
}
impl ToPure for syn::ImplItemType {
type Output = PureTypeAlias;
fn to_pure(&self) -> PureTypeAlias {
PureTypeAlias {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
generics: self.generics.to_pure(),
ty: self.ty.to_pure(),
}
}
}
impl ToPure for syn::ItemConst {
type Output = PureConst;
fn to_pure(&self) -> PureConst {
PureConst {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
ty: self.ty.to_pure(),
value: Some(self.expr.to_pure()),
}
}
}
impl ToPure for syn::ItemStatic {
type Output = PureStatic;
fn to_pure(&self) -> PureStatic {
PureStatic {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
is_mut: matches!(self.mutability, syn::StaticMutability::Mut(_)),
name: self.ident.to_string(),
ty: self.ty.to_pure(),
value: self.expr.to_pure(),
}
}
}
impl ToPure for syn::ItemType {
type Output = PureTypeAlias;
fn to_pure(&self) -> PureTypeAlias {
PureTypeAlias {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
generics: self.generics.to_pure(),
ty: self.ty.to_pure(),
}
}
}
impl ToPure for syn::ItemMod {
type Output = PureMod;
fn to_pure(&self) -> PureMod {
let items = if let Some((_, items)) = &self.content {
items.iter().map(|item| item.to_pure()).collect()
} else {
Vec::new()
};
PureMod {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
name: self.ident.to_string(),
items,
}
}
}
impl ToPure for syn::ItemTrait {
type Output = PureTrait;
fn to_pure(&self) -> PureTrait {
PureTrait {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: self.vis.to_pure(),
is_unsafe: self.unsafety.is_some(),
is_auto: self.auto_token.is_some(),
name: self.ident.to_string(),
generics: self.generics.to_pure(),
supertraits: self
.supertraits
.iter()
.map(|b| b.to_token_stream().to_string())
.collect(),
items: self.items.iter().map(|i| i.to_pure()).collect(),
}
}
}
impl ToPure for syn::TraitItem {
type Output = PureTraitItem;
fn to_pure(&self) -> PureTraitItem {
match self {
syn::TraitItem::Fn(f) => PureTraitItem::Fn(f.to_pure()),
syn::TraitItem::Const(c) => PureTraitItem::Const(c.to_pure()),
syn::TraitItem::Type(t) => PureTraitItem::Type {
name: t.ident.to_string(),
bounds: t
.bounds
.iter()
.map(|b| b.to_token_stream().to_string())
.collect(),
default: t.default.as_ref().map(|(_, ty)| ty.to_pure()),
},
other => PureTraitItem::Other(other.to_token_stream().to_string()),
}
}
}
impl ToPure for syn::TraitItemFn {
type Output = PureFn;
fn to_pure(&self) -> PureFn {
let is_async = self.sig.asyncness.is_some();
let is_async_inferred = !is_async && infer_async_from_return_type(&self.sig.output);
PureFn {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: PureVis::Private, is_async,
is_async_inferred,
is_const: self.sig.constness.is_some(),
is_unsafe: self.sig.unsafety.is_some(),
abi: self.sig.abi.as_ref().map(abi_to_string),
name: self.sig.ident.to_string(),
generics: self.sig.generics.to_pure(),
params: self.sig.inputs.iter().map(|p| p.to_pure()).collect(),
ret: match &self.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(ty.to_pure()),
},
body: self
.default
.as_ref()
.map(|b| b.to_pure())
.unwrap_or_default(),
}
}
}
impl ToPure for syn::TraitItemConst {
type Output = PureConst;
fn to_pure(&self) -> PureConst {
PureConst {
attrs: self.attrs.iter().map(|a| a.to_pure()).collect(),
vis: PureVis::Private,
name: self.ident.to_string(),
ty: self.ty.to_pure(),
value: self.default.as_ref().map(|(_, e)| e.to_pure()),
}
}
}
impl ToPure for syn::ItemMacro {
type Output = PureMacro;
fn to_pure(&self) -> PureMacro {
PureMacro {
path: path_to_string(&self.mac.path),
delimiter: match self.mac.delimiter {
syn::MacroDelimiter::Paren(_) => MacroDelimiter::Paren,
syn::MacroDelimiter::Brace(_) => MacroDelimiter::Brace,
syn::MacroDelimiter::Bracket(_) => MacroDelimiter::Bracket,
},
tokens: self.mac.tokens.to_string(),
}
}
}
fn path_to_string(path: &syn::Path) -> String {
path.segments
.iter()
.map(|s| {
let ident = s.ident.to_string();
match &s.arguments {
syn::PathArguments::None => ident,
syn::PathArguments::AngleBracketed(args) => {
format!("{}{}", ident, args.to_token_stream())
}
syn::PathArguments::Parenthesized(args) => {
format!("{}{}", ident, args.to_token_stream())
}
}
})
.collect::<Vec<_>>()
.join("::")
}
fn pat_to_name(pat: &syn::Pat) -> String {
match pat {
syn::Pat::Ident(i) => i.ident.to_string(),
syn::Pat::Type(t) => pat_to_name(&t.pat),
syn::Pat::Tuple(t) => {
format!("_tuple{}", t.elems.len())
}
syn::Pat::TupleStruct(ts) => {
ts.path
.segments
.last()
.map(|s| s.ident.to_string())
.unwrap_or_else(|| "_pattern".to_string())
}
syn::Pat::Struct(s) => {
s.path
.segments
.last()
.map(|seg| seg.ident.to_string())
.unwrap_or_else(|| "_pattern".to_string())
}
syn::Pat::Wild(_) => "_".to_string(),
syn::Pat::Rest(_) => "_rest".to_string(),
syn::Pat::Slice(_) => "_slice".to_string(),
syn::Pat::Reference(r) => pat_to_name(&r.pat),
syn::Pat::Or(o) => {
o.cases
.first()
.map(pat_to_name)
.unwrap_or_else(|| "_or".to_string())
}
_other => "_param".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_fn() {
let pure = PureFile::from_source("fn main() {}").unwrap();
assert_eq!(pure.functions().len(), 1);
assert_eq!(pure.functions()[0].name, "main");
}
#[test]
fn test_parse_struct() {
let pure = PureFile::from_source(
r#"
struct Point {
x: i32,
y: i32,
}
"#,
)
.unwrap();
assert_eq!(pure.structs().len(), 1);
let s = &pure.structs()[0];
assert_eq!(s.name, "Point");
if let PureFields::Named(fields) = &s.fields {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name, "x");
assert_eq!(fields[1].name, "y");
} else {
panic!("Expected named fields");
}
}
#[test]
fn test_parse_use() {
let pure = PureFile::from_source("use std::io;").unwrap();
assert_eq!(pure.uses().len(), 1);
}
#[test]
fn test_thread_safe() {
use std::sync::Arc;
use std::thread;
let pure = PureFile::from_source(
r#"
fn foo() {}
fn bar() {}
"#,
)
.unwrap();
let shared = Arc::new(pure);
let s1 = Arc::clone(&shared);
let s2 = Arc::clone(&shared);
let h1 = thread::spawn(move || s1.functions().len());
let h2 = thread::spawn(move || s2.find_fn("foo").map(|f| f.name.clone()));
assert_eq!(h1.join().unwrap(), 2);
assert_eq!(h2.join().unwrap(), Some("foo".to_string()));
}
#[test]
fn test_expr_binary() {
let pure = PureFile::from_source("fn f() { let _ = 1 + 2; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(expr), ..
} = &f.body.stmts[0]
{
if let PureExpr::Binary { op, .. } = expr {
assert_eq!(op, "+");
} else {
panic!("Expected Binary expr");
}
} else {
panic!("Expected Local stmt");
}
}
#[test]
fn test_expr_unary() {
let pure = PureFile::from_source("fn f() { let _ = -x; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(expr), ..
} = &f.body.stmts[0]
{
if let PureExpr::Unary { op, .. } = expr {
assert_eq!(op, "-");
} else {
panic!("Expected Unary expr");
}
} else {
panic!("Expected Local stmt");
}
}
#[test]
fn test_expr_method_call() {
let pure = PureFile::from_source("fn f() { x.foo(1, 2); }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Semi(PureExpr::MethodCall { method, args, .. }) = &f.body.stmts[0] {
assert_eq!(method, "foo");
assert_eq!(args.len(), 2);
} else {
panic!("Expected MethodCall expr");
}
}
#[test]
fn test_expr_method_call_with_turbofish() {
let pure = PureFile::from_source("fn f() { x.collect::<Vec<_>>(); }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Semi(PureExpr::MethodCall {
method, turbofish, ..
}) = &f.body.stmts[0]
{
assert_eq!(method, "collect");
assert!(turbofish.is_some());
assert!(turbofish.as_ref().unwrap().contains("Vec"));
} else {
panic!("Expected MethodCall expr with turbofish");
}
}
#[test]
fn test_expr_closure() {
let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(expr), ..
} = &f.body.stmts[0]
{
if let PureExpr::Closure {
params, is_move, ..
} = expr
{
assert!(!is_move);
assert_eq!(params.len(), 1);
} else {
panic!("Expected Closure expr");
}
} else {
panic!("Expected Local stmt");
}
}
#[test]
fn test_expr_closure_move() {
let pure = PureFile::from_source("fn f() { let _ = move |x| x; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Closure { is_move, .. }),
..
} = &f.body.stmts[0]
{
assert!(is_move);
} else {
panic!("Expected move Closure");
}
}
#[test]
fn test_expr_async_closure() {
let pure = PureFile::from_source("fn f() { let _ = async || {}; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Closure { is_async, .. }),
..
} = &f.body.stmts[0]
{
assert!(is_async);
} else {
panic!("Expected async Closure");
}
}
#[test]
fn test_expr_closure_typed_params() {
let pure =
PureFile::from_source("fn f() { let _ = |x: i32, y: String| -> bool { true }; }")
.unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(expr), ..
} = &f.body.stmts[0]
{
if let PureExpr::Closure { params, ret, .. } = expr {
assert_eq!(params.len(), 2);
assert!(params[0].ty.is_some());
assert_eq!(
params[0].ty.as_ref().unwrap(),
&PureType::Path("i32".to_string())
);
assert!(params[1].ty.is_some());
assert_eq!(
params[1].ty.as_ref().unwrap(),
&PureType::Path("String".to_string())
);
assert!(ret.is_some());
assert_eq!(ret.as_ref().unwrap(), &PureType::Path("bool".to_string()));
} else {
panic!("Expected Closure expr");
}
} else {
panic!("Expected Local stmt");
}
}
#[test]
fn test_expr_closure_untyped_params_no_ret() {
let pure = PureFile::from_source("fn f() { let _ = |x| x + 1; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Closure { params, ret, .. }),
..
} = &f.body.stmts[0]
{
assert_eq!(params.len(), 1);
assert!(params[0].ty.is_none());
assert!(ret.is_none());
} else {
panic!("Expected Closure");
}
}
#[test]
fn test_expr_if() {
let pure = PureFile::from_source("fn f() { if true { 1 } else { 2 } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::If { else_branch, .. }) = &f.body.stmts[0] {
assert!(else_branch.is_some());
} else {
panic!("Expected If expr");
}
}
#[test]
fn test_expr_match() {
let pure =
PureFile::from_source(r#"fn f() { match x { 1 => "one", _ => "other" } }"#).unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
assert_eq!(arms.len(), 2);
} else {
panic!("Expected Match expr");
}
}
#[test]
fn test_expr_for_loop() {
let pure = PureFile::from_source("fn f() { for i in 0..10 {} }").unwrap();
let f = &pure.functions()[0];
match &f.body.stmts[0] {
PureStmt::Expr(PureExpr::For { pat, .. })
| PureStmt::Semi(PureExpr::For { pat, .. }) => {
if let PurePattern::Ident { name, .. } = pat {
assert_eq!(name, "i");
} else {
panic!("Expected Ident pattern");
}
}
_ => panic!("Expected For expr"),
}
}
#[test]
fn test_expr_while() {
let pure = PureFile::from_source("fn f() { while x > 0 { x -= 1; } }").unwrap();
let f = &pure.functions()[0];
match &f.body.stmts[0] {
PureStmt::Expr(PureExpr::While { .. }) | PureStmt::Semi(PureExpr::While { .. }) => {}
_ => panic!("Expected While expr"),
}
}
#[test]
fn test_expr_loop() {
let pure = PureFile::from_source("fn f() { loop { break; } }").unwrap();
let f = &pure.functions()[0];
match &f.body.stmts[0] {
PureStmt::Expr(PureExpr::Loop { .. }) | PureStmt::Semi(PureExpr::Loop { .. }) => {}
_ => panic!("Expected Loop expr"),
}
}
#[test]
fn test_expr_return() {
let pure = PureFile::from_source("fn f() -> i32 { return 42; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Semi(PureExpr::Return(Some(_))) = &f.body.stmts[0] {
} else {
panic!("Expected Return expr");
}
}
#[test]
fn test_expr_struct() {
let pure = PureFile::from_source("fn f() { Point { x: 1, y: 2 } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Struct { path, fields }) = &f.body.stmts[0] {
assert_eq!(path, "Point");
assert_eq!(fields.len(), 2);
} else {
panic!("Expected Struct expr");
}
}
#[test]
fn test_expr_tuple() {
let pure = PureFile::from_source("fn f() { (1, 2, 3) }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Tuple(elems)) = &f.body.stmts[0] {
assert_eq!(elems.len(), 3);
} else {
panic!("Expected Tuple expr");
}
}
#[test]
fn test_expr_array() {
let pure = PureFile::from_source("fn f() { [1, 2, 3] }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Array(elems)) = &f.body.stmts[0] {
assert_eq!(elems.len(), 3);
} else {
panic!("Expected Array expr");
}
}
#[test]
fn test_expr_repeat() {
let pure = PureFile::from_source("fn f() { [0; 10] }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Repeat { .. }) = &f.body.stmts[0] {
} else {
panic!("Expected Repeat expr");
}
}
#[test]
fn test_expr_reference() {
let pure = PureFile::from_source("fn f() { let _ = &x; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Ref { is_mut, .. }),
..
} = &f.body.stmts[0]
{
assert!(!is_mut);
} else {
panic!("Expected Ref expr");
}
}
#[test]
fn test_expr_reference_mut() {
let pure = PureFile::from_source("fn f() { let _ = &mut x; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Ref { is_mut, .. }),
..
} = &f.body.stmts[0]
{
assert!(is_mut);
} else {
panic!("Expected mut Ref expr");
}
}
#[test]
fn test_expr_await() {
let pure = PureFile::from_source("async fn f() { x.await }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Await(_)) = &f.body.stmts[0] {
} else {
panic!("Expected Await expr");
}
}
#[test]
fn test_expr_try() {
let pure = PureFile::from_source("fn f() -> Result<(), ()> { x?; Ok(()) }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Semi(PureExpr::Try(_)) = &f.body.stmts[0] {
} else {
panic!("Expected Try expr");
}
}
#[test]
fn test_expr_range() {
let pure = PureFile::from_source("fn f() { let _ = 0..10; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init:
Some(PureExpr::Range {
start,
end,
inclusive,
}),
..
} = &f.body.stmts[0]
{
assert!(start.is_some());
assert!(end.is_some());
assert!(!inclusive);
} else {
panic!("Expected Range expr");
}
}
#[test]
fn test_expr_range_inclusive() {
let pure = PureFile::from_source("fn f() { let _ = 0..=10; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Range { inclusive, .. }),
..
} = &f.body.stmts[0]
{
assert!(inclusive);
} else {
panic!("Expected inclusive Range expr");
}
}
#[test]
fn test_expr_cast() {
let pure = PureFile::from_source("fn f() { let _ = x as u32; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
init: Some(PureExpr::Cast { ty, .. }),
..
} = &f.body.stmts[0]
{
if let PureType::Path(p) = ty {
assert_eq!(p, "u32");
} else {
panic!("Expected Path type");
}
} else {
panic!("Expected Cast expr");
}
}
#[test]
fn test_expr_let() {
let pure = PureFile::from_source("fn f() { if let Some(x) = y { } }").unwrap();
let f = &pure.functions()[0];
let if_expr = match &f.body.stmts[0] {
PureStmt::Expr(e) | PureStmt::Semi(e) => e,
_ => panic!("Expected expression statement"),
};
if let PureExpr::If { cond, .. } = if_expr {
if let PureExpr::Let { pattern, .. } = cond.as_ref() {
if let PurePattern::Struct { path, .. } = pattern {
assert_eq!(path, "Some");
} else {
panic!("Expected Struct pattern");
}
} else {
panic!("Expected Let expr in condition");
}
} else {
panic!("Expected If expr");
}
}
#[test]
fn test_expr_async_block() {
let pure = PureFile::from_source("fn f() { async { 42 } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Async { is_move, .. }) = &f.body.stmts[0] {
assert!(!is_move);
} else {
panic!("Expected Async block");
}
}
#[test]
fn test_expr_unsafe_block() {
let pure = PureFile::from_source("fn f() { unsafe { dangerous() } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Unsafe(_)) = &f.body.stmts[0] {
} else {
panic!("Expected Unsafe block");
}
}
#[test]
fn test_expr_macro() {
let pure = PureFile::from_source("fn f() { println!(\"hello\"); }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Semi(PureExpr::Macro { name, .. }) = &f.body.stmts[0] {
assert_eq!(name, "println");
} else {
panic!("Expected Macro expr");
}
}
#[test]
fn test_pattern_ident() {
let pure = PureFile::from_source("fn f() { let x = 1; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Ident { name, is_mut },
..
} = &f.body.stmts[0]
{
assert_eq!(name, "x");
assert!(!is_mut);
} else {
panic!("Expected Ident pattern");
}
}
#[test]
fn test_pattern_ident_mut() {
let pure = PureFile::from_source("fn f() { let mut x = 1; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Ident { is_mut, .. },
..
} = &f.body.stmts[0]
{
assert!(is_mut);
} else {
panic!("Expected mut Ident pattern");
}
}
#[test]
fn test_pattern_wild() {
let pure = PureFile::from_source("fn f() { let _ = 1; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Wild,
..
} = &f.body.stmts[0]
{
} else {
panic!("Expected Wild pattern");
}
}
#[test]
fn test_pattern_tuple() {
let pure = PureFile::from_source("fn f() { let (a, b) = (1, 2); }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Tuple(elems),
..
} = &f.body.stmts[0]
{
assert_eq!(elems.len(), 2);
} else {
panic!("Expected Tuple pattern");
}
}
#[test]
fn test_pattern_struct() {
let pure = PureFile::from_source("fn f() { let Point { x, y } = p; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Struct { path, fields, rest },
..
} = &f.body.stmts[0]
{
assert_eq!(path, "Point");
assert_eq!(fields.len(), 2);
assert!(!rest);
} else {
panic!("Expected Struct pattern");
}
}
#[test]
fn test_pattern_struct_with_rest() {
let pure = PureFile::from_source("fn f() { let Point { x, .. } = p; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Struct { rest, .. },
..
} = &f.body.stmts[0]
{
assert!(rest);
} else {
panic!("Expected Struct pattern with rest");
}
}
#[test]
fn test_pattern_tuple_struct() {
let pure = PureFile::from_source("fn f() { let Some(x) = opt; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Struct { path, .. },
..
} = &f.body.stmts[0]
{
assert_eq!(path, "Some");
} else {
panic!("Expected TupleStruct pattern");
}
}
#[test]
fn test_pattern_reference() {
let pure = PureFile::from_source("fn f() { let &x = r; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Ref { is_mut, .. },
..
} = &f.body.stmts[0]
{
assert!(!is_mut);
} else {
panic!("Expected Ref pattern");
}
}
#[test]
fn test_pattern_or() {
let pure =
PureFile::from_source("fn f(x: i32) { match x { 1 | 2 | 3 => {} _ => {} } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
if let PurePattern::Or(cases) = &arms[0].pattern {
assert_eq!(cases.len(), 3);
} else {
panic!("Expected Or pattern");
}
} else {
panic!("Expected Match expr");
}
}
#[test]
fn test_pattern_range() {
let pure =
PureFile::from_source("fn f(x: i32) { match x { 0..=10 => {} _ => {} } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
if let PurePattern::Range { inclusive, .. } = &arms[0].pattern {
assert!(inclusive);
} else {
panic!("Expected Range pattern");
}
} else {
panic!("Expected Match expr");
}
}
#[test]
fn test_pattern_slice() {
let pure = PureFile::from_source("fn f() { let [a, b, c] = arr; }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Local {
pattern: PurePattern::Slice(elems),
..
} = &f.body.stmts[0]
{
assert_eq!(elems.len(), 3);
} else {
panic!("Expected Slice pattern");
}
}
#[test]
fn test_pattern_path() {
let pure = PureFile::from_source(
"fn f(x: std::cmp::Ordering) { match x { std::cmp::Ordering::Less => {} _ => {} } }",
)
.unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
if let PurePattern::Path(path) = &arms[0].pattern {
assert!(path.contains("Ordering"));
assert!(path.contains("Less"));
} else {
panic!("Expected Path pattern, got {:?}", arms[0].pattern);
}
} else {
panic!("Expected Match expr");
}
}
#[test]
fn test_type_path() {
let pure = PureFile::from_source("fn f(x: i32) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Path(p),
..
} = &f.params[0]
{
assert_eq!(p, "i32");
} else {
panic!("Expected Path type");
}
}
#[test]
fn test_type_reference() {
let pure = PureFile::from_source("fn f(x: &str) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Ref { is_mut, .. },
..
} = &f.params[0]
{
assert!(!is_mut);
} else {
panic!("Expected Ref type");
}
}
#[test]
fn test_type_reference_mut() {
let pure = PureFile::from_source("fn f(x: &mut String) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Ref { is_mut, .. },
..
} = &f.params[0]
{
assert!(is_mut);
} else {
panic!("Expected mut Ref type");
}
}
#[test]
fn test_type_reference_with_lifetime() {
let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Ref { lifetime, .. },
..
} = &f.params[0]
{
assert!(lifetime.is_some());
assert!(lifetime.as_ref().unwrap().contains("'a"));
} else {
panic!("Expected Ref type with lifetime");
}
}
#[test]
fn test_type_tuple() {
let pure = PureFile::from_source("fn f(x: (i32, i32)) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Tuple(elems),
..
} = &f.params[0]
{
assert_eq!(elems.len(), 2);
} else {
panic!("Expected Tuple type");
}
}
#[test]
fn test_type_array() {
let pure = PureFile::from_source("fn f(x: [i32; 10]) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Array { len, .. },
..
} = &f.params[0]
{
assert_eq!(len, "10");
} else {
panic!("Expected Array type");
}
}
#[test]
fn test_type_slice() {
let pure = PureFile::from_source("fn f(x: &[i32]) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Ref { ty, .. },
..
} = &f.params[0]
{
if let PureType::Slice(_) = ty.as_ref() {
} else {
panic!("Expected Slice type");
}
} else {
panic!("Expected Ref to Slice type");
}
}
#[test]
fn test_type_fn() {
let pure = PureFile::from_source("fn f(x: fn(i32) -> i32) {}").unwrap();
let f = &pure.functions()[0];
if let PureParam::Typed {
ty: PureType::Fn { params, ret },
..
} = &f.params[0]
{
assert_eq!(params.len(), 1);
assert!(ret.is_some());
} else {
panic!("Expected Fn type");
}
}
#[test]
fn test_type_impl_trait() {
let pure =
PureFile::from_source("fn f() -> impl Iterator<Item = i32> { todo!() }").unwrap();
let f = &pure.functions()[0];
if let Some(PureType::ImplTrait(bounds)) = &f.ret {
assert!(!bounds.is_empty());
} else {
panic!("Expected ImplTrait type");
}
}
#[test]
fn test_type_never() {
let pure = PureFile::from_source("fn f() -> ! { panic!() }").unwrap();
let f = &pure.functions()[0];
if let Some(PureType::Never) = &f.ret {
} else {
panic!("Expected Never type");
}
}
#[test]
fn test_generics_type_param() {
let pure = PureFile::from_source("fn f<T>(x: T) {}").unwrap();
let f = &pure.functions()[0];
assert_eq!(f.generics.params.len(), 1);
if let PureGenericParam::Type { name, .. } = &f.generics.params[0] {
assert_eq!(name, "T");
} else {
panic!("Expected Type param");
}
}
#[test]
fn test_generics_type_param_with_bound() {
let pure = PureFile::from_source("fn f<T: Clone>(x: T) {}").unwrap();
let f = &pure.functions()[0];
if let PureGenericParam::Type { bounds, .. } = &f.generics.params[0] {
assert!(!bounds.is_empty());
assert!(bounds[0].contains("Clone"));
} else {
panic!("Expected Type param with bound");
}
}
#[test]
fn test_generics_lifetime_param() {
let pure = PureFile::from_source("fn f<'a>(x: &'a str) {}").unwrap();
let f = &pure.functions()[0];
if let PureGenericParam::Lifetime { name, .. } = &f.generics.params[0] {
assert!(name.contains("'a"));
} else {
panic!("Expected Lifetime param");
}
}
#[test]
fn test_generics_const_param() {
let pure = PureFile::from_source("fn f<const N: usize>() {}").unwrap();
let f = &pure.functions()[0];
if let PureGenericParam::Const { name, ty } = &f.generics.params[0] {
assert_eq!(name, "N");
assert!(ty.contains("usize"));
} else {
panic!("Expected Const param");
}
}
#[test]
fn test_generics_where_clause() {
let pure = PureFile::from_source("fn f<T>(x: T) where T: Clone {}").unwrap();
let f = &pure.functions()[0];
assert!(!f.generics.where_clause.is_empty());
assert!(f.generics.where_clause[0].contains("Clone"));
}
#[test]
fn test_fn_async() {
let pure = PureFile::from_source("async fn f() {}").unwrap();
assert!(pure.functions()[0].is_async);
}
#[test]
fn test_fn_const() {
let pure = PureFile::from_source("const fn f() {}").unwrap();
assert!(pure.functions()[0].is_const);
}
#[test]
fn test_fn_unsafe() {
let pure = PureFile::from_source("unsafe fn f() {}").unwrap();
assert!(pure.functions()[0].is_unsafe);
}
#[test]
fn test_fn_self_param() {
let pure = PureFile::from_source("impl Foo { fn f(&self) {} }").unwrap();
let impls = pure.impls();
if let PureImplItem::Fn(f) = &impls[0].items[0] {
if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
assert!(is_ref);
assert!(!is_mut);
} else {
panic!("Expected SelfValue param");
}
} else {
panic!("Expected Fn item");
}
}
#[test]
fn test_fn_self_mut_param() {
let pure = PureFile::from_source("impl Foo { fn f(&mut self) {} }").unwrap();
let impls = pure.impls();
if let PureImplItem::Fn(f) = &impls[0].items[0] {
if let PureParam::SelfValue { is_ref, is_mut } = &f.params[0] {
assert!(is_ref);
assert!(is_mut);
} else {
panic!("Expected mut SelfValue param");
}
} else {
panic!("Expected Fn item");
}
}
#[test]
fn test_impl_inherent() {
let pure = PureFile::from_source("impl Foo { fn new() -> Self { Foo } }").unwrap();
let impls = pure.impls();
assert_eq!(impls.len(), 1);
assert!(impls[0].trait_.is_none());
assert_eq!(impls[0].self_ty, "Foo");
}
#[test]
fn test_impl_trait() {
let pure = PureFile::from_source("impl Clone for Foo { fn clone(&self) -> Self { Foo } }")
.unwrap();
let impls = pure.impls();
assert_eq!(impls[0].trait_.as_ref().unwrap(), "Clone");
}
#[test]
fn test_impl_unsafe() {
let pure = PureFile::from_source("unsafe impl Send for Foo {}").unwrap();
let impls = pure.impls();
assert!(impls[0].is_unsafe);
}
#[test]
fn test_impl_const() {
let pure = PureFile::from_source("impl Foo { const X: i32 = 42; }").unwrap();
let impls = pure.impls();
if let PureImplItem::Const(c) = &impls[0].items[0] {
assert_eq!(c.name, "X");
} else {
panic!("Expected Const item");
}
}
#[test]
fn test_impl_type() {
let pure = PureFile::from_source(
"impl Iterator for Foo { type Item = i32; fn next(&mut self) -> Option<i32> { None } }",
)
.unwrap();
let impls = pure.impls();
if let PureImplItem::Type(t) = &impls[0].items[0] {
assert_eq!(t.name, "Item");
} else {
panic!("Expected Type item");
}
}
#[test]
fn test_trait_simple() {
let pure = PureFile::from_source("trait Foo {}").unwrap();
let traits = pure.traits();
assert_eq!(traits.len(), 1);
assert_eq!(traits[0].name, "Foo");
}
#[test]
fn test_trait_unsafe() {
let pure = PureFile::from_source("unsafe trait Foo {}").unwrap();
assert!(pure.traits()[0].is_unsafe);
}
#[test]
fn test_trait_with_supertraits() {
let pure = PureFile::from_source("trait Foo: Clone + Send {}").unwrap();
let traits = pure.traits();
assert_eq!(traits[0].supertraits.len(), 2);
}
#[test]
fn test_trait_with_method() {
let pure = PureFile::from_source("trait Foo { fn bar(&self); }").unwrap();
let traits = pure.traits();
if let PureTraitItem::Fn(f) = &traits[0].items[0] {
assert_eq!(f.name, "bar");
} else {
panic!("Expected Fn item");
}
}
#[test]
fn test_trait_with_default_method() {
let pure = PureFile::from_source("trait Foo { fn bar(&self) { } }").unwrap();
let traits = pure.traits();
if let PureTraitItem::Fn(f) = &traits[0].items[0] {
assert!(!f.body.stmts.is_empty() || f.body.stmts.is_empty()); } else {
panic!("Expected Fn item");
}
}
#[test]
fn test_trait_with_associated_type() {
let pure = PureFile::from_source("trait Foo { type Item; }").unwrap();
let traits = pure.traits();
if let PureTraitItem::Type { name, .. } = &traits[0].items[0] {
assert_eq!(name, "Item");
} else {
panic!("Expected Type item");
}
}
#[test]
fn test_trait_with_associated_const() {
let pure = PureFile::from_source("trait Foo { const X: i32; }").unwrap();
let traits = pure.traits();
if let PureTraitItem::Const(c) = &traits[0].items[0] {
assert_eq!(c.name, "X");
} else {
panic!("Expected Const item");
}
}
#[test]
fn test_enum_simple() {
let pure = PureFile::from_source("enum Color { Red, Green, Blue }").unwrap();
let enums: Vec<_> = pure
.items
.iter()
.filter_map(|i| {
if let PureItem::Enum(e) = i {
Some(e)
} else {
None
}
})
.collect();
assert_eq!(enums.len(), 1);
assert_eq!(enums[0].variants.len(), 3);
}
#[test]
fn test_enum_with_discriminant() {
let pure = PureFile::from_source("enum Num { One = 1, Two = 2 }").unwrap();
if let PureItem::Enum(e) = &pure.items[0] {
assert!(e.variants[0].discriminant.is_some());
} else {
panic!("Expected Enum item");
}
}
#[test]
fn test_enum_tuple_variant() {
let pure = PureFile::from_source("enum Opt { Some(i32), None }").unwrap();
if let PureItem::Enum(e) = &pure.items[0] {
if let PureFields::Tuple(types) = &e.variants[0].fields {
assert_eq!(types.len(), 1);
} else {
panic!("Expected Tuple fields");
}
} else {
panic!("Expected Enum item");
}
}
#[test]
fn test_enum_struct_variant() {
let pure = PureFile::from_source("enum Msg { Move { x: i32, y: i32 } }").unwrap();
if let PureItem::Enum(e) = &pure.items[0] {
if let PureFields::Named(fields) = &e.variants[0].fields {
assert_eq!(fields.len(), 2);
} else {
panic!("Expected Named fields");
}
} else {
panic!("Expected Enum item");
}
}
#[test]
fn test_struct_unit() {
let pure = PureFile::from_source("struct Unit;").unwrap();
let structs = pure.structs();
assert!(matches!(structs[0].fields, PureFields::Unit));
}
#[test]
fn test_struct_tuple() {
let pure = PureFile::from_source("struct Point(i32, i32);").unwrap();
let structs = pure.structs();
if let PureFields::Tuple(types) = &structs[0].fields {
assert_eq!(types.len(), 2);
} else {
panic!("Expected Tuple fields");
}
}
#[test]
fn test_vis_public() {
let pure = PureFile::from_source("pub fn f() {}").unwrap();
assert!(matches!(pure.functions()[0].vis, PureVis::Public));
}
#[test]
fn test_vis_private() {
let pure = PureFile::from_source("fn f() {}").unwrap();
assert!(matches!(pure.functions()[0].vis, PureVis::Private));
}
#[test]
fn test_vis_crate() {
let pure = PureFile::from_source("pub(crate) fn f() {}").unwrap();
assert!(matches!(pure.functions()[0].vis, PureVis::Crate));
}
#[test]
fn test_vis_super() {
let pure = PureFile::from_source("pub(super) fn f() {}").unwrap();
assert!(matches!(pure.functions()[0].vis, PureVis::Super));
}
#[test]
fn test_vis_in_path() {
let pure = PureFile::from_source("pub(in crate::foo) fn f() {}").unwrap();
if let PureVis::In(path) = &pure.functions()[0].vis {
assert!(path.contains("crate"));
} else {
panic!("Expected In visibility");
}
}
#[test]
fn test_attr_derive() {
let pure = PureFile::from_source("#[derive(Clone, Debug)] struct Foo;").unwrap();
let attrs = &pure.structs()[0].attrs;
assert_eq!(attrs.len(), 1);
assert_eq!(attrs[0].path, "derive");
}
#[test]
fn test_attr_simple() {
let pure = PureFile::from_source("#[test] fn f() {}").unwrap();
let attrs = &pure.functions()[0].attrs;
assert_eq!(attrs.len(), 1);
assert_eq!(attrs[0].path, "test");
}
#[test]
fn test_attr_inner() {
let pure = PureFile::from_source("#![allow(unused)]").unwrap();
assert!(pure.attrs[0].is_inner);
}
#[test]
fn test_attr_outer() {
let pure = PureFile::from_source("#[allow(unused)] fn f() {}").unwrap();
assert!(!pure.functions()[0].attrs[0].is_inner);
}
#[test]
fn test_use_path() {
let pure = PureFile::from_source("use std::io::Read;").unwrap();
let uses = pure.uses();
if let PureUseTree::Path { path, tree } = &uses[0].tree {
assert_eq!(path, "std");
if let PureUseTree::Path { path, tree } = tree.as_ref() {
assert_eq!(path, "io");
if let PureUseTree::Name(name) = tree.as_ref() {
assert_eq!(name, "Read");
} else {
panic!("Expected Name");
}
} else {
panic!("Expected Path");
}
} else {
panic!("Expected Path");
}
}
#[test]
fn test_use_glob() {
let pure = PureFile::from_source("use std::io::*;").unwrap();
let uses = pure.uses();
if let PureUseTree::Path { tree, .. } = &uses[0].tree {
if let PureUseTree::Path { tree, .. } = tree.as_ref() {
assert!(matches!(tree.as_ref(), PureUseTree::Glob));
} else {
panic!("Expected Path");
}
} else {
panic!("Expected Path");
}
}
#[test]
fn test_use_group() {
let pure = PureFile::from_source("use std::{io, fs};").unwrap();
let uses = pure.uses();
if let PureUseTree::Path { tree, .. } = &uses[0].tree {
if let PureUseTree::Group(items) = tree.as_ref() {
assert_eq!(items.len(), 2);
} else {
panic!("Expected Group");
}
} else {
panic!("Expected Path");
}
}
#[test]
fn test_use_rename() {
let pure = PureFile::from_source("use std::io::Result as IoResult;").unwrap();
let uses = pure.uses();
if let PureUseTree::Path { tree, .. } = &uses[0].tree {
if let PureUseTree::Path { tree, .. } = tree.as_ref() {
if let PureUseTree::Rename { name, rename } = tree.as_ref() {
assert_eq!(name, "Result");
assert_eq!(rename, "IoResult");
} else {
panic!("Expected Rename");
}
} else {
panic!("Expected Path");
}
} else {
panic!("Expected Path");
}
}
#[test]
fn test_const_item() {
let pure = PureFile::from_source("const MAX: i32 = 100;").unwrap();
if let PureItem::Const(c) = &pure.items[0] {
assert_eq!(c.name, "MAX");
} else {
panic!("Expected Const item");
}
}
#[test]
fn test_static_item() {
let pure = PureFile::from_source("static mut COUNTER: i32 = 0;").unwrap();
if let PureItem::Static(s) = &pure.items[0] {
assert!(s.is_mut);
} else {
panic!("Expected Static item");
}
}
#[test]
fn test_type_alias() {
let pure =
PureFile::from_source("type Result<T> = std::result::Result<T, Error>;").unwrap();
if let PureItem::Type(t) = &pure.items[0] {
assert_eq!(t.name, "Result");
} else {
panic!("Expected Type item");
}
}
#[test]
fn test_mod_declaration() {
let pure = PureFile::from_source("mod foo;").unwrap();
if let PureItem::Mod(m) = &pure.items[0] {
assert!(m.items.is_empty());
} else {
panic!("Expected Mod item");
}
}
#[test]
fn test_mod_inline() {
let pure = PureFile::from_source("mod foo { fn bar() {} }").unwrap();
if let PureItem::Mod(m) = &pure.items[0] {
assert!(!m.items.is_empty());
assert_eq!(m.items.len(), 1);
} else {
panic!("Expected Mod item");
}
}
#[test]
fn test_macro_invocation() {
let pure = PureFile::from_source("include!(\"foo.rs\");").unwrap();
if let PureItem::Macro(m) = &pure.items[0] {
assert_eq!(m.path, "include");
} else {
panic!("Expected Macro item");
}
}
#[test]
fn test_match_arm_guard() {
let pure =
PureFile::from_source("fn f(x: i32) { match x { n if n > 0 => {} _ => {} } }").unwrap();
let f = &pure.functions()[0];
if let PureStmt::Expr(PureExpr::Match { arms, .. }) = &f.body.stmts[0] {
assert!(arms[0].guard.is_some());
} else {
panic!("Expected Match expr");
}
}
#[test]
fn test_path_to_string_simple() {
assert_eq!(
path_to_string(&syn::parse_str::<syn::Path>("std").unwrap()),
"std"
);
}
#[test]
fn test_path_to_string_nested() {
assert_eq!(
path_to_string(&syn::parse_str::<syn::Path>("std::io::Read").unwrap()),
"std::io::Read"
);
}
#[test]
fn test_path_to_string_with_generics() {
let path = syn::parse_str::<syn::Path>("Vec<i32>").unwrap();
let result = path_to_string(&path);
assert!(result.contains("Vec"));
assert!(result.contains("i32"));
}
#[test]
fn test_is_pinned_boxed_future_basic() {
assert!(is_pinned_boxed_future("Pin<Box<dyn Future<Output = ()>>>"));
assert!(is_pinned_boxed_future(
"Pin<Box<dyn Future<Output = Result<T, E>>>>"
));
assert!(is_pinned_boxed_future(
"Pin<Box<dyn Future<Output = ()> + Send>>"
));
assert!(is_pinned_boxed_future(
"Pin<Box<dyn Future<Output = ()> + Send + 'static>>"
));
}
#[test]
fn test_is_pinned_boxed_future_with_spaces() {
assert!(is_pinned_boxed_future(
"Pin < Box < dyn Future < Output = () > > >"
));
assert!(is_pinned_boxed_future(
"Pin < Box < dyn Future < Output = Result < T , E > > + Send > >"
));
}
#[test]
fn test_is_pinned_boxed_future_with_path_prefix() {
assert!(is_pinned_boxed_future(
"::core::pin::Pin<Box<dyn Future<Output = ()>>>"
));
assert!(is_pinned_boxed_future(
"core::pin::Pin<Box<dyn Future<Output = ()>>>"
));
assert!(is_pinned_boxed_future(
"std::pin::Pin<Box<dyn Future<Output = ()>>>"
));
assert!(is_pinned_boxed_future(
"::std::pin::Pin<Box<dyn Future<Output = ()>>>"
));
}
#[test]
fn test_is_pinned_boxed_future_negative() {
assert!(!is_pinned_boxed_future("Result<T, E>"));
assert!(!is_pinned_boxed_future("Option<T>"));
assert!(!is_pinned_boxed_future("Box<dyn Future<Output = ()>>"));
assert!(!is_pinned_boxed_future("Pin<Box<T>>"));
assert!(!is_pinned_boxed_future("Future<Output = ()>"));
}
#[test]
fn test_async_fn_is_async() {
let pure = PureFile::from_source("async fn foo() {}").unwrap();
let f = &pure.functions()[0];
assert!(f.is_async);
assert!(!f.is_async_inferred);
assert!(f.is_effectively_async());
}
#[test]
fn test_sync_fn_not_async() {
let pure = PureFile::from_source("fn foo() {}").unwrap();
let f = &pure.functions()[0];
assert!(!f.is_async);
assert!(!f.is_async_inferred);
assert!(!f.is_effectively_async());
}
#[test]
fn test_async_trait_style_fn_inferred() {
let code = r#"
fn foo(&self) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(async { })
}
"#;
let pure = PureFile::from_source(code).unwrap();
let f = &pure.functions()[0];
assert!(!f.is_async, "Should not be explicitly async");
assert!(
f.is_async_inferred,
"Should be inferred as async from return type"
);
assert!(f.is_effectively_async());
}
#[test]
fn test_async_trait_in_impl() {
let code = r#"
struct Foo;
impl Foo {
fn bar(&self) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send>> {
Box::pin(async { Ok(()) })
}
}
"#;
let pure = PureFile::from_source(code).unwrap();
let impls = pure.impls();
assert_eq!(impls.len(), 1);
if let PureImplItem::Fn(method) = &impls[0].items[0] {
assert!(!method.is_async);
assert!(method.is_async_inferred);
assert!(method.is_effectively_async());
} else {
panic!("Expected Fn in impl");
}
}
}