RobloxType function

This would behave like the type function but it would support roblox types.

Changing the type function to do this isn’t a valid solution because old code relies on it returning userdata for roblox objects.

This is necessary when creating APIs because the vast majority of the time you want a very specific value type to be sent through your function, and if the value isn’t the right type everything breaks. By testing the type you can provide yourself with a verbose error about exactly what the problem is instead of waiting for the code to break.

I’m aware you can do this with pcall but that’s extremely slow and prone to breaking when roblox adds new types.

I suggest that it should support and identify these types, in addition to every type supported by the original type function:

Object - All roblox objects Vector2 Vector2int16 Vector3 Vector3int16 Region3 Region3int16 Ray CFrame Enums - The Enum global Enum - A specific enum (NormalId, InOut, KeyCode, etc) EnumItem - A specific enum item (NormalId.Front, InOut.Out, KeyCode.Backspace, etc.) Axes BrickColor Color3 Faces UDim2 UDim RBXScriptSignal RBXScriptConnection
I don’t think I missed any

12 Likes

A while ago I wrote up a specification for how I would like to see a function like this implemented. I haven’t posted it anywhere yet, but I could dig it up if anyone is interested.

I’m interested!

1 Like

Alrighty, I touched it up a bit. Here’s the proposal:

1 Like

[quote] Alrighty, I touched it up a bit. Here’s the proposal:

Proposal to add function that returns internal value type · GitHub [/quote]

I disagree about the LuaType idea.

error('You provided a "..RobloxType(val))

Is a lot cleaner than

error("You provided a "..(RobloxType(val)~="LuaType" and RobloxType(val) or type(val)))

[quote] I disagree about the LuaType idea.

error('You provided a "..RobloxType(val))

Is a lot cleaner than

error("You provided a "..(RobloxType(val)~="LuaType" and RobloxType(val) or type(val)))

Unusually long post ahead.

My argument for it is the conflict between the names of Lua types and roblox types. Namely, the “string” type. Roblox’s internal “string” type is different from Lua’s: unlike Lua::string, a Roblox::string is null-terminated. If you allowed Lua types to return their exact name as usual, then there wouldn’t be a difference between the returned value of a Lua::string and a Roblox::string. Now, you can argue that one can never truly have a Roblox::string in a Lua context, so it doesn’t matter. But that goes against the entire purpose of the proposal, which is to make Roblox types distinct.

Doing it this way ties in well with the “sub-typing” pattern I mentioned. The RobloxType function doesn’t care exactly what the Lua type is, because it’s operating in the context of Roblox types. It’s the same as how the function doesn’t care that a SurfaceGui is anything more than an Instance, or NormalId an enum.

A potential solution might be to add a namespace to the returned type name. For example, [tt]Instance:SurfaceGui[/tt], [tt]Lua:string[/tt], [tt]Enum:NormalId[/tt], [tt]Enum:NormalId:Front[/tt]. This would skip the need to verify the types further, at the cost of longer type names. You’d also have to parse the name if you wanted the name without a namespace, which I greatly disapprove of.

Perhaps another solution might be to return multiple values. The first value could work as per the proposal. If the value has more type-ness to it, then that is returned in the second value, and so on. A few examples:

RobloxType(cframe) --> "CFrame"
RobloxType(surfaceGui) --> "Instance", "SurfaceGui"
RobloxType(Enum.NormalId) --> "Enum", "NormalId"
RobloxType(Enum.NormalId.Front) --> "Enum", "NormalId", "Front"

A reason I would disagree with both of these solutions is because it would require RobloxType to look at type information outside of its context, which could be doing more than it necessarily needs to, depending on how you use it. However, if I had to compromise, I would definitely favor the multiple-value solution.

I have to disagree with Anaminus here. Scripters don’t and shouldn’t have to know whether a string is a Lua string or a Roblox string. I think the function should act similar to type except that instead of returning “userdata” it returns a more specific type if possible. So rbxtype(newproxy()) would return “userdata”, but rbxtype(Vector3.new()) would return “Vector3”.

2 Likes

It’s very necessary to know that Roblox strings are null-terminated while Lua strings aren’t. Pretending that they are the same thing doesn’t help anyone in the long run.

Perhaps 5-10 people in all of ROBLOX, excluding staff, know that. So it’s clearly not essential information.

I support this and a group object

So you’re saying that it’s okay to ignore a particular behavior because only a few people happen to know about it at the moment? That’s bullshit.

Do you know why the wiki’s previous documentation system was a complete mess? Because no one new about inheritance. At the time, you could say that it was “not essential” in the same way. It wasn’t necessary to know that instances inherited from others. Obviously things have evolved since then. With the way Roblox wants to appeal to developers as a platform, things are going to evolve even further. The line of thinking that “not essential” information should be hidden or abstracted needs to stop.

Except this function will never be able to tell the difference.

Except this function will never be able to tell the difference.[/quote]
Of course not. I’m not asking for it to be able to tell the difference. You can never have a Roblox string in a Lua context, because it always gets converted to a Lua string first. Which means it is impossible to pass a Roblox string to the RobloxType function in the first place.

That said, there’s no excuse that passing a Lua string to this RobloxType function should return the value “string”, as if it were a Roblox string. What I’m arguing is that, in general, it is necessary to understand that Lua strings and Roblox strings are two distinct things. If this RobloxType function pretends that a Lua string is a Roblox string, then this distinction is lost.

How about we meet halfway and have the RobloxType function return “LuaString” or “RobloxString”? I’m mostly just against having to run two type functions in order to get the name of a value type.

RobloxType() should only work on ROBLOX-specific types. If it gets a type it doesn’t know about, it should return “Unknown”.

Except this function will never be able to tell the difference.[/quote]
Of course not. I’m not asking for it to be able to tell the difference. You can never have a Roblox string in a Lua context, because it always gets converted to a Lua string first. Which means it is impossible to pass a Roblox string to the RobloxType function in the first place.

That said, there’s no excuse that passing a Lua string to this RobloxType function should return the value “string”, as if it were a Roblox string. What I’m arguing is that, in general, it is necessary to understand that Lua strings and Roblox strings are two distinct things. If this RobloxType function pretends that a Lua string is a Roblox string, then this distinction is lost.[/quote]

Okay, I get your point. In that case, perhaps the function should either return nil or throw an error when a non-ROBLOX object is passed in, to indicate that such values should not be passed in.

I am going to bump this up.
This feature needs a heavy workaround that can easily break as Ethan said.

This code it long and uses a boat load of pcalls, and I would need to update it every update that adds a new userdata (ex: An old version said NumberRange was a Region3int16), and some userdata are impossible to figure out (ex: Vector3s vs Vector3int16s).

Seriously editted code I use these days:

local function GetType(Value)
	local function Set(Object, Property, Value)
		Object[Property] = Value
	end
	
	local function IsAColor3(Value)
		return pcall(Set, Instance.new('Color3Value'), 'Value', Value) == true
	end
	
	local function IsACoordinateFrame(Value)
		return pcall(Set, Instance.new('CFrameValue'), 'Value', Value) == true
	end
	
	local function IsABrickColor(Value)
		return pcall(Set, Instance.new('BrickColorValue'), 'Value', Value) == true
	end
	
	local function IsARay(Value)
		return pcall(Set, Instance.new('RayValue'), 'Value', Value) == true
	end
	
	local function IsAVector3(Value)
		return pcall(function() local Mag = Value.magnitude Set(Instance.new('Vector3Value'), 'Value', Value) local Value = Value.magnitude end) == true
	end
	
	local function IsARegion3(Value) 
		return pcall(function() local Size,CFrame = Value.Size,Value.CFrame end) == true
	end
	
	local function IsARegion3int16(Value)
		local Work,Return =  pcall(function() local Min,Max = Value.Min,Value.Max return type(Min) ~= "number" and type(Max) ~= "number" end)
		return Work == true and Return == true
	end
	
	local function IsAVector3int16(Value)
		--Not working.
	end
	
	local function IsAVector2(Value)
		return pcall(function() return Vector2.new() + Value end) == true
	end
	
	local function IsAUdim2(Value)
		return pcall(Set, Instance.new('Frame'), 'Position', Value) == true
	end
	
	local function IsAUDim(value)
		return pcall(function() return UDim.new() + value end) == true
	end
	
	local function IsAAxis(Value)
		return pcall(Set, Instance.new('ArcHandles'), 'Axes', Value) == true
	end
	
	local function IsAFace(Value)
		return pcall(Set, Instance.new('Handles'), 'Faces', Value) == true
	end
	
	local function IsAnEnumItem(Value)
		return string.sub(tostring(Value),1,5) == "Enum." and string.find(tostring(Value),"Enum%..-%.")
	end
	
	local function IsAnEnums(Value)
		return string.sub(tostring(Value),1,5) == "Enum." and string.find(tostring(Value),"Enum%..-%")
	end
	
	local function IsAnEnum(Value)
		return string.sub(tostring(Value),1,4) == "Enum"
	end
	
	local function IsAColorSequence(Value)
		local Worked,Return =  pcall(function() return type(Value.Keypoints[1].Value) ~= "number" end)
		return Worked == true and Return == true
	end
	
	local function IsAColorSequenceKeypoint(Value)
		local Worked,Return =  pcall(function() return type(Value.Value) ~= "number" end)
		return Worked == true and Return == true
	end
	
	local function IsANumberRange(Value)
		return pcall(function() local Min,Max = Value.Min,Value.Max end)
	end
	
	local function IsANumberSequence(Value)
		local Worked,Return =  pcall(function() return type(Value.Keypoints[1].Value) == "number" end)
		return Worked == true and Return == true
	end
	
	local function IsANumberSequenceKeypoint(Value)
		local Worked,Return =  pcall(function() return type(Value.Value) == "number" end)
		return Worked == true and Return == true
	end
	
	if IsAColor3(Value) then return 'Color3'
	elseif IsACoordinateFrame(Value) then return 'CFrame'
	elseif IsABrickColor(Value) then return 'BrickColor'
	elseif IsAUdim2(Value) then return 'UDim2'
	elseif IsAUDim(Value) then return 'UDim'
	elseif IsARegion3(Value) then return "Region3"
	elseif IsARegion3int16(Value) then return "Region3int16"
	elseif IsAVector3(Value) then return 'Vector3'
	elseif IsAVector2(Value) then return 'Vector2'
	elseif IsAnEnumItem(Value) then return "EnumItem"
	elseif IsAnEnums(Value) then return "Enums"
	elseif IsAnEnum(Value) then return "Enum"
	--elseif isAVector3int16(Value) then return 'Vector3int16' --Someone mind fixing?
	elseif IsARay(Value) then return 'Ray'
	elseif IsAAxis(Value) then return 'Axes'
	elseif IsAFace(Value) then return 'Faces'
	elseif IsAColorSequence(Value) then return "ColorSequence"
	elseif IsAColorSequenceKeypoint(Value) then return "ColorSequenceKeypoint"
	elseif IsANumberRange(Value) then return "NumberRange"
	elseif IsANumberSequence(Value) then return "NumberSequence"
	elseif IsANumberSequenceKeypoint(Value) then return "NumberSequenceKeypoint"
	else return nil 
	end
end

Extreme necro-bump, but I found a brilliant way to work around this using only a single pcall.

local function rbxType(this)
	local rawType = type(this)
	if rawType == "userdata" then
		local meta = getmetatable(this)
		if meta == "The metatable is locked" then
			local success,errorMsg = pcall(function ()
				return workspace:FindPartOnRay(this)
			end)
			if success then
				return "Ray"
			else
				local rbxType = errorMsg:sub(16,#errorMsg-7)
				if rbxType == "token" then
					return "EnumItem"
				else
					local asString = tostring(this)
					if rbxType == "int" then -- Most likely a BrickColor?
						if asString == "Medium stone grey" or tostring(BrickColor.new(asString)) ~= "Medium stone grey" then
							return "BrickColor"
						else -- ...okay?
							return rbxType
						end
					elseif rbxType == "void" then
						if asString == "Connection" then
							return "RBXScriptConnection"
						elseif asString:sub(1,6) == "Signal" then
							return "RBXScriptSignal"
						elseif asString == "Enums" then
							return asString
						elseif this.GetEnumItems then
							return "Enum"
						elseif this.Help then
							return "RbxLibrary"
						else
							return rbxType
						end
					else
						return rbxType
					end
				end
			end
		else -- newproxy?
			return rawType
		end
	else
		return rawType
	end
end

print(rbxType(Axes.new()))
print(rbxType(BrickColor.new()))
print(rbxType(CellId.new()))
print(rbxType(CFrame.new()))
print(rbxType(Color3.new()))
print(rbxType(ColorSequence.new(Color3.new())))
print(rbxType(ColorSequenceKeypoint.new(0,Color3.new())))
print(rbxType(Enum))
print(rbxType(Enum.NormalId))
print(rbxType(Enum.NormalId.Top))
print(rbxType(Faces.new()))
print(rbxType(Instance.new("Part")))
print(rbxType(LoadLibrary("RbxGui")))
print(rbxType(NumberRange.new(0)))
print(rbxType(NumberSequence.new(0)))
print(rbxType(NumberSequenceKeypoint.new(0,0)))
print(rbxType(PhysicalProperties.new(0)))
print(rbxType(Ray.new()))
print(rbxType(UDim.new()))
print(rbxType(UDim2.new()))
print(rbxType(Vector2.new()))
print(rbxType(Vector2int16.new()))
print(rbxType(Vector3.new()))
print(rbxType(Vector3int16.new()))
print(rbxType(newproxy(true)))
print(rbxType(workspace.Changed))
print(rbxType(workspace.Changed:connect(function () end)))
1 Like

This is coming soon TM

22 Likes

… oh yeah

1 Like