mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
0a0012e26d
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
|
42
Analysis/include/Luau/TypeArena.h
Normal file
42
Analysis/include/Luau/TypeArena.h
Normal 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);
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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!");
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
88
Analysis/src/TypeArena.cpp
Normal file
88
Analysis/src/TypeArena.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
@ -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, ¤tModule->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
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)");
|
||||
}
|
||||
|
||||
|
@ -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 = {}
|
||||
|
@ -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 = {}
|
||||
|
@ -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();
|
||||
|
@ -22,8 +22,6 @@ struct LimitFixture : BuiltinsFixture
|
||||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
||||
#endif
|
||||
|
||||
ScopedFastFlag LuauJustOneCallFrameForHaveSeen{"LuauJustOneCallFrameForHaveSeen", true};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -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();
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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}}}}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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 = {}
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauEqConstraint)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user