Allow documenting of extern "Lua" functions

This commit is contained in:
lumi 2025-06-24 22:23:26 +10:00
parent 1aa9b4aa02
commit 122ef04b16
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
2 changed files with 128 additions and 36 deletions

View File

@ -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<T: 'static>() -> u64 {
let mut hash = FxHasher::default();
TypeId::of::<T>().hash(&mut hash);

View File

@ -202,10 +202,7 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
{
func.sig.abi = None;
// normalise inputs to PatType
funcs.push(FfiFunction {
name: func.sig.ident.clone(),
params: func
let params = func
.sig
.inputs
.iter()
@ -216,12 +213,21 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
}
FnArg::Typed(ty) => ty.clone(),
})
.collect(),
ret: match func.sig.output {
ReturnType::Default => parse_quote_spanned!(func.sig.span() => ()),
.collect();
let ret = match func.sig.output {
ReturnType::Default => parse_quote!(()),
ReturnType::Type(_, ref ty) => (**ty).clone(),
},
attrs: parse_ffi_function_attrs(&mut func.attrs)?,
};
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,
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<Vec<LuaFunction>> {
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<Vec<LuaFunction>> {
.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<Vec<LuaFunction>> {
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<Attribute>) -> Result<LuaFunctionAttrs> {
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 = &registry.ty;
let lua = format!(