mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Support Control Flow type Refinements for "break" and "continue" statements (#1004)
Fixes: https://github.com/Roblox/luau/issues/913 This PR adds support for type refinements around guard clauses that use `break` and `continue` statements inside a loop, similar to how guard clauses with `return` is supported. I had some free time today, so I figure I'd give a shot at a naïve fix for this at the very least. --- ## Resulting Change: Luau now supports type refinements within loops where a `continue` or `break` guard clause was used. For example: ```lua for _, object in objects :: {{value: string?}} do if not object.value then continue end local x: string = object.value -- OK; Used to emit "Type 'string?' could not be converted into 'string'" end ``` --------- Co-authored-by: Alexander McCord <amccord@roblox.com>
This commit is contained in:
parent
309001020a
commit
d00e93c82c
4 changed files with 786 additions and 18 deletions
|
@ -14,8 +14,8 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
inline ControlFlow operator&(ControlFlow a, ControlFlow b)
|
inline ControlFlow operator&(ControlFlow a, ControlFlow b)
|
||||||
|
|
|
@ -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);
|
||||||
LUAU_FASTFLAG(LuauFloorDivision);
|
LUAU_FASTFLAG(LuauFloorDivision);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -537,11 +538,10 @@ 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 ? ControlFlow::Breaks : ControlFlow::None;
|
||||||
// Nothing
|
else if (stat->is<AstStatContinue>())
|
||||||
return ControlFlow::None; // TODO: ControlFlow::Break/Continue
|
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>())
|
||||||
|
@ -1072,12 +1072,14 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifSt
|
||||||
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 (thencf != ControlFlow::None && 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 && elsecf != ControlFlow::None)
|
||||||
scope->inheritRefinements(thenScope);
|
scope->inheritRefinements(thenScope);
|
||||||
|
|
||||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
if (FFlag::LuauLoopControlFlowAnalysis && thencf == elsecf)
|
||||||
|
return thencf;
|
||||||
|
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||||
return ControlFlow::Returns;
|
return ControlFlow::Returns;
|
||||||
else
|
else
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
|
|
@ -38,6 +38,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||||
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauVariadicOverloadFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauVariadicOverloadFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
|
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
|
||||||
|
@ -350,11 +351,10 @@ 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 ? ControlFlow::Breaks : ControlFlow::None;
|
||||||
// Nothing to do
|
else if (program.is<AstStatContinue>())
|
||||||
return ControlFlow::None;
|
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>())
|
||||||
|
@ -752,12 +752,14 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
|
||||||
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 (thencf != ControlFlow::None && 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 && elsecf != ControlFlow::None)
|
||||||
scope->inheritRefinements(thenScope);
|
scope->inheritRefinements(thenScope);
|
||||||
|
|
||||||
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
if (FFlag::LuauLoopControlFlowAnalysis && thencf == elsecf)
|
||||||
|
return thencf;
|
||||||
|
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||||
return ControlFlow::Returns;
|
return ControlFlow::Returns;
|
||||||
else
|
else
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
|
|
@ -26,6 +26,52 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24})));
|
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
if not record.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({7, 34})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
if not record.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({7, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -48,6 +94,118 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({9, 24})));
|
CHECK_EQ("string", toString(requireTypeAtPosition({9, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
elseif not recordY.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({10, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
elseif not recordY.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({10, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
return
|
||||||
|
elseif not recordY.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({10, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
elseif not recordY.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({10, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -72,6 +230,66 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({11, 24})));
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
elseif math.random() > 0.5 then
|
||||||
|
break
|
||||||
|
elseif not recordY.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
elseif math.random() > 0.5 then
|
||||||
|
continue
|
||||||
|
elseif not recordY.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -96,6 +314,66 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_no
|
||||||
CHECK_EQ("string?", toString(requireTypeAtPosition({11, 24})));
|
CHECK_EQ("string?", toString(requireTypeAtPosition({11, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fallthrough")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
elseif math.random() > 0.5 then
|
||||||
|
break
|
||||||
|
elseif not recordY.value then
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
elseif math.random() > 0.5 then
|
||||||
|
continue
|
||||||
|
elseif not recordY.value then
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -122,6 +400,138 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
|
||||||
CHECK_EQ("string?", toString(requireTypeAtPosition({12, 24})));
|
CHECK_EQ("string?", toString(requireTypeAtPosition({12, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_not_z_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
local recordZ = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
elseif not recordY.value then
|
||||||
|
|
||||||
|
elseif not recordZ.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
local baz = recordZ.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({14, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({15, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
local recordZ = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
elseif not recordY.value then
|
||||||
|
|
||||||
|
elseif not recordZ.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
local baz = recordZ.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({14, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({15, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
local recordZ = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
elseif not recordY.value then
|
||||||
|
error("Y value not defined")
|
||||||
|
elseif not recordZ.value then
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
local baz = recordZ.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({14, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({15, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
local recordZ = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
return
|
||||||
|
elseif not recordY.value then
|
||||||
|
|
||||||
|
elseif not recordZ.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
local baz = recordZ.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({14, 38})));
|
||||||
|
CHECK_EQ("string?", toString(requireTypeAtPosition({15, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -142,6 +552,56 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({8, 24})));
|
CHECK_EQ("string", toString(requireTypeAtPosition({8, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
do
|
||||||
|
if not record.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({9, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
do
|
||||||
|
if not record.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({9, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -271,6 +731,126 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({11, 24})));
|
CHECK_EQ("string", toString(requireTypeAtPosition({11, 24})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recordY.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recordY.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recordY.value then
|
||||||
|
error("Y value not defined")
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||||
|
for i, recordX in x do
|
||||||
|
local recordY = y[i]
|
||||||
|
if not recordX.value then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recordY.value then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = recordX.value
|
||||||
|
local bar = recordY.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({12, 38})));
|
||||||
|
CHECK_EQ("string", toString(requireTypeAtPosition({13, 38})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -294,6 +874,62 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||||
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 29})));
|
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 29})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
if typeof(record.value) == "string" then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
type Foo = number
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo: Foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CHECK_EQ("Unknown type 'Foo'", toString(result.errors[0]));
|
||||||
|
|
||||||
|
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 43})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
if typeof(record.value) == "string" then
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
type Foo = number
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo: Foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CHECK_EQ("Unknown type 'Foo'", toString(result.errors[0]));
|
||||||
|
|
||||||
|
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 43})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -320,6 +956,62 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||||
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 29})));
|
CHECK_EQ("nil", toString(requireTypeAtPosition({8, 29})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_breaking")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
type Foo = number
|
||||||
|
|
||||||
|
if typeof(record.value) == "string" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo: Foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CHECK_EQ("Type 'nil' could not be converted into 'number'", toString(result.errors[0]));
|
||||||
|
|
||||||
|
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 43})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_continuing")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(x: {{value: string?}})
|
||||||
|
for _, record in x do
|
||||||
|
type Foo = number
|
||||||
|
|
||||||
|
if typeof(record.value) == "string" then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo: Foo = record.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CHECK_EQ("Type 'nil' could not be converted into 'number'", toString(result.errors[0]));
|
||||||
|
|
||||||
|
CHECK_EQ("nil", toString(requireTypeAtPosition({9, 43})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
@ -355,6 +1047,78 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||||
CHECK_EQ("Err<E>", toString(requireTypeAtPosition({16, 19})));
|
CHECK_EQ("Err<E>", toString(requireTypeAtPosition({16, 19})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Ok<T> = { tag: "ok", value: T }
|
||||||
|
type Err<E> = { tag: "err", error: E }
|
||||||
|
type Result<T, E> = Ok<T> | Err<E>
|
||||||
|
|
||||||
|
local function process<T, E>(results: {Result<T, E>})
|
||||||
|
for _, result in results do
|
||||||
|
if result.tag == "ok" then
|
||||||
|
local tag = result.tag
|
||||||
|
local val = result.value
|
||||||
|
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local tag = result.tag
|
||||||
|
local err = result.error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("\"ok\"", toString(requireTypeAtPosition({8, 39})));
|
||||||
|
CHECK_EQ("T", toString(requireTypeAtPosition({9, 39})));
|
||||||
|
|
||||||
|
CHECK_EQ("\"err\"", toString(requireTypeAtPosition({14, 35})));
|
||||||
|
CHECK_EQ("E", toString(requireTypeAtPosition({15, 35})));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{"LuauTinyControlFlowAnalysis", true},
|
||||||
|
{"LuauLoopControlFlowAnalysis", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Ok<T> = { tag: "ok", value: T }
|
||||||
|
type Err<E> = { tag: "err", error: E }
|
||||||
|
type Result<T, E> = Ok<T> | Err<E>
|
||||||
|
|
||||||
|
local function process<T, E>(results: {Result<T, E>})
|
||||||
|
for _, result in results do
|
||||||
|
if result.tag == "ok" then
|
||||||
|
local tag = result.tag
|
||||||
|
local val = result.value
|
||||||
|
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local tag = result.tag
|
||||||
|
local err = result.error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("\"ok\"", toString(requireTypeAtPosition({8, 39})));
|
||||||
|
CHECK_EQ("T", toString(requireTypeAtPosition({9, 39})));
|
||||||
|
|
||||||
|
CHECK_EQ("\"err\"", toString(requireTypeAtPosition({14, 35})));
|
||||||
|
CHECK_EQ("E", toString(requireTypeAtPosition({15, 35})));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||||
|
|
Loading…
Reference in a new issue