luau icon indicating copy to clipboard operation
luau copied to clipboard

Inaccuracies with "assert" return typing

Open nightcycle opened this issue 1 year ago • 6 comments

Currently the assert function type is defined as

function assert(value: a, errorMessage: string?): a

when in actuality assert returns a tuple, thus it should be some variant of

function assert(value: a, errorMessage: string?): (a, ...)

Under most circumstances this is fine, however recently it caused some headaches when I tried asserting a value directly into a table, leading to the second return variable, the error message itself, being added to the table.

nightcycle avatar Jul 18 '23 08:07 nightcycle

The problem is that assert only returns an error message if one is provided, so the most accurate return type would have to have either a constrained generic (which we don't have in luau yet) or an overloaded function, which would look something like this:

type assert = (<a>(value: a) -> (a)) & (<a>(value: a, errorMessage: string) -> (a, string))

(I'm also unsure why you included a variadic return type? Does assert ever return more than 2 values or was it just a placeholder?)

BizzarBlitz avatar Jul 18 '23 13:07 BizzarBlitz

Makes sense! I didn't really understand the underlying mechanism, so I didn't want to assume too much hence the (a, ...), your type definition is certainly better and should be used.

nightcycle avatar Jul 19 '23 16:07 nightcycle

The problem is that assert only returns an error message if one is provided, so the most accurate return type would have to have either a constrained generic (which we don't have in luau yet) or an overloaded function, which would look something like this:

type assert = (<a>(value: a) -> (a)) & (<a>(value: a, errorMessage: string) -> (a, string))

Actually, assert has a rather weird, and undocumented, behaviour. errorMessage can be any object (in fact, it is variadic) as long as value is truthy, but same argument can only be string or number otherwise (for some reason), and it only prints the first value passed to errorMessage .

Therefore, the more accurate type annotation would be like this:

type assert = ((value: false?, errorMessage: (string | number)?) -> never)
	    & (<V, A...>(value: V, A...) -> (V, A...))

BenMactavsin avatar Jul 19 '23 17:07 BenMactavsin

Interesting, is it possible that the usage of non-strings is deprecated? If not then a second variadic type would certainly be the best way to type it.

nightcycle avatar Jul 19 '23 17:07 nightcycle

Interesting, is it possible that the usage of non-strings is deprecated?

This implies that this behaviour was documented at some point, but a quick check of the official documentation for different versions of Lua reveals that it was never documented in the first place ever since the function was added back in Lua 2.4.

BenMactavsin avatar Jul 19 '23 18:07 BenMactavsin

Therefore, the more accurate type annotation would be like this:

type assert = ((value: false?, errorMessage: (string | number)?) -> never)
	    & (<V, A...>(value: V, A...) -> (V, A...))

I think it would be best to use the first annotation since this would just be confusing to newcomers. I doubt anyone in the history of Lua has actually used this behavior, and it would be best to keep it that way.

MagmaBurnsV avatar Jul 20 '23 19:07 MagmaBurnsV