mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Accurately enumerate all control flow states
This commit is contained in:
parent
1364b16ef9
commit
a309ebbfcc
3 changed files with 124 additions and 22 deletions
|
@ -14,9 +14,24 @@ enum class ControlFlow
|
|||
None = 0b00001,
|
||||
Returns = 0b00010,
|
||||
Throws = 0b00100,
|
||||
Break = 0b01000, // Currently unused.
|
||||
Continue = 0b10000, // Currently unused.
|
||||
Breaks = 0b01000,
|
||||
Continues = 0b10000,
|
||||
|
||||
// Returns OR Throws (e.g. if predicate then return else error() end)
|
||||
MixedFunctionExit = 0b100000,
|
||||
|
||||
// Breaks OR Continues (e.g. if predicate then break else continue end)
|
||||
MixedLoopExit = 0b1000000,
|
||||
|
||||
// Exits a loop OR function (e.g. if prediate then continue else return end)
|
||||
MixedExit = 0b10000000,
|
||||
};
|
||||
// Bitmask of all control flows which exit the nearest function scope
|
||||
#define FunctionExitControlFlows ControlFlow::Returns | ControlFlow::Throws | ControlFlow::MixedFunctionExit
|
||||
// Bitmask of all control flows which exit the nearest loop scope
|
||||
#define LoopExitControlFlows ControlFlow::Breaks | ControlFlow::Continues | ControlFlow::MixedLoopExit
|
||||
// Bitmask of all control flows which exit the nearest function or loop scopes
|
||||
#define ExitingControlFlows ControlFlow::Returns | ControlFlow::Throws | ControlFlow::Breaks | ControlFlow::Continues | ControlFlow::MixedFunctionExit | ControlFlow::MixedLoopExit | ControlFlow::MixedExit
|
||||
|
||||
inline ControlFlow operator&(ControlFlow a, ControlFlow b)
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit);
|
|||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
|
||||
LUAU_FASTFLAG(LuauLoopControlFlowAnalysis);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -536,11 +537,14 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
|||
return visit(scope, s);
|
||||
else if (auto s = stat->as<AstStatRepeat>())
|
||||
return visit(scope, s);
|
||||
else if (stat->is<AstStatBreak>() || stat->is<AstStatContinue>())
|
||||
{
|
||||
// Nothing
|
||||
return ControlFlow::None; // TODO: ControlFlow::Break/Continue
|
||||
}
|
||||
else if (stat->is<AstStatBreak>())
|
||||
return FFlag::LuauLoopControlFlowAnalysis
|
||||
? ControlFlow::Breaks
|
||||
: ControlFlow::None;
|
||||
else if (stat->is<AstStatContinue>())
|
||||
return FFlag::LuauLoopControlFlowAnalysis
|
||||
? ControlFlow::Continues
|
||||
: ControlFlow::None;
|
||||
else if (auto r = stat->as<AstStatReturn>())
|
||||
return visit(scope, r);
|
||||
else if (auto e = stat->as<AstStatExpr>())
|
||||
|
@ -1066,20 +1070,60 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifSt
|
|||
ScopePtr elseScope = childScope(ifStatement->elsebody ? ifStatement->elsebody : ifStatement, scope);
|
||||
applyRefinements(elseScope, ifStatement->elseLocation.value_or(ifStatement->condition->location), refinementArena.negation(refinement));
|
||||
|
||||
const ControlFlow guardClauseFlows = FFlag::LuauLoopControlFlowAnalysis
|
||||
? ExitingControlFlows
|
||||
: ControlFlow::Returns | ControlFlow :: Throws;
|
||||
|
||||
ControlFlow thencf = visit(thenScope, ifStatement->thenbody);
|
||||
ControlFlow elsecf = ControlFlow::None;
|
||||
if (ifStatement->elsebody)
|
||||
elsecf = visit(elseScope, ifStatement->elsebody);
|
||||
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && elsecf == ControlFlow::None)
|
||||
if (matches(thencf, guardClauseFlows) && elsecf == ControlFlow::None)
|
||||
scope->inheritRefinements(elseScope);
|
||||
else if (thencf == ControlFlow::None && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
else if (thencf == ControlFlow::None && matches(elsecf, guardClauseFlows))
|
||||
scope->inheritRefinements(thenScope);
|
||||
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
return ControlFlow::Returns;
|
||||
else
|
||||
if (FFlag::LuauLoopControlFlowAnalysis)
|
||||
{
|
||||
if (thencf == elsecf)
|
||||
{
|
||||
return thencf;
|
||||
}
|
||||
if (
|
||||
matches(thencf, FunctionExitControlFlows)
|
||||
&& matches(elsecf, FunctionExitControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedFunctionExit;
|
||||
}
|
||||
if (
|
||||
matches(thencf, LoopExitControlFlows)
|
||||
&& matches(elsecf, LoopExitControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedLoopExit;
|
||||
}
|
||||
if (
|
||||
matches(thencf, ExitingControlFlows)
|
||||
&& matches(elsecf, ExitingControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedExit;
|
||||
}
|
||||
return ControlFlow::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
{
|
||||
return ControlFlow::Returns;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ControlFlow::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool occursCheck(TypeId needle, TypeId haystack)
|
||||
|
|
|
@ -366,11 +366,14 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
|
|||
return check(scope, *while_);
|
||||
else if (auto repeat = program.as<AstStatRepeat>())
|
||||
return check(scope, *repeat);
|
||||
else if (program.is<AstStatBreak>() || program.is<AstStatContinue>())
|
||||
{
|
||||
// Nothing to do
|
||||
return ControlFlow::None;
|
||||
}
|
||||
else if (program.is<AstStatBreak>())
|
||||
return FFlag::LuauLoopControlFlowAnalysis
|
||||
? ControlFlow::Breaks
|
||||
: ControlFlow::None;
|
||||
else if (program.is<AstStatContinue>())
|
||||
return FFlag::LuauLoopControlFlowAnalysis
|
||||
? ControlFlow::Continues
|
||||
: ControlFlow::None;
|
||||
else if (auto return_ = program.as<AstStatReturn>())
|
||||
return check(scope, *return_);
|
||||
else if (auto expr = program.as<AstStatExpr>())
|
||||
|
@ -763,20 +766,60 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
|
|||
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
|
||||
resolve(result.predicates, elseScope, false);
|
||||
|
||||
const ControlFlow guardClauseFlows = FFlag::LuauLoopControlFlowAnalysis
|
||||
? ExitingControlFlows
|
||||
: ControlFlow::Returns | ControlFlow :: Throws;
|
||||
|
||||
ControlFlow thencf = check(thenScope, *statement.thenbody);
|
||||
ControlFlow elsecf = ControlFlow::None;
|
||||
if (statement.elsebody)
|
||||
elsecf = check(elseScope, *statement.elsebody);
|
||||
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && elsecf == ControlFlow::None)
|
||||
if (matches(thencf, guardClauseFlows) && elsecf == ControlFlow::None)
|
||||
scope->inheritRefinements(elseScope);
|
||||
else if (thencf == ControlFlow::None && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
else if (thencf == ControlFlow::None && matches(elsecf, guardClauseFlows))
|
||||
scope->inheritRefinements(thenScope);
|
||||
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
return ControlFlow::Returns;
|
||||
else
|
||||
if (FFlag::LuauLoopControlFlowAnalysis)
|
||||
{
|
||||
if (thencf == elsecf)
|
||||
{
|
||||
return thencf;
|
||||
}
|
||||
if (
|
||||
matches(thencf, FunctionExitControlFlows)
|
||||
&& matches(elsecf, FunctionExitControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedFunctionExit;
|
||||
}
|
||||
if (
|
||||
matches(thencf, LoopExitControlFlows)
|
||||
&& matches(elsecf, LoopExitControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedLoopExit;
|
||||
}
|
||||
if (
|
||||
matches(thencf, ExitingControlFlows)
|
||||
&& matches(elsecf, ExitingControlFlows)
|
||||
)
|
||||
{
|
||||
return ControlFlow::MixedExit;
|
||||
}
|
||||
return ControlFlow::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
{
|
||||
return ControlFlow::Returns;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ControlFlow::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue