diff --git a/crates/luaffi/src/internal.rs b/crates/luaffi/src/internal.rs index 67a0823..b8cce11 100644 --- a/crates/luaffi/src/internal.rs +++ b/crates/luaffi/src/internal.rs @@ -6,6 +6,23 @@ use std::{ hash::{Hash, Hasher}, }; +#[allow(non_camel_case_types)] +pub mod stub_types { + pub struct any; + pub struct nil; + pub struct boolean; + pub struct lightuserdata; + pub struct number; + pub struct integer; + pub struct string; + pub struct table; + pub struct function; + pub struct userdata; + pub struct thread; + pub struct cdata; + pub struct variadic; +} + pub fn type_id() -> u64 { let mut hash = FxHasher::default(); TypeId::of::().hash(&mut hash); diff --git a/crates/luaffi_impl/src/metatype.rs b/crates/luaffi_impl/src/metatype.rs index fb27951..ad9f0e2 100644 --- a/crates/luaffi_impl/src/metatype.rs +++ b/crates/luaffi_impl/src/metatype.rs @@ -202,26 +202,32 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result> { { func.sig.abi = None; - // normalise inputs to PatType + let params = func + .sig + .inputs + .iter() + .map(|arg| match arg { + FnArg::Receiver(recv) => { + let ty = &recv.ty; + parse_quote_spanned!(ty.span() => self: #ty) + } + FnArg::Typed(ty) => ty.clone(), + }) + .collect(); + + let ret = match func.sig.output { + ReturnType::Default => parse_quote!(()), + ReturnType::Type(_, ref ty) => (**ty).clone(), + }; + + let attrs = parse_ffi_function_attrs(&mut func.attrs)?; + attrs.metamethod.map(|mm| document_metamethod(func, mm)); + funcs.push(FfiFunction { name: func.sig.ident.clone(), - params: func - .sig - .inputs - .iter() - .map(|arg| match arg { - FnArg::Receiver(recv) => { - let ty = &recv.ty; - parse_quote_spanned!(ty.span() => self: #ty) - } - FnArg::Typed(ty) => ty.clone(), - }) - .collect(), - ret: match func.sig.output { - ReturnType::Default => parse_quote_spanned!(func.sig.span() => ()), - ReturnType::Type(_, ref ty) => (**ty).clone(), - }, - attrs: parse_ffi_function_attrs(&mut func.attrs)?, + params, + ret, + attrs, }); } } @@ -339,13 +345,13 @@ fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<() match get_ffi_param_type(func_param) { FfiParameterType::Default => { - shim_params.push(quote_spanned!(param.span() => + shim_params.push(quote_spanned!(func_param.span() => #shim_param: <#func_param as #ffi::FromFfi>::From )); - func_args.push(quote_spanned!(param.span() => + func_args.push(quote_spanned!(param.pat.span() => <#func_param as #ffi::FromFfi>::convert(#shim_param) )); - build.push(quote_spanned!(param.span() => + build.push(quote_spanned!(param.pat.span() => b.param::<#func_param>(#name); )); } @@ -432,15 +438,13 @@ struct LuaFunctionAttrs { fn get_lua_functions(imp: &mut ItemImpl) -> Result> { let mut funcs = vec![]; - let mut i = 0; - while i < imp.items.len() { - if let ImplItem::Fn(ref mut func) = imp.items[i] + for item in imp.items.iter_mut() { + if let ImplItem::Fn(func) = item && let Some(ref abi) = func.sig.abi && let Some(ref abi) = abi.name && abi.value() == "Lua" { - // normalise inputs to Pat let mut params: Vec<_> = func .sig .inputs @@ -448,7 +452,6 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result> { .map(|arg| { Ok(match arg { FnArg::Receiver(recv) => { - syn_assert!(ty_name(&recv.ty)? == "Self", recv, "must be `self`"); syn_assert!(recv.mutability.is_none(), recv, "cannot be mut"); Pat::Type(parse_quote_spanned!(recv.span() => self: cdata)) } @@ -461,29 +464,71 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result> { params.push(parse_quote_spanned!(variadic.span() => variadic!())); } - // shouldn't specify an output type - syn_assert!( - matches!(func.sig.output, ReturnType::Default), - func.sig.output, - "cannot have return type" - ); + let attrs = parse_lua_function_attrs(&mut func.attrs)?; + attrs.metamethod.map(|mm| document_metamethod(func, mm)); funcs.push(LuaFunction { name: func.sig.ident.clone(), params, body: func.block.clone(), - attrs: parse_lua_function_attrs(&mut func.attrs)?, + attrs, }); - imp.items.remove(i); - } else { - i += 1; + stub_lua_function(func)?; } } Ok(funcs) } +fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> { + // converts an extern "Lua" function into a regular function with no body to allow for + // documentation generation + let ffi = ffi_crate(); + + func.sig.abi = None; + func.attrs.push(parse_quote!(#[allow(unused)])); + func.block = parse_quote!({ + ::std::unreachable!("cannot call lua function from rust"); + }); + + let inputs = &mut func.sig.inputs; + for input in inputs.iter_mut() { + if let FnArg::Typed(pat) = input { + let span = pat.ty.span(); + let ty = if let Type::Infer(_) = *pat.ty { + quote_spanned!(span => any) + } else { + match ty_name(&pat.ty)?.to_string().as_str() { + "any" => quote_spanned!(span => any), + "nil" => quote_spanned!(span => nil), + "boolean" => quote_spanned!(span => boolean), + "lightuserdata" => quote_spanned!(span => lightuserdata), + "number" => quote_spanned!(span => number), + "integer" => quote_spanned!(span => integer), + "string" => quote_spanned!(span => string), + "table" => quote_spanned!(span => table), + "function" => quote_spanned!(span => function), + "userdata" => quote_spanned!(span => userdata), + "thread" => quote_spanned!(span => thread), + "cdata" => quote_spanned!(span => cdata), + _ => syn_error!(pat, "unknown lua type"), + } + }; + + pat.ty = Box::new(parse_quote!(#ffi::__internal::stub_types::#ty)); + } + } + + if let Some(ref variadic) = func.sig.variadic { + let ty = quote_spanned!(variadic.span() => variadic); + inputs.push(parse_quote!(rest: #ffi::__internal::stub_types::#ty)); + func.sig.variadic = None; + } + + Ok(()) +} + fn parse_lua_function_attrs(attrs: &mut Vec) -> Result { let mut parsed = LuaFunctionAttrs::default(); let mut i = 0; @@ -532,6 +577,36 @@ fn add_lua_function(registry: &mut LuaRegistry, func: &LuaFunction) -> Result<() Ok(()) } +fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) { + let s = match method { + Metamethod::Eq => "This is a metamethod which is called by the `==` operator.".into(), + Metamethod::Len => "This is a metamethod which is called by the `#` operator.".into(), + Metamethod::Lt => "This is a metamethod which is called by the `<` operator.".into(), + Metamethod::Le => "This is a metamethod which is called by the `<=` operator.".into(), + Metamethod::Concat => "This is a metamethod which is called by the `..` operator.".into(), + Metamethod::Add => "This is a metamethod which is called by the `+` operator.".into(), + Metamethod::Sub => "This is a metamethod which is called by the `-` operator.".into(), + Metamethod::Mul => "This is a metamethod which is called by the `*` operator.".into(), + Metamethod::Div => "This is a metamethod which is called by the `/` operator.".into(), + Metamethod::Mod => "This is a metamethod which is called by the `%` operator.".into(), + Metamethod::Pow => "This is a metamethod which is called by the `^` operator.".into(), + Metamethod::Unm => "This is a metamethod which is called by the `-` operator.".into(), + Metamethod::ToString => { + "This is a metamethod which can be called by the `tostring` built-in function.".into() + } + Metamethod::Pairs => { + "This is a metamethod which can be called by the `pairs` built-in function.".into() + } + Metamethod::Ipairs => { + "This is a metamethod which can be called by the `ipairs` built-in function.".into() + } + _ => format!("This is a metamethod and cannot be called directly."), + }; + + func.attrs.push(parse_quote!(#[doc = ""])); + func.attrs.push(parse_quote!(#[doc = #s])); +} + fn inject_fallback_new(registry: &mut LuaRegistry) -> Result<()> { let ty = ®istry.ty; let lua = format!(