mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Sync to upstream/release/524 (#462)
This commit is contained in:
parent
5bb9f379b0
commit
e0a6461173
53 changed files with 1596 additions and 355 deletions
|
@ -18,7 +18,7 @@ struct CloneState
|
||||||
SeenTypePacks seenTypePacks;
|
SeenTypePacks seenTypePacks;
|
||||||
|
|
||||||
int recursionCount = 0;
|
int recursionCount = 0;
|
||||||
bool encounteredFreeType = false;
|
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
|
||||||
};
|
};
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||||
|
LUAU_FASTFLAG(LuauDirtySourceModule)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -57,19 +58,27 @@ std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleNa
|
||||||
|
|
||||||
struct SourceNode
|
struct SourceNode
|
||||||
{
|
{
|
||||||
bool isDirty(bool forAutocomplete) const
|
bool hasDirtySourceModule() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
|
||||||
|
|
||||||
|
return dirtySourceModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasDirtyModule(bool forAutocomplete) const
|
||||||
{
|
{
|
||||||
if (FFlag::LuauSeparateTypechecks)
|
if (FFlag::LuauSeparateTypechecks)
|
||||||
return forAutocomplete ? dirtyAutocomplete : dirty;
|
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||||
else
|
else
|
||||||
return dirty;
|
return dirtyModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleName name;
|
ModuleName name;
|
||||||
std::unordered_set<ModuleName> requires;
|
std::unordered_set<ModuleName> requires;
|
||||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||||
bool dirty = true;
|
bool dirtySourceModule = true;
|
||||||
bool dirtyAutocomplete = true;
|
bool dirtyModule = true;
|
||||||
|
bool dirtyModuleForAutocomplete = true;
|
||||||
double autocompleteLimitsMult = 1.0;
|
double autocompleteLimitsMult = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -163,7 +172,7 @@ struct Frontend
|
||||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete);
|
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED);
|
||||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||||
|
|
||||||
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);
|
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);
|
||||||
|
|
|
@ -373,15 +373,17 @@ struct ClassTypeVar
|
||||||
std::optional<TypeId> metatable; // metaclass?
|
std::optional<TypeId> metatable; // metaclass?
|
||||||
Tags tags;
|
Tags tags;
|
||||||
std::shared_ptr<ClassUserData> userData;
|
std::shared_ptr<ClassUserData> userData;
|
||||||
|
ModuleName definitionModuleName;
|
||||||
|
|
||||||
ClassTypeVar(
|
ClassTypeVar(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
|
||||||
Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags, std::shared_ptr<ClassUserData> userData)
|
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName)
|
||||||
: name(name)
|
: name(name)
|
||||||
, props(props)
|
, props(props)
|
||||||
, parent(parent)
|
, parent(parent)
|
||||||
, metatable(metatable)
|
, metatable(metatable)
|
||||||
, tags(tags)
|
, tags(tags)
|
||||||
, userData(userData)
|
, userData(userData)
|
||||||
|
, definitionModuleName(definitionModuleName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,7 +92,6 @@ private:
|
||||||
|
|
||||||
bool canCacheResult(TypeId subTy, TypeId superTy);
|
bool canCacheResult(TypeId subTy, TypeId superTy);
|
||||||
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
|
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
|
||||||
void cacheResult_DEPRECATED(TypeId subTy, TypeId superTy);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||||
|
|
|
@ -52,7 +52,7 @@ inline void unsee(std::unordered_set<void*>& seen, const void* tv)
|
||||||
|
|
||||||
inline void unsee(DenseHashSet<void*>& seen, const void* tv)
|
inline void unsee(DenseHashSet<void*>& seen, const void* tv)
|
||||||
{
|
{
|
||||||
// When DenseHashSet is used for 'visitOnce', where don't forget visited elements
|
// When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F, typename Set>
|
template<typename F, typename Set>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSingletonTypes, false);
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSingletonTypes, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteClassSecurityLevel, false);
|
||||||
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix)
|
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix)
|
||||||
|
|
||||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||||
|
@ -462,7 +463,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
|
||||||
containingClass = containingClass.value_or(cls);
|
containingClass = containingClass.value_or(cls);
|
||||||
fillProps(cls->props);
|
fillProps(cls->props);
|
||||||
if (cls->parent)
|
if (cls->parent)
|
||||||
autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen, cls);
|
autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen,
|
||||||
|
FFlag::LuauFixAutocompleteClassSecurityLevel ? containingClass : cls);
|
||||||
}
|
}
|
||||||
else if (auto tbl = get<TableTypeVar>(ty))
|
else if (auto tbl = get<TableTypeVar>(ty))
|
||||||
fillProps(tbl->props);
|
fillProps(tbl->props);
|
||||||
|
|
|
@ -10,6 +10,7 @@ LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -87,11 +88,18 @@ struct TypePackCloner
|
||||||
|
|
||||||
void operator()(const Unifiable::Free& t)
|
void operator()(const Unifiable::Free& t)
|
||||||
{
|
{
|
||||||
cloneState.encounteredFreeType = true;
|
if (FFlag::LuauLosslessClone)
|
||||||
|
{
|
||||||
|
defaultClone(t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cloneState.encounteredFreeType = true;
|
||||||
|
|
||||||
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
|
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
|
||||||
TypePackId cloned = dest.addTypePack(*err);
|
TypePackId cloned = dest.addTypePack(*err);
|
||||||
seenTypePacks[typePackId] = cloned;
|
seenTypePacks[typePackId] = cloned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const Unifiable::Generic& t)
|
void operator()(const Unifiable::Generic& t)
|
||||||
|
@ -143,10 +151,18 @@ void TypeCloner::defaultClone(const T& t)
|
||||||
|
|
||||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||||
{
|
{
|
||||||
cloneState.encounteredFreeType = true;
|
if (FFlag::LuauLosslessClone)
|
||||||
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
|
{
|
||||||
TypeId cloned = dest.addType(*err);
|
defaultClone(t);
|
||||||
seenTypes[typeId] = cloned;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cloneState.encounteredFreeType = true;
|
||||||
|
|
||||||
|
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
|
||||||
|
TypeId cloned = dest.addType(*err);
|
||||||
|
seenTypes[typeId] = cloned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeCloner::operator()(const Unifiable::Generic& t)
|
void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||||
|
@ -174,7 +190,8 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
||||||
|
|
||||||
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
||||||
{
|
{
|
||||||
cloneState.encounteredFreeType = true;
|
if (!FFlag::LuauLosslessClone)
|
||||||
|
cloneState.encounteredFreeType = true;
|
||||||
|
|
||||||
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
||||||
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
|
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
|
||||||
|
@ -252,7 +269,7 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
||||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||||
arg = clone(arg, dest, cloneState);
|
arg = clone(arg, dest, cloneState);
|
||||||
|
|
||||||
if (ttv->state == TableState::Free)
|
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
|
||||||
{
|
{
|
||||||
cloneState.encounteredFreeType = true;
|
cloneState.encounteredFreeType = true;
|
||||||
|
|
||||||
|
@ -276,7 +293,7 @@ void TypeCloner::operator()(const MetatableTypeVar& t)
|
||||||
|
|
||||||
void TypeCloner::operator()(const ClassTypeVar& t)
|
void TypeCloner::operator()(const ClassTypeVar& t)
|
||||||
{
|
{
|
||||||
TypeId result = dest.addType(ClassTypeVar{t.name, {}, std::nullopt, std::nullopt, t.tags, t.userData});
|
TypeId result = dest.addType(ClassTypeVar{t.name, {}, std::nullopt, std::nullopt, t.tags, t.userData, t.definitionModuleName});
|
||||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(result);
|
ClassTypeVar* ctv = getMutable<ClassTypeVar>(result);
|
||||||
|
|
||||||
seenTypes[typeId] = result;
|
seenTypes[typeId] = result;
|
||||||
|
@ -361,7 +378,10 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||||
|
|
||||||
// Persistent types are not being cloned and we get the original type back which might be read-only
|
// Persistent types are not being cloned and we get the original type back which might be read-only
|
||||||
if (!res->persistent)
|
if (!res->persistent)
|
||||||
|
{
|
||||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||||
|
asMutable(res)->normal = typeId->normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleName, false);
|
|
||||||
|
|
||||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||||
{
|
{
|
||||||
std::string s = "expects ";
|
std::string s = "expects ";
|
||||||
|
@ -59,27 +57,20 @@ struct ErrorConverter
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
if (FFlag::LuauTypeMismatchModuleName)
|
if (givenTypeName == wantedTypeName)
|
||||||
{
|
{
|
||||||
if (givenTypeName == wantedTypeName)
|
if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType))
|
||||||
{
|
{
|
||||||
if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType))
|
if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType))
|
||||||
{
|
{
|
||||||
if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType))
|
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
|
||||||
{
|
"' from '" + *wantedDefinitionModule + "'";
|
||||||
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
|
|
||||||
"' from '" + *wantedDefinitionModule + "'";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.empty())
|
if (result.empty())
|
||||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||||
}
|
|
||||||
|
|
||||||
if (tm.error)
|
if (tm.error)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
|
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
|
||||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 0)
|
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 0)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -358,7 +359,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||||
CheckResult checkResult;
|
CheckResult checkResult;
|
||||||
|
|
||||||
auto it = sourceNodes.find(name);
|
auto it = sourceNodes.find(name);
|
||||||
if (it != sourceNodes.end() && !it->second.isDirty(frontendOptions.forAutocomplete))
|
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
|
||||||
{
|
{
|
||||||
// No recheck required.
|
// No recheck required.
|
||||||
if (FFlag::LuauSeparateTypechecks)
|
if (FFlag::LuauSeparateTypechecks)
|
||||||
|
@ -402,7 +403,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||||
LUAU_ASSERT(sourceNodes.count(moduleName));
|
LUAU_ASSERT(sourceNodes.count(moduleName));
|
||||||
SourceNode& sourceNode = sourceNodes[moduleName];
|
SourceNode& sourceNode = sourceNodes[moduleName];
|
||||||
|
|
||||||
if (!sourceNode.isDirty(frontendOptions.forAutocomplete))
|
if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
LUAU_ASSERT(sourceModules.count(moduleName));
|
LUAU_ASSERT(sourceModules.count(moduleName));
|
||||||
|
@ -478,7 +479,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||||
stats.timeCheck += duration;
|
stats.timeCheck += duration;
|
||||||
stats.filesStrict += 1;
|
stats.filesStrict += 1;
|
||||||
|
|
||||||
sourceNode.dirtyAutocomplete = false;
|
sourceNode.dirtyModuleForAutocomplete = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +542,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||||
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
||||||
|
|
||||||
moduleResolver.modules[moduleName] = std::move(module);
|
moduleResolver.modules[moduleName] = std::move(module);
|
||||||
sourceNode.dirty = false;
|
sourceNode.dirtyModule = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkResult;
|
return checkResult;
|
||||||
|
@ -618,7 +619,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
|
||||||
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
|
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
|
||||||
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
|
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
|
||||||
// to be built, *and* can't form a cycle with any nodes we did process.
|
// to be built, *and* can't form a cycle with any nodes we did process.
|
||||||
if (!it->second.isDirty(forAutocomplete))
|
if (!it->second.hasDirtyModule(forAutocomplete))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
|
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
|
||||||
|
@ -768,7 +769,7 @@ LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOp
|
||||||
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||||
{
|
{
|
||||||
auto it = sourceNodes.find(name);
|
auto it = sourceNodes.find(name);
|
||||||
return it == sourceNodes.end() || it->second.isDirty(forAutocomplete);
|
return it == sourceNodes.end() || it->second.hasDirtyModule(forAutocomplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -810,20 +811,31 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
||||||
if (markedDirty)
|
if (markedDirty)
|
||||||
markedDirty->push_back(next);
|
markedDirty->push_back(next);
|
||||||
|
|
||||||
if (FFlag::LuauSeparateTypechecks)
|
if (FFlag::LuauDirtySourceModule)
|
||||||
{
|
{
|
||||||
if (sourceNode.dirty && sourceNode.dirtyAutocomplete)
|
LUAU_ASSERT(FFlag::LuauSeparateTypechecks);
|
||||||
|
|
||||||
|
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sourceNode.dirty = true;
|
sourceNode.dirtySourceModule = true;
|
||||||
sourceNode.dirtyAutocomplete = true;
|
sourceNode.dirtyModule = true;
|
||||||
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauSeparateTypechecks)
|
||||||
|
{
|
||||||
|
if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sourceNode.dirtyModule = true;
|
||||||
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (sourceNode.dirty)
|
if (sourceNode.dirtyModule)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sourceNode.dirty = true;
|
sourceNode.dirtyModule = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 == reverseDeps.count(name))
|
if (0 == reverseDeps.count(name))
|
||||||
|
@ -851,13 +863,14 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||||
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete)
|
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED)
|
||||||
{
|
{
|
||||||
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
|
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
|
||||||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||||
|
|
||||||
auto it = sourceNodes.find(name);
|
auto it = sourceNodes.find(name);
|
||||||
if (it != sourceNodes.end() && !it->second.isDirty(forAutocomplete))
|
if (it != sourceNodes.end() &&
|
||||||
|
(FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED)))
|
||||||
{
|
{
|
||||||
auto moduleIt = sourceModules.find(name);
|
auto moduleIt = sourceModules.find(name);
|
||||||
if (moduleIt != sourceModules.end())
|
if (moduleIt != sourceModules.end())
|
||||||
|
@ -901,17 +914,20 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
||||||
sourceNode.requires.clear();
|
sourceNode.requires.clear();
|
||||||
sourceNode.requireLocations.clear();
|
sourceNode.requireLocations.clear();
|
||||||
|
|
||||||
|
if (FFlag::LuauDirtySourceModule)
|
||||||
|
sourceNode.dirtySourceModule = false;
|
||||||
|
|
||||||
if (FFlag::LuauSeparateTypechecks)
|
if (FFlag::LuauSeparateTypechecks)
|
||||||
{
|
{
|
||||||
if (it == sourceNodes.end())
|
if (it == sourceNodes.end())
|
||||||
{
|
{
|
||||||
sourceNode.dirty = true;
|
sourceNode.dirtyModule = true;
|
||||||
sourceNode.dirtyAutocomplete = true;
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sourceNode.dirty = true;
|
sourceNode.dirtyModule = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCloneDeclaredGlobals, false)
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
|
LUAU_FASTFLAG(LuauLosslessClone)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -182,20 +182,20 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauCloneDeclaredGlobals)
|
for (auto& [name, ty] : declaredGlobals)
|
||||||
{
|
{
|
||||||
for (auto& [name, ty] : declaredGlobals)
|
ty = clone(ty, interfaceTypes, cloneState);
|
||||||
{
|
if (FFlag::LuauLowerBoundsCalculation)
|
||||||
ty = clone(ty, interfaceTypes, cloneState);
|
normalize(ty, interfaceTypes, ice);
|
||||||
if (FFlag::LuauLowerBoundsCalculation)
|
|
||||||
normalize(ty, interfaceTypes, ice);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
freeze(internalTypes);
|
freeze(internalTypes);
|
||||||
freeze(interfaceTypes);
|
freeze(interfaceTypes);
|
||||||
|
|
||||||
return cloneState.encounteredFreeType;
|
if (FFlag::LuauLosslessClone)
|
||||||
|
return false; // TODO: make function return void.
|
||||||
|
else
|
||||||
|
return cloneState.encounteredFreeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
#include "Luau/DenseHash.h"
|
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
#include "Luau/VisitTypeVar.h"
|
#include "Luau/VisitTypeVar.h"
|
||||||
|
@ -254,7 +253,7 @@ bool isSubtype(TypeId subTy, TypeId superTy, InternalErrorReporter& ice)
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static bool areNormal_(const T& t, const DenseHashSet<void*>& seen, InternalErrorReporter& ice)
|
static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, InternalErrorReporter& ice)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
auto isNormal = [&](TypeId ty) {
|
auto isNormal = [&](TypeId ty) {
|
||||||
|
@ -262,18 +261,19 @@ static bool areNormal_(const T& t, const DenseHashSet<void*>& seen, InternalErro
|
||||||
if (count >= FInt::LuauNormalizeIterationLimit)
|
if (count >= FInt::LuauNormalizeIterationLimit)
|
||||||
ice.ice("Luau::areNormal hit iteration limit");
|
ice.ice("Luau::areNormal hit iteration limit");
|
||||||
|
|
||||||
return ty->normal || seen.find(asMutable(ty));
|
// The follow is here because a bound type may not be normal, but the bound type is normal.
|
||||||
|
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
return std::all_of(begin(t), end(t), isNormal);
|
return std::all_of(begin(t), end(t), isNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool areNormal(const std::vector<TypeId>& types, const DenseHashSet<void*>& seen, InternalErrorReporter& ice)
|
static bool areNormal(const std::vector<TypeId>& types, const std::unordered_set<void*>& seen, InternalErrorReporter& ice)
|
||||||
{
|
{
|
||||||
return areNormal_(types, seen, ice);
|
return areNormal_(types, seen, ice);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool areNormal(TypePackId tp, const DenseHashSet<void*>& seen, InternalErrorReporter& ice)
|
static bool areNormal(TypePackId tp, const std::unordered_set<void*>& seen, InternalErrorReporter& ice)
|
||||||
{
|
{
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
if (get<FreeTypePack>(tp))
|
if (get<FreeTypePack>(tp))
|
||||||
|
@ -288,7 +288,7 @@ static bool areNormal(TypePackId tp, const DenseHashSet<void*>& seen, InternalEr
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (auto vtp = get<VariadicTypePack>(*tail))
|
if (auto vtp = get<VariadicTypePack>(*tail))
|
||||||
return vtp->ty->normal || seen.find(asMutable(vtp->ty));
|
return vtp->ty->normal || follow(vtp->ty)->normal || seen.find(asMutable(vtp->ty)) != seen.end();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -335,9 +335,14 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const BoundTypeVar& btv)
|
bool operator()(TypeId ty, const BoundTypeVar& btv, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
// It should never be the case that this TypeVar is normal, but is bound to a non-normal type.
|
// A type could be considered normal when it is in the stack, but we will eventually find out it is not normal as normalization progresses.
|
||||||
|
// So we need to avoid eagerly saying that this bound type is normal if the thing it is bound to is in the stack.
|
||||||
|
if (seen.find(asMutable(btv.boundTo)) != seen.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases.
|
||||||
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
|
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
|
||||||
|
|
||||||
asMutable(ty)->normal = btv.boundTo->normal;
|
asMutable(ty)->normal = btv.boundTo->normal;
|
||||||
|
@ -365,7 +370,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, DenseHashSet<void*>& seen)
|
bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -391,8 +396,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv) = delete;
|
bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set<void*>& seen)
|
||||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv, DenseHashSet<void*>& seen)
|
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -407,7 +411,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const TableTypeVar& ttv, DenseHashSet<void*>& seen)
|
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -419,7 +423,7 @@ struct Normalize
|
||||||
auto checkNormal = [&](TypeId t) {
|
auto checkNormal = [&](TypeId t) {
|
||||||
// if t is on the stack, it is possible that this type is normal.
|
// if t is on the stack, it is possible that this type is normal.
|
||||||
// If t is not normal and it is not on the stack, this type is definitely not normal.
|
// If t is not normal and it is not on the stack, this type is definitely not normal.
|
||||||
if (!t->normal && !seen.find(asMutable(t)))
|
if (!t->normal && seen.find(asMutable(t)) == seen.end())
|
||||||
normal = false;
|
normal = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -449,7 +453,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const MetatableTypeVar& mtv, DenseHashSet<void*>& seen)
|
bool operator()(TypeId ty, const MetatableTypeVar& mtv, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -477,7 +481,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const UnionTypeVar& utvRef, DenseHashSet<void*>& seen)
|
bool operator()(TypeId ty, const UnionTypeVar& utvRef, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -507,7 +511,7 @@ struct Normalize
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, DenseHashSet<void*>& seen)
|
bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, std::unordered_set<void*>& seen)
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
|
||||||
|
@ -775,8 +779,8 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
|
||||||
(void)clone(ty, arena, state);
|
(void)clone(ty, arena, state);
|
||||||
|
|
||||||
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
||||||
DenseHashSet<void*> seen{nullptr};
|
std::unordered_set<void*> seen;
|
||||||
visitTypeVarOnce(ty, n, seen);
|
visitTypeVar(ty, n, seen);
|
||||||
|
|
||||||
return {ty, !n.limitExceeded};
|
return {ty, !n.limitExceeded};
|
||||||
}
|
}
|
||||||
|
@ -800,8 +804,8 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
|
||||||
(void)clone(tp, arena, state);
|
(void)clone(tp, arena, state);
|
||||||
|
|
||||||
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
||||||
DenseHashSet<void*> seen{nullptr};
|
std::unordered_set<void*> seen;
|
||||||
visitTypeVarOnce(tp, n, seen);
|
visitTypeVar(tp, n, seen);
|
||||||
|
|
||||||
return {tp, !n.limitExceeded};
|
return {tp, !n.limitExceeded};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
|
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -106,7 +107,7 @@ void Tarjan::visitChildren(TypePackId tp, int index)
|
||||||
|
|
||||||
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
LUAU_ASSERT(ty == log->follow(ty));
|
LUAU_ASSERT(ty == log->follow(ty));
|
||||||
else
|
else
|
||||||
ty = log->follow(ty);
|
ty = log->follow(ty);
|
||||||
|
@ -127,7 +128,7 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||||
|
|
||||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
LUAU_ASSERT(tp == log->follow(tp));
|
LUAU_ASSERT(tp == log->follow(tp));
|
||||||
else
|
else
|
||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
@ -148,7 +149,8 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||||
|
|
||||||
void Tarjan::visitChild(TypeId ty)
|
void Tarjan::visitChild(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = log->follow(ty);
|
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
|
ty = log->follow(ty);
|
||||||
|
|
||||||
edgesTy.push_back(ty);
|
edgesTy.push_back(ty);
|
||||||
edgesTp.push_back(nullptr);
|
edgesTp.push_back(nullptr);
|
||||||
|
@ -156,7 +158,8 @@ void Tarjan::visitChild(TypeId ty)
|
||||||
|
|
||||||
void Tarjan::visitChild(TypePackId tp)
|
void Tarjan::visitChild(TypePackId tp)
|
||||||
{
|
{
|
||||||
tp = log->follow(tp);
|
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
|
tp = log->follow(tp);
|
||||||
|
|
||||||
edgesTy.push_back(nullptr);
|
edgesTy.push_back(nullptr);
|
||||||
edgesTp.push_back(tp);
|
edgesTp.push_back(tp);
|
||||||
|
@ -471,7 +474,7 @@ TypePackId Substitution::clone(TypePackId tp)
|
||||||
|
|
||||||
void Substitution::foundDirty(TypeId ty)
|
void Substitution::foundDirty(TypeId ty)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
LUAU_ASSERT(ty == log->follow(ty));
|
LUAU_ASSERT(ty == log->follow(ty));
|
||||||
else
|
else
|
||||||
ty = log->follow(ty);
|
ty = log->follow(ty);
|
||||||
|
@ -484,7 +487,7 @@ void Substitution::foundDirty(TypeId ty)
|
||||||
|
|
||||||
void Substitution::foundDirty(TypePackId tp)
|
void Substitution::foundDirty(TypePackId tp)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||||
LUAU_ASSERT(tp == log->follow(tp));
|
LUAU_ASSERT(tp == log->follow(tp));
|
||||||
else
|
else
|
||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
|
|
@ -327,7 +327,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
|
||||||
}
|
}
|
||||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||||
{
|
{
|
||||||
formatAppend(result, "VariadicTypePack %d", index);
|
formatAppend(result, "VariadicTypePack %s%d", vtp->hidden ? "hidden " : "", index);
|
||||||
finishNodeLabel(tp);
|
finishNodeLabel(tp);
|
||||||
finishNode();
|
finishNode();
|
||||||
|
|
||||||
|
|
|
@ -1025,31 +1025,42 @@ struct Printer
|
||||||
}
|
}
|
||||||
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
|
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
|
||||||
{
|
{
|
||||||
CommaSeparatorInserter comma(writer);
|
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
|
||||||
|
|
||||||
writer.symbol("{");
|
if (a->props.size == 0 && indexType && indexType->name == "number")
|
||||||
|
|
||||||
for (std::size_t i = 0; i < a->props.size; ++i)
|
|
||||||
{
|
{
|
||||||
comma();
|
writer.symbol("{");
|
||||||
advance(a->props.data[i].location.begin);
|
|
||||||
writer.identifier(a->props.data[i].name.value);
|
|
||||||
if (a->props.data[i].type)
|
|
||||||
{
|
|
||||||
writer.symbol(":");
|
|
||||||
visualizeTypeAnnotation(*a->props.data[i].type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a->indexer)
|
|
||||||
{
|
|
||||||
comma();
|
|
||||||
writer.symbol("[");
|
|
||||||
visualizeTypeAnnotation(*a->indexer->indexType);
|
|
||||||
writer.symbol("]");
|
|
||||||
writer.symbol(":");
|
|
||||||
visualizeTypeAnnotation(*a->indexer->resultType);
|
visualizeTypeAnnotation(*a->indexer->resultType);
|
||||||
|
writer.symbol("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CommaSeparatorInserter comma(writer);
|
||||||
|
|
||||||
|
writer.symbol("{");
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < a->props.size; ++i)
|
||||||
|
{
|
||||||
|
comma();
|
||||||
|
advance(a->props.data[i].location.begin);
|
||||||
|
writer.identifier(a->props.data[i].name.value);
|
||||||
|
if (a->props.data[i].type)
|
||||||
|
{
|
||||||
|
writer.symbol(":");
|
||||||
|
visualizeTypeAnnotation(*a->props.data[i].type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a->indexer)
|
||||||
|
{
|
||||||
|
comma();
|
||||||
|
writer.symbol("[");
|
||||||
|
visualizeTypeAnnotation(*a->indexer->indexType);
|
||||||
|
writer.symbol("]");
|
||||||
|
writer.symbol(":");
|
||||||
|
visualizeTypeAnnotation(*a->indexer->resultType);
|
||||||
|
}
|
||||||
|
writer.symbol("}");
|
||||||
}
|
}
|
||||||
writer.symbol("}");
|
|
||||||
}
|
}
|
||||||
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
|
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
|
||||||
{
|
{
|
||||||
|
|
|
@ -479,6 +479,20 @@ public:
|
||||||
{
|
{
|
||||||
return visitLocal(al->local);
|
return visitLocal(al->local);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool visit(AstStatFor* stat) override
|
||||||
|
{
|
||||||
|
visitLocal(stat->var);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool visit(AstStatForIn* stat) override
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||||
|
visitLocal(stat->vars.data[i]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool visit(AstExprFunction* fn) override
|
virtual bool visit(AstExprFunction* fn) override
|
||||||
{
|
{
|
||||||
// TODO: add generics if the inferred type of the function is generic CLI-39908
|
// TODO: add generics if the inferred type of the function is generic CLI-39908
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
|
#include "Luau/Clone.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
@ -47,7 +48,6 @@ LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify4, false)
|
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify4, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false)
|
LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||||
LUAU_FASTFLAG(LuauTypeMismatchModuleName)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||||
|
@ -61,7 +61,9 @@ LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDecoupleOperatorInferenceFromUnifiedTypeInference, false)
|
LUAU_FASTFLAGVARIABLE(LuauDecoupleOperatorInferenceFromUnifiedTypeInference, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauArgCountMismatchSaysAtLeastWhenVariadic, false)
|
LUAU_FASTFLAGVARIABLE(LuauArgCountMismatchSaysAtLeastWhenVariadic, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableUseCounterInstead, false)
|
LUAU_FASTFLAGVARIABLE(LuauTableUseCounterInstead, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
||||||
|
LUAU_FASTFLAG(LuauLosslessClone)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -376,7 +378,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||||
prepareErrorsForDisplay(currentModule->errors);
|
prepareErrorsForDisplay(currentModule->errors);
|
||||||
|
|
||||||
bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler);
|
bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler);
|
||||||
if (encounteredFreeType)
|
if (!FFlag::LuauLosslessClone && encounteredFreeType)
|
||||||
{
|
{
|
||||||
reportError(TypeError{module.root->location,
|
reportError(TypeError{module.root->location,
|
||||||
GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}});
|
GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}});
|
||||||
|
@ -785,7 +787,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
|
||||||
|
|
||||||
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
||||||
|
|
||||||
if (useConstrainedIntersections())
|
if (FFlag::LuauReturnTypeInferenceInNonstrict ? FFlag::LuauLowerBoundsCalculation : useConstrainedIntersections())
|
||||||
{
|
{
|
||||||
unifyLowerBound(retPack, scope->returnType, return_.location);
|
unifyLowerBound(retPack, scope->returnType, return_.location);
|
||||||
return;
|
return;
|
||||||
|
@ -1241,7 +1243,12 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
||||||
// If in nonstrict mode and allowing redefinition of global function, restore the previous definition type
|
// If in nonstrict mode and allowing redefinition of global function, restore the previous definition type
|
||||||
// in case this function has a differing signature. The signature discrepancy will be caught in checkBlock.
|
// in case this function has a differing signature. The signature discrepancy will be caught in checkBlock.
|
||||||
if (previouslyDefined)
|
if (previouslyDefined)
|
||||||
|
{
|
||||||
|
if (FFlag::LuauReturnTypeInferenceInNonstrict && FFlag::LuauLowerBoundsCalculation)
|
||||||
|
quantify(funScope, ty, exprName->location);
|
||||||
|
|
||||||
globalBindings[name] = oldBinding;
|
globalBindings[name] = oldBinding;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
globalBindings[name] = {quantify(funScope, ty, exprName->location), exprName->location};
|
globalBindings[name] = {quantify(funScope, ty, exprName->location), exprName->location};
|
||||||
|
|
||||||
|
@ -1555,7 +1562,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
||||||
|
|
||||||
Name className(declaredClass.name.value);
|
Name className(declaredClass.name.value);
|
||||||
|
|
||||||
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}));
|
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
|
||||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||||
|
|
||||||
TypeId metaTy = addType(TableTypeVar{TableState::Sealed, scope->level});
|
TypeId metaTy = addType(TableTypeVar{TableState::Sealed, scope->level});
|
||||||
|
@ -3284,7 +3291,7 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
|
||||||
TypePackId retPack;
|
TypePackId retPack;
|
||||||
if (expr.returnAnnotation)
|
if (expr.returnAnnotation)
|
||||||
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
||||||
else if (isNonstrictMode())
|
else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode())
|
||||||
retPack = anyTypePack;
|
retPack = anyTypePack;
|
||||||
else if (expectedFunctionType)
|
else if (expectedFunctionType)
|
||||||
{
|
{
|
||||||
|
@ -5328,19 +5335,9 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
||||||
if (const auto& indexer = table->indexer)
|
if (const auto& indexer = table->indexer)
|
||||||
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
|
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
|
||||||
|
|
||||||
if (FFlag::LuauTypeMismatchModuleName)
|
TableTypeVar ttv{props, tableIndexer, scope->level, TableState::Sealed};
|
||||||
{
|
ttv.definitionModuleName = currentModuleName;
|
||||||
TableTypeVar ttv{props, tableIndexer, scope->level, TableState::Sealed};
|
return addType(std::move(ttv));
|
||||||
ttv.definitionModuleName = currentModuleName;
|
|
||||||
return addType(std::move(ttv));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return addType(TableTypeVar{
|
|
||||||
props, tableIndexer, scope->level,
|
|
||||||
TableState::Sealed // FIXME: probably want a way to annotate other kinds of tables maybe
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (const auto& func = annotation.as<AstTypeFunction>())
|
else if (const auto& func = annotation.as<AstTypeFunction>())
|
||||||
{
|
{
|
||||||
|
@ -5602,9 +5599,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
||||||
{
|
{
|
||||||
ttv->instantiatedTypeParams = typeParams;
|
ttv->instantiatedTypeParams = typeParams;
|
||||||
ttv->instantiatedTypePackParams = typePackParams;
|
ttv->instantiatedTypePackParams = typePackParams;
|
||||||
|
ttv->definitionModuleName = currentModuleName;
|
||||||
if (FFlag::LuauTypeMismatchModuleName)
|
|
||||||
ttv->definitionModuleName = currentModuleName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return instantiated;
|
return instantiated;
|
||||||
|
|
|
@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||||
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
|
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -304,6 +305,11 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
|
||||||
if (ftv->definition)
|
if (ftv->definition)
|
||||||
return ftv->definition->definitionModuleName;
|
return ftv->definition->definitionModuleName;
|
||||||
}
|
}
|
||||||
|
else if (auto ctv = get<ClassTypeVar>(type); ctv && FFlag::LuauClassDefinitionModuleInError)
|
||||||
|
{
|
||||||
|
if (!ctv->definitionModuleName.empty())
|
||||||
|
return ctv->definitionModuleName;
|
||||||
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExtendedIndexerError, false);
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||||
|
@ -28,7 +27,6 @@ LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false)
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false)
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnifierCacheErrors, false)
|
|
||||||
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||||
|
|
||||||
|
@ -474,32 +472,21 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
if (get<ErrorTypeVar>(subTy))
|
if (get<ErrorTypeVar>(subTy))
|
||||||
return tryUnifyWithAny(superTy, subTy);
|
return tryUnifyWithAny(superTy, subTy);
|
||||||
|
|
||||||
bool cacheEnabled;
|
|
||||||
auto& cache = sharedState.cachedUnify;
|
auto& cache = sharedState.cachedUnify;
|
||||||
|
|
||||||
// What if the types are immutable and we proved their relation before
|
// What if the types are immutable and we proved their relation before
|
||||||
if (FFlag::LuauUnifierCacheErrors)
|
bool cacheEnabled = !isFunctionCall && !isIntersection && variance == Invariant;
|
||||||
|
|
||||||
|
if (cacheEnabled)
|
||||||
{
|
{
|
||||||
cacheEnabled = !isFunctionCall && !isIntersection && variance == Invariant;
|
if (cache.contains({subTy, superTy}))
|
||||||
|
|
||||||
if (cacheEnabled)
|
|
||||||
{
|
|
||||||
if (cache.contains({subTy, superTy}))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (auto error = sharedState.cachedUnifyError.find({subTy, superTy}))
|
|
||||||
{
|
|
||||||
reportError(TypeError{location, *error});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cacheEnabled = !isFunctionCall && !isIntersection;
|
|
||||||
|
|
||||||
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy})))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (auto error = sharedState.cachedUnifyError.find({subTy, superTy}))
|
||||||
|
{
|
||||||
|
reportError(TypeError{location, *error});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have seen this pair of types before, we are currently recursing into cyclic types.
|
// If we have seen this pair of types before, we are currently recursing into cyclic types.
|
||||||
|
@ -543,12 +530,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
else if (log.getMutable<TableTypeVar>(superTy) && log.getMutable<TableTypeVar>(subTy))
|
else if (log.getMutable<TableTypeVar>(superTy) && log.getMutable<TableTypeVar>(subTy))
|
||||||
{
|
{
|
||||||
tryUnifyTables(subTy, superTy, isIntersection);
|
tryUnifyTables(subTy, superTy, isIntersection);
|
||||||
|
|
||||||
if (!FFlag::LuauUnifierCacheErrors)
|
|
||||||
{
|
|
||||||
if (cacheEnabled && errors.empty())
|
|
||||||
cacheResult_DEPRECATED(subTy, superTy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical.
|
// tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical.
|
||||||
|
@ -568,7 +549,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
else
|
else
|
||||||
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||||
|
|
||||||
if (FFlag::LuauUnifierCacheErrors && cacheEnabled)
|
if (cacheEnabled)
|
||||||
cacheResult(subTy, superTy, errorCount);
|
cacheResult(subTy, superTy, errorCount);
|
||||||
|
|
||||||
log.popSeen(superTy, subTy);
|
log.popSeen(superTy, subTy);
|
||||||
|
@ -705,21 +686,10 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
||||||
{
|
{
|
||||||
TypeId type = uv->options[i];
|
TypeId type = uv->options[i];
|
||||||
|
|
||||||
if (FFlag::LuauUnifierCacheErrors)
|
if (cache.contains({subTy, type}))
|
||||||
{
|
{
|
||||||
if (cache.contains({subTy, type}))
|
startIndex = i;
|
||||||
{
|
break;
|
||||||
startIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type})))
|
|
||||||
{
|
|
||||||
startIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -807,21 +777,10 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
||||||
{
|
{
|
||||||
TypeId type = uv->parts[i];
|
TypeId type = uv->parts[i];
|
||||||
|
|
||||||
if (FFlag::LuauUnifierCacheErrors)
|
if (cache.contains({type, superTy}))
|
||||||
{
|
{
|
||||||
if (cache.contains({type, superTy}))
|
startIndex = i;
|
||||||
{
|
break;
|
||||||
startIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy})))
|
|
||||||
{
|
|
||||||
startIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -896,19 +855,6 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unifier::cacheResult_DEPRECATED(TypeId subTy, TypeId superTy)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauUnifierCacheErrors);
|
|
||||||
|
|
||||||
if (!canCacheResult(subTy, superTy))
|
|
||||||
return;
|
|
||||||
|
|
||||||
sharedState.cachedUnify.insert({superTy, subTy});
|
|
||||||
|
|
||||||
if (variance == Invariant)
|
|
||||||
sharedState.cachedUnify.insert({subTy, superTy});
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WeirdIter
|
struct WeirdIter
|
||||||
{
|
{
|
||||||
TypePackId packId;
|
TypePackId packId;
|
||||||
|
@ -1650,24 +1596,16 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||||
|
|
||||||
Unifier innerState = makeChildUnifier();
|
Unifier innerState = makeChildUnifier();
|
||||||
|
|
||||||
if (FFlag::LuauExtendedIndexerError)
|
innerState.tryUnify_(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||||
{
|
|
||||||
innerState.tryUnify_(subTable->indexer->indexType, superTable->indexer->indexType);
|
|
||||||
|
|
||||||
bool reported = !innerState.errors.empty();
|
bool reported = !innerState.errors.empty();
|
||||||
|
|
||||||
checkChildUnifierTypeMismatch(innerState.errors, "[indexer key]", superTy, subTy);
|
checkChildUnifierTypeMismatch(innerState.errors, "[indexer key]", superTy, subTy);
|
||||||
|
|
||||||
innerState.tryUnify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
innerState.tryUnify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
||||||
|
|
||||||
if (!reported)
|
if (!reported)
|
||||||
checkChildUnifierTypeMismatch(innerState.errors, "[indexer value]", superTy, subTy);
|
checkChildUnifierTypeMismatch(innerState.errors, "[indexer value]", superTy, subTy);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
|
||||||
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innerState.errors.empty())
|
if (innerState.errors.empty())
|
||||||
log.concat(std::move(innerState.log));
|
log.concat(std::move(innerState.log));
|
||||||
|
@ -2225,7 +2163,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
|
||||||
|
|
||||||
void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
|
void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2 || !FFlag::LuauExtendedIndexerError);
|
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
|
||||||
|
|
||||||
tryUnify_(subIndexer.indexType, superIndexer.indexType);
|
tryUnify_(subIndexer.indexType, superIndexer.indexType);
|
||||||
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
|
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
|
||||||
|
|
|
@ -19,6 +19,7 @@ std::string format(const char* fmt, ...) LUAU_PRINTF_ATTR(1, 2);
|
||||||
std::string vformat(const char* fmt, va_list args);
|
std::string vformat(const char* fmt, va_list args);
|
||||||
|
|
||||||
void formatAppend(std::string& str, const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
void formatAppend(std::string& str, const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
void vformatAppend(std::string& ret, const char* fmt, va_list args);
|
||||||
|
|
||||||
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter);
|
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter);
|
||||||
std::string join(const std::vector<std::string>& segments, std::string_view delimiter);
|
std::string join(const std::vector<std::string>& segments, std::string_view delimiter);
|
||||||
|
|
|
@ -167,6 +167,7 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
|
||||||
Function top;
|
Function top;
|
||||||
top.vararg = true;
|
top.vararg = true;
|
||||||
|
|
||||||
|
functionStack.reserve(8);
|
||||||
functionStack.push_back(top);
|
functionStack.push_back(top);
|
||||||
|
|
||||||
nameSelf = names.addStatic("self");
|
nameSelf = names.addStatic("self");
|
||||||
|
@ -186,6 +187,13 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
|
||||||
|
|
||||||
// all hot comments parsed after the first non-comment lexeme are special in that they don't affect type checking / linting mode
|
// all hot comments parsed after the first non-comment lexeme are special in that they don't affect type checking / linting mode
|
||||||
hotcommentHeader = false;
|
hotcommentHeader = false;
|
||||||
|
|
||||||
|
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
|
||||||
|
localStack.reserve(16);
|
||||||
|
scratchStat.reserve(16);
|
||||||
|
scratchExpr.reserve(16);
|
||||||
|
scratchLocal.reserve(16);
|
||||||
|
scratchBinding.reserve(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::blockFollow(const Lexeme& l)
|
bool Parser::blockFollow(const Lexeme& l)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
static void vformatAppend(std::string& ret, const char* fmt, va_list args)
|
void vformatAppend(std::string& ret, const char* fmt, va_list args)
|
||||||
{
|
{
|
||||||
va_list argscopy;
|
va_list argscopy;
|
||||||
va_copy(argscopy, args);
|
va_copy(argscopy, args);
|
||||||
|
|
72
CLI/Repl.cpp
72
CLI/Repl.cpp
|
@ -579,7 +579,8 @@ static bool compileFile(const char* name, CompileFormat format)
|
||||||
|
|
||||||
if (format == CompileFormat::Text)
|
if (format == CompileFormat::Text)
|
||||||
{
|
{
|
||||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals);
|
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
|
||||||
|
Luau::BytecodeBuilder::Dump_Remarks);
|
||||||
bcb.setDumpSource(*source);
|
bcb.setDumpSource(*source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,13 +637,60 @@ static int assertionHandler(const char* expr, const char* file, int line, const
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void setLuauFlags(bool state)
|
||||||
|
{
|
||||||
|
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||||
|
{
|
||||||
|
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||||
|
flag->value = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setFlag(std::string_view name, bool state)
|
||||||
|
{
|
||||||
|
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||||
|
{
|
||||||
|
if (name == flag->name)
|
||||||
|
{
|
||||||
|
flag->value = state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Warning: --fflag unrecognized flag '%.*s'.\n\n", int(name.length()), name.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void applyFlagKeyValue(std::string_view element)
|
||||||
|
{
|
||||||
|
if (size_t separator = element.find('='); separator != std::string_view::npos)
|
||||||
|
{
|
||||||
|
std::string_view key = element.substr(0, separator);
|
||||||
|
std::string_view value = element.substr(separator + 1);
|
||||||
|
|
||||||
|
if (value == "true")
|
||||||
|
setFlag(key, true);
|
||||||
|
else if (value == "false")
|
||||||
|
setFlag(key, false);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Warning: --fflag unrecognized value '%.*s' for flag '%.*s'.\n\n", int(value.length()), value.data(), int(key.length()),
|
||||||
|
key.data());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (element == "true")
|
||||||
|
setLuauFlags(true);
|
||||||
|
else if (element == "false")
|
||||||
|
setLuauFlags(false);
|
||||||
|
else
|
||||||
|
setFlag(element, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int replMain(int argc, char** argv)
|
int replMain(int argc, char** argv)
|
||||||
{
|
{
|
||||||
Luau::assertHandler() = assertionHandler;
|
Luau::assertHandler() = assertionHandler;
|
||||||
|
|
||||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
setLuauFlags(true);
|
||||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
|
||||||
flag->value = true;
|
|
||||||
|
|
||||||
CliMode mode = CliMode::Unknown;
|
CliMode mode = CliMode::Unknown;
|
||||||
CompileFormat compileFormat{};
|
CompileFormat compileFormat{};
|
||||||
|
@ -727,6 +775,22 @@ int replMain(int argc, char** argv)
|
||||||
return 1;
|
return 1;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||||
|
{
|
||||||
|
std::string_view list = argv[i] + 9;
|
||||||
|
|
||||||
|
while (!list.empty())
|
||||||
|
{
|
||||||
|
size_t ending = list.find(",");
|
||||||
|
|
||||||
|
applyFlagKeyValue(list.substr(0, ending));
|
||||||
|
|
||||||
|
if (ending != std::string_view::npos)
|
||||||
|
list.remove_prefix(ending + 1);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (argv[i][0] == '-')
|
else if (argv[i][0] == '-')
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
||||||
|
|
|
@ -73,6 +73,12 @@ else()
|
||||||
list(APPEND LUAU_OPTIONS -Wall) # All warnings
|
list(APPEND LUAU_OPTIONS -Wall) # All warnings
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
|
# Some gcc versions treat var in `if (type var = val)` as unused
|
||||||
|
# Some gcc versions treat variables used in constexpr if blocks as unused
|
||||||
|
list(APPEND LUAU_OPTIONS -Wno-unused)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Enabled in CI; we should be warning free on our main compiler versions but don't guarantee being warning free everywhere
|
# Enabled in CI; we should be warning free on our main compiler versions but don't guarantee being warning free everywhere
|
||||||
if(LUAU_WERROR)
|
if(LUAU_WERROR)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "Luau/Bytecode.h"
|
#include "Luau/Bytecode.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -80,6 +81,8 @@ public:
|
||||||
void pushDebugUpval(StringRef name);
|
void pushDebugUpval(StringRef name);
|
||||||
uint32_t getDebugPC() const;
|
uint32_t getDebugPC() const;
|
||||||
|
|
||||||
|
void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||||
|
|
||||||
void finalize();
|
void finalize();
|
||||||
|
|
||||||
enum DumpFlags
|
enum DumpFlags
|
||||||
|
@ -88,6 +91,7 @@ public:
|
||||||
Dump_Lines = 1 << 1,
|
Dump_Lines = 1 << 1,
|
||||||
Dump_Source = 1 << 2,
|
Dump_Source = 1 << 2,
|
||||||
Dump_Locals = 1 << 3,
|
Dump_Locals = 1 << 3,
|
||||||
|
Dump_Remarks = 1 << 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
void setDumpFlags(uint32_t flags)
|
void setDumpFlags(uint32_t flags)
|
||||||
|
@ -228,6 +232,9 @@ private:
|
||||||
|
|
||||||
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||||
|
|
||||||
|
DenseHashMap<uint32_t, uint32_t> debugRemarks;
|
||||||
|
std::string debugRemarkBuffer;
|
||||||
|
|
||||||
BytecodeEncoder* encoder = nullptr;
|
BytecodeEncoder* encoder = nullptr;
|
||||||
std::string bytecode;
|
std::string bytecode;
|
||||||
|
|
||||||
|
|
|
@ -181,9 +181,17 @@ BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
||||||
: constantMap({Constant::Type_Nil, ~0ull})
|
: constantMap({Constant::Type_Nil, ~0ull})
|
||||||
, tableShapeMap(TableShape())
|
, tableShapeMap(TableShape())
|
||||||
, stringTable({nullptr, 0})
|
, stringTable({nullptr, 0})
|
||||||
|
, debugRemarks(~0u)
|
||||||
, encoder(encoder)
|
, encoder(encoder)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(stringTable.find(StringRef{"", 0}) == nullptr);
|
LUAU_ASSERT(stringTable.find(StringRef{"", 0}) == nullptr);
|
||||||
|
|
||||||
|
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
|
||||||
|
insns.reserve(32);
|
||||||
|
lines.reserve(32);
|
||||||
|
constants.reserve(16);
|
||||||
|
protos.reserve(16);
|
||||||
|
functions.reserve(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BytecodeBuilder::beginFunction(uint8_t numparams, bool isvararg)
|
uint32_t BytecodeBuilder::beginFunction(uint8_t numparams, bool isvararg)
|
||||||
|
@ -219,8 +227,8 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
||||||
validate();
|
validate();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants
|
// very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants plus overhead
|
||||||
func.data.reserve(insns.size() * 7);
|
func.data.reserve(32 + insns.size() * 7);
|
||||||
|
|
||||||
writeFunction(func.data, currentFunction);
|
writeFunction(func.data, currentFunction);
|
||||||
|
|
||||||
|
@ -242,6 +250,9 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
||||||
|
|
||||||
constantMap.clear();
|
constantMap.clear();
|
||||||
tableShapeMap.clear();
|
tableShapeMap.clear();
|
||||||
|
|
||||||
|
debugRemarks.clear();
|
||||||
|
debugRemarkBuffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BytecodeBuilder::setMainFunction(uint32_t fid)
|
void BytecodeBuilder::setMainFunction(uint32_t fid)
|
||||||
|
@ -505,9 +516,40 @@ uint32_t BytecodeBuilder::getDebugPC() const
|
||||||
return uint32_t(insns.size());
|
return uint32_t(insns.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BytecodeBuilder::addDebugRemark(const char* format, ...)
|
||||||
|
{
|
||||||
|
if ((dumpFlags & Dump_Remarks) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t offset = debugRemarkBuffer.size();
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vformatAppend(debugRemarkBuffer, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
// we null-terminate all remarks to avoid storing remark length
|
||||||
|
debugRemarkBuffer += '\0';
|
||||||
|
|
||||||
|
debugRemarks[uint32_t(insns.size())] = uint32_t(offset);
|
||||||
|
}
|
||||||
|
|
||||||
void BytecodeBuilder::finalize()
|
void BytecodeBuilder::finalize()
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(bytecode.empty());
|
LUAU_ASSERT(bytecode.empty());
|
||||||
|
|
||||||
|
// preallocate space for bytecode blob
|
||||||
|
size_t capacity = 16;
|
||||||
|
|
||||||
|
for (auto& p : stringTable)
|
||||||
|
capacity += p.first.length + 2;
|
||||||
|
|
||||||
|
for (const Function& func : functions)
|
||||||
|
capacity += func.data.size();
|
||||||
|
|
||||||
|
bytecode.reserve(capacity);
|
||||||
|
|
||||||
|
// assemble final bytecode blob
|
||||||
bytecode = char(LBC_VERSION);
|
bytecode = char(LBC_VERSION);
|
||||||
|
|
||||||
writeStringTable(bytecode);
|
writeStringTable(bytecode);
|
||||||
|
@ -663,6 +705,8 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
|
||||||
|
|
||||||
void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
||||||
{
|
{
|
||||||
|
LUAU_ASSERT(!lines.empty());
|
||||||
|
|
||||||
// this function encodes lines inside each span as a 8-bit delta to span baseline
|
// this function encodes lines inside each span as a 8-bit delta to span baseline
|
||||||
// span is always a power of two; depending on the line info input, it may need to be as low as 1
|
// span is always a power of two; depending on the line info input, it may need to be as low as 1
|
||||||
int span = 1 << 24;
|
int span = 1 << 24;
|
||||||
|
@ -693,7 +737,17 @@ void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// second pass: compute span base
|
// second pass: compute span base
|
||||||
std::vector<int> baseline((lines.size() - 1) / span + 1);
|
int baselineOne = 0;
|
||||||
|
std::vector<int> baselineScratch;
|
||||||
|
int* baseline = &baselineOne;
|
||||||
|
size_t baselineSize = (lines.size() - 1) / span + 1;
|
||||||
|
|
||||||
|
if (baselineSize > 1)
|
||||||
|
{
|
||||||
|
// avoid heap allocation for single-element baseline which is most functions (<256 lines)
|
||||||
|
baselineScratch.resize(baselineSize);
|
||||||
|
baseline = baselineScratch.data();
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t offset = 0; offset < lines.size(); offset += span)
|
for (size_t offset = 0; offset < lines.size(); offset += span)
|
||||||
{
|
{
|
||||||
|
@ -725,7 +779,7 @@ void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
||||||
|
|
||||||
int lastLine = 0;
|
int lastLine = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < baseline.size(); ++i)
|
for (size_t i = 0; i < baselineSize; ++i)
|
||||||
{
|
{
|
||||||
writeInt(ss, baseline[i] - lastLine);
|
writeInt(ss, baseline[i] - lastLine);
|
||||||
lastLine = baseline[i];
|
lastLine = baseline[i];
|
||||||
|
@ -1695,6 +1749,14 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dumpFlags & Dump_Remarks)
|
||||||
|
{
|
||||||
|
const uint32_t* remark = debugRemarks.find(uint32_t(code - insns.data()));
|
||||||
|
|
||||||
|
if (remark)
|
||||||
|
formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + *remark);
|
||||||
|
}
|
||||||
|
|
||||||
if (dumpFlags & Dump_Source)
|
if (dumpFlags & Dump_Source)
|
||||||
{
|
{
|
||||||
int line = lines[code - insns.data()];
|
int line = lines[code - insns.data()];
|
||||||
|
|
|
@ -8,12 +8,17 @@
|
||||||
|
|
||||||
#include "Builtins.h"
|
#include "Builtins.h"
|
||||||
#include "ConstantFolding.h"
|
#include "ConstantFolding.h"
|
||||||
|
#include "CostModel.h"
|
||||||
#include "TableShape.h"
|
#include "TableShape.h"
|
||||||
#include "ValueTracking.h"
|
#include "ValueTracking.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
||||||
|
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThresholdMaxBoost, 300)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -77,8 +82,12 @@ struct Compiler
|
||||||
, globals(AstName())
|
, globals(AstName())
|
||||||
, variables(nullptr)
|
, variables(nullptr)
|
||||||
, constants(nullptr)
|
, constants(nullptr)
|
||||||
|
, locstants(nullptr)
|
||||||
, tableShapes(nullptr)
|
, tableShapes(nullptr)
|
||||||
{
|
{
|
||||||
|
// preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays
|
||||||
|
localStack.reserve(16);
|
||||||
|
upvals.reserve(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getLocal(AstLocal* local)
|
uint8_t getLocal(AstLocal* local)
|
||||||
|
@ -209,7 +218,9 @@ struct Compiler
|
||||||
|
|
||||||
Function& f = functions[func];
|
Function& f = functions[func];
|
||||||
f.id = fid;
|
f.id = fid;
|
||||||
f.upvals = std::move(upvals);
|
f.upvals = upvals;
|
||||||
|
|
||||||
|
upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes
|
||||||
|
|
||||||
return fid;
|
return fid;
|
||||||
}
|
}
|
||||||
|
@ -2133,10 +2144,119 @@ struct Compiler
|
||||||
pushLocal(stat->vars.data[i], uint8_t(vars + i));
|
pushLocal(stat->vars.data[i], uint8_t(vars + i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getConstantShort(AstExpr* expr)
|
||||||
|
{
|
||||||
|
const Constant* c = constants.find(expr);
|
||||||
|
|
||||||
|
if (c && c->type == Constant::Type_Number)
|
||||||
|
{
|
||||||
|
double n = c->valueNumber;
|
||||||
|
|
||||||
|
if (n >= -32767 && n <= 32767 && double(int(n)) == n)
|
||||||
|
return int(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return INT_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canUnrollForBody(AstStatFor* stat)
|
||||||
|
{
|
||||||
|
struct CanUnrollVisitor : AstVisitor
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
bool visit(AstExpr* node) override
|
||||||
|
{
|
||||||
|
// functions may capture loop variable, and our upval handling doesn't handle elided variables (constant)
|
||||||
|
result = result && !node->is<AstExprFunction>();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStat* node) override
|
||||||
|
{
|
||||||
|
// while we can easily unroll nested loops, our cost model doesn't take unrolling into account so this can result in code explosion
|
||||||
|
// we also avoid continue/break since they introduce control flow across iterations
|
||||||
|
result = result && !node->is<AstStatFor>() && !node->is<AstStatContinue>() && !node->is<AstStatBreak>();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CanUnrollVisitor canUnroll;
|
||||||
|
stat->body->visit(&canUnroll);
|
||||||
|
|
||||||
|
return canUnroll.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost)
|
||||||
|
{
|
||||||
|
int from = getConstantShort(stat->from);
|
||||||
|
int to = getConstantShort(stat->to);
|
||||||
|
int step = stat->step ? getConstantShort(stat->step) : 1;
|
||||||
|
|
||||||
|
// check that limits are reasonably small and trip count can be computed
|
||||||
|
if (from == INT_MIN || to == INT_MIN || step == INT_MIN || step == 0 || (step < 0 && to > from) || (step > 0 && to < from))
|
||||||
|
{
|
||||||
|
bytecode.addDebugRemark("loop unroll failed: invalid iteration count");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canUnrollForBody(stat))
|
||||||
|
{
|
||||||
|
bytecode.addDebugRemark("loop unroll failed: unsupported loop body");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tripCount = (to - from) / step + 1;
|
||||||
|
|
||||||
|
if (tripCount > thresholdBase * thresholdMaxBoost / 100)
|
||||||
|
{
|
||||||
|
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstLocal* var = stat->var;
|
||||||
|
uint64_t costModel = modelCost(stat->body, &var, 1);
|
||||||
|
|
||||||
|
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to unrolling
|
||||||
|
bool varc = true;
|
||||||
|
int unrolledCost = computeCost(costModel, &varc, 1) * tripCount;
|
||||||
|
int baselineCost = (computeCost(costModel, nullptr, 0) + 1) * tripCount;
|
||||||
|
int unrollProfit = (unrolledCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / unrolledCost);
|
||||||
|
|
||||||
|
int threshold = thresholdBase * unrollProfit / 100;
|
||||||
|
|
||||||
|
if (unrolledCost > threshold)
|
||||||
|
{
|
||||||
|
bytecode.addDebugRemark(
|
||||||
|
"loop unroll failed: too expensive (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytecode.addDebugRemark("loop unroll succeeded (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100);
|
||||||
|
|
||||||
|
for (int i = from; step > 0 ? i <= to : i >= to; i += step)
|
||||||
|
{
|
||||||
|
// we need to re-fold constants in the loop body with the new value; this reuses computed constant values elsewhere in the tree
|
||||||
|
locstants[var].type = Constant::Type_Number;
|
||||||
|
locstants[var].valueNumber = i;
|
||||||
|
|
||||||
|
foldConstants(constants, variables, locstants, stat);
|
||||||
|
|
||||||
|
compileStat(stat->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void compileStatFor(AstStatFor* stat)
|
void compileStatFor(AstStatFor* stat)
|
||||||
{
|
{
|
||||||
RegScope rs(this);
|
RegScope rs(this);
|
||||||
|
|
||||||
|
// Optimization: small loops can be unrolled when it is profitable
|
||||||
|
if (options.optimizationLevel >= 2 && isConstant(stat->to) && isConstant(stat->from) && (!stat->step || isConstant(stat->step)))
|
||||||
|
if (tryCompileUnrolledFor(stat, FInt::LuauCompileLoopUnrollThreshold, FInt::LuauCompileLoopUnrollThresholdMaxBoost))
|
||||||
|
return;
|
||||||
|
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
size_t oldJumps = loopJumps.size();
|
size_t oldJumps = loopJumps.size();
|
||||||
|
|
||||||
|
@ -2826,6 +2946,8 @@ struct Compiler
|
||||||
: self(self)
|
: self(self)
|
||||||
, functions(functions)
|
, functions(functions)
|
||||||
{
|
{
|
||||||
|
// preallocate the result; this works around std::vector's inefficient growth policy for small arrays
|
||||||
|
functions.reserve(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(AstExprFunction* node) override
|
bool visit(AstExprFunction* node) override
|
||||||
|
@ -2979,6 +3101,7 @@ struct Compiler
|
||||||
DenseHashMap<AstName, Global> globals;
|
DenseHashMap<AstName, Global> globals;
|
||||||
DenseHashMap<AstLocal*, Variable> variables;
|
DenseHashMap<AstLocal*, Variable> variables;
|
||||||
DenseHashMap<AstExpr*, Constant> constants;
|
DenseHashMap<AstExpr*, Constant> constants;
|
||||||
|
DenseHashMap<AstLocal*, Constant> locstants;
|
||||||
DenseHashMap<AstExprTable*, TableShape> tableShapes;
|
DenseHashMap<AstExprTable*, TableShape> tableShapes;
|
||||||
|
|
||||||
unsigned int regTop = 0;
|
unsigned int regTop = 0;
|
||||||
|
@ -3008,7 +3131,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
|
||||||
if (options.optimizationLevel >= 1)
|
if (options.optimizationLevel >= 1)
|
||||||
{
|
{
|
||||||
// this pass analyzes constantness of expressions
|
// this pass analyzes constantness of expressions
|
||||||
foldConstants(compiler.constants, compiler.variables, root);
|
foldConstants(compiler.constants, compiler.variables, compiler.locstants, root);
|
||||||
|
|
||||||
// this pass analyzes table assignments to estimate table shapes for initially empty tables
|
// this pass analyzes table assignments to estimate table shapes for initially empty tables
|
||||||
predictTableShapes(compiler.tableShapes, root);
|
predictTableShapes(compiler.tableShapes, root);
|
||||||
|
|
|
@ -191,13 +191,13 @@ struct ConstantVisitor : AstVisitor
|
||||||
{
|
{
|
||||||
DenseHashMap<AstExpr*, Constant>& constants;
|
DenseHashMap<AstExpr*, Constant>& constants;
|
||||||
DenseHashMap<AstLocal*, Variable>& variables;
|
DenseHashMap<AstLocal*, Variable>& variables;
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals;
|
||||||
|
|
||||||
DenseHashMap<AstLocal*, Constant> locals;
|
ConstantVisitor(
|
||||||
|
DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, DenseHashMap<AstLocal*, Constant>& locals)
|
||||||
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables)
|
|
||||||
: constants(constants)
|
: constants(constants)
|
||||||
, variables(variables)
|
, variables(variables)
|
||||||
, locals(nullptr)
|
, locals(locals)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,9 +385,10 @@ struct ConstantVisitor : AstVisitor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root)
|
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals, AstNode* root)
|
||||||
{
|
{
|
||||||
ConstantVisitor visitor{constants, variables};
|
ConstantVisitor visitor{constants, variables, locals};
|
||||||
root->visit(&visitor);
|
root->visit(&visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ struct Constant
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root);
|
void foldConstants(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables,
|
||||||
|
DenseHashMap<AstLocal*, Constant>& locals, AstNode* root);
|
||||||
|
|
||||||
} // namespace Compile
|
} // namespace Compile
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauGcWorkTrackFix)
|
||||||
|
|
||||||
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
||||||
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
||||||
"$URL: www.lua.org $\n";
|
"$URL: www.lua.org $\n";
|
||||||
|
@ -1050,6 +1052,7 @@ int lua_gc(lua_State* L, int what, int data)
|
||||||
{
|
{
|
||||||
size_t prevthreshold = g->GCthreshold;
|
size_t prevthreshold = g->GCthreshold;
|
||||||
size_t amount = (cast_to(size_t, data) << 10);
|
size_t amount = (cast_to(size_t, data) << 10);
|
||||||
|
ptrdiff_t oldcredit = g->gcstate == GCSpause ? 0 : g->GCthreshold - g->totalbytes;
|
||||||
|
|
||||||
// temporarily adjust the threshold so that we can perform GC work
|
// temporarily adjust the threshold so that we can perform GC work
|
||||||
if (amount <= g->totalbytes)
|
if (amount <= g->totalbytes)
|
||||||
|
@ -1069,9 +1072,9 @@ int lua_gc(lua_State* L, int what, int data)
|
||||||
|
|
||||||
while (g->GCthreshold <= g->totalbytes)
|
while (g->GCthreshold <= g->totalbytes)
|
||||||
{
|
{
|
||||||
luaC_step(L, false);
|
size_t stepsize = luaC_step(L, false);
|
||||||
|
|
||||||
actualwork += g->gcstepsize;
|
actualwork += FFlag::LuauGcWorkTrackFix ? stepsize : g->gcstepsize;
|
||||||
|
|
||||||
if (g->gcstate == GCSpause)
|
if (g->gcstate == GCSpause)
|
||||||
{ /* end of cycle? */
|
{ /* end of cycle? */
|
||||||
|
@ -1107,11 +1110,20 @@ int lua_gc(lua_State* L, int what, int data)
|
||||||
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
|
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
|
||||||
if (g->gcstate != GCSpause)
|
if (g->gcstate != GCSpause)
|
||||||
{
|
{
|
||||||
// if a new cycle was triggered by explicit step, we ignore old threshold as that shows an incorrect 'credit' of GC work
|
if (FFlag::LuauGcWorkTrackFix)
|
||||||
if (waspaused)
|
{
|
||||||
g->GCthreshold = g->totalbytes + actualwork;
|
// if a new cycle was triggered by explicit step, old 'credit' of GC work is 0
|
||||||
|
ptrdiff_t newthreshold = g->totalbytes + actualwork + oldcredit;
|
||||||
|
g->GCthreshold = newthreshold < 0 ? 0 : newthreshold;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
g->GCthreshold = prevthreshold + actualwork;
|
{
|
||||||
|
// if a new cycle was triggered by explicit step, we ignore old threshold as that shows an incorrect 'credit' of GC work
|
||||||
|
if (waspaused)
|
||||||
|
g->GCthreshold = g->totalbytes + actualwork;
|
||||||
|
else
|
||||||
|
g->GCthreshold = prevthreshold + actualwork;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define GC_SWEEPMAX 40
|
LUAU_FASTFLAGVARIABLE(LuauGcWorkTrackFix, false)
|
||||||
#define GC_SWEEPCOST 10
|
LUAU_FASTFLAGVARIABLE(LuauGcSweepCostFix, false)
|
||||||
#define GC_SWEEPPAGESTEPCOST 4
|
|
||||||
|
#define GC_SWEEPPAGESTEPCOST (FFlag::LuauGcSweepCostFix ? 16 : 4)
|
||||||
|
|
||||||
#define GC_INTERRUPT(state) \
|
#define GC_INTERRUPT(state) \
|
||||||
{ \
|
{ \
|
||||||
|
@ -64,7 +65,7 @@ static void recordGcStateStep(global_State* g, int startgcstate, double seconds,
|
||||||
case GCSpropagate:
|
case GCSpropagate:
|
||||||
case GCSpropagateagain:
|
case GCSpropagateagain:
|
||||||
g->gcmetrics.currcycle.marktime += seconds;
|
g->gcmetrics.currcycle.marktime += seconds;
|
||||||
g->gcmetrics.currcycle.markrequests += g->gcstepsize;
|
g->gcmetrics.currcycle.markwork += work;
|
||||||
|
|
||||||
if (assist)
|
if (assist)
|
||||||
g->gcmetrics.currcycle.markassisttime += seconds;
|
g->gcmetrics.currcycle.markassisttime += seconds;
|
||||||
|
@ -74,7 +75,7 @@ static void recordGcStateStep(global_State* g, int startgcstate, double seconds,
|
||||||
break;
|
break;
|
||||||
case GCSsweep:
|
case GCSsweep:
|
||||||
g->gcmetrics.currcycle.sweeptime += seconds;
|
g->gcmetrics.currcycle.sweeptime += seconds;
|
||||||
g->gcmetrics.currcycle.sweeprequests += g->gcstepsize;
|
g->gcmetrics.currcycle.sweepwork += work;
|
||||||
|
|
||||||
if (assist)
|
if (assist)
|
||||||
g->gcmetrics.currcycle.sweepassisttime += seconds;
|
g->gcmetrics.currcycle.sweepassisttime += seconds;
|
||||||
|
@ -87,13 +88,11 @@ static void recordGcStateStep(global_State* g, int startgcstate, double seconds,
|
||||||
{
|
{
|
||||||
g->gcmetrics.stepassisttimeacc += seconds;
|
g->gcmetrics.stepassisttimeacc += seconds;
|
||||||
g->gcmetrics.currcycle.assistwork += work;
|
g->gcmetrics.currcycle.assistwork += work;
|
||||||
g->gcmetrics.currcycle.assistrequests += g->gcstepsize;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g->gcmetrics.stepexplicittimeacc += seconds;
|
g->gcmetrics.stepexplicittimeacc += seconds;
|
||||||
g->gcmetrics.currcycle.explicitwork += work;
|
g->gcmetrics.currcycle.explicitwork += work;
|
||||||
g->gcmetrics.currcycle.explicitrequests += g->gcstepsize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -878,11 +877,11 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
|
||||||
return heaptrigger < int64_t(g->totalbytes) ? g->totalbytes : (heaptrigger > int64_t(heapgoal) ? heapgoal : size_t(heaptrigger));
|
return heaptrigger < int64_t(g->totalbytes) ? g->totalbytes : (heaptrigger > int64_t(heapgoal) ? heapgoal : size_t(heaptrigger));
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_step(lua_State* L, bool assist)
|
size_t luaC_step(lua_State* L, bool assist)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
int lim = FFlag::LuauGcWorkTrackFix ? g->gcstepsize * g->gcstepmul / 100 : (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
||||||
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
||||||
size_t debt = g->totalbytes - g->GCthreshold;
|
size_t debt = g->totalbytes - g->GCthreshold;
|
||||||
|
|
||||||
|
@ -902,12 +901,13 @@ void luaC_step(lua_State* L, bool assist)
|
||||||
int lastgcstate = g->gcstate;
|
int lastgcstate = g->gcstate;
|
||||||
|
|
||||||
size_t work = gcstep(L, lim);
|
size_t work = gcstep(L, lim);
|
||||||
(void)work;
|
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
|
recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
size_t actualstepsize = work * 100 / g->gcstepmul;
|
||||||
|
|
||||||
// at the end of the last cycle
|
// at the end of the last cycle
|
||||||
if (g->gcstate == GCSpause)
|
if (g->gcstate == GCSpause)
|
||||||
{
|
{
|
||||||
|
@ -927,14 +927,16 @@ void luaC_step(lua_State* L, bool assist)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g->GCthreshold = g->totalbytes + g->gcstepsize;
|
g->GCthreshold = g->totalbytes + (FFlag::LuauGcWorkTrackFix ? actualstepsize : g->gcstepsize);
|
||||||
|
|
||||||
// compensate if GC is "behind schedule" (has some debt to pay)
|
// compensate if GC is "behind schedule" (has some debt to pay)
|
||||||
if (g->GCthreshold > debt)
|
if (FFlag::LuauGcWorkTrackFix ? g->GCthreshold >= debt : g->GCthreshold > debt)
|
||||||
g->GCthreshold -= debt;
|
g->GCthreshold -= debt;
|
||||||
}
|
}
|
||||||
|
|
||||||
GC_INTERRUPT(lastgcstate);
|
GC_INTERRUPT(lastgcstate);
|
||||||
|
|
||||||
|
return actualstepsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_fullgc(lua_State* L)
|
void luaC_fullgc(lua_State* L)
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
#define luaC_init(L, o, tt) luaC_initobj(L, cast_to(GCObject*, (o)), tt)
|
#define luaC_init(L, o, tt) luaC_initobj(L, cast_to(GCObject*, (o)), tt)
|
||||||
|
|
||||||
LUAI_FUNC void luaC_freeall(lua_State* L);
|
LUAI_FUNC void luaC_freeall(lua_State* L);
|
||||||
LUAI_FUNC void luaC_step(lua_State* L, bool assist);
|
LUAI_FUNC size_t luaC_step(lua_State* L, bool assist);
|
||||||
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
||||||
LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
||||||
LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv);
|
LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv);
|
||||||
|
|
|
@ -106,7 +106,7 @@ struct GCCycleMetrics
|
||||||
double markassisttime = 0.0;
|
double markassisttime = 0.0;
|
||||||
double markmaxexplicittime = 0.0;
|
double markmaxexplicittime = 0.0;
|
||||||
size_t markexplicitsteps = 0;
|
size_t markexplicitsteps = 0;
|
||||||
size_t markrequests = 0;
|
size_t markwork = 0;
|
||||||
|
|
||||||
double atomicstarttimestamp = 0.0;
|
double atomicstarttimestamp = 0.0;
|
||||||
size_t atomicstarttotalsizebytes = 0;
|
size_t atomicstarttotalsizebytes = 0;
|
||||||
|
@ -122,10 +122,7 @@ struct GCCycleMetrics
|
||||||
double sweepassisttime = 0.0;
|
double sweepassisttime = 0.0;
|
||||||
double sweepmaxexplicittime = 0.0;
|
double sweepmaxexplicittime = 0.0;
|
||||||
size_t sweepexplicitsteps = 0;
|
size_t sweepexplicitsteps = 0;
|
||||||
size_t sweeprequests = 0;
|
size_t sweepwork = 0;
|
||||||
|
|
||||||
size_t assistrequests = 0;
|
|
||||||
size_t explicitrequests = 0;
|
|
||||||
|
|
||||||
size_t assistwork = 0;
|
size_t assistwork = 0;
|
||||||
size_t explicitwork = 0;
|
size_t explicitwork = 0;
|
||||||
|
|
|
@ -814,13 +814,12 @@ def run(args, argsubcb):
|
||||||
|
|
||||||
analyzeResult('', mainResult, compareResults)
|
analyzeResult('', mainResult, compareResults)
|
||||||
else:
|
else:
|
||||||
for subdir, dirs, files in os.walk(arguments.folder):
|
all_files = [subdir + os.sep + filename for subdir, dirs, files in os.walk(arguments.folder) for filename in files]
|
||||||
for filename in files:
|
for filepath in sorted(all_files):
|
||||||
filepath = subdir + os.sep + filename
|
subdir, filename = os.path.split(filepath)
|
||||||
|
if filename.endswith(".lua"):
|
||||||
if filename.endswith(".lua"):
|
if arguments.run_test == None or re.match(arguments.run_test, filename[:-4]):
|
||||||
if arguments.run_test == None or re.match(arguments.run_test, filename[:-4]):
|
runTest(subdir, filename, filepath)
|
||||||
runTest(subdir, filename, filepath)
|
|
||||||
|
|
||||||
if arguments.sort and len(plotValueLists) > 1:
|
if arguments.sort and len(plotValueLists) > 1:
|
||||||
rearrange(rearrangeSortKeyForComparison)
|
rearrange(rearrangeSortKeyForComparison)
|
||||||
|
|
|
@ -103,7 +103,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||||
// Vector3 stub
|
// Vector3 stub
|
||||||
TypeId vector3MetaType = arena.addType(TableTypeVar{});
|
TypeId vector3MetaType = arena.addType(TableTypeVar{});
|
||||||
|
|
||||||
TypeId vector3InstanceType = arena.addType(ClassTypeVar{"Vector3", {}, nullopt, vector3MetaType, {}, {}});
|
TypeId vector3InstanceType = arena.addType(ClassTypeVar{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(vector3InstanceType)->props = {
|
getMutable<ClassTypeVar>(vector3InstanceType)->props = {
|
||||||
{"X", {env.numberType}},
|
{"X", {env.numberType}},
|
||||||
{"Y", {env.numberType}},
|
{"Y", {env.numberType}},
|
||||||
|
@ -117,7 +117,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||||
env.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
env.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
||||||
|
|
||||||
// Instance stub
|
// Instance stub
|
||||||
TypeId instanceType = arena.addType(ClassTypeVar{"Instance", {}, nullopt, nullopt, {}, {}});
|
TypeId instanceType = arena.addType(ClassTypeVar{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(instanceType)->props = {
|
getMutable<ClassTypeVar>(instanceType)->props = {
|
||||||
{"Name", {env.stringType}},
|
{"Name", {env.stringType}},
|
||||||
};
|
};
|
||||||
|
@ -125,7 +125,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||||
env.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
env.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||||
|
|
||||||
// Part stub
|
// Part stub
|
||||||
TypeId partType = arena.addType(ClassTypeVar{"Part", {}, instanceType, nullopt, {}, {}});
|
TypeId partType = arena.addType(ClassTypeVar{"Part", {}, instanceType, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(partType)->props = {
|
getMutable<ClassTypeVar>(partType)->props = {
|
||||||
{"Position", {vector3InstanceType}},
|
{"Position", {vector3InstanceType}},
|
||||||
};
|
};
|
||||||
|
@ -173,7 +173,7 @@ struct FuzzConfigResolver : Luau::ConfigResolver
|
||||||
{
|
{
|
||||||
FuzzConfigResolver()
|
FuzzConfigResolver()
|
||||||
{
|
{
|
||||||
defaultConfig.mode = Luau::Mode::Nonstrict; // typecheckTwice option will cover Strict mode
|
defaultConfig.mode = Luau::Mode::Nonstrict;
|
||||||
defaultConfig.enabledLint.warningMask = ~0ull;
|
defaultConfig.enabledLint.warningMask = ~0ull;
|
||||||
defaultConfig.parseOptions.captureComments = true;
|
defaultConfig.parseOptions.captureComments = true;
|
||||||
}
|
}
|
||||||
|
@ -275,6 +275,11 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||||
// lint (note that we need access to types so we need to do this with typeck in scope)
|
// lint (note that we need access to types so we need to do this with typeck in scope)
|
||||||
if (kFuzzLinter && result.errors.empty())
|
if (kFuzzLinter && result.errors.empty())
|
||||||
frontend.lint(name, std::nullopt);
|
frontend.lint(name, std::nullopt);
|
||||||
|
|
||||||
|
// Second pass in strict mode (forced by auto-complete)
|
||||||
|
Luau::FrontendOptions opts;
|
||||||
|
opts.forAutocomplete = true;
|
||||||
|
frontend.check(name, opts);
|
||||||
}
|
}
|
||||||
catch (std::exception&)
|
catch (std::exception&)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3034,4 +3034,40 @@ string:@1
|
||||||
CHECK(ac.entryMap["sub"].wrongIndexType == true);
|
CHECK(ac.entryMap["sub"].wrongIndexType == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ACFixture, "source_module_preservation_and_invalidation")
|
||||||
|
{
|
||||||
|
check(R"(
|
||||||
|
local a = { x = 2, y = 4 }
|
||||||
|
a.@1
|
||||||
|
)");
|
||||||
|
|
||||||
|
frontend.clear();
|
||||||
|
|
||||||
|
auto ac = autocomplete('1');
|
||||||
|
|
||||||
|
CHECK(ac.entryMap.count("x"));
|
||||||
|
CHECK(ac.entryMap.count("y"));
|
||||||
|
|
||||||
|
frontend.check("MainModule", {});
|
||||||
|
|
||||||
|
ac = autocomplete('1');
|
||||||
|
|
||||||
|
CHECK(ac.entryMap.count("x"));
|
||||||
|
CHECK(ac.entryMap.count("y"));
|
||||||
|
|
||||||
|
frontend.markDirty("MainModule", nullptr);
|
||||||
|
|
||||||
|
ac = autocomplete('1');
|
||||||
|
|
||||||
|
CHECK(ac.entryMap.count("x"));
|
||||||
|
CHECK(ac.entryMap.count("y"));
|
||||||
|
|
||||||
|
frontend.check("MainModule", {});
|
||||||
|
|
||||||
|
ac = autocomplete('1');
|
||||||
|
|
||||||
|
CHECK(ac.entryMap.count("x"));
|
||||||
|
CHECK(ac.entryMap.count("y"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -17,11 +17,13 @@ std::string rep(const std::string& s, size_t n);
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
static std::string compileFunction(const char* source, uint32_t id)
|
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1)
|
||||||
{
|
{
|
||||||
Luau::BytecodeBuilder bcb;
|
Luau::BytecodeBuilder bcb;
|
||||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||||
Luau::compileOrThrow(bcb, source);
|
Luau::CompileOptions options;
|
||||||
|
options.optimizationLevel = optimizationLevel;
|
||||||
|
Luau::compileOrThrow(bcb, source, options);
|
||||||
|
|
||||||
return bcb.dumpFunction(id);
|
return bcb.dumpFunction(id);
|
||||||
}
|
}
|
||||||
|
@ -2689,6 +2691,27 @@ local 8: reg 3, start pc 34 line 21, end pc 34 line 21
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("DebugRemarks")
|
||||||
|
{
|
||||||
|
Luau::BytecodeBuilder bcb;
|
||||||
|
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Remarks);
|
||||||
|
|
||||||
|
uint32_t fid = bcb.beginFunction(0);
|
||||||
|
|
||||||
|
bcb.addDebugRemark("test remark #%d", 42);
|
||||||
|
bcb.emitABC(LOP_RETURN, 0, 1, 0);
|
||||||
|
|
||||||
|
bcb.endFunction(0, 0);
|
||||||
|
|
||||||
|
bcb.setMainFunction(fid);
|
||||||
|
bcb.finalize();
|
||||||
|
|
||||||
|
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||||
|
REMARK test remark #42
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("AssignmentConflict")
|
TEST_CASE("AssignmentConflict")
|
||||||
{
|
{
|
||||||
// assignments are left to right
|
// assignments are left to right
|
||||||
|
@ -4076,4 +4099,336 @@ RETURN R1 6
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopUnrollBasic")
|
||||||
|
{
|
||||||
|
// forward loops
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,2 do
|
||||||
|
t[i] = i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 2
|
||||||
|
LOADN R1 1
|
||||||
|
SETTABLEN R1 R0 1
|
||||||
|
LOADN R1 2
|
||||||
|
SETTABLEN R1 R0 2
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// backward loops
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=2,1,-1 do
|
||||||
|
t[i] = i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 0
|
||||||
|
LOADN R1 2
|
||||||
|
SETTABLEN R1 R0 2
|
||||||
|
LOADN R1 1
|
||||||
|
SETTABLEN R1 R0 1
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// loops with step that doesn't divide to-from
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,4,2 do
|
||||||
|
t[i] = i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 0
|
||||||
|
LOADN R1 1
|
||||||
|
SETTABLEN R1 R0 1
|
||||||
|
LOADN R1 3
|
||||||
|
SETTABLEN R1 R0 3
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopUnrollUnsupported")
|
||||||
|
{
|
||||||
|
// can't unroll loops with non-constant bounds
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=x,y,z do
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
GETIMPORT R2 1
|
||||||
|
GETIMPORT R0 3
|
||||||
|
GETIMPORT R1 5
|
||||||
|
FORNPREP R0 +1
|
||||||
|
FORNLOOP R0 -1
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// can't unroll loops with bounds where we can't compute trip count
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=2,1 do
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
LOADN R2 2
|
||||||
|
LOADN R0 1
|
||||||
|
LOADN R1 1
|
||||||
|
FORNPREP R0 +1
|
||||||
|
FORNLOOP R0 -1
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// can't unroll loops with bounds that might be imprecise (non-integer)
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=1,2,0.1 do
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
LOADN R2 1
|
||||||
|
LOADN R0 2
|
||||||
|
LOADK R1 K0
|
||||||
|
FORNPREP R0 +1
|
||||||
|
FORNLOOP R0 -1
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// can't unroll loops if the bounds are too large, as it might overflow trip count math
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=4294967295,4294967296 do
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
LOADK R2 K0
|
||||||
|
LOADK R0 K1
|
||||||
|
LOADN R1 1
|
||||||
|
FORNPREP R0 +1
|
||||||
|
FORNLOOP R0 -1
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// can't unroll loops if the body has loop control flow or nested loops
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=1,1 do
|
||||||
|
for j=1,1 do
|
||||||
|
if i == 1 then
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
LOADN R2 1
|
||||||
|
LOADN R0 1
|
||||||
|
LOADN R1 1
|
||||||
|
FORNPREP R0 +11
|
||||||
|
LOADN R5 1
|
||||||
|
LOADN R3 1
|
||||||
|
LOADN R4 1
|
||||||
|
FORNPREP R3 +6
|
||||||
|
JUMPIFNOTEQK R2 K0 +5
|
||||||
|
JUMP +2
|
||||||
|
JUMP +1
|
||||||
|
JUMP +1
|
||||||
|
FORNLOOP R3 -6
|
||||||
|
FORNLOOP R0 -11
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
|
||||||
|
// can't unroll loops if the body has functions that refer to loop variables
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
for i=1,1 do
|
||||||
|
local x = function() return i end
|
||||||
|
end
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
LOADN R2 1
|
||||||
|
LOADN R0 1
|
||||||
|
LOADN R1 1
|
||||||
|
FORNPREP R0 +3
|
||||||
|
NEWCLOSURE R3 P0
|
||||||
|
CAPTURE VAL R2
|
||||||
|
FORNLOOP R0 -3
|
||||||
|
RETURN R0 0
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("LoopUnrollCost")
|
||||||
|
{
|
||||||
|
ScopedFastInt sfis[] = {
|
||||||
|
{"LuauCompileLoopUnrollThreshold", 25},
|
||||||
|
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
|
||||||
|
};
|
||||||
|
|
||||||
|
// loops with short body
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,10 do
|
||||||
|
t[i] = i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 10
|
||||||
|
LOADN R1 1
|
||||||
|
SETTABLEN R1 R0 1
|
||||||
|
LOADN R1 2
|
||||||
|
SETTABLEN R1 R0 2
|
||||||
|
LOADN R1 3
|
||||||
|
SETTABLEN R1 R0 3
|
||||||
|
LOADN R1 4
|
||||||
|
SETTABLEN R1 R0 4
|
||||||
|
LOADN R1 5
|
||||||
|
SETTABLEN R1 R0 5
|
||||||
|
LOADN R1 6
|
||||||
|
SETTABLEN R1 R0 6
|
||||||
|
LOADN R1 7
|
||||||
|
SETTABLEN R1 R0 7
|
||||||
|
LOADN R1 8
|
||||||
|
SETTABLEN R1 R0 8
|
||||||
|
LOADN R1 9
|
||||||
|
SETTABLEN R1 R0 9
|
||||||
|
LOADN R1 10
|
||||||
|
SETTABLEN R1 R0 10
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// loops with body that's too long
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,100 do
|
||||||
|
t[i] = i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 0
|
||||||
|
LOADN R3 1
|
||||||
|
LOADN R1 100
|
||||||
|
LOADN R2 1
|
||||||
|
FORNPREP R1 +2
|
||||||
|
SETTABLE R3 R0 R3
|
||||||
|
FORNLOOP R1 -2
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// loops with body that's long but has a high boost factor due to constant folding
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,30 do
|
||||||
|
t[i] = i * i * i
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 0
|
||||||
|
LOADN R1 1
|
||||||
|
SETTABLEN R1 R0 1
|
||||||
|
LOADN R1 8
|
||||||
|
SETTABLEN R1 R0 2
|
||||||
|
LOADN R1 27
|
||||||
|
SETTABLEN R1 R0 3
|
||||||
|
LOADN R1 64
|
||||||
|
SETTABLEN R1 R0 4
|
||||||
|
LOADN R1 125
|
||||||
|
SETTABLEN R1 R0 5
|
||||||
|
LOADN R1 216
|
||||||
|
SETTABLEN R1 R0 6
|
||||||
|
LOADN R1 343
|
||||||
|
SETTABLEN R1 R0 7
|
||||||
|
LOADN R1 512
|
||||||
|
SETTABLEN R1 R0 8
|
||||||
|
LOADN R1 729
|
||||||
|
SETTABLEN R1 R0 9
|
||||||
|
LOADN R1 1000
|
||||||
|
SETTABLEN R1 R0 10
|
||||||
|
LOADN R1 1331
|
||||||
|
SETTABLEN R1 R0 11
|
||||||
|
LOADN R1 1728
|
||||||
|
SETTABLEN R1 R0 12
|
||||||
|
LOADN R1 2197
|
||||||
|
SETTABLEN R1 R0 13
|
||||||
|
LOADN R1 2744
|
||||||
|
SETTABLEN R1 R0 14
|
||||||
|
LOADN R1 3375
|
||||||
|
SETTABLEN R1 R0 15
|
||||||
|
LOADN R1 4096
|
||||||
|
SETTABLEN R1 R0 16
|
||||||
|
LOADN R1 4913
|
||||||
|
SETTABLEN R1 R0 17
|
||||||
|
LOADN R1 5832
|
||||||
|
SETTABLEN R1 R0 18
|
||||||
|
LOADN R1 6859
|
||||||
|
SETTABLEN R1 R0 19
|
||||||
|
LOADN R1 8000
|
||||||
|
SETTABLEN R1 R0 20
|
||||||
|
LOADN R1 9261
|
||||||
|
SETTABLEN R1 R0 21
|
||||||
|
LOADN R1 10648
|
||||||
|
SETTABLEN R1 R0 22
|
||||||
|
LOADN R1 12167
|
||||||
|
SETTABLEN R1 R0 23
|
||||||
|
LOADN R1 13824
|
||||||
|
SETTABLEN R1 R0 24
|
||||||
|
LOADN R1 15625
|
||||||
|
SETTABLEN R1 R0 25
|
||||||
|
LOADN R1 17576
|
||||||
|
SETTABLEN R1 R0 26
|
||||||
|
LOADN R1 19683
|
||||||
|
SETTABLEN R1 R0 27
|
||||||
|
LOADN R1 21952
|
||||||
|
SETTABLEN R1 R0 28
|
||||||
|
LOADN R1 24389
|
||||||
|
SETTABLEN R1 R0 29
|
||||||
|
LOADN R1 27000
|
||||||
|
SETTABLEN R1 R0 30
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// loops with body that's long and doesn't have a high boost factor
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local t = {}
|
||||||
|
for i=1,10 do
|
||||||
|
t[i] = math.abs(math.sin(i))
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
)",
|
||||||
|
0, 2),
|
||||||
|
R"(
|
||||||
|
NEWTABLE R0 0 10
|
||||||
|
LOADN R3 1
|
||||||
|
LOADN R1 10
|
||||||
|
LOADN R2 1
|
||||||
|
FORNPREP R1 +11
|
||||||
|
FASTCALL1 24 R3 +3
|
||||||
|
MOVE R6 R3
|
||||||
|
GETIMPORT R5 2
|
||||||
|
CALL R5 1 -1
|
||||||
|
FASTCALL 2 +2
|
||||||
|
GETIMPORT R4 4
|
||||||
|
CALL R4 -1 1
|
||||||
|
SETTABLE R4 R0 R3
|
||||||
|
FORNLOOP R1 -11
|
||||||
|
RETURN R0 1
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -98,4 +98,129 @@ end
|
||||||
CHECK_EQ(2, Luau::Compile::computeCost(model, args2, 1));
|
CHECK_EQ(2, Luau::Compile::computeCost(model, args2, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ImportCall")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(a)
|
||||||
|
return Instance.new(a)
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
const bool args1[] = {false};
|
||||||
|
const bool args2[] = {true};
|
||||||
|
|
||||||
|
CHECK_EQ(6, Luau::Compile::computeCost(model, args1, 1));
|
||||||
|
CHECK_EQ(6, Luau::Compile::computeCost(model, args2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("FastCall")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(a)
|
||||||
|
return math.abs(a + 1)
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
const bool args1[] = {false};
|
||||||
|
const bool args2[] = {true};
|
||||||
|
|
||||||
|
// note: we currently don't treat fast calls differently from cost model perspective
|
||||||
|
CHECK_EQ(6, Luau::Compile::computeCost(model, args1, 1));
|
||||||
|
CHECK_EQ(5, Luau::Compile::computeCost(model, args2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ControlFlow")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(a)
|
||||||
|
while a < 0 do
|
||||||
|
a += 1
|
||||||
|
end
|
||||||
|
for i=1,2 do
|
||||||
|
a += 1
|
||||||
|
end
|
||||||
|
for i in pairs({}) do
|
||||||
|
a += 1
|
||||||
|
if a % 2 == 0 then continue end
|
||||||
|
end
|
||||||
|
repeat
|
||||||
|
a += 1
|
||||||
|
if a % 2 == 0 then break end
|
||||||
|
until a > 10
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
const bool args1[] = {false};
|
||||||
|
const bool args2[] = {true};
|
||||||
|
|
||||||
|
CHECK_EQ(38, Luau::Compile::computeCost(model, args1, 1));
|
||||||
|
CHECK_EQ(37, Luau::Compile::computeCost(model, args2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Conditional")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(a)
|
||||||
|
return if a < 0 then -a else a
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
const bool args1[] = {false};
|
||||||
|
const bool args2[] = {true};
|
||||||
|
|
||||||
|
CHECK_EQ(4, Luau::Compile::computeCost(model, args1, 1));
|
||||||
|
CHECK_EQ(2, Luau::Compile::computeCost(model, args2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VarArgs")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(...)
|
||||||
|
return select('#', ...) :: number
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(8, Luau::Compile::computeCost(model, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("TablesFunctions")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test()
|
||||||
|
return { 42, op = function() end }
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(22, Luau::Compile::computeCost(model, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CostOverflow")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test()
|
||||||
|
return {{{{{{{{{{{{{{{}}}}}}}}}}}}}}}
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK_EQ(127, Luau::Compile::computeCost(model, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("TableAssign")
|
||||||
|
{
|
||||||
|
uint64_t model = modelFunction(R"(
|
||||||
|
function test(a)
|
||||||
|
for i=1,#a do
|
||||||
|
a[i] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
const bool args1[] = {false};
|
||||||
|
const bool args2[] = {true};
|
||||||
|
|
||||||
|
CHECK_EQ(4, Luau::Compile::computeCost(model, args1, 1));
|
||||||
|
CHECK_EQ(3, Luau::Compile::computeCost(model, args2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -9,6 +9,46 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
struct JsonEncoderFixture
|
||||||
|
{
|
||||||
|
Allocator allocator;
|
||||||
|
AstNameTable names{allocator};
|
||||||
|
|
||||||
|
ParseResult parse(std::string_view src)
|
||||||
|
{
|
||||||
|
ParseOptions opts;
|
||||||
|
opts.allowDeclarationSyntax = true;
|
||||||
|
return Parser::parse(src.data(), src.size(), names, allocator, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStatBlock* expectParse(std::string_view src)
|
||||||
|
{
|
||||||
|
ParseResult res = parse(src);
|
||||||
|
REQUIRE(res.errors.size() == 0);
|
||||||
|
return res.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstStat* expectParseStatement(std::string_view src)
|
||||||
|
{
|
||||||
|
AstStatBlock* root = expectParse(src);
|
||||||
|
REQUIRE(1 == root->body.size);
|
||||||
|
return root->body.data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExpr* expectParseExpr(std::string_view src)
|
||||||
|
{
|
||||||
|
std::string s = "a = ";
|
||||||
|
s.append(src);
|
||||||
|
AstStatBlock* root = expectParse(s);
|
||||||
|
|
||||||
|
AstStatAssign* statAssign = root->body.data[0]->as<AstStatAssign>();
|
||||||
|
REQUIRE(statAssign != nullptr);
|
||||||
|
REQUIRE(statAssign->values.size == 1);
|
||||||
|
|
||||||
|
return statAssign->values.data[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("JsonEncoderTests");
|
TEST_SUITE_BEGIN("JsonEncoderTests");
|
||||||
|
|
||||||
TEST_CASE("encode_constants")
|
TEST_CASE("encode_constants")
|
||||||
|
@ -51,7 +91,7 @@ TEST_CASE("encode_AstStatBlock")
|
||||||
toJson(&block));
|
toJson(&block));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("encode_tables")
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables")
|
||||||
{
|
{
|
||||||
std::string src = R"(
|
std::string src = R"(
|
||||||
local x: {
|
local x: {
|
||||||
|
@ -61,16 +101,294 @@ TEST_CASE("encode_tables")
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
Allocator allocator;
|
AstStatBlock* root = expectParse(src);
|
||||||
AstNameTable names(allocator);
|
std::string json = toJson(root);
|
||||||
ParseResult parseResult = Parser::parse(src.c_str(), src.length(), names, allocator);
|
|
||||||
|
|
||||||
REQUIRE(parseResult.errors.size() == 0);
|
|
||||||
std::string json = toJson(parseResult.root);
|
|
||||||
|
|
||||||
CHECK(
|
CHECK(
|
||||||
json ==
|
json ==
|
||||||
R"({"type":"AstStatBlock","location":"0,0 - 6,4","body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"type":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","location":"2,12 - 2,15","type":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","parameters":[]}}],"indexer":false},"name":"x","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})");
|
R"({"type":"AstStatBlock","location":"0,0 - 6,4","body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"type":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","location":"2,12 - 2,15","type":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","parameters":[]}}],"indexer":false},"name":"x","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("encode_AstExprGroup")
|
||||||
|
{
|
||||||
|
AstExprConstantNumber number{Location{}, 5.0};
|
||||||
|
AstExprGroup group{Location{}, &number};
|
||||||
|
|
||||||
|
std::string json = toJson(&group);
|
||||||
|
|
||||||
|
const std::string expected = R"({"type":"AstExprGroup","location":"0,0 - 0,0","expr":{"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":5}})";
|
||||||
|
|
||||||
|
CHECK(json == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("encode_AstExprGlobal")
|
||||||
|
{
|
||||||
|
AstExprGlobal global{Location{}, AstName{"print"}};
|
||||||
|
|
||||||
|
std::string json = toJson(&global);
|
||||||
|
std::string expected = R"({"type":"AstExprGlobal","location":"0,0 - 0,0","global":"print"})";
|
||||||
|
|
||||||
|
CHECK(json == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("encode_AstExprLocal")
|
||||||
|
{
|
||||||
|
AstLocal local{AstName{"foo"}, Location{}, nullptr, 0, 0, nullptr};
|
||||||
|
AstExprLocal exprLocal{Location{}, &local, false};
|
||||||
|
|
||||||
|
CHECK(toJson(&exprLocal) == R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"type":null,"name":"foo","location":"0,0 - 0,0"}})");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("encode_AstExprVarargs")
|
||||||
|
{
|
||||||
|
AstExprVarargs varargs{Location{}};
|
||||||
|
|
||||||
|
CHECK(toJson(&varargs) == R"({"type":"AstExprVarargs","location":"0,0 - 0,0"})");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprCall")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("foo(1, 2, 3)");
|
||||||
|
std::string_view expected = R"({"type":"AstExprCall","location":"0,4 - 0,16","func":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"args":[{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},{"type":"AstExprConstantNumber","location":"0,11 - 0,12","value":2},{"type":"AstExprConstantNumber","location":"0,14 - 0,15","value":3}],"self":false,"argLocation":"0,8 - 0,16"})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexName")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("foo.bar");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprIndexName","location":"0,4 - 0,11","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":"bar","indexLocation":"0,8 - 0,11","op":"."})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexExpr")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("foo['bar']");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprIndexExpr","location":"0,4 - 0,14","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":{"type":"AstExprConstantString","location":"0,8 - 0,13","value":"bar"}})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("function (a) return a end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTable")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("{true, key=true, [key2]=true}");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprUnary")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("-b");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprBinary")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("b + c");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprBinary","location":"0,4 - 0,9","op":"Add","left":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"right":{"type":"AstExprGlobal","location":"0,8 - 0,9","global":"c"}})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTypeAssertion")
|
||||||
|
{
|
||||||
|
AstExpr* expr = expectParseExpr("b :: any");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprTypeAssertion","location":"0,4 - 0,12","expr":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"annotation":{"type":"AstTypeReference","location":"0,9 - 0,12","name":"any","parameters":[]}})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprError")
|
||||||
|
{
|
||||||
|
std::string_view src = "a = ";
|
||||||
|
ParseResult parseResult = Parser::parse(src.data(), src.size(), names, allocator);
|
||||||
|
|
||||||
|
REQUIRE(1 == parseResult.root->body.size);
|
||||||
|
|
||||||
|
AstStatAssign* statAssign = parseResult.root->body.data[0]->as<AstStatAssign>();
|
||||||
|
REQUIRE(statAssign != nullptr);
|
||||||
|
REQUIRE(1 == statAssign->values.size);
|
||||||
|
|
||||||
|
AstExpr* expr = statAssign->values.data[0];
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstExprError","location":"0,4 - 0,4","expressions":[],"messageIndex":0})";
|
||||||
|
|
||||||
|
CHECK(toJson(expr) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatIf")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("if true then else end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("while true do end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatRepeat")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("repeat until true");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("while true do break end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("while true do continue end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("for a=0,1 do end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("for a in b do end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatCompoundAssign")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("a += b");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatCompoundAssign","location":"0,0 - 0,6","op":"Add","var":{"type":"AstExprGlobal","location":"0,0 - 0,1","global":"a"},"value":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("local function a(b) return end");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("type A = B");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","parameters":[]},"exported":false})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("declare function foo(x: number): string");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
|
||||||
|
{
|
||||||
|
AstStatBlock* root = expectParse(R"(
|
||||||
|
declare class Foo
|
||||||
|
prop: number
|
||||||
|
function method(self, foo: number): string
|
||||||
|
end
|
||||||
|
|
||||||
|
declare class Bar extends Foo
|
||||||
|
prop2: string
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
REQUIRE(2 == root->body.size);
|
||||||
|
|
||||||
|
std::string_view expected1 = R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})";
|
||||||
|
CHECK(toJson(root->body.data[0]) == expected1);
|
||||||
|
|
||||||
|
std::string_view expected2 = R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})";
|
||||||
|
CHECK(toJson(root->body.data[1]) == expected2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
||||||
|
{
|
||||||
|
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
|
||||||
|
{
|
||||||
|
ParseResult parseResult = parse("type T = ");
|
||||||
|
REQUIRE(1 == parseResult.root->body.size);
|
||||||
|
|
||||||
|
AstStat* statement = parseResult.root->body.data[0];
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
|
||||||
|
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
|
||||||
|
{
|
||||||
|
AstStatBlock* root = expectParse(R"(
|
||||||
|
type A<T...> = () -> T...
|
||||||
|
local a: A<(number, string)>
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK(2 == root->body.size);
|
||||||
|
|
||||||
|
std::string_view expected = R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})";
|
||||||
|
|
||||||
|
CHECK(toJson(root->body.data[1]) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -1436,7 +1436,7 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
|
||||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
|
TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
|
||||||
{
|
{
|
||||||
unfreeze(typeChecker.globalTypes);
|
unfreeze(typeChecker.globalTypes);
|
||||||
TypeId instanceType = typeChecker.globalTypes.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, {}});
|
TypeId instanceType = typeChecker.globalTypes.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test"});
|
||||||
persist(instanceType);
|
persist(instanceType);
|
||||||
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||||
|
|
||||||
|
|
|
@ -173,13 +173,13 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||||
{
|
{
|
||||||
{"__add", {typeChecker.anyType}},
|
{"__add", {typeChecker.anyType}},
|
||||||
},
|
},
|
||||||
std::nullopt, std::nullopt, {}, {}}};
|
std::nullopt, std::nullopt, {}, {}, "Test"}};
|
||||||
TypeVar exampleClass{ClassTypeVar{"ExampleClass",
|
TypeVar exampleClass{ClassTypeVar{"ExampleClass",
|
||||||
{
|
{
|
||||||
{"PropOne", {typeChecker.numberType}},
|
{"PropOne", {typeChecker.numberType}},
|
||||||
{"PropTwo", {typeChecker.stringType}},
|
{"PropTwo", {typeChecker.stringType}},
|
||||||
},
|
},
|
||||||
std::nullopt, &exampleMetaClass, {}, {}}};
|
std::nullopt, &exampleMetaClass, {}, {}, "Test"}};
|
||||||
|
|
||||||
TypeArena dest;
|
TypeArena dest;
|
||||||
CloneState cloneState;
|
CloneState cloneState;
|
||||||
|
@ -196,9 +196,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||||
CHECK_EQ("ExampleClassMeta", metatable->name);
|
CHECK_EQ("ExampleClassMeta", metatable->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
|
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauErrorRecoveryType", true};
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauErrorRecoveryType", true},
|
||||||
|
{"LuauLosslessClone", true},
|
||||||
|
};
|
||||||
|
|
||||||
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
|
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
|
||||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||||
|
@ -207,17 +210,17 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
|
||||||
CloneState cloneState;
|
CloneState cloneState;
|
||||||
|
|
||||||
TypeId clonedTy = clone(&freeTy, dest, cloneState);
|
TypeId clonedTy = clone(&freeTy, dest, cloneState);
|
||||||
CHECK_EQ("any", toString(clonedTy));
|
CHECK(get<FreeTypeVar>(clonedTy));
|
||||||
CHECK(cloneState.encounteredFreeType);
|
|
||||||
|
|
||||||
cloneState = {};
|
cloneState = {};
|
||||||
TypePackId clonedTp = clone(&freeTp, dest, cloneState);
|
TypePackId clonedTp = clone(&freeTp, dest, cloneState);
|
||||||
CHECK_EQ("...any", toString(clonedTp));
|
CHECK(get<FreeTypePack>(clonedTp));
|
||||||
CHECK(cloneState.encounteredFreeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLosslessClone", true};
|
||||||
|
|
||||||
TypeVar tableTy{TableTypeVar{}};
|
TypeVar tableTy{TableTypeVar{}};
|
||||||
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
||||||
ttv->state = TableState::Free;
|
ttv->state = TableState::Free;
|
||||||
|
@ -227,8 +230,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||||
|
|
||||||
TypeId cloned = clone(&tableTy, dest, cloneState);
|
TypeId cloned = clone(&tableTy, dest, cloneState);
|
||||||
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
|
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
|
||||||
CHECK_EQ(clonedTtv->state, TableState::Sealed);
|
CHECK_EQ(clonedTtv->state, TableState::Free);
|
||||||
CHECK(cloneState.encounteredFreeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
|
TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
|
||||||
|
|
|
@ -13,6 +13,29 @@ using namespace Luau;
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("NonstrictModeTests");
|
TEST_SUITE_BEGIN("NonstrictModeTests");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "function_returns_number_or_string")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
|
local function f()
|
||||||
|
if math.random() > 0.5 then
|
||||||
|
return 5
|
||||||
|
else
|
||||||
|
return "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK("() -> number | string" == toString(requireType("f")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
|
TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -35,8 +58,13 @@ TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
|
||||||
REQUIRE_EQ(0, rets.size());
|
REQUIRE_EQ(0, rets.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_could_return")
|
TEST_CASE_FIXTURE(Fixture, "first_return_type_dictates_number_of_return_types")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
function getMinCardCountForWidth(width)
|
function getMinCardCountForWidth(width)
|
||||||
|
@ -51,22 +79,18 @@ TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_coul
|
||||||
TypeId t = requireType("getMinCardCountForWidth");
|
TypeId t = requireType("getMinCardCountForWidth");
|
||||||
REQUIRE(t);
|
REQUIRE(t);
|
||||||
|
|
||||||
REQUIRE_EQ("(any) -> (...any)", toString(t));
|
REQUIRE_EQ("(any) -> number", toString(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Maybe we want this?
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked")
|
TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
function foo(x): number return 'hello' end
|
function foo(x): number return 'hello' end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
REQUIRE_NE(*typeChecker.anyType, *requireType("foo"));
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "function_parameters_are_any")
|
TEST_CASE_FIXTURE(Fixture, "function_parameters_are_any")
|
||||||
{
|
{
|
||||||
|
@ -256,6 +280,12 @@ TEST_CASE_FIXTURE(Fixture, "delay_function_does_not_require_its_argument_to_retu
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauSealExports", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
|
|
||||||
|
@ -272,7 +302,7 @@ TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
REQUIRE_EQ("any", toString(getMainModule()->getModuleScope()->returnType));
|
REQUIRE_EQ("((any) -> string) | {| foo: any |}", toString(getMainModule()->getModuleScope()->returnType));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "returning_insufficient_return_values")
|
TEST_CASE_FIXTURE(Fixture, "returning_insufficient_return_values")
|
||||||
|
|
|
@ -21,7 +21,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||||
|
|
||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
|
|
||||||
TypeId parentType = arena.addType(ClassTypeVar{"Parent", {}, std::nullopt, std::nullopt, {}, nullptr});
|
TypeId parentType = arena.addType(ClassTypeVar{"Parent", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
|
||||||
|
|
||||||
ClassTypeVar* parentClass = getMutable<ClassTypeVar>(parentType);
|
ClassTypeVar* parentClass = getMutable<ClassTypeVar>(parentType);
|
||||||
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})};
|
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})};
|
||||||
|
@ -31,7 +31,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||||
addGlobalBinding(typeChecker, "Parent", {parentType});
|
addGlobalBinding(typeChecker, "Parent", {parentType});
|
||||||
typeChecker.globalScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
|
typeChecker.globalScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
|
||||||
|
|
||||||
TypeId childType = arena.addType(ClassTypeVar{"Child", {}, parentType, std::nullopt, {}, nullptr});
|
TypeId childType = arena.addType(ClassTypeVar{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
|
||||||
|
|
||||||
ClassTypeVar* childClass = getMutable<ClassTypeVar>(childType);
|
ClassTypeVar* childClass = getMutable<ClassTypeVar>(childType);
|
||||||
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
|
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
|
||||||
|
@ -39,7 +39,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||||
addGlobalBinding(typeChecker, "Child", {childType});
|
addGlobalBinding(typeChecker, "Child", {childType});
|
||||||
typeChecker.globalScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
|
typeChecker.globalScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
|
||||||
|
|
||||||
TypeId unrelatedType = arena.addType(ClassTypeVar{"Unrelated", {}, std::nullopt, std::nullopt, {}, nullptr});
|
TypeId unrelatedType = arena.addType(ClassTypeVar{"Unrelated", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
|
||||||
|
|
||||||
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
|
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
|
||||||
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||||
|
@ -400,7 +400,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_table_prop")
|
||||||
CHECK_EQ("{| x: {| y: number & string |} |}", toString(requireType("a")));
|
CHECK_EQ("{| x: {| y: number & string |} |}", toString(requireType("a")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||||
{
|
{
|
||||||
check(R"(
|
check(R"(
|
||||||
|
@ -428,6 +427,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||||
CHECK(!isSubtype(b, d));
|
CHECK(!isSubtype(b, d));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_indexers_are_invariant")
|
TEST_CASE_FIXTURE(NormalizeFixture, "table_indexers_are_invariant")
|
||||||
{
|
{
|
||||||
check(R"(
|
check(R"(
|
||||||
|
@ -619,6 +619,7 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
|
@ -639,7 +640,7 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type")
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
CHECK_EQ("(any, any) -> (...any)", toString(getMainModule()->getModuleScope()->returnType));
|
CHECK_EQ("(any, any) -> (any, any) -> any", toString(getMainModule()->getModuleScope()->returnType));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "return_type_is_not_a_constrained_intersection")
|
TEST_CASE_FIXTURE(Fixture, "return_type_is_not_a_constrained_intersection")
|
||||||
|
@ -950,6 +951,27 @@ TEST_CASE_FIXTURE(Fixture, "nested_table_normalization_with_non_table__no_ice")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "visiting_a_type_twice_is_not_considered_normal")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
function f(a, b)
|
||||||
|
local function g()
|
||||||
|
if math.random() > 0.5 then
|
||||||
|
return a()
|
||||||
|
else
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("<a>(() -> a, a) -> ()", toString(requireType("f")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "fuzz_failure_instersection_combine_must_follow")
|
TEST_CASE_FIXTURE(Fixture, "fuzz_failure_instersection_combine_must_follow")
|
||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
|
@ -964,4 +986,16 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_instersection_combine_must_follow")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounded_to")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type t252 = ((t0<t252...>)|(any))|(any)
|
||||||
|
type t0 = t252<t0<any,t24...>,t24...>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -21,13 +21,13 @@ struct ToDotClassFixture : Fixture
|
||||||
|
|
||||||
TypeId baseClassMetaType = arena.addType(TableTypeVar{});
|
TypeId baseClassMetaType = arena.addType(TableTypeVar{});
|
||||||
|
|
||||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}});
|
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||||
{"BaseField", {typeChecker.numberType}},
|
{"BaseField", {typeChecker.numberType}},
|
||||||
};
|
};
|
||||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||||
|
|
||||||
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}});
|
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
||||||
{"ChildField", {typeChecker.stringType}},
|
{"ChildField", {typeChecker.stringType}},
|
||||||
};
|
};
|
||||||
|
|
|
@ -661,4 +661,21 @@ type t4 = false
|
||||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "transpile_array_types")
|
||||||
|
{
|
||||||
|
std::string code = R"(
|
||||||
|
type t1 = {number}
|
||||||
|
type t2 = {[string]: number}
|
||||||
|
)";
|
||||||
|
|
||||||
|
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
|
||||||
|
{
|
||||||
|
std::string code = "for k:string,v:boolean in next,{}do end";
|
||||||
|
|
||||||
|
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -19,13 +19,13 @@ struct ClassFixture : Fixture
|
||||||
|
|
||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
|
|
||||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}});
|
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||||
{"BaseMethod", {makeFunction(arena, baseClassInstanceType, {numberType}, {})}},
|
{"BaseMethod", {makeFunction(arena, baseClassInstanceType, {numberType}, {})}},
|
||||||
{"BaseField", {numberType}},
|
{"BaseField", {numberType}},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}});
|
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(baseClassType)->props = {
|
getMutable<ClassTypeVar>(baseClassType)->props = {
|
||||||
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
|
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
|
||||||
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
||||||
|
@ -34,39 +34,39 @@ struct ClassFixture : Fixture
|
||||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||||
addGlobalBinding(typeChecker, "BaseClass", baseClassType, "@test");
|
addGlobalBinding(typeChecker, "BaseClass", baseClassType, "@test");
|
||||||
|
|
||||||
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}});
|
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||||
|
|
||||||
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
||||||
{"Method", {makeFunction(arena, childClassInstanceType, {}, {typeChecker.stringType})}},
|
{"Method", {makeFunction(arena, childClassInstanceType, {}, {typeChecker.stringType})}},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId childClassType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassType, nullopt, {}, {}});
|
TypeId childClassType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(childClassType)->props = {
|
getMutable<ClassTypeVar>(childClassType)->props = {
|
||||||
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
||||||
};
|
};
|
||||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||||
addGlobalBinding(typeChecker, "ChildClass", childClassType, "@test");
|
addGlobalBinding(typeChecker, "ChildClass", childClassType, "@test");
|
||||||
|
|
||||||
TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}});
|
TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"});
|
||||||
|
|
||||||
getMutable<ClassTypeVar>(grandChildInstanceType)->props = {
|
getMutable<ClassTypeVar>(grandChildInstanceType)->props = {
|
||||||
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {typeChecker.stringType})}},
|
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {typeChecker.stringType})}},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId grandChildType = arena.addType(ClassTypeVar{"GrandChild", {}, baseClassType, nullopt, {}, {}});
|
TypeId grandChildType = arena.addType(ClassTypeVar{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(grandChildType)->props = {
|
getMutable<ClassTypeVar>(grandChildType)->props = {
|
||||||
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
||||||
};
|
};
|
||||||
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||||
addGlobalBinding(typeChecker, "GrandChild", childClassType, "@test");
|
addGlobalBinding(typeChecker, "GrandChild", childClassType, "@test");
|
||||||
|
|
||||||
TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}});
|
TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||||
|
|
||||||
getMutable<ClassTypeVar>(anotherChildInstanceType)->props = {
|
getMutable<ClassTypeVar>(anotherChildInstanceType)->props = {
|
||||||
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {typeChecker.stringType})}},
|
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {typeChecker.stringType})}},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId anotherChildType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassType, nullopt, {}, {}});
|
TypeId anotherChildType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(anotherChildType)->props = {
|
getMutable<ClassTypeVar>(anotherChildType)->props = {
|
||||||
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
||||||
};
|
};
|
||||||
|
@ -75,13 +75,13 @@ struct ClassFixture : Fixture
|
||||||
|
|
||||||
TypeId vector2MetaType = arena.addType(TableTypeVar{});
|
TypeId vector2MetaType = arena.addType(TableTypeVar{});
|
||||||
|
|
||||||
TypeId vector2InstanceType = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, vector2MetaType, {}, {}});
|
TypeId vector2InstanceType = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(vector2InstanceType)->props = {
|
getMutable<ClassTypeVar>(vector2InstanceType)->props = {
|
||||||
{"X", {numberType}},
|
{"X", {numberType}},
|
||||||
{"Y", {numberType}},
|
{"Y", {numberType}},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}});
|
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassTypeVar>(vector2Type)->props = {
|
getMutable<ClassTypeVar>(vector2Type)->props = {
|
||||||
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
|
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
|
||||||
};
|
};
|
||||||
|
@ -468,4 +468,18 @@ caused by:
|
||||||
toString(result.errors[0]));
|
toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauClassDefinitionModuleInError{"LuauClassDefinitionModuleInError", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local i = ChildClass.New()
|
||||||
|
type ChildClass = { x: number }
|
||||||
|
local a: ChildClass = i
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK_EQ("Type 'ChildClass' from 'Test' could not be converted into 'ChildClass' from 'MainModule'", toString(result.errors[0]));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -295,8 +295,6 @@ TEST_CASE_FIXTURE(Fixture, "documentation_symbols_dont_attach_to_persistent_type
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "single_class_type_identity_in_global_types")
|
TEST_CASE_FIXTURE(Fixture, "single_class_type_identity_in_global_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauCloneDeclaredGlobals{"LuauCloneDeclaredGlobals", true};
|
|
||||||
|
|
||||||
loadDefinition(R"(
|
loadDefinition(R"(
|
||||||
declare class Cls
|
declare class Cls
|
||||||
end
|
end
|
||||||
|
|
|
@ -656,6 +656,11 @@ TEST_CASE_FIXTURE(Fixture, "toposort_doesnt_break_mutual_recursion")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it")
|
TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
|
|
||||||
|
@ -664,7 +669,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it")
|
||||||
end
|
end
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
return f():andThen()
|
return f()
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
|
@ -791,14 +796,18 @@ TEST_CASE_FIXTURE(Fixture, "calling_function_with_incorrect_argument_type_yields
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "calling_function_with_anytypepack_doesnt_leak_free_types")
|
TEST_CASE_FIXTURE(Fixture, "calling_function_with_anytypepack_doesnt_leak_free_types")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
|
|
||||||
function Test(a)
|
function Test(a): ...any
|
||||||
return 1, ""
|
return 1, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local tab = {}
|
local tab = {}
|
||||||
table.insert(tab, Test(1));
|
table.insert(tab, Test(1));
|
||||||
)");
|
)");
|
||||||
|
@ -1616,4 +1625,19 @@ TEST_CASE_FIXTURE(Fixture, "occurs_check_failure_in_function_return_type")
|
||||||
CHECK(nullptr != get<OccursCheckFailed>(result.errors[0]));
|
CHECK(nullptr != get<OccursCheckFailed>(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f() return end
|
||||||
|
local g = function() return f() end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -307,8 +307,6 @@ type Rename = typeof(x.x)
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "module_type_conflict")
|
TEST_CASE_FIXTURE(Fixture, "module_type_conflict")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
|
|
||||||
|
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
export type T = { x: number }
|
export type T = { x: number }
|
||||||
return {}
|
return {}
|
||||||
|
@ -343,8 +341,6 @@ caused by:
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "module_type_conflict_instantiated")
|
TEST_CASE_FIXTURE(Fixture, "module_type_conflict_instantiated")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
|
|
||||||
|
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
export type Wrap<T> = { x: T }
|
export type Wrap<T> = { x: T }
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -584,20 +584,6 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
|
||||||
{
|
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauLowerBoundsCalculation", false},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
local function f() return end
|
|
||||||
local g = function() return f() end
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {
|
||||||
|
|
|
@ -44,7 +44,7 @@ struct RefinementClassFixture : Fixture
|
||||||
TypeArena& arena = typeChecker.globalTypes;
|
TypeArena& arena = typeChecker.globalTypes;
|
||||||
|
|
||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
TypeId vec3 = arena.addType(ClassTypeVar{"Vector3", {}, std::nullopt, std::nullopt, {}, nullptr});
|
TypeId vec3 = arena.addType(ClassTypeVar{"Vector3", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
|
||||||
getMutable<ClassTypeVar>(vec3)->props = {
|
getMutable<ClassTypeVar>(vec3)->props = {
|
||||||
{"X", Property{typeChecker.numberType}},
|
{"X", Property{typeChecker.numberType}},
|
||||||
{"Y", Property{typeChecker.numberType}},
|
{"Y", Property{typeChecker.numberType}},
|
||||||
|
@ -52,7 +52,7 @@ struct RefinementClassFixture : Fixture
|
||||||
};
|
};
|
||||||
normalize(vec3, arena, *typeChecker.iceHandler);
|
normalize(vec3, arena, *typeChecker.iceHandler);
|
||||||
|
|
||||||
TypeId inst = arena.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, nullptr});
|
TypeId inst = arena.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
|
||||||
|
|
||||||
TypePackId isAParams = arena.addTypePack({inst, typeChecker.stringType});
|
TypePackId isAParams = arena.addTypePack({inst, typeChecker.stringType});
|
||||||
TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
|
TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
|
||||||
|
@ -66,9 +66,9 @@ struct RefinementClassFixture : Fixture
|
||||||
};
|
};
|
||||||
normalize(inst, arena, *typeChecker.iceHandler);
|
normalize(inst, arena, *typeChecker.iceHandler);
|
||||||
|
|
||||||
TypeId folder = typeChecker.globalTypes.addType(ClassTypeVar{"Folder", {}, inst, std::nullopt, {}, nullptr});
|
TypeId folder = typeChecker.globalTypes.addType(ClassTypeVar{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||||
normalize(folder, arena, *typeChecker.iceHandler);
|
normalize(folder, arena, *typeChecker.iceHandler);
|
||||||
TypeId part = typeChecker.globalTypes.addType(ClassTypeVar{"Part", {}, inst, std::nullopt, {}, nullptr});
|
TypeId part = typeChecker.globalTypes.addType(ClassTypeVar{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||||
getMutable<ClassTypeVar>(part)->props = {
|
getMutable<ClassTypeVar>(part)->props = {
|
||||||
{"Position", Property{vec3}},
|
{"Position", Property{vec3}},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2086,7 +2086,6 @@ caused by:
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
||||||
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = { [number]: string }
|
type A = { [number]: string }
|
||||||
|
@ -2105,7 +2104,6 @@ caused by:
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
||||||
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = { [number]: number }
|
type A = { [number]: number }
|
||||||
|
|
|
@ -86,16 +86,21 @@ TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nocheck
|
--!nocheck
|
||||||
function f(x)
|
function f(x)
|
||||||
return x
|
return 5
|
||||||
end
|
end
|
||||||
-- we get type information even if there's type errors
|
-- we get type information even if there's type errors
|
||||||
f(1, 2)
|
f(1, 2)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
CHECK_EQ("(any) -> (...any)", toString(requireType("f")));
|
CHECK_EQ("(any) -> number", toString(requireType("f")));
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
@ -363,6 +368,11 @@ TEST_CASE_FIXTURE(Fixture, "globals")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals2")
|
TEST_CASE_FIXTURE(Fixture, "globals2")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
foo = function() return 1 end
|
foo = function() return 1 end
|
||||||
|
@ -373,9 +383,9 @@ TEST_CASE_FIXTURE(Fixture, "globals2")
|
||||||
|
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
CHECK_EQ("() -> (...any)", toString(tm->wantedType));
|
CHECK_EQ("() -> number", toString(tm->wantedType));
|
||||||
CHECK_EQ("string", toString(tm->givenType));
|
CHECK_EQ("string", toString(tm->givenType));
|
||||||
CHECK_EQ("() -> (...any)", toString(requireType("foo")));
|
CHECK_EQ("() -> number", toString(requireType("foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode")
|
TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode")
|
||||||
|
|
|
@ -275,7 +275,7 @@ TEST_CASE("tagging_tables")
|
||||||
|
|
||||||
TEST_CASE("tagging_classes")
|
TEST_CASE("tagging_classes")
|
||||||
{
|
{
|
||||||
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr}};
|
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}};
|
||||||
CHECK(!Luau::hasTag(&base, "foo"));
|
CHECK(!Luau::hasTag(&base, "foo"));
|
||||||
Luau::attachTag(&base, "foo");
|
Luau::attachTag(&base, "foo");
|
||||||
CHECK(Luau::hasTag(&base, "foo"));
|
CHECK(Luau::hasTag(&base, "foo"));
|
||||||
|
@ -283,8 +283,8 @@ TEST_CASE("tagging_classes")
|
||||||
|
|
||||||
TEST_CASE("tagging_subclasses")
|
TEST_CASE("tagging_subclasses")
|
||||||
{
|
{
|
||||||
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr}};
|
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}};
|
||||||
TypeVar derived{ClassTypeVar{"Derived", {}, &base, std::nullopt, {}, nullptr}};
|
TypeVar derived{ClassTypeVar{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test"}};
|
||||||
|
|
||||||
CHECK(!Luau::hasTag(&base, "foo"));
|
CHECK(!Luau::hasTag(&base, "foo"));
|
||||||
CHECK(!Luau::hasTag(&derived, "foo"));
|
CHECK(!Luau::hasTag(&derived, "foo"));
|
||||||
|
|
Loading…
Reference in a new issue