use core::cell::RefCell;
use luau_common::{BStr, BString};
use luau_vm::LuaDebug;
use luau_vm::thread::StackGuard;
use super::Function;
use crate::error::Error;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct FunctionInfo {
pub name: Option<BString>,
pub what: BString,
pub source: Option<BString>,
pub short_src: Option<BString>,
pub line_defined: Option<usize>,
pub num_upvalues: u8,
pub num_params: u8,
pub is_vararg: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CoverageInfo {
pub function: Option<BString>,
pub line_defined: i32,
pub depth: i32,
pub hits: Vec<i32>,
}
impl Function<'_> {
pub fn info(&self) -> Result<FunctionInfo, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
let mut debug = LuaDebug::default();
self.push_to(vm_thread)?;
if vm_thread
.get_info(-1, "snau", &mut debug)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?
== 0
{
return Err(Error::runtime("failed to read function debug info"));
}
let source = debug.source.as_bytes();
let short_src = debug.short_src();
Ok(FunctionInfo {
name: debug.name.map(|name| BString::from(name.as_bytes())),
what: BString::from(debug.what.as_bytes()),
source: (!source.is_empty()).then(|| BString::from(source)),
short_src: (!short_src.is_empty()).then(|| BString::from(short_src)),
line_defined: usize::try_from(debug.linedefined).ok(),
num_upvalues: debug.nupvals,
num_params: debug.nparams,
is_vararg: debug.is_vararg,
})
}
}
pub fn coverage<F>(&self, callback: F) -> Result<(), Error>
where
F: FnMut(CoverageInfo),
{
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(vm_thread)?;
if vm_thread.is_native_function(-1) != 0 {
return Err(Error::runtime(
"coverage is only available for Lua functions",
));
}
let callback = RefCell::new(callback);
vm_thread
.get_coverage(
-1,
(&callback as *const RefCell<F>).cast_mut().cast(),
coverage_callback::<F>,
)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
}
fn coverage_callback<F>(
context: *mut (),
function: Option<&BStr>,
line_defined: i32,
depth: i32,
hits: &[i32],
) where
F: FnMut(CoverageInfo),
{
unsafe {
let callback = &*context.cast::<RefCell<F>>();
let mut callback = callback.borrow_mut();
callback(CoverageInfo {
function: function.map(BString::from),
line_defined,
depth,
hits: hits.to_vec(),
});
}
}