Rewrite stab generation to be more extensible

This commit is contained in:
lumi 2025-06-28 07:58:52 +10:00
parent ef811ecfa9
commit c760d12c39
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
3 changed files with 217 additions and 107 deletions

View File

@ -101,7 +101,7 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let ty = &str.ident; let ty = &str.ident;
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?; let build = generate_cdef_build(&parse_cfields(&mut str.fields)?)?;
Ok(quote!( Ok(quote!(
unsafe impl #ffi::Cdef for #ty { unsafe impl #ffi::Cdef for #ty {
@ -123,7 +123,7 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
.variants .variants
.iter_mut() .iter_mut()
.map(|variant| { .map(|variant| {
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?; let build = generate_cdef_build(&parse_cfields(&mut variant.fields)?)?;
Ok(quote!(b.inner_struct(|b| { #build }))) Ok(quote!(b.inner_struct(|b| { #build })))
}) })
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
@ -148,7 +148,7 @@ struct CFieldAttrs {
opaque: bool, opaque: bool,
} }
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> { fn parse_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
match fields { match fields {
Fields::Named(fields) => fields.named.iter_mut(), Fields::Named(fields) => fields.named.iter_mut(),
Fields::Unnamed(fields) => fields.unnamed.iter_mut(), Fields::Unnamed(fields) => fields.unnamed.iter_mut(),

View File

@ -1,6 +1,6 @@
use crate::utils::{ use crate::utils::{
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident, StringLike, ffi_crate, is_optionlike, is_primitivelike, is_resultlike, is_stringlike, is_unit,
syn_assert, syn_error, ty_name, pat_ident, syn_assert, syn_error, ty_name,
}; };
use darling::FromMeta; use darling::FromMeta;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
@ -78,7 +78,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
}); });
// process extern "Lua-C" ffi functions // process extern "Lua-C" ffi functions
for func in get_ffi_functions(imp)? { for func in parse_ffi_functions(imp)? {
if let Some(mm) = func.attrs.metamethod { if let Some(mm) = func.attrs.metamethod {
syn_assert!( syn_assert!(
mms.insert(mm), mms.insert(mm),
@ -91,7 +91,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
} }
// process extern "Lua" lua functions // process extern "Lua" lua functions
for func in get_lua_functions(imp)? { for func in parse_lua_functions(imp)? {
if let Some(mm) = func.attrs.metamethod { if let Some(mm) = func.attrs.metamethod {
syn_assert!( syn_assert!(
mms.insert(mm), mms.insert(mm),
@ -230,8 +230,8 @@ struct FfiFunctionAttrs {
metamethod: Option<Metamethod>, metamethod: Option<Metamethod>,
} }
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> { fn parse_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
let mut funcs = vec![]; let mut parsed = vec![];
for item in imp.items.iter_mut() { for item in imp.items.iter_mut() {
if let ImplItem::Fn(func) = item if let ImplItem::Fn(func) = item
@ -284,22 +284,19 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
} }
} }
let attrs = parse_ffi_function_attrs(&mut func.attrs)?; parsed.push(FfiFunction {
attrs.metamethod.map(|mm| document_metamethod(func, mm));
func.sig.asyncness.is_some().then(|| document_async(func));
document_ffi_function(func);
funcs.push(FfiFunction {
name: func.sig.ident.clone(), name: func.sig.ident.clone(),
params, params,
ret, ret,
attrs, attrs: parse_ffi_function_attrs(&mut func.attrs)?,
is_async: func.sig.asyncness.is_some(), is_async: func.sig.asyncness.is_some(),
}); });
document_ffi_function(func, parsed.last().unwrap());
} }
} }
Ok(funcs) Ok(parsed)
} }
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> { fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
@ -547,8 +544,8 @@ struct LuaFunctionAttrs {
metamethod: Option<Metamethod>, metamethod: Option<Metamethod>,
} }
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> { fn parse_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
let mut funcs = vec![]; let mut parsed = vec![];
for item in imp.items.iter_mut() { for item in imp.items.iter_mut() {
if let ImplItem::Fn(func) = item if let ImplItem::Fn(func) = item
@ -582,23 +579,36 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
params.push(parse_quote_spanned!(variadic.span() => variadic!())); // luaify builtin macro params.push(parse_quote_spanned!(variadic.span() => variadic!())); // luaify builtin macro
} }
let attrs = parse_lua_function_attrs(&mut func.attrs)?; parsed.push(LuaFunction {
attrs.metamethod.map(|mm| document_metamethod(func, mm));
func.sig.asyncness.is_some().then(|| document_async(func));
document_lua_function(func);
funcs.push(LuaFunction {
name: func.sig.ident.clone(), name: func.sig.ident.clone(),
params, params,
body: func.block.clone(), body: func.block.clone(),
attrs, attrs: parse_lua_function_attrs(&mut func.attrs)?,
}); });
document_lua_function(func, parsed.last().unwrap());
stub_lua_function(func)?; stub_lua_function(func)?;
} }
} }
Ok(funcs) Ok(parsed)
}
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
let mut parsed = LuaFunctionAttrs::default();
let mut i = 0;
while let Some(attr) = attrs.get(i) {
if let Some(name) = attr.path().get_ident()
&& let Ok(method) = Metamethod::try_from(&name.unraw())
{
parsed.metamethod = Some(method);
attrs.remove(i);
} else {
i += 1;
}
}
Ok(parsed)
} }
fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> { fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
@ -649,23 +659,6 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
Ok(()) Ok(())
} }
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
let mut parsed = LuaFunctionAttrs::default();
let mut i = 0;
while let Some(attr) = attrs.get(i) {
if let Some(name) = attr.path().get_ident()
&& let Ok(method) = Metamethod::try_from(&name.unraw())
{
parsed.metamethod = Some(method);
attrs.remove(i);
} else {
i += 1;
}
}
Ok(parsed)
}
fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> { fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let luaify = quote!(#ffi::__internal::luaify!); let luaify = quote!(#ffi::__internal::luaify!);
@ -750,75 +743,176 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
Ok(()) Ok(())
} }
fn document_ffi_function(func: &mut ImplItemFn) { // the transparency makes it work in dark mode too
func.attrs.insert(0, parse_quote!(#[doc = // const FFI_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
r#"<span // const LUA_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
class="stab" const FALLIBLE_COLOR: &str = "rgba(255, 168, 168, 0.3)"; // red
title="This function is implemented in Rust and called via FFI." const ASYNC_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;" const METAMETHOD_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
>FFI</span>"#
])); struct StabConfig {
text: &'static str,
desc: &'static str,
color: &'static str,
strong: bool,
} }
fn document_lua_function(func: &mut ImplItemFn) { fn generate_stab(
func.attrs.insert(0, parse_quote!(#[doc = StabConfig {
r#"<span text,
class="stab" desc,
title="This function is implemented in Lua." color,
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;" strong,
>Lua</span>"# }: StabConfig,
])); ) -> Attribute {
let attrs = vec![
("class", "stab".into()),
("title", desc.into()),
("aria-label", desc.into()),
("style", {
let mut style = vec![
("float", "right"),
("margin", "1px"),
("margin-left", "4px"),
("padding-left", "5px"),
("padding-right", "5px"),
("background", color),
];
if strong {
style.push(("font-weight", "600"));
}
style
.into_iter()
.map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>()
.join("; ")
}),
];
let attrs = attrs
.into_iter()
.map(|(k, v)| format!(r#"{k}="{v}""#))
.collect::<Vec<_>>()
.join(" ");
let tag = format!(r#"<span {attrs}>{text}</span>"#);
parse_quote!(#[doc = #tag])
} }
fn document_async(func: &mut ImplItemFn) { fn document_ffi_function(item: &mut ImplItemFn, func: &FfiFunction) {
func.attrs.insert(0, parse_quote!(#[doc = let mut attrs = vec![];
r#"<span
class="stab" // attrs.push(generate_stab(StabConfig {
title="This function is asynchronous and will yield the calling thread." // text: "FFI",
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;" // desc: "This function is implemented in Rust and called via FFI.",
>Async</span>"# // color: FFI_COLOR,
])); // strong: true,
// }));
generate_stab_fallible(item).map(|stab| attrs.push(stab));
generate_stab_async(item).map(|stab| attrs.push(stab));
generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
attrs.push(stab);
item.attrs.push(help);
});
item.attrs.splice(0..0, attrs);
} }
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) { fn document_lua_function(item: &mut ImplItemFn, func: &LuaFunction) {
func.attrs.insert(0, parse_quote!(#[doc = let mut attrs = vec![];
r#"<span
class="stab"
title="This function is a metamethod."
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
>Metamethod</span>"#
]));
let doc = match method { // attrs.push(generate_stab(StabConfig {
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.", // text: "Lua",
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.", // desc: "This function is implemented in Lua.",
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.", // color: LUA_COLOR,
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.", // strong: true,
Metamethod::Concat => "This function is a metamethod which is called by the `..` operator.", // }));
Metamethod::Call => {
"This function is a metamethod which can be called by calling `(...)` on the value directly."
}
Metamethod::Add => "This function is a metamethod which is called by the `+` operator.",
Metamethod::Sub => "This function is a metamethod which is called by the `-` operator.",
Metamethod::Mul => "This function is a metamethod which is called by the `*` operator.",
Metamethod::Div => "This function is a metamethod which is called by the `/` operator.",
Metamethod::Mod => "This function is a metamethod which is called by the `%` operator.",
Metamethod::Pow => "This function is a metamethod which is called by the `^` operator.",
Metamethod::Unm => "This function is a metamethod which is called by the `-` operator.",
Metamethod::ToString => {
"This function is a metamethod which is called by the [`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) built-in function."
}
Metamethod::Pairs => {
"This function is a metamethod which is called by the [`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) built-in function."
}
Metamethod::Ipairs => {
"This function is a metamethod which is called by the [`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) built-in function."
}
_ => "This function is a metamethod and cannot be called directly.",
};
func.attrs.push(parse_quote!(#[doc = ""])); generate_stab_fallible(item).map(|stab| attrs.push(stab));
func.attrs.push(parse_quote!(#[doc = "# Metamethod"])); generate_stab_async(item).map(|stab| attrs.push(stab));
func.attrs.push(parse_quote!(#[doc = ""])); generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
func.attrs.push(parse_quote!(#[doc = #doc])); attrs.push(stab);
item.attrs.push(help);
});
item.attrs.splice(0..0, attrs);
}
fn generate_stab_async(item: &ImplItemFn) -> Option<Attribute> {
item.sig.asyncness.as_ref().map(|_| {
generate_stab(StabConfig {
text: "Async",
desc: "This function is asynchronous and will yield the calling thread.",
color: ASYNC_COLOR,
strong: false,
})
})
}
fn generate_stab_fallible(item: &ImplItemFn) -> Option<Attribute> {
match item.sig.output {
ReturnType::Default => None,
ReturnType::Type(_, ref ty) => is_resultlike(ty).map(|_| {
generate_stab(StabConfig {
text: "Fallible",
desc: "This function is fallible and may throw an error on failure.",
color: FALLIBLE_COLOR,
strong: false,
})
}),
}
}
fn generate_stab_metamethod(method: Option<Metamethod>) -> Option<(Attribute, Attribute)> {
method.map(|method| {
let stab = generate_stab(StabConfig {
text: "Metamethod",
desc: "This function is a metamethod.",
color: METAMETHOD_COLOR,
strong: false,
});
let help = match method {
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.",
Metamethod::Concat => {
"This function is a metamethod which is called by the `..` operator."
}
Metamethod::Call => {
"This function is a metamethod which can be called by calling \
`(...)` on the value directly."
}
Metamethod::Add => "This function is a metamethod which is called by the `+` operator.",
Metamethod::Sub => "This function is a metamethod which is called by the `-` operator.",
Metamethod::Mul => "This function is a metamethod which is called by the `*` operator.",
Metamethod::Div => "This function is a metamethod which is called by the `/` operator.",
Metamethod::Mod => "This function is a metamethod which is called by the `%` operator.",
Metamethod::Pow => "This function is a metamethod which is called by the `^` operator.",
Metamethod::Unm => "This function is a metamethod which is called by the `-` operator.",
Metamethod::ToString => {
"This function is a metamethod which is called by the \
[`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) \
built-in function."
}
Metamethod::Pairs => {
"This function is a metamethod which is called by the \
[`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) \
built-in function."
}
Metamethod::Ipairs => {
"This function is a metamethod which is called by the \
[`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) \
built-in function."
}
_ => "This function is a metamethod and cannot be called directly.",
};
let help = format!("\n# Metamethod\n\n{help}");
(stab, parse_quote!(#[doc = #help]))
})
} }

View File

@ -163,3 +163,19 @@ pub fn is_optionlike(ty: &Type) -> Option<&Type> {
None None
} }
} }
pub fn is_resultlike(ty: &Type) -> Option<&Type> {
if let Type::Path(path) = ty
&& path.path.leading_colon.is_none()
&& path.path.segments.len() == 1
&& let Some(segment) = path.path.segments.get(0)
&& segment.ident == "Result"
&& let PathArguments::AngleBracketed(ref angle) = segment.arguments
&& angle.args.len() <= 2 // allow Result<T, E> or Result<T>
&& let Some(GenericArgument::Type(ty)) = angle.args.get(0)
{
Some(ty)
} else {
None
}
}