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 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!(
unsafe impl #ffi::Cdef for #ty {
@ -123,7 +123,7 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
.variants
.iter_mut()
.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 })))
})
.collect::<Result<Vec<_>>>()?;
@ -148,7 +148,7 @@ struct CFieldAttrs {
opaque: bool,
}
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
fn parse_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
match fields {
Fields::Named(fields) => fields.named.iter_mut(),
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),

View File

@ -1,6 +1,6 @@
use crate::utils::{
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
syn_assert, syn_error, ty_name,
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_resultlike, is_stringlike, is_unit,
pat_ident, syn_assert, syn_error, ty_name,
};
use darling::FromMeta;
use proc_macro2::TokenStream;
@ -78,7 +78,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
});
// 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 {
syn_assert!(
mms.insert(mm),
@ -91,7 +91,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
}
// 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 {
syn_assert!(
mms.insert(mm),
@ -230,8 +230,8 @@ struct FfiFunctionAttrs {
metamethod: Option<Metamethod>,
}
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
let mut funcs = vec![];
fn parse_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
let mut parsed = vec![];
for item in imp.items.iter_mut() {
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)?;
attrs.metamethod.map(|mm| document_metamethod(func, mm));
func.sig.asyncness.is_some().then(|| document_async(func));
document_ffi_function(func);
funcs.push(FfiFunction {
parsed.push(FfiFunction {
name: func.sig.ident.clone(),
params,
ret,
attrs,
attrs: parse_ffi_function_attrs(&mut func.attrs)?,
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> {
@ -547,8 +544,8 @@ struct LuaFunctionAttrs {
metamethod: Option<Metamethod>,
}
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
let mut funcs = vec![];
fn parse_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
let mut parsed = vec![];
for item in imp.items.iter_mut() {
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
}
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
attrs.metamethod.map(|mm| document_metamethod(func, mm));
func.sig.asyncness.is_some().then(|| document_async(func));
document_lua_function(func);
funcs.push(LuaFunction {
parsed.push(LuaFunction {
name: func.sig.ident.clone(),
params,
body: func.block.clone(),
attrs,
attrs: parse_lua_function_attrs(&mut func.attrs)?,
});
document_lua_function(func, parsed.last().unwrap());
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<()> {
@ -649,23 +659,6 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
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<()> {
let ffi = ffi_crate();
let luaify = quote!(#ffi::__internal::luaify!);
@ -750,53 +743,149 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
Ok(())
}
fn document_ffi_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span
class="stab"
title="This function is implemented in Rust and called via FFI."
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
>FFI</span>"#
]));
// the transparency makes it work in dark mode too
// const FFI_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
// const LUA_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
const FALLIBLE_COLOR: &str = "rgba(255, 168, 168, 0.3)"; // red
const ASYNC_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
const METAMETHOD_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
struct StabConfig {
text: &'static str,
desc: &'static str,
color: &'static str,
strong: bool,
}
fn document_lua_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span
class="stab"
title="This function is implemented in Lua."
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
>Lua</span>"#
]));
fn generate_stab(
StabConfig {
text,
desc,
color,
strong,
}: 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"));
}
fn document_async(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span
class="stab"
title="This function is asynchronous and will yield the calling thread."
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
>Async</span>"#
]));
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_metamethod(func: &mut ImplItemFn, method: Metamethod) {
func.attrs.insert(0, parse_quote!(#[doc =
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>"#
]));
fn document_ffi_function(item: &mut ImplItemFn, func: &FfiFunction) {
let mut attrs = vec![];
let doc = match method {
// attrs.push(generate_stab(StabConfig {
// text: "FFI",
// desc: "This function is implemented in Rust and called via FFI.",
// 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_lua_function(item: &mut ImplItemFn, func: &LuaFunction) {
let mut attrs = vec![];
// attrs.push(generate_stab(StabConfig {
// text: "Lua",
// desc: "This function is implemented in Lua.",
// color: LUA_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 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::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."
"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.",
@ -806,19 +895,24 @@ fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
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."
"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."
"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 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 = ""]));
func.attrs.push(parse_quote!(#[doc = "# Metamethod"]));
func.attrs.push(parse_quote!(#[doc = ""]));
func.attrs.push(parse_quote!(#[doc = #doc]));
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
}
}
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
}
}