Luau incorrectly views non-deterministic function calls as identical in else-if chains

Reproduction Steps

  1. Create a script
  2. Enable strict mode (–!strict)
  3. Write an if-elseif-else chain
  4. Use the same function call in each conditional statement
  5. See the warning

Expected Behavior
Functions that are non-deterministic or read from global variables should not be considered “repeated” even if they are called identically in an else-if chain.

Actual Behavior
When writing else-if chains in Luau with strict mode enabled, repeated non-deterministic function calls (such as math.random()) are incorrectly considered identical statements.
See this example:

image

Issue Area: Studio
Issue Type: Other
Impact: Low
Frequency: Constantly
Date First Experienced: 2021-08-25 00:08:00 (-05:00)
Date Last Experienced: 2021-08-25 00:08:00 (-05:00)

8 Likes

This seems to happen with nonstrict also, with the LuaU Powered Autocomplete & Language Features.
image

1 Like

Very interesting bug, wonder how Luau would actually add an edgecase for this considering the line is theoretically identical.

1 Like

Yeah it’s not clear how we can fix this to me. We could special-case math.random but that doesn’t seem reasonable to do. We could stop emitting this warning if we see function calls, but that is actually counter-productive in that it stops catching copy&paste errors in many common cases such as if foo:IsA("Part") then ... elseif foo:IsA("Part") then ... end

FWIW if this is an example from actual production code you can always rewrite it as:

local r = math.random()

if r > 0.5 then
  ...
elseif r > 0.25 then
  ...
else
  ...
end

which won’t trigger this warning but also makes it more obvious as to what the probabilities of individual cases are.

3 Likes

P.S. Note that you can always add --!nolint DuplicateCondition to the top of your script to silence this.

4 Likes

Are the linting macros documented somewhere?

2 Likes

There is a list of the warnings which can be produced and their names on the Luau website, and it describes how to disable them (--!nocheck, --!nolint NAME, and --!nolint).

2 Likes

We plan to switch Studio away from numeric codes as well to make things more obvious.

5 Likes

The only way I can think of for Luau to solve this problem precisely would be to recursively check the function for a read from any variable defined outside the function’s scope.

A close approximation would be to add compiler annotations to all built-in Roblox functions to tell it whether to consider a particular function deterministic or not. Then assume all non-annotated functions are non-deterministic by default. That would take care of the math.random() and foo:IsA("Part") examples.

1 Like

It won’t because it assumes you always know that foo is an Instance which will only work in type-annotated or strict code. In practice it’s very difficult to guarantee any determinism especially given the existence of metatables which means even if foo.X then elseif foo.X then end is potentially running non-deterministic code during two evaluations of .X

3 Likes