New type solver incorrectly infers function return type as number?
I expect GetValidRandomNumber to return number because the while loop will never exit unless the number is returned, but the solver infers that it returns number?.
local function IsValid(x)
return x > 50
end
local function GetValidRandomNumber() -- number?
while true do
local id = math.random(100)
if IsValid(id) then
return id
end
end
end
I don't think this is a bug... It's theoretically possible that the while true loop would run indefinitely and not return anything, but the probability of that happening is equivalent to the probability of the IsValid function always returning false for an infinite sequence of independent random numbers (which is lim (n → ∞) (1/2)n ≈ 0, or speaking words, the probability of this event occurring is infinitesimally small). So the solver's infer is right?
(or it's just Luau being Luau I don't know ¯\_(ツ)_/¯)
It's theoretically possible that the while true loop would run indefinitely and not return anything ...
In that case the appropriate typing would be never: this function cannot return. Other languages sometimes break this out into it's own concept, like C++'s [[noreturn]]. I see PHP is adopting noreturn as a type as well (https://wiki.php.net/rfc/noreturn_type).
This is probably the same root cause as https://github.com/luau-lang/luau/issues/1269, which is that we're minting a refinement for id which is leaking into the next loop iteration.
In that case the appropriate typing would be
never: this function cannot return ...
Okay, so we got information that the solver infers the given GetValidRandomNumber() function returns number?, where the ? symbol at the end means that the return is optional. So it's either number or nil. Therefore the type never could be possible in this scenario only if the probability of infinite iteration were given as 100%. However, it approximates to 0%:
lim (n → ∞) (1/2)n ≈ 0
Sorry, to be clear I'm referring to the exact case when a function does not return (it diverges: gets stuck in a loop, throws an error, etc.). For this function as written there are two correct options (IMO):
- The solver infers
number: it sees there's exactly one reachablereturnstatement that always returns anumber. - The solver infers
number?: it sees there's onereturnstatement that always returns anumberand adds an implicitreturn nilat the end of a function. The implicit return can be seen if you run a program like:
local function f() end
print(f() == nil) -- prints "true"
(2) Is kinda correct, but I don't think Luau does that, nor does it want to: generally if someone writes a return x in their program, they expect it to return a value. We'd want (1), however, even if the function was:
local function IsValid(x)
return x > 101
end
local function GetValidRandomNumber()
while true do
local id = math.random(100)
if IsValid(id) then
return id
end
end
end
Luau should still infer number. You and I can see that's incorrect and the function will never return (unless math.random(100) decides to suddenly return a number greater than 100, IsValid(id) will never return true). There are some languages that allow you to encode ranges of numbers and might be able to infer this function never returns, but that's outside Luau's scope.
Oh I see what you mean now.
Okay, all jokes aside, the theoretical possibilities are too abstract to brag about, so I'll just say that the type system is quite wrong and you are quite correct :clap:
I believe this issue has been fixed.