Sync to upstream/release/527 (#491)

This commit is contained in:
Arseny Kapoulkine 2022-05-19 17:02:24 -07:00 committed by GitHub
parent 8b4c6aabc2
commit f5923aefeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 1097 additions and 1369 deletions

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypeArena.h"
#include "Luau/TypeVar.h"
#include <unordered_map>
@ -18,7 +19,6 @@ struct CloneState
SeenTypePacks seenTypePacks;
int recursionCount = 0;
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
};
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);

View file

@ -5,6 +5,7 @@
#include "Luau/Location.h"
#include "Luau/TypeVar.h"
#include "Luau/Variant.h"
#include "Luau/TypeArena.h"
namespace Luau
{
@ -108,9 +109,6 @@ struct FunctionDoesNotTakeSelf
struct FunctionRequiresSelf
{
// TODO: Delete with LuauAnyInIsOptionalIsOptional
int requiredExtraNils = 0;
bool operator==(const FunctionRequiresSelf& rhs) const;
};

View file

@ -34,10 +34,6 @@ const LValue* baseof(const LValue& lvalue);
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
// Utility function: breaks down an LValue to get at the Symbol, and reverses the vector of keys.
// TODO: remove with FFlagLuauTypecheckOptPass
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue);
// Utility function: breaks down an LValue to get at the Symbol
Symbol getBaseSymbol(const LValue& lvalue);

View file

@ -2,11 +2,10 @@
#pragma once
#include "Luau/FileResolver.h"
#include "Luau/TypePack.h"
#include "Luau/TypedAllocator.h"
#include "Luau/ParseOptions.h"
#include "Luau/Error.h"
#include "Luau/ParseResult.h"
#include "Luau/TypeArena.h"
#include <memory>
#include <vector>
@ -54,35 +53,6 @@ struct RequireCycle
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
};
struct TypeArena
{
TypedAllocator<TypeVar> typeVars;
TypedAllocator<TypePackVar> typePacks;
void clear();
template<typename T>
TypeId addType(T tv)
{
if constexpr (std::is_same_v<T, UnionTypeVar>)
LUAU_ASSERT(tv.options.size() >= 2);
return addTV(TypeVar(std::move(tv)));
}
TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types);
TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack);
};
void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena);
struct Module
{
~Module();
@ -111,9 +81,7 @@ struct Module
// Once a module has been typechecked, we clone its public interface into a separate arena.
// This helps us to force TypeVar ownership into a DAG rather than a DCG.
// Returns true if there were any free types encountered in the public interface. This
// indicates a bug in the type checker that we want to surface.
bool clonePublicInterface(InternalErrorReporter& ice);
void clonePublicInterface(InternalErrorReporter& ice);
};
} // namespace Luau

View file

@ -1,8 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/DenseHash.h"

View file

@ -7,8 +7,6 @@
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -93,15 +91,6 @@ struct TxnLog
{
}
TxnLog(TxnLog* parent, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, parent(parent)
, sharedSeen(sharedSeen)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;

View file

@ -0,0 +1,42 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypedAllocator.h"
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
#include <vector>
namespace Luau
{
struct TypeArena
{
TypedAllocator<TypeVar> typeVars;
TypedAllocator<TypePackVar> typePacks;
void clear();
template<typename T>
TypeId addType(T tv)
{
if constexpr (std::is_same_v<T, UnionTypeVar>)
LUAU_ASSERT(tv.options.size() >= 2);
return addTV(TypeVar(std::move(tv)));
}
TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types);
TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack);
};
void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena);
}

View file

@ -187,7 +187,6 @@ struct TypeChecker
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
TypeId checkRelationalOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
@ -395,7 +394,7 @@ private:
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
public:
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
private:
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
@ -403,14 +402,14 @@ private:
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
void resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
void resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense);
bool isNonstrictMode() const;
bool useConstrainedIntersections() const;

View file

@ -5,7 +5,7 @@
#include "Luau/Location.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeInfer.h"
#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header.
#include "Luau/TypeArena.h"
#include "Luau/UnifierSharedState.h"
#include <unordered_set>
@ -55,8 +55,6 @@ struct Unifier
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location, Variance variance,
UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy);

View file

@ -10,6 +10,7 @@
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
namespace Luau
{
@ -471,18 +472,21 @@ struct GenericTypeVarVisitor
else if (auto pack = get<TypePack>(tp))
{
visit(tp, *pack);
bool res = visit(tp, *pack);
if (!FFlag::LuauNormalizeFlagIsConservative || res)
{
for (TypeId ty : pack->head)
traverse(ty);
for (TypeId ty : pack->head)
traverse(ty);
if (pack->tail)
traverse(*pack->tail);
if (pack->tail)
traverse(*pack->tail);
}
}
else if (auto pack = get<VariadicTypePack>(tp))
{
visit(tp, *pack);
traverse(pack->ty);
bool res = visit(tp, *pack);
if (!FFlag::LuauNormalizeFlagIsConservative || res)
traverse(pack->ty);
}
else
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");

View file

@ -8,7 +8,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
/** FIXME: Many of these type definitions are not quite completely accurate.
@ -408,41 +407,29 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
{
auto [paramPack, predicates] = exprResult;
if (FFlag::LuauAssertStripsFalsyTypes)
TypeArena& arena = typechecker.currentModule->internalTypes;
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{
TypeArena& arena = typechecker.currentModule->internalTypes;
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{
std::optional<TypeId> fst = first(*tail);
if (!fst)
return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
}
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
else
{
if (expr.args.size < 1)
std::optional<TypeId> fst = first(*tail);
if (!fst)
return ExprResult<TypePackId>{paramPack};
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
}
typechecker.resolve(predicates, scope, true);
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
static std::optional<ExprResult<TypePackId>> magicFunctionPack(

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Clone.h"
#include "Luau/Module.h"
#include "Luau/RecursionCounter.h"
#include "Luau/TypePack.h"
#include "Luau/Unifiable.h"
@ -9,8 +8,6 @@
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
LUAU_FASTFLAG(LuauNoMethodLocations)
namespace Luau
@ -89,20 +86,8 @@ struct TypePackCloner
void operator()(const Unifiable::Free& t)
{
if (FFlag::LuauLosslessClone)
{
defaultClone(t);
}
else
{
cloneState.encounteredFreeType = true;
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
TypePackId cloned = dest.addTypePack(*err);
seenTypePacks[typePackId] = cloned;
}
defaultClone(t);
}
void operator()(const Unifiable::Generic& t)
{
defaultClone(t);
@ -152,18 +137,7 @@ 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;
}
defaultClone(t);
}
void TypeCloner::operator()(const Unifiable::Generic& t)
@ -191,9 +165,6 @@ 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});
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
LUAU_ASSERT(ctv);
@ -230,9 +201,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
ftv->argTypes = clone(t.argTypes, dest, cloneState);
ftv->argNames = t.argNames;
ftv->retType = clone(t.retType, dest, cloneState);
if (FFlag::LuauTypecheckOptPass)
ftv->hasNoGenerics = t.hasNoGenerics;
ftv->hasNoGenerics = t.hasNoGenerics;
}
void TypeCloner::operator()(const TableTypeVar& t)
@ -270,13 +239,6 @@ void TypeCloner::operator()(const TableTypeVar& t)
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, cloneState);
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
{
cloneState.encounteredFreeType = true;
ttv->state = TableState::Sealed;
}
ttv->definitionModuleName = t.definitionModuleName;
if (!FFlag::LuauNoMethodLocations)
ttv->methodDefinitionLocations = t.methodDefinitionLocations;

View file

@ -2,7 +2,6 @@
#include "Luau/Error.h"
#include "Luau/Clone.h"
#include "Luau/Module.h"
#include "Luau/StringUtils.h"
#include "Luau/ToString.h"
@ -178,15 +177,7 @@ struct ErrorConverter
std::string operator()(const Luau::FunctionRequiresSelf& e) const
{
if (e.requiredExtraNils)
{
const char* plural = e.requiredExtraNils == 1 ? "" : "s";
return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or "
"pass %i extra nil%s to suppress this warning",
e.requiredExtraNils, plural);
}
else
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
}
std::string operator()(const Luau::OccursCheckFailed&) const
@ -539,7 +530,7 @@ bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
{
return requiredExtraNils == e.requiredExtraNils;
return true;
}
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const

View file

@ -48,7 +48,7 @@ static void errorToString(std::ostream& stream, const T& err)
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
stream << "FunctionDoesNotTakeSelf { }";
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
stream << "FunctionRequiresSelf { extraNils " << err.requiredExtraNils << " }";
stream << "FunctionRequiresSelf { }";
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
stream << "OccursCheckFailed { }";
else if constexpr (std::is_same_v<T, UnknownRequire>)

View file

@ -5,8 +5,6 @@
#include <vector>
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -79,27 +77,8 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
return std::nullopt;
}
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
const LValue* current = &lvalue;
std::vector<std::string> keys;
while (auto field = get<Field>(*current))
{
keys.push_back(field->key);
current = baseof(*current);
}
const Symbol* symbol = get<Symbol>(*current);
LUAU_ASSERT(symbol);
return {*symbol, std::vector<std::string>(keys.rbegin(), keys.rend())};
}
Symbol getBaseSymbol(const LValue& lvalue)
{
LUAU_ASSERT(FFlag::LuauTypecheckOptPass);
const LValue* current = &lvalue;
while (auto field = get<Field>(*current))
current = baseof(*current);

View file

@ -13,9 +13,8 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauLosslessClone)
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative);
namespace Luau
{
@ -55,89 +54,25 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
return contains(pos, *iter);
}
void TypeArena::clear()
struct ForceNormal : TypeVarOnceVisitor
{
typeVars.clear();
typePacks.clear();
}
bool visit(TypeId ty) override
{
asMutable(ty)->normal = true;
return true;
}
TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
bool visit(TypeId ty, const FreeTypeVar& ftv) override
{
visit(ty);
return true;
}
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
ScopePtr Module::getModuleScope() const
{
LUAU_ASSERT(!scopes.empty());
return scopes.front().second;
}
void freeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.freeze();
arena.typePacks.freeze();
}
void unfreeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.unfreeze();
arena.typePacks.unfreeze();
}
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
return true;
}
};
Module::~Module()
{
@ -145,7 +80,7 @@ Module::~Module()
unfreeze(internalTypes);
}
bool Module::clonePublicInterface(InternalErrorReporter& ice)
void Module::clonePublicInterface(InternalErrorReporter& ice)
{
LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty());
@ -165,11 +100,22 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
normalize(*moduleScope->varargPack, interfaceTypes, ice);
}
ForceNormal forceNormal;
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
{
tf = clone(tf, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(tf.type, interfaceTypes, ice);
if (FFlag::LuauNormalizeFlagIsConservative)
{
// We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables
// won't be marked normal. If the types aren't normal by now, they never will be.
forceNormal.traverse(tf.type);
}
}
}
for (TypeId ty : moduleScope->returnType)
@ -191,11 +137,12 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
freeze(internalTypes);
freeze(interfaceTypes);
}
if (FFlag::LuauLosslessClone)
return false; // TODO: make function return void.
else
return cloneState.encounteredFreeType;
ScopePtr Module::getModuleScope() const
{
LUAU_ASSERT(!scopes.empty());
return scopes.front().second;
}
} // namespace Luau

View file

@ -14,6 +14,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
namespace Luau
{
@ -260,8 +261,13 @@ static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, Intern
if (count >= FInt::LuauNormalizeIterationLimit)
ice.ice("Luau::areNormal hit iteration limit");
// 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();
if (FFlag::LuauNormalizeFlagIsConservative)
return ty->normal;
else
{
// 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);
@ -1003,8 +1009,15 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
(void)clone(ty, arena, state);
Normalize n{arena, ice};
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(ty, n, seen);
if (FFlag::LuauNormalizeFlagIsConservative)
{
DEPRECATED_visitTypeVar(ty, n);
}
else
{
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(ty, n, seen);
}
return {ty, !n.limitExceeded};
}
@ -1028,8 +1041,15 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
(void)clone(tp, arena, state);
Normalize n{arena, ice};
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(tp, n, seen);
if (FFlag::LuauNormalizeFlagIsConservative)
{
DEPRECATED_visitTypeVar(tp, n);
}
else
{
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(tp, n, seen);
}
return {tp, !n.limitExceeded};
}

View file

@ -4,7 +4,7 @@
#include "Luau/VisitTypeVar.h"
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAG(LuauAlwaysQuantify)
namespace Luau
{
@ -59,8 +59,7 @@ struct Quantifier final : TypeVarOnceVisitor
bool visit(TypeId ty, const FreeTypeVar& ftv) override
{
if (FFlag::LuauTypecheckOptPass)
seenMutableType = true;
seenMutableType = true;
if (!level.subsumes(ftv.level))
return false;
@ -76,20 +75,17 @@ struct Quantifier final : TypeVarOnceVisitor
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
if (FFlag::LuauTypecheckOptPass)
{
if (ttv.state == TableState::Generic)
seenGenericType = true;
if (ttv.state == TableState::Generic)
seenGenericType = true;
if (ttv.state == TableState::Free)
seenMutableType = true;
}
if (ttv.state == TableState::Free)
seenMutableType = true;
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
return false;
if (!level.subsumes(ttv.level))
{
if (FFlag::LuauTypecheckOptPass && ttv.state == TableState::Unsealed)
if (ttv.state == TableState::Unsealed)
seenMutableType = true;
return false;
}
@ -97,9 +93,7 @@ struct Quantifier final : TypeVarOnceVisitor
if (ttv.state == TableState::Free)
{
ttv.state = TableState::Generic;
if (FFlag::LuauTypecheckOptPass)
seenGenericType = true;
seenGenericType = true;
}
else if (ttv.state == TableState::Unsealed)
ttv.state = TableState::Sealed;
@ -111,8 +105,7 @@ struct Quantifier final : TypeVarOnceVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (FFlag::LuauTypecheckOptPass)
seenMutableType = true;
seenMutableType = true;
if (!level.subsumes(ftp.level))
return false;
@ -131,10 +124,18 @@ void quantify(TypeId ty, TypeLevel level)
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
if (FFlag::LuauAlwaysQuantify)
{
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
if (FFlag::LuauTypecheckOptPass && ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
}

View file

@ -9,9 +9,6 @@
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
LUAU_FASTFLAG(LuauNoMethodLocations)
namespace Luau
@ -19,26 +16,20 @@ namespace Luau
void Tarjan::visitChildren(TypeId ty, int index)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty))
return;
if (FFlag::LuauTypecheckOptPass)
{
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (auto pty = log->pending(ty))
ty = &pty->pending;
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
visitChild(ftv->argTypes);
visitChild(ftv->retType);
}
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
LUAU_ASSERT(!ttv->boundTo);
for (const auto& [name, prop] : ttv->props)
@ -55,17 +46,17 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp);
}
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
visitChild(mtv->table);
visitChild(mtv->metatable);
}
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
for (TypeId opt : utv->options)
visitChild(opt);
}
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
for (TypeId part : itv->parts)
visitChild(part);
@ -79,28 +70,22 @@ void Tarjan::visitChildren(TypeId ty, int index)
void Tarjan::visitChildren(TypePackId tp, int index)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp))
return;
if (FFlag::LuauTypecheckOptPass)
{
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
if (const TypePack* tpp = get<TypePack>(tp))
{
for (TypeId tv : tpp->head)
visitChild(tv);
if (tpp->tail)
visitChild(*tpp->tail);
}
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
visitChild(vtp->ty);
}
@ -108,10 +93,7 @@ void Tarjan::visitChildren(TypePackId tp, int index)
std::pair<int, bool> Tarjan::indexify(TypeId ty)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
ty = log->follow(ty);
bool fresh = !typeToIndex.contains(ty);
int& index = typeToIndex[ty];
@ -129,10 +111,7 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
tp = log->follow(tp);
bool fresh = !packToIndex.contains(tp);
int& index = packToIndex[tp];
@ -150,8 +129,7 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
void Tarjan::visitChild(TypeId ty)
{
if (!FFlag::LuauSubstituteFollowPossibleMutations)
ty = log->follow(ty);
ty = log->follow(ty);
edgesTy.push_back(ty);
edgesTp.push_back(nullptr);
@ -159,8 +137,7 @@ void Tarjan::visitChild(TypeId ty)
void Tarjan::visitChild(TypePackId tp)
{
if (!FFlag::LuauSubstituteFollowPossibleMutations)
tp = log->follow(tp);
tp = log->follow(tp);
edgesTy.push_back(nullptr);
edgesTp.push_back(tp);
@ -389,13 +366,10 @@ TypeId Substitution::clone(TypeId ty)
TypeId result = ty;
if (FFlag::LuauTypecheckOptPass)
{
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (auto pty = log->pending(ty))
ty = &pty->pending;
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.generics = ftv->generics;
@ -405,7 +379,7 @@ TypeId Substitution::clone(TypeId ty)
clone.argNames = ftv->argNames;
result = addType(std::move(clone));
}
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
LUAU_ASSERT(!ttv->boundTo);
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
@ -419,19 +393,19 @@ TypeId Substitution::clone(TypeId ty)
clone.tags = ttv->tags;
result = addType(std::move(clone));
}
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
clone.syntheticName = mtv->syntheticName;
result = addType(std::move(clone));
}
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
UnionTypeVar clone;
clone.options = utv->options;
result = addType(std::move(clone));
}
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
IntersectionTypeVar clone;
clone.parts = itv->parts;
@ -451,20 +425,17 @@ TypePackId Substitution::clone(TypePackId tp)
{
tp = log->follow(tp);
if (FFlag::LuauTypecheckOptPass)
{
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
if (const TypePack* tpp = get<TypePack>(tp))
{
TypePack clone;
clone.head = tpp->head;
clone.tail = tpp->tail;
return addTypePack(std::move(clone));
}
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
VariadicTypePack clone;
clone.ty = vtp->ty;
@ -476,28 +447,22 @@ TypePackId Substitution::clone(TypePackId tp)
void Substitution::foundDirty(TypeId ty)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
ty = log->follow(ty);
if (isDirty(ty))
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(ty)) : clean(ty);
newTypes[ty] = follow(clean(ty));
else
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(ty)) : clone(ty);
newTypes[ty] = follow(clone(ty));
}
void Substitution::foundDirty(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
tp = log->follow(tp);
if (isDirty(tp))
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(tp)) : clean(tp);
newPacks[tp] = follow(clean(tp));
else
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(tp)) : clone(tp);
newPacks[tp] = follow(clone(tp));
}
TypeId Substitution::replace(TypeId ty)
@ -525,10 +490,7 @@ void Substitution::replaceChildren(TypeId ty)
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
btv->boundTo = replace(btv->boundTo);
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty))
return;
@ -579,10 +541,7 @@ void Substitution::replaceChildren(TypeId ty)
void Substitution::replaceChildren(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp))
return;

View file

@ -219,6 +219,8 @@ struct StringifierState
return generateName(s);
}
int previousNameIndex = 0;
std::string getName(TypePackId ty)
{
const size_t s = result.nameMap.typePacks.size();
@ -228,9 +230,10 @@ struct StringifierState
for (int count = 0; count < 256; ++count)
{
std::string candidate = generateName(usedNames.size() + count);
std::string candidate = generateName(previousNameIndex + count);
if (!usedNames.count(candidate))
{
previousNameIndex += count;
usedNames.insert(candidate);
n = candidate;
return candidate;
@ -399,6 +402,7 @@ struct TypeVarStringifier
{
if (gtv.explicitName)
{
state.usedNames.insert(gtv.name);
state.result.nameMap.typeVars[ty] = gtv.name;
state.emit(gtv.name);
}
@ -943,6 +947,7 @@ struct TypePackStringifier
state.emit("gen-");
if (pack.explicitName)
{
state.usedNames.insert(pack.name);
state.result.nameMap.typePacks[tp] = pack.name;
state.emit(pack.name);
}

View file

@ -7,8 +7,6 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauJustOneCallFrameForHaveSeen, false)
namespace Luau
{
@ -150,37 +148,13 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
{
if (FFlag::LuauJustOneCallFrameForHaveSeen && !FFlag::LuauTypecheckOptPass)
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
for (const TxnLog* current = this; current; current = current->parent)
{
if (current->sharedSeen->end() != std::find(current->sharedSeen->begin(), current->sharedSeen->end(), sortedPair))
return true;
}
return false;
return true;
}
else
{
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
return true;
}
if (!FFlag::LuauTypecheckOptPass && parent)
{
return parent->haveSeen(lhs, rhs);
}
return false;
}
return false;
}
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)

View file

@ -0,0 +1,88 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeArena.h"
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false);
namespace Luau
{
void TypeArena::clear()
{
typeVars.clear();
typePacks.clear();
}
TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
void freeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.freeze();
arena.typePacks.freeze();
}
void unfreeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.unfreeze();
arena.typePacks.unfreeze();
}
}

View file

@ -32,32 +32,25 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSeparateTypechecks)
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauInstantiateFollows, false)
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false)
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2)
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false)
LUAU_FASTFLAGVARIABLE(LuauCheckImplicitNumbericKeys, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
LUAU_FASTFLAGVARIABLE(LuauTableUseCounterInstead, false)
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
LUAU_FASTFLAG(LuauLosslessClone)
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false);
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false);
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
namespace Luau
{
@ -371,12 +364,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
prepareErrorsForDisplay(currentModule->errors);
bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler);
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."}});
}
currentModule->clonePublicInterface(*iceHandler);
// Clear unifier cache since it's keyed off internal types that get deallocated
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
@ -701,7 +689,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement)
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
ScopePtr ifScope = childScope(scope, statement.thenbody->location);
reportErrors(resolve(result.predicates, ifScope, true));
resolve(result.predicates, ifScope, true);
check(ifScope, *statement.thenbody);
if (statement.elsebody)
@ -734,7 +722,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement)
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
ScopePtr whileScope = childScope(scope, statement.body->location);
reportErrors(resolve(result.predicates, whileScope, true));
resolve(result.predicates, whileScope, true);
check(whileScope, *statement.body);
}
@ -1154,10 +1142,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
}
else
{
if (FFlag::LuauInstantiateFollows)
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
else
iterTy = follow(instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location));
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
}
if (FFlag::LuauTypecheckIter)
@ -1849,23 +1834,11 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
tablify(type);
if (FFlag::LuauDiscriminableUnions2)
if (isString(type))
{
if (isString(type))
{
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
LUAU_ASSERT(mtIndex);
type = *mtIndex;
}
}
else
{
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
{
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
type = *mtIndex;
}
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
LUAU_ASSERT(mtIndex);
type = *mtIndex;
}
if (TableTypeVar* tableType = getMutableTableType(type))
@ -1966,23 +1939,10 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
return std::nullopt;
}
if (FFlag::LuauDoNotTryToReduce)
{
if (parts.size() == 1)
return parts[0];
if (parts.size() == 1)
return parts[0];
return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
}
else
{
// TODO(amccord): Write some logic to correctly handle intersections. CLI-34659
std::vector<TypeId> result = reduceUnion(parts);
if (result.size() == 1)
return result[0];
return addType(IntersectionTypeVar{result});
}
return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
}
if (addErrors)
@ -1993,103 +1953,55 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
{
if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering)
std::vector<TypeId> result;
for (TypeId t : types)
{
std::vector<TypeId> result;
for (TypeId t : types)
t = follow(t);
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
{
t = follow(t);
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
if (FFlag::LuauReduceUnionRecursion)
{
if (FFlag::LuauReduceUnionRecursion)
for (TypeId ty : utv)
{
for (TypeId ty : utv)
{
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (result.end() == std::find(result.begin(), result.end(), ty))
result.push_back(ty);
}
}
else
{
std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r)
{
if (FFlag::LuauNormalizeFlagIsConservative)
ty = follow(ty);
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (std::find(result.begin(), result.end(), ty) == result.end())
result.push_back(ty);
}
if (result.end() == std::find(result.begin(), result.end(), ty))
result.push_back(ty);
}
}
else if (std::find(result.begin(), result.end(), t) == result.end())
result.push_back(t);
}
return result;
}
else
{
std::set<TypeId> s;
for (TypeId t : types)
{
if (const UnionTypeVar* utv = get<UnionTypeVar>(follow(t)))
else
{
std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r)
s.insert(ty);
{
ty = follow(ty);
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (std::find(result.begin(), result.end(), ty) == result.end())
result.push_back(ty);
}
}
else
s.insert(t);
}
// If any of them are ErrorTypeVars/AnyTypeVars, decay into them.
for (TypeId t : s)
{
t = follow(t);
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
}
std::vector<TypeId> r(s.begin(), s.end());
std::sort(r.begin(), r.end());
return r;
else if (std::find(result.begin(), result.end(), t) == result.end())
result.push_back(t);
}
return result;
}
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
{
if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
if (FFlag::LuauAnyInIsOptionalIsOptional)
{
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
else
{
bool hasNil = false;
for (TypeId option : utv)
{
if (isNil(option))
{
hasNil = true;
break;
}
}
if (!hasNil)
return ty;
}
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
std::vector<TypeId> result;
@ -2110,32 +2022,18 @@ std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
{
if (FFlag::LuauAnyInIsOptionalIsOptional)
ty = follow(ty);
if (auto utv = get<UnionTypeVar>(ty))
{
ty = follow(ty);
if (auto utv = get<UnionTypeVar>(ty))
{
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
{
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
}
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
else
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
{
if (isOptional(ty))
{
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(follow(ty)))
{
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
}
}
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
}
return ty;
@ -2194,8 +2092,7 @@ TypeId TypeChecker::checkExprTable(
if (indexer)
{
if (FFlag::LuauCheckImplicitNumbericKeys)
unify(numberType, indexer->indexType, value->location);
unify(numberType, indexer->indexType, value->location);
unify(valueType, indexer->indexResultType, value->location);
}
else
@ -2219,7 +2116,8 @@ TypeId TypeChecker::checkExprTable(
if (errors.empty())
exprType = expectedProp.type;
}
else if (expectedTable->indexer && isString(expectedTable->indexer->indexType))
else if (expectedTable->indexer && (FFlag::LuauExpectedPropTypeFromIndexer ? maybeString(expectedTable->indexer->indexType)
: isString(expectedTable->indexer->indexType)))
{
ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, k->location);
if (errors.empty())
@ -2259,26 +2157,13 @@ TypeId TypeChecker::checkExprTable(
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
{
if (FFlag::LuauTableUseCounterInstead)
RecursionCounter _rc(&checkRecursionCount);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
{
RecursionCounter _rc(&checkRecursionCount);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
{
reportErrorCodeTooComplex(expr.location);
return {errorRecoveryType(scope)};
}
return checkExpr_(scope, expr, expectedType);
reportErrorCodeTooComplex(expr.location);
return {errorRecoveryType(scope)};
}
else
{
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit, "checkExpr for tables");
return checkExpr_(scope, expr, expectedType);
}
}
ExprResult<TypeId> TypeChecker::checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
{
std::vector<std::pair<TypeId, TypeId>> fieldTypes(expr.items.size);
const TableTypeVar* expectedTable = nullptr;
@ -2324,6 +2209,8 @@ ExprResult<TypeId> TypeChecker::checkExpr_(const ScopePtr& scope, const AstExprT
{
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
expectedResultType = prop->second.type;
else if (FFlag::LuauExpectedPropTypeFromIndexer && expectedIndexType && maybeString(*expectedIndexType))
expectedResultType = expectedIndexResultType;
}
else if (expectedUnion)
{
@ -2529,7 +2416,7 @@ TypeId TypeChecker::checkRelationalOperation(
if (expr.op == AstExprBinary::Or && subexp->op == AstExprBinary::And)
{
ScopePtr subScope = childScope(scope, subexp->location);
reportErrors(resolve(predicates, subScope, true));
resolve(predicates, subScope, true);
return unionOfTypes(rhsType, stripNil(checkExpr(subScope, *subexp->right).type, true), expr.location);
}
}
@ -2851,8 +2738,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy),
{AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
{
@ -2864,7 +2750,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
@ -2872,8 +2758,8 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
PredicateVec predicates;
@ -2931,12 +2817,12 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprEr
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType)
{
ExprResult<TypeId> result = checkExpr(scope, *expr.condition);
ScopePtr trueScope = childScope(scope, expr.trueExpr->location);
reportErrors(resolve(result.predicates, trueScope, true));
resolve(result.predicates, trueScope, true);
ExprResult<TypeId> trueType = checkExpr(trueScope, *expr.trueExpr, expectedType);
ScopePtr falseScope = childScope(scope, expr.falseExpr->location);
// Don't report errors for this scope to avoid potentially duplicating errors reported for the first scope.
resolve(result.predicates, falseScope, false);
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
@ -3668,9 +3554,6 @@ void TypeChecker::checkArgumentList(
else if (state.log.getMutable<ErrorTypeVar>(t))
{
} // ok
else if (!FFlag::LuauAnyInIsOptionalIsOptional && isNonstrictMode() && state.log.get<AnyTypeVar>(t))
{
} // ok
else
{
size_t minParams = getMinParameterCount(&state.log, paramPack);
@ -3823,9 +3706,6 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
actualFunctionType = instantiate(scope, functionType, expr.func->location);
}
if (!FFlag::LuauInstantiateFollows)
actualFunctionType = follow(actualFunctionType);
TypePackId retPack;
if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2)
{
@ -4096,32 +3976,6 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
{
state.log.commit();
if (!FFlag::LuauAnyInIsOptionalIsOptional && isNonstrictMode() && !expr.self && expr.func->is<AstExprIndexName>() && ftv->hasSelf)
{
// If we are running in nonstrict mode, passing fewer arguments than the function is declared to take AND
// the function is declared with colon notation AND we use dot notation, warn.
auto [providedArgs, providedTail] = flatten(argPack);
// If we have a variadic tail, we can't say how many arguments were actually provided
if (!providedTail)
{
std::vector<TypeId> actualArgs = flatten(ftv->argTypes).first;
size_t providedCount = providedArgs.size();
size_t requiredCount = actualArgs.size();
// Ignore optional arguments
while (providedCount < requiredCount && requiredCount != 0 && isOptional(actualArgs[requiredCount - 1]))
requiredCount--;
if (providedCount < requiredCount)
{
int requiredExtraNils = int(requiredCount - providedCount);
reportError(TypeError{expr.func->location, FunctionRequiresSelf{requiredExtraNils}});
}
}
}
currentModule->astOverloadResolvedTypes[&expr] = fn;
// We select this overload
@ -4525,7 +4379,7 @@ bool Instantiation::isDirty(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics)
if (ftv->hasNoGenerics)
return false;
return true;
@ -4582,7 +4436,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics)
if (ftv->hasNoGenerics)
return true;
// We aren't recursing in the case of a generic function which
@ -4701,8 +4555,17 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
ty = follow(ty);
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
if (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
Luau::quantify(ty, scope->level);
if (FFlag::LuauAlwaysQuantify)
{
if (ftv)
Luau::quantify(ty, scope->level);
}
else
{
if (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
Luau::quantify(ty, scope->level);
}
if (FFlag::LuauLowerBoundsCalculation && ftv)
{
@ -4717,15 +4580,11 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
{
if (FFlag::LuauInstantiateFollows)
ty = follow(ty);
ty = follow(ty);
if (FFlag::LuauTypecheckOptPass)
{
const FunctionTypeVar* ftv = get<FunctionTypeVar>(FFlag::LuauInstantiateFollows ? ty : follow(ty));
if (ftv && ftv->hasNoGenerics)
return ty;
}
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
if (ftv && ftv->hasNoGenerics)
return ty;
Instantiation instantiation{log, &currentModule->internalTypes, scope->level};
@ -5392,10 +5251,9 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack
bool ApplyTypeFunction::isDirty(TypeId ty)
{
// Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace
// all generics.
if (get<GenericTypeVar>(ty))
if (FFlag::LuauApplyTypeFunctionFix && typeArguments.count(ty))
return true;
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypeVar>(ty))
return true;
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
{
@ -5409,10 +5267,9 @@ bool ApplyTypeFunction::isDirty(TypeId ty)
bool ApplyTypeFunction::isDirty(TypePackId tp)
{
// Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace
// all generics.
if (get<GenericTypePack>(tp))
if (FFlag::LuauApplyTypeFunctionFix && typePackArguments.count(tp))
return true;
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypePack>(tp))
return true;
else
return false;
@ -5436,11 +5293,13 @@ bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
TypeId ApplyTypeFunction::clean(TypeId ty)
{
// Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace
// all generics by free type variables.
TypeId& arg = typeArguments[ty];
if (arg)
if (FFlag::LuauApplyTypeFunctionFix)
{
LUAU_ASSERT(arg);
return arg;
}
else if (arg)
return arg;
else
return addType(FreeTypeVar{level});
@ -5448,11 +5307,13 @@ TypeId ApplyTypeFunction::clean(TypeId ty)
TypePackId ApplyTypeFunction::clean(TypePackId tp)
{
// Really this should just replace the arguments,
// but for bug-compatibility with existing code, we replace
// all generics by free type variables.
TypePackId& arg = typePackArguments[tp];
if (arg)
if (FFlag::LuauApplyTypeFunctionFix)
{
LUAU_ASSERT(arg);
return arg;
}
else if (arg)
return arg;
else
return addTypePack(FreeTypePack{level});
@ -5596,8 +5457,6 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
{
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2 || FFlag::LuauAssertStripsFalsyTypes);
const LValue* target = &lvalue;
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
@ -5683,66 +5542,6 @@ std::optional<TypeId> TypeChecker::resolveLValue(const ScopePtr& scope, const LV
// We need to search in the provided Scope. Find t.x.y first.
// We fail to find t.x.y. Try t.x. We found it. Now we must return the type of the property y from the mapped-to type of t.x.
// If we completely fail to find the Symbol t but the Scope has that entry, then we should walk that all the way through and terminate.
if (!FFlag::LuauTypecheckOptPass)
{
const auto& [symbol, keys] = getFullName(lvalue);
ScopePtr currentScope = scope;
while (currentScope)
{
std::optional<TypeId> found;
std::vector<LValue> childKeys;
const LValue* currentLValue = &lvalue;
while (currentLValue)
{
if (auto it = currentScope->refinements.find(*currentLValue); it != currentScope->refinements.end())
{
found = it->second;
break;
}
childKeys.push_back(*currentLValue);
currentLValue = baseof(*currentLValue);
}
if (!found)
{
// Should not be using scope->lookup. This is already recursive.
if (auto it = currentScope->bindings.find(symbol); it != currentScope->bindings.end())
found = it->second.typeId;
else
{
// Nothing exists in this Scope. Just skip and try the parent one.
currentScope = currentScope->parent;
continue;
}
}
for (auto it = childKeys.rbegin(); it != childKeys.rend(); ++it)
{
const LValue& key = *it;
// Symbol can happen. Skip.
if (get<Symbol>(key))
continue;
else if (auto field = get<Field>(key))
{
found = getIndexTypeFromType(scope, *found, field->key, Location(), false);
if (!found)
return std::nullopt; // Turns out this type doesn't have the property at all. We're done.
}
else
LUAU_ASSERT(!"New LValue alternative not handled here.");
}
return found;
}
// No entry for it at all. Can happen when LValue root is a global.
return std::nullopt;
}
const Symbol symbol = getBaseSymbol(lvalue);
ScopePtr currentScope = scope;
@ -5820,85 +5619,47 @@ static bool isUndecidable(TypeId ty)
return get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty);
}
ErrorVec TypeChecker::resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense)
{
ErrorVec errVec;
resolve(predicates, errVec, scope->refinements, scope, sense);
return errVec;
resolve(predicates, scope->refinements, scope, sense);
}
void TypeChecker::resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
void TypeChecker::resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
for (const Predicate& c : predicates)
resolve(c, errVec, refis, scope, sense, fromOr);
resolve(c, refis, scope, sense, fromOr);
}
void TypeChecker::resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
void TypeChecker::resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
if (auto truthyP = get<TruthyPredicate>(predicate))
resolve(*truthyP, errVec, refis, scope, sense, fromOr);
resolve(*truthyP, refis, scope, sense, fromOr);
else if (auto andP = get<AndPredicate>(predicate))
resolve(*andP, errVec, refis, scope, sense);
resolve(*andP, refis, scope, sense);
else if (auto orP = get<OrPredicate>(predicate))
resolve(*orP, errVec, refis, scope, sense);
resolve(*orP, refis, scope, sense);
else if (auto notP = get<NotPredicate>(predicate))
resolve(notP->predicates, errVec, refis, scope, !sense, fromOr);
resolve(notP->predicates, refis, scope, !sense, fromOr);
else if (auto isaP = get<IsAPredicate>(predicate))
resolve(*isaP, errVec, refis, scope, sense);
resolve(*isaP, refis, scope, sense);
else if (auto typeguardP = get<TypeGuardPredicate>(predicate))
resolve(*typeguardP, errVec, refis, scope, sense);
resolve(*typeguardP, refis, scope, sense);
else if (auto eqP = get<EqPredicate>(predicate))
resolve(*eqP, errVec, refis, scope, sense);
resolve(*eqP, refis, scope, sense);
else
ice("Unhandled predicate kind");
}
void TypeChecker::resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
void TypeChecker::resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
if (FFlag::LuauAssertStripsFalsyTypes)
{
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr)
return addRefinement(refis, truthyP.lvalue, *ty);
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr)
return addRefinement(refis, truthyP.lvalue, *ty);
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense));
}
else
{
auto predicate = [sense](TypeId option) -> std::optional<TypeId> {
if (isUndecidable(option) || isBoolean(option) || isNil(option) != sense)
return option;
return std::nullopt;
};
if (FFlag::LuauDiscriminableUnions2)
{
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr)
return addRefinement(refis, truthyP.lvalue, *ty);
refineLValue(truthyP.lvalue, refis, scope, predicate);
}
else
{
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (!ty)
return;
// This is a hack. :(
// Without this, the expression 'a or b' might refine 'b' to be falsy.
// I'm not yet sure how else to get this to do the right thing without this hack, so we'll do this for now in the meantime.
if (fromOr)
return addRefinement(refis, truthyP.lvalue, *ty);
if (std::optional<TypeId> result = filterMap(*ty, predicate))
addRefinement(refis, truthyP.lvalue, *result);
}
}
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense));
}
void TypeChecker::resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
if (!sense)
{
@ -5907,14 +5668,14 @@ void TypeChecker::resolve(const AndPredicate& andP, ErrorVec& errVec, Refinement
{NotPredicate{std::move(andP.rhs)}},
};
return resolve(orP, errVec, refis, scope, !sense);
return resolve(orP, refis, scope, !sense);
}
resolve(andP.lhs, errVec, refis, scope, sense);
resolve(andP.rhs, errVec, refis, scope, sense);
resolve(andP.lhs, refis, scope, sense);
resolve(andP.rhs, refis, scope, sense);
}
void TypeChecker::resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
if (!sense)
{
@ -5923,28 +5684,24 @@ void TypeChecker::resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMa
{NotPredicate{std::move(orP.rhs)}},
};
return resolve(andP, errVec, refis, scope, !sense);
return resolve(andP, refis, scope, !sense);
}
ErrorVec discarded;
RefinementMap leftRefis;
resolve(orP.lhs, errVec, leftRefis, scope, sense);
resolve(orP.lhs, leftRefis, scope, sense);
RefinementMap rightRefis;
resolve(orP.lhs, discarded, rightRefis, scope, !sense);
resolve(orP.rhs, errVec, rightRefis, scope, sense, true); // :(
resolve(orP.lhs, rightRefis, scope, !sense);
resolve(orP.rhs, rightRefis, scope, sense, true); // :(
merge(refis, leftRefis);
merge(refis, rightRefis);
}
void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
// This by itself is not truly enough to determine that A is stronger than B or vice versa.
// The best unambiguous way about this would be to have a function that returns the relationship ordering of a pair.
// i.e. TypeRelationship relationshipOf(TypeId superTy, TypeId subTy)
bool optionIsSubtype = canUnify(option, isaP.ty, isaP.location).empty();
bool targetIsSubtype = canUnify(isaP.ty, option, isaP.location).empty();
@ -5985,32 +5742,15 @@ void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, Refinement
return res;
};
if (FFlag::LuauDiscriminableUnions2)
{
refineLValue(isaP.lvalue, refis, scope, predicate);
}
else
{
std::optional<TypeId> ty = resolveLValue(refis, scope, isaP.lvalue);
if (!ty)
return;
if (std::optional<TypeId> result = filterMap(*ty, predicate))
addRefinement(refis, isaP.lvalue, *result);
else
{
addRefinement(refis, isaP.lvalue, errorRecoveryType(scope));
errVec.push_back(TypeError{isaP.location, TypeMismatch{isaP.ty, *ty}});
}
}
refineLValue(isaP.lvalue, refis, scope, predicate);
}
void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
// Rewrite the predicate 'type(foo) == "vector"' to be 'typeof(foo) == "Vector3"'. They're exactly identical.
// This allows us to avoid writing in edge cases.
if (!typeguardP.isTypeof && typeguardP.kind == "vector")
return resolve(TypeGuardPredicate{std::move(typeguardP.lvalue), typeguardP.location, "Vector3", true}, errVec, refis, scope, sense);
return resolve(TypeGuardPredicate{std::move(typeguardP.lvalue), typeguardP.location, "Vector3", true}, refis, scope, sense);
std::optional<TypeId> ty = resolveLValue(refis, scope, typeguardP.lvalue);
if (!ty)
@ -6060,52 +5800,29 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
{
if (FFlag::LuauDiscriminableUnions2)
{
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return;
}
else
{
if (std::optional<TypeId> result = filterMap(*ty, it->second(sense)))
addRefinement(refis, typeguardP.lvalue, *result);
else
{
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
if (sense)
errVec.push_back(
TypeError{typeguardP.location, GenericError{"Type '" + toString(*ty) + "' has no overlap with '" + typeguardP.kind + "'"}});
}
return;
}
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return;
}
auto fail = [&](const TypeErrorData& err) {
if (!FFlag::LuauDiscriminableUnions2)
errVec.push_back(TypeError{typeguardP.location, err});
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
};
if (!typeguardP.isTypeof)
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
auto typeFun = globalScope->lookupType(typeguardP.kind);
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
TypeId type = follow(typeFun->type);
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassTypeVar>(type); !ctv || ctv->parent)
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.
// Until then, we rewrite this to be the same as using IsA.
return resolve(IsAPredicate{std::move(typeguardP.lvalue), typeguardP.location, type}, errVec, refis, scope, sense);
return resolve(IsAPredicate{std::move(typeguardP.lvalue), typeguardP.location, type}, refis, scope, sense);
}
void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense)
void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
// This refinement will require success typing to do everything correctly. For now, we can get most of the way there.
auto options = [](TypeId ty) -> std::vector<TypeId> {
@ -6114,82 +5831,33 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
return {ty};
};
if (FFlag::LuauDiscriminableUnions2)
{
std::vector<TypeId> rhs = options(eqP.type);
std::vector<TypeId> rhs = options(eqP.type);
if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable))
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable))
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
if (sense && isUndecidable(option))
return FFlag::LuauWeakEqConstraint ? option : eqP.type;
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
if (sense && isUndecidable(option))
return FFlag::LuauWeakEqConstraint ? option : eqP.type;
if (!sense && isNil(eqP.type))
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;
if (!sense && isNil(eqP.type))
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;
if (maybeSingleton(eqP.type))
{
// Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this.
if (!sense || canUnify(eqP.type, option, eqP.location).empty())
return sense ? eqP.type : option;
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
}
return option;
};
refineLValue(eqP.lvalue, refis, scope, predicate);
}
else
{
if (FFlag::LuauWeakEqConstraint)
if (maybeSingleton(eqP.type))
{
if (!sense && isNil(eqP.type))
resolve(TruthyPredicate{std::move(eqP.lvalue), eqP.location}, errVec, refis, scope, true, /* fromOr= */ false);
// Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this.
if (!sense || canUnify(eqP.type, option, eqP.location).empty())
return sense ? eqP.type : option;
return;
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
}
if (FFlag::LuauEqConstraint)
{
std::optional<TypeId> ty = resolveLValue(refis, scope, eqP.lvalue);
if (!ty)
return;
return option;
};
std::vector<TypeId> lhs = options(*ty);
std::vector<TypeId> rhs = options(eqP.type);
if (sense && std::any_of(lhs.begin(), lhs.end(), isUndecidable))
{
addRefinement(refis, eqP.lvalue, eqP.type);
return;
}
else if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable))
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
std::unordered_set<TypeId> set;
for (TypeId left : lhs)
{
for (TypeId right : rhs)
{
// When singleton types arrive, `isNil` here probably should be replaced with `isLiteral`.
if (canUnify(right, left, eqP.location).empty() == sense || (!sense && !isNil(left)))
set.insert(left);
}
}
if (set.empty())
return;
std::vector<TypeId> viable(set.begin(), set.end());
TypeId result = viable.size() == 1 ? viable[0] : addType(UnionTypeVar{std::move(viable)});
addRefinement(refis, eqP.lvalue, result);
}
}
refineLValue(eqP.lvalue, refis, scope, predicate);
}
bool TypeChecker::isNonstrictMode() const

View file

@ -5,8 +5,6 @@
#include "Luau/ToString.h"
#include "Luau/TypeInfer.h"
LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
namespace Luau
{
@ -55,13 +53,10 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
{
TypeId index = follow(*mtIndex);
if (FFlag::LuauTerminateCyclicMetatableIndexLookup)
{
if (count >= 100)
return std::nullopt;
if (count >= 100)
return std::nullopt;
++count;
}
++count;
if (const auto& itt = getTableType(index))
{

View file

@ -24,8 +24,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
LUAU_FASTFLAG(LuauDiscriminableUnions2)
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
namespace Luau
@ -204,14 +202,14 @@ bool isOptional(TypeId ty)
ty = follow(ty);
if (FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
if (get<AnyTypeVar>(ty))
return true;
auto utv = get<UnionTypeVar>(ty);
if (!utv)
return false;
return std::any_of(begin(utv), end(utv), FFlag::LuauAnyInIsOptionalIsOptional ? isOptional : isNil);
return std::any_of(begin(utv), end(utv), isOptional);
}
bool isTableIntersection(TypeId ty)
@ -378,8 +376,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
if (seen.contains(ty))
return true;
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
if (isString(ty) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
return true;
if (auto uty = get<UnionTypeVar>(ty))

View file

@ -24,8 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -382,19 +380,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
LUAU_ASSERT(sharedState.iceHandler);
}
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
: types(types)
, mode(mode)
, log(parentLog, sharedSeen)
, location(location)
, variance(variance)
, sharedState(sharedState)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
LUAU_ASSERT(sharedState.iceHandler);
}
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
{
sharedState.counters.iterationCount = 0;
@ -1219,14 +1204,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
continue;
}
// In nonstrict mode, any also marks an optional argument.
else if (!FFlag::LuauAnyInIsOptionalIsOptional && superIter.good() && isNonstrictMode() &&
log.getMutable<AnyTypeVar>(log.follow(*superIter)))
{
superIter.advance();
continue;
}
if (log.getMutable<VariadicTypePack>(superIter.packId))
{
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
@ -1454,21 +1431,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto subIter = subTable->props.find(propName);
if (FFlag::LuauAnyInIsOptionalIsOptional)
{
if (subIter == subTable->props.end() &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type))
missingProperties.push_back(propName);
}
else
{
bool isAny = log.getMutable<AnyTypeVar>(log.follow(superProp.type));
if (subIter == subTable->props.end() &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) &&
!isAny)
missingProperties.push_back(propName);
}
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
!isOptional(superProp.type))
missingProperties.push_back(propName);
}
if (!missingProperties.empty())
@ -1485,18 +1450,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto superIter = superTable->props.find(propName);
if (FFlag::LuauAnyInIsOptionalIsOptional)
{
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
extraProperties.push_back(propName);
}
else
{
bool isAny = log.is<AnyTypeVar>(log.follow(subProp.type));
if (superIter == superTable->props.end() &&
(FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny)))
extraProperties.push_back(propName);
}
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
extraProperties.push_back(propName);
}
if (!extraProperties.empty())
@ -1540,21 +1495,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
if (innerState.errors.empty())
log.concat(std::move(innerState.log));
}
else if (FFlag::LuauAnyInIsOptionalIsOptional &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
{
}
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
(isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: should isOptional(anyType) be true?
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
{
}
else if (subTable->state == TableState::Free)
{
PendingType* pendingSub = log.queue(subTy);
@ -1618,10 +1564,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
else if (variance == Covariant)
{
}
else if (FFlag::LuauAnyInIsOptionalIsOptional && !FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
{
}
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
{
}
else if (superTable->state == TableState::Free)
@ -1753,9 +1696,7 @@ TypePackId Unifier::widen(TypePackId tp)
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
{
ty = follow(ty);
if (!FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
return ty;
else if (isOptional(ty))
if (isOptional(ty))
return ty;
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
@ -2666,14 +2607,7 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
Unifier Unifier::makeChildUnifier()
{
if (FFlag::LuauTypecheckOptPass)
{
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop;
return u;
}
Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop;
return u;
}

View file

@ -224,6 +224,7 @@ private:
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
DenseHashMap<uint32_t, int16_t> protoMap;
int debugLine = 0;

View file

@ -6,6 +6,8 @@
#include <algorithm>
#include <string.h>
LUAU_FASTFLAG(LuauCompileNestedClosureO2)
namespace Luau
{
@ -181,6 +183,7 @@ size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
: constantMap({Constant::Type_Nil, ~0ull})
, tableShapeMap(TableShape())
, protoMap(~0u)
, stringTable({nullptr, 0})
, encoder(encoder)
{
@ -250,6 +253,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
constantMap.clear();
tableShapeMap.clear();
protoMap.clear();
debugRemarks.clear();
debugRemarkBuffer.clear();
@ -372,11 +376,17 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid)
int16_t BytecodeBuilder::addChildFunction(uint32_t fid)
{
if (FFlag::LuauCompileNestedClosureO2)
if (int16_t* cache = protoMap.find(fid))
return *cache;
uint32_t id = uint32_t(protos.size());
if (id >= kMaxClosureCount)
return -1;
if (FFlag::LuauCompileNestedClosureO2)
protoMap[fid] = int16_t(id);
protos.push_back(fid);
return int16_t(id);

View file

@ -17,8 +17,6 @@
#include <math.h>
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileSupportInlining, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
@ -30,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileNestedClosureO2, false)
namespace Luau
{
@ -100,13 +100,11 @@ struct Compiler
upvals.reserve(16);
}
uint8_t getLocal(AstLocal* local)
int getLocalReg(AstLocal* local)
{
Local* l = locals.find(local);
LUAU_ASSERT(l);
LUAU_ASSERT(l->allocated);
return l->reg;
return l && l->allocated ? l->reg : -1;
}
uint8_t getUpval(AstLocal* local)
@ -159,17 +157,19 @@ struct Compiler
AstExprFunction* getFunctionExpr(AstExpr* node)
{
if (AstExprLocal* le = node->as<AstExprLocal>())
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
Variable* lv = variables.find(le->local);
Variable* lv = variables.find(expr->local);
if (!lv || lv->written || !lv->init)
return nullptr;
return getFunctionExpr(lv->init);
}
else if (AstExprGroup* ge = node->as<AstExprGroup>())
return getFunctionExpr(ge->expr);
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return getFunctionExpr(expr->expr);
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return getFunctionExpr(expr->expr);
else
return node->as<AstExprFunction>();
}
@ -180,13 +180,13 @@ struct Compiler
{
bool result = true;
bool visit(AstExpr* node) override
bool visit(AstExprFunction* node) override
{
// nested functions may capture function arguments, and our upval handling doesn't handle elided variables (constant)
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
// TODO: additionally we would need to change upvalue handling in compileExprFunction to handle upvalue->local migration
result = result && !node->is<AstExprFunction>();
return result;
if (!FFlag::LuauCompileNestedClosureO2)
result = false;
// short-circuit to avoid analyzing nested closure bodies
return false;
}
bool visit(AstStat* node) override
@ -275,8 +275,7 @@ struct Compiler
f.upvals = upvals;
// record information for inlining
if (FFlag::LuauCompileSupportInlining && options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) &&
!getfenvUsed && !setfenvUsed)
if (options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && !getfenvUsed && !setfenvUsed)
{
f.canInline = true;
f.stackSize = stackSize;
@ -346,8 +345,8 @@ struct Compiler
uint8_t argreg;
if (isExprLocalReg(arg))
argreg = getLocal(arg->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(arg); reg >= 0)
argreg = uint8_t(reg);
else
{
argreg = uint8_t(regs + 1);
@ -403,8 +402,8 @@ struct Compiler
}
}
if (isExprLocalReg(expr->args.data[i]))
args[i] = getLocal(expr->args.data[i]->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
args[i] = uint8_t(reg);
else
{
args[i] = uint8_t(regs + 1 + i);
@ -489,19 +488,18 @@ struct Compiler
return false;
}
// TODO: we can compile functions with mismatching arity at call site but it's more annoying
if (func->args.size != expr->args.size)
{
bytecode.addDebugRemark("inlining failed: argument count mismatch (expected %d, got %d)", int(func->args.size), int(expr->args.size));
return false;
}
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
// compute constant bitvector for all arguments to feed the cost model
bool varc[8] = {};
for (size_t i = 0; i < expr->args.size && i < 8; ++i)
for (size_t i = 0; i < func->args.size && i < expr->args.size && i < 8; ++i)
varc[i] = isConstant(expr->args.data[i]);
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(expr->args.size), 8));
// if the last argument only returns a single value, all following arguments are nil
if (expr->args.size != 0 && !(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>()))
for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i)
varc[i] = true;
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(func->args.size), 8));
int baselineCost = computeCost(fi->costModel, nullptr, 0) + 3;
int inlineProfit = (inlinedCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / inlinedCost);
@ -533,15 +531,44 @@ struct Compiler
for (size_t i = 0; i < func->args.size; ++i)
{
AstLocal* var = func->args.data[i];
AstExpr* arg = expr->args.data[i];
AstExpr* arg = i < expr->args.size ? expr->args.data[i] : nullptr;
if (Variable* vv = variables.find(var); vv && vv->written)
if (i + 1 == expr->args.size && func->args.size > expr->args.size && (arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{
// if the last argument can return multiple values, we need to compute all of them into the remaining arguments
unsigned int tail = unsigned(func->args.size - expr->args.size) + 1;
uint8_t reg = allocReg(arg, tail);
if (AstExprCall* expr = arg->as<AstExprCall>())
compileExprCall(expr, reg, tail, /* targetTop= */ true);
else if (AstExprVarargs* expr = arg->as<AstExprVarargs>())
compileExprVarargs(expr, reg, tail);
else
LUAU_ASSERT(!"Unexpected expression type");
for (size_t j = i; j < func->args.size; ++j)
pushLocal(func->args.data[j], uint8_t(reg + (j - i)));
// all remaining function arguments have been allocated and assigned to
break;
}
else if (Variable* vv = variables.find(var); vv && vv->written)
{
// if the argument is mutated, we need to allocate a fresh register even if it's a constant
uint8_t reg = allocReg(arg, 1);
compileExprTemp(arg, reg);
if (arg)
compileExprTemp(arg, reg);
else
bytecode.emitABC(LOP_LOADNIL, reg, 0, 0);
pushLocal(var, reg);
}
else if (arg == nullptr)
{
// since the argument is not mutated, we can simply fold the value into the expressions that need it
locstants[var] = {Constant::Type_Nil};
}
else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown)
{
// since the argument is not mutated, we can simply fold the value into the expressions that need it
@ -553,20 +580,26 @@ struct Compiler
Variable* lv = le ? variables.find(le->local) : nullptr;
// if the argument is a local that isn't mutated, we will simply reuse the existing register
if (isExprLocalReg(arg) && (!lv || !lv->written))
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
{
uint8_t reg = getLocal(le->local);
pushLocal(var, reg);
pushLocal(var, uint8_t(reg));
}
else
{
uint8_t reg = allocReg(arg, 1);
compileExprTemp(arg, reg);
pushLocal(var, reg);
uint8_t temp = allocReg(arg, 1);
compileExprTemp(arg, temp);
pushLocal(var, temp);
}
}
}
// evaluate extra expressions for side effects
for (size_t i = func->args.size; i < expr->args.size; ++i)
{
RegScope rsi(this);
compileExprAuto(expr->args.data[i], rsi);
}
// fold constant values updated above into expressions in the function body
foldConstants(constants, variables, locstants, func->body);
@ -627,12 +660,15 @@ struct Compiler
FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth))
return;
if (fi && !fi->canInline)
// add a debug remark for cases when we didn't even call tryCompileInlinedCall
if (func && !(fi && fi->canInline))
{
if (func->vararg)
bytecode.addDebugRemark("inlining failed: function is variadic");
else
else if (fi)
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
else
bytecode.addDebugRemark("inlining failed: can't inline recursive calls");
}
}
@ -677,9 +713,9 @@ struct Compiler
LUAU_ASSERT(fi);
// Optimization: use local register directly in NAMECALL if possible
if (isExprLocalReg(fi->expr))
if (int reg = getExprLocalReg(fi->expr); reg >= 0)
{
selfreg = getLocal(fi->expr->as<AstExprLocal>()->local);
selfreg = uint8_t(reg);
}
else
{
@ -785,6 +821,8 @@ struct Compiler
void compileExprFunction(AstExprFunction* expr, uint8_t target)
{
RegScope rs(this);
const Function* f = functions.find(expr);
LUAU_ASSERT(f);
@ -795,6 +833,67 @@ struct Compiler
if (pid < 0)
CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile");
if (FFlag::LuauCompileNestedClosureO2)
{
captures.clear();
captures.reserve(f->upvals.size());
for (AstLocal* uv : f->upvals)
{
LUAU_ASSERT(uv->functionDepth < expr->functionDepth);
if (int reg = getLocalReg(uv); reg >= 0)
{
// note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals
Variable* ul = variables.find(uv);
bool immutable = !ul || !ul->written;
captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)});
}
else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown)
{
// inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register
uint8_t reg = allocReg(expr, 1);
compileExprConstant(expr, uc, reg);
captures.push_back({LCT_VAL, reg});
}
else
{
LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1);
// get upvalue from parent frame
// note: this will add uv to the current upvalue list if necessary
uint8_t uid = getUpval(uv);
captures.push_back({LCT_UPVAL, uid});
}
}
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
// is used)
int16_t shared = -1;
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
{
int32_t cid = bytecode.addConstantClosure(f->id);
if (cid >= 0 && cid < 32768)
shared = int16_t(cid);
}
if (shared >= 0)
bytecode.emitAD(LOP_DUPCLOSURE, target, shared);
else
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
for (const Capture& c : captures)
bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0);
return;
}
bool shared = false;
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
@ -824,9 +923,10 @@ struct Compiler
if (uv->functionDepth == expr->functionDepth - 1)
{
// get local variable
uint8_t reg = getLocal(uv);
int reg = getLocalReg(uv);
LUAU_ASSERT(reg >= 0);
bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), reg, 0);
bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), uint8_t(reg), 0);
}
else
{
@ -1213,10 +1313,10 @@ struct Compiler
if (!isConditionFast(expr->left))
{
// Optimization: when right hand side is a local variable, we can use AND/OR
if (isExprLocalReg(expr->right))
if (int reg = getExprLocalReg(expr->right); reg >= 0)
{
uint8_t lr = compileExprAuto(expr->left, rs);
uint8_t rr = getLocal(expr->right->as<AstExprLocal>()->local);
uint8_t rr = uint8_t(reg);
bytecode.emitABC(and_ ? LOP_AND : LOP_OR, target, lr, rr);
return;
@ -1803,19 +1903,18 @@ struct Compiler
}
else if (AstExprLocal* expr = node->as<AstExprLocal>())
{
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
if (int reg = getExprLocalReg(expr); reg >= 0)
{
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
}
else
{
LUAU_ASSERT(expr->upvalue);
uint8_t uid = getUpval(expr->local);
bytecode.emitABC(LOP_GETUPVAL, target, uid, 0);
}
else
{
uint8_t reg = getLocal(expr->local);
bytecode.emitABC(LOP_MOVE, target, reg, 0);
}
}
else if (AstExprGlobal* expr = node->as<AstExprGlobal>())
{
@ -1879,8 +1978,8 @@ struct Compiler
uint8_t compileExprAuto(AstExpr* node, RegScope&)
{
// Optimization: directly return locals instead of copying them to a temporary
if (isExprLocalReg(node))
return getLocal(node->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(node); reg >= 0)
return uint8_t(reg);
// note: the register is owned by the parent scope
uint8_t reg = allocReg(node, 1);
@ -1910,7 +2009,7 @@ struct Compiler
for (size_t i = 0; i < targetCount; ++i)
compileExprTemp(list.data[i], uint8_t(target + i));
// compute expressions with values that go nowhere; this is required to run side-effecting code if any
// evaluate extra expressions for side effects
for (size_t i = targetCount; i < list.size; ++i)
{
RegScope rsi(this);
@ -2008,20 +2107,21 @@ struct Compiler
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
if (int reg = getExprLocalReg(expr); reg >= 0)
{
LUAU_ASSERT(expr->upvalue);
LValue result = {LValue::Kind_Upvalue};
result.upval = getUpval(expr->local);
LValue result = {LValue::Kind_Local};
result.reg = uint8_t(reg);
result.location = node->location;
return result;
}
else
{
LValue result = {LValue::Kind_Local};
result.reg = getLocal(expr->local);
LUAU_ASSERT(expr->upvalue);
LValue result = {LValue::Kind_Upvalue};
result.upval = getUpval(expr->local);
result.location = node->location;
return result;
@ -2115,15 +2215,21 @@ struct Compiler
compileLValueUse(lv, source, /* set= */ true);
}
bool isExprLocalReg(AstExpr* expr)
int getExprLocalReg(AstExpr* node)
{
AstExprLocal* le = expr->as<AstExprLocal>();
if (!le || (!FFlag::LuauCompileSupportInlining && le->upvalue))
return false;
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
Local* l = locals.find(expr->local);
Local* l = locals.find(le->local);
return l && l->allocated;
return l && l->allocated ? l->reg : -1;
}
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return getExprLocalReg(expr->expr);
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return getExprLocalReg(expr->expr);
else
return -1;
}
bool isStatBreak(AstStat* node)
@ -2352,20 +2458,17 @@ struct Compiler
// Optimization: return locals directly instead of copying them into a temporary
// this is very important for a single return value and occasionally effective for multiple values
if (stat->list.size > 0 && isExprLocalReg(stat->list.data[0]))
if (int reg = stat->list.size > 0 ? getExprLocalReg(stat->list.data[0]) : -1; reg >= 0)
{
temp = getLocal(stat->list.data[0]->as<AstExprLocal>()->local);
temp = uint8_t(reg);
consecutive = true;
for (size_t i = 1; i < stat->list.size; ++i)
{
AstExpr* v = stat->list.data[i];
if (!isExprLocalReg(v) || getLocal(v->as<AstExprLocal>()->local) != temp + i)
if (getExprLocalReg(stat->list.data[i]) != int(temp + i))
{
consecutive = false;
break;
}
}
}
if (!consecutive && stat->list.size > 0)
@ -2438,12 +2541,13 @@ struct Compiler
{
bool result = true;
bool visit(AstExpr* node) override
bool visit(AstExprFunction* node) override
{
// functions may capture loop variable, and our upval handling doesn't handle elided variables (constant)
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
result = result && !node->is<AstExprFunction>();
return result;
if (!FFlag::LuauCompileNestedClosureO2)
result = false;
// short-circuit to avoid analyzing nested closure bodies
return false;
}
bool visit(AstStat* node) override
@ -2874,12 +2978,9 @@ struct Compiler
void compileStatFunction(AstStatFunction* stat)
{
// Optimization: compile value expresion directly into target local register
if (isExprLocalReg(stat->name))
if (int reg = getExprLocalReg(stat->name); reg >= 0)
{
AstExprLocal* le = stat->name->as<AstExprLocal>();
LUAU_ASSERT(le);
compileExpr(stat->func, getLocal(le->local));
compileExpr(stat->func, uint8_t(reg));
return;
}
@ -3399,6 +3500,12 @@ struct Compiler
std::vector<size_t> returnJumps;
};
struct Capture
{
LuauCaptureType type;
uint8_t data;
};
BytecodeBuilder& bytecode;
CompileOptions options;
@ -3422,6 +3529,7 @@ struct Compiler
std::vector<LoopJump> loopJumps;
std::vector<Loop> loops;
std::vector<InlineFrame> inlineFrames;
std::vector<Capture> captures;
};
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options)
@ -3465,6 +3573,9 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName());
uint32_t mainid = compiler.compileFunction(&main);
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid);
bytecode.finalize();
}

View file

@ -3,8 +3,6 @@
#include <math.h>
LUAU_FASTFLAG(LuauCompileSupportInlining)
namespace Luau
{
namespace Compile
@ -330,7 +328,7 @@ struct ConstantVisitor : AstVisitor
{
if (value.type != Constant::Type_Unknown)
map[key] = value;
else if (!FFlag::LuauCompileSupportInlining || wasEmpty)
else if (wasEmpty)
;
else if (Constant* old = map.find(key))
old->type = Constant::Type_Unknown;

View file

@ -73,6 +73,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/ToString.h
Analysis/include/Luau/Transpiler.h
Analysis/include/Luau/TxnLog.h
Analysis/include/Luau/TypeArena.h
Analysis/include/Luau/TypeAttach.h
Analysis/include/Luau/TypedAllocator.h
Analysis/include/Luau/TypeInfer.h
@ -108,6 +109,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/ToString.cpp
Analysis/src/Transpiler.cpp
Analysis/src/TxnLog.cpp
Analysis/src/TypeArena.cpp
Analysis/src/TypeAttach.cpp
Analysis/src/TypedAllocator.cpp
Analysis/src/TypeInfer.cpp

View file

@ -10,10 +10,6 @@
#include "ldebug.h"
#include "lvm.h"
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauTableMoveTelemetry2, false)
void (*lua_table_move_telemetry)(lua_State* L, int f, int e, int t, int nf, int nt);
static int foreachi(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
@ -199,29 +195,6 @@ static int tmove(lua_State* L)
int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */
luaL_checktype(L, tt, LUA_TTABLE);
void (*telemetrycb)(lua_State * L, int f, int e, int t, int nf, int nt) = lua_table_move_telemetry;
if (DFFlag::LuauTableMoveTelemetry2 && telemetrycb && e >= f)
{
int nf = lua_objlen(L, 1);
int nt = lua_objlen(L, tt);
bool report = false;
// source index range must be in bounds in source table unless the table is empty (permits 1..#t moves)
if (!(f == 1 || (f >= 1 && f <= nf)))
report = true;
if (!(e == nf || (e >= 1 && e <= nf)))
report = true;
// destination index must be in bounds in dest table or be exactly at the first empty element (permits concats)
if (!(t == nt + 1 || (t >= 1 && t <= nt)))
report = true;
if (report)
telemetrycb(L, f, e, t, nf, nt);
}
if (e >= f)
{ /* otherwise, nothing to move */
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");

View file

@ -17,9 +17,6 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauIter, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauIterCallTelemetry, false)
void (*lua_iter_call_telemetry)(lua_State* L);
// Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__
@ -157,17 +154,6 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c)
StkId ra = &L->base[a];
LUAU_ASSERT(ra + 3 <= L->top);
if (DFFlag::LuauIterCallTelemetry)
{
/* TODO: we might be able to stop supporting this depending on whether it's used in practice */
void (*telemetrycb)(lua_State* L) = lua_iter_call_telemetry;
if (telemetrycb && ttistable(ra) && fasttm(L, hvalue(ra)->metatable, TM_CALL))
telemetrycb(L);
if (telemetrycb && ttisuserdata(ra) && fasttm(L, uvalue(ra)->metatable, TM_CALL))
telemetrycb(L);
}
setobjs2s(L, ra + 3 + 2, ra + 2);
setobjs2s(L, ra + 3 + 1, ra + 1);
setobjs2s(L, ra + 3, ra);

View file

@ -2772,6 +2772,8 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
{
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
check(R"(
type tag = "cat" | "dog"
local function f(a: tag) end
@ -2844,6 +2846,8 @@ f(@1)
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape")
{
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
check(R"(
type tag = "strange\t\"cat\"" | 'nice\t"dog"'
local function f(x: tag) end

View file

@ -4269,22 +4269,26 @@ FORNLOOP R3 -6
FORNLOOP R0 -11
RETURN R0 0
)");
}
// can't unroll loops if the body has functions that refer to loop variables
TEST_CASE("LoopUnrollNestedClosure")
{
ScopedFastFlag sff("LuauCompileNestedClosureO2", true);
// if the body has functions that refer to loop variables, we unroll the loop and use MOVE+CAPTURE for upvalues
CHECK_EQ("\n" + compileFunction(R"(
for i=1,1 do
for i=1,2 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
NEWCLOSURE R0 P0
CAPTURE VAL R1
LOADN R1 2
NEWCLOSURE R0 P0
CAPTURE VAL R1
RETURN R0 0
)");
}
@ -4469,8 +4473,6 @@ RETURN R0 0
TEST_CASE("InlineBasic")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// inline function that returns a constant
CHECK_EQ("\n" + compileFunction(R"(
local function foo()
@ -4550,10 +4552,72 @@ RETURN R1 1
)");
}
TEST_CASE("InlineBasicProhibited")
{
ScopedFastFlag sff("LuauCompileNestedClosureO2", true);
// we can't inline variadic functions
CHECK_EQ("\n" + compileFunction(R"(
local function foo(...)
return 42
end
local x = foo()
return x
)",
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
CALL R1 0 1
RETURN R1 1
)");
// we also can't inline functions that have internal loops
CHECK_EQ("\n" + compileFunction(R"(
local function foo()
for i=1,4 do end
end
local x = foo()
return x
)",
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
CALL R1 0 1
RETURN R1 1
)");
}
TEST_CASE("InlineNestedClosures")
{
ScopedFastFlag sff("LuauCompileNestedClosureO2", true);
// we can inline functions that contain/return functions
CHECK_EQ("\n" + compileFunction(R"(
local function foo(x)
return function(y) return x + y end
end
local x = foo(1)(2)
return x
)",
2, 2),
R"(
DUPCLOSURE R0 K0
LOADN R2 1
NEWCLOSURE R1 P1
CAPTURE VAL R2
LOADN R2 2
CALL R1 1 1
RETURN R1 1
)");
}
TEST_CASE("InlineMutate")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// if the argument is mutated, it gets a register even if the value is constant
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
@ -4636,8 +4700,6 @@ RETURN R1 1
TEST_CASE("InlineUpval")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// if the argument is an upvalue, we naturally need to copy it to a local
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
@ -4705,8 +4767,6 @@ RETURN R1 1
TEST_CASE("InlineFallthrough")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// if the function doesn't return, we still fill the results with nil
CHECK_EQ("\n" + compileFunction(R"(
local function foo()
@ -4759,8 +4819,6 @@ RETURN R1 -1
TEST_CASE("InlineCapture")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// can't inline function with nested functions that capture locals because they might be constants
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
@ -4782,12 +4840,9 @@ RETURN R2 -1
TEST_CASE("InlineArgMismatch")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// when inlining a function, we must respect all the usual rules
// caller might not have enough arguments
// TODO: we don't inline this atm
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
return a
@ -4799,13 +4854,11 @@ return x
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
CALL R1 0 1
LOADNIL R1
RETURN R1 1
)");
// caller might be using multret for arguments
// TODO: we don't inline this atm
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a, b)
return a + b
@ -4817,17 +4870,32 @@ return x
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
LOADK R3 K1
FASTCALL1 20 R3 +2
GETIMPORT R2 4
CALL R2 1 -1
CALL R1 -1 1
CALL R2 1 2
ADD R1 R2 R3
RETURN R1 1
)");
// caller might be using varargs for arguments
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a, b)
return a + b
end
local x = foo(...)
return x
)",
1, 2),
R"(
DUPCLOSURE R0 K0
GETVARARGS R2 2
ADD R1 R2 R3
RETURN R1 1
)");
// caller might have too many arguments, but we still need to compute them for side effects
// TODO: we don't inline this atm
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
return a
@ -4839,19 +4907,34 @@ return x
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
GETIMPORT R2 2
CALL R2 0 1
LOADN R1 42
RETURN R1 1
)");
// caller might not have enough arguments, and the arg might be mutated so it needs a register
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
a = 42
return a
end
local x = foo()
return x
)",
1, 2),
R"(
DUPCLOSURE R0 K0
LOADNIL R2
LOADN R2 42
GETIMPORT R3 2
CALL R3 0 -1
CALL R1 -1 1
MOVE R1 R2
RETURN R1 1
)");
}
TEST_CASE("InlineMultiple")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// we call this with a different set of variable/constant args
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a, b)
@ -4880,8 +4963,6 @@ RETURN R3 4
TEST_CASE("InlineChain")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// inline a chain of functions
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a, b)
@ -4912,8 +4993,6 @@ RETURN R3 1
TEST_CASE("InlineThresholds")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
ScopedFastInt sfis[] = {
{"LuauCompileInlineThreshold", 25},
{"LuauCompileInlineThresholdMaxBoost", 300},
@ -4988,8 +5067,6 @@ RETURN R3 1
TEST_CASE("InlineIIFE")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// IIFE with arguments
CHECK_EQ("\n" + compileFunction(R"(
function choose(a, b, c)
@ -5025,8 +5102,6 @@ RETURN R3 1
TEST_CASE("InlineRecurseArguments")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
// we can't inline a function if it's used to compute its own arguments
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a, b)
@ -5036,22 +5111,20 @@ foo(foo(foo,foo(foo,foo))[foo])
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
MOVE R2 R0
MOVE R3 R0
MOVE R4 R0
MOVE R5 R0
MOVE R6 R0
CALL R4 2 1
LOADNIL R3
GETTABLE R2 R3 R0
CALL R1 1 0
CALL R4 2 -1
CALL R2 -1 1
GETTABLE R1 R2 R0
RETURN R0 0
)");
}
TEST_CASE("InlineFastCallK")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
CHECK_EQ("\n" + compileFunction(R"(
local function set(l0)
rawset({}, l0)
@ -5080,8 +5153,6 @@ RETURN R0 0
TEST_CASE("InlineExprIndexK")
{
ScopedFastFlag sff("LuauCompileSupportInlining", true);
CHECK_EQ("\n" + compileFunction(R"(
local _ = function(l0)
local _ = nil
@ -5141,6 +5212,58 @@ RETURN R0 0
)");
}
TEST_CASE("InlineHiddenMutation")
{
// when the argument is assigned inside the function, we can't reuse the local
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
a = 42
return a
end
local x = ...
local y = foo(x :: number)
return y
)",
1, 2),
R"(
DUPCLOSURE R0 K0
GETVARARGS R1 1
MOVE R3 R1
LOADN R3 42
MOVE R2 R3
RETURN R2 1
)");
// and neither can we do that when it's assigned outside the function
CHECK_EQ("\n" + compileFunction(R"(
local function foo(a)
mutator()
return a
end
local x = ...
mutator = function() x = 42 end
local y = foo(x :: number)
return y
)",
2, 2),
R"(
DUPCLOSURE R0 K0
GETVARARGS R1 1
NEWCLOSURE R2 P1
CAPTURE REF R1
SETGLOBAL R2 K1
MOVE R3 R1
GETGLOBAL R4 K1
CALL R4 0 0
MOVE R2 R3
CLOSEUPVALS R1
RETURN R2 1
)");
}
TEST_CASE("ReturnConsecutive")
{
// we can return a single local directly
@ -5193,6 +5316,16 @@ return
)"),
R"(
RETURN R0 0
)");
// this optimization also works in presence of group / type casts
CHECK_EQ("\n" + compileFunction0(R"(
local x, y = ...
return (x), y :: number
)"),
R"(
GETVARARGS R0 2
RETURN R0 2
)");
}

View file

@ -198,10 +198,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
{
ScopedFastFlag sff[]{
{"LuauLosslessClone", true},
};
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
@ -218,8 +214,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types")
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
{
ScopedFastFlag sff{"LuauLosslessClone", true};
TypeVar tableTy{TableTypeVar{}};
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
ttv->state = TableState::Free;
@ -252,8 +246,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")
{
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
fileResolver.source["Module/A"] = R"(
--!nonstrict
local a = {}

View file

@ -150,8 +150,6 @@ TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional")
TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
{
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
CheckResult result = check(R"(
--!nonstrict
local T = {}
@ -169,8 +167,6 @@ TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon")
{
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
CheckResult result = check(R"(
--!nonstrict
local T = {}

View file

@ -683,6 +683,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
{
ScopedFastFlag flags[] = {
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", false}
};
check(R"(
@ -697,6 +698,26 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
CHECK(t->normal);
}
// Unfortunately, getting this right in the general case is difficult.
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal")
{
ScopedFastFlag flags[] = {
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", true}
};
check(R"(
type Fiber = {
return_: Fiber?
}
local f: Fiber
)");
TypeId t = requireType("f");
CHECK(!t->normal);
}
TEST_CASE_FIXTURE(Fixture, "variadic_tail_is_marked_normal")
{
ScopedFastFlag flags[] = {
@ -997,4 +1018,28 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounde
LUAU_REQUIRE_ERRORS(result);
}
// We had an issue where a normal BoundTypeVar might point at a non-normal BoundTypeVar if it in turn pointed to a
// normal TypeVar because we were calling follow() in an improper place.
TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their_pointee_is_normal")
{
ScopedFastFlag sff[]{
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", true},
};
CheckResult result = check(R"(
local T = {}
function T:M()
local function f(a)
print(self.prop)
self:g(a)
self.prop = a
end
end
return T
)");
}
TEST_SUITE_END();

View file

@ -22,8 +22,6 @@ struct LimitFixture : BuiltinsFixture
#if defined(_NOOPT) || defined(_DEBUG)
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
#endif
ScopedFastFlag LuauJustOneCallFrameForHaveSeen{"LuauJustOneCallFrameForHaveSeen", true};
};
template <typename T>

View file

@ -650,6 +650,19 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names")
CHECK_EQ("test<a>(first: a, second: string, ...: number): a", toStringNamedFunction("test", *ftv, opts));
}
TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_generics")
{
ScopedFastFlag sff[] = {
{"LuauAlwaysQuantify", true},
};
CheckResult result = check(R"(
function foo<a>(x: a, y) end
)");
CHECK("<a, b>(a, b) -> ()" == toString(requireType("foo")));
}
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
@ -685,5 +698,4 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
}
TEST_SUITE_END();

View file

@ -878,8 +878,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
{
ScopedFastFlag sff[]{
{"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true},
};
@ -899,8 +897,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
{
ScopedFastFlag sff[]{
{"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true},
};
@ -916,11 +912,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
{
ScopedFastFlag sff[]{
{"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local function f(...: number?)
return assert(...)
@ -933,11 +924,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pa
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy")
{
ScopedFastFlag sff[]{
{"LuauAssertStripsFalsyTypes", true},
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local function f(x: nil)
return assert(x, "hmm")

View file

@ -1496,8 +1496,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
{
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
CheckResult result = check(R"(
local function f(x: any) end
f()

View file

@ -1121,4 +1121,78 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1")
{
ScopedFastFlag sff{"LuauApplyTypeFunctionFix", true};
// https://github.com/Roblox/luau/issues/484
CheckResult result = check(R"(
--!strict
type MyObject = {
getReturnValue: <V>(cb: () -> V) -> V
}
local object: MyObject = {
getReturnValue = function<U>(cb: () -> U): U
return cb()
end,
}
type ComplexObject<T> = {
id: T,
nested: MyObject
}
local complex: ComplexObject<string> = {
id = "Foo",
nested = object,
}
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics2")
{
ScopedFastFlag sff{"LuauApplyTypeFunctionFix", true};
// https://github.com/Roblox/luau/issues/484
CheckResult result = check(R"(
--!strict
type MyObject = {
getReturnValue: <V>(cb: () -> V) -> V
}
type ComplexObject<T> = {
id: T,
nested: MyObject
}
local complex2: ComplexObject<string> = nil
local x = complex2.nested.getReturnValue(function(): string
return ""
end)
local y = complex2.nested.getReturnValue(function()
return 3
end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic")
{
ScopedFastFlag sff[] = {
{"LuauAlwaysQuantify", true},
};
CheckResult result = check(R"(
function foo<X>(f, x: X)
return f(x)
end
)");
CHECK("<X, a...>((X) -> (a...), X) -> (a...)" == toString(requireType("foo")));
}
TEST_SUITE_END();

View file

@ -177,8 +177,6 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
{
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
CheckResult result = check(R"(
type A = {x: {y: {z: {thing: string}}}}
type B = {x: {y: {z: {thing: string}}}}

View file

@ -475,8 +475,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional")
TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow")
{
ScopedFastFlag luauInstantiateFollows{"LuauInstantiateFollows", true};
// Just check that this doesn't assert
check(R"(
--!nonstrict

View file

@ -728,8 +728,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_verifies_types_do_intersect")
TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: string | number, b: boolean | number)
return a == b

View file

@ -7,7 +7,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauEqConstraint)
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
using namespace Luau;
@ -183,8 +182,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_completely_incompatible")
// We'll need to not only report an error on `a == b`, but also to refine both operands as `never` in the `==` branch.
TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: string, b: boolean?)
if a == b then
@ -208,8 +205,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
// Just needs to fully support equality refinement. Which is annoying without type states.
TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
{
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
CheckResult result = check(R"(
type T = {x: string, y: number} | {x: nil, y: nil}
@ -471,4 +466,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_many_things_but_first_of_it
CHECK_EQ("boolean", toString(requireType("b")));
}
TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent")
{
ScopedFastFlag sff[]{
{"LuauLowerBoundsCalculation", true},
{"LuauNormalizeFlagIsConservative", true},
};
CheckResult result = check(R"(
local function f(o)
local t = {}
t[o] = true
local function foo(o)
o:m1()
t[o] = nil
end
local function bar(o)
o:m2()
t[o] = true
end
return t
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
// TODO: We're missing generics a... and b...
CHECK_EQ("(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f")));
}
TEST_SUITE_END();

View file

@ -7,7 +7,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauDiscriminableUnions2)
LUAU_FASTFLAG(LuauWeakEqConstraint)
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
@ -268,18 +267,10 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
end
)");
if (FFlag::LuauDiscriminableUnions2)
{
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44})));
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38})));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'number' has no overlap with 'string'", toString(result.errors[0]));
}
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44})));
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38})));
}
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
@ -378,8 +369,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: (string | number)?, b: boolean?)
if a == b then
@ -392,28 +381,15 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauWeakEqConstraint)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "nil"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "nil"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
}
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b
}
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: (string | number)?)
if a == 1 then
@ -426,24 +402,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauWeakEqConstraint)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number"); // a == 1
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
}
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
}
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local function f(a: (string | number)?)
if "hello" == a then
@ -462,8 +426,6 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: (string | number)?)
if a ~= nil then
@ -476,21 +438,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauWeakEqConstraint)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil
}
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
}
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
{
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
CheckResult result = check(R"(
@ -509,8 +462,6 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local function f(a: any, b: {x: number}?)
if a ~= b then
@ -521,22 +472,12 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauWeakEqConstraint)
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}"); // a ~= b
}
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
}
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
{
ScopedFastFlag sff1{"LuauEqConstraint", true};
CheckResult result = check(R"(
local t: {string} = {"hello"}
@ -554,18 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b
if (FFlag::LuauWeakEqConstraint)
{
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
}
else
{
// This is technically not wrong, but it's also wrong at the same time.
// The refinement code is none the wiser about the fact we pulled a string out of an array, so it has no choice but to narrow as just string.
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string"); // a == b
}
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
}
TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
@ -594,16 +525,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
end
)");
if (FFlag::LuauDiscriminableUnions2)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
// This is kinda weird to see, but this actually only happens in Luau without Roblox type bindings because we don't have a Vector3 type.
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Unknown type 'Vector3'", toString(result.errors[0]));
}
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({3, 28})));
}
@ -1009,10 +931,6 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
@ -1033,10 +951,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
type Cat = {tag: "Cat", name: string, catfood: string}
type Dog = {tag: "Dog", name: string, dogfood: string}
@ -1070,11 +984,6 @@ TEST_CASE_FIXTURE(Fixture, "and_or_peephole_refinement")
TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauAssertStripsFalsyTypes", true},
};
CheckResult result = check(R"(
local function is_true(b: true) end
local function is_false(b: false) end
@ -1093,11 +1002,6 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauAssertStripsFalsyTypes", true},
};
CheckResult result = check(R"(
type Ok<T> = { ok: true, value: T }
type Err<E> = { ok: false, error: E }
@ -1117,8 +1021,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_
TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersection_table")
{
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
CheckResult result = check(R"(
type T = {} & {f: ((string) -> string)?}
local function f(t: T, x)
@ -1133,10 +1035,6 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersect
TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
{
ScopedFastFlag sff[] = {
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
type T = {tag: "Part", x: Part} | {tag: "Folder", x: Folder}
@ -1171,14 +1069,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
end
)");
if (FFlag::LuauDiscriminableUnions2)
LUAU_REQUIRE_NO_ERRORS(result);
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0]));
}
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"

View file

@ -139,6 +139,8 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons")
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
{
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
CheckResult result = check(R"(
type MyEnum = "foo" | "bar" | "baz"
local a : MyEnum = "bang"
@ -325,8 +327,6 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
{
ScopedFastFlag sff[]{
{"LuauEqConstraint", true},
{"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", false},
};
@ -350,11 +350,8 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauEqConstraint", true},
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", false},
{"LuauDoNotAccidentallyDependOnPointerOrdering", true},
};
CheckResult result = check(R"(
@ -390,7 +387,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
{"LuauWidenIfSupertypeIsFree2", true},
};
@ -419,6 +415,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
{
ScopedFastFlag sff[]{
{"LuauWidenIfSupertypeIsFree2", true},
{"LuauWeakEqConstraint", true},
};
CheckResult result = check(R"(
@ -456,10 +453,6 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened")
TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local a: string = "hi"
if a == "hi" then
@ -474,10 +467,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local a: string = "hi"
if a == "hi" or a == "bye" then
@ -492,10 +481,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local a: string = "hi"
if a == "hi" then
@ -510,10 +495,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
{
ScopedFastFlag sff[]{
{"LuauDiscriminableUnions2", true},
};
CheckResult result = check(R"(
local a: string = "hi"
if a == "hi" or a == "bye" then

View file

@ -2279,8 +2279,6 @@ local y = #x
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_hang_when_trying_to_look_up_in_cyclic_metatable_index")
{
ScopedFastFlag sff{"LuauTerminateCyclicMetatableIndexLookup", true};
// t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}}
CheckResult result = check(R"(
local mt = {}
@ -2313,8 +2311,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "give_up_after_one_metatable_index_look_up")
TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
{
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
CheckResult result = check(R"(
type T = {} & {p: number | string}
local function f(t: T)
@ -2971,8 +2967,6 @@ TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
{
ScopedFastFlag sff{"LuauCheckImplicitNumbericKeys", true};
CheckResult result = check(R"(
local t: { [string]: number } = { 5, 6, 7 }
)");
@ -2984,4 +2978,32 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2]));
}
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra")
{
ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true};
ScopedFastFlag luauSubtypingAddOptPropsToUnsealedTables{"LuauSubtypingAddOptPropsToUnsealedTables", true};
CheckResult result = check(R"(
type X = { { x: boolean?, y: boolean? } }
local l1: {[string]: X} = { key = { { x = true }, { y = true } } }
local l2: {[any]: X} = { key = { { x = true }, { y = true } } }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
{
ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true};
CheckResult result = check(R"(
type X = {[any]: string | boolean}
local x: X = { key = "str" }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -15,7 +15,6 @@
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
LUAU_FASTFLAG(LuauEqConstraint)
using namespace Luau;
@ -308,7 +307,6 @@ TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count")
int limit = 600;
#endif
ScopedFastFlag sff{"LuauTableUseCounterInstead", true};
ScopedFastInt sfi{"LuauCheckRecursionLimit", limit};
CheckResult result = check("function f() return " + rep("{a=", limit) + "'a'" + rep("}", limit) + " end");
@ -1011,8 +1009,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
{
ScopedFastFlag substituteFollowNewTypes{"LuauSubstituteFollowNewTypes", true};
CheckResult result = check(R"(
local obj = {}

View file

@ -7,7 +7,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauEqConstraint)
using namespace Luau;