Last active
June 5, 2025 19:51
-
-
Save cameronpcampbell/0a491ec5c4f2143e00b775f610424fd6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!strict | |
--!nolint LocalShadow | |
--> Table Helpers ---------------------------------------------------------------------- | |
--[=[ | |
Performs a swap remove on a table type. | |
@category Table | |
@param input { [any]: any } -- The table value to perform a swap remove on. | |
@param idx number -- The index to swap remove. | |
@param input_len number? -- You can optionally pass the length of the input. | |
@example | |
local tble = { 1, 2, 3, 4 } | |
table_value_swap_remove(tble, 2) | |
-- tble = { 1, 4, 3 } | |
]=] | |
type function table_value_swap_remove(input: { [any]: any }, idx: number, input_len: number?) | |
local input_len = input_len or #input | |
-- Last item, we can just remove from | |
-- the end of the input table. | |
if idx == input_len then | |
table.remove(input, idx) | |
-- Swap remove optimisation. | |
else | |
input[idx] = table.remove(input) | |
end | |
return input_len - 1 | |
end | |
---------------------------------------------------------------------------------------- | |
--> Negation Helpers ------------------------------------------------------------------- | |
--[=[ | |
Creates a negation with a specified amount of nestedness | |
@category Negation | |
@param input any -- The type to be negated. | |
@param amount any -- The amount of nestedness. | |
@example | |
local result = negation_nested(string, 3) | |
-- local result = ~~~string | |
]=] | |
type function negation_nested(input: type, amount: number) | |
for count = 1, amount do | |
input = types.negationof(input) | |
end | |
return input | |
end | |
---------------------------------------------------------------------------------------- | |
--> Inner Helpers ---------------------------------------------------------------------- | |
--[=[ | |
Gets the inner tag of a type. Recurses until it finds a non-negated type. | |
@category Inner | |
@param input any -- The type to get the inner tag of. | |
@example | |
local result = inner_tag_deep(types.negationof(types.negationof(types.negationof(types.string)))) | |
-- local result = string | |
]=] | |
type function inner_tag_deep(input: type) | |
local input_tag = input.tag | |
return if input_tag == "negation" then inner_tag_deep(input:inner()).tag else input_tag | |
end | |
--[=[ | |
Maps the innermost (recurses until it finds a non-negated type) value of a type. | |
@category Inner | |
@param input any -- The type to map the innermost value of. | |
@param callback any -- The function which maps the innermost value. | |
@example | |
local result = inner_map_deep(types.negationof(types.negationof(types.string)), function(inner: type, inner_tag: string) | |
return if inner_tag == "string" then types.number else inner | |
end) | |
-- local result = ~~number | |
]=] | |
type function inner_map_deep(input: type, callback: (inner: type, input_tag: string) -> type) | |
local input_tag = input.tag | |
if input_tag == "negation" then | |
local negation_nestedness = 0 | |
while true do | |
input = input:inner() | |
input_tag = input.tag | |
negation_nestedness += 1 | |
if input_tag ~= "negation" then break end | |
end | |
return negation_nested(callback(input, input_tag), negation_nestedness) | |
else | |
return callback(input, input_tag) | |
end | |
end | |
---------------------------------------------------------------------------------------- | |
--> Components Helpers ----------------------------------------------------------------- | |
--[=[ | |
Recursively filters a components table. A much better developer experience is provided via the `union_filter_deep` and `intersection_filter_deep` type functions. If every component in the table is filtered then `nil` is returned. | |
@category Component | |
@param input { [number]: type } -- the components table to filter. | |
@param as "union" | "intersection" -- The type tag which the components table belongs to. | |
@param as_builder (components: { [number]: type }) -> (typeof(types.unionof()) | typeof(types.intersectionof())) -- A function to turn inner component tables back into a type. | |
@param callback (component: type) -> boolean -- The function which filters the components. Return `true` to filter a component. | |
@example | |
local result = components_filter_deep( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)):components(), | |
"union", | |
union_from_components, | |
function(component: type) | |
return component == types.string | |
end | |
) | |
-- local result = { boolean, number } | |
]=] | |
type function components_filter_deep( | |
input: { [number]: type }, | |
as: "union" | "intersection", | |
as_builder: (components: { [number]: type }) -> (typeof(types.unionof()) | typeof(types.intersectionof())), | |
callback: (component: type) -> boolean | |
) | |
local input_len = #input | |
for idx = input_len, 1, -1 do | |
local component = input[idx] | |
if component.tag == as then | |
local filtered = as_builder(components_filter_deep(component:components(), as, as_builder, callback)) | |
if filtered == nil then | |
input_len = table_value_swap_remove(input, idx, input_len) | |
else | |
input[idx] = filtered | |
end | |
else | |
if not callback(component) then continue end | |
input_len = table_value_swap_remove(input, idx, input_len) | |
end | |
end | |
return input | |
end | |
--[=[ | |
Maps a component table. A much better developer experience is provided via the `union_map_shallow` and `intersection_map_shallow` type functions. | |
@category Component | |
@param input { [number]: type } -- the components table to map. | |
@param callback (component: type) -> boolean -- The function which maps the components. | |
@example | |
local result = components_map_shallow( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)):components(), | |
function(component: type) | |
return if component == types.string then types.singleton("mapped") else component | |
end | |
) | |
-- local result = { "mapped", (boolean | number) } | |
]=] | |
type function components_map_shallow( | |
input: { [number]: type }, | |
callback: (value: type, ...any) -> any | |
) | |
for idx, value in input do | |
input[idx] = callback(value) | |
end | |
return input | |
end | |
--[=[ | |
Recursively maps a component table. A much better developer experience is provided via the `union_map_deep` and `intersection_map_deep` type functions. | |
@category Component | |
@param input { [number]: type } -- the components table to map. | |
@param as "union" | "intersection" -- The type tag which the components table belongs to. | |
@param as_builder (components: { [number]: type }) -> (typeof(types.unionof()) | typeof(types.intersectionof())) -- A function to turn inner component tables back into a type. | |
@param callback (component: type) -> boolean -- The function which maps the components. | |
@example | |
local result = components_map_deep( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)):components(), | |
"union", | |
union_from_components, | |
function(component: type) | |
return if component == types.number then types.singleton("mapped") else component | |
end | |
) | |
-- local result = { ("mapped" | boolean), string } | |
]=] | |
type function components_map_deep( | |
input: { [number]: type }, | |
as: "union" | "intersection", | |
as_builder: (components: { [number]: type }) -> (typeof(types.unionof()) | typeof(types.intersectionof())), | |
callback: (value: type, ...any) -> any | |
) | |
for idx, value in input do | |
input[idx] = if value.tag == as then as_builder(components_map_deep(value:components(), as, as_builder, callback)) else callback(value) | |
end | |
return input | |
end | |
---------------------------------------------------------------------------------------- | |
--> Union Helpers ---------------------------------------------------------------------- | |
--[=[ | |
Turns a component table into a union. If the table has only one component then it returns that component, and if the component table is empty then it returns nil. | |
@category Component | |
@param input { [number]: type } -- the components table to turn into a union. | |
@example | |
local result = union_from_components({ types.string, types.number, types.boolean }) | |
-- local result = boolean | number | string | |
]=] | |
type function union_from_components(input: { [number]: type }) | |
local input_len = #input | |
return if input_len == 0 then nil elseif input_len == 1 then input[1] else types.unionof(table.unpack(input)) | |
end | |
--[=[ | |
Recursively filters a union (passing in a non-union will be treated as a single component). If every component in the union is filtered then `nil` is returned. | |
@category Component | |
@param input type -- the union to filter. | |
@param callback (component: type) -> boolean -- The function which filters the components. Return `true` to filter a component. | |
@example | |
local result = union_filter_deep( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)), | |
function(component: type) | |
return component == types.string | |
end | |
) or types.never | |
-- local result = boolean | number | |
]=] | |
type function union_filter_deep(input: type, callback: (component: type) -> boolean) | |
if input.tag == "union" then | |
return union_from_components(components_filter_deep(input:components(), "union", union_from_components, callback)) | |
else | |
return if callback(input) then nil else input | |
end | |
end | |
--[=[ | |
Maps a union. | |
@category Component | |
@param input type -- the union table to map. | |
@param callback (component: type) -> boolean -- The function which maps the unions components. | |
@example | |
local result = union_map_shallow( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)):components(), | |
function(component: type) | |
return if component == types.string then types.singleton("mapped") else component | |
end | |
) | |
-- local result = "mapped" | (boolean | number) | |
]=] | |
type function union_map_shallow(input: type, callback: (component: type) -> boolean) | |
if input.tag == "union" then | |
return union_from_components(components_map_shallow(input:components(), callback)) | |
else | |
return callback(input) | |
end | |
end | |
--[=[ | |
Recursively maps a union. | |
@category Component | |
@param input { [number]: type } -- the union to recursively map. | |
@param callback (component: type) -> boolean -- The function which maps the unions components. | |
@example | |
local result = union_map_deep( | |
types.unionof(types.string, types.unionof(types.number, types.boolean)):components(), | |
function(component: type) | |
return if component == types.number then types.singleton("mapped") else component | |
end | |
) | |
-- local result = ("mapped" | boolean) | string | |
]=] | |
type function union_map_deep(input: type, callback: (component: type) -> boolean) | |
if input.tag == "union" then | |
return union_from_components(components_map_deep(input:components(), "union", union_from_components, callback)) | |
else | |
return callback(input) | |
end | |
end | |
---------------------------------------------------------------------------------------- | |
--> Intersection Helpers --------------------------------------------------------------- | |
--[=[ | |
Turns a component table into an intersection. If the table has only one component then it returns that component, and if the component table is empty then it returns nil. | |
@category Component | |
@param input { [number]: type } -- the components table to turn into an intersection. | |
@example | |
local result = intersection_from_components({ types.string, types.number, types.boolean }) | |
-- local result = boolean & number & string | |
]=] | |
type function intersection_from_components(input: { [number]: type }) | |
local input_len = #input | |
return if input_len == 0 then nil elseif input_len == 1 then input[1] else types.intersectionof(table.unpack(input)) | |
end | |
--[=[ | |
Recursively filters an intersection (passing in a non-intersection will be treated as a single component). If every component in the intersection is filtered then `nil` is returned. | |
@category Component | |
@param input type -- the intersection to filter. | |
@param callback (component: type) -> boolean -- The function which filters the components. Return `true` to filter a component. | |
@example | |
local result = intersection_filter_deep( | |
types.intersectionof(types.string, types.intersectionof(types.number, types.boolean)), | |
function(component: type) | |
return component == types.string | |
end | |
) or types.never | |
-- local result = boolean & number | |
]=] | |
type function intersection_filter_deep(input: type, callback: (component: type) -> boolean) | |
if input.tag == "intersection" then | |
return intersection_from_components(components_filter_deep(input:components(), "intersection", intersection_from_components, callback)) | |
else | |
return if callback(input) then nil else input | |
end | |
end | |
--[=[ | |
Maps an intersection. | |
@category Component | |
@param input type -- the intersection table to map. | |
@param callback (component: type) -> boolean -- The function which maps the intersections components. | |
@example | |
local result = intersection_map_shallow( | |
types.intersectionof(types.string, types.intersectionof(types.number, types.boolean)):components(), | |
function(component: type) | |
return if component == types.string then types.singleton("mapped") else component | |
end | |
) | |
-- local result = "mapped" & (boolean & number) | |
]=] | |
type function intersection_map_shallow(input: type, input_tag: string, callback: (component: type) -> boolean) | |
if input_tag == "intersection" then | |
return intersection_from_components(components_map_shallow(input:components(), callback)) | |
else | |
return callback(input) | |
end | |
end | |
--[=[ | |
Recursively maps an intersection. | |
@category Component | |
@param input { [number]: type } -- the intersection to recursively map. | |
@param callback (component: type) -> boolean -- The function which maps the intersections components. | |
@example | |
local result = intersection_map_deep( | |
types.intersectionof(types.string, types.intersectionof(types.number, types.boolean)):components(), | |
function(component: type) | |
return if component == types.number then types.singleton("mapped") else component | |
end | |
) | |
-- local result = ("mapped" & boolean) & string | |
]=] | |
type function intersection_map_deep(input: type, input_tag: string, callback: (component: type) -> boolean) | |
if input_tag == "intersection" then | |
return intersection_from_components(components_map_deep(input:components(), "intersection", intersection_from_components, callback)) | |
else | |
return callback(input) | |
end | |
end | |
---------------------------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment