luau icon indicating copy to clipboard operation
luau copied to clipboard

Cannot intelligently infer type of re-defined parameters with default values

Open cyrus01337 opened this issue 3 years ago • 5 comments

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.

cyrus01337 avatar Jun 12 '22 23:06 cyrus01337

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.

zeux avatar Jun 13 '22 18:06 zeux

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

JohnnyMorganz avatar Jun 18 '22 12:06 JohnnyMorganz

Has there been any motion on this? I've just run into it.

davidgiven avatar Mar 19 '23 14:03 davidgiven

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)

JohnnyMorganz avatar May 03 '23 11:05 JohnnyMorganz

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.

davidgiven avatar May 03 '23 13:05 davidgiven