mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/524
This commit is contained in:
parent
02ed5373ec
commit
f2677f6975
@ -18,7 +18,7 @@ struct CloneState
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
int recursionCount = 0;
|
||||
bool encounteredFreeType = false;
|
||||
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
|
||||
};
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
LUAU_FASTFLAG(LuauDirtySourceModule)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -57,19 +58,27 @@ std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleNa
|
||||
|
||||
struct SourceNode
|
||||
{
|
||||
bool isDirty(bool forAutocomplete) const
|
||||
bool hasDirtySourceModule() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
|
||||
|
||||
return dirtySourceModule;
|
||||
}
|
||||
|
||||
bool hasDirtyModule(bool forAutocomplete) const
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
return forAutocomplete ? dirtyAutocomplete : dirty;
|
||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||
else
|
||||
return dirty;
|
||||
return dirtyModule;
|
||||
}
|
||||
|
||||
ModuleName name;
|
||||
std::unordered_set<ModuleName> requires;
|
||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||
bool dirty = true;
|
||||
bool dirtyAutocomplete = true;
|
||||
bool dirtySourceModule = true;
|
||||
bool dirtyModule = true;
|
||||
bool dirtyModuleForAutocomplete = true;
|
||||
double autocompleteLimitsMult = 1.0;
|
||||
};
|
||||
|
||||
@ -163,7 +172,7 @@ struct Frontend
|
||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
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);
|
||||
|
||||
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);
|
||||
|
@ -373,15 +373,17 @@ struct ClassTypeVar
|
||||
std::optional<TypeId> metatable; // metaclass?
|
||||
Tags tags;
|
||||
std::shared_ptr<ClassUserData> userData;
|
||||
ModuleName definitionModuleName;
|
||||
|
||||
ClassTypeVar(
|
||||
Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags, std::shared_ptr<ClassUserData> userData)
|
||||
ClassTypeVar(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
|
||||
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName)
|
||||
: name(name)
|
||||
, props(props)
|
||||
, parent(parent)
|
||||
, metatable(metatable)
|
||||
, tags(tags)
|
||||
, userData(userData)
|
||||
, definitionModuleName(definitionModuleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -92,7 +92,6 @@ private:
|
||||
|
||||
bool canCacheResult(TypeId subTy, TypeId superTy);
|
||||
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
|
||||
void cacheResult_DEPRECATED(TypeId subTy, TypeId superTy);
|
||||
|
||||
public:
|
||||
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)
|
||||
{
|
||||
// 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>
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSingletonTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteClassSecurityLevel, false);
|
||||
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix)
|
||||
|
||||
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);
|
||||
fillProps(cls->props);
|
||||
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))
|
||||
fillProps(tbl->props);
|
||||
|
@ -10,6 +10,7 @@ LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -86,6 +87,12 @@ struct TypePackCloner
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (FFlag::LuauLosslessClone)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
@ -93,6 +100,7 @@ struct TypePackCloner
|
||||
TypePackId cloned = dest.addTypePack(*err);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Generic& t)
|
||||
{
|
||||
@ -142,12 +150,20 @@ void TypeCloner::defaultClone(const T& t)
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (FFlag::LuauLosslessClone)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
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)
|
||||
{
|
||||
@ -174,6 +190,7 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
||||
|
||||
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
||||
{
|
||||
if (!FFlag::LuauLosslessClone)
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
||||
@ -252,7 +269,7 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, cloneState);
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
|
||||
{
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
@ -276,7 +293,7 @@ void TypeCloner::operator()(const MetatableTypeVar& 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);
|
||||
|
||||
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
|
||||
if (!res->persistent)
|
||||
{
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
asMutable(res)->normal = typeId->normal;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleName, false);
|
||||
|
||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
{
|
||||
std::string s = "expects ";
|
||||
@ -59,8 +57,6 @@ struct ErrorConverter
|
||||
|
||||
std::string result;
|
||||
|
||||
if (FFlag::LuauTypeMismatchModuleName)
|
||||
{
|
||||
if (givenTypeName == wantedTypeName)
|
||||
{
|
||||
if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType))
|
||||
@ -75,11 +71,6 @@ struct ErrorConverter
|
||||
|
||||
if (result.empty())
|
||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||
}
|
||||
|
||||
if (tm.error)
|
||||
{
|
||||
|
@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 0)
|
||||
|
||||
namespace Luau
|
||||
@ -358,7 +359,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
CheckResult checkResult;
|
||||
|
||||
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.
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
@ -402,7 +403,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
LUAU_ASSERT(sourceNodes.count(moduleName));
|
||||
SourceNode& sourceNode = sourceNodes[moduleName];
|
||||
|
||||
if (!sourceNode.isDirty(frontendOptions.forAutocomplete))
|
||||
if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete))
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(sourceModules.count(moduleName));
|
||||
@ -478,7 +479,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
stats.timeCheck += duration;
|
||||
stats.filesStrict += 1;
|
||||
|
||||
sourceNode.dirtyAutocomplete = false;
|
||||
sourceNode.dirtyModuleForAutocomplete = false;
|
||||
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());
|
||||
|
||||
moduleResolver.modules[moduleName] = std::move(module);
|
||||
sourceNode.dirty = false;
|
||||
sourceNode.dirtyModule = false;
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
if (!it->second.isDirty(forAutocomplete))
|
||||
if (!it->second.hasDirtyModule(forAutocomplete))
|
||||
continue;
|
||||
|
||||
// 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
|
||||
{
|
||||
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)
|
||||
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;
|
||||
|
||||
sourceNode.dirty = true;
|
||||
sourceNode.dirtyAutocomplete = true;
|
||||
sourceNode.dirtySourceModule = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
else if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
continue;
|
||||
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceNode.dirty)
|
||||
if (sourceNode.dirtyModule)
|
||||
continue;
|
||||
|
||||
sourceNode.dirty = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
}
|
||||
|
||||
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.
|
||||
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_ARGUMENT("name", name.c_str());
|
||||
|
||||
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);
|
||||
if (moduleIt != sourceModules.end())
|
||||
@ -901,17 +914,20 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
||||
sourceNode.requires.clear();
|
||||
sourceNode.requireLocations.clear();
|
||||
|
||||
if (FFlag::LuauDirtySourceModule)
|
||||
sourceNode.dirtySourceModule = false;
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (it == sourceNodes.end())
|
||||
{
|
||||
sourceNode.dirty = true;
|
||||
sourceNode.dirtyAutocomplete = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceNode.dirty = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
}
|
||||
|
||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||
|
@ -14,8 +14,8 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneDeclaredGlobals, false)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauLosslessClone)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -182,19 +182,19 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauCloneDeclaredGlobals)
|
||||
{
|
||||
for (auto& [name, ty] : declaredGlobals)
|
||||
{
|
||||
ty = clone(ty, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
normalize(ty, interfaceTypes, ice);
|
||||
}
|
||||
}
|
||||
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
|
||||
if (FFlag::LuauLosslessClone)
|
||||
return false; // TODO: make function return void.
|
||||
else
|
||||
return cloneState.encounteredFreeType;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
@ -254,7 +253,7 @@ bool isSubtype(TypeId subTy, TypeId superTy, InternalErrorReporter& ice)
|
||||
}
|
||||
|
||||
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;
|
||||
auto isNormal = [&](TypeId ty) {
|
||||
@ -262,18 +261,19 @@ static bool areNormal_(const T& t, const DenseHashSet<void*>& seen, InternalErro
|
||||
if (count >= FInt::LuauNormalizeIterationLimit)
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
if (get<FreeTypePack>(tp))
|
||||
@ -288,7 +288,7 @@ static bool areNormal(TypePackId tp, const DenseHashSet<void*>& seen, InternalEr
|
||||
return true;
|
||||
|
||||
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;
|
||||
}
|
||||
@ -335,9 +335,14 @@ struct Normalize
|
||||
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);
|
||||
|
||||
asMutable(ty)->normal = btv.boundTo->normal;
|
||||
@ -365,7 +370,7 @@ struct Normalize
|
||||
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);
|
||||
|
||||
@ -391,8 +396,7 @@ struct Normalize
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv) = delete;
|
||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv, DenseHashSet<void*>& seen)
|
||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
@ -407,7 +411,7 @@ struct Normalize
|
||||
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);
|
||||
|
||||
@ -419,7 +423,7 @@ struct Normalize
|
||||
auto checkNormal = [&](TypeId t) {
|
||||
// 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->normal && !seen.find(asMutable(t)))
|
||||
if (!t->normal && seen.find(asMutable(t)) == seen.end())
|
||||
normal = false;
|
||||
};
|
||||
|
||||
@ -449,7 +453,7 @@ struct Normalize
|
||||
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);
|
||||
|
||||
@ -477,7 +481,7 @@ struct Normalize
|
||||
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);
|
||||
|
||||
@ -507,7 +511,7 @@ struct Normalize
|
||||
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);
|
||||
|
||||
@ -775,8 +779,8 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
|
||||
(void)clone(ty, arena, state);
|
||||
|
||||
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, n, seen);
|
||||
std::unordered_set<void*> seen;
|
||||
visitTypeVar(ty, n, seen);
|
||||
|
||||
return {ty, !n.limitExceeded};
|
||||
}
|
||||
@ -800,8 +804,8 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
|
||||
(void)clone(tp, arena, state);
|
||||
|
||||
Normalize n{arena, ice, std::move(state.seenTypes), std::move(state.seenTypePacks)};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(tp, n, seen);
|
||||
std::unordered_set<void*> seen;
|
||||
visitTypeVar(tp, n, seen);
|
||||
|
||||
return {tp, !n.limitExceeded};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -106,7 +107,7 @@ void Tarjan::visitChildren(TypePackId tp, int index)
|
||||
|
||||
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
@ -127,7 +128,7 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||
|
||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
@ -148,6 +149,7 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||
|
||||
void Tarjan::visitChild(TypeId ty)
|
||||
{
|
||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
ty = log->follow(ty);
|
||||
|
||||
edgesTy.push_back(ty);
|
||||
@ -156,6 +158,7 @@ void Tarjan::visitChild(TypeId ty)
|
||||
|
||||
void Tarjan::visitChild(TypePackId tp)
|
||||
{
|
||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
tp = log->follow(tp);
|
||||
|
||||
edgesTy.push_back(nullptr);
|
||||
@ -471,7 +474,7 @@ TypePackId Substitution::clone(TypePackId tp)
|
||||
|
||||
void Substitution::foundDirty(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
@ -484,7 +487,7 @@ void Substitution::foundDirty(TypeId ty)
|
||||
|
||||
void Substitution::foundDirty(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
|
@ -327,7 +327,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
formatAppend(result, "VariadicTypePack %d", index);
|
||||
formatAppend(result, "VariadicTypePack %s%d", vtp->hidden ? "hidden " : "", index);
|
||||
finishNodeLabel(tp);
|
||||
finishNode();
|
||||
|
||||
|
@ -1024,6 +1024,16 @@ struct Printer
|
||||
visualizeTypeList(a->returnTypes, true);
|
||||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
|
||||
{
|
||||
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
|
||||
|
||||
if (a->props.size == 0 && indexType && indexType->name == "number")
|
||||
{
|
||||
writer.symbol("{");
|
||||
visualizeTypeAnnotation(*a->indexer->resultType);
|
||||
writer.symbol("}");
|
||||
}
|
||||
else
|
||||
{
|
||||
CommaSeparatorInserter comma(writer);
|
||||
|
||||
@ -1051,6 +1061,7 @@ struct Printer
|
||||
}
|
||||
writer.symbol("}");
|
||||
}
|
||||
}
|
||||
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
|
||||
{
|
||||
writer.keyword("typeof");
|
||||
|
@ -479,6 +479,20 @@ public:
|
||||
{
|
||||
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
|
||||
{
|
||||
// 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
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Normalize.h"
|
||||
@ -47,7 +48,6 @@ LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStatFunctionSimplify4, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchModuleName)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
@ -61,7 +61,9 @@ LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDecoupleOperatorInferenceFromUnifiedTypeInference, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauArgCountMismatchSaysAtLeastWhenVariadic, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUseCounterInstead, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
||||
LUAU_FASTFLAG(LuauLosslessClone)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -376,7 +378,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||
prepareErrorsForDisplay(currentModule->errors);
|
||||
|
||||
bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler);
|
||||
if (encounteredFreeType)
|
||||
if (!FFlag::LuauLosslessClone && encounteredFreeType)
|
||||
{
|
||||
reportError(TypeError{module.root->location,
|
||||
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;
|
||||
|
||||
if (useConstrainedIntersections())
|
||||
if (FFlag::LuauReturnTypeInferenceInNonstrict ? FFlag::LuauLowerBoundsCalculation : useConstrainedIntersections())
|
||||
{
|
||||
unifyLowerBound(retPack, scope->returnType, return_.location);
|
||||
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
|
||||
// in case this function has a differing signature. The signature discrepancy will be caught in checkBlock.
|
||||
if (previouslyDefined)
|
||||
{
|
||||
if (FFlag::LuauReturnTypeInferenceInNonstrict && FFlag::LuauLowerBoundsCalculation)
|
||||
quantify(funScope, ty, exprName->location);
|
||||
|
||||
globalBindings[name] = oldBinding;
|
||||
}
|
||||
else
|
||||
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);
|
||||
|
||||
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}));
|
||||
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
|
||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||
|
||||
TypeId metaTy = addType(TableTypeVar{TableState::Sealed, scope->level});
|
||||
@ -3284,7 +3291,7 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
|
||||
TypePackId retPack;
|
||||
if (expr.returnAnnotation)
|
||||
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
||||
else if (isNonstrictMode())
|
||||
else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode())
|
||||
retPack = anyTypePack;
|
||||
else if (expectedFunctionType)
|
||||
{
|
||||
@ -5328,20 +5335,10 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
|
||||
if (const auto& indexer = table->indexer)
|
||||
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
|
||||
|
||||
if (FFlag::LuauTypeMismatchModuleName)
|
||||
{
|
||||
TableTypeVar ttv{props, tableIndexer, scope->level, TableState::Sealed};
|
||||
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>())
|
||||
{
|
||||
ScopePtr funcScope = childScope(scope, func->location);
|
||||
@ -5602,8 +5599,6 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
||||
{
|
||||
ttv->instantiatedTypeParams = typeParams;
|
||||
ttv->instantiatedTypePackParams = typePackParams;
|
||||
|
||||
if (FFlag::LuauTypeMismatchModuleName)
|
||||
ttv->definitionModuleName = currentModuleName;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -304,6 +305,11 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
|
||||
if (ftv->definition)
|
||||
return ftv->definition->definitionModuleName;
|
||||
}
|
||||
else if (auto ctv = get<ClassTypeVar>(type); ctv && FFlag::LuauClassDefinitionModuleInError)
|
||||
{
|
||||
if (!ctv->definitionModuleName.empty())
|
||||
return ctv->definitionModuleName;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedIndexerError, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
@ -28,7 +27,6 @@ LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogCheckForInvalidation, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogDontRetryForIndexers, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifierCacheErrors, false)
|
||||
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
|
||||
@ -474,13 +472,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
if (get<ErrorTypeVar>(subTy))
|
||||
return tryUnifyWithAny(superTy, subTy);
|
||||
|
||||
bool cacheEnabled;
|
||||
auto& cache = sharedState.cachedUnify;
|
||||
|
||||
// What if the types are immutable and we proved their relation before
|
||||
if (FFlag::LuauUnifierCacheErrors)
|
||||
{
|
||||
cacheEnabled = !isFunctionCall && !isIntersection && variance == Invariant;
|
||||
bool cacheEnabled = !isFunctionCall && !isIntersection && variance == Invariant;
|
||||
|
||||
if (cacheEnabled)
|
||||
{
|
||||
@ -493,14 +488,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cacheEnabled = !isFunctionCall && !isIntersection;
|
||||
|
||||
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy})))
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have seen this pair of types before, we are currently recursing into cyclic types.
|
||||
// Here, we assume that the types unify. If they do not, we will find out as we roll back
|
||||
@ -543,12 +530,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
else if (log.getMutable<TableTypeVar>(superTy) && log.getMutable<TableTypeVar>(subTy))
|
||||
{
|
||||
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.
|
||||
@ -568,7 +549,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
else
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
|
||||
if (FFlag::LuauUnifierCacheErrors && cacheEnabled)
|
||||
if (cacheEnabled)
|
||||
cacheResult(subTy, superTy, errorCount);
|
||||
|
||||
log.popSeen(superTy, subTy);
|
||||
@ -705,23 +686,12 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
||||
{
|
||||
TypeId type = uv->options[i];
|
||||
|
||||
if (FFlag::LuauUnifierCacheErrors)
|
||||
{
|
||||
if (cache.contains({subTy, type}))
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type})))
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < uv->options.size(); ++i)
|
||||
@ -807,23 +777,12 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
||||
{
|
||||
TypeId type = uv->parts[i];
|
||||
|
||||
if (FFlag::LuauUnifierCacheErrors)
|
||||
{
|
||||
if (cache.contains({type, superTy}))
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy})))
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < uv->parts.size(); ++i)
|
||||
@ -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
|
||||
{
|
||||
TypePackId packId;
|
||||
@ -1650,8 +1596,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
||||
if (FFlag::LuauExtendedIndexerError)
|
||||
{
|
||||
innerState.tryUnify_(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||
|
||||
bool reported = !innerState.errors.empty();
|
||||
@ -1662,12 +1606,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
|
||||
if (!reported)
|
||||
checkChildUnifierTypeMismatch(innerState.errors, "[indexer value]", superTy, subTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
||||
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
||||
}
|
||||
|
||||
if (innerState.errors.empty())
|
||||
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)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2 || !FFlag::LuauExtendedIndexerError);
|
||||
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
|
||||
|
||||
tryUnify_(subIndexer.indexType, superIndexer.indexType);
|
||||
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);
|
||||
|
||||
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>& segments, std::string_view delimiter);
|
||||
|
@ -167,6 +167,7 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
|
||||
Function top;
|
||||
top.vararg = true;
|
||||
|
||||
functionStack.reserve(8);
|
||||
functionStack.push_back(top);
|
||||
|
||||
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
|
||||
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)
|
||||
|
@ -11,7 +11,7 @@
|
||||
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_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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -636,13 +637,60 @@ static int assertionHandler(const char* expr, const char* file, int line, const
|
||||
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)
|
||||
{
|
||||
Luau::assertHandler() = assertionHandler;
|
||||
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||
flag->value = true;
|
||||
setLuauFlags(true);
|
||||
|
||||
CliMode mode = CliMode::Unknown;
|
||||
CompileFormat compileFormat{};
|
||||
@ -727,6 +775,22 @@ int replMain(int argc, char** argv)
|
||||
return 1;
|
||||
#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] == '-')
|
||||
{
|
||||
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
||||
|
@ -73,6 +73,12 @@ else()
|
||||
list(APPEND LUAU_OPTIONS -Wall) # All warnings
|
||||
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
|
||||
if(LUAU_WERROR)
|
||||
if(MSVC)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -80,6 +81,8 @@ public:
|
||||
void pushDebugUpval(StringRef name);
|
||||
uint32_t getDebugPC() const;
|
||||
|
||||
void addDebugRemark(const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
|
||||
void finalize();
|
||||
|
||||
enum DumpFlags
|
||||
@ -88,6 +91,7 @@ public:
|
||||
Dump_Lines = 1 << 1,
|
||||
Dump_Source = 1 << 2,
|
||||
Dump_Locals = 1 << 3,
|
||||
Dump_Remarks = 1 << 4,
|
||||
};
|
||||
|
||||
void setDumpFlags(uint32_t flags)
|
||||
@ -228,6 +232,9 @@ private:
|
||||
|
||||
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||
|
||||
DenseHashMap<uint32_t, uint32_t> debugRemarks;
|
||||
std::string debugRemarkBuffer;
|
||||
|
||||
BytecodeEncoder* encoder = nullptr;
|
||||
std::string bytecode;
|
||||
|
||||
|
@ -181,9 +181,17 @@ BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
||||
: constantMap({Constant::Type_Nil, ~0ull})
|
||||
, tableShapeMap(TableShape())
|
||||
, stringTable({nullptr, 0})
|
||||
, debugRemarks(~0u)
|
||||
, encoder(encoder)
|
||||
{
|
||||
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)
|
||||
@ -219,8 +227,8 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
||||
validate();
|
||||
#endif
|
||||
|
||||
// very approximate: 4 bytes per instruction for code, 1 byte for debug line, and 1-2 bytes for aux data like constants
|
||||
func.data.reserve(insns.size() * 7);
|
||||
// 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(32 + insns.size() * 7);
|
||||
|
||||
writeFunction(func.data, currentFunction);
|
||||
|
||||
@ -242,6 +250,9 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
||||
|
||||
constantMap.clear();
|
||||
tableShapeMap.clear();
|
||||
|
||||
debugRemarks.clear();
|
||||
debugRemarkBuffer.clear();
|
||||
}
|
||||
|
||||
void BytecodeBuilder::setMainFunction(uint32_t fid)
|
||||
@ -505,9 +516,40 @@ uint32_t BytecodeBuilder::getDebugPC() const
|
||||
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()
|
||||
{
|
||||
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);
|
||||
|
||||
writeStringTable(bytecode);
|
||||
@ -663,6 +705,8 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) 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
|
||||
// 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;
|
||||
@ -693,7 +737,17 @@ void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
@ -725,7 +779,7 @@ void BytecodeBuilder::writeLineInfo(std::string& ss) const
|
||||
|
||||
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);
|
||||
lastLine = baseline[i];
|
||||
@ -1695,6 +1749,14 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
||||
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)
|
||||
{
|
||||
int line = lines[code - insns.data()];
|
||||
|
@ -8,12 +8,17 @@
|
||||
|
||||
#include "Builtins.h"
|
||||
#include "ConstantFolding.h"
|
||||
#include "CostModel.h"
|
||||
#include "TableShape.h"
|
||||
#include "ValueTracking.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThresholdMaxBoost, 300)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -77,8 +82,12 @@ struct Compiler
|
||||
, globals(AstName())
|
||||
, variables(nullptr)
|
||||
, constants(nullptr)
|
||||
, locstants(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)
|
||||
@ -209,7 +218,9 @@ struct Compiler
|
||||
|
||||
Function& f = functions[func];
|
||||
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;
|
||||
}
|
||||
@ -2133,10 +2144,119 @@ struct Compiler
|
||||
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)
|
||||
{
|
||||
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 oldJumps = loopJumps.size();
|
||||
|
||||
@ -2826,6 +2946,8 @@ struct Compiler
|
||||
: self(self)
|
||||
, 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
|
||||
@ -2979,6 +3101,7 @@ struct Compiler
|
||||
DenseHashMap<AstName, Global> globals;
|
||||
DenseHashMap<AstLocal*, Variable> variables;
|
||||
DenseHashMap<AstExpr*, Constant> constants;
|
||||
DenseHashMap<AstLocal*, Constant> locstants;
|
||||
DenseHashMap<AstExprTable*, TableShape> tableShapes;
|
||||
|
||||
unsigned int regTop = 0;
|
||||
@ -3008,7 +3131,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
|
||||
if (options.optimizationLevel >= 1)
|
||||
{
|
||||
// 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
|
||||
predictTableShapes(compiler.tableShapes, root);
|
||||
|
@ -191,13 +191,13 @@ struct ConstantVisitor : AstVisitor
|
||||
{
|
||||
DenseHashMap<AstExpr*, Constant>& constants;
|
||||
DenseHashMap<AstLocal*, Variable>& variables;
|
||||
DenseHashMap<AstLocal*, Constant>& locals;
|
||||
|
||||
DenseHashMap<AstLocal*, Constant> locals;
|
||||
|
||||
ConstantVisitor(DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables)
|
||||
ConstantVisitor(
|
||||
DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, DenseHashMap<AstLocal*, Constant>& locals)
|
||||
: constants(constants)
|
||||
, 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);
|
||||
}
|
||||
|
||||
|
@ -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 Luau
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauGcWorkTrackFix)
|
||||
|
||||
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"
|
||||
"$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 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
|
||||
if (amount <= g->totalbytes)
|
||||
@ -1069,9 +1072,9 @@ int lua_gc(lua_State* L, int what, int data)
|
||||
|
||||
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)
|
||||
{ /* end of cycle? */
|
||||
@ -1106,6 +1109,14 @@ 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 (g->gcstate != GCSpause)
|
||||
{
|
||||
if (FFlag::LuauGcWorkTrackFix)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// if a new cycle was triggered by explicit step, we ignore old threshold as that shows an incorrect 'credit' of GC work
|
||||
if (waspaused)
|
||||
@ -1113,6 +1124,7 @@ int lua_gc(lua_State* L, int what, int data)
|
||||
else
|
||||
g->GCthreshold = prevthreshold + actualwork;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LUA_GCSETGOAL:
|
||||
|
@ -13,9 +13,10 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define GC_SWEEPMAX 40
|
||||
#define GC_SWEEPCOST 10
|
||||
#define GC_SWEEPPAGESTEPCOST 4
|
||||
LUAU_FASTFLAGVARIABLE(LuauGcWorkTrackFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGcSweepCostFix, false)
|
||||
|
||||
#define GC_SWEEPPAGESTEPCOST (FFlag::LuauGcSweepCostFix ? 16 : 4)
|
||||
|
||||
#define GC_INTERRUPT(state) \
|
||||
{ \
|
||||
@ -64,7 +65,7 @@ static void recordGcStateStep(global_State* g, int startgcstate, double seconds,
|
||||
case GCSpropagate:
|
||||
case GCSpropagateagain:
|
||||
g->gcmetrics.currcycle.marktime += seconds;
|
||||
g->gcmetrics.currcycle.markrequests += g->gcstepsize;
|
||||
g->gcmetrics.currcycle.markwork += work;
|
||||
|
||||
if (assist)
|
||||
g->gcmetrics.currcycle.markassisttime += seconds;
|
||||
@ -74,7 +75,7 @@ static void recordGcStateStep(global_State* g, int startgcstate, double seconds,
|
||||
break;
|
||||
case GCSsweep:
|
||||
g->gcmetrics.currcycle.sweeptime += seconds;
|
||||
g->gcmetrics.currcycle.sweeprequests += g->gcstepsize;
|
||||
g->gcmetrics.currcycle.sweepwork += work;
|
||||
|
||||
if (assist)
|
||||
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.currcycle.assistwork += work;
|
||||
g->gcmetrics.currcycle.assistrequests += g->gcstepsize;
|
||||
}
|
||||
else
|
||||
{
|
||||
g->gcmetrics.stepexplicittimeacc += seconds;
|
||||
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));
|
||||
}
|
||||
|
||||
void luaC_step(lua_State* L, bool assist)
|
||||
size_t luaC_step(lua_State* L, bool assist)
|
||||
{
|
||||
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);
|
||||
size_t debt = g->totalbytes - g->GCthreshold;
|
||||
|
||||
@ -902,12 +901,13 @@ void luaC_step(lua_State* L, bool assist)
|
||||
int lastgcstate = g->gcstate;
|
||||
|
||||
size_t work = gcstep(L, lim);
|
||||
(void)work;
|
||||
|
||||
#ifdef LUAI_GCMETRICS
|
||||
recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
|
||||
#endif
|
||||
|
||||
size_t actualstepsize = work * 100 / g->gcstepmul;
|
||||
|
||||
// at the end of the last cycle
|
||||
if (g->gcstate == GCSpause)
|
||||
{
|
||||
@ -927,14 +927,16 @@ void luaC_step(lua_State* L, bool assist)
|
||||
}
|
||||
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)
|
||||
if (g->GCthreshold > debt)
|
||||
if (FFlag::LuauGcWorkTrackFix ? g->GCthreshold >= debt : g->GCthreshold > debt)
|
||||
g->GCthreshold -= debt;
|
||||
}
|
||||
|
||||
GC_INTERRUPT(lastgcstate);
|
||||
|
||||
return actualstepsize;
|
||||
}
|
||||
|
||||
void luaC_fullgc(lua_State* L)
|
||||
|
@ -133,7 +133,7 @@
|
||||
#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_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_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
||||
LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv);
|
||||
|
@ -106,7 +106,7 @@ struct GCCycleMetrics
|
||||
double markassisttime = 0.0;
|
||||
double markmaxexplicittime = 0.0;
|
||||
size_t markexplicitsteps = 0;
|
||||
size_t markrequests = 0;
|
||||
size_t markwork = 0;
|
||||
|
||||
double atomicstarttimestamp = 0.0;
|
||||
size_t atomicstarttotalsizebytes = 0;
|
||||
@ -122,10 +122,7 @@ struct GCCycleMetrics
|
||||
double sweepassisttime = 0.0;
|
||||
double sweepmaxexplicittime = 0.0;
|
||||
size_t sweepexplicitsteps = 0;
|
||||
size_t sweeprequests = 0;
|
||||
|
||||
size_t assistrequests = 0;
|
||||
size_t explicitrequests = 0;
|
||||
size_t sweepwork = 0;
|
||||
|
||||
size_t assistwork = 0;
|
||||
size_t explicitwork = 0;
|
||||
|
@ -814,10 +814,9 @@ def run(args, argsubcb):
|
||||
|
||||
analyzeResult('', mainResult, compareResults)
|
||||
else:
|
||||
for subdir, dirs, files in os.walk(arguments.folder):
|
||||
for filename in files:
|
||||
filepath = subdir + os.sep + filename
|
||||
|
||||
all_files = [subdir + os.sep + filename for subdir, dirs, files in os.walk(arguments.folder) for filename in files]
|
||||
for filepath in sorted(all_files):
|
||||
subdir, filename = os.path.split(filepath)
|
||||
if filename.endswith(".lua"):
|
||||
if arguments.run_test == None or re.match(arguments.run_test, filename[:-4]):
|
||||
runTest(subdir, filename, filepath)
|
||||
|
@ -103,7 +103,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||
// Vector3 stub
|
||||
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 = {
|
||||
{"X", {env.numberType}},
|
||||
{"Y", {env.numberType}},
|
||||
@ -117,7 +117,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||
env.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
||||
|
||||
// Instance stub
|
||||
TypeId instanceType = arena.addType(ClassTypeVar{"Instance", {}, nullopt, nullopt, {}, {}});
|
||||
TypeId instanceType = arena.addType(ClassTypeVar{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(instanceType)->props = {
|
||||
{"Name", {env.stringType}},
|
||||
};
|
||||
@ -125,7 +125,7 @@ int registerTypes(Luau::TypeChecker& env)
|
||||
env.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
|
||||
// Part stub
|
||||
TypeId partType = arena.addType(ClassTypeVar{"Part", {}, instanceType, nullopt, {}, {}});
|
||||
TypeId partType = arena.addType(ClassTypeVar{"Part", {}, instanceType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(partType)->props = {
|
||||
{"Position", {vector3InstanceType}},
|
||||
};
|
||||
@ -173,7 +173,7 @@ struct FuzzConfigResolver : Luau::ConfigResolver
|
||||
{
|
||||
FuzzConfigResolver()
|
||||
{
|
||||
defaultConfig.mode = Luau::Mode::Nonstrict; // typecheckTwice option will cover Strict mode
|
||||
defaultConfig.mode = Luau::Mode::Nonstrict;
|
||||
defaultConfig.enabledLint.warningMask = ~0ull;
|
||||
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)
|
||||
if (kFuzzLinter && result.errors.empty())
|
||||
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&)
|
||||
{
|
||||
|
@ -3034,4 +3034,40 @@ string:@1
|
||||
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();
|
||||
|
@ -17,11 +17,13 @@ std::string rep(const std::string& s, size_t n);
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
@ -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")
|
||||
{
|
||||
// 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();
|
||||
|
@ -98,4 +98,129 @@ end
|
||||
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();
|
||||
|
@ -9,6 +9,46 @@
|
||||
|
||||
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_CASE("encode_constants")
|
||||
@ -51,7 +91,7 @@ TEST_CASE("encode_AstStatBlock")
|
||||
toJson(&block));
|
||||
}
|
||||
|
||||
TEST_CASE("encode_tables")
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables")
|
||||
{
|
||||
std::string src = R"(
|
||||
local x: {
|
||||
@ -61,16 +101,294 @@ TEST_CASE("encode_tables")
|
||||
}
|
||||
)";
|
||||
|
||||
Allocator allocator;
|
||||
AstNameTable names(allocator);
|
||||
ParseResult parseResult = Parser::parse(src.c_str(), src.length(), names, allocator);
|
||||
|
||||
REQUIRE(parseResult.errors.size() == 0);
|
||||
std::string json = toJson(parseResult.root);
|
||||
AstStatBlock* root = expectParse(src);
|
||||
std::string json = toJson(root);
|
||||
|
||||
CHECK(
|
||||
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}}]}]}]})");
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -1436,7 +1436,7 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
|
||||
{
|
||||
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);
|
||||
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
|
||||
|
@ -173,13 +173,13 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
{
|
||||
{"__add", {typeChecker.anyType}},
|
||||
},
|
||||
std::nullopt, std::nullopt, {}, {}}};
|
||||
std::nullopt, std::nullopt, {}, {}, "Test"}};
|
||||
TypeVar exampleClass{ClassTypeVar{"ExampleClass",
|
||||
{
|
||||
{"PropOne", {typeChecker.numberType}},
|
||||
{"PropTwo", {typeChecker.stringType}},
|
||||
},
|
||||
std::nullopt, &exampleMetaClass, {}, {}}};
|
||||
std::nullopt, &exampleMetaClass, {}, {}, "Test"}};
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
@ -196,9 +196,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
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{}});
|
||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||
@ -207,17 +210,17 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types")
|
||||
CloneState cloneState;
|
||||
|
||||
TypeId clonedTy = clone(&freeTy, dest, cloneState);
|
||||
CHECK_EQ("any", toString(clonedTy));
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
CHECK(get<FreeTypeVar>(clonedTy));
|
||||
|
||||
cloneState = {};
|
||||
TypePackId clonedTp = clone(&freeTp, dest, cloneState);
|
||||
CHECK_EQ("...any", toString(clonedTp));
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
CHECK(get<FreeTypePack>(clonedTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLosslessClone", true};
|
||||
|
||||
TypeVar tableTy{TableTypeVar{}};
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
||||
ttv->state = TableState::Free;
|
||||
@ -227,8 +230,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables")
|
||||
|
||||
TypeId cloned = clone(&tableTy, dest, cloneState);
|
||||
const TableTypeVar* clonedTtv = get<TableTypeVar>(cloned);
|
||||
CHECK_EQ(clonedTtv->state, TableState::Sealed);
|
||||
CHECK(cloneState.encounteredFreeType);
|
||||
CHECK_EQ(clonedTtv->state, TableState::Free);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
|
||||
|
@ -13,6 +13,29 @@ using namespace Luau;
|
||||
|
||||
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")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -35,8 +58,13 @@ TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
|
||||
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"(
|
||||
--!nonstrict
|
||||
function getMinCardCountForWidth(width)
|
||||
@ -51,22 +79,18 @@ TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_coul
|
||||
TypeId t = requireType("getMinCardCountForWidth");
|
||||
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")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
function foo(x): number return 'hello' end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
REQUIRE_NE(*typeChecker.anyType, *requireType("foo"));
|
||||
}
|
||||
#endif
|
||||
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauSealExports", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
@ -272,7 +302,7 @@ TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
||||
|
||||
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")
|
||||
|
@ -21,7 +21,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||
|
||||
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);
|
||||
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
@ -31,7 +31,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||
addGlobalBinding(typeChecker, "Parent", {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);
|
||||
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
|
||||
@ -39,7 +39,7 @@ void createSomeClasses(TypeChecker& typeChecker)
|
||||
addGlobalBinding(typeChecker, "Child", {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});
|
||||
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")));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||
{
|
||||
check(R"(
|
||||
@ -428,6 +427,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||
CHECK(!isSubtype(b, d));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_indexers_are_invariant")
|
||||
{
|
||||
check(R"(
|
||||
@ -619,6 +619,7 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -639,7 +640,7 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type")
|
||||
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")
|
||||
@ -950,6 +951,27 @@ TEST_CASE_FIXTURE(Fixture, "nested_table_normalization_with_non_table__no_ice")
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
@ -964,4 +986,16 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_instersection_combine_must_follow")
|
||||
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();
|
||||
|
@ -21,13 +21,13 @@ struct ToDotClassFixture : Fixture
|
||||
|
||||
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 = {
|
||||
{"BaseField", {typeChecker.numberType}},
|
||||
};
|
||||
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 = {
|
||||
{"ChildField", {typeChecker.stringType}},
|
||||
};
|
||||
|
@ -661,4 +661,21 @@ type t4 = false
|
||||
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();
|
||||
|
@ -19,13 +19,13 @@ struct ClassFixture : Fixture
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}});
|
||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||
{"BaseMethod", {makeFunction(arena, baseClassInstanceType, {numberType}, {})}},
|
||||
{"BaseField", {numberType}},
|
||||
};
|
||||
|
||||
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}});
|
||||
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassType)->props = {
|
||||
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
|
||||
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
||||
@ -34,39 +34,39 @@ struct ClassFixture : Fixture
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
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 = {
|
||||
{"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 = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
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 = {
|
||||
{"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 = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||
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 = {
|
||||
{"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 = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
||||
};
|
||||
@ -75,13 +75,13 @@ struct ClassFixture : Fixture
|
||||
|
||||
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 = {
|
||||
{"X", {numberType}},
|
||||
{"Y", {numberType}},
|
||||
};
|
||||
|
||||
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}});
|
||||
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(vector2Type)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
|
||||
};
|
||||
@ -468,4 +468,18 @@ caused by:
|
||||
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();
|
||||
|
@ -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")
|
||||
{
|
||||
ScopedFastFlag luauCloneDeclaredGlobals{"LuauCloneDeclaredGlobals", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Cls
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
@ -664,7 +669,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it")
|
||||
end
|
||||
|
||||
return function()
|
||||
return f():andThen()
|
||||
return f()
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
function Test(a)
|
||||
function Test(a): ...any
|
||||
return 1, ""
|
||||
end
|
||||
|
||||
|
||||
local tab = {}
|
||||
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]));
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -307,8 +307,6 @@ type Rename = typeof(x.x)
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "module_type_conflict")
|
||||
{
|
||||
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type T = { x: number }
|
||||
return {}
|
||||
@ -343,8 +341,6 @@ caused by:
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "module_type_conflict_instantiated")
|
||||
{
|
||||
ScopedFastFlag luauTypeMismatchModuleName{"LuauTypeMismatchModuleName", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Wrap<T> = { x: T }
|
||||
return {}
|
||||
|
@ -584,20 +584,6 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
@ -636,6 +622,10 @@ TEST_CASE_FIXTURE(Fixture, "lower_bounds_calculation_is_too_permissive_with_over
|
||||
// Once fixed, move this to Normalize.test.cpp
|
||||
TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_tables")
|
||||
{
|
||||
#if defined(_DEBUG) || defined(_NOOPT)
|
||||
ScopedFastInt sfi("LuauNormalizeIterationLimit", 500);
|
||||
#endif
|
||||
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ struct RefinementClassFixture : Fixture
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
|
||||
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 = {
|
||||
{"X", Property{typeChecker.numberType}},
|
||||
{"Y", Property{typeChecker.numberType}},
|
||||
@ -52,7 +52,7 @@ struct RefinementClassFixture : Fixture
|
||||
};
|
||||
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 isARets = arena.addTypePack({typeChecker.booleanType});
|
||||
@ -66,9 +66,9 @@ struct RefinementClassFixture : Fixture
|
||||
};
|
||||
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);
|
||||
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 = {
|
||||
{"Position", Property{vec3}},
|
||||
};
|
||||
|
@ -2086,7 +2086,6 @@ caused by:
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||
{
|
||||
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
||||
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = { [number]: string }
|
||||
@ -2105,7 +2104,6 @@ caused by:
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
||||
{
|
||||
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
|
||||
ScopedFastFlag luauExtendedIndexerError{"LuauExtendedIndexerError", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
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")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nocheck
|
||||
function f(x)
|
||||
return x
|
||||
return 5
|
||||
end
|
||||
-- we get type information even if there's type errors
|
||||
f(1, 2)
|
||||
)");
|
||||
|
||||
CHECK_EQ("(any) -> (...any)", toString(requireType("f")));
|
||||
CHECK_EQ("(any) -> number", toString(requireType("f")));
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
@ -363,6 +368,11 @@ TEST_CASE_FIXTURE(Fixture, "globals")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "globals2")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
foo = function() return 1 end
|
||||
@ -373,9 +383,9 @@ TEST_CASE_FIXTURE(Fixture, "globals2")
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("() -> (...any)", toString(tm->wantedType));
|
||||
CHECK_EQ("() -> number", toString(tm->wantedType));
|
||||
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")
|
||||
|
@ -275,7 +275,7 @@ TEST_CASE("tagging_tables")
|
||||
|
||||
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"));
|
||||
Luau::attachTag(&base, "foo");
|
||||
CHECK(Luau::hasTag(&base, "foo"));
|
||||
@ -283,8 +283,8 @@ TEST_CASE("tagging_classes")
|
||||
|
||||
TEST_CASE("tagging_subclasses")
|
||||
{
|
||||
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr}};
|
||||
TypeVar derived{ClassTypeVar{"Derived", {}, &base, std::nullopt, {}, nullptr}};
|
||||
TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}};
|
||||
TypeVar derived{ClassTypeVar{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test"}};
|
||||
|
||||
CHECK(!Luau::hasTag(&base, "foo"));
|
||||
CHECK(!Luau::hasTag(&derived, "foo"));
|
||||
|
Loading…
Reference in New Issue
Block a user