How do I indicate to intellisense that these functions return tables containing parameter contents?

I’ve been making two functions which let me create similar-functioning enums but for module-specific occasions. The process of making the enums is fine, but the intellisense doesn’t realize that the returned table contains all of a given table or that the table receives new indexes based on the strings in the inputted array.

Is there a way I can tell the code editor’s intellisense that it should also include a list of indexes based on what the function was given?

Code:

local function _et(t)
	function t:GetEnums()
		local c = {}
		for i,v in pairs(self) do
			if i ~= 'GetEnums' then
				table.insert(c, i)
			end
		end
		return c
	end
	return setmetatable({}, {__index = t, __metatable='NUH UH'})
end
local function _ev(names)
	local t = {}
	local p
	for i,n in ipairs(names) do
		t[n] = setmetatable({}, {__index = {
			Name = n,
			Value = i,
			Enum = p
		}, __metatable='NUH UH'})
	end
	function t:GetEnumItems()
		local c = {}
		for i,v in pairs(self) do
			if i ~= 'GetEnums' then
				c[v.Value] = v
			end
		end
		return c
	end
	p = setmetatable({}, {__index = t, __metatable='NUH UH'})
	return p
end

humanex.Enum = _et{
	Operation = _ev{
		'Subtract',
		'Divide',
		'MultiplyNegative',
		'Add',
		'Multiply',
		'DividePositive',
		'Custom',
	}
}

I haven’t found any potential solutions on how to make the intellisense work as I intend.

You can always create a custom type and declare it as that type.

It’s doesn’t seem like it’s that simple for my case. I’m basically asking how to make {a,b,c} turn into {a,b,c,GetEnums} without losing a,b,c in the intellisense. In addition, I’m also asking how I can turn [“x”,“y”,“z”] into {x=1,y=2,z=3} in a way the intellisense either understands or is told directly to understand on the fly rather than hard-coded.

If I understand correctly, you could use table intersections and type packs to “append” items to a table, but I would not recommend doing this.

It only works well in nonstrict mode, and requires ascribing a pseudo-type to the functions.

local _et = _et :: <Table>(Table: Table) -> Table & {GetEnumItems: (self: Table) -> {Name: string, Value: any, Enum: any}}
local _ev = _ev :: <Table>(Table: Table) -> Table & {GetEnums: (self: Table) -> {any}}
1 Like

Apart from the two functions having the functions mixed up with each other, _et() works exactly how I wanted it to. _ev() on the other hand doesn’t use the array of strings and make indexes out of them. Is there a way to handle this kind of case?

Also, I’d like to know if there’s a documentation on this kind of type declaration. It’d be nice to learn more about handling types for definitions.

I don’t think is possible right now to do it dynamically. What I had to do was to build everything statically and essentially “trick” the intellisense with custom classes and generics. If you would like my Enumeration model that you can use/copy from, feel free to take it:
Enumerations.rbxm (2.1 KB)

It contains 3 ModuleScripts,

  1. Enumerations
--!strict

--[=[
	Custom implementation of Roblox's Enums

	@class Enumeration
]=]
local Enumeration = require(script.Enumeration)
local EnumerationItem = require(script.EnumerationItem)

local Enumerations = {}

-- Enums equivalent
export type Enumerations = typeof(Enumerations)

-- Enum equivalent
export type Enumeration = typeof(Enumeration.new("" :: string, {} :: {[string]: EnumerationItem}))

-- EnumItem equivalent
export type EnumerationItem = typeof(EnumerationItem.new("" :: string, 0 :: number, {} :: Enumeration))

-- Enumerations.Direction
export type Direction = typeof(Enumerations.Direction.Forward)

Enumerations.Direction = Enumeration.new("Directions", {
	Forward = EnumerationItem.new("Forward", 1, Enumerations.Direction),
	Backward = EnumerationItem.new("Backward", 2, Enumerations.Direction),
	Left = EnumerationItem.new("Left", 3, Enumerations.Direction),
	Right = EnumerationItem.new("Right", 4, Enumerations.Direction),
	Up = EnumerationItem.new("Up", 5, Enumerations.Direction),
	Down = EnumerationItem.new("Down", 6, Enumerations.Direction)
})

-- Enumerations.Action
export type Action = typeof(Enumerations.Action.Walk)

Enumerations.Action = Enumeration.new("Action", {
	Walk = EnumerationItem.new("Walk", 1, Enumerations.Action),
	Run = EnumerationItem.new("Run", 2, Enumerations.Action),
	Dash = EnumerationItem.new("Dash", 3, Enumerations.Action),
	Jump = EnumerationItem.new("Jump", 4, Enumerations.Action)
})

do
	local self = setmetatable({}, {
		__index = Enumerations,
		__newindex = function(self: any, index: any): ()
			error(`{index} cannot be assigned to`, 0)
		end,
		__tostring = function(): string
			return script.Name
		end,
	}) :: any

	return self :: typeof(Enumerations)
end
  1. Enumeration (inside Enumerations)
--!strict

local EnumerationItem = require(script.Parent.EnumerationItem)
local Enumeration = {}

export type Enumeration<T, N, V> = T & {
	GetEnumItems: (self: Enumeration<T, N, V>) -> {EnumerationItem<T, N, V>}
}

export type EnumerationItem<T, N, V> = EnumerationItem.EnumerationItem<T, N, V>

function Enumeration.new<T, N, V>(name: string, enumerationItems: T): Enumeration<T, N, V>
	local enumeration = {}

	for enumerationItemName: string, enumerationItem: EnumerationItem<T, N, V> in enumerationItems :: any do
		enumeration[enumerationItemName] = enumerationItem
	end

	function enumeration:GetEnumItems(): {EnumerationItem<T, N, V>}
		local enumerationItemsToReturn: {EnumerationItem<T, N, V>} = {}

		for _: string, enumerationItem: EnumerationItem<T, N, V> in enumeration :: any do
			if typeof(enumerationItem) ~= "function" then
				table.insert(enumerationItemsToReturn, enumerationItem)
			end
		end

		return enumerationItemsToReturn
	end

	return setmetatable({}, {
		__name = name,
		__index = enumeration,
		__newindex = function(self: any, index: any): ()
			error(`{index} cannot be assigned to`, 0)
		end,
		__tostring = function(): string
			return name
		end,
	}) :: any
end

return Enumeration
  1. EnumerationItem (inside Enumerations)
--!strict

local EnumerationItem = {}

export type EnumerationItem<T, N, V> = {
	Name: N,
	Value: V,
	EnumType: T
}

function EnumerationItem.new<T, N, V>(name: N, value: V, enumType: T): EnumerationItem<T, N, V>
	local enumerationItem: EnumerationItem<T, N, V> = {
		Name = name,
		Value = value,
		EnumType = enumType
	}
	
	return setmetatable({}, {
		__index = enumerationItem,
		__newindex = function(self: any, index: any)
			error(`{index} cannot be assigned to`, 0)
		end,
		__tostring = function(): string
			return `{script.Parent.Name}.{getmetatable(enumerationItem.EnumType :: any).__name}.{name}`
		end,
	}) :: any
end

return EnumerationItem

I then just require my Enumerations and use it like I would with Enum:

--!strict

local Enumerations = require(script.Parent)

-- this will have full intellisense, even if you do
-- upDirection.EnumType
local upDirection: Enumerations.Direction = Enumerations.Direction.Up

-- this will error since it's not an action
local downDirection: Enumerations.Action = Enumerations.Direction.Down

local function someCallbackFunction(direction: Enumerations.Direction): ()
	if direction == upDirection then
		-- do something
	else
		-- do something else
	end
end

Unfortunately, I couldn’t do something like this as intellisense would just declare every type/generics as any and their types, and I couldn’t figure out a pattern that allows me to pass the enumType with this format without resorting to creating a whole different Enumeration class that is a copy of itself:

Actually I solved this. See latest edit for updated examples