luau
luau copied to clipboard
Cannot intelligently infer type of re-defined parameters with default values
When creating a parameter with an optional type, typically used for parameters with default values that are later assigned, the following error is raised during reassignment:
local iterations: number?
TypeError: Type 'number?' could not be converted into 'number' Luau(1000)
The parameter's inferred type is expected to be number (or the inferred type of the new value) after re-defining due to the impossibility of it's value being nil.
Minimal reproduction code snippet:
local function count(iterations: number?)
iterations = iterations or 10 -- type remains "number?"
for i = 1, iterations do -- error raised here
print(i)
end
end
Original issue (here)[https://github.com/JohnnyMorganz/luau-lsp/issues/32].
Type-casting the parameter upon usage and forcing the type to number silences the error despite the odd approach:
for i = 1, iterations::number do
print(i)
end
If there are any mistakes made/lacking information, do let me know and I will make edits/reply accordingly.
Yeah this is a known limitation due to the lack of feature called "type states" (briefly, any variable currently only has a single type in a single scope, short of assert statements, and assignments don't change it). It's on the roadmap for later this year.
Another example, I think this might also fall under the type states feature:
local Example: Instance? = workspace:FindFirstChild("SpecialPart")
if not Example then
Example = Instance.new("Part")
Example.Name = "SpecialPart"
-- ^ TypeError: Expected type table, got 'nil' instead
end
Has there been any motion on this? I've just run into it.
I don't know how the internals of the recent control flow analysis stuff works, but is it possible to maybe build off that to get something like the following to work?
local x: string? = nil
if not x then
x = "default"
end
-- x: string
i.e., treat a non-nil assignment to a variable in the same way if there was a return inside the if block?
Seems a little hacky, but it would solve this specific problem whilst we wait for something like type states
(Don't think it would be able to solve x = x or "default" though)
It's not a solution to the underlying problem, but after remembering that the Lua scoping rules only adds the binding for a local statement after compiling the expression, then this idiom works round this in a lot of circumstances:
local variable: string? = something()
local variable = variable or "defaultvalue"
Regarding the return-inside-an-if-block case: that doesn't change the type of the variable. You still need this:
-- x: string?
if not x then
return "default"
end
assert(x)
-- x: string
x is only known to be non-nullable in the else block in this situation.