use proc_macro2::Span;
use syn::punctuated::Punctuated;
use syn::token;
use super::helpers::{ident, make_abi};
use super::{ToSyn, ToSynError};
use crate::pure::ast::{PureBlock, PureFn, PureGenericParam, PureGenerics, PureParam, PureStmt};
impl ToSyn for PureFn {
type Output = syn::ItemFn;
fn to_syn(&self) -> Result<syn::ItemFn, ToSynError> {
Ok(syn::ItemFn {
attrs: self
.attrs
.iter()
.map(|a| a.to_syn())
.collect::<Result<Vec<_>, _>>()?,
vis: self.vis.to_syn()?,
sig: syn::Signature {
constness: if self.is_const {
Some(token::Const::default())
} else {
None
},
asyncness: if self.is_async {
Some(token::Async::default())
} else {
None
},
unsafety: if self.is_unsafe {
Some(token::Unsafe::default())
} else {
None
},
abi: self.abi.as_ref().map(|a| make_abi(a)),
fn_token: token::Fn::default(),
ident: ident(&self.name),
generics: self.generics.to_syn()?,
paren_token: token::Paren::default(),
inputs: self
.params
.iter()
.map(|p| p.to_syn())
.collect::<Result<_, _>>()?,
variadic: None,
output: match &self.ret {
Some(ty) => {
syn::ReturnType::Type(token::RArrow::default(), Box::new(ty.to_syn()?))
}
None => syn::ReturnType::Default,
},
},
block: Box::new(self.body.to_syn()?),
})
}
}
impl ToSyn for PureParam {
type Output = syn::FnArg;
fn to_syn(&self) -> Result<syn::FnArg, ToSynError> {
Ok(match self {
PureParam::SelfValue { is_ref, is_mut } => {
let self_ty = if *is_ref {
if *is_mut {
syn::Type::Reference(syn::TypeReference {
and_token: token::And::default(),
lifetime: None,
mutability: Some(token::Mut::default()),
elem: Box::new(syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path::from(ident("Self")),
})),
})
} else {
syn::Type::Reference(syn::TypeReference {
and_token: token::And::default(),
lifetime: None,
mutability: None,
elem: Box::new(syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path::from(ident("Self")),
})),
})
}
} else {
syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path::from(ident("Self")),
})
};
syn::FnArg::Receiver(syn::Receiver {
attrs: vec![],
reference: if *is_ref {
Some((token::And::default(), None))
} else {
None
},
mutability: if *is_mut {
Some(token::Mut::default())
} else {
None
},
self_token: token::SelfValue::default(),
colon_token: None,
ty: Box::new(self_ty),
})
}
PureParam::Typed { name, ty } => syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: ident(name),
subpat: None,
})),
colon_token: token::Colon::default(),
ty: Box::new(ty.to_syn()?),
}),
})
}
}
impl ToSyn for PureGenerics {
type Output = syn::Generics;
fn to_syn(&self) -> Result<syn::Generics, ToSynError> {
let params: Punctuated<syn::GenericParam, token::Comma> = self
.params
.iter()
.map(|p| p.to_syn())
.collect::<Result<_, _>>()?;
let where_clause = if self.where_clause.is_empty() {
None
} else {
Some(syn::WhereClause {
where_token: token::Where::default(),
predicates: self
.where_clause
.iter()
.filter_map(|s| syn::parse_str::<syn::WherePredicate>(s).ok())
.collect(),
})
};
Ok(syn::Generics {
lt_token: if params.is_empty() {
None
} else {
Some(token::Lt::default())
},
params,
gt_token: if self.params.is_empty() {
None
} else {
Some(token::Gt::default())
},
where_clause,
})
}
}
impl ToSyn for PureGenericParam {
type Output = syn::GenericParam;
fn to_syn(&self) -> Result<syn::GenericParam, ToSynError> {
Ok(match self {
PureGenericParam::Type { name, bounds } => syn::GenericParam::Type(syn::TypeParam {
attrs: vec![],
ident: ident(name),
colon_token: if bounds.is_empty() {
None
} else {
Some(token::Colon::default())
},
bounds: bounds
.iter()
.filter_map(|b| syn::parse_str::<syn::TypeParamBound>(b).ok())
.collect(),
eq_token: None,
default: None,
}),
PureGenericParam::Lifetime { name, bounds } => {
syn::GenericParam::Lifetime(syn::LifetimeParam {
attrs: vec![],
lifetime: syn::Lifetime::new(name, Span::call_site()),
colon_token: if bounds.is_empty() {
None
} else {
Some(token::Colon::default())
},
bounds: bounds
.iter()
.map(|b| syn::Lifetime::new(b, Span::call_site()))
.collect(),
})
}
PureGenericParam::Const { name, ty } => syn::GenericParam::Const(syn::ConstParam {
attrs: vec![],
const_token: token::Const::default(),
ident: ident(name),
colon_token: token::Colon::default(),
ty: syn::parse_str(ty).map_err(|e| ToSynError::ParseType {
input: ty.clone(),
message: e.to_string(),
})?,
eq_token: None,
default: None,
}),
})
}
}
impl ToSyn for PureBlock {
type Output = syn::Block;
fn to_syn(&self) -> Result<syn::Block, ToSynError> {
Ok(syn::Block {
brace_token: token::Brace::default(),
stmts: self
.stmts
.iter()
.map(|s| s.to_syn())
.collect::<Result<Vec<_>, _>>()?,
})
}
}
impl ToSyn for PureStmt {
type Output = syn::Stmt;
fn to_syn(&self) -> Result<syn::Stmt, ToSynError> {
match self {
PureStmt::Local { pattern, ty, init } => {
let pat = if let Some(ty) = ty {
syn::Pat::Type(syn::PatType {
attrs: vec![],
pat: Box::new(pattern.to_syn()?),
colon_token: token::Colon::default(),
ty: Box::new(ty.to_syn()?),
})
} else {
pattern.to_syn()?
};
let local_init = init
.as_ref()
.map(|e| {
Ok(syn::LocalInit {
eq_token: token::Eq::default(),
expr: Box::new(e.to_syn()?),
diverge: None,
})
})
.transpose()?;
Ok(syn::Stmt::Local(syn::Local {
attrs: vec![],
let_token: token::Let::default(),
pat,
init: local_init,
semi_token: token::Semi::default(),
}))
}
PureStmt::Semi(expr) => Ok(syn::Stmt::Expr(
expr.to_syn()?,
Some(token::Semi::default()),
)),
PureStmt::Expr(expr) => Ok(syn::Stmt::Expr(expr.to_syn()?, None)),
PureStmt::Item(item) => Ok(syn::Stmt::Item(item.to_syn()?)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pure::ast::{PureExpr, PureType, PureVis};
use quote::ToTokens;
#[test]
fn test_pure_fn_simple() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Public,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: None,
name: "foo".to_string(),
generics: PureGenerics::default(),
params: vec![],
ret: None,
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(output.contains("pub"), "Output: {}", output);
assert!(output.contains("foo"), "Output: {}", output);
}
#[test]
fn test_pure_fn_with_params() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Private,
is_async: true,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: None,
name: "bar".to_string(),
generics: PureGenerics::default(),
params: vec![PureParam::Typed {
name: "x".to_string(),
ty: PureType::Path("i32".to_string()),
}],
ret: Some(PureType::Path("i32".to_string())),
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(output.contains("async"), "Output: {}", output);
assert!(output.contains("i32"), "Output: {}", output);
}
#[test]
fn test_pure_block_with_stmts() {
let block = PureBlock {
stmts: vec![PureStmt::Local {
pattern: crate::pure::ast::PurePattern::Ident {
name: "x".to_string(),
is_mut: false,
},
ty: None,
init: Some(PureExpr::Lit("42".to_string())),
}],
};
let syn_block = block.to_syn().unwrap();
let output = syn_block.to_token_stream().to_string();
assert!(output.contains("let"), "Output: {}", output);
assert!(output.contains("42"), "Output: {}", output);
}
#[test]
fn test_pure_fn_with_extern_c_abi() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Public,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: Some("C".to_string()),
name: "c_func".to_string(),
generics: PureGenerics::default(),
params: vec![PureParam::Typed {
name: "x".to_string(),
ty: PureType::Path("i32".to_string()),
}],
ret: Some(PureType::Path("i32".to_string())),
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(
output.contains(r#"extern "C""#),
r#"Output should contain 'extern "C"': {}"#,
output
);
assert!(output.contains("c_func"), "Output: {}", output);
}
#[test]
fn test_pure_fn_with_extern_system_abi() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Private,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: Some("system".to_string()),
name: "system_func".to_string(),
generics: PureGenerics::default(),
params: vec![],
ret: None,
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(
output.contains(r#"extern "system""#),
r#"Output should contain 'extern "system"': {}"#,
output
);
}
#[test]
fn test_pure_fn_with_extern_rust_abi() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Public,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: Some("Rust".to_string()),
name: "rust_abi_func".to_string(),
generics: PureGenerics::default(),
params: vec![],
ret: None,
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(
output.contains(r#"extern "Rust""#),
r#"Output should contain 'extern "Rust"': {}"#,
output
);
}
#[test]
fn test_pure_fn_no_abi() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Public,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: false,
abi: None,
name: "normal_func".to_string(),
generics: PureGenerics::default(),
params: vec![],
ret: None,
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(
!output.contains("extern"),
"Output should NOT contain 'extern': {}",
output
);
}
#[test]
fn test_pure_fn_unsafe_extern_c() {
let f = PureFn {
attrs: vec![],
vis: PureVis::Public,
is_async: false,
is_async_inferred: false,
is_const: false,
is_unsafe: true,
abi: Some("C".to_string()),
name: "unsafe_c_func".to_string(),
generics: PureGenerics::default(),
params: vec![],
ret: None,
body: PureBlock::default(),
};
let syn_fn = f.to_syn().unwrap();
let output = syn_fn.to_token_stream().to_string();
assert!(
output.contains("unsafe"),
"Output should contain 'unsafe': {}",
output
);
assert!(
output.contains(r#"extern "C""#),
r#"Output should contain 'extern "C"': {}"#,
output
);
}
}