[New Solver] Type Utilities [NOT COMPLETE]

Type Utilities [NOT COMPLETE]


[WARNINGS]

  • Since the new type solver is still in development, not all of the features will remain constant.
  • The names for the functions are also subject to change to make them more understandable, especially those that conflict with existing supported functions.

Purpose

The package helps speed up the process of debugging user-defined types, it also provides (or will*) necessary types to speed up the process of typing components, which allows for more time to be spent pondering over the design of the type rather than the implementation of the type.

The package is designed with the future in mind, not the present, so it will (currently) require copy/pasting some of the user-defined function types if you want to use them within your own user-defined function types. Otherwise you can treat them like generic types. Purely Typed Luau, so none of the code will do anything when run.

The user-defined type functions I have provided are NOT optimized and may have bugs. If you spot any bugs or have advice on how to make it faster or think that better names would suit certain functions, feel free to reply. Suggestions are most appreciated.

Utils

Name_<_List[Types]_>_ Description
equal< A, B > Whether two types are equivalent, it doesn’t matter whether it is a table, a singleton, or a function, everything is considered. Returns a boolean singleton type.
negate< A > Returns the negation of type a.
union< ... > Returns the union of the provided types.
assert< A > Throws an error if the provided type is false or nil.
assertf< A, B > Throws an error with the specified message (B) if the provided type is false or nil.
printType< A > Prints the variant of the type, whether it is a singleton, class, table, or anything.
prettyPrint< A > Pretty prints the type, whether it is a singleton, table, cyclic function, anything.
Util Examples

equal

-- Rarely used as types in practice, mainly used in user-defined types for validity
type Sample1 = equal<{[{a: number}]: number}, {[{a: number}]: number}>
type Sample2 = equal<<A,C,D,F...>(F...)->(A,C,D), <A,C,D,F...>(F...)->(A,C,D)>
type Sample3 = equal<{a: "Hello"}, {a: "hello"}>

local sample1: Sample1 -- true singleton type
local sample2: Sample2 -- true singleton type
local sample3: Sample3 -- false singleton type

negate

-- the type is not a string
type Sample = negate<string>

assert

-- Shows assertion failed
type Sample = assert<equal<"Hello", "World">>

assertf

-- Shows Unable to match values
type Sample = assertf<equal<"Hello", string>, "Unable to match values">

printType

-- Shows the type is singleton
type Sample = printType<"Hello World!">

prettyPrint

-- Shows "Hello World!"
type Sample = prettyPrint<"Hello World!">

Set

Name_<_List[Types]_>_ Description
constrain< A, B > Throws an error is the constrained type A doesn't fit into the constraint type B.
Set Examples

constrain

type Fruit = "Fruit" | string
type ConstrainToFruit<A> = constrain<A, Fruit>

-- Shows that "Apple cannot fit inside Fruit", 
--	BUT since "Apple" is a string, will still return itself
--	Only worry when the returned type is never
type Apple = ConstrainToFruit<"Apple">

How to Use

You can download the package here:
TypeUtils0.2.3.rbxm (15.1 KB)

You must first ensure that the New-Type Solver beta feature is enabled.
To access the Util type functions, require TypeUtilities/variant/Utils.
To access the Set type functions, require TypeUtilities/variant/Set.
I will try to find a way to use TypeUtilities to store all of the types for convenience, but it may not be possible.

Plans

  • Table accessing, combining, and setting functions (may include some aspects of table library)
  • Boolean logical functions (or, and, xor, …)
  • Number arithmetic functions (math library and bit32 library and <= / <)
  • String base functions (just string library)
2 Likes

Pls add a bounded generic type function like this

--constrain<> should take the generic type as the first arg, and the type it should be constrained to as the 2nd arg
-- If the first arg doesnt fit into the 2nd arg, it should error
local function func<T, U>(a: constrain<T, ~U>, b: constrain<U, ("A" | "B" | "C") | Instance>)

end

Can do, although I don’t know if it’ll work properly with generic types that aren’t immediately set.
There are a few functions I could think of that are similar, so I’ll probably group them together under a different umbrella than Utils.

1 Like

[Updated] Version 0.1.1

Utils

  1. Changed prettyPrintType’s name to be prettyPrint (it doesn’t print the type, it prints EVERYTHING about the type, excluding the metatable)
  2. Changed prettyPrint to be more consistent with the hover tips (*CYCLIC* instead of [CYCLIC])
  3. Made prettyPrint more readable (the code was horrid prior)
  4. Added assert and assertf (since you way want to force equivalence)
  5. Added intersect and union
  6. Removed printValuesOfType and printValuesOfType (will be adding Table.filter functions so this becomes redundant)
1 Like

I will have to change them, had certain expectations about how types.intersectionof and types.unionof functions behaved by default, but these expectations aren’t met.

For intersections (and):

You assume that if you say:

type Greeting = "Hello" & "Hi" & "Salutations"

it will show an error, since this is IMPOSSIBLE, but it doesn’t error. You cannot have multiple different singleton values intersected, UNLESS you want to treat it as concatenation, but that is not assumed initially when you read it. You expect it to work for certain situations, like:

type Greeting = any & string & "Hello"

since you know that "Hello" is considered to be both any AND a string, but if we try to say it can be a number then that would not be possible.

So the approach I will take is as follows:

  • Should ANY of the types input be a singleton and any of the other singletons not match the initial singleton’s value, it will error.
  • Should ANY of the types input be a singleton, the output WILL be a singleton and NOT an intersection, UNLESS there is a type conflict, in which case it will error. A string singleton cannot be nil, number, table, buffer, thread, class, function, generic, negation, union, intersection, or more.
  • Should ANY of the types input conflict, it will error, as table and string cannot mesh.
  • Remove duplicates.

For unions (or):

Unions are to be alot less strict, I may just remove duplicates. The below type makes sense, as it can be interpreted as “One of the following can be used as a greeting”.

type Greeting = "Hello" | "Hi" | "Salutations"

[Updated] Version 0.2.3

0.1.2
	Utils
		Added support for buffer and thread types
		Made equal more readable
		Updated assert to be an example of how to use assertf
0.2.3
	Utils
		Modified negate to be more consistent with 'not'
		Modified union to remove duplicates and recursively 
			combine unions prior
		Temporarily removed intersection until completion
		Fixed a bug with equal in regards to negate
		Identified a bug within equal caused by the new solver.
			type A = equal<{a: number}, {b: number}> -- true, should be false
			type A = equal<{a: number, b: string}, {c: number, d: string}> -- true, should be false
	Set
		Added constrain to force a type to fit within another

I am still working on this, I am redesigning everything and would like to express that foreach<{foo: bar}, (key: (), value: ())->()> is quite overpowered when it comes to tables.

If there are any suggestions, feel free to share.

foreach<X,Y>: Modifies all values within X to be Y. If Y is a function, the parameters are (key, value) and the first return type is the value to set. All empty will be assumed to be any, which may seem confusing, but it being nil would yield incorrect answers when nothing is input. The returned type will be a table that has been modified according to Y.

Later versions will perform simplifications to the input table type (X) and work with key/value unions. The return value for Y will not be modified. Union or intersections of Y will not be supported if Y has a function. If you want to set a function as the returned type, you will have to use a function as Y and have the function type that you wish to return be the return value for Y.

Example Usage (this is VERY powerful):

-- Provided type functions for tables:
foreach<X,Y>
read<X> -- Converts all properties to readonly
write<X> -- Converts all properties to writeonly
get<X,Y> --Similar to index, but returns nil if it doesn't exist
rawgetmetatable<X> -- Bypasses __metatable
rawsetmetatable<X,Y> -- Bypasses __metatable, allows for Y to be nil, removes metatable
create<X> -- Used within type functions to help, X is a table, not a table type
----

-- Only keeps Y properties
filter<X,Y> = foreach<X, (key: ~Y)->(nil)>
-- Does no change, just performs the simplification
combine<X> = foreach<X, (key: nil)->(nil)>

rawaccess<X,Y> = get< rawsetmetatable< filter<X,Y>, nil >, Y >
access<X,Y> = get< filter<X,Y>, Y >
rawmodify<X,Y,Z> = foreach<X, (key: Y)->(Z)>
modify<X,Y,Z> -- not possible since it relies on recursion, so it must be provided
remove<X,Y> = rawmodify<X,Y,nil>

-- Modifies all properties from Y onto X
rawupdate<X,Y> = combine<X & Y>
update<X,Y> -- not possible since it relies on recursion, so it must be provided

-- gets all keys with a certain value type
find<X,Y> = keyof< foreach<X, (key: any, value: ~Y)->(nil)> >
clear<X> = filter<X, ~any> -- becomes (any)->(nil)
clone<X> = combine<X>

-- Sets Y keys to readonly
readonly<X,Y> = combine< read<filter<X,Y>> & filter<X,~Y> >
writeonly<X,Y> = combine< write<filter<X,Y>> & filter<X,~Y> >

freeze<X> = read<combine<X>>
unfreeze<X> = combine< read<combine<X>>, write<combine<X>> >

Instead of having 23 utility types for tables, we will only have 9, with the rest being provided here and in the documentation so it can be recreated as needed. I disregarded insert, pack, unpack, move, concat, and sort as they are useful for arrays, but table types are mostly dictionaries or mixed.