Accurately enumerate all control flow states

This commit is contained in:
AmberGraceSoftware 2023-08-06 17:49:56 -06:00
parent 1364b16ef9
commit a309ebbfcc
3 changed files with 124 additions and 22 deletions

View file

@ -14,9 +14,24 @@ enum class ControlFlow
None = 0b00001, None = 0b00001,
Returns = 0b00010, Returns = 0b00010,
Throws = 0b00100, Throws = 0b00100,
Break = 0b01000, // Currently unused. Breaks = 0b01000,
Continue = 0b10000, // Currently unused. 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) inline ControlFlow operator&(ControlFlow a, ControlFlow b)
{ {

View file

@ -24,6 +24,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauParseDeclareClassIndexer); LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
LUAU_FASTFLAG(LuauLoopControlFlowAnalysis);
namespace Luau namespace Luau
{ {
@ -536,11 +537,14 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatRepeat>()) else if (auto s = stat->as<AstStatRepeat>())
return visit(scope, s); return visit(scope, s);
else if (stat->is<AstStatBreak>() || stat->is<AstStatContinue>()) else if (stat->is<AstStatBreak>())
{ return FFlag::LuauLoopControlFlowAnalysis
// Nothing ? ControlFlow::Breaks
return ControlFlow::None; // TODO: ControlFlow::Break/Continue : ControlFlow::None;
} else if (stat->is<AstStatContinue>())
return FFlag::LuauLoopControlFlowAnalysis
? ControlFlow::Continues
: ControlFlow::None;
else if (auto r = stat->as<AstStatReturn>()) else if (auto r = stat->as<AstStatReturn>())
return visit(scope, r); return visit(scope, r);
else if (auto e = stat->as<AstStatExpr>()) 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); ScopePtr elseScope = childScope(ifStatement->elsebody ? ifStatement->elsebody : ifStatement, scope);
applyRefinements(elseScope, ifStatement->elseLocation.value_or(ifStatement->condition->location), refinementArena.negation(refinement)); 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 thencf = visit(thenScope, ifStatement->thenbody);
ControlFlow elsecf = ControlFlow::None; ControlFlow elsecf = ControlFlow::None;
if (ifStatement->elsebody) if (ifStatement->elsebody)
elsecf = visit(elseScope, 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); 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); scope->inheritRefinements(thenScope);
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws)) if (FFlag::LuauLoopControlFlowAnalysis)
return ControlFlow::Returns; {
else 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; 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) static bool occursCheck(TypeId needle, TypeId haystack)

View file

@ -366,11 +366,14 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
return check(scope, *while_); return check(scope, *while_);
else if (auto repeat = program.as<AstStatRepeat>()) else if (auto repeat = program.as<AstStatRepeat>())
return check(scope, *repeat); return check(scope, *repeat);
else if (program.is<AstStatBreak>() || program.is<AstStatContinue>()) else if (program.is<AstStatBreak>())
{ return FFlag::LuauLoopControlFlowAnalysis
// Nothing to do ? ControlFlow::Breaks
return ControlFlow::None; : ControlFlow::None;
} else if (program.is<AstStatContinue>())
return FFlag::LuauLoopControlFlowAnalysis
? ControlFlow::Continues
: ControlFlow::None;
else if (auto return_ = program.as<AstStatReturn>()) else if (auto return_ = program.as<AstStatReturn>())
return check(scope, *return_); return check(scope, *return_);
else if (auto expr = program.as<AstStatExpr>()) else if (auto expr = program.as<AstStatExpr>())
@ -763,22 +766,62 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location); ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
resolve(result.predicates, elseScope, false); resolve(result.predicates, elseScope, false);
const ControlFlow guardClauseFlows = FFlag::LuauLoopControlFlowAnalysis
? ExitingControlFlows
: ControlFlow::Returns | ControlFlow :: Throws;
ControlFlow thencf = check(thenScope, *statement.thenbody); ControlFlow thencf = check(thenScope, *statement.thenbody);
ControlFlow elsecf = ControlFlow::None; ControlFlow elsecf = ControlFlow::None;
if (statement.elsebody) if (statement.elsebody)
elsecf = check(elseScope, *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); 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); scope->inheritRefinements(thenScope);
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws)) if (FFlag::LuauLoopControlFlowAnalysis)
return ControlFlow::Returns; {
else 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; return ControlFlow::None;
} }
else else
{
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
{
return ControlFlow::Returns;
}
else
{
return ControlFlow::None;
}
}
}
else
{ {
check(thenScope, *statement.thenbody); check(thenScope, *statement.thenbody);