typeof now returns roblox type names

This commit is contained in:
Filip Tibell 2023-05-26 09:38:11 +02:00
parent 65ea0edc12
commit 151d8cc945
No known key found for this signature in database
8 changed files with 148 additions and 42 deletions

View file

@ -50,7 +50,7 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
} else { } else {
Err(LuaError::RuntimeError(format!( Err(LuaError::RuntimeError(format!(
"'{}' is not a valid attribute type", "'{}' is not a valid attribute type",
value.ty().variant_name() value.ty().variant_name().unwrap_or("???")
))) )))
} }
} }

View file

@ -116,7 +116,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
(LuaValue::UserData(u), d) => u.lua_to_dom_value(lua, Some(d)), (LuaValue::UserData(u), d) => u.lua_to_dom_value(lua, Some(d)),
(v, d) => Err(DomConversionError::ToDomValue { (v, d) => Err(DomConversionError::ToDomValue {
to: d.variant_name(), to: d.variant_name().unwrap_or("???"),
from: v.type_name(), from: v.type_name(),
detail: None, detail: None,
}), }),
@ -154,6 +154,16 @@ macro_rules! dom_to_userdata {
}; };
} }
/**
Converts a generic lua userdata to an rbx-dom type.
Since the type of the userdata needs to be specified
in an explicit manner, this macro syntax was chosen:
```rs
userdata_to_dom!(value_identifier as UserdataType => DomType)
```
*/
macro_rules! userdata_to_dom { macro_rules! userdata_to_dom {
($userdata:ident as $from_type:ty => $to_type:ty) => { ($userdata:ident as $from_type:ty => $to_type:ty) => {
match $userdata.borrow::<$from_type>() { match $userdata.borrow::<$from_type>() {
@ -212,7 +222,7 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
v => { v => {
Err(DomConversionError::FromDomValue { Err(DomConversionError::FromDomValue {
from: v.variant_name(), from: v.variant_name().unwrap_or("???"),
to: "userdata", to: "userdata",
detail: Some("Type not supported".to_string()), detail: Some("Type not supported".to_string()),
}) })
@ -282,7 +292,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
ty => { ty => {
return Err(DomConversionError::ToDomValue { return Err(DomConversionError::ToDomValue {
to: ty.variant_name(), to: ty.variant_name().unwrap_or("???"),
from: "userdata", from: "userdata",
detail: Some("Type not supported".to_string()), detail: Some("Type not supported".to_string()),
}) })
@ -305,11 +315,11 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum), value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces), value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font), value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
value if value.is::<NumberRange>() => userdata_to_dom!(value as NumberRange => dom::NumberRange), value if value.is::<NumberRange>() => userdata_to_dom!(value as NumberRange => dom::NumberRange),
value if value.is::<NumberSequence>() => userdata_to_dom!(value as NumberSequence => dom::NumberSequence), value if value.is::<NumberSequence>() => userdata_to_dom!(value as NumberSequence => dom::NumberSequence),
value if value.is::<Ray>() => userdata_to_dom!(value as Ray => dom::Ray), value if value.is::<Ray>() => userdata_to_dom!(value as Ray => dom::Ray),
value if value.is::<Rect>() => userdata_to_dom!(value as Rect => dom::Rect), value if value.is::<Rect>() => userdata_to_dom!(value as Rect => dom::Rect),
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
value if value.is::<Region3>() => userdata_to_dom!(value as Region3 => dom::Region3), value if value.is::<Region3>() => userdata_to_dom!(value as Region3 => dom::Region3),
value if value.is::<Region3int16>() => userdata_to_dom!(value as Region3int16 => dom::Region3int16), value if value.is::<Region3int16>() => userdata_to_dom!(value as Region3int16 => dom::Region3int16),
value if value.is::<UDim>() => userdata_to_dom!(value as UDim => dom::UDim), value if value.is::<UDim>() => userdata_to_dom!(value as UDim => dom::UDim),

View file

@ -1,13 +1,17 @@
use mlua::prelude::*;
use crate::instance::Instance;
use super::*; use super::*;
pub(super) trait DomValueExt { pub(super) trait DomValueExt {
fn variant_name(&self) -> &'static str; fn variant_name(&self) -> Option<&'static str>;
} }
impl DomValueExt for DomType { impl DomValueExt for DomType {
fn variant_name(&self) -> &'static str { fn variant_name(&self) -> Option<&'static str> {
use DomType::*; use DomType::*;
match self { Some(match self {
Axes => "Axes", Axes => "Axes",
BinaryString => "BinaryString", BinaryString => "BinaryString",
Bool => "Bool", Bool => "Bool",
@ -40,13 +44,54 @@ impl DomValueExt for DomType {
Vector3 => "Vector3", Vector3 => "Vector3",
Vector3int16 => "Vector3int16", Vector3int16 => "Vector3int16",
OptionalCFrame => "OptionalCFrame", OptionalCFrame => "OptionalCFrame",
_ => "?", _ => return None,
} })
} }
} }
impl DomValueExt for DomValue { impl DomValueExt for DomValue {
fn variant_name(&self) -> &'static str { fn variant_name(&self) -> Option<&'static str> {
self.ty().variant_name() self.ty().variant_name()
} }
} }
pub trait RobloxUserdataTypenameExt {
fn roblox_type_name(&self) -> Option<&'static str>;
}
impl<'lua> RobloxUserdataTypenameExt for LuaAnyUserData<'lua> {
#[rustfmt::skip]
fn roblox_type_name(&self) -> Option<&'static str> {
use super::types::*;
Some(match self {
value if value.is::<Axes>() => "Axes",
value if value.is::<BrickColor>() => "BrickColor",
value if value.is::<CFrame>() => "CFrame",
value if value.is::<Color3>() => "Color3",
value if value.is::<ColorSequence>() => "ColorSequence",
value if value.is::<ColorSequenceKeypoint>() => "ColorSequenceKeypoint",
value if value.is::<Enums>() => "Enums",
value if value.is::<Enum>() => "Enum",
value if value.is::<EnumItem>() => "EnumItem",
value if value.is::<Faces>() => "Faces",
value if value.is::<Font>() => "Font",
value if value.is::<Instance>() => "Instance",
value if value.is::<NumberRange>() => "NumberRange",
value if value.is::<NumberSequence>() => "NumberSequence",
value if value.is::<NumberSequenceKeypoint>() => "NumberSequenceKeypoint",
value if value.is::<PhysicalProperties>() => "PhysicalProperties",
value if value.is::<Ray>() => "Ray",
value if value.is::<Rect>() => "Rect",
value if value.is::<Region3>() => "Region3",
value if value.is::<Region3int16>() => "Region3int16",
value if value.is::<UDim>() => "UDim",
value if value.is::<UDim2>() => "UDim2",
value if value.is::<Vector2>() => "Vector2",
value if value.is::<Vector2int16>() => "Vector2int16",
value if value.is::<Vector3>() => "Vector3",
value if value.is::<Vector3int16>() => "Vector3int16",
_ => return None,
})
}
}

View file

@ -48,16 +48,11 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
.build_readonly()?, .build_readonly()?,
)? )?
.into_function()?; .into_function()?;
// We want the task scheduler to be transparent,
// but it does not return real lua threads, so
// we need to override some globals to fake it
let globals = lua.globals();
globals.set("type", lua.create_function(proxy_type)?)?;
globals.set("typeof", lua.create_function(proxy_typeof)?)?;
// Functions in the built-in coroutine library also need to be // Functions in the built-in coroutine library also need to be
// replaced, these are a bit different than the ones above because // replaced, these are a bit different than the ones above because
// calling resume or the function that wrap returns must return // calling resume or the function that wrap returns must return
// whatever lua value(s) that the thread or task yielded back // whatever lua value(s) that the thread or task yielded back
let globals = lua.globals();
let coroutine = globals.get::<_, LuaTable>("coroutine")?; let coroutine = globals.get::<_, LuaTable>("coroutine")?;
coroutine.set("status", lua.create_function(coroutine_status)?)?; coroutine.set("status", lua.create_function(coroutine_status)?)?;
coroutine.set("resume", lua.create_function(coroutine_resume)?)?; coroutine.set("resume", lua.create_function(coroutine_resume)?)?;
@ -98,30 +93,6 @@ fn task_delay(
sched.schedule_blocking_after_seconds(secs, tof.into_thread(lua)?, args) sched.schedule_blocking_after_seconds(secs, tof.into_thread(lua)?, args)
} }
/*
Type getter overrides for compat with task scheduler
*/
fn proxy_type<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
}
lua.named_registry_value::<_, LuaFunction>("type")?
.call(value)
}
fn proxy_typeof<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
}
lua.named_registry_value::<_, LuaFunction>("typeof")?
.call(value)
}
/* /*
Coroutine library overrides for compat with task scheduler Coroutine library overrides for compat with task scheduler
*/ */

View file

@ -1,6 +1,12 @@
use mlua::prelude::*; use mlua::prelude::*;
use crate::lua::stdio::formatting::{format_label, pretty_format_multi_value}; #[cfg(feature = "roblox")]
use lune_roblox::datatypes::extension::RobloxUserdataTypenameExt;
use crate::lua::{
stdio::formatting::{format_label, pretty_format_multi_value},
task::TaskReference,
};
// HACK: We need to preserve the default behavior of the // HACK: We need to preserve the default behavior of the
// print and error functions, for pcall and such, which // print and error functions, for pcall and such, which
@ -42,4 +48,30 @@ pub fn error(lua: &Lua, (arg, level): (LuaValue, Option<u32>)) -> LuaResult<()>
Ok(()) Ok(())
} }
pub fn proxy_type<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
}
lua.named_registry_value::<_, LuaFunction>("type")?
.call(value)
}
pub fn proxy_typeof<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
#[cfg(feature = "roblox")]
{
if let Some(type_name) = u.roblox_type_name() {
return lua.create_string(type_name);
}
}
}
lua.named_registry_value::<_, LuaFunction>("typeof")?
.call(value)
}
// TODO: Add an override for tostring that formats errors in a nicer way // TODO: Add an override for tostring that formats errors in a nicer way

View file

@ -37,6 +37,8 @@ pub fn create(lua: &'static Lua, args: Vec<String>) -> LuaResult<()> {
("print", lua.create_function(top_level::print)?), ("print", lua.create_function(top_level::print)?),
("warn", lua.create_function(top_level::warn)?), ("warn", lua.create_function(top_level::warn)?),
("error", lua.create_function(top_level::error)?), ("error", lua.create_function(top_level::error)?),
("type", lua.create_function(top_level::proxy_type)?),
("typeof", lua.create_function(top_level::proxy_typeof)?),
]; ];
// Set top-level globals // Set top-level globals

View file

@ -147,4 +147,6 @@ create_tests! {
roblox_instance_methods_is_a: "roblox/instance/methods/IsA", roblox_instance_methods_is_a: "roblox/instance/methods/IsA",
roblox_instance_methods_is_ancestor_of: "roblox/instance/methods/IsAncestorOf", roblox_instance_methods_is_ancestor_of: "roblox/instance/methods/IsAncestorOf",
roblox_instance_methods_is_descendant_of: "roblox/instance/methods/IsDescendantOf", roblox_instance_methods_is_descendant_of: "roblox/instance/methods/IsDescendantOf",
roblox_misc_typeof: "roblox/misc/typeof",
} }

View file

@ -0,0 +1,44 @@
local roblox = require("@lune/roblox") :: any
local TYPES_AND_VALUES = {
Axes = roblox.Axes.new(),
BrickColor = roblox.BrickColor.new("Really red"),
CFrame = roblox.CFrame.new(),
Color3 = roblox.Color3.new(0, 0, 0),
ColorSequence = roblox.ColorSequence.new(roblox.Color3.new(0, 0, 0)),
ColorSequenceKeypoint = roblox.ColorSequenceKeypoint.new(0, roblox.Color3.new(0, 0, 0)),
Enums = roblox.Enum,
Enum = roblox.Enum.KeyCode,
EnumItem = roblox.Enum.KeyCode.Unknown,
Faces = roblox.Faces.new(),
Font = roblox.Font.new("Gotham"),
NumberRange = roblox.NumberRange.new(0, 1),
NumberSequence = roblox.NumberSequence.new(0, 1),
NumberSequenceKeypoint = roblox.NumberSequenceKeypoint.new(0, 1),
PhysicalProperties = roblox.PhysicalProperties.new(1, 1, 1),
Ray = roblox.Ray.new(roblox.Vector3.zero, roblox.Vector3.one),
Rect = roblox.Rect.new(0, 0, 0, 0),
Region3 = roblox.Region3.new(roblox.Vector3.zero, roblox.Vector3.one),
Region3int16 = roblox.Region3int16.new(
roblox.Vector3int16.new(0, 0, 0),
roblox.Vector3int16.new(1, 1, 1)
),
UDim = roblox.UDim.new(0, 0),
UDim2 = roblox.UDim2.new(0, 0, 0, 0),
Vector2 = roblox.Vector2.new(0, 0),
Vector2int16 = roblox.Vector2int16.new(0, 0),
Vector3 = roblox.Vector3.new(0, 0),
Vector3int16 = roblox.Vector3int16.new(0, 0),
}
for name, value in TYPES_AND_VALUES :: { [string]: any } do
if typeof(value) ~= name then
error(
string.format(
"typeof() did not return correct value!\nExpected: %s\nActual: %s",
name,
typeof(value)
)
)
end
end