Unexpected variadic casting behavior

When creating an array from variadic arguments, {... :: type} selects only the first value and ignores all subsequent values, whereas {...} selects all values. This behavior is unexpected and caused a bug in my code after I made a pass to add more thorough type-checking.

I don’t like using table.pack() as an alternative because it adds an extra field to the resulting array which will need to be removed before using the new iterator (i.e. for _ in Table do).

EDIT: I was just told that this behavior is intentional and happens any time ... is casted. I find it rather unintuitive that type casting in this one place is affecting code behavior in a way I’ve never seen before in Luau,.

Here is an example:

local function Good(...)
	return {...}
end
local function Bad(...)
	return {... :: any}
end
print(Good(1, 2), Bad(1, 2))

You will get two different tables:

{ [1] = 1, [2] = 2 }
{ [1] = 1 }
1 Like

... is not a value, it’s a pack of values, so giving it a value type doesn’t make sense (unless you are only getting the first value). That’s the sane explanation here.

Alternatively, this is normal behavior that you’ve just happened to encounter with types. This happens whenever you have an expression that expands into multiple values but mess with it in any way. You get the same result out of (...) or if condition then ... else 10 and similar.

Definitely unintuitive but I don’t know if worth considering a bug. Maybe they oughta make a lint for it?

The reported behavior is intentional because any type represents only a single value.
More can be found in the original design of it: Type ascriptions | Luau RFCs

Unfortunately, there is no value pack cast operator.

1 Like

Having a warning when cast is used directly on … or table.unpack might be a good idea (and if that’s intended, you can write (...) :: type to silence the warning).

2 Likes

Tuples being second class citizens in Lua strikes again! I think a warning would at least be wise.

1 Like

The cases you point out are the normal semantics that I’m okay with and which are unrelated to type checking.

cc @WheretIB

What’s confusing to me is that, as far as I am aware, in no other place in the language does performing a type cast influence the actual behavior of the code. Casting 2 :: string doesn’t cause the 2 to silently have tostring() called on it or something.

Yet it seems to me like an analogous thing is happening here, where casting is also silently selecting the first value of the tuple. But we already have a select() function, and the (...) syntax, and other semantics which cause the first value to be selected, so this extra behavior is unnecessary. I expected type casting to only change what the type checker sees, not what the language does.

Anyway, it’s too late for that to be fixed now I guess. Maybe one day we will get proper tuple types. Until then, I will start being a lot more careful about how I do type checking with variadics.