mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/597
This commit is contained in:
parent
81681e2948
commit
3bfc864280
49 changed files with 2006 additions and 316 deletions
|
@ -106,6 +106,14 @@ struct ConstraintGraphBuilder
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||||
std::vector<RequireCycle> requireCycles);
|
std::vector<RequireCycle> requireCycles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||||
|
* of scopes, constraints, and free types that can be solved later.
|
||||||
|
* @param block the root block to generate constraints for.
|
||||||
|
*/
|
||||||
|
void visitModuleRoot(AstStatBlock* block);
|
||||||
|
|
||||||
|
private:
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type belonging to a given scope.
|
* Fabricates a new free type belonging to a given scope.
|
||||||
* @param scope the scope the free type belongs to.
|
* @param scope the scope the free type belongs to.
|
||||||
|
@ -143,13 +151,6 @@ struct ConstraintGraphBuilder
|
||||||
|
|
||||||
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
||||||
|
|
||||||
/**
|
|
||||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
|
||||||
* of scopes, constraints, and free types that can be solved later.
|
|
||||||
* @param block the root block to generate constraints for.
|
|
||||||
*/
|
|
||||||
void visit(AstStatBlock* block);
|
|
||||||
|
|
||||||
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
|
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
|
||||||
|
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStat* stat);
|
ControlFlow visit(const ScopePtr& scope, AstStat* stat);
|
||||||
|
@ -172,7 +173,8 @@ struct ConstraintGraphBuilder
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
||||||
|
|
||||||
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||||
InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
InferencePack checkPack(
|
||||||
|
const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {}, bool generalize = true);
|
||||||
|
|
||||||
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call);
|
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call);
|
||||||
|
|
||||||
|
@ -182,10 +184,11 @@ struct ConstraintGraphBuilder
|
||||||
* @param expr the expression to check.
|
* @param expr the expression to check.
|
||||||
* @param expectedType the type of the expression that is expected from its
|
* @param expectedType the type of the expression that is expected from its
|
||||||
* surrounding context. Used to implement bidirectional type checking.
|
* surrounding context. Used to implement bidirectional type checking.
|
||||||
|
* @param generalize If true, generalize any lambdas that are encountered.
|
||||||
* @return the type of the expression.
|
* @return the type of the expression.
|
||||||
*/
|
*/
|
||||||
Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional<TypeId> expectedType = {},
|
Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional<TypeId> expectedType = {},
|
||||||
bool forceSingleton = false);
|
bool forceSingleton = false, bool generalize = true);
|
||||||
|
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
|
@ -193,7 +196,7 @@ struct ConstraintGraphBuilder
|
||||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType);
|
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
|
||||||
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
||||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||||
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
15
Analysis/include/Luau/NonStrictTypeChecker.h
Normal file
15
Analysis/include/Luau/NonStrictTypeChecker.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Module.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct BuiltinTypes;
|
||||||
|
|
||||||
|
|
||||||
|
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module);
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -8,7 +8,6 @@
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -253,8 +252,10 @@ private:
|
||||||
|
|
||||||
void cloneChildren(FreeType* t)
|
void cloneChildren(FreeType* t)
|
||||||
{
|
{
|
||||||
// TODO: clone lower and upper bounds.
|
if (t->lowerBound)
|
||||||
// TODO: In the new solver, we should ice.
|
t->lowerBound = shallowClone(t->lowerBound);
|
||||||
|
if (t->upperBound)
|
||||||
|
t->upperBound = shallowClone(t->upperBound);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneChildren(GenericType* t)
|
void cloneChildren(GenericType* t)
|
||||||
|
@ -376,7 +377,11 @@ private:
|
||||||
|
|
||||||
void cloneChildren(TypeFamilyInstanceType* t)
|
void cloneChildren(TypeFamilyInstanceType* t)
|
||||||
{
|
{
|
||||||
// TODO: In the new solver, we should ice.
|
for (TypeId& ty : t->typeArguments)
|
||||||
|
ty = shallowClone(ty);
|
||||||
|
|
||||||
|
for (TypePackId& tp : t->packArguments)
|
||||||
|
tp = shallowClone(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneChildren(FreeTypePack* t)
|
void cloneChildren(FreeTypePack* t)
|
||||||
|
@ -416,7 +421,11 @@ private:
|
||||||
|
|
||||||
void cloneChildren(TypeFamilyInstanceTypePack* t)
|
void cloneChildren(TypeFamilyInstanceTypePack* t)
|
||||||
{
|
{
|
||||||
// TODO: In the new solver, we should ice.
|
for (TypeId& ty : t->typeArguments)
|
||||||
|
ty = shallowClone(ty);
|
||||||
|
|
||||||
|
for (TypePackId& tp : t->packArguments)
|
||||||
|
tp = shallowClone(tp);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -560,8 +569,6 @@ struct TypePackCloner
|
||||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||||
{
|
{
|
||||||
TypePackId cloned = clone(t.boundTo, dest, cloneState);
|
TypePackId cloned = clone(t.boundTo, dest, cloneState);
|
||||||
if (FFlag::DebugLuauCopyBeforeNormalizing)
|
|
||||||
cloned = dest.addTypePack(TypePackVar{BoundTypePack{cloned}});
|
|
||||||
seenTypePacks[typePackId] = cloned;
|
seenTypePacks[typePackId] = cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,8 +636,6 @@ void TypeCloner::operator()(const GenericType& t)
|
||||||
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
||||||
{
|
{
|
||||||
TypeId boundTo = clone(t.boundTo, dest, cloneState);
|
TypeId boundTo = clone(t.boundTo, dest, cloneState);
|
||||||
if (FFlag::DebugLuauCopyBeforeNormalizing)
|
|
||||||
boundTo = dest.addType(BoundType{boundTo});
|
|
||||||
seenTypes[typeId] = boundTo;
|
seenTypes[typeId] = boundTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,7 +706,7 @@ void TypeCloner::operator()(const FunctionType& t)
|
||||||
void TypeCloner::operator()(const TableType& t)
|
void TypeCloner::operator()(const TableType& t)
|
||||||
{
|
{
|
||||||
// If table is now bound to another one, we ignore the content of the original
|
// If table is now bound to another one, we ignore the content of the original
|
||||||
if (!FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo)
|
if (t.boundTo)
|
||||||
{
|
{
|
||||||
TypeId boundTo = clone(*t.boundTo, dest, cloneState);
|
TypeId boundTo = clone(*t.boundTo, dest, cloneState);
|
||||||
seenTypes[typeId] = boundTo;
|
seenTypes[typeId] = boundTo;
|
||||||
|
@ -718,9 +723,6 @@ void TypeCloner::operator()(const TableType& t)
|
||||||
|
|
||||||
ttv->level = TypeLevel{0, 0};
|
ttv->level = TypeLevel{0, 0};
|
||||||
|
|
||||||
if (FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo)
|
|
||||||
ttv->boundTo = clone(*t.boundTo, dest, cloneState);
|
|
||||||
|
|
||||||
for (const auto& [name, prop] : t.props)
|
for (const auto& [name, prop] : t.props)
|
||||||
ttv->props[name] = clone(prop, dest, cloneState);
|
ttv->props[name] = clone(prop, dest, cloneState);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -159,6 +160,25 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, NotNull<Normali
|
||||||
LUAU_ASSERT(module);
|
LUAU_ASSERT(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(scopes.empty());
|
||||||
|
LUAU_ASSERT(rootScope == nullptr);
|
||||||
|
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
||||||
|
rootScope = scope.get();
|
||||||
|
scopes.emplace_back(block->location, scope);
|
||||||
|
module->astScopes[block] = NotNull{scope.get()};
|
||||||
|
|
||||||
|
rootScope->returnType = freshTypePack(scope);
|
||||||
|
|
||||||
|
prepopulateGlobalScope(scope, block);
|
||||||
|
|
||||||
|
visitBlockWithoutChildScope(scope, block);
|
||||||
|
|
||||||
|
if (logger)
|
||||||
|
logger->captureGenerationModule(module);
|
||||||
|
}
|
||||||
|
|
||||||
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
|
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
|
||||||
{
|
{
|
||||||
return Luau::freshType(arena, builtinTypes, scope.get());
|
return Luau::freshType(arena, builtinTypes, scope.get());
|
||||||
|
@ -443,25 +463,6 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||||
addConstraint(scope, location, c);
|
addConstraint(scope, location, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(AstStatBlock* block)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(scopes.empty());
|
|
||||||
LUAU_ASSERT(rootScope == nullptr);
|
|
||||||
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
|
||||||
rootScope = scope.get();
|
|
||||||
scopes.emplace_back(block->location, scope);
|
|
||||||
module->astScopes[block] = NotNull{scope.get()};
|
|
||||||
|
|
||||||
rootScope->returnType = freshTypePack(scope);
|
|
||||||
|
|
||||||
prepopulateGlobalScope(scope, block);
|
|
||||||
|
|
||||||
visitBlockWithoutChildScope(scope, block);
|
|
||||||
|
|
||||||
if (logger)
|
|
||||||
logger->captureGenerationModule(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
ControlFlow ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
||||||
{
|
{
|
||||||
RecursionCounter counter{&recursionCount};
|
RecursionCounter counter{&recursionCount};
|
||||||
|
@ -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>())
|
||||||
|
@ -616,7 +616,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||||
// See the test TypeInfer/infer_locals_with_nil_value. Better flow
|
// See the test TypeInfer/infer_locals_with_nil_value. Better flow
|
||||||
// awareness should make this obsolete.
|
// awareness should make this obsolete.
|
||||||
|
|
||||||
if (!varTypes[i])
|
if (i < varTypes.size() && !varTypes[i])
|
||||||
varTypes[i] = freshType(scope);
|
varTypes[i] = freshType(scope);
|
||||||
}
|
}
|
||||||
// Only function calls and vararg expressions can produce packs. All
|
// Only function calls and vararg expressions can produce packs. All
|
||||||
|
@ -627,7 +627,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||||
if (hasAnnotation)
|
if (hasAnnotation)
|
||||||
expectedType = varTypes.at(i);
|
expectedType = varTypes.at(i);
|
||||||
|
|
||||||
TypeId exprType = check(scope, value, ValueContext::RValue, expectedType).ty;
|
TypeId exprType = check(scope, value, ValueContext::RValue, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty;
|
||||||
if (i < varTypes.size())
|
if (i < varTypes.size())
|
||||||
{
|
{
|
||||||
if (varTypes[i])
|
if (varTypes[i])
|
||||||
|
@ -645,7 +645,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||||
if (hasAnnotation)
|
if (hasAnnotation)
|
||||||
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
|
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
|
||||||
|
|
||||||
TypePackId exprPack = checkPack(scope, value, expectedTypes).tp;
|
TypePackId exprPack = checkPack(scope, value, expectedTypes, /*generalize*/ true).tp;
|
||||||
|
|
||||||
if (i < local->vars.size)
|
if (i < local->vars.size)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -1378,7 +1380,8 @@ InferencePack ConstraintGraphBuilder::checkPack(
|
||||||
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
||||||
}
|
}
|
||||||
|
|
||||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes)
|
InferencePack ConstraintGraphBuilder::checkPack(
|
||||||
|
const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes, bool generalize)
|
||||||
{
|
{
|
||||||
RecursionCounter counter{&recursionCount};
|
RecursionCounter counter{&recursionCount};
|
||||||
|
|
||||||
|
@ -1404,7 +1407,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
||||||
std::optional<TypeId> expectedType;
|
std::optional<TypeId> expectedType;
|
||||||
if (!expectedTypes.empty())
|
if (!expectedTypes.empty())
|
||||||
expectedType = expectedTypes[0];
|
expectedType = expectedTypes[0];
|
||||||
TypeId t = check(scope, expr, ValueContext::RValue, expectedType).ty;
|
TypeId t = check(scope, expr, ValueContext::RValue, expectedType, /*forceSingletons*/ false, generalize).ty;
|
||||||
result = InferencePack{arena->addTypePack({t})};
|
result = InferencePack{arena->addTypePack({t})};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1452,51 +1455,25 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
discriminantTypes.push_back(std::nullopt);
|
discriminantTypes.push_back(std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
Checkpoint startCheckpoint = checkpoint(this);
|
|
||||||
TypeId fnType = check(scope, call->func).ty;
|
TypeId fnType = check(scope, call->func).ty;
|
||||||
Checkpoint fnEndCheckpoint = checkpoint(this);
|
|
||||||
|
|
||||||
std::vector<std::optional<TypeId>> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType);
|
std::vector<std::optional<TypeId>> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType);
|
||||||
|
|
||||||
module->astOriginalCallTypes[call->func] = fnType;
|
module->astOriginalCallTypes[call->func] = fnType;
|
||||||
module->astOriginalCallTypes[call] = fnType;
|
module->astOriginalCallTypes[call] = fnType;
|
||||||
|
|
||||||
TypePackId expectedArgPack = arena->freshTypePack(scope.get());
|
|
||||||
TypePackId expectedRetPack = arena->freshTypePack(scope.get());
|
|
||||||
TypeId expectedFunctionType = arena->addType(FunctionType{expectedArgPack, expectedRetPack, std::nullopt, call->self});
|
|
||||||
|
|
||||||
TypeId instantiatedFnType = arena->addType(BlockedType{});
|
TypeId instantiatedFnType = arena->addType(BlockedType{});
|
||||||
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
|
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
|
||||||
|
|
||||||
NotNull<Constraint> extractArgsConstraint = addConstraint(scope, call->location, SubtypeConstraint{instantiatedFnType, expectedFunctionType});
|
Checkpoint argBeginCheckpoint = checkpoint(this);
|
||||||
|
|
||||||
// Fully solve fnType, then extract its argument list as expectedArgPack.
|
|
||||||
forEachConstraint(startCheckpoint, fnEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
|
||||||
extractArgsConstraint->dependencies.emplace_back(constraint.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
const AstExpr* lastArg = exprArgs.size() ? exprArgs[exprArgs.size() - 1] : nullptr;
|
|
||||||
const bool needTail = lastArg && (lastArg->is<AstExprCall>() || lastArg->is<AstExprVarargs>());
|
|
||||||
|
|
||||||
TypePack expectedArgs;
|
|
||||||
|
|
||||||
if (!needTail)
|
|
||||||
expectedArgs = extendTypePack(*arena, builtinTypes, expectedArgPack, exprArgs.size(), expectedTypesForCall);
|
|
||||||
else
|
|
||||||
expectedArgs = extendTypePack(*arena, builtinTypes, expectedArgPack, exprArgs.size() - 1, expectedTypesForCall);
|
|
||||||
|
|
||||||
std::vector<TypeId> args;
|
std::vector<TypeId> args;
|
||||||
std::optional<TypePackId> argTail;
|
std::optional<TypePackId> argTail;
|
||||||
std::vector<RefinementId> argumentRefinements;
|
std::vector<RefinementId> argumentRefinements;
|
||||||
|
|
||||||
Checkpoint argCheckpoint = checkpoint(this);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < exprArgs.size(); ++i)
|
for (size_t i = 0; i < exprArgs.size(); ++i)
|
||||||
{
|
{
|
||||||
AstExpr* arg = exprArgs[i];
|
AstExpr* arg = exprArgs[i];
|
||||||
std::optional<TypeId> expectedType;
|
|
||||||
if (i < expectedArgs.head.size())
|
|
||||||
expectedType = expectedArgs.head[i];
|
|
||||||
|
|
||||||
if (i == 0 && call->self)
|
if (i == 0 && call->self)
|
||||||
{
|
{
|
||||||
|
@ -1512,7 +1489,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
}
|
}
|
||||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||||
{
|
{
|
||||||
auto [ty, refinement] = check(scope, arg, ValueContext::RValue, expectedType);
|
auto [ty, refinement] =
|
||||||
|
check(scope, arg, ValueContext::RValue, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
|
||||||
args.push_back(ty);
|
args.push_back(ty);
|
||||||
argumentRefinements.push_back(refinement);
|
argumentRefinements.push_back(refinement);
|
||||||
}
|
}
|
||||||
|
@ -1526,12 +1504,6 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
|
|
||||||
Checkpoint argEndCheckpoint = checkpoint(this);
|
Checkpoint argEndCheckpoint = checkpoint(this);
|
||||||
|
|
||||||
// Do not solve argument constraints until after we have extracted the
|
|
||||||
// expected types from the callable.
|
|
||||||
forEachConstraint(argCheckpoint, argEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
|
||||||
constraint->dependencies.push_back(extractArgsConstraint);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (matchSetmetatable(*call))
|
if (matchSetmetatable(*call))
|
||||||
{
|
{
|
||||||
TypePack argTailPack;
|
TypePack argTailPack;
|
||||||
|
@ -1607,8 +1579,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
// This ensures, for instance, that we start inferring the contents of
|
// This ensures, for instance, that we start inferring the contents of
|
||||||
// lambdas under the assumption that their arguments and return types
|
// lambdas under the assumption that their arguments and return types
|
||||||
// will be compatible with the enclosing function call.
|
// will be compatible with the enclosing function call.
|
||||||
forEachConstraint(fnEndCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) {
|
forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) {
|
||||||
fcc->dependencies.emplace_back(constraint.get());
|
constraint->dependencies.emplace_back(fcc);
|
||||||
});
|
});
|
||||||
|
|
||||||
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
|
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
|
||||||
|
@ -1616,7 +1588,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(
|
Inference ConstraintGraphBuilder::check(
|
||||||
const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional<TypeId> expectedType, bool forceSingleton)
|
const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional<TypeId> expectedType, bool forceSingleton, bool generalize)
|
||||||
{
|
{
|
||||||
RecursionCounter counter{&recursionCount};
|
RecursionCounter counter{&recursionCount};
|
||||||
|
|
||||||
|
@ -1647,7 +1619,7 @@ Inference ConstraintGraphBuilder::check(
|
||||||
else if (auto call = expr->as<AstExprCall>())
|
else if (auto call = expr->as<AstExprCall>())
|
||||||
result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too
|
result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too
|
||||||
else if (auto a = expr->as<AstExprFunction>())
|
else if (auto a = expr->as<AstExprFunction>())
|
||||||
result = check(scope, a, expectedType);
|
result = check(scope, a, expectedType, generalize);
|
||||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||||
result = check(scope, indexName);
|
result = check(scope, indexName);
|
||||||
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||||
|
@ -1815,30 +1787,37 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
||||||
return Inference{result};
|
return Inference{result};
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType)
|
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize)
|
||||||
{
|
{
|
||||||
Checkpoint startCheckpoint = checkpoint(this);
|
Checkpoint startCheckpoint = checkpoint(this);
|
||||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||||
checkFunctionBody(sig.bodyScope, func);
|
checkFunctionBody(sig.bodyScope, func);
|
||||||
Checkpoint endCheckpoint = checkpoint(this);
|
Checkpoint endCheckpoint = checkpoint(this);
|
||||||
|
|
||||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
if (generalize)
|
||||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
{
|
||||||
|
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||||
|
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||||
|
|
||||||
Constraint* previous = nullptr;
|
Constraint* previous = nullptr;
|
||||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
|
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
|
||||||
gc->dependencies.emplace_back(constraint.get());
|
gc->dependencies.emplace_back(constraint.get());
|
||||||
|
|
||||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||||
{
|
{
|
||||||
if (previous)
|
if (previous)
|
||||||
constraint->dependencies.push_back(NotNull{previous});
|
constraint->dependencies.push_back(NotNull{previous});
|
||||||
|
|
||||||
previous = constraint.get();
|
previous = constraint.get();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Inference{generalizedTy};
|
return Inference{generalizedTy};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Inference{sig.signature};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||||
|
@ -2379,10 +2358,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||||
argTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
|
argTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
argTy = freshType(signatureScope);
|
|
||||||
|
|
||||||
if (i < expectedArgPack.head.size())
|
if (i < expectedArgPack.head.size())
|
||||||
addConstraint(signatureScope, local->location, SubtypeConstraint{argTy, expectedArgPack.head[i]});
|
argTy = expectedArgPack.head[i];
|
||||||
|
else
|
||||||
|
argTy = freshType(signatureScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
argTypes.push_back(argTy);
|
argTypes.push_back(argTy);
|
||||||
|
|
|
@ -1380,6 +1380,44 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
|
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We know the type of the function and the arguments it expects to receive.
|
||||||
|
// We also know the TypeIds of the actual arguments that will be passed.
|
||||||
|
//
|
||||||
|
// Bidirectional type checking: Force those TypeIds to be the expected
|
||||||
|
// arguments. If something is incoherent, we'll spot it in type checking.
|
||||||
|
//
|
||||||
|
// Most important detail: If a function argument is a lambda, we also want
|
||||||
|
// to force unannotated argument types of that lambda to be the expected
|
||||||
|
// types.
|
||||||
|
|
||||||
|
// FIXME: Bidirectional type checking of overloaded functions is not yet supported.
|
||||||
|
if (auto ftv = get<FunctionType>(fn))
|
||||||
|
{
|
||||||
|
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
|
||||||
|
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i)
|
||||||
|
{
|
||||||
|
const FunctionType* expectedLambdaTy = get<FunctionType>(follow(expectedArgs[i]));
|
||||||
|
const FunctionType* lambdaTy = get<FunctionType>(follow(argPackHead[i]));
|
||||||
|
const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as<AstExprFunction>();
|
||||||
|
|
||||||
|
if (expectedLambdaTy && lambdaTy && lambdaExpr)
|
||||||
|
{
|
||||||
|
const std::vector<TypeId> expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first;
|
||||||
|
const std::vector<TypeId> lambdaArgTys = flatten(lambdaTy->argTypes).first;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j)
|
||||||
|
{
|
||||||
|
if (!lambdaExpr->args.data[j]->annotation && get<FreeType>(follow(lambdaArgTys[j])))
|
||||||
|
{
|
||||||
|
asMutable(lambdaArgTys[j])->ty.emplace<BoundType>(expectedLambdaArgTys[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
||||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
||||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauNewNonStrictMode, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1257,7 +1258,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||||
ConstraintGraphBuilder cgb{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
ConstraintGraphBuilder cgb{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||||
logger.get(), NotNull{&dfg}, requireCycles};
|
logger.get(), NotNull{&dfg}, requireCycles};
|
||||||
|
|
||||||
cgb.visit(sourceModule.root);
|
cgb.visitModuleRoot(sourceModule.root);
|
||||||
result->errors = std::move(cgb.errors);
|
result->errors = std::move(cgb.errors);
|
||||||
|
|
||||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->humanReadableName, moduleResolver,
|
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->humanReadableName, moduleResolver,
|
||||||
|
|
87
Analysis/src/NonStrictTypeChecker.cpp
Normal file
87
Analysis/src/NonStrictTypeChecker.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/NonStrictTypeChecker.h"
|
||||||
|
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/Subtyping.h"
|
||||||
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/Error.h"
|
||||||
|
#include "Luau/TypeArena.h"
|
||||||
|
#include "Luau/Def.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct NonStrictContext
|
||||||
|
{
|
||||||
|
std::unordered_map<DefId, TypeId> context;
|
||||||
|
|
||||||
|
NonStrictContext() = default;
|
||||||
|
|
||||||
|
NonStrictContext(const NonStrictContext&) = delete;
|
||||||
|
NonStrictContext& operator=(const NonStrictContext&) = delete;
|
||||||
|
|
||||||
|
NonStrictContext(NonStrictContext&&) = default;
|
||||||
|
NonStrictContext& operator=(NonStrictContext&&) = default;
|
||||||
|
|
||||||
|
void unionContexts(const NonStrictContext& other)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
void intersectContexts(const NonStrictContext& other)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFromContext(const std::vector<DefId>& defs)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> find(const DefId& def)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Satisfies means that for a given DefId n, and an actual type t for `n`, t satisfies the context if t <: context[n]
|
||||||
|
// ice if the DefId is not in the context
|
||||||
|
bool satisfies(const DefId& def, TypeId inferredType)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool willRunTimeError(const DefId& def, TypeId inferredType)
|
||||||
|
{
|
||||||
|
return satisfies(def, inferredType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NonStrictTypeChecker
|
||||||
|
{
|
||||||
|
|
||||||
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
|
const NotNull<InternalErrorReporter> ice;
|
||||||
|
TypeArena arena;
|
||||||
|
Module* module;
|
||||||
|
Normalizer normalizer;
|
||||||
|
Subtyping subtyping;
|
||||||
|
|
||||||
|
|
||||||
|
NonStrictTypeChecker(NotNull<BuiltinTypes> builtinTypes, Subtyping subtyping, const NotNull<InternalErrorReporter> ice,
|
||||||
|
NotNull<UnifierSharedState> unifierState, Module* module)
|
||||||
|
: builtinTypes(builtinTypes)
|
||||||
|
, ice(ice)
|
||||||
|
, module(module)
|
||||||
|
, normalizer{&arena, builtinTypes, unifierState, /* cache inhabitance */ true}
|
||||||
|
, subtyping{builtinTypes, NotNull{&arena}, NotNull(&normalizer), ice, NotNull{module->getModuleScope().get()}}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module)
|
||||||
|
{
|
||||||
|
// TODO: unimplemented
|
||||||
|
}
|
||||||
|
} // namespace Luau
|
|
@ -11,7 +11,6 @@
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||||
|
|
||||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||||
|
|
|
@ -696,16 +696,33 @@ struct TypeStringifier
|
||||||
|
|
||||||
std::string openbrace = "@@@";
|
std::string openbrace = "@@@";
|
||||||
std::string closedbrace = "@@@?!";
|
std::string closedbrace = "@@@?!";
|
||||||
switch (state.opts.hideTableKind ? TableState::Unsealed : ttv.state)
|
switch (state.opts.hideTableKind ? (FFlag::DebugLuauDeferredConstraintResolution ? TableState::Sealed : TableState::Unsealed) : ttv.state)
|
||||||
{
|
{
|
||||||
case TableState::Sealed:
|
case TableState::Sealed:
|
||||||
state.result.invalid = true;
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
openbrace = "{|";
|
{
|
||||||
closedbrace = "|}";
|
openbrace = "{";
|
||||||
|
closedbrace = "}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.result.invalid = true;
|
||||||
|
openbrace = "{|";
|
||||||
|
closedbrace = "|}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TableState::Unsealed:
|
case TableState::Unsealed:
|
||||||
openbrace = "{";
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
closedbrace = "}";
|
{
|
||||||
|
state.result.invalid = true;
|
||||||
|
openbrace = "{|";
|
||||||
|
closedbrace = "|}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
openbrace = "{";
|
||||||
|
closedbrace = "}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TableState::Free:
|
case TableState::Free:
|
||||||
state.result.invalid = true;
|
state.result.invalid = true;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -320,14 +320,26 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||||
for (size_t i = 0; i < maxLength; ++i)
|
for (size_t i = 0; i < maxLength; ++i)
|
||||||
unify(subTypes[i], superTypes[i]);
|
unify(subTypes[i], superTypes[i]);
|
||||||
|
|
||||||
if (!subTail || !superTail)
|
if (subTail && superTail)
|
||||||
return true;
|
{
|
||||||
|
TypePackId followedSubTail = follow(*subTail);
|
||||||
|
TypePackId followedSuperTail = follow(*superTail);
|
||||||
|
|
||||||
TypePackId followedSubTail = follow(*subTail);
|
if (get<FreeTypePack>(followedSubTail) || get<FreeTypePack>(followedSuperTail))
|
||||||
TypePackId followedSuperTail = follow(*superTail);
|
return unify(followedSubTail, followedSuperTail);
|
||||||
|
}
|
||||||
if (get<FreeTypePack>(followedSubTail) || get<FreeTypePack>(followedSuperTail))
|
else if (subTail)
|
||||||
return unify(followedSubTail, followedSuperTail);
|
{
|
||||||
|
TypePackId followedSubTail = follow(*subTail);
|
||||||
|
if (get<FreeTypePack>(followedSubTail))
|
||||||
|
asMutable(followedSubTail)->ty.emplace<BoundTypePack>(builtinTypes->emptyTypePack);
|
||||||
|
}
|
||||||
|
else if (superTail)
|
||||||
|
{
|
||||||
|
TypePackId followedSuperTail = follow(*superTail);
|
||||||
|
if (get<FreeTypePack>(followedSuperTail))
|
||||||
|
asMutable(followedSuperTail)->ty.emplace<BoundTypePack>(builtinTypes->emptyTypePack);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -582,13 +594,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||||
TableType* tt = getMutable<TableType>(ty);
|
TableType* tt = getMutable<TableType>(ty);
|
||||||
LUAU_ASSERT(tt);
|
LUAU_ASSERT(tt);
|
||||||
|
|
||||||
// We only unseal tables if they occur within function argument or
|
|
||||||
// return lists. In principle, we could always seal tables when
|
|
||||||
// generalizing, but that would mean that we'd lose the ability to
|
|
||||||
// report the existence of unsealed tables via things like hovertype.
|
|
||||||
if (tt->state == TableState::Unsealed && !isWithinFunction)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
tt->state = TableState::Sealed;
|
tt->state = TableState::Sealed;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -611,10 +616,7 @@ std::optional<TypeId> Unifier2::generalize(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (ty->owningArena != arena)
|
if (ty->owningArena != arena || ty->persistent)
|
||||||
return ty;
|
|
||||||
|
|
||||||
if (ty->persistent)
|
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
if (const FunctionType* ft = get<FunctionType>(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty()))
|
if (const FunctionType* ft = get<FunctionType>(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty()))
|
||||||
|
@ -627,16 +629,23 @@ std::optional<TypeId> Unifier2::generalize(TypeId ty)
|
||||||
|
|
||||||
gen.traverse(ty);
|
gen.traverse(ty);
|
||||||
|
|
||||||
std::optional<TypeId> res = ty;
|
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
|
||||||
|
* been transmuted to a BoundType. We must follow it again and verify that
|
||||||
|
* we are allowed to mutate it before we attach generics to it.
|
||||||
|
*/
|
||||||
|
ty = follow(ty);
|
||||||
|
|
||||||
FunctionType* ftv = getMutable<FunctionType>(follow(*res));
|
if (ty->owningArena != arena || ty->persistent)
|
||||||
|
return ty;
|
||||||
|
|
||||||
|
FunctionType* ftv = getMutable<FunctionType>(ty);
|
||||||
if (ftv)
|
if (ftv)
|
||||||
{
|
{
|
||||||
ftv->generics = std::move(gen.generics);
|
ftv->generics = std::move(gen.generics);
|
||||||
ftv->genericPacks = std::move(gen.genericPacks);
|
ftv->genericPacks = std::move(gen.genericPacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
||||||
|
|
|
@ -477,17 +477,9 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||||
{
|
{
|
||||||
Table* h = hvalue(rb);
|
Table* h = hvalue(rb);
|
||||||
|
|
||||||
int slot = LUAU_INSN_C(insn) & h->nodemask8;
|
// we ignore the fast path that checks for the cached slot since IrTranslation already checks for it.
|
||||||
LuaNode* n = &h->node[slot];
|
|
||||||
|
|
||||||
// fast-path: value is in expected slot
|
if (fastnotm(h->metatable, TM_NEWINDEX) && !h->readonly)
|
||||||
if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly))
|
|
||||||
{
|
|
||||||
setobj2t(L, gval(n), ra);
|
|
||||||
luaC_barriert(L, h, ra);
|
|
||||||
return pc;
|
|
||||||
}
|
|
||||||
else if (fastnotm(h->metatable, TM_NEWINDEX) && !h->readonly)
|
|
||||||
{
|
{
|
||||||
VM_PROTECT_PC(); // set may fail
|
VM_PROTECT_PC(); // set may fail
|
||||||
|
|
||||||
|
@ -502,6 +494,7 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// slow-path, may invoke Lua calls via __newindex metamethod
|
// slow-path, may invoke Lua calls via __newindex metamethod
|
||||||
|
int slot = LUAU_INSN_C(insn) & h->nodemask8;
|
||||||
L->cachedslot = slot;
|
L->cachedslot = slot;
|
||||||
VM_PROTECT(luaV_settable(L, rb, kv, ra));
|
VM_PROTECT(luaV_settable(L, rb, kv, ra));
|
||||||
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
||||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false)
|
LUAU_FASTFLAGVARIABLE(LuauReuseHashSlots2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false)
|
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMergeTagLoads, false)
|
LUAU_FASTFLAGVARIABLE(LuauMergeTagLoads, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauReuseArrSlots, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -174,14 +175,32 @@ struct ConstPropState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value propagation extends the live range of an SSA register
|
||||||
|
// In some cases we can't propagate earlier values because we can't guarantee that we will be able to find a storage/restore location
|
||||||
|
// As an example, when Luau call is performed, both volatile registers and stack slots might be overwritten
|
||||||
|
void invalidateValuePropagation()
|
||||||
|
{
|
||||||
|
valueMap.clear();
|
||||||
|
tryNumToIndexCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
||||||
|
// Same goes for table array elements as well
|
||||||
|
void invalidateHeapTableData()
|
||||||
|
{
|
||||||
|
getSlotNodeCache.clear();
|
||||||
|
checkSlotMatchCache.clear();
|
||||||
|
|
||||||
|
getArrAddrCache.clear();
|
||||||
|
checkArraySizeCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void invalidateHeap()
|
void invalidateHeap()
|
||||||
{
|
{
|
||||||
for (int i = 0; i <= maxReg; ++i)
|
for (int i = 0; i <= maxReg; ++i)
|
||||||
invalidateHeap(regs[i]);
|
invalidateHeap(regs[i]);
|
||||||
|
|
||||||
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
invalidateHeapTableData();
|
||||||
getSlotNodeCache.clear();
|
|
||||||
checkSlotMatchCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void invalidateHeap(RegisterInfo& reg)
|
void invalidateHeap(RegisterInfo& reg)
|
||||||
|
@ -203,9 +222,7 @@ struct ConstPropState
|
||||||
for (int i = 0; i <= maxReg; ++i)
|
for (int i = 0; i <= maxReg; ++i)
|
||||||
invalidateTableArraySize(regs[i]);
|
invalidateTableArraySize(regs[i]);
|
||||||
|
|
||||||
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
|
invalidateHeapTableData();
|
||||||
getSlotNodeCache.clear();
|
|
||||||
checkSlotMatchCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void invalidateTableArraySize(RegisterInfo& reg)
|
void invalidateTableArraySize(RegisterInfo& reg)
|
||||||
|
@ -389,9 +406,9 @@ struct ConstPropState
|
||||||
checkedGc = false;
|
checkedGc = false;
|
||||||
|
|
||||||
instLink.clear();
|
instLink.clear();
|
||||||
valueMap.clear();
|
|
||||||
getSlotNodeCache.clear();
|
invalidateValuePropagation();
|
||||||
checkSlotMatchCache.clear();
|
invalidateHeapTableData();
|
||||||
}
|
}
|
||||||
|
|
||||||
IrFunction& function;
|
IrFunction& function;
|
||||||
|
@ -410,8 +427,15 @@ struct ConstPropState
|
||||||
|
|
||||||
DenseHashMap<IrInst, uint32_t, IrInstHash, IrInstEq> valueMap;
|
DenseHashMap<IrInst, uint32_t, IrInstHash, IrInstEq> valueMap;
|
||||||
|
|
||||||
std::vector<uint32_t> getSlotNodeCache;
|
// Some instruction re-uses can't be stored in valueMap because of extra requirements
|
||||||
std::vector<uint32_t> checkSlotMatchCache;
|
std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different
|
||||||
|
|
||||||
|
// Heap changes might affect table state
|
||||||
|
std::vector<uint32_t> getSlotNodeCache; // Additionally, pcpos argument might be different
|
||||||
|
std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different
|
||||||
|
|
||||||
|
std::vector<uint32_t> getArrAddrCache;
|
||||||
|
std::vector<uint32_t> checkArraySizeCache; // Additionally, fallback block argument might be different
|
||||||
};
|
};
|
||||||
|
|
||||||
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
|
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
|
||||||
|
@ -873,7 +897,24 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
// These instructions don't have an effect on register/memory state we are tracking
|
// These instructions don't have an effect on register/memory state we are tracking
|
||||||
case IrCmd::NOP:
|
case IrCmd::NOP:
|
||||||
case IrCmd::LOAD_ENV:
|
case IrCmd::LOAD_ENV:
|
||||||
|
break;
|
||||||
case IrCmd::GET_ARR_ADDR:
|
case IrCmd::GET_ARR_ADDR:
|
||||||
|
if (!FFlag::LuauReuseArrSlots)
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (uint32_t prevIdx : state.getArrAddrCache)
|
||||||
|
{
|
||||||
|
const IrInst& prev = function.instructions[prevIdx];
|
||||||
|
|
||||||
|
if (prev.a == inst.a && prev.b == inst.b)
|
||||||
|
{
|
||||||
|
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
|
||||||
|
return; // Break out from both the loop and the switch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int(state.getArrAddrCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||||
|
state.getArrAddrCache.push_back(index);
|
||||||
break;
|
break;
|
||||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||||
if (!FFlag::LuauReuseHashSlots2)
|
if (!FFlag::LuauReuseHashSlots2)
|
||||||
|
@ -929,7 +970,25 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
case IrCmd::STRING_LEN:
|
case IrCmd::STRING_LEN:
|
||||||
case IrCmd::NEW_TABLE:
|
case IrCmd::NEW_TABLE:
|
||||||
case IrCmd::DUP_TABLE:
|
case IrCmd::DUP_TABLE:
|
||||||
|
break;
|
||||||
case IrCmd::TRY_NUM_TO_INDEX:
|
case IrCmd::TRY_NUM_TO_INDEX:
|
||||||
|
if (!FFlag::LuauReuseArrSlots)
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (uint32_t prevIdx : state.tryNumToIndexCache)
|
||||||
|
{
|
||||||
|
const IrInst& prev = function.instructions[prevIdx];
|
||||||
|
|
||||||
|
if (prev.a == inst.a)
|
||||||
|
{
|
||||||
|
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
|
||||||
|
return; // Break out from both the loop and the switch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int(state.tryNumToIndexCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||||
|
state.tryNumToIndexCache.push_back(index);
|
||||||
|
break;
|
||||||
case IrCmd::TRY_CALL_FASTGETTM:
|
case IrCmd::TRY_CALL_FASTGETTM:
|
||||||
break;
|
break;
|
||||||
case IrCmd::INT_TO_NUM:
|
case IrCmd::INT_TO_NUM:
|
||||||
|
@ -967,8 +1026,42 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
{
|
{
|
||||||
replace(function, block, index, {IrCmd::JUMP, inst.c});
|
replace(function, block, index, {IrCmd::JUMP, inst.c});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return; // Break out from both the loop and the switch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!FFlag::LuauReuseArrSlots)
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (uint32_t prevIdx : state.checkArraySizeCache)
|
||||||
|
{
|
||||||
|
const IrInst& prev = function.instructions[prevIdx];
|
||||||
|
|
||||||
|
if (prev.a != inst.a)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool sameBoundary = prev.b == inst.b;
|
||||||
|
|
||||||
|
// If arguments are different, in case they are both constant, we can check if a larger bound was already tested
|
||||||
|
if (!sameBoundary && inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant &&
|
||||||
|
function.intOp(inst.b) < function.intOp(prev.b))
|
||||||
|
sameBoundary = true;
|
||||||
|
|
||||||
|
if (sameBoundary)
|
||||||
|
{
|
||||||
|
if (FFlag::DebugLuauAbortingChecks)
|
||||||
|
replace(function, inst.c, build.undef());
|
||||||
|
else
|
||||||
|
kill(function, inst);
|
||||||
|
return; // Break out from both the loop and the switch
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: it should be possible to update previous check with a higher bound if current and previous checks are against a constant
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int(state.checkArraySizeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||||
|
state.checkArraySizeCache.push_back(index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IrCmd::CHECK_SLOT_MATCH:
|
case IrCmd::CHECK_SLOT_MATCH:
|
||||||
|
@ -1053,9 +1146,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
replace(function, inst.f, build.constUint(info->knownTableArraySize));
|
replace(function, inst.f, build.constUint(info->knownTableArraySize));
|
||||||
|
|
||||||
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
|
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
|
||||||
state.valueMap.clear();
|
state.invalidateValuePropagation();
|
||||||
state.getSlotNodeCache.clear();
|
state.invalidateHeapTableData();
|
||||||
state.checkSlotMatchCache.clear();
|
|
||||||
break;
|
break;
|
||||||
case IrCmd::CALL:
|
case IrCmd::CALL:
|
||||||
state.invalidateRegistersFrom(vmRegOp(inst.a));
|
state.invalidateRegistersFrom(vmRegOp(inst.a));
|
||||||
|
@ -1064,15 +1156,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
// We cannot guarantee right now that all live values can be rematerialized from non-stack memory locations
|
// We cannot guarantee right now that all live values can be rematerialized from non-stack memory locations
|
||||||
// To prevent earlier values from being propagated to after the call, we have to clear the map
|
// To prevent earlier values from being propagated to after the call, we have to clear the map
|
||||||
// TODO: remove only the values that don't have a guaranteed restore location
|
// TODO: remove only the values that don't have a guaranteed restore location
|
||||||
state.valueMap.clear();
|
state.invalidateValuePropagation();
|
||||||
break;
|
break;
|
||||||
case IrCmd::FORGLOOP:
|
case IrCmd::FORGLOOP:
|
||||||
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
||||||
|
|
||||||
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
|
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
|
||||||
state.valueMap.clear();
|
state.invalidateValuePropagation();
|
||||||
state.getSlotNodeCache.clear();
|
state.invalidateHeapTableData();
|
||||||
state.checkSlotMatchCache.clear();
|
|
||||||
break;
|
break;
|
||||||
case IrCmd::FORGLOOP_FALLBACK:
|
case IrCmd::FORGLOOP_FALLBACK:
|
||||||
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
||||||
|
@ -1139,11 +1230,10 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s
|
||||||
if (!FFlag::LuauKeepVmapLinear)
|
if (!FFlag::LuauKeepVmapLinear)
|
||||||
{
|
{
|
||||||
// Value numbering and load/store propagation is not performed between blocks
|
// Value numbering and load/store propagation is not performed between blocks
|
||||||
state.valueMap.clear();
|
state.invalidateValuePropagation();
|
||||||
|
|
||||||
// Same for table slot data propagation
|
// Same for table slot data propagation
|
||||||
state.getSlotNodeCache.clear();
|
state.invalidateHeapTableData();
|
||||||
state.checkSlotMatchCache.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1168,11 +1258,10 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
|
||||||
{
|
{
|
||||||
// Value numbering and load/store propagation is not performed between blocks right now
|
// Value numbering and load/store propagation is not performed between blocks right now
|
||||||
// This is because cross-block value uses limit creation of linear block (restriction in collectDirectBlockJumpPath)
|
// This is because cross-block value uses limit creation of linear block (restriction in collectDirectBlockJumpPath)
|
||||||
state.valueMap.clear();
|
state.invalidateValuePropagation();
|
||||||
|
|
||||||
// Same for table slot data propagation
|
// Same for table slot data propagation
|
||||||
state.getSlotNodeCache.clear();
|
state.invalidateHeapTableData();
|
||||||
state.checkSlotMatchCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks in a chain are guaranteed to follow each other
|
// Blocks in a chain are guaranteed to follow each other
|
||||||
|
|
|
@ -178,6 +178,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/Metamethods.h
|
Analysis/include/Luau/Metamethods.h
|
||||||
Analysis/include/Luau/Module.h
|
Analysis/include/Luau/Module.h
|
||||||
Analysis/include/Luau/ModuleResolver.h
|
Analysis/include/Luau/ModuleResolver.h
|
||||||
|
Analysis/include/Luau/NonStrictTypeChecker.h
|
||||||
Analysis/include/Luau/Normalize.h
|
Analysis/include/Luau/Normalize.h
|
||||||
Analysis/include/Luau/Predicate.h
|
Analysis/include/Luau/Predicate.h
|
||||||
Analysis/include/Luau/Quantify.h
|
Analysis/include/Luau/Quantify.h
|
||||||
|
@ -235,6 +236,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/Linter.cpp
|
Analysis/src/Linter.cpp
|
||||||
Analysis/src/LValue.cpp
|
Analysis/src/LValue.cpp
|
||||||
Analysis/src/Module.cpp
|
Analysis/src/Module.cpp
|
||||||
|
Analysis/src/NonStrictTypeChecker.cpp
|
||||||
Analysis/src/Normalize.cpp
|
Analysis/src/Normalize.cpp
|
||||||
Analysis/src/Quantify.cpp
|
Analysis/src/Quantify.cpp
|
||||||
Analysis/src/Refinement.cpp
|
Analysis/src/Refinement.cpp
|
||||||
|
@ -398,6 +400,7 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/LValue.test.cpp
|
tests/LValue.test.cpp
|
||||||
tests/Module.test.cpp
|
tests/Module.test.cpp
|
||||||
tests/NonstrictMode.test.cpp
|
tests/NonstrictMode.test.cpp
|
||||||
|
tests/NonStrictTypeChecker.test.cpp
|
||||||
tests/Normalize.test.cpp
|
tests/Normalize.test.cpp
|
||||||
tests/NotNull.test.cpp
|
tests/NotNull.test.cpp
|
||||||
tests/Parser.test.cpp
|
tests/Parser.test.cpp
|
||||||
|
|
|
@ -22,27 +22,6 @@ static time_t timegm(struct tm* timep)
|
||||||
{
|
{
|
||||||
return _mkgmtime(timep);
|
return _mkgmtime(timep);
|
||||||
}
|
}
|
||||||
#elif defined(__FreeBSD__)
|
|
||||||
static tm* gmtime_r(const time_t* timep, tm* result)
|
|
||||||
{
|
|
||||||
// Note: return is reversed from Windows (0 is success on Windows, but 0/null is failure elsewhere)
|
|
||||||
// Windows https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-s-gmtime32-s-gmtime64-s?view=msvc-170#return-value
|
|
||||||
// Everyone else https://en.cppreference.com/w/c/chrono/gmtime
|
|
||||||
return gmtime_s(timep, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static tm* localtime_r(const time_t* timep, tm* result)
|
|
||||||
{
|
|
||||||
// Note: return is reversed from Windows (0 is success on Windows, but 0/null is failure elsewhere)
|
|
||||||
// Windows https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s?view=msvc-170#return-value
|
|
||||||
// Everyone else https://en.cppreference.com/w/c/chrono/localtime
|
|
||||||
return localtime_s(timep, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static time_t timegm(struct tm* timep)
|
|
||||||
{
|
|
||||||
return mktime(timep);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int os_clock(lua_State* L)
|
static int os_clock(lua_State* L)
|
||||||
|
|
6
extern/doctest.h
vendored
6
extern/doctest.h
vendored
|
@ -3139,7 +3139,11 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#if !defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS)
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
@ -5667,6 +5671,8 @@ namespace {
|
||||||
std::tm timeInfo;
|
std::tm timeInfo;
|
||||||
#ifdef DOCTEST_PLATFORM_WINDOWS
|
#ifdef DOCTEST_PLATFORM_WINDOWS
|
||||||
gmtime_s(&timeInfo, &rawtime);
|
gmtime_s(&timeInfo, &rawtime);
|
||||||
|
#elif defined(DOCTEST_CONFIG_USE_GMTIME_S)
|
||||||
|
gmtime_s(&rawtime, &timeInfo);
|
||||||
#else // DOCTEST_PLATFORM_WINDOWS
|
#else // DOCTEST_PLATFORM_WINDOWS
|
||||||
gmtime_r(&rawtime, &timeInfo);
|
gmtime_r(&rawtime, &timeInfo);
|
||||||
#endif // DOCTEST_PLATFORM_WINDOWS
|
#endif // DOCTEST_PLATFORM_WINDOWS
|
||||||
|
|
|
@ -2162,7 +2162,10 @@ local fp: @1= f
|
||||||
|
|
||||||
auto ac = autocomplete('1');
|
auto ac = autocomplete('1');
|
||||||
|
|
||||||
REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f")));
|
||||||
|
else
|
||||||
|
REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f")));
|
||||||
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
|
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,9 @@ constexpr X64::RegisterX64 rNonVol4 = X64::r14;
|
||||||
|
|
||||||
TEST_CASE("GeneratedCodeExecutionX64")
|
TEST_CASE("GeneratedCodeExecutionX64")
|
||||||
{
|
{
|
||||||
|
if (!Luau::CodeGen::isSupported())
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace X64;
|
using namespace X64;
|
||||||
|
|
||||||
AssemblyBuilderX64 build(/* logText= */ false);
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
@ -315,6 +318,9 @@ static void nonthrowing(int64_t arg)
|
||||||
|
|
||||||
TEST_CASE("GeneratedCodeExecutionWithThrowX64")
|
TEST_CASE("GeneratedCodeExecutionWithThrowX64")
|
||||||
{
|
{
|
||||||
|
if (!Luau::CodeGen::isSupported())
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace X64;
|
using namespace X64;
|
||||||
|
|
||||||
AssemblyBuilderX64 build(/* logText= */ false);
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
@ -513,6 +519,9 @@ TEST_CASE("GeneratedCodeExecutionWithThrowX64Simd")
|
||||||
|
|
||||||
TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
|
TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
|
||||||
{
|
{
|
||||||
|
if (!Luau::CodeGen::isSupported())
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace X64;
|
using namespace X64;
|
||||||
|
|
||||||
AssemblyBuilderX64 build(/* logText= */ false);
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
@ -650,6 +659,9 @@ TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
|
||||||
|
|
||||||
TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64")
|
TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64")
|
||||||
{
|
{
|
||||||
|
if (!Luau::CodeGen::isSupported())
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace X64;
|
using namespace X64;
|
||||||
|
|
||||||
AssemblyBuilderX64 build(/* logText= */ false);
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
|
|
@ -21,7 +21,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
||||||
cgb->visit(root);
|
cgb->visitModuleRoot(root);
|
||||||
rootScope = cgb->rootScope;
|
rootScope = cgb->rootScope;
|
||||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||||
}
|
}
|
||||||
|
|
|
@ -657,8 +657,12 @@ TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
compareTypesNe("foo", "almostFoo",
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = { bar: () -> t1 }, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
||||||
|
else
|
||||||
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
|
TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
|
||||||
|
@ -812,8 +816,12 @@ TEST_CASE_FIXTURE(DifferFixture, "union_missing")
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
compareTypesNe("foo", "almostFoo",
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at <unlabeled-symbol> is a union missing type {| baz: boolean, rot: "singleton" |})");
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { baz: boolean, rot: "singleton" }, while the right type at <unlabeled-symbol> is a union missing type { baz: boolean, rot: "singleton" })");
|
||||||
|
else
|
||||||
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at <unlabeled-symbol> is a union missing type {| baz: boolean, rot: "singleton" |})");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_right")
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_right")
|
||||||
|
@ -848,8 +856,12 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_right")
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
compareTypesNe("foo", "almostFoo",
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type {| x: number |}, while the right type at <unlabeled-symbol> is an intersection missing type {| x: number |})");
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type { x: number }, while the right type at <unlabeled-symbol> is an intersection missing type { x: number })");
|
||||||
|
else
|
||||||
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type {| x: number |}, while the right type at <unlabeled-symbol> is an intersection missing type {| x: number |})");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
||||||
|
@ -860,8 +872,12 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
compareTypesNe("foo", "almostFoo",
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type {| z: boolean |}, while the right type at <unlabeled-symbol> is an intersection containing type {| z: boolean |})");
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type { z: boolean }, while the right type at <unlabeled-symbol> is an intersection containing type { z: boolean })");
|
||||||
|
else
|
||||||
|
compareTypesNe("foo", "almostFoo",
|
||||||
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type {| z: boolean |}, while the right type at <unlabeled-symbol> is an intersection containing type {| z: boolean |})");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function")
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function")
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -160,7 +162,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
||||||
auto bExports = first(bModule->returnType);
|
auto bExports = first(bModule->returnType);
|
||||||
REQUIRE(!!bExports);
|
REQUIRE(!!bExports);
|
||||||
|
|
||||||
CHECK_EQ("{| b_value: number |}", toString(*bExports));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ b_value: number }", toString(*bExports));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| b_value: number |}", toString(*bExports));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_cyclically_dependent_scripts")
|
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_cyclically_dependent_scripts")
|
||||||
|
@ -302,7 +307,11 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
|
||||||
|
|
||||||
std::optional<TypeId> cExports = first(cModule->returnType);
|
std::optional<TypeId> cExports = first(cModule->returnType);
|
||||||
REQUIRE(bool(cExports));
|
REQUIRE(bool(cExports));
|
||||||
CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ a: any, b: any }", toString(*cExports));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_disabled_in_nocheck")
|
TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_disabled_in_nocheck")
|
||||||
|
@ -473,20 +482,32 @@ return {mod_b = 2}
|
||||||
LUAU_REQUIRE_ERRORS(resultB);
|
LUAU_REQUIRE_ERRORS(resultB);
|
||||||
|
|
||||||
TypeId tyB = requireExportedType("game/B", "btype");
|
TypeId tyB = requireExportedType("game/B", "btype");
|
||||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(tyB, opts), "{ x: number }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||||
|
|
||||||
TypeId tyA = requireExportedType("game/A", "atype");
|
TypeId tyA = requireExportedType("game/A", "atype");
|
||||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(tyA, opts), "{ x: any }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||||
|
|
||||||
frontend.markDirty("game/B");
|
frontend.markDirty("game/B");
|
||||||
resultB = frontend.check("game/B");
|
resultB = frontend.check("game/B");
|
||||||
LUAU_REQUIRE_ERRORS(resultB);
|
LUAU_REQUIRE_ERRORS(resultB);
|
||||||
|
|
||||||
tyB = requireExportedType("game/B", "btype");
|
tyB = requireExportedType("game/B", "btype");
|
||||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(tyB, opts), "{ x: number }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||||
|
|
||||||
tyA = requireExportedType("game/A", "atype");
|
tyA = requireExportedType("game/A", "atype");
|
||||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(tyA, opts), "{ x: any }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "dont_reparse_clean_file_when_linting")
|
TEST_CASE_FIXTURE(FrontendFixture, "dont_reparse_clean_file_when_linting")
|
||||||
|
@ -559,7 +580,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty")
|
||||||
auto bExports = first(bModule->returnType);
|
auto bExports = first(bModule->returnType);
|
||||||
REQUIRE(!!bExports);
|
REQUIRE(!!bExports);
|
||||||
|
|
||||||
CHECK_EQ("{| b_value: string |}", toString(*bExports));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ b_value: string }", toString(*bExports));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| b_value: string |}", toString(*bExports));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "mark_non_immediate_reverse_deps_as_dirty")
|
TEST_CASE_FIXTURE(FrontendFixture, "mark_non_immediate_reverse_deps_as_dirty")
|
||||||
|
@ -883,8 +907,12 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
|
||||||
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
|
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
|
||||||
// It is thus basically impossible to predict what will happen when this assert is evaluated.
|
// It is thus basically impossible to predict what will happen when this assert is evaluated.
|
||||||
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
|
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
|
||||||
REQUIRE_EQ(
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
|
REQUIRE_EQ(
|
||||||
|
"Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
REQUIRE_EQ(
|
||||||
|
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode")
|
TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode")
|
||||||
|
|
|
@ -2060,6 +2060,244 @@ bb_fallback_1:
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||||
|
|
||||||
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
build.beginBlock(block);
|
||||||
|
|
||||||
|
// This roughly corresponds to 'return t[1] + t[1]'
|
||||||
|
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
|
||||||
|
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||||
|
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||||
|
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||||
|
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||||
|
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||||
|
|
||||||
|
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||||
|
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||||
|
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||||
|
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||||
|
|
||||||
|
build.beginBlock(fallback);
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||||
|
|
||||||
|
updateUseCounts(build.function);
|
||||||
|
constPropInBlockChains(build, true);
|
||||||
|
|
||||||
|
// In the future, we might even see duplicate identical TValue loads go away
|
||||||
|
// In the future, we might even see loads of different VM regs with the same value go away
|
||||||
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||||
|
bb_0:
|
||||||
|
%0 = LOAD_POINTER R1
|
||||||
|
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||||
|
%2 = GET_ARR_ADDR %0, 0i
|
||||||
|
%3 = LOAD_TVALUE %2, 0i
|
||||||
|
STORE_TVALUE R3, %3
|
||||||
|
%7 = LOAD_TVALUE %2, 0i
|
||||||
|
STORE_TVALUE R4, %7
|
||||||
|
%9 = LOAD_DOUBLE R3
|
||||||
|
%10 = LOAD_DOUBLE R4
|
||||||
|
%11 = ADD_NUM %9, %10
|
||||||
|
STORE_DOUBLE R2, %11
|
||||||
|
RETURN R2, 1u
|
||||||
|
|
||||||
|
bb_fallback_1:
|
||||||
|
RETURN R0, 1u
|
||||||
|
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||||
|
|
||||||
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
build.beginBlock(block);
|
||||||
|
|
||||||
|
// This roughly corresponds to 'return t[i] + t[i]'
|
||||||
|
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||||
|
IrOp index = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
|
||||||
|
IrOp validIndex = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
|
||||||
|
IrOp validOffset = build.inst(IrCmd::SUB_INT, validIndex, build.constInt(1));
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset, fallback);
|
||||||
|
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||||
|
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||||
|
|
||||||
|
IrOp validIndex2 = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
|
||||||
|
IrOp validOffset2 = build.inst(IrCmd::SUB_INT, validIndex2, build.constInt(1));
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset2, fallback); // This will be removed
|
||||||
|
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||||
|
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||||
|
|
||||||
|
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||||
|
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||||
|
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||||
|
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||||
|
|
||||||
|
build.beginBlock(fallback);
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||||
|
|
||||||
|
updateUseCounts(build.function);
|
||||||
|
constPropInBlockChains(build, true);
|
||||||
|
|
||||||
|
// In the future, we might even see duplicate identical TValue loads go away
|
||||||
|
// In the future, we might even see loads of different VM regs with the same value go away
|
||||||
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||||
|
bb_0:
|
||||||
|
%0 = LOAD_POINTER R1
|
||||||
|
%1 = LOAD_DOUBLE R2
|
||||||
|
%2 = TRY_NUM_TO_INDEX %1, bb_fallback_1
|
||||||
|
%3 = SUB_INT %2, 1i
|
||||||
|
CHECK_ARRAY_SIZE %0, %3, bb_fallback_1
|
||||||
|
%5 = GET_ARR_ADDR %0, 0i
|
||||||
|
%6 = LOAD_TVALUE %5, 0i
|
||||||
|
STORE_TVALUE R3, %6
|
||||||
|
%12 = LOAD_TVALUE %5, 0i
|
||||||
|
STORE_TVALUE R4, %12
|
||||||
|
%14 = LOAD_DOUBLE R3
|
||||||
|
%15 = LOAD_DOUBLE R4
|
||||||
|
%16 = ADD_NUM %14, %15
|
||||||
|
STORE_DOUBLE R2, %16
|
||||||
|
RETURN R2, 1u
|
||||||
|
|
||||||
|
bb_fallback_1:
|
||||||
|
RETURN R0, 1u
|
||||||
|
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||||
|
|
||||||
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
build.beginBlock(block);
|
||||||
|
|
||||||
|
// This roughly corresponds to 'return t[2] + t[1]'
|
||||||
|
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(1), fallback);
|
||||||
|
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(1));
|
||||||
|
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||||
|
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||||
|
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||||
|
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||||
|
|
||||||
|
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||||
|
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||||
|
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||||
|
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||||
|
|
||||||
|
build.beginBlock(fallback);
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||||
|
|
||||||
|
updateUseCounts(build.function);
|
||||||
|
constPropInBlockChains(build, true);
|
||||||
|
|
||||||
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||||
|
bb_0:
|
||||||
|
%0 = LOAD_POINTER R1
|
||||||
|
CHECK_ARRAY_SIZE %0, 1i, bb_fallback_1
|
||||||
|
%2 = GET_ARR_ADDR %0, 1i
|
||||||
|
%3 = LOAD_TVALUE %2, 0i
|
||||||
|
STORE_TVALUE R3, %3
|
||||||
|
%6 = GET_ARR_ADDR %0, 0i
|
||||||
|
%7 = LOAD_TVALUE %6, 0i
|
||||||
|
STORE_TVALUE R4, %7
|
||||||
|
%9 = LOAD_DOUBLE R3
|
||||||
|
%10 = LOAD_DOUBLE R4
|
||||||
|
%11 = ADD_NUM %9, %10
|
||||||
|
STORE_DOUBLE R2, %11
|
||||||
|
RETURN R2, 1u
|
||||||
|
|
||||||
|
bb_fallback_1:
|
||||||
|
RETURN R0, 1u
|
||||||
|
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots", true};
|
||||||
|
|
||||||
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
build.beginBlock(block);
|
||||||
|
|
||||||
|
// This roughly corresponds to 'return t[1] + t[1]' with a strange table.insert in the middle
|
||||||
|
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
|
||||||
|
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
|
||||||
|
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||||
|
|
||||||
|
build.inst(IrCmd::TABLE_SETNUM, table1, build.constInt(2));
|
||||||
|
|
||||||
|
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
|
||||||
|
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
|
||||||
|
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||||
|
|
||||||
|
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||||
|
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||||
|
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||||
|
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||||
|
|
||||||
|
build.beginBlock(fallback);
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||||
|
|
||||||
|
updateUseCounts(build.function);
|
||||||
|
constPropInBlockChains(build, true);
|
||||||
|
|
||||||
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||||
|
bb_0:
|
||||||
|
%0 = LOAD_POINTER R1
|
||||||
|
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||||
|
%2 = GET_ARR_ADDR %0, 0i
|
||||||
|
%3 = LOAD_TVALUE %2, 0i
|
||||||
|
STORE_TVALUE R3, %3
|
||||||
|
%5 = TABLE_SETNUM %0, 2i
|
||||||
|
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
|
||||||
|
%7 = GET_ARR_ADDR %0, 0i
|
||||||
|
%8 = LOAD_TVALUE %7, 0i
|
||||||
|
STORE_TVALUE R4, %8
|
||||||
|
%10 = LOAD_DOUBLE R3
|
||||||
|
%11 = LOAD_DOUBLE R4
|
||||||
|
%12 = ADD_NUM %10, %11
|
||||||
|
STORE_DOUBLE R2, %12
|
||||||
|
RETURN R2, 1u
|
||||||
|
|
||||||
|
bb_fallback_1:
|
||||||
|
RETURN R0, 1u
|
||||||
|
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("Analysis");
|
TEST_SUITE_BEGIN("Analysis");
|
||||||
|
|
|
@ -489,15 +489,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
ModulePtr modA = frontend.moduleResolver.getModule("Module/A");
|
ModulePtr modA = frontend.moduleResolver.getModule("Module/A");
|
||||||
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
|
||||||
REQUIRE(modA);
|
REQUIRE(modA);
|
||||||
|
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
||||||
REQUIRE(modB);
|
REQUIRE(modB);
|
||||||
|
|
||||||
std::optional<TypeId> typeA = first(modA->returnType);
|
std::optional<TypeId> typeA = first(modA->returnType);
|
||||||
std::optional<TypeId> typeB = first(modB->returnType);
|
|
||||||
REQUIRE(typeA);
|
REQUIRE(typeA);
|
||||||
|
std::optional<TypeId> typeB = first(modB->returnType);
|
||||||
REQUIRE(typeB);
|
REQUIRE(typeB);
|
||||||
|
|
||||||
TableType* tableA = getMutable<TableType>(*typeA);
|
TableType* tableA = getMutable<TableType>(*typeA);
|
||||||
|
REQUIRE_MESSAGE(tableA, "Expected a table, but got " << toString(*typeA));
|
||||||
TableType* tableB = getMutable<TableType>(*typeB);
|
TableType* tableB = getMutable<TableType>(*typeB);
|
||||||
|
REQUIRE_MESSAGE(tableB, "Expected a table, but got " << toString(*typeB));
|
||||||
|
|
||||||
CHECK(tableA->props["a"].type() == tableB->props["b"].type());
|
CHECK(tableA->props["a"].type() == tableB->props["b"].type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
tests/NonStrictTypeChecker.test.cpp
Normal file
17
tests/NonStrictTypeChecker.test.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/NonStrictTypeChecker.h"
|
||||||
|
|
||||||
|
#include "Fixture.h"
|
||||||
|
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
using namespace Luau;
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "basic")
|
||||||
|
{
|
||||||
|
Luau::checkNonStrict(builtinTypes, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
|
@ -797,7 +797,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom")
|
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom")
|
||||||
{
|
{
|
||||||
CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
|
||||||
|
else
|
||||||
|
CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection")
|
TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection")
|
||||||
|
@ -863,7 +866,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_never")
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
|
TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
|
||||||
{
|
{
|
||||||
CHECK("table" == toString(normal("{} | tbl")));
|
CHECK("table" == toString(normal("{} | tbl")));
|
||||||
CHECK("{| |}" == toString(normal("{} & tbl")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK("{ }" == toString(normal("{} & tbl")));
|
||||||
|
else
|
||||||
|
CHECK("{| |}" == toString(normal("{} & tbl")));
|
||||||
CHECK("never" == toString(normal("number & tbl")));
|
CHECK("never" == toString(normal("number & tbl")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -394,8 +394,8 @@ TEST_CASE_FIXTURE(SimplifyFixture, "table_with_a_tag")
|
||||||
TypeId t1 = mkTable({{"tag", stringTy}, {"prop", numberTy}});
|
TypeId t1 = mkTable({{"tag", stringTy}, {"prop", numberTy}});
|
||||||
TypeId t2 = mkTable({{"tag", helloTy}});
|
TypeId t2 = mkTable({{"tag", helloTy}});
|
||||||
|
|
||||||
CHECK("{| prop: number, tag: string |} & {| tag: \"hello\" |}" == intersectStr(t1, t2));
|
CHECK("{ prop: number, tag: string } & { tag: \"hello\" }" == intersectStr(t1, t2));
|
||||||
CHECK("{| prop: number, tag: string |} & {| tag: \"hello\" |}" == intersectStr(t2, t1));
|
CHECK("{ prop: number, tag: string } & { tag: \"hello\" }" == intersectStr(t2, t1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SimplifyFixture, "nested_table_tag_test")
|
TEST_CASE_FIXTURE(SimplifyFixture, "nested_table_tag_test")
|
||||||
|
|
|
@ -50,7 +50,10 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table")
|
||||||
TableType* tableOne = getMutable<TableType>(&cyclicTable);
|
TableType* tableOne = getMutable<TableType>(&cyclicTable);
|
||||||
tableOne->props["self"] = {&cyclicTable};
|
tableOne->props["self"] = {&cyclicTable};
|
||||||
|
|
||||||
CHECK_EQ("t1 where t1 = { self: t1 }", toString(&cyclicTable));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("t1 where t1 = {| self: t1 |}", toString(&cyclicTable));
|
||||||
|
else
|
||||||
|
CHECK_EQ("t1 where t1 = { self: t1 }", toString(&cyclicTable));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "named_table")
|
TEST_CASE_FIXTURE(Fixture, "named_table")
|
||||||
|
@ -68,12 +71,18 @@ TEST_CASE_FIXTURE(Fixture, "empty_table")
|
||||||
local a: {}
|
local a: {}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
CHECK_EQ("{| |}", toString(requireType("a")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ }", toString(requireType("a")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| |}", toString(requireType("a")));
|
||||||
|
|
||||||
// Should stay the same with useLineBreaks enabled
|
// Should stay the same with useLineBreaks enabled
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
opts.useLineBreaks = true;
|
opts.useLineBreaks = true;
|
||||||
CHECK_EQ("{| |}", toString(requireType("a"), opts));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ }", toString(requireType("a"), opts));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| |}", toString(requireType("a"), opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||||
|
@ -86,12 +95,20 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||||
opts.useLineBreaks = true;
|
opts.useLineBreaks = true;
|
||||||
|
|
||||||
//clang-format off
|
//clang-format off
|
||||||
CHECK_EQ("{|\n"
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
" anotherProp: number,\n"
|
CHECK_EQ("{\n"
|
||||||
" prop: string,\n"
|
" anotherProp: number,\n"
|
||||||
" thirdProp: boolean\n"
|
" prop: string,\n"
|
||||||
"|}",
|
" thirdProp: boolean\n"
|
||||||
toString(requireType("a"), opts));
|
"}",
|
||||||
|
toString(requireType("a"), opts));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{|\n"
|
||||||
|
" anotherProp: number,\n"
|
||||||
|
" prop: string,\n"
|
||||||
|
" thirdProp: boolean\n"
|
||||||
|
"|}",
|
||||||
|
toString(requireType("a"), opts));
|
||||||
//clang-format on
|
//clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +139,10 @@ TEST_CASE_FIXTURE(Fixture, "metatable")
|
||||||
Type table{TypeVariant(TableType())};
|
Type table{TypeVariant(TableType())};
|
||||||
Type metatable{TypeVariant(TableType())};
|
Type metatable{TypeVariant(TableType())};
|
||||||
Type mtv{TypeVariant(MetatableType{&table, &metatable})};
|
Type mtv{TypeVariant(MetatableType{&table, &metatable})};
|
||||||
CHECK_EQ("{ @metatable { }, { } }", toString(&mtv));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ @metatable {| |}, {| |} }", toString(&mtv));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{ @metatable { }, { } }", toString(&mtv));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "named_metatable")
|
TEST_CASE_FIXTURE(Fixture, "named_metatable")
|
||||||
|
@ -258,7 +278,10 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded
|
||||||
ToStringOptions o;
|
ToStringOptions o;
|
||||||
o.exhaustive = false;
|
o.exhaustive = false;
|
||||||
o.maxTableLength = 40;
|
o.maxTableLength = 40;
|
||||||
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 10 more ... }");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 10 more ... |}");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 10 more ... }");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaustive")
|
TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaustive")
|
||||||
|
@ -272,7 +295,10 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaust
|
||||||
ToStringOptions o;
|
ToStringOptions o;
|
||||||
o.exhaustive = true;
|
o.exhaustive = true;
|
||||||
o.maxTableLength = 40;
|
o.maxTableLength = 40;
|
||||||
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 2 more ... }");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 2 more ... |}");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 2 more ... }");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded")
|
TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded")
|
||||||
|
@ -346,7 +372,10 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table
|
||||||
|
|
||||||
ToStringOptions o;
|
ToStringOptions o;
|
||||||
o.maxTableLength = 40;
|
o.maxTableLength = 40;
|
||||||
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 5 more ... }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_union_type_bails_early")
|
TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_union_type_bails_early")
|
||||||
|
@ -377,7 +406,10 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_array_uses_array_syntax")
|
||||||
CHECK_EQ("{string}", toString(Type{ttv}));
|
CHECK_EQ("{string}", toString(Type{ttv}));
|
||||||
|
|
||||||
ttv.props["A"] = {builtinTypes->numberType};
|
ttv.props["A"] = {builtinTypes->numberType};
|
||||||
CHECK_EQ("{| [number]: string, A: number |}", toString(Type{ttv}));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ [number]: string, A: number }", toString(Type{ttv}));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| [number]: string, A: number |}", toString(Type{ttv}));
|
||||||
|
|
||||||
ttv.props.clear();
|
ttv.props.clear();
|
||||||
ttv.state = TableState::Unsealed;
|
ttv.state = TableState::Unsealed;
|
||||||
|
@ -576,8 +608,17 @@ TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_T
|
||||||
|
|
||||||
TypePackVar tpv2{TypePack{{&tv2}}};
|
TypePackVar tpv2{TypePack{{&tv2}}};
|
||||||
|
|
||||||
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv1));
|
|
||||||
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
CHECK_EQ("{ hello: number, world: number }", toString(&tpv1));
|
||||||
|
CHECK_EQ("{ hello: number, world: number }", toString(&tpv2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv1));
|
||||||
|
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_empty_head_link")
|
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_empty_head_link")
|
||||||
|
@ -846,7 +887,24 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||||
end
|
end
|
||||||
|
|
||||||
)");
|
)");
|
||||||
std::string expected = R"(Type
|
//clang-format off
|
||||||
|
std::string expected =
|
||||||
|
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||||
|
R"(Type
|
||||||
|
'{| a: number, b: string, c: {| d: string |} |}'
|
||||||
|
could not be converted into
|
||||||
|
'{ a: number, b: string, c: { d: number } }'
|
||||||
|
caused by:
|
||||||
|
Property 'c' is not compatible.
|
||||||
|
Type
|
||||||
|
'{| d: string |}'
|
||||||
|
could not be converted into
|
||||||
|
'{ d: number }'
|
||||||
|
caused by:
|
||||||
|
Property 'd' is not compatible.
|
||||||
|
Type 'string' could not be converted into 'number' in an invariant context)"
|
||||||
|
:
|
||||||
|
R"(Type
|
||||||
'{ a: number, b: string, c: { d: string } }'
|
'{ a: number, b: string, c: { d: string } }'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{| a: number, b: string, c: {| d: number |} |}'
|
'{| a: number, b: string, c: {| d: number |} |}'
|
||||||
|
@ -859,9 +917,12 @@ could not be converted into
|
||||||
caused by:
|
caused by:
|
||||||
Property 'd' is not compatible.
|
Property 'd' is not compatible.
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||||
|
//clang-format on
|
||||||
|
//
|
||||||
std::string actual = toString(result.errors[0]);
|
std::string actual = toString(result.errors[0]);
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK(expected == actual);
|
CHECK(expected == actual);
|
||||||
}
|
}
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -223,7 +223,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'
|
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'
|
||||||
caused by:
|
caused by:
|
||||||
Property 't' is not compatible.
|
Property 't' is not compatible.
|
||||||
Type '{ v: string }' could not be converted into 'T<number>'
|
Type '{| v: string |}' could not be converted into 'T<number>'
|
||||||
caused by:
|
caused by:
|
||||||
Property 'v' is not compatible.
|
Property 'v' is not compatible.
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||||
|
@ -332,7 +332,10 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||||
|
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
CHECK_EQ("t1 where t1 = ({| a: t1 |}) -> string", toString(tm->wantedType));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("t1 where t1 = ({ a: t1 }) -> string", toString(tm->wantedType));
|
||||||
|
else
|
||||||
|
CHECK_EQ("t1 where t1 = ({| a: t1 |}) -> string", toString(tm->wantedType));
|
||||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,7 +818,10 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
|
||||||
local d: FutureType = { smth = true } -- missing error, 'd' is resolved to 'any'
|
local d: FutureType = { smth = true } -- missing error, 'd' is resolved to 'any'
|
||||||
)");
|
)");
|
||||||
|
|
||||||
CHECK_EQ("{| foo: number |}", toString(requireType("d"), {true}));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ foo: number }", toString(requireType("d"), {true}));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| foo: number |}", toString(requireType("d"), {true}));
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,7 +398,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ("{| [number]: boolean | number | string, n: number |}", toString(requireType("t")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ [number]: boolean | number | string, n: number }", toString(requireType("t")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| [number]: boolean | number | string, n: number |}", toString(requireType("t")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_variadic")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_variadic")
|
||||||
|
@ -413,7 +416,10 @@ local t = table.pack(f())
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ("{| [number]: number | string, n: number |}", toString(requireType("t")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ [number]: number | string, n: number }", toString(requireType("t")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| [number]: number | string, n: number |}", toString(requireType("t")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
||||||
|
@ -423,14 +429,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ("{| [number]: boolean | number, n: number |}", toString(requireType("t")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ [number]: boolean | number, n: number }", toString(requireType("t")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| [number]: boolean | number, n: number |}", toString(requireType("t")));
|
||||||
|
|
||||||
result = check(R"(
|
result = check(R"(
|
||||||
local t = table.pack("a", "b", "c")
|
local t = table.pack("a", "b", "c")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gcinfo")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gcinfo")
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -2077,7 +2077,7 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |} & {| y: string |}");
|
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
|
||||||
else
|
else
|
||||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK("{| y: number |}" == toString(requireType("r")));
|
CHECK("{ y: number }" == toString(requireType("r")));
|
||||||
else
|
else
|
||||||
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
|
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
|
||||||
}
|
}
|
||||||
|
@ -513,7 +513,13 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type
|
const std::string expected =
|
||||||
|
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||||
|
"Type "
|
||||||
|
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
|
||||||
|
" could not be converted into "
|
||||||
|
"'{ p: nil }'; none of the intersection parts are compatible" :
|
||||||
|
R"(Type
|
||||||
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
'{| p: nil |}'; none of the intersection parts are compatible)";
|
||||||
|
@ -581,7 +587,13 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type
|
const std::string expected =
|
||||||
|
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||||
|
R"(Type
|
||||||
|
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
|
||||||
|
could not be converted into
|
||||||
|
'(number?) -> { p: number, q: number, r: number }'; none of the intersection parts are compatible)" :
|
||||||
|
R"(Type
|
||||||
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
|
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)";
|
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)";
|
||||||
|
@ -933,7 +945,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> never", toString(requireType("f")));
|
CHECK_EQ("({ x: number } & { x: string }) -> never", toString(requireType("f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_1")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_1")
|
||||||
|
|
|
@ -225,7 +225,10 @@ local tbl: string = require(game.A)
|
||||||
|
|
||||||
CheckResult result = frontend.check("game/B");
|
CheckResult result = frontend.check("game/B");
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ("Type '{| def: number |}' could not be converted into 'string'", toString(result.errors[0]));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("Type '{ def: number }' could not be converted into 'string'", toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
CHECK_EQ("Type '{| def: number |}' could not be converted into 'string'", toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok")
|
TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok")
|
||||||
|
|
|
@ -410,7 +410,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
|
||||||
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
|
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(0.5))
|
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(2))
|
||||||
{
|
{
|
||||||
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
|
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
|
||||||
// shared self should fix it, but there may be other mitigations possible as well
|
// shared self should fix it, but there may be other mitigations possible as well
|
||||||
|
|
|
@ -571,6 +571,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error")
|
||||||
CHECK_EQ("number", toString(requireType("a")));
|
CHECK_EQ("number", toString(requireType("a")));
|
||||||
|
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
|
REQUIRE_MESSAGE(tm, "Expected a TypeMismatch but got " << result.errors[0]);
|
||||||
|
|
||||||
REQUIRE_EQ(*tm->wantedType, *builtinTypes->numberType);
|
REQUIRE_EQ(*tm->wantedType, *builtinTypes->numberType);
|
||||||
REQUIRE_EQ(*tm->givenType, *builtinTypes->stringType);
|
REQUIRE_EQ(*tm->givenType, *builtinTypes->stringType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,10 +251,22 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("{| x: string, y: number |}", toString(requireTypeAtPosition({5, 28})));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
|
||||||
|
CHECK_EQ("{ x: string, y: number }", toString(requireTypeAtPosition({5, 28})));
|
||||||
|
|
||||||
|
// Should be { x: nil, y: nil }
|
||||||
|
CHECK_EQ("{ x: nil, y: nil } | { x: string, y: number }", toString(requireTypeAtPosition({7, 28})));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("{| x: string, y: number |}", toString(requireTypeAtPosition({5, 28})));
|
||||||
|
|
||||||
|
// Should be {| x: nil, y: nil |}
|
||||||
|
CHECK_EQ("{| x: nil, y: nil |} | {| x: string, y: number |}", toString(requireTypeAtPosition({7, 28})));
|
||||||
|
}
|
||||||
|
|
||||||
// Should be {| x: nil, y: nil |}
|
|
||||||
CHECK_EQ("{| x: nil, y: nil |} | {| x: string, y: number |}", toString(requireTypeAtPosition({7, 28})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated" * doctest::timeout(0.5))
|
TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated" * doctest::timeout(0.5))
|
||||||
|
|
|
@ -535,7 +535,10 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
||||||
|
@ -658,7 +661,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("{| x: number |} | {| y: boolean |}", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table"
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table"
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| x: number |} | {| y: boolean |}", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table"
|
||||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table"
|
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -697,7 +703,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_ta
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
opts.exhaustive = true;
|
opts.exhaustive = true;
|
||||||
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ x: number } & { y: number }", toString(requireTypeAtPosition({4, 28}), opts));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts));
|
||||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1216,8 +1225,17 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
|
||||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
CHECK_EQ(R"({ tag: "Part", x: Part })", toString(requireTypeAtPosition({5, 28})));
|
||||||
|
CHECK_EQ(R"({ tag: "Folder", x: Folder })", toString(requireTypeAtPosition({7, 28})));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||||
|
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||||
|
|
|
@ -91,7 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table")
|
||||||
|
|
||||||
// TODO: better, more robust comparison of type vars
|
// TODO: better, more robust comparison of type vars
|
||||||
auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true});
|
auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true});
|
||||||
CHECK_EQ(s, "{| prop: number |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(s, "{ prop: number }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(s, "{| prop: number |}");
|
||||||
CHECK_EQ(error->prop, "foo");
|
CHECK_EQ(error->prop, "foo");
|
||||||
CHECK_EQ(error->context, CannotExtendTable::Property);
|
CHECK_EQ(error->context, CannotExtendTable::Property);
|
||||||
}
|
}
|
||||||
|
@ -733,7 +736,10 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal")
|
||||||
CHECK(bool(retType->indexer));
|
CHECK(bool(retType->indexer));
|
||||||
|
|
||||||
const TableIndexer& indexer = *retType->indexer;
|
const TableIndexer& indexer = *retType->indexer;
|
||||||
CHECK_EQ("{| __name: string |}", toString(indexer.indexType));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ __name: string }", toString(indexer.indexType));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| __name: string |}", toString(indexer.indexType));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable")
|
TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable")
|
||||||
|
@ -775,7 +781,10 @@ TEST_CASE_FIXTURE(Fixture, "indexer_mismatch")
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm != nullptr);
|
REQUIRE(tm != nullptr);
|
||||||
CHECK(toString(tm->wantedType) == "{number}");
|
CHECK(toString(tm->wantedType) == "{number}");
|
||||||
CHECK(toString(tm->givenType) == "{| [string]: string |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK(toString(tm->givenType) == "{ [string]: string }");
|
||||||
|
else
|
||||||
|
CHECK(toString(tm->givenType) == "{| [string]: string |}");
|
||||||
|
|
||||||
CHECK_NE(*t1, *t2);
|
CHECK_NE(*t1, *t2);
|
||||||
}
|
}
|
||||||
|
@ -1564,8 +1573,16 @@ TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_ind
|
||||||
ToStringOptions o{/* exhaustive= */ true};
|
ToStringOptions o{/* exhaustive= */ true};
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK_EQ("{| foo: number |}", toString(tm->givenType, o));
|
{
|
||||||
|
CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o));
|
||||||
|
CHECK_EQ("{ foo: number }", toString(tm->givenType, o));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o));
|
||||||
|
CHECK_EQ("{| foo: number |}", toString(tm->givenType, o));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2")
|
TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2")
|
||||||
|
@ -1803,8 +1820,16 @@ TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
|
||||||
CHECK_EQ("Cannot add property 'a' to table '{| x: number |}'", toString(result.errors[0]));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK_EQ("Cannot add property 'b' to table '{| x: number |}'", toString(result.errors[1]));
|
{
|
||||||
|
CHECK_EQ("Cannot add property 'a' to table '{ x: number }'", toString(result.errors[0]));
|
||||||
|
CHECK_EQ("Cannot add property 'b' to table '{ x: number }'", toString(result.errors[1]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("Cannot add property 'a' to table '{| x: number |}'", toString(result.errors[0]));
|
||||||
|
CHECK_EQ("Cannot add property 'b' to table '{| x: number |}'", toString(result.errors[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
||||||
|
@ -2969,7 +2994,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "access_index_metamethod_that_returns_variadi
|
||||||
|
|
||||||
ToStringOptions o;
|
ToStringOptions o;
|
||||||
o.exhaustive = true;
|
o.exhaustive = true;
|
||||||
CHECK_EQ("{| x: string |}", toString(requireType("foo"), o));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ x: string }", toString(requireType("foo"), o));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| x: string |}", toString(requireType("foo"), o));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back")
|
TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back")
|
||||||
|
@ -3029,7 +3057,10 @@ TEST_CASE_FIXTURE(Fixture, "accidentally_checked_prop_in_opposite_branch")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0]));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0]));
|
||||||
CHECK_EQ("boolean", toString(requireType("u")));
|
CHECK_EQ("boolean", toString(requireType("u")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3260,7 +3291,10 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_unions_of_indexers_where_key_whose_ty
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0]));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("Type '{ [boolean]: number } | {number}' does not have key 'x'", toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
|
||||||
|
@ -3824,4 +3858,24 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
|
||||||
CHECK_EQ("prop", error2->properties[0]);
|
CHECK_EQ("prop", error2->properties[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "simple_method_definition")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local T = {}
|
||||||
|
|
||||||
|
function T:m()
|
||||||
|
return 5
|
||||||
|
end
|
||||||
|
|
||||||
|
return T
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ m: (unknown) -> number }", toString(getMainModule()->returnType, ToStringOptions{true}));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| m: <a>(a) -> number |}", toString(getMainModule()->returnType, ToStringOptions{true}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -69,6 +69,18 @@ TEST_CASE_FIXTURE(Fixture, "infer_locals_with_nil_value")
|
||||||
CHECK_EQ(getPrimitiveType(ty), PrimitiveType::String);
|
CHECK_EQ(getPrimitiveType(ty), PrimitiveType::String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "infer_locals_with_nil_value_2")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a = 2
|
||||||
|
local b = a,nil
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("number", toString(requireType("a")));
|
||||||
|
CHECK_EQ("number", toString(requireType("b")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -1168,6 +1180,28 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_higher_order_function")
|
||||||
CHECK(location.end.line == 4);
|
CHECK(location.end.line == 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_callback_property")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local print: (number) -> ()
|
||||||
|
|
||||||
|
type Point = {x: number, y: number}
|
||||||
|
local T : {callback: ((Point) -> ())?} = {}
|
||||||
|
|
||||||
|
T.callback = function(p) -- No error here
|
||||||
|
print(p.z) -- error here. Point has no property z
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CHECK_MESSAGE(get<UnknownProperty>(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]);
|
||||||
|
|
||||||
|
Location location = result.errors[0].location;
|
||||||
|
CHECK(location.begin.line == 7);
|
||||||
|
CHECK(location.end.line == 7);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
|
|
@ -373,12 +373,22 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||||
state.log.commit();
|
state.log.commit();
|
||||||
|
|
||||||
REQUIRE_EQ(state.errors.size(), 1);
|
REQUIRE_EQ(state.errors.size(), 1);
|
||||||
const std::string expected = R"(Type
|
// clang-format off
|
||||||
|
const std::string expected =
|
||||||
|
(FFlag::DebugLuauDeferredConstraintResolution) ?
|
||||||
|
R"(Type
|
||||||
|
'{ @metatable { __index: { foo: string } }, {| |} }'
|
||||||
|
could not be converted into
|
||||||
|
'{- foo: number -}'
|
||||||
|
caused by:
|
||||||
|
Type 'number' could not be converted into 'string')" :
|
||||||
|
R"(Type
|
||||||
'{ @metatable {| __index: {| foo: string |} |}, { } }'
|
'{ @metatable {| __index: {| foo: string |} |}, { } }'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{- foo: number -}'
|
'{- foo: number -}'
|
||||||
caused by:
|
caused by:
|
||||||
Type 'number' could not be converted into 'string')";
|
Type 'number' could not be converted into 'string')";
|
||||||
|
// clang-format on
|
||||||
CHECK_EQ(expected, toString(state.errors[0]));
|
CHECK_EQ(expected, toString(state.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -308,12 +308,18 @@ local c: Packed<string, number, boolean>
|
||||||
tf = lookupType("Packed");
|
tf = lookupType("Packed");
|
||||||
REQUIRE(tf);
|
REQUIRE(tf);
|
||||||
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
||||||
CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{ f: (T, U...) -> (T, U...) }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}");
|
||||||
|
|
||||||
auto ttvA = get<TableType>(requireType("a"));
|
auto ttvA = get<TableType>(requireType("a"));
|
||||||
REQUIRE(ttvA);
|
REQUIRE(ttvA);
|
||||||
CHECK_EQ(toString(requireType("a")), "Packed<number>");
|
CHECK_EQ(toString(requireType("a")), "Packed<number>");
|
||||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{ f: (number) -> number }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
||||||
REQUIRE(ttvA->instantiatedTypeParams.size() == 1);
|
REQUIRE(ttvA->instantiatedTypeParams.size() == 1);
|
||||||
REQUIRE(ttvA->instantiatedTypePackParams.size() == 1);
|
REQUIRE(ttvA->instantiatedTypePackParams.size() == 1);
|
||||||
CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number");
|
CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number");
|
||||||
|
@ -322,7 +328,10 @@ local c: Packed<string, number, boolean>
|
||||||
auto ttvB = get<TableType>(requireType("b"));
|
auto ttvB = get<TableType>(requireType("b"));
|
||||||
REQUIRE(ttvB);
|
REQUIRE(ttvB);
|
||||||
CHECK_EQ(toString(requireType("b")), "Packed<string, number>");
|
CHECK_EQ(toString(requireType("b")), "Packed<string, number>");
|
||||||
CHECK_EQ(toString(requireType("b"), {true}), "{| f: (string, number) -> (string, number) |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireType("b"), {true}), "{ f: (string, number) -> (string, number) }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireType("b"), {true}), "{| f: (string, number) -> (string, number) |}");
|
||||||
REQUIRE(ttvB->instantiatedTypeParams.size() == 1);
|
REQUIRE(ttvB->instantiatedTypeParams.size() == 1);
|
||||||
REQUIRE(ttvB->instantiatedTypePackParams.size() == 1);
|
REQUIRE(ttvB->instantiatedTypePackParams.size() == 1);
|
||||||
CHECK_EQ(toString(ttvB->instantiatedTypeParams[0], {true}), "string");
|
CHECK_EQ(toString(ttvB->instantiatedTypeParams[0], {true}), "string");
|
||||||
|
@ -331,7 +340,10 @@ local c: Packed<string, number, boolean>
|
||||||
auto ttvC = get<TableType>(requireType("c"));
|
auto ttvC = get<TableType>(requireType("c"));
|
||||||
REQUIRE(ttvC);
|
REQUIRE(ttvC);
|
||||||
CHECK_EQ(toString(requireType("c")), "Packed<string, number, boolean>");
|
CHECK_EQ(toString(requireType("c")), "Packed<string, number, boolean>");
|
||||||
CHECK_EQ(toString(requireType("c"), {true}), "{| f: (string, number, boolean) -> (string, number, boolean) |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireType("c"), {true}), "{ f: (string, number, boolean) -> (string, number, boolean) }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireType("c"), {true}), "{| f: (string, number, boolean) -> (string, number, boolean) |}");
|
||||||
REQUIRE(ttvC->instantiatedTypeParams.size() == 1);
|
REQUIRE(ttvC->instantiatedTypeParams.size() == 1);
|
||||||
REQUIRE(ttvC->instantiatedTypePackParams.size() == 1);
|
REQUIRE(ttvC->instantiatedTypePackParams.size() == 1);
|
||||||
CHECK_EQ(toString(ttvC->instantiatedTypeParams[0], {true}), "string");
|
CHECK_EQ(toString(ttvC->instantiatedTypeParams[0], {true}), "string");
|
||||||
|
@ -360,12 +372,25 @@ local d: { a: typeof(c) }
|
||||||
auto tf = lookupImportedType("Import", "Packed");
|
auto tf = lookupImportedType("Import", "Packed");
|
||||||
REQUIRE(tf);
|
REQUIRE(tf);
|
||||||
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
CHECK_EQ(toString(*tf), "Packed<T, U...>");
|
||||||
CHECK_EQ(toString(*tf, {true}), "{| a: T, b: (U...) -> () |}");
|
|
||||||
|
|
||||||
CHECK_EQ(toString(requireType("a"), {true}), "{| a: number, b: () -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK_EQ(toString(requireType("b"), {true}), "{| a: string, b: (number) -> () |}");
|
{
|
||||||
CHECK_EQ(toString(requireType("c"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
CHECK_EQ(toString(*tf, {true}), "{ a: T, b: (U...) -> () }");
|
||||||
CHECK_EQ(toString(requireType("d")), "{| a: Packed<string, number, boolean> |}");
|
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{ a: number, b: () -> () }");
|
||||||
|
CHECK_EQ(toString(requireType("b"), {true}), "{ a: string, b: (number) -> () }");
|
||||||
|
CHECK_EQ(toString(requireType("c"), {true}), "{ a: string, b: (number, boolean) -> () }");
|
||||||
|
CHECK_EQ(toString(requireType("d")), "{ a: Packed<string, number, boolean> }");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{| a: T, b: (U...) -> () |}");
|
||||||
|
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{| a: number, b: () -> () |}");
|
||||||
|
CHECK_EQ(toString(requireType("b"), {true}), "{| a: string, b: (number) -> () |}");
|
||||||
|
CHECK_EQ(toString(requireType("c"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
||||||
|
CHECK_EQ(toString(requireType("d")), "{| a: Packed<string, number, boolean> |}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_pack_type_parameters")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_pack_type_parameters")
|
||||||
|
@ -388,19 +413,31 @@ type C<X...> = Import.Packed<string, (number, X...)>
|
||||||
auto tf = lookupType("Alias");
|
auto tf = lookupType("Alias");
|
||||||
REQUIRE(tf);
|
REQUIRE(tf);
|
||||||
CHECK_EQ(toString(*tf), "Alias<S, T, R...>");
|
CHECK_EQ(toString(*tf), "Alias<S, T, R...>");
|
||||||
CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{ a: S, b: (T, R...) -> () }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}");
|
||||||
|
|
||||||
CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{ a: string, b: (number, boolean) -> () }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}");
|
||||||
|
|
||||||
tf = lookupType("B");
|
tf = lookupType("B");
|
||||||
REQUIRE(tf);
|
REQUIRE(tf);
|
||||||
CHECK_EQ(toString(*tf), "B<X...>");
|
CHECK_EQ(toString(*tf), "B<X...>");
|
||||||
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (X...) -> () }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}");
|
||||||
|
|
||||||
tf = lookupType("C");
|
tf = lookupType("C");
|
||||||
REQUIRE(tf);
|
REQUIRE(tf);
|
||||||
CHECK_EQ(toString(*tf), "C<X...>");
|
CHECK_EQ(toString(*tf), "C<X...>");
|
||||||
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (number, X...) -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (number, X...) -> () }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (number, X...) -> () |}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested")
|
TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested")
|
||||||
|
@ -867,7 +904,10 @@ type R = { m: F<R> }
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}");
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = { m: (t1) -> (t1) -> () }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check")
|
TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check")
|
||||||
|
|
|
@ -356,7 +356,10 @@ a.x = 2
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
auto s = toString(result.errors[0]);
|
auto s = toString(result.errors[0]);
|
||||||
CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", s);
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("Value of type '({ x: number } & { y: number })?' could be nil", s);
|
||||||
|
else
|
||||||
|
CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||||
|
@ -471,11 +474,20 @@ local b: { w: number } = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
caused by:
|
REQUIRE(tm);
|
||||||
Not all union options are compatible.
|
|
||||||
Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')";
|
CHECK_EQ(tm->reason, "Not all union options are compatible.");
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
|
||||||
|
CHECK_EQ("X | Y | Z", toString(tm->givenType));
|
||||||
|
|
||||||
|
const TableType* expected = get<TableType>(tm->wantedType);
|
||||||
|
REQUIRE(expected);
|
||||||
|
CHECK_EQ(TableState::Sealed, expected->state);
|
||||||
|
CHECK_EQ(1, expected->props.size());
|
||||||
|
auto propW = expected->props.find("w");
|
||||||
|
REQUIRE_NE(expected->props.end(), propW);
|
||||||
|
CHECK_EQ("number", toString(propW->second.type()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
|
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
|
||||||
|
@ -744,7 +756,10 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> number | string", toString(requireType("f")));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("({ x: number } | { x: string }) -> number | string", toString(requireType("f")));
|
||||||
|
else
|
||||||
|
CHECK_EQ("({| x: number |} | {| x: string |}) -> number | string", toString(requireType("f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "union_table_any_property")
|
TEST_CASE_FIXTURE(Fixture, "union_table_any_property")
|
||||||
|
|
|
@ -298,7 +298,10 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
||||||
|
|
||||||
REQUIRE(!anyification.normalizationTooComplex);
|
REQUIRE(!anyification.normalizationTooComplex);
|
||||||
REQUIRE(any.has_value());
|
REQUIRE(any.has_value());
|
||||||
CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(*any));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(*any));
|
||||||
|
else
|
||||||
|
CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(*any));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("tagging_tables")
|
TEST_CASE("tagging_tables")
|
||||||
|
|
|
@ -38,6 +38,11 @@ struct Unifier2Fixture
|
||||||
{
|
{
|
||||||
return ::Luau::toString(ty, opts);
|
return ::Luau::toString(ty, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string toString(TypePackId ty)
|
||||||
|
{
|
||||||
|
return ::Luau::toString(ty, opts);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("Unifier2");
|
TEST_SUITE_BEGIN("Unifier2");
|
||||||
|
@ -99,6 +104,32 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||||
CHECK(!yPack->tail);
|
CHECK(!yPack->tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Unifier2Fixture, "unify_binds_free_subtype_tail_pack")
|
||||||
|
{
|
||||||
|
TypePackId numberPack = arena.addTypePack({builtinTypes.numberType});
|
||||||
|
|
||||||
|
TypePackId freeTail = arena.freshTypePack(&scope);
|
||||||
|
TypeId freeHead = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType});
|
||||||
|
TypePackId freeAndFree = arena.addTypePack({freeHead}, freeTail);
|
||||||
|
|
||||||
|
u2.unify(freeAndFree, numberPack);
|
||||||
|
|
||||||
|
CHECK("('a <: number)" == toString(freeAndFree));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Unifier2Fixture, "unify_binds_free_supertype_tail_pack")
|
||||||
|
{
|
||||||
|
TypePackId numberPack = arena.addTypePack({builtinTypes.numberType});
|
||||||
|
|
||||||
|
TypePackId freeTail = arena.freshTypePack(&scope);
|
||||||
|
TypeId freeHead = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType});
|
||||||
|
TypePackId freeAndFree = arena.addTypePack({freeHead}, freeTail);
|
||||||
|
|
||||||
|
u2.unify(numberPack, freeAndFree);
|
||||||
|
|
||||||
|
CHECK("(number <: 'a)" == toString(freeAndFree));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type")
|
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type")
|
||||||
{
|
{
|
||||||
auto [t1, ft1] = freshType();
|
auto [t1, ft1] = freshType();
|
||||||
|
|
|
@ -18,6 +18,11 @@ assert(os.date(string.rep("%d", 1000), t) ==
|
||||||
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
|
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
|
||||||
assert(os.date("", -1) == nil)
|
assert(os.date("", -1) == nil)
|
||||||
|
|
||||||
|
assert(os.time({ year = 1969, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == nil) -- just before start
|
||||||
|
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0}) == 0) -- start
|
||||||
|
assert(os.time({ year = 3000, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == 32535215999) -- just before Windows max range
|
||||||
|
assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = -1}) == nil) -- going before using time fields
|
||||||
|
|
||||||
local function checkDateTable (t)
|
local function checkDateTable (t)
|
||||||
local D = os.date("!*t", t)
|
local D = os.date("!*t", t)
|
||||||
assert(os.time(D) == t)
|
assert(os.time(D) == t)
|
||||||
|
|
|
@ -171,4 +171,38 @@ end
|
||||||
|
|
||||||
nilInvalidatesSlot()
|
nilInvalidatesSlot()
|
||||||
|
|
||||||
|
local function arraySizeOpt1(a)
|
||||||
|
a[1] += 2
|
||||||
|
a[1] *= 3
|
||||||
|
|
||||||
|
table.insert(a, 3)
|
||||||
|
table.insert(a, 4)
|
||||||
|
table.insert(a, 5)
|
||||||
|
table.insert(a, 6)
|
||||||
|
|
||||||
|
a[1] += 4
|
||||||
|
a[1] *= 5
|
||||||
|
|
||||||
|
return a[1] + a[5]
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(arraySizeOpt1({1}) == 71)
|
||||||
|
|
||||||
|
local function arraySizeOpt2(a, i)
|
||||||
|
a[i] += 2
|
||||||
|
a[i] *= 3
|
||||||
|
|
||||||
|
table.insert(a, 3)
|
||||||
|
table.insert(a, 4)
|
||||||
|
table.insert(a, 5)
|
||||||
|
table.insert(a, 6)
|
||||||
|
|
||||||
|
a[i] += 4
|
||||||
|
a[i] *= 5
|
||||||
|
|
||||||
|
return a[i] + a[5]
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(arraySizeOpt1({1}, 1) == 71)
|
||||||
|
|
||||||
return('OK')
|
return('OK')
|
||||||
|
|
|
@ -94,6 +94,8 @@ static int testAssertionHandler(const char* expr, const char* file, int line, co
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct BoostLikeReporter : doctest::IReporter
|
struct BoostLikeReporter : doctest::IReporter
|
||||||
{
|
{
|
||||||
const doctest::TestCaseData* currentTest = nullptr;
|
const doctest::TestCaseData* currentTest = nullptr;
|
||||||
|
@ -255,6 +257,8 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
Luau::assertHandler() = testAssertionHandler;
|
Luau::assertHandler() = testAssertionHandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
doctest::registerReporter<BoostLikeReporter>("boost", 0, true);
|
doctest::registerReporter<BoostLikeReporter>("boost", 0, true);
|
||||||
|
|
||||||
doctest::Context context;
|
doctest::Context context;
|
||||||
|
|
|
@ -14,9 +14,12 @@ AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||||
|
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||||
|
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||||
AutocompleteTest.type_correct_function_no_parenthesis
|
AutocompleteTest.type_correct_function_no_parenthesis
|
||||||
AutocompleteTest.type_correct_function_return_types
|
AutocompleteTest.type_correct_function_return_types
|
||||||
AutocompleteTest.type_correct_keywords
|
AutocompleteTest.type_correct_keywords
|
||||||
|
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||||
AutocompleteTest.type_correct_suggestion_in_argument
|
AutocompleteTest.type_correct_suggestion_in_argument
|
||||||
AutocompleteTest.unsealed_table_2
|
AutocompleteTest.unsealed_table_2
|
||||||
BuiltinTests.aliased_string_format
|
BuiltinTests.aliased_string_format
|
||||||
|
@ -55,8 +58,35 @@ BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||||
BuiltinTests.table_freeze_is_generic
|
BuiltinTests.table_freeze_is_generic
|
||||||
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
||||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||||
|
BuiltinTests.table_pack_variadic
|
||||||
BuiltinTests.trivial_select
|
BuiltinTests.trivial_select
|
||||||
BuiltinTests.xpcall
|
BuiltinTests.xpcall
|
||||||
|
ControlFlowAnalysis.for_record_do_if_not_x_break
|
||||||
|
ControlFlowAnalysis.for_record_do_if_not_x_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_break
|
||||||
|
ControlFlowAnalysis.if_not_x_break_elif_not_y_break
|
||||||
|
ControlFlowAnalysis.if_not_x_break_elif_not_y_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_break_elif_not_y_fallthrough_elif_not_z_break
|
||||||
|
ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_break
|
||||||
|
ControlFlowAnalysis.if_not_x_break_elif_rand_break_elif_not_y_fallthrough
|
||||||
|
ControlFlowAnalysis.if_not_x_break_if_not_y_break
|
||||||
|
ControlFlowAnalysis.if_not_x_break_if_not_y_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_elif_not_y_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_elif_rand_continue_elif_not_y_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_if_not_y_continue
|
||||||
|
ControlFlowAnalysis.if_not_x_continue_if_not_y_throw
|
||||||
|
ControlFlowAnalysis.if_not_x_return_elif_not_y_break
|
||||||
|
ControlFlowAnalysis.if_not_x_return_elif_not_y_fallthrough_elif_not_z_break
|
||||||
|
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_breaking
|
||||||
|
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing
|
||||||
|
ControlFlowAnalysis.tagged_unions_breaking
|
||||||
|
ControlFlowAnalysis.tagged_unions_continuing
|
||||||
|
ControlFlowAnalysis.type_alias_does_not_leak_out_breaking
|
||||||
|
ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
|
||||||
DefinitionTests.class_definition_indexer
|
DefinitionTests.class_definition_indexer
|
||||||
DefinitionTests.class_definition_overload_metamethods
|
DefinitionTests.class_definition_overload_metamethods
|
||||||
DefinitionTests.class_definition_string_props
|
DefinitionTests.class_definition_string_props
|
||||||
|
@ -81,7 +111,6 @@ GenericsTests.generic_argument_count_too_many
|
||||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
GenericsTests.generic_functions_dont_cache_type_parameters
|
||||||
GenericsTests.generic_functions_should_be_memory_safe
|
GenericsTests.generic_functions_should_be_memory_safe
|
||||||
GenericsTests.generic_type_pack_parentheses
|
GenericsTests.generic_type_pack_parentheses
|
||||||
GenericsTests.generic_type_pack_unification2
|
|
||||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||||
GenericsTests.hof_subtype_instantiation_regression
|
GenericsTests.hof_subtype_instantiation_regression
|
||||||
GenericsTests.infer_generic_function_function_argument
|
GenericsTests.infer_generic_function_function_argument
|
||||||
|
@ -90,9 +119,6 @@ GenericsTests.infer_generic_function_function_argument_3
|
||||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||||
GenericsTests.infer_generic_lib_function_function_argument
|
GenericsTests.infer_generic_lib_function_function_argument
|
||||||
GenericsTests.infer_generic_property
|
GenericsTests.infer_generic_property
|
||||||
GenericsTests.instantiate_cyclic_generic_function
|
|
||||||
GenericsTests.instantiate_generic_function_in_assignments
|
|
||||||
GenericsTests.instantiate_generic_function_in_assignments2
|
|
||||||
GenericsTests.instantiated_function_argument_names
|
GenericsTests.instantiated_function_argument_names
|
||||||
GenericsTests.mutable_state_polymorphism
|
GenericsTests.mutable_state_polymorphism
|
||||||
GenericsTests.no_stack_overflow_from_quantifying
|
GenericsTests.no_stack_overflow_from_quantifying
|
||||||
|
@ -135,7 +161,6 @@ RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||||
RefinementTest.narrow_property_of_a_bounded_variable
|
RefinementTest.narrow_property_of_a_bounded_variable
|
||||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||||
RefinementTest.not_t_or_some_prop_of_t
|
RefinementTest.not_t_or_some_prop_of_t
|
||||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage_2
|
|
||||||
RefinementTest.refine_a_property_of_some_global
|
RefinementTest.refine_a_property_of_some_global
|
||||||
RefinementTest.truthy_constraint_on_properties
|
RefinementTest.truthy_constraint_on_properties
|
||||||
RefinementTest.type_narrow_to_vector
|
RefinementTest.type_narrow_to_vector
|
||||||
|
@ -193,9 +218,9 @@ TableTests.shared_selfs
|
||||||
TableTests.shared_selfs_from_free_param
|
TableTests.shared_selfs_from_free_param
|
||||||
TableTests.shared_selfs_through_metatables
|
TableTests.shared_selfs_through_metatables
|
||||||
TableTests.table_call_metamethod_basic
|
TableTests.table_call_metamethod_basic
|
||||||
|
TableTests.table_call_metamethod_generic
|
||||||
TableTests.table_param_width_subtyping_1
|
TableTests.table_param_width_subtyping_1
|
||||||
TableTests.table_param_width_subtyping_2
|
TableTests.table_param_width_subtyping_2
|
||||||
TableTests.table_param_width_subtyping_3
|
|
||||||
TableTests.table_simple_call
|
TableTests.table_simple_call
|
||||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||||
|
@ -213,9 +238,7 @@ ToString.named_metatable_toStringNamedFunction
|
||||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||||
ToString.toStringDetailed2
|
ToString.toStringDetailed2
|
||||||
ToString.toStringErrorPack
|
ToString.toStringErrorPack
|
||||||
ToString.toStringGenericPack
|
|
||||||
ToString.toStringNamedFunction_generic_pack
|
ToString.toStringNamedFunction_generic_pack
|
||||||
ToString.toStringNamedFunction_map
|
|
||||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||||
|
@ -239,8 +262,7 @@ TypeFamilyTests.function_internal_families
|
||||||
TypeFamilyTests.internal_families_raise_errors
|
TypeFamilyTests.internal_families_raise_errors
|
||||||
TypeFamilyTests.table_internal_families
|
TypeFamilyTests.table_internal_families
|
||||||
TypeFamilyTests.unsolvable_family
|
TypeFamilyTests.unsolvable_family
|
||||||
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
TypeInfer.bidirectional_checking_of_callback_property
|
||||||
TypeInfer.bidirectional_checking_of_higher_order_function
|
|
||||||
TypeInfer.check_expr_recursion_limit
|
TypeInfer.check_expr_recursion_limit
|
||||||
TypeInfer.check_type_infer_recursion_count
|
TypeInfer.check_type_infer_recursion_count
|
||||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||||
|
@ -249,14 +271,12 @@ TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||||
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
|
||||||
TypeInfer.interesting_local_type_inference_case
|
|
||||||
TypeInfer.no_stack_overflow_from_isoptional
|
TypeInfer.no_stack_overflow_from_isoptional
|
||||||
TypeInfer.promote_tail_type_packs
|
TypeInfer.promote_tail_type_packs
|
||||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
TypeInfer.type_infer_cache_limit_normalizer
|
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||||
TypeInfer.type_infer_recursion_limit_no_ice
|
TypeInfer.type_infer_recursion_limit_no_ice
|
||||||
TypeInfer.type_infer_recursion_limit_normalizer
|
TypeInfer.type_infer_recursion_limit_normalizer
|
||||||
TypeInferAnyError.can_subscript_any
|
TypeInferAnyError.can_subscript_any
|
||||||
|
@ -274,7 +294,6 @@ TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||||
TypeInferClasses.detailed_class_unification_error
|
TypeInferClasses.detailed_class_unification_error
|
||||||
TypeInferClasses.index_instance_property
|
TypeInferClasses.index_instance_property
|
||||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||||
TypeInferClasses.table_indexers_are_invariant
|
|
||||||
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
||||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||||
|
@ -295,7 +314,6 @@ TypeInferFunctions.infer_anonymous_function_arguments
|
||||||
TypeInferFunctions.infer_generic_function_function_argument
|
TypeInferFunctions.infer_generic_function_function_argument
|
||||||
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||||
TypeInferFunctions.infer_higher_order_function
|
|
||||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||||
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
||||||
|
@ -336,6 +354,7 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||||
TypeInferModules.bound_free_table_export_is_ok
|
TypeInferModules.bound_free_table_export_is_ok
|
||||||
|
TypeInferModules.do_not_modify_imported_types_4
|
||||||
TypeInferModules.do_not_modify_imported_types_5
|
TypeInferModules.do_not_modify_imported_types_5
|
||||||
TypeInferModules.module_type_conflict
|
TypeInferModules.module_type_conflict
|
||||||
TypeInferModules.module_type_conflict_instantiated
|
TypeInferModules.module_type_conflict_instantiated
|
||||||
|
@ -358,6 +377,7 @@ TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||||
TypeInferOperators.luau_polyfill_is_array
|
TypeInferOperators.luau_polyfill_is_array
|
||||||
TypeInferOperators.operator_eq_completely_incompatible
|
TypeInferOperators.operator_eq_completely_incompatible
|
||||||
|
TypeInferOperators.reducing_and
|
||||||
TypeInferOperators.strict_binary_op_where_lhs_unknown
|
TypeInferOperators.strict_binary_op_where_lhs_unknown
|
||||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||||
|
@ -370,7 +390,7 @@ TypeInferPrimitives.CheckMethodsOfNumber
|
||||||
TypeInferPrimitives.string_index
|
TypeInferPrimitives.string_index
|
||||||
TypeInferUnknownNever.length_of_never
|
TypeInferUnknownNever.length_of_never
|
||||||
TypeInferUnknownNever.math_operators_and_never
|
TypeInferUnknownNever.math_operators_and_never
|
||||||
TypePackTests.higher_order_function
|
TypePackTests.fuzz_typepack_iter_follow_2
|
||||||
TypePackTests.pack_tail_unification_check
|
TypePackTests.pack_tail_unification_check
|
||||||
TypePackTests.type_alias_backwards_compatible
|
TypePackTests.type_alias_backwards_compatible
|
||||||
TypePackTests.type_alias_default_type_errors
|
TypePackTests.type_alias_default_type_errors
|
||||||
|
@ -378,6 +398,7 @@ TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
||||||
TypeSingletons.function_args_infer_singletons
|
TypeSingletons.function_args_infer_singletons
|
||||||
TypeSingletons.function_call_with_singletons
|
TypeSingletons.function_call_with_singletons
|
||||||
TypeSingletons.function_call_with_singletons_mismatch
|
TypeSingletons.function_call_with_singletons_mismatch
|
||||||
|
TypeSingletons.overloaded_function_call_with_singletons
|
||||||
TypeSingletons.return_type_of_f_is_not_widened
|
TypeSingletons.return_type_of_f_is_not_widened
|
||||||
TypeSingletons.table_properties_type_error_escapes
|
TypeSingletons.table_properties_type_error_escapes
|
||||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||||
|
@ -385,3 +406,4 @@ TypeSingletons.widening_happens_almost_everywhere
|
||||||
UnionTypes.index_on_a_union_type_with_missing_property
|
UnionTypes.index_on_a_union_type_with_missing_property
|
||||||
UnionTypes.less_greedy_unification_with_union_types
|
UnionTypes.less_greedy_unification_with_union_types
|
||||||
UnionTypes.table_union_write_indirect
|
UnionTypes.table_union_write_indirect
|
||||||
|
UnionTypes.unify_unsealed_table_union_check
|
||||||
|
|
Loading…
Reference in a new issue