use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parse, ImplItem, ItemImpl, Token};
use crate::get_leo3_crate;
pub struct LeanInstanceOptions {
pub typeclass: syn::Ident,
pub krate: Option<syn::Path>,
}
impl Parse for LeanInstanceOptions {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let typeclass: syn::Ident = input.parse()?;
let krate = if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
if input.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
if ident == "crate" {
input.parse::<Token![=]>()?;
Some(input.parse()?)
} else {
return Err(syn::Error::new(ident.span(), "Expected `crate = path`"));
}
} else {
None
}
} else {
None
};
Ok(LeanInstanceOptions { typeclass, krate })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeClass {
BEq,
Hashable,
Repr,
ToString,
Ord,
}
impl TypeClass {
pub fn from_ident(ident: &syn::Ident) -> Option<Self> {
match ident.to_string().as_str() {
"BEq" => Some(TypeClass::BEq),
"Hashable" => Some(TypeClass::Hashable),
"Repr" => Some(TypeClass::Repr),
"ToString" => Some(TypeClass::ToString),
"Ord" => Some(TypeClass::Ord),
_ => None,
}
}
pub fn method_name(&self) -> &'static str {
match self {
TypeClass::BEq => "beq",
TypeClass::Hashable => "hash",
TypeClass::Repr => "repr",
TypeClass::ToString => "to_string",
TypeClass::Ord => "compare",
}
}
pub fn ffi_prefix(&self) -> &'static str {
match self {
TypeClass::BEq => "__lean_beq",
TypeClass::Hashable => "__lean_hash",
TypeClass::Repr => "__lean_repr",
TypeClass::ToString => "__lean_to_string",
TypeClass::Ord => "__lean_compare",
}
}
}
pub fn build_lean_instance(
item: &mut ItemImpl,
options: LeanInstanceOptions,
) -> syn::Result<TokenStream> {
let typeclass = TypeClass::from_ident(&options.typeclass).ok_or_else(|| {
syn::Error::new(
options.typeclass.span(),
format!(
"Unsupported type class '{}'. Supported: BEq, Hashable, Repr, ToString, Ord",
options.typeclass
),
)
})?;
let struct_name = extract_struct_name(item)?;
let method = find_method(item, typeclass.method_name())?;
let leo3_crate = get_leo3_crate(options.krate.as_ref());
let ffi_fn = generate_ffi_function(&leo3_crate, &struct_name, typeclass, method)?;
Ok(quote! {
#item
#ffi_fn
})
}
fn extract_struct_name(item: &ItemImpl) -> syn::Result<syn::Ident> {
match &*item.self_ty {
syn::Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
Ok(segment.ident.clone())
} else {
Err(syn::Error::new_spanned(
&item.self_ty,
"Cannot determine struct name from impl block",
))
}
}
_ => Err(syn::Error::new_spanned(
&item.self_ty,
"Expected a path type in impl block",
)),
}
}
fn find_method<'a>(item: &'a ItemImpl, name: &str) -> syn::Result<&'a syn::ImplItemFn> {
for impl_item in &item.items {
if let ImplItem::Fn(method) = impl_item {
if method.sig.ident == name {
return Ok(method);
}
}
}
Err(syn::Error::new_spanned(
item,
format!(
"Method '{}' not found in impl block. This method is required for the type class.",
name
),
))
}
fn generate_ffi_function(
leo3_crate: &TokenStream,
struct_name: &syn::Ident,
typeclass: TypeClass,
_method: &syn::ImplItemFn,
) -> syn::Result<TokenStream> {
let ffi_name = format_ident!("{}_{}", typeclass.ffi_prefix(), struct_name);
let try_name = format_ident!("__leo3_try_{}", ffi_name);
let method_ident = format_ident!("{}", typeclass.method_name());
match typeclass {
TypeClass::BEq => Ok(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(
a: *mut #leo3_crate::ffi::lean_object,
b: *mut #leo3_crate::ffi::lean_object,
) -> *mut #leo3_crate::ffi::lean_object {
#leo3_crate::__private::ffi_panic_boundary(|| #try_name(a, b))
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub(crate) unsafe fn #try_name(
a: *mut #leo3_crate::ffi::lean_object,
b: *mut #leo3_crate::ffi::lean_object,
) -> #leo3_crate::LeanResult<*mut #leo3_crate::ffi::lean_object> {
let lean = #leo3_crate::Lean::assume_initialized();
let a_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, a);
let b_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, b);
let result = a_bound.get_ref().#method_ident(b_bound.get_ref());
Ok(#leo3_crate::ffi::inline::lean_box(result as usize))
}
}),
TypeClass::Hashable => Ok(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> u64 {
#leo3_crate::__private::scalar_u64_ffi_panic_boundary(stringify!(#ffi_name), || #try_name(obj))
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub(crate) unsafe fn #try_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> #leo3_crate::LeanResult<u64> {
let lean = #leo3_crate::Lean::assume_initialized();
let obj_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, obj);
Ok(obj_bound.get_ref().#method_ident())
}
}),
TypeClass::Repr => Ok(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> *mut #leo3_crate::ffi::lean_object {
#leo3_crate::__private::ffi_panic_boundary(|| #try_name(obj))
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub(crate) unsafe fn #try_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> #leo3_crate::LeanResult<*mut #leo3_crate::ffi::lean_object> {
let lean = #leo3_crate::Lean::assume_initialized();
let obj_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, obj);
let repr_str: String = obj_bound.get_ref().#method_ident();
let s = #leo3_crate::types::LeanString::mk(lean, &repr_str)
.map_err(|e| #leo3_crate::LeanError::Conversion(format!(
"Failed to convert Rust repr result to Lean: {}",
e
)))?;
Ok(s.into_ptr())
}
}),
TypeClass::ToString => Ok(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> *mut #leo3_crate::ffi::lean_object {
#leo3_crate::__private::ffi_panic_boundary(|| #try_name(obj))
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub(crate) unsafe fn #try_name(
obj: *mut #leo3_crate::ffi::lean_object,
) -> #leo3_crate::LeanResult<*mut #leo3_crate::ffi::lean_object> {
let lean = #leo3_crate::Lean::assume_initialized();
let obj_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, obj);
let s: String = obj_bound.get_ref().#method_ident();
let lean_str = #leo3_crate::types::LeanString::mk(lean, &s)
.map_err(|e| #leo3_crate::LeanError::Conversion(format!(
"Failed to convert Rust string result to Lean: {}",
e
)))?;
Ok(lean_str.into_ptr())
}
}),
TypeClass::Ord => Ok(quote! {
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(
a: *mut #leo3_crate::ffi::lean_object,
b: *mut #leo3_crate::ffi::lean_object,
) -> *mut #leo3_crate::ffi::lean_object {
#leo3_crate::__private::ffi_panic_boundary(|| #try_name(a, b))
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub(crate) unsafe fn #try_name(
a: *mut #leo3_crate::ffi::lean_object,
b: *mut #leo3_crate::ffi::lean_object,
) -> #leo3_crate::LeanResult<*mut #leo3_crate::ffi::lean_object> {
let lean = #leo3_crate::Lean::assume_initialized();
let a_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, a);
let b_bound: #leo3_crate::LeanBound<'_, #leo3_crate::external::LeanExternalType<#struct_name>> =
#leo3_crate::LeanBound::from_owned_ptr(lean, b);
let ordering = a_bound.get_ref().#method_ident(b_bound.get_ref());
let ord_val = match ordering {
std::cmp::Ordering::Less => 0usize,
std::cmp::Ordering::Equal => 1usize,
std::cmp::Ordering::Greater => 2usize,
};
Ok(#leo3_crate::ffi::inline::lean_box(ord_val))
}
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_lean_instance_wrappers_use_boundary_helpers() {
let mut item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
fn repr(&self) -> String {
"demo".to_string()
}
}
};
let repr_tokens = build_lean_instance(
&mut item,
LeanInstanceOptions {
typeclass: syn::parse_quote!(Repr),
krate: None,
},
)
.expect("lean_instance repr expansion should succeed");
let rendered_repr = repr_tokens.to_string();
assert!(rendered_repr.contains("__private :: ffi_panic_boundary"));
assert!(rendered_repr.contains("Failed to convert Rust repr result to Lean"));
assert!(!rendered_repr.contains("empty string"));
assert!(!rendered_repr.contains(".expect("));
assert!(!rendered_repr.contains(". expect ("));
let mut hash_item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
fn hash(&self) -> u64 {
7
}
}
};
let hash_tokens = build_lean_instance(
&mut hash_item,
LeanInstanceOptions {
typeclass: syn::parse_quote!(Hashable),
krate: None,
},
)
.expect("lean_instance hash expansion should succeed");
let rendered_hash = hash_tokens.to_string();
assert!(rendered_hash.contains("__private :: scalar_u64_ffi_panic_boundary"));
assert!(!rendered_hash.contains(".expect("));
assert!(!rendered_hash.contains(". expect ("));
}
}