mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 22:35:43 +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
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -18,7 +19,6 @@ struct CloneState
|
|||||||
SeenTypePacks seenTypePacks;
|
SeenTypePacks seenTypePacks;
|
||||||
|
|
||||||
int recursionCount = 0;
|
int recursionCount = 0;
|
||||||
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
#include "Luau/TypeArena.h"
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -108,9 +109,6 @@ struct FunctionDoesNotTakeSelf
|
|||||||
|
|
||||||
struct FunctionRequiresSelf
|
struct FunctionRequiresSelf
|
||||||
{
|
{
|
||||||
// TODO: Delete with LuauAnyInIsOptionalIsOptional
|
|
||||||
int requiredExtraNils = 0;
|
|
||||||
|
|
||||||
bool operator==(const FunctionRequiresSelf& rhs) const;
|
bool operator==(const FunctionRequiresSelf& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,10 +34,6 @@ const LValue* baseof(const LValue& lvalue);
|
|||||||
|
|
||||||
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
|
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
|
// Utility function: breaks down an LValue to get at the Symbol
|
||||||
Symbol getBaseSymbol(const LValue& lvalue);
|
Symbol getBaseSymbol(const LValue& lvalue);
|
||||||
|
|
||||||
|
@ -2,11 +2,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/FileResolver.h"
|
#include "Luau/FileResolver.h"
|
||||||
#include "Luau/TypePack.h"
|
|
||||||
#include "Luau/TypedAllocator.h"
|
|
||||||
#include "Luau/ParseOptions.h"
|
#include "Luau/ParseOptions.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/ParseResult.h"
|
#include "Luau/ParseResult.h"
|
||||||
|
#include "Luau/TypeArena.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#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
|
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
|
struct Module
|
||||||
{
|
{
|
||||||
~Module();
|
~Module();
|
||||||
@ -111,9 +81,7 @@ struct Module
|
|||||||
|
|
||||||
// Once a module has been typechecked, we clone its public interface into a separate arena.
|
// 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.
|
// 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
|
void clonePublicInterface(InternalErrorReporter& ice);
|
||||||
// indicates a bug in the type checker that we want to surface.
|
|
||||||
bool clonePublicInterface(InternalErrorReporter& ice);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // 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
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Module.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
|
||||||
|
|
||||||
namespace Luau
|
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(const TxnLog&) = delete;
|
||||||
TxnLog& operator=(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 AstExprIndexExpr& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
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 AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
||||||
TypeId checkRelationalOperation(
|
TypeId checkRelationalOperation(
|
||||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
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);
|
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
|
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 ScopePtr& scope, const LValue& lvalue);
|
||||||
std::optional<TypeId> resolveLValue(const RefinementMap& refis, 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 PredicateVec& predicates, 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 Predicate& predicate, 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 TruthyPredicate& truthyP, 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 AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||||
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||||
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||||
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||||
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||||
|
|
||||||
bool isNonstrictMode() const;
|
bool isNonstrictMode() const;
|
||||||
bool useConstrainedIntersections() const;
|
bool useConstrainedIntersections() const;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/TypeInfer.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 "Luau/UnifierSharedState.h"
|
||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@ -55,8 +55,6 @@ struct Unifier
|
|||||||
UnifierSharedState& sharedState;
|
UnifierSharedState& sharedState;
|
||||||
|
|
||||||
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
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.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
|
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
|
||||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||||
|
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -471,17 +472,20 @@ struct GenericTypeVarVisitor
|
|||||||
|
|
||||||
else if (auto pack = get<TypePack>(tp))
|
else if (auto pack = get<TypePack>(tp))
|
||||||
{
|
{
|
||||||
visit(tp, *pack);
|
bool res = visit(tp, *pack);
|
||||||
|
if (!FFlag::LuauNormalizeFlagIsConservative || res)
|
||||||
|
{
|
||||||
for (TypeId ty : pack->head)
|
for (TypeId ty : pack->head)
|
||||||
traverse(ty);
|
traverse(ty);
|
||||||
|
|
||||||
if (pack->tail)
|
if (pack->tail)
|
||||||
traverse(*pack->tail);
|
traverse(*pack->tail);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (auto pack = get<VariadicTypePack>(tp))
|
else if (auto pack = get<VariadicTypePack>(tp))
|
||||||
{
|
{
|
||||||
visit(tp, *pack);
|
bool res = visit(tp, *pack);
|
||||||
|
if (!FFlag::LuauNormalizeFlagIsConservative || res)
|
||||||
traverse(pack->ty);
|
traverse(pack->ty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||||
|
|
||||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||||
@ -408,8 +407,6 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||||||
{
|
{
|
||||||
auto [paramPack, predicates] = exprResult;
|
auto [paramPack, predicates] = exprResult;
|
||||||
|
|
||||||
if (FFlag::LuauAssertStripsFalsyTypes)
|
|
||||||
{
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
auto [head, tail] = flatten(paramPack);
|
auto [head, tail] = flatten(paramPack);
|
||||||
@ -421,7 +418,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||||||
head.push_back(*fst);
|
head.push_back(*fst);
|
||||||
}
|
}
|
||||||
|
|
||||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
typechecker.resolve(predicates, scope, true);
|
||||||
|
|
||||||
if (head.size() > 0)
|
if (head.size() > 0)
|
||||||
{
|
{
|
||||||
@ -433,16 +430,6 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (expr.args.size < 1)
|
|
||||||
return ExprResult<TypePackId>{paramPack};
|
|
||||||
|
|
||||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
|
||||||
|
|
||||||
return ExprResult<TypePackId>{paramPack};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
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
|
// 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/Clone.h"
|
||||||
#include "Luau/Module.h"
|
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
@ -9,8 +8,6 @@
|
|||||||
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
|
|
||||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -88,21 +85,9 @@ struct TypePackCloner
|
|||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const Unifiable::Free& t)
|
void operator()(const Unifiable::Free& t)
|
||||||
{
|
|
||||||
if (FFlag::LuauLosslessClone)
|
|
||||||
{
|
{
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cloneState.encounteredFreeType = true;
|
|
||||||
|
|
||||||
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
|
|
||||||
TypePackId cloned = dest.addTypePack(*err);
|
|
||||||
seenTypePacks[typePackId] = cloned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void operator()(const Unifiable::Generic& t)
|
void operator()(const Unifiable::Generic& t)
|
||||||
{
|
{
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
@ -152,18 +137,7 @@ void TypeCloner::defaultClone(const T& t)
|
|||||||
|
|
||||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauLosslessClone)
|
|
||||||
{
|
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cloneState.encounteredFreeType = true;
|
|
||||||
|
|
||||||
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
|
|
||||||
TypeId cloned = dest.addType(*err);
|
|
||||||
seenTypes[typeId] = cloned;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeCloner::operator()(const Unifiable::Generic& t)
|
void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||||
@ -191,9 +165,6 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
|||||||
|
|
||||||
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauLosslessClone)
|
|
||||||
cloneState.encounteredFreeType = true;
|
|
||||||
|
|
||||||
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
||||||
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
|
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
|
||||||
LUAU_ASSERT(ctv);
|
LUAU_ASSERT(ctv);
|
||||||
@ -230,8 +201,6 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
|
|||||||
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
||||||
ftv->argNames = t.argNames;
|
ftv->argNames = t.argNames;
|
||||||
ftv->retType = clone(t.retType, dest, cloneState);
|
ftv->retType = clone(t.retType, dest, cloneState);
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
ftv->hasNoGenerics = t.hasNoGenerics;
|
ftv->hasNoGenerics = t.hasNoGenerics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,13 +239,6 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
|||||||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||||
arg = clone(arg, dest, cloneState);
|
arg = clone(arg, dest, cloneState);
|
||||||
|
|
||||||
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
|
|
||||||
{
|
|
||||||
cloneState.encounteredFreeType = true;
|
|
||||||
|
|
||||||
ttv->state = TableState::Sealed;
|
|
||||||
}
|
|
||||||
|
|
||||||
ttv->definitionModuleName = t.definitionModuleName;
|
ttv->definitionModuleName = t.definitionModuleName;
|
||||||
if (!FFlag::LuauNoMethodLocations)
|
if (!FFlag::LuauNoMethodLocations)
|
||||||
ttv->methodDefinitionLocations = t.methodDefinitionLocations;
|
ttv->methodDefinitionLocations = t.methodDefinitionLocations;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
|
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
#include "Luau/Module.h"
|
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
|
||||||
@ -178,14 +177,6 @@ struct ErrorConverter
|
|||||||
|
|
||||||
std::string operator()(const Luau::FunctionRequiresSelf& e) const
|
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?";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,7 +530,7 @@ bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
|
|||||||
|
|
||||||
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
|
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
|
||||||
{
|
{
|
||||||
return requiredExtraNils == e.requiredExtraNils;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const
|
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>)
|
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
|
||||||
stream << "FunctionDoesNotTakeSelf { }";
|
stream << "FunctionDoesNotTakeSelf { }";
|
||||||
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
|
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>)
|
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
|
||||||
stream << "OccursCheckFailed { }";
|
stream << "OccursCheckFailed { }";
|
||||||
else if constexpr (std::is_same_v<T, UnknownRequire>)
|
else if constexpr (std::is_same_v<T, UnknownRequire>)
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -79,27 +77,8 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
|
|||||||
return std::nullopt;
|
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)
|
Symbol getBaseSymbol(const LValue& lvalue)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauTypecheckOptPass);
|
|
||||||
|
|
||||||
const LValue* current = &lvalue;
|
const LValue* current = &lvalue;
|
||||||
while (auto field = get<Field>(*current))
|
while (auto field = get<Field>(*current))
|
||||||
current = baseof(*current);
|
current = baseof(*current);
|
||||||
|
@ -13,9 +13,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative);
|
||||||
LUAU_FASTFLAG(LuauLosslessClone)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -55,89 +54,25 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
|||||||
return contains(pos, *iter);
|
return contains(pos, *iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeArena::clear()
|
struct ForceNormal : TypeVarOnceVisitor
|
||||||
{
|
{
|
||||||
typeVars.clear();
|
bool visit(TypeId ty) override
|
||||||
typePacks.clear();
|
{
|
||||||
}
|
asMutable(ty)->normal = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
TypeId TypeArena::addTV(TypeVar&& tv)
|
bool visit(TypeId ty, const FreeTypeVar& ftv) override
|
||||||
{
|
{
|
||||||
TypeId allocated = typeVars.allocate(std::move(tv));
|
visit(ty);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
asMutable(allocated)->owningArena = this;
|
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||||
|
{
|
||||||
return allocated;
|
return true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Module::~Module()
|
Module::~Module()
|
||||||
{
|
{
|
||||||
@ -145,7 +80,7 @@ Module::~Module()
|
|||||||
unfreeze(internalTypes);
|
unfreeze(internalTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
void Module::clonePublicInterface(InternalErrorReporter& ice)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
||||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||||
@ -165,11 +100,22 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||||||
normalize(*moduleScope->varargPack, interfaceTypes, ice);
|
normalize(*moduleScope->varargPack, interfaceTypes, ice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForceNormal forceNormal;
|
||||||
|
|
||||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||||
{
|
{
|
||||||
tf = clone(tf, interfaceTypes, cloneState);
|
tf = clone(tf, interfaceTypes, cloneState);
|
||||||
if (FFlag::LuauLowerBoundsCalculation)
|
if (FFlag::LuauLowerBoundsCalculation)
|
||||||
|
{
|
||||||
normalize(tf.type, interfaceTypes, ice);
|
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)
|
for (TypeId ty : moduleScope->returnType)
|
||||||
@ -191,11 +137,12 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||||||
|
|
||||||
freeze(internalTypes);
|
freeze(internalTypes);
|
||||||
freeze(interfaceTypes);
|
freeze(interfaceTypes);
|
||||||
|
}
|
||||||
|
|
||||||
if (FFlag::LuauLosslessClone)
|
ScopePtr Module::getModuleScope() const
|
||||||
return false; // TODO: make function return void.
|
{
|
||||||
else
|
LUAU_ASSERT(!scopes.empty());
|
||||||
return cloneState.encounteredFreeType;
|
return scopes.front().second;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -14,6 +14,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
|||||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -260,8 +261,13 @@ static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, Intern
|
|||||||
if (count >= FInt::LuauNormalizeIterationLimit)
|
if (count >= FInt::LuauNormalizeIterationLimit)
|
||||||
ice.ice("Luau::areNormal hit iteration limit");
|
ice.ice("Luau::areNormal hit iteration limit");
|
||||||
|
|
||||||
|
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.
|
// 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 ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return std::all_of(begin(t), end(t), isNormal);
|
return std::all_of(begin(t), end(t), isNormal);
|
||||||
@ -1003,8 +1009,15 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
|
|||||||
(void)clone(ty, arena, state);
|
(void)clone(ty, arena, state);
|
||||||
|
|
||||||
Normalize n{arena, ice};
|
Normalize n{arena, ice};
|
||||||
|
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||||
|
{
|
||||||
|
DEPRECATED_visitTypeVar(ty, n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
std::unordered_set<void*> seen;
|
std::unordered_set<void*> seen;
|
||||||
DEPRECATED_visitTypeVar(ty, n, seen);
|
DEPRECATED_visitTypeVar(ty, n, seen);
|
||||||
|
}
|
||||||
|
|
||||||
return {ty, !n.limitExceeded};
|
return {ty, !n.limitExceeded};
|
||||||
}
|
}
|
||||||
@ -1028,8 +1041,15 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
|
|||||||
(void)clone(tp, arena, state);
|
(void)clone(tp, arena, state);
|
||||||
|
|
||||||
Normalize n{arena, ice};
|
Normalize n{arena, ice};
|
||||||
|
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||||
|
{
|
||||||
|
DEPRECATED_visitTypeVar(tp, n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
std::unordered_set<void*> seen;
|
std::unordered_set<void*> seen;
|
||||||
DEPRECATED_visitTypeVar(tp, n, seen);
|
DEPRECATED_visitTypeVar(tp, n, seen);
|
||||||
|
}
|
||||||
|
|
||||||
return {tp, !n.limitExceeded};
|
return {tp, !n.limitExceeded};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "Luau/VisitTypeVar.h"
|
#include "Luau/VisitTypeVar.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
LUAU_FASTFLAG(LuauAlwaysQuantify)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -59,7 +59,6 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
|
|
||||||
bool visit(TypeId ty, const FreeTypeVar& ftv) override
|
bool visit(TypeId ty, const FreeTypeVar& ftv) override
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (!level.subsumes(ftv.level))
|
if (!level.subsumes(ftv.level))
|
||||||
@ -76,20 +75,17 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
|
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
|
||||||
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
|
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
if (ttv.state == TableState::Generic)
|
if (ttv.state == TableState::Generic)
|
||||||
seenGenericType = true;
|
seenGenericType = true;
|
||||||
|
|
||||||
if (ttv.state == TableState::Free)
|
if (ttv.state == TableState::Free)
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
|
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
|
||||||
return false;
|
return false;
|
||||||
if (!level.subsumes(ttv.level))
|
if (!level.subsumes(ttv.level))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass && ttv.state == TableState::Unsealed)
|
if (ttv.state == TableState::Unsealed)
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -97,8 +93,6 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
if (ttv.state == TableState::Free)
|
if (ttv.state == TableState::Free)
|
||||||
{
|
{
|
||||||
ttv.state = TableState::Generic;
|
ttv.state = TableState::Generic;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
seenGenericType = true;
|
seenGenericType = true;
|
||||||
}
|
}
|
||||||
else if (ttv.state == TableState::Unsealed)
|
else if (ttv.state == TableState::Unsealed)
|
||||||
@ -111,7 +105,6 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
|
|
||||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (!level.subsumes(ftp.level))
|
if (!level.subsumes(ftp.level))
|
||||||
@ -131,10 +124,18 @@ void quantify(TypeId ty, TypeLevel level)
|
|||||||
|
|
||||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
|
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->generics = q.generics;
|
||||||
ftv->genericPacks = q.genericPacks;
|
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;
|
ftv->hasNoGenerics = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,6 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
|
|
||||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -19,26 +16,20 @@ namespace Luau
|
|||||||
|
|
||||||
void Tarjan::visitChildren(TypeId ty, int index)
|
void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
LUAU_ASSERT(ty == log->follow(ty));
|
LUAU_ASSERT(ty == log->follow(ty));
|
||||||
else
|
|
||||||
ty = log->follow(ty);
|
|
||||||
|
|
||||||
if (ignoreChildren(ty))
|
if (ignoreChildren(ty))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
if (auto pty = log->pending(ty))
|
if (auto pty = log->pending(ty))
|
||||||
ty = &pty->pending;
|
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->argTypes);
|
||||||
visitChild(ftv->retType);
|
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);
|
LUAU_ASSERT(!ttv->boundTo);
|
||||||
for (const auto& [name, prop] : ttv->props)
|
for (const auto& [name, prop] : ttv->props)
|
||||||
@ -55,17 +46,17 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
|||||||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||||
visitChild(itp);
|
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->table);
|
||||||
visitChild(mtv->metatable);
|
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)
|
for (TypeId opt : utv->options)
|
||||||
visitChild(opt);
|
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)
|
for (TypeId part : itv->parts)
|
||||||
visitChild(part);
|
visitChild(part);
|
||||||
@ -79,28 +70,22 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
|||||||
|
|
||||||
void Tarjan::visitChildren(TypePackId tp, int index)
|
void Tarjan::visitChildren(TypePackId tp, int index)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
LUAU_ASSERT(tp == log->follow(tp));
|
LUAU_ASSERT(tp == log->follow(tp));
|
||||||
else
|
|
||||||
tp = log->follow(tp);
|
|
||||||
|
|
||||||
if (ignoreChildren(tp))
|
if (ignoreChildren(tp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
if (auto ptp = log->pending(tp))
|
if (auto ptp = log->pending(tp))
|
||||||
tp = &ptp->pending;
|
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)
|
for (TypeId tv : tpp->head)
|
||||||
visitChild(tv);
|
visitChild(tv);
|
||||||
if (tpp->tail)
|
if (tpp->tail)
|
||||||
visitChild(*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);
|
visitChild(vtp->ty);
|
||||||
}
|
}
|
||||||
@ -108,9 +93,6 @@ void Tarjan::visitChildren(TypePackId tp, int index)
|
|||||||
|
|
||||||
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
|
||||||
LUAU_ASSERT(ty == log->follow(ty));
|
|
||||||
else
|
|
||||||
ty = log->follow(ty);
|
ty = log->follow(ty);
|
||||||
|
|
||||||
bool fresh = !typeToIndex.contains(ty);
|
bool fresh = !typeToIndex.contains(ty);
|
||||||
@ -129,9 +111,6 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
|||||||
|
|
||||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
|
||||||
LUAU_ASSERT(tp == log->follow(tp));
|
|
||||||
else
|
|
||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
|
||||||
bool fresh = !packToIndex.contains(tp);
|
bool fresh = !packToIndex.contains(tp);
|
||||||
@ -150,7 +129,6 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
|||||||
|
|
||||||
void Tarjan::visitChild(TypeId ty)
|
void Tarjan::visitChild(TypeId ty)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
|
||||||
ty = log->follow(ty);
|
ty = log->follow(ty);
|
||||||
|
|
||||||
edgesTy.push_back(ty);
|
edgesTy.push_back(ty);
|
||||||
@ -159,7 +137,6 @@ void Tarjan::visitChild(TypeId ty)
|
|||||||
|
|
||||||
void Tarjan::visitChild(TypePackId tp)
|
void Tarjan::visitChild(TypePackId tp)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
|
||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
|
||||||
edgesTy.push_back(nullptr);
|
edgesTy.push_back(nullptr);
|
||||||
@ -389,13 +366,10 @@ TypeId Substitution::clone(TypeId ty)
|
|||||||
|
|
||||||
TypeId result = ty;
|
TypeId result = ty;
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
if (auto pty = log->pending(ty))
|
if (auto pty = log->pending(ty))
|
||||||
ty = &pty->pending;
|
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};
|
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
||||||
clone.generics = ftv->generics;
|
clone.generics = ftv->generics;
|
||||||
@ -405,7 +379,7 @@ TypeId Substitution::clone(TypeId ty)
|
|||||||
clone.argNames = ftv->argNames;
|
clone.argNames = ftv->argNames;
|
||||||
result = addType(std::move(clone));
|
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);
|
LUAU_ASSERT(!ttv->boundTo);
|
||||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||||
@ -419,19 +393,19 @@ TypeId Substitution::clone(TypeId ty)
|
|||||||
clone.tags = ttv->tags;
|
clone.tags = ttv->tags;
|
||||||
result = addType(std::move(clone));
|
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};
|
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
|
||||||
clone.syntheticName = mtv->syntheticName;
|
clone.syntheticName = mtv->syntheticName;
|
||||||
result = addType(std::move(clone));
|
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;
|
UnionTypeVar clone;
|
||||||
clone.options = utv->options;
|
clone.options = utv->options;
|
||||||
result = addType(std::move(clone));
|
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;
|
IntersectionTypeVar clone;
|
||||||
clone.parts = itv->parts;
|
clone.parts = itv->parts;
|
||||||
@ -451,20 +425,17 @@ TypePackId Substitution::clone(TypePackId tp)
|
|||||||
{
|
{
|
||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
if (auto ptp = log->pending(tp))
|
if (auto ptp = log->pending(tp))
|
||||||
tp = &ptp->pending;
|
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;
|
TypePack clone;
|
||||||
clone.head = tpp->head;
|
clone.head = tpp->head;
|
||||||
clone.tail = tpp->tail;
|
clone.tail = tpp->tail;
|
||||||
return addTypePack(std::move(clone));
|
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;
|
VariadicTypePack clone;
|
||||||
clone.ty = vtp->ty;
|
clone.ty = vtp->ty;
|
||||||
@ -476,28 +447,22 @@ TypePackId Substitution::clone(TypePackId tp)
|
|||||||
|
|
||||||
void Substitution::foundDirty(TypeId ty)
|
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))
|
if (isDirty(ty))
|
||||||
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(ty)) : clean(ty);
|
newTypes[ty] = follow(clean(ty));
|
||||||
else
|
else
|
||||||
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(ty)) : clone(ty);
|
newTypes[ty] = follow(clone(ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Substitution::foundDirty(TypePackId tp)
|
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))
|
if (isDirty(tp))
|
||||||
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(tp)) : clean(tp);
|
newPacks[tp] = follow(clean(tp));
|
||||||
else
|
else
|
||||||
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(tp)) : clone(tp);
|
newPacks[tp] = follow(clone(tp));
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId Substitution::replace(TypeId ty)
|
TypeId Substitution::replace(TypeId ty)
|
||||||
@ -525,10 +490,7 @@ void Substitution::replaceChildren(TypeId ty)
|
|||||||
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
|
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
|
||||||
btv->boundTo = replace(btv->boundTo);
|
btv->boundTo = replace(btv->boundTo);
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
LUAU_ASSERT(ty == log->follow(ty));
|
LUAU_ASSERT(ty == log->follow(ty));
|
||||||
else
|
|
||||||
ty = log->follow(ty);
|
|
||||||
|
|
||||||
if (ignoreChildren(ty))
|
if (ignoreChildren(ty))
|
||||||
return;
|
return;
|
||||||
@ -579,10 +541,7 @@ void Substitution::replaceChildren(TypeId ty)
|
|||||||
|
|
||||||
void Substitution::replaceChildren(TypePackId tp)
|
void Substitution::replaceChildren(TypePackId tp)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
LUAU_ASSERT(tp == log->follow(tp));
|
LUAU_ASSERT(tp == log->follow(tp));
|
||||||
else
|
|
||||||
tp = log->follow(tp);
|
|
||||||
|
|
||||||
if (ignoreChildren(tp))
|
if (ignoreChildren(tp))
|
||||||
return;
|
return;
|
||||||
|
@ -219,6 +219,8 @@ struct StringifierState
|
|||||||
return generateName(s);
|
return generateName(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int previousNameIndex = 0;
|
||||||
|
|
||||||
std::string getName(TypePackId ty)
|
std::string getName(TypePackId ty)
|
||||||
{
|
{
|
||||||
const size_t s = result.nameMap.typePacks.size();
|
const size_t s = result.nameMap.typePacks.size();
|
||||||
@ -228,9 +230,10 @@ struct StringifierState
|
|||||||
|
|
||||||
for (int count = 0; count < 256; ++count)
|
for (int count = 0; count < 256; ++count)
|
||||||
{
|
{
|
||||||
std::string candidate = generateName(usedNames.size() + count);
|
std::string candidate = generateName(previousNameIndex + count);
|
||||||
if (!usedNames.count(candidate))
|
if (!usedNames.count(candidate))
|
||||||
{
|
{
|
||||||
|
previousNameIndex += count;
|
||||||
usedNames.insert(candidate);
|
usedNames.insert(candidate);
|
||||||
n = candidate;
|
n = candidate;
|
||||||
return candidate;
|
return candidate;
|
||||||
@ -399,6 +402,7 @@ struct TypeVarStringifier
|
|||||||
{
|
{
|
||||||
if (gtv.explicitName)
|
if (gtv.explicitName)
|
||||||
{
|
{
|
||||||
|
state.usedNames.insert(gtv.name);
|
||||||
state.result.nameMap.typeVars[ty] = gtv.name;
|
state.result.nameMap.typeVars[ty] = gtv.name;
|
||||||
state.emit(gtv.name);
|
state.emit(gtv.name);
|
||||||
}
|
}
|
||||||
@ -943,6 +947,7 @@ struct TypePackStringifier
|
|||||||
state.emit("gen-");
|
state.emit("gen-");
|
||||||
if (pack.explicitName)
|
if (pack.explicitName)
|
||||||
{
|
{
|
||||||
|
state.usedNames.insert(pack.name);
|
||||||
state.result.nameMap.typePacks[tp] = pack.name;
|
state.result.nameMap.typePacks[tp] = pack.name;
|
||||||
state.emit(pack.name);
|
state.emit(pack.name);
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauJustOneCallFrameForHaveSeen, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -150,37 +148,13 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
|
|||||||
|
|
||||||
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
|
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
|
||||||
{
|
{
|
||||||
if (FFlag::LuauJustOneCallFrameForHaveSeen && !FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
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))
|
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FFlag::LuauTypecheckOptPass && parent)
|
|
||||||
{
|
|
||||||
return parent->haveSeen(lhs, rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
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(LuauSeparateTypechecks)
|
||||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
|
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
|
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
|
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateFollows, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false)
|
LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
|
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||||
|
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
|
||||||
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2)
|
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(LuauReturnTypeInferenceInNonstrict, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
||||||
LUAU_FASTFLAG(LuauLosslessClone)
|
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false);
|
LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
|
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false);
|
LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -371,12 +364,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||||||
|
|
||||||
prepareErrorsForDisplay(currentModule->errors);
|
prepareErrorsForDisplay(currentModule->errors);
|
||||||
|
|
||||||
bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler);
|
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."}});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear unifier cache since it's keyed off internal types that get deallocated
|
// 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.
|
// 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);
|
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
|
||||||
|
|
||||||
ScopePtr ifScope = childScope(scope, statement.thenbody->location);
|
ScopePtr ifScope = childScope(scope, statement.thenbody->location);
|
||||||
reportErrors(resolve(result.predicates, ifScope, true));
|
resolve(result.predicates, ifScope, true);
|
||||||
check(ifScope, *statement.thenbody);
|
check(ifScope, *statement.thenbody);
|
||||||
|
|
||||||
if (statement.elsebody)
|
if (statement.elsebody)
|
||||||
@ -734,7 +722,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement)
|
|||||||
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
|
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
|
||||||
|
|
||||||
ScopePtr whileScope = childScope(scope, statement.body->location);
|
ScopePtr whileScope = childScope(scope, statement.body->location);
|
||||||
reportErrors(resolve(result.predicates, whileScope, true));
|
resolve(result.predicates, whileScope, true);
|
||||||
check(whileScope, *statement.body);
|
check(whileScope, *statement.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1154,10 +1142,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FFlag::LuauInstantiateFollows)
|
|
||||||
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
|
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
|
||||||
else
|
|
||||||
iterTy = follow(instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauTypecheckIter)
|
if (FFlag::LuauTypecheckIter)
|
||||||
@ -1849,24 +1834,12 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
|
|
||||||
tablify(type);
|
tablify(type);
|
||||||
|
|
||||||
if (FFlag::LuauDiscriminableUnions2)
|
|
||||||
{
|
|
||||||
if (isString(type))
|
if (isString(type))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
|
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
|
||||||
LUAU_ASSERT(mtIndex);
|
LUAU_ASSERT(mtIndex);
|
||||||
type = *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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TableTypeVar* tableType = getMutableTableType(type))
|
if (TableTypeVar* tableType = getMutableTableType(type))
|
||||||
{
|
{
|
||||||
@ -1966,24 +1939,11 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauDoNotTryToReduce)
|
|
||||||
{
|
|
||||||
if (parts.size() == 1)
|
if (parts.size() == 1)
|
||||||
return parts[0];
|
return parts[0];
|
||||||
|
|
||||||
return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
|
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addErrors)
|
if (addErrors)
|
||||||
reportError(location, UnknownProperty{type, name});
|
reportError(location, UnknownProperty{type, name});
|
||||||
@ -1993,8 +1953,6 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
|
|
||||||
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering)
|
|
||||||
{
|
|
||||||
std::vector<TypeId> result;
|
std::vector<TypeId> result;
|
||||||
for (TypeId t : types)
|
for (TypeId t : types)
|
||||||
{
|
{
|
||||||
@ -2008,6 +1966,8 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
|||||||
{
|
{
|
||||||
for (TypeId ty : utv)
|
for (TypeId ty : utv)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||||
|
ty = follow(ty);
|
||||||
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||||
return {ty};
|
return {ty};
|
||||||
|
|
||||||
@ -2034,62 +1994,14 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::set<TypeId> s;
|
|
||||||
|
|
||||||
for (TypeId t : types)
|
|
||||||
{
|
|
||||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(follow(t)))
|
|
||||||
{
|
|
||||||
std::vector<TypeId> r = reduceUnion(utv->options);
|
|
||||||
for (TypeId ty : r)
|
|
||||||
s.insert(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
|
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
|
||||||
{
|
{
|
||||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||||
{
|
|
||||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
|
||||||
{
|
{
|
||||||
if (!std::any_of(begin(utv), end(utv), isNil))
|
if (!std::any_of(begin(utv), end(utv), isNil))
|
||||||
return ty;
|
return ty;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool hasNil = false;
|
|
||||||
|
|
||||||
for (TypeId option : utv)
|
|
||||||
{
|
|
||||||
if (isNil(option))
|
|
||||||
{
|
|
||||||
hasNil = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasNil)
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<TypeId> result;
|
std::vector<TypeId> result;
|
||||||
|
|
||||||
@ -2110,8 +2022,6 @@ std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
|
|||||||
|
|
||||||
TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
|
TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
|
||||||
{
|
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (auto utv = get<UnionTypeVar>(ty))
|
if (auto utv = get<UnionTypeVar>(ty))
|
||||||
@ -2125,18 +2035,6 @@ TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
|
|||||||
reportError(location, OptionalValueAccess{ty});
|
reportError(location, OptionalValueAccess{ty});
|
||||||
return follow(*strippedUnion);
|
return follow(*strippedUnion);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (isOptional(ty))
|
|
||||||
{
|
|
||||||
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(follow(ty)))
|
|
||||||
{
|
|
||||||
reportError(location, OptionalValueAccess{ty});
|
|
||||||
return follow(*strippedUnion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
@ -2194,7 +2092,6 @@ TypeId TypeChecker::checkExprTable(
|
|||||||
|
|
||||||
if (indexer)
|
if (indexer)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauCheckImplicitNumbericKeys)
|
|
||||||
unify(numberType, indexer->indexType, value->location);
|
unify(numberType, indexer->indexType, value->location);
|
||||||
unify(valueType, indexer->indexResultType, value->location);
|
unify(valueType, indexer->indexResultType, value->location);
|
||||||
}
|
}
|
||||||
@ -2219,7 +2116,8 @@ TypeId TypeChecker::checkExprTable(
|
|||||||
if (errors.empty())
|
if (errors.empty())
|
||||||
exprType = expectedProp.type;
|
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);
|
ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, k->location);
|
||||||
if (errors.empty())
|
if (errors.empty())
|
||||||
@ -2259,8 +2157,6 @@ TypeId TypeChecker::checkExprTable(
|
|||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
|
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTableUseCounterInstead)
|
|
||||||
{
|
|
||||||
RecursionCounter _rc(&checkRecursionCount);
|
RecursionCounter _rc(&checkRecursionCount);
|
||||||
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
||||||
{
|
{
|
||||||
@ -2268,17 +2164,6 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTa
|
|||||||
return {errorRecoveryType(scope)};
|
return {errorRecoveryType(scope)};
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkExpr_(scope, expr, expectedType);
|
|
||||||
}
|
|
||||||
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);
|
std::vector<std::pair<TypeId, TypeId>> fieldTypes(expr.items.size);
|
||||||
|
|
||||||
const TableTypeVar* expectedTable = nullptr;
|
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())
|
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
|
||||||
expectedResultType = prop->second.type;
|
expectedResultType = prop->second.type;
|
||||||
|
else if (FFlag::LuauExpectedPropTypeFromIndexer && expectedIndexType && maybeString(*expectedIndexType))
|
||||||
|
expectedResultType = expectedIndexResultType;
|
||||||
}
|
}
|
||||||
else if (expectedUnion)
|
else if (expectedUnion)
|
||||||
{
|
{
|
||||||
@ -2529,7 +2416,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||||||
if (expr.op == AstExprBinary::Or && subexp->op == AstExprBinary::And)
|
if (expr.op == AstExprBinary::Or && subexp->op == AstExprBinary::And)
|
||||||
{
|
{
|
||||||
ScopePtr subScope = childScope(scope, subexp->location);
|
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);
|
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);
|
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
|
||||||
|
|
||||||
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy),
|
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||||
{AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
|
||||||
}
|
}
|
||||||
else if (expr.op == AstExprBinary::Or)
|
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);
|
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.
|
// 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)}}};
|
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||||
}
|
}
|
||||||
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
|
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))
|
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||||
return {booleanType, {std::move(*predicate)}};
|
return {booleanType, {std::move(*predicate)}};
|
||||||
|
|
||||||
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, 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=*/FFlag::LuauDiscriminableUnions2);
|
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
|
||||||
|
|
||||||
PredicateVec predicates;
|
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> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *expr.condition);
|
ExprResult<TypeId> result = checkExpr(scope, *expr.condition);
|
||||||
|
|
||||||
ScopePtr trueScope = childScope(scope, expr.trueExpr->location);
|
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);
|
ExprResult<TypeId> trueType = checkExpr(trueScope, *expr.trueExpr, expectedType);
|
||||||
|
|
||||||
ScopePtr falseScope = childScope(scope, expr.falseExpr->location);
|
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);
|
resolve(result.predicates, falseScope, false);
|
||||||
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
|
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
|
||||||
|
|
||||||
@ -3668,9 +3554,6 @@ void TypeChecker::checkArgumentList(
|
|||||||
else if (state.log.getMutable<ErrorTypeVar>(t))
|
else if (state.log.getMutable<ErrorTypeVar>(t))
|
||||||
{
|
{
|
||||||
} // ok
|
} // ok
|
||||||
else if (!FFlag::LuauAnyInIsOptionalIsOptional && isNonstrictMode() && state.log.get<AnyTypeVar>(t))
|
|
||||||
{
|
|
||||||
} // ok
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size_t minParams = getMinParameterCount(&state.log, paramPack);
|
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);
|
actualFunctionType = instantiate(scope, functionType, expr.func->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FFlag::LuauInstantiateFollows)
|
|
||||||
actualFunctionType = follow(actualFunctionType);
|
|
||||||
|
|
||||||
TypePackId retPack;
|
TypePackId retPack;
|
||||||
if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2)
|
if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2)
|
||||||
{
|
{
|
||||||
@ -4096,32 +3976,6 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||||||
{
|
{
|
||||||
state.log.commit();
|
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;
|
currentModule->astOverloadResolvedTypes[&expr] = fn;
|
||||||
|
|
||||||
// We select this overload
|
// We select this overload
|
||||||
@ -4525,7 +4379,7 @@ bool Instantiation::isDirty(TypeId ty)
|
|||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics)
|
if (ftv->hasNoGenerics)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -4582,7 +4436,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
|
|||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics)
|
if (ftv->hasNoGenerics)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// We aren't recursing in the case of a generic function which
|
// 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);
|
ty = follow(ty);
|
||||||
|
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty);
|
||||||
|
|
||||||
|
if (FFlag::LuauAlwaysQuantify)
|
||||||
|
{
|
||||||
|
if (ftv)
|
||||||
|
Luau::quantify(ty, scope->level);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
|
if (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
|
||||||
Luau::quantify(ty, scope->level);
|
Luau::quantify(ty, scope->level);
|
||||||
|
}
|
||||||
|
|
||||||
if (FFlag::LuauLowerBoundsCalculation && ftv)
|
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)
|
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>(ty);
|
||||||
{
|
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(FFlag::LuauInstantiateFollows ? ty : follow(ty));
|
|
||||||
if (ftv && ftv->hasNoGenerics)
|
if (ftv && ftv->hasNoGenerics)
|
||||||
return ty;
|
return ty;
|
||||||
}
|
|
||||||
|
|
||||||
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level};
|
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level};
|
||||||
|
|
||||||
@ -5392,10 +5251,9 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack
|
|||||||
|
|
||||||
bool ApplyTypeFunction::isDirty(TypeId ty)
|
bool ApplyTypeFunction::isDirty(TypeId ty)
|
||||||
{
|
{
|
||||||
// Really this should just replace the arguments,
|
if (FFlag::LuauApplyTypeFunctionFix && typeArguments.count(ty))
|
||||||
// but for bug-compatibility with existing code, we replace
|
return true;
|
||||||
// all generics.
|
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypeVar>(ty))
|
||||||
if (get<GenericTypeVar>(ty))
|
|
||||||
return true;
|
return true;
|
||||||
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
|
||||||
{
|
{
|
||||||
@ -5409,10 +5267,9 @@ bool ApplyTypeFunction::isDirty(TypeId ty)
|
|||||||
|
|
||||||
bool ApplyTypeFunction::isDirty(TypePackId tp)
|
bool ApplyTypeFunction::isDirty(TypePackId tp)
|
||||||
{
|
{
|
||||||
// Really this should just replace the arguments,
|
if (FFlag::LuauApplyTypeFunctionFix && typePackArguments.count(tp))
|
||||||
// but for bug-compatibility with existing code, we replace
|
return true;
|
||||||
// all generics.
|
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypePack>(tp))
|
||||||
if (get<GenericTypePack>(tp))
|
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
@ -5436,11 +5293,13 @@ bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
|
|||||||
|
|
||||||
TypeId ApplyTypeFunction::clean(TypeId ty)
|
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];
|
TypeId& arg = typeArguments[ty];
|
||||||
if (arg)
|
if (FFlag::LuauApplyTypeFunctionFix)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(arg);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
else if (arg)
|
||||||
return arg;
|
return arg;
|
||||||
else
|
else
|
||||||
return addType(FreeTypeVar{level});
|
return addType(FreeTypeVar{level});
|
||||||
@ -5448,11 +5307,13 @@ TypeId ApplyTypeFunction::clean(TypeId ty)
|
|||||||
|
|
||||||
TypePackId ApplyTypeFunction::clean(TypePackId tp)
|
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];
|
TypePackId& arg = typePackArguments[tp];
|
||||||
if (arg)
|
if (FFlag::LuauApplyTypeFunctionFix)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(arg);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
else if (arg)
|
||||||
return arg;
|
return arg;
|
||||||
else
|
else
|
||||||
return addTypePack(FreeTypePack{level});
|
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)
|
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2 || FFlag::LuauAssertStripsFalsyTypes);
|
|
||||||
|
|
||||||
const LValue* target = &lvalue;
|
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.
|
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 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.
|
// 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 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);
|
const Symbol symbol = getBaseSymbol(lvalue);
|
||||||
|
|
||||||
ScopePtr currentScope = scope;
|
ScopePtr currentScope = scope;
|
||||||
@ -5820,85 +5619,47 @@ static bool isUndecidable(TypeId ty)
|
|||||||
return get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(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, scope->refinements, scope, sense);
|
||||||
resolve(predicates, errVec, scope->refinements, scope, sense);
|
|
||||||
return errVec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
else if (auto eqP = get<EqPredicate>(predicate))
|
||||||
resolve(*eqP, errVec, refis, scope, sense);
|
resolve(*eqP, refis, scope, sense);
|
||||||
else
|
else
|
||||||
ice("Unhandled predicate kind");
|
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);
|
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
|
||||||
if (ty && fromOr)
|
if (ty && fromOr)
|
||||||
return addRefinement(refis, truthyP.lvalue, *ty);
|
return addRefinement(refis, truthyP.lvalue, *ty);
|
||||||
|
|
||||||
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense));
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (!sense)
|
||||||
{
|
{
|
||||||
@ -5907,14 +5668,14 @@ void TypeChecker::resolve(const AndPredicate& andP, ErrorVec& errVec, Refinement
|
|||||||
{NotPredicate{std::move(andP.rhs)}},
|
{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.lhs, refis, scope, sense);
|
||||||
resolve(andP.rhs, errVec, 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)
|
if (!sense)
|
||||||
{
|
{
|
||||||
@ -5923,28 +5684,24 @@ void TypeChecker::resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMa
|
|||||||
{NotPredicate{std::move(orP.rhs)}},
|
{NotPredicate{std::move(orP.rhs)}},
|
||||||
};
|
};
|
||||||
|
|
||||||
return resolve(andP, errVec, refis, scope, !sense);
|
return resolve(andP, refis, scope, !sense);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorVec discarded;
|
|
||||||
|
|
||||||
RefinementMap leftRefis;
|
RefinementMap leftRefis;
|
||||||
resolve(orP.lhs, errVec, leftRefis, scope, sense);
|
resolve(orP.lhs, leftRefis, scope, sense);
|
||||||
|
|
||||||
RefinementMap rightRefis;
|
RefinementMap rightRefis;
|
||||||
resolve(orP.lhs, discarded, rightRefis, scope, !sense);
|
resolve(orP.lhs, rightRefis, scope, !sense);
|
||||||
resolve(orP.rhs, errVec, rightRefis, scope, sense, true); // :(
|
resolve(orP.rhs, rightRefis, scope, sense, true); // :(
|
||||||
|
|
||||||
merge(refis, leftRefis);
|
merge(refis, leftRefis);
|
||||||
merge(refis, rightRefis);
|
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> {
|
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.
|
// 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 optionIsSubtype = canUnify(option, isaP.ty, isaP.location).empty();
|
||||||
bool targetIsSubtype = canUnify(isaP.ty, option, 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;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (FFlag::LuauDiscriminableUnions2)
|
|
||||||
{
|
|
||||||
refineLValue(isaP.lvalue, refis, scope, predicate);
|
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}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Rewrite the predicate 'type(foo) == "vector"' to be 'typeof(foo) == "Vector3"'. They're exactly identical.
|
||||||
// This allows us to avoid writing in edge cases.
|
// This allows us to avoid writing in edge cases.
|
||||||
if (!typeguardP.isTypeof && typeguardP.kind == "vector")
|
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);
|
std::optional<TypeId> ty = resolveLValue(refis, scope, typeguardP.lvalue);
|
||||||
if (!ty)
|
if (!ty)
|
||||||
@ -6059,53 +5799,30 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
|
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
|
||||||
{
|
|
||||||
if (FFlag::LuauDiscriminableUnions2)
|
|
||||||
{
|
{
|
||||||
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
|
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
|
||||||
return;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto fail = [&](const TypeErrorData& err) {
|
|
||||||
if (!FFlag::LuauDiscriminableUnions2)
|
|
||||||
errVec.push_back(TypeError{typeguardP.location, err});
|
|
||||||
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!typeguardP.isTypeof)
|
if (!typeguardP.isTypeof)
|
||||||
return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type});
|
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||||
|
|
||||||
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
||||||
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
|
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);
|
TypeId type = follow(typeFun->type);
|
||||||
|
|
||||||
// We're only interested in the root class of any classes.
|
// We're only interested in the root class of any classes.
|
||||||
if (auto ctv = get<ClassTypeVar>(type); !ctv || ctv->parent)
|
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.
|
// 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.
|
// 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.
|
// 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> {
|
auto options = [](TypeId ty) -> std::vector<TypeId> {
|
||||||
@ -6114,8 +5831,6 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
|
|||||||
return {ty};
|
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))
|
if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable))
|
||||||
@ -6143,53 +5858,6 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
|
|||||||
};
|
};
|
||||||
|
|
||||||
refineLValue(eqP.lvalue, refis, scope, predicate);
|
refineLValue(eqP.lvalue, refis, scope, predicate);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (FFlag::LuauWeakEqConstraint)
|
|
||||||
{
|
|
||||||
if (!sense && isNil(eqP.type))
|
|
||||||
resolve(TruthyPredicate{std::move(eqP.lvalue), eqP.location}, errVec, refis, scope, true, /* fromOr= */ false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FFlag::LuauEqConstraint)
|
|
||||||
{
|
|
||||||
std::optional<TypeId> ty = resolveLValue(refis, scope, eqP.lvalue);
|
|
||||||
if (!ty)
|
|
||||||
return;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::isNonstrictMode() const
|
bool TypeChecker::isNonstrictMode() const
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -55,13 +53,10 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
|
|||||||
{
|
{
|
||||||
TypeId index = follow(*mtIndex);
|
TypeId index = follow(*mtIndex);
|
||||||
|
|
||||||
if (FFlag::LuauTerminateCyclicMetatableIndexLookup)
|
|
||||||
{
|
|
||||||
if (count >= 100)
|
if (count >= 100)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
++count;
|
++count;
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto& itt = getTableType(index))
|
if (const auto& itt = getTableType(index))
|
||||||
{
|
{
|
||||||
|
@ -24,8 +24,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -204,14 +202,14 @@ bool isOptional(TypeId ty)
|
|||||||
|
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
|
if (get<AnyTypeVar>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto utv = get<UnionTypeVar>(ty);
|
auto utv = get<UnionTypeVar>(ty);
|
||||||
if (!utv)
|
if (!utv)
|
||||||
return false;
|
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)
|
bool isTableIntersection(TypeId ty)
|
||||||
@ -378,8 +376,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
|||||||
if (seen.contains(ty))
|
if (seen.contains(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
|
if (isString(ty) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
||||||
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (auto uty = get<UnionTypeVar>(ty))
|
if (auto uty = get<UnionTypeVar>(ty))
|
||||||
|
@ -24,8 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
|
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
|
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||||
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
|
||||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -382,19 +380,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
|
|||||||
LUAU_ASSERT(sharedState.iceHandler);
|
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)
|
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
||||||
{
|
{
|
||||||
sharedState.counters.iterationCount = 0;
|
sharedState.counters.iterationCount = 0;
|
||||||
@ -1219,14 +1204,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
continue;
|
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))
|
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
||||||
{
|
{
|
||||||
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
||||||
@ -1454,22 +1431,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
{
|
{
|
||||||
auto subIter = subTable->props.find(propName);
|
auto subIter = subTable->props.find(propName);
|
||||||
|
|
||||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
|
||||||
{
|
!isOptional(superProp.type))
|
||||||
if (subIter == subTable->props.end() &&
|
|
||||||
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type))
|
|
||||||
missingProperties.push_back(propName);
|
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 (!missingProperties.empty())
|
if (!missingProperties.empty())
|
||||||
{
|
{
|
||||||
@ -1485,19 +1450,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
{
|
{
|
||||||
auto superIter = superTable->props.find(propName);
|
auto superIter = superTable->props.find(propName);
|
||||||
|
|
||||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
|
||||||
{
|
|
||||||
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
|
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
|
||||||
extraProperties.push_back(propName);
|
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 (!extraProperties.empty())
|
if (!extraProperties.empty())
|
||||||
{
|
{
|
||||||
@ -1540,21 +1495,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
if (innerState.errors.empty())
|
if (innerState.errors.empty())
|
||||||
log.concat(std::move(innerState.log));
|
log.concat(std::move(innerState.log));
|
||||||
}
|
}
|
||||||
else if (FFlag::LuauAnyInIsOptionalIsOptional &&
|
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
|
||||||
(!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? }`
|
// 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`.
|
// 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?)
|
// 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)
|
else if (subTable->state == TableState::Free)
|
||||||
{
|
{
|
||||||
PendingType* pendingSub = log.queue(subTy);
|
PendingType* pendingSub = log.queue(subTy);
|
||||||
@ -1618,10 +1564,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
else if (variance == Covariant)
|
else if (variance == Covariant)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (FFlag::LuauAnyInIsOptionalIsOptional && !FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
|
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
|
||||||
{
|
|
||||||
}
|
|
||||||
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (superTable->state == TableState::Free)
|
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)
|
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
if (!FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
|
if (isOptional(ty))
|
||||||
return ty;
|
|
||||||
else if (isOptional(ty))
|
|
||||||
return ty;
|
return ty;
|
||||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
@ -2666,16 +2607,9 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
|||||||
|
|
||||||
Unifier Unifier::makeChildUnifier()
|
Unifier Unifier::makeChildUnifier()
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypecheckOptPass)
|
|
||||||
{
|
|
||||||
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
|
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
|
||||||
u.anyIsTop = anyIsTop;
|
u.anyIsTop = anyIsTop;
|
||||||
return u;
|
return u;
|
||||||
}
|
|
||||||
|
|
||||||
Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
|
|
||||||
u.anyIsTop = anyIsTop;
|
|
||||||
return u;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A utility function that appends the given error to the unifier's error log.
|
// A utility function that appends the given error to the unifier's error log.
|
||||||
|
@ -224,6 +224,7 @@ private:
|
|||||||
|
|
||||||
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
|
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
|
||||||
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
|
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
|
||||||
|
DenseHashMap<uint32_t, int16_t> protoMap;
|
||||||
|
|
||||||
int debugLine = 0;
|
int debugLine = 0;
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauCompileNestedClosureO2)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -181,6 +183,7 @@ size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
|
|||||||
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
||||||
: constantMap({Constant::Type_Nil, ~0ull})
|
: constantMap({Constant::Type_Nil, ~0ull})
|
||||||
, tableShapeMap(TableShape())
|
, tableShapeMap(TableShape())
|
||||||
|
, protoMap(~0u)
|
||||||
, stringTable({nullptr, 0})
|
, stringTable({nullptr, 0})
|
||||||
, encoder(encoder)
|
, encoder(encoder)
|
||||||
{
|
{
|
||||||
@ -250,6 +253,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
|||||||
|
|
||||||
constantMap.clear();
|
constantMap.clear();
|
||||||
tableShapeMap.clear();
|
tableShapeMap.clear();
|
||||||
|
protoMap.clear();
|
||||||
|
|
||||||
debugRemarks.clear();
|
debugRemarks.clear();
|
||||||
debugRemarkBuffer.clear();
|
debugRemarkBuffer.clear();
|
||||||
@ -372,11 +376,17 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid)
|
|||||||
|
|
||||||
int16_t BytecodeBuilder::addChildFunction(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());
|
uint32_t id = uint32_t(protos.size());
|
||||||
|
|
||||||
if (id >= kMaxClosureCount)
|
if (id >= kMaxClosureCount)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (FFlag::LuauCompileNestedClosureO2)
|
||||||
|
protoMap[fid] = int16_t(id);
|
||||||
protos.push_back(fid);
|
protos.push_back(fid);
|
||||||
|
|
||||||
return int16_t(id);
|
return int16_t(id);
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileSupportInlining, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
|
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
|
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
|
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
|
||||||
@ -30,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
|||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileNestedClosureO2, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -100,13 +100,11 @@ struct Compiler
|
|||||||
upvals.reserve(16);
|
upvals.reserve(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getLocal(AstLocal* local)
|
int getLocalReg(AstLocal* local)
|
||||||
{
|
{
|
||||||
Local* l = locals.find(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)
|
uint8_t getUpval(AstLocal* local)
|
||||||
@ -159,17 +157,19 @@ struct Compiler
|
|||||||
|
|
||||||
AstExprFunction* getFunctionExpr(AstExpr* node)
|
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)
|
if (!lv || lv->written || !lv->init)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return getFunctionExpr(lv->init);
|
return getFunctionExpr(lv->init);
|
||||||
}
|
}
|
||||||
else if (AstExprGroup* ge = node->as<AstExprGroup>())
|
else if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||||
return getFunctionExpr(ge->expr);
|
return getFunctionExpr(expr->expr);
|
||||||
|
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
|
||||||
|
return getFunctionExpr(expr->expr);
|
||||||
else
|
else
|
||||||
return node->as<AstExprFunction>();
|
return node->as<AstExprFunction>();
|
||||||
}
|
}
|
||||||
@ -180,13 +180,13 @@ struct Compiler
|
|||||||
{
|
{
|
||||||
bool result = true;
|
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)
|
if (!FFlag::LuauCompileNestedClosureO2)
|
||||||
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
|
result = false;
|
||||||
// TODO: additionally we would need to change upvalue handling in compileExprFunction to handle upvalue->local migration
|
|
||||||
result = result && !node->is<AstExprFunction>();
|
// short-circuit to avoid analyzing nested closure bodies
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(AstStat* node) override
|
bool visit(AstStat* node) override
|
||||||
@ -275,8 +275,7 @@ struct Compiler
|
|||||||
f.upvals = upvals;
|
f.upvals = upvals;
|
||||||
|
|
||||||
// record information for inlining
|
// record information for inlining
|
||||||
if (FFlag::LuauCompileSupportInlining && options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) &&
|
if (options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && !getfenvUsed && !setfenvUsed)
|
||||||
!getfenvUsed && !setfenvUsed)
|
|
||||||
{
|
{
|
||||||
f.canInline = true;
|
f.canInline = true;
|
||||||
f.stackSize = stackSize;
|
f.stackSize = stackSize;
|
||||||
@ -346,8 +345,8 @@ struct Compiler
|
|||||||
|
|
||||||
uint8_t argreg;
|
uint8_t argreg;
|
||||||
|
|
||||||
if (isExprLocalReg(arg))
|
if (int reg = getExprLocalReg(arg); reg >= 0)
|
||||||
argreg = getLocal(arg->as<AstExprLocal>()->local);
|
argreg = uint8_t(reg);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
argreg = uint8_t(regs + 1);
|
argreg = uint8_t(regs + 1);
|
||||||
@ -403,8 +402,8 @@ struct Compiler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExprLocalReg(expr->args.data[i]))
|
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
||||||
args[i] = getLocal(expr->args.data[i]->as<AstExprLocal>()->local);
|
args[i] = uint8_t(reg);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
args[i] = uint8_t(regs + 1 + i);
|
args[i] = uint8_t(regs + 1 + i);
|
||||||
@ -489,19 +488,18 @@ struct Compiler
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we can compile functions with mismatching arity at call site but it's more annoying
|
// compute constant bitvector for all arguments to feed the cost model
|
||||||
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
|
|
||||||
bool varc[8] = {};
|
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]);
|
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 baselineCost = computeCost(fi->costModel, nullptr, 0) + 3;
|
||||||
int inlineProfit = (inlinedCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / inlinedCost);
|
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)
|
for (size_t i = 0; i < func->args.size; ++i)
|
||||||
{
|
{
|
||||||
AstLocal* var = func->args.data[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
|
// if the argument is mutated, we need to allocate a fresh register even if it's a constant
|
||||||
uint8_t reg = allocReg(arg, 1);
|
uint8_t reg = allocReg(arg, 1);
|
||||||
|
|
||||||
|
if (arg)
|
||||||
compileExprTemp(arg, reg);
|
compileExprTemp(arg, reg);
|
||||||
|
else
|
||||||
|
bytecode.emitABC(LOP_LOADNIL, reg, 0, 0);
|
||||||
|
|
||||||
pushLocal(var, reg);
|
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)
|
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
|
// 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;
|
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 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, uint8_t(reg));
|
||||||
pushLocal(var, reg);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uint8_t reg = allocReg(arg, 1);
|
uint8_t temp = allocReg(arg, 1);
|
||||||
compileExprTemp(arg, reg);
|
compileExprTemp(arg, temp);
|
||||||
pushLocal(var, reg);
|
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
|
// fold constant values updated above into expressions in the function body
|
||||||
foldConstants(constants, variables, locstants, func->body);
|
foldConstants(constants, variables, locstants, func->body);
|
||||||
|
|
||||||
@ -627,12 +660,15 @@ struct Compiler
|
|||||||
FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth))
|
FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth))
|
||||||
return;
|
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)
|
if (func->vararg)
|
||||||
bytecode.addDebugRemark("inlining failed: function is variadic");
|
bytecode.addDebugRemark("inlining failed: function is variadic");
|
||||||
else
|
else if (fi)
|
||||||
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
|
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);
|
LUAU_ASSERT(fi);
|
||||||
|
|
||||||
// Optimization: use local register directly in NAMECALL if possible
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@ -785,6 +821,8 @@ struct Compiler
|
|||||||
|
|
||||||
void compileExprFunction(AstExprFunction* expr, uint8_t target)
|
void compileExprFunction(AstExprFunction* expr, uint8_t target)
|
||||||
{
|
{
|
||||||
|
RegScope rs(this);
|
||||||
|
|
||||||
const Function* f = functions.find(expr);
|
const Function* f = functions.find(expr);
|
||||||
LUAU_ASSERT(f);
|
LUAU_ASSERT(f);
|
||||||
|
|
||||||
@ -795,6 +833,67 @@ struct Compiler
|
|||||||
if (pid < 0)
|
if (pid < 0)
|
||||||
CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile");
|
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;
|
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
|
// 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)
|
if (uv->functionDepth == expr->functionDepth - 1)
|
||||||
{
|
{
|
||||||
// get local variable
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@ -1213,10 +1313,10 @@ struct Compiler
|
|||||||
if (!isConditionFast(expr->left))
|
if (!isConditionFast(expr->left))
|
||||||
{
|
{
|
||||||
// Optimization: when right hand side is a local variable, we can use AND/OR
|
// 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 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);
|
bytecode.emitABC(and_ ? LOP_AND : LOP_OR, target, lr, rr);
|
||||||
return;
|
return;
|
||||||
@ -1803,19 +1903,18 @@ struct Compiler
|
|||||||
}
|
}
|
||||||
else if (AstExprLocal* expr = node->as<AstExprLocal>())
|
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);
|
LUAU_ASSERT(expr->upvalue);
|
||||||
uint8_t uid = getUpval(expr->local);
|
uint8_t uid = getUpval(expr->local);
|
||||||
|
|
||||||
bytecode.emitABC(LOP_GETUPVAL, target, uid, 0);
|
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>())
|
else if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||||
{
|
{
|
||||||
@ -1879,8 +1978,8 @@ struct Compiler
|
|||||||
uint8_t compileExprAuto(AstExpr* node, RegScope&)
|
uint8_t compileExprAuto(AstExpr* node, RegScope&)
|
||||||
{
|
{
|
||||||
// Optimization: directly return locals instead of copying them to a temporary
|
// Optimization: directly return locals instead of copying them to a temporary
|
||||||
if (isExprLocalReg(node))
|
if (int reg = getExprLocalReg(node); reg >= 0)
|
||||||
return getLocal(node->as<AstExprLocal>()->local);
|
return uint8_t(reg);
|
||||||
|
|
||||||
// note: the register is owned by the parent scope
|
// note: the register is owned by the parent scope
|
||||||
uint8_t reg = allocReg(node, 1);
|
uint8_t reg = allocReg(node, 1);
|
||||||
@ -1910,7 +2009,7 @@ struct Compiler
|
|||||||
for (size_t i = 0; i < targetCount; ++i)
|
for (size_t i = 0; i < targetCount; ++i)
|
||||||
compileExprTemp(list.data[i], uint8_t(target + 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)
|
for (size_t i = targetCount; i < list.size; ++i)
|
||||||
{
|
{
|
||||||
RegScope rsi(this);
|
RegScope rsi(this);
|
||||||
@ -2008,20 +2107,21 @@ struct Compiler
|
|||||||
|
|
||||||
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
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_Local};
|
||||||
|
result.reg = uint8_t(reg);
|
||||||
LValue result = {LValue::Kind_Upvalue};
|
|
||||||
result.upval = getUpval(expr->local);
|
|
||||||
result.location = node->location;
|
result.location = node->location;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LValue result = {LValue::Kind_Local};
|
LUAU_ASSERT(expr->upvalue);
|
||||||
result.reg = getLocal(expr->local);
|
|
||||||
|
LValue result = {LValue::Kind_Upvalue};
|
||||||
|
result.upval = getUpval(expr->local);
|
||||||
result.location = node->location;
|
result.location = node->location;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -2115,15 +2215,21 @@ struct Compiler
|
|||||||
compileLValueUse(lv, source, /* set= */ true);
|
compileLValueUse(lv, source, /* set= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isExprLocalReg(AstExpr* expr)
|
int getExprLocalReg(AstExpr* node)
|
||||||
{
|
{
|
||||||
AstExprLocal* le = expr->as<AstExprLocal>();
|
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
if (!le || (!FFlag::LuauCompileSupportInlining && le->upvalue))
|
{
|
||||||
return false;
|
// 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 ? l->reg : -1;
|
||||||
|
}
|
||||||
return l && l->allocated;
|
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)
|
bool isStatBreak(AstStat* node)
|
||||||
@ -2352,21 +2458,18 @@ struct Compiler
|
|||||||
|
|
||||||
// Optimization: return locals directly instead of copying them into a temporary
|
// 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
|
// 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;
|
consecutive = true;
|
||||||
|
|
||||||
for (size_t i = 1; i < stat->list.size; ++i)
|
for (size_t i = 1; i < stat->list.size; ++i)
|
||||||
{
|
if (getExprLocalReg(stat->list.data[i]) != int(temp + i))
|
||||||
AstExpr* v = stat->list.data[i];
|
|
||||||
if (!isExprLocalReg(v) || getLocal(v->as<AstExprLocal>()->local) != temp + i)
|
|
||||||
{
|
{
|
||||||
consecutive = false;
|
consecutive = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!consecutive && stat->list.size > 0)
|
if (!consecutive && stat->list.size > 0)
|
||||||
{
|
{
|
||||||
@ -2438,12 +2541,13 @@ struct Compiler
|
|||||||
{
|
{
|
||||||
bool result = true;
|
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)
|
if (!FFlag::LuauCompileNestedClosureO2)
|
||||||
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
|
result = false;
|
||||||
result = result && !node->is<AstExprFunction>();
|
|
||||||
return result;
|
// short-circuit to avoid analyzing nested closure bodies
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(AstStat* node) override
|
bool visit(AstStat* node) override
|
||||||
@ -2874,12 +2978,9 @@ struct Compiler
|
|||||||
void compileStatFunction(AstStatFunction* stat)
|
void compileStatFunction(AstStatFunction* stat)
|
||||||
{
|
{
|
||||||
// Optimization: compile value expresion directly into target local register
|
// 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>();
|
compileExpr(stat->func, uint8_t(reg));
|
||||||
LUAU_ASSERT(le);
|
|
||||||
|
|
||||||
compileExpr(stat->func, getLocal(le->local));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3399,6 +3500,12 @@ struct Compiler
|
|||||||
std::vector<size_t> returnJumps;
|
std::vector<size_t> returnJumps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Capture
|
||||||
|
{
|
||||||
|
LuauCaptureType type;
|
||||||
|
uint8_t data;
|
||||||
|
};
|
||||||
|
|
||||||
BytecodeBuilder& bytecode;
|
BytecodeBuilder& bytecode;
|
||||||
|
|
||||||
CompileOptions options;
|
CompileOptions options;
|
||||||
@ -3422,6 +3529,7 @@ struct Compiler
|
|||||||
std::vector<LoopJump> loopJumps;
|
std::vector<LoopJump> loopJumps;
|
||||||
std::vector<Loop> loops;
|
std::vector<Loop> loops;
|
||||||
std::vector<InlineFrame> inlineFrames;
|
std::vector<InlineFrame> inlineFrames;
|
||||||
|
std::vector<Capture> captures;
|
||||||
};
|
};
|
||||||
|
|
||||||
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options)
|
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());
|
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName());
|
||||||
uint32_t mainid = compiler.compileFunction(&main);
|
uint32_t mainid = compiler.compileFunction(&main);
|
||||||
|
|
||||||
|
const Compiler::Function* mainf = compiler.functions.find(&main);
|
||||||
|
LUAU_ASSERT(mainf && mainf->upvals.empty());
|
||||||
|
|
||||||
bytecode.setMainFunction(mainid);
|
bytecode.setMainFunction(mainid);
|
||||||
bytecode.finalize();
|
bytecode.finalize();
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauCompileSupportInlining)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace Compile
|
namespace Compile
|
||||||
@ -330,7 +328,7 @@ struct ConstantVisitor : AstVisitor
|
|||||||
{
|
{
|
||||||
if (value.type != Constant::Type_Unknown)
|
if (value.type != Constant::Type_Unknown)
|
||||||
map[key] = value;
|
map[key] = value;
|
||||||
else if (!FFlag::LuauCompileSupportInlining || wasEmpty)
|
else if (wasEmpty)
|
||||||
;
|
;
|
||||||
else if (Constant* old = map.find(key))
|
else if (Constant* old = map.find(key))
|
||||||
old->type = Constant::Type_Unknown;
|
old->type = Constant::Type_Unknown;
|
||||||
|
@ -73,6 +73,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||||||
Analysis/include/Luau/ToString.h
|
Analysis/include/Luau/ToString.h
|
||||||
Analysis/include/Luau/Transpiler.h
|
Analysis/include/Luau/Transpiler.h
|
||||||
Analysis/include/Luau/TxnLog.h
|
Analysis/include/Luau/TxnLog.h
|
||||||
|
Analysis/include/Luau/TypeArena.h
|
||||||
Analysis/include/Luau/TypeAttach.h
|
Analysis/include/Luau/TypeAttach.h
|
||||||
Analysis/include/Luau/TypedAllocator.h
|
Analysis/include/Luau/TypedAllocator.h
|
||||||
Analysis/include/Luau/TypeInfer.h
|
Analysis/include/Luau/TypeInfer.h
|
||||||
@ -108,6 +109,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||||||
Analysis/src/ToString.cpp
|
Analysis/src/ToString.cpp
|
||||||
Analysis/src/Transpiler.cpp
|
Analysis/src/Transpiler.cpp
|
||||||
Analysis/src/TxnLog.cpp
|
Analysis/src/TxnLog.cpp
|
||||||
|
Analysis/src/TypeArena.cpp
|
||||||
Analysis/src/TypeAttach.cpp
|
Analysis/src/TypeAttach.cpp
|
||||||
Analysis/src/TypedAllocator.cpp
|
Analysis/src/TypedAllocator.cpp
|
||||||
Analysis/src/TypeInfer.cpp
|
Analysis/src/TypeInfer.cpp
|
||||||
|
@ -10,10 +10,6 @@
|
|||||||
#include "ldebug.h"
|
#include "ldebug.h"
|
||||||
#include "lvm.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)
|
static int foreachi(lua_State* L)
|
||||||
{
|
{
|
||||||
luaL_checktype(L, 1, LUA_TTABLE);
|
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 */
|
int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */
|
||||||
luaL_checktype(L, tt, LUA_TTABLE);
|
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)
|
if (e >= f)
|
||||||
{ /* otherwise, nothing to move */
|
{ /* otherwise, nothing to move */
|
||||||
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");
|
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIter, false)
|
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
|
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
@ -157,17 +154,6 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c)
|
|||||||
StkId ra = &L->base[a];
|
StkId ra = &L->base[a];
|
||||||
LUAU_ASSERT(ra + 3 <= L->top);
|
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 + 2, ra + 2);
|
||||||
setobjs2s(L, ra + 3 + 1, ra + 1);
|
setobjs2s(L, ra + 3 + 1, ra + 1);
|
||||||
setobjs2s(L, ra + 3, ra);
|
setobjs2s(L, ra + 3, ra);
|
||||||
|
@ -2772,6 +2772,8 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
type tag = "cat" | "dog"
|
type tag = "cat" | "dog"
|
||||||
local function f(a: tag) end
|
local function f(a: tag) end
|
||||||
@ -2844,6 +2846,8 @@ f(@1)
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
type tag = "strange\t\"cat\"" | 'nice\t"dog"'
|
type tag = "strange\t\"cat\"" | 'nice\t"dog"'
|
||||||
local function f(x: tag) end
|
local function f(x: tag) end
|
||||||
|
@ -4269,22 +4269,26 @@ FORNLOOP R3 -6
|
|||||||
FORNLOOP R0 -11
|
FORNLOOP R0 -11
|
||||||
RETURN R0 0
|
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"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
for i=1,1 do
|
for i=1,2 do
|
||||||
local x = function() return i end
|
local x = function() return i end
|
||||||
end
|
end
|
||||||
)",
|
)",
|
||||||
1, 2),
|
1, 2),
|
||||||
R"(
|
R"(
|
||||||
LOADN R2 1
|
|
||||||
LOADN R0 1
|
|
||||||
LOADN R1 1
|
LOADN R1 1
|
||||||
FORNPREP R0 +3
|
NEWCLOSURE R0 P0
|
||||||
NEWCLOSURE R3 P0
|
CAPTURE VAL R1
|
||||||
CAPTURE VAL R2
|
LOADN R1 2
|
||||||
FORNLOOP R0 -3
|
NEWCLOSURE R0 P0
|
||||||
|
CAPTURE VAL R1
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
@ -4469,8 +4473,6 @@ RETURN R0 0
|
|||||||
|
|
||||||
TEST_CASE("InlineBasic")
|
TEST_CASE("InlineBasic")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// inline function that returns a constant
|
// inline function that returns a constant
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo()
|
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")
|
TEST_CASE("InlineMutate")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// if the argument is mutated, it gets a register even if the value is constant
|
// if the argument is mutated, it gets a register even if the value is constant
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a)
|
local function foo(a)
|
||||||
@ -4636,8 +4700,6 @@ RETURN R1 1
|
|||||||
|
|
||||||
TEST_CASE("InlineUpval")
|
TEST_CASE("InlineUpval")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// if the argument is an upvalue, we naturally need to copy it to a local
|
// if the argument is an upvalue, we naturally need to copy it to a local
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a)
|
local function foo(a)
|
||||||
@ -4705,8 +4767,6 @@ RETURN R1 1
|
|||||||
|
|
||||||
TEST_CASE("InlineFallthrough")
|
TEST_CASE("InlineFallthrough")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// if the function doesn't return, we still fill the results with nil
|
// if the function doesn't return, we still fill the results with nil
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo()
|
local function foo()
|
||||||
@ -4759,8 +4819,6 @@ RETURN R1 -1
|
|||||||
|
|
||||||
TEST_CASE("InlineCapture")
|
TEST_CASE("InlineCapture")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// can't inline function with nested functions that capture locals because they might be constants
|
// can't inline function with nested functions that capture locals because they might be constants
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a)
|
local function foo(a)
|
||||||
@ -4782,12 +4840,9 @@ RETURN R2 -1
|
|||||||
|
|
||||||
TEST_CASE("InlineArgMismatch")
|
TEST_CASE("InlineArgMismatch")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// when inlining a function, we must respect all the usual rules
|
// when inlining a function, we must respect all the usual rules
|
||||||
|
|
||||||
// caller might not have enough arguments
|
// caller might not have enough arguments
|
||||||
// TODO: we don't inline this atm
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a)
|
local function foo(a)
|
||||||
return a
|
return a
|
||||||
@ -4799,13 +4854,11 @@ return x
|
|||||||
1, 2),
|
1, 2),
|
||||||
R"(
|
R"(
|
||||||
DUPCLOSURE R0 K0
|
DUPCLOSURE R0 K0
|
||||||
MOVE R1 R0
|
LOADNIL R1
|
||||||
CALL R1 0 1
|
|
||||||
RETURN R1 1
|
RETURN R1 1
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// caller might be using multret for arguments
|
// caller might be using multret for arguments
|
||||||
// TODO: we don't inline this atm
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a, b)
|
local function foo(a, b)
|
||||||
return a + b
|
return a + b
|
||||||
@ -4817,17 +4870,32 @@ return x
|
|||||||
1, 2),
|
1, 2),
|
||||||
R"(
|
R"(
|
||||||
DUPCLOSURE R0 K0
|
DUPCLOSURE R0 K0
|
||||||
MOVE R1 R0
|
|
||||||
LOADK R3 K1
|
LOADK R3 K1
|
||||||
FASTCALL1 20 R3 +2
|
FASTCALL1 20 R3 +2
|
||||||
GETIMPORT R2 4
|
GETIMPORT R2 4
|
||||||
CALL R2 1 -1
|
CALL R2 1 2
|
||||||
CALL R1 -1 1
|
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
|
RETURN R1 1
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// caller might have too many arguments, but we still need to compute them for side effects
|
// 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"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a)
|
local function foo(a)
|
||||||
return a
|
return a
|
||||||
@ -4839,19 +4907,34 @@ return x
|
|||||||
1, 2),
|
1, 2),
|
||||||
R"(
|
R"(
|
||||||
DUPCLOSURE R0 K0
|
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
|
LOADN R2 42
|
||||||
GETIMPORT R3 2
|
MOVE R1 R2
|
||||||
CALL R3 0 -1
|
|
||||||
CALL R1 -1 1
|
|
||||||
RETURN R1 1
|
RETURN R1 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("InlineMultiple")
|
TEST_CASE("InlineMultiple")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// we call this with a different set of variable/constant args
|
// we call this with a different set of variable/constant args
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a, b)
|
local function foo(a, b)
|
||||||
@ -4880,8 +4963,6 @@ RETURN R3 4
|
|||||||
|
|
||||||
TEST_CASE("InlineChain")
|
TEST_CASE("InlineChain")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// inline a chain of functions
|
// inline a chain of functions
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a, b)
|
local function foo(a, b)
|
||||||
@ -4912,8 +4993,6 @@ RETURN R3 1
|
|||||||
|
|
||||||
TEST_CASE("InlineThresholds")
|
TEST_CASE("InlineThresholds")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
ScopedFastInt sfis[] = {
|
ScopedFastInt sfis[] = {
|
||||||
{"LuauCompileInlineThreshold", 25},
|
{"LuauCompileInlineThreshold", 25},
|
||||||
{"LuauCompileInlineThresholdMaxBoost", 300},
|
{"LuauCompileInlineThresholdMaxBoost", 300},
|
||||||
@ -4988,8 +5067,6 @@ RETURN R3 1
|
|||||||
|
|
||||||
TEST_CASE("InlineIIFE")
|
TEST_CASE("InlineIIFE")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// IIFE with arguments
|
// IIFE with arguments
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
function choose(a, b, c)
|
function choose(a, b, c)
|
||||||
@ -5025,8 +5102,6 @@ RETURN R3 1
|
|||||||
|
|
||||||
TEST_CASE("InlineRecurseArguments")
|
TEST_CASE("InlineRecurseArguments")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
// we can't inline a function if it's used to compute its own arguments
|
// we can't inline a function if it's used to compute its own arguments
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function foo(a, b)
|
local function foo(a, b)
|
||||||
@ -5036,22 +5111,20 @@ foo(foo(foo,foo(foo,foo))[foo])
|
|||||||
1, 2),
|
1, 2),
|
||||||
R"(
|
R"(
|
||||||
DUPCLOSURE R0 K0
|
DUPCLOSURE R0 K0
|
||||||
MOVE R1 R0
|
MOVE R2 R0
|
||||||
|
MOVE R3 R0
|
||||||
MOVE R4 R0
|
MOVE R4 R0
|
||||||
MOVE R5 R0
|
MOVE R5 R0
|
||||||
MOVE R6 R0
|
MOVE R6 R0
|
||||||
CALL R4 2 1
|
CALL R4 2 -1
|
||||||
LOADNIL R3
|
CALL R2 -1 1
|
||||||
GETTABLE R2 R3 R0
|
GETTABLE R1 R2 R0
|
||||||
CALL R1 1 0
|
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("InlineFastCallK")
|
TEST_CASE("InlineFastCallK")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local function set(l0)
|
local function set(l0)
|
||||||
rawset({}, l0)
|
rawset({}, l0)
|
||||||
@ -5080,8 +5153,6 @@ RETURN R0 0
|
|||||||
|
|
||||||
TEST_CASE("InlineExprIndexK")
|
TEST_CASE("InlineExprIndexK")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileSupportInlining", true);
|
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local _ = function(l0)
|
local _ = function(l0)
|
||||||
local _ = nil
|
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")
|
TEST_CASE("ReturnConsecutive")
|
||||||
{
|
{
|
||||||
// we can return a single local directly
|
// we can return a single local directly
|
||||||
@ -5193,6 +5316,16 @@ return
|
|||||||
)"),
|
)"),
|
||||||
R"(
|
R"(
|
||||||
RETURN R0 0
|
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")
|
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauLosslessClone", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
|
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
|
||||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||||
|
|
||||||
@ -218,8 +214,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauLosslessClone", true};
|
|
||||||
|
|
||||||
TypeVar tableTy{TableTypeVar{}};
|
TypeVar tableTy{TableTypeVar{}};
|
||||||
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
||||||
ttv->state = TableState::Free;
|
ttv->state = TableState::Free;
|
||||||
@ -252,8 +246,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
|
||||||
|
|
||||||
fileResolver.source["Module/A"] = R"(
|
fileResolver.source["Module/A"] = R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
local a = {}
|
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")
|
TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
local T = {}
|
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")
|
TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!nonstrict
|
--!nonstrict
|
||||||
local T = {}
|
local T = {}
|
||||||
|
@ -683,6 +683,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauNormalizeFlagIsConservative", false}
|
||||||
};
|
};
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
@ -697,6 +698,26 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
|
|||||||
CHECK(t->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")
|
TEST_CASE_FIXTURE(Fixture, "variadic_tail_is_marked_normal")
|
||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
@ -997,4 +1018,28 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounde
|
|||||||
LUAU_REQUIRE_ERRORS(result);
|
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();
|
TEST_SUITE_END();
|
||||||
|
@ -22,8 +22,6 @@ struct LimitFixture : BuiltinsFixture
|
|||||||
#if defined(_NOOPT) || defined(_DEBUG)
|
#if defined(_NOOPT) || defined(_DEBUG)
|
||||||
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ScopedFastFlag LuauJustOneCallFrameForHaveSeen{"LuauJustOneCallFrameForHaveSeen", true};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
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));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
||||||
{
|
{
|
||||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
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));
|
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauAssertStripsFalsyTypes", true},
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauWidenIfSupertypeIsFree2", true},
|
{"LuauWidenIfSupertypeIsFree2", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -899,8 +897,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
|||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauAssertStripsFalsyTypes", true},
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauWidenIfSupertypeIsFree2", 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")
|
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"(
|
CheckResult result = check(R"(
|
||||||
local function f(...: number?)
|
local function f(...: number?)
|
||||||
return assert(...)
|
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")
|
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"(
|
CheckResult result = check(R"(
|
||||||
local function f(x: nil)
|
local function f(x: nil)
|
||||||
return assert(x, "hmm")
|
return assert(x, "hmm")
|
||||||
|
@ -1496,8 +1496,6 @@ caused by:
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
|
TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(x: any) end
|
local function f(x: any) end
|
||||||
f()
|
f()
|
||||||
|
@ -1121,4 +1121,78 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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();
|
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")
|
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {x: {y: {z: {thing: string}}}}
|
type A = {x: {y: {z: {thing: string}}}}
|
||||||
type B = {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")
|
TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauInstantiateFollows{"LuauInstantiateFollows", true};
|
|
||||||
|
|
||||||
// Just check that this doesn't assert
|
// Just check that this doesn't assert
|
||||||
check(R"(
|
check(R"(
|
||||||
--!nonstrict
|
--!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")
|
TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: string | number, b: boolean | number)
|
local function f(a: string | number, b: boolean | number)
|
||||||
return a == b
|
return a == b
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauEqConstraint)
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
|
|
||||||
using namespace Luau;
|
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.
|
// 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")
|
TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: string, b: boolean?)
|
local function f(a: string, b: boolean?)
|
||||||
if a == b then
|
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.
|
// Just needs to fully support equality refinement. Which is annoying without type states.
|
||||||
TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {x: string, y: number} | {x: nil, y: nil}
|
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")));
|
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();
|
TEST_SUITE_END();
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
|
||||||
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
|
|
||||||
@ -268,18 +267,10 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
|
|||||||
end
|
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({8, 44})));
|
||||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38})));
|
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]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
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")
|
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: (string | number)?, b: boolean?)
|
local function f(a: (string | number)?, b: boolean?)
|
||||||
if a == b then
|
if a == b then
|
||||||
@ -392,28 +381,15 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
|
|||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauWeakEqConstraint)
|
|
||||||
{
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // 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({3, 36})), "boolean?"); // a == b
|
||||||
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // 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, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: (string | number)?)
|
local function f(a: (string | number)?)
|
||||||
if a == 1 then
|
if a == 1 then
|
||||||
@ -426,24 +402,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
|||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauWeakEqConstraint)
|
|
||||||
{
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 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
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
|
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: (string | number)?)
|
local function f(a: (string | number)?)
|
||||||
if "hello" == a then
|
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")
|
TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: (string | number)?)
|
local function f(a: (string | number)?)
|
||||||
if a ~= nil then
|
if a ~= nil then
|
||||||
@ -476,21 +438,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
|||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauWeakEqConstraint)
|
|
||||||
{
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({5, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
|
|
||||||
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
|
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
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")
|
TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(a: any, b: {x: number}?)
|
local function f(a: any, b: {x: number}?)
|
||||||
if a ~= b then
|
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);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauWeakEqConstraint)
|
|
||||||
{
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff1{"LuauEqConstraint", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local t: {string} = {"hello"}
|
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, 29})), "string"); // a ~= b
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "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, 29})), "string"); // a == b
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
|
TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
|
||||||
@ -594,16 +525,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
|
|||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
if (FFlag::LuauDiscriminableUnions2)
|
|
||||||
{
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({3, 28})));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
|
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")
|
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type Cat = {tag: "Cat", name: string, catfood: string}
|
type Cat = {tag: "Cat", name: string, catfood: string}
|
||||||
type Dog = {tag: "Dog", name: string, dogfood: 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")
|
TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauAssertStripsFalsyTypes", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function is_true(b: true) end
|
local function is_true(b: true) end
|
||||||
local function is_false(b: false) 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")
|
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"(
|
CheckResult result = check(R"(
|
||||||
type Ok<T> = { ok: true, value: T }
|
type Ok<T> = { ok: true, value: T }
|
||||||
type Err<E> = { ok: false, error: E }
|
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")
|
TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersection_table")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {} & {f: ((string) -> string)?}
|
type T = {} & {f: ((string) -> string)?}
|
||||||
local function f(t: T, x)
|
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")
|
TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {tag: "Part", x: Part} | {tag: "Folder", x: Folder}
|
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
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
if (FFlag::LuauDiscriminableUnions2)
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"
|
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")
|
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type MyEnum = "foo" | "bar" | "baz"
|
type MyEnum = "foo" | "bar" | "baz"
|
||||||
local a : MyEnum = "bang"
|
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")
|
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauEqConstraint", true},
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauWidenIfSupertypeIsFree2", true},
|
{"LuauWidenIfSupertypeIsFree2", true},
|
||||||
{"LuauWeakEqConstraint", false},
|
{"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")
|
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauEqConstraint", true},
|
|
||||||
{"LuauWidenIfSupertypeIsFree2", true},
|
{"LuauWidenIfSupertypeIsFree2", true},
|
||||||
{"LuauWeakEqConstraint", false},
|
{"LuauWeakEqConstraint", false},
|
||||||
{"LuauDoNotAccidentallyDependOnPointerOrdering", true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
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")
|
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
{"LuauWidenIfSupertypeIsFree2", true},
|
{"LuauWidenIfSupertypeIsFree2", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -419,6 +415,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauWidenIfSupertypeIsFree2", true},
|
{"LuauWidenIfSupertypeIsFree2", true},
|
||||||
|
{"LuauWeakEqConstraint", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
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")
|
TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local a: string = "hi"
|
local a: string = "hi"
|
||||||
if a == "hi" then
|
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")
|
TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local a: string = "hi"
|
local a: string = "hi"
|
||||||
if a == "hi" or a == "bye" then
|
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")
|
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local a: string = "hi"
|
local a: string = "hi"
|
||||||
if a == "hi" then
|
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")
|
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauDiscriminableUnions2", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local a: string = "hi"
|
local a: string = "hi"
|
||||||
if a == "hi" or a == "bye" then
|
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")
|
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}}
|
// t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}}
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local mt = {}
|
local mt = {}
|
||||||
@ -2313,8 +2311,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "give_up_after_one_metatable_index_look_up")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
|
TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {} & {p: number | string}
|
type T = {} & {p: number | string}
|
||||||
local function f(t: T)
|
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")
|
TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauCheckImplicitNumbericKeys", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local t: { [string]: number } = { 5, 6, 7 }
|
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]));
|
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();
|
TEST_SUITE_END();
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
|
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
|
||||||
LUAU_FASTFLAG(LuauEqConstraint)
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -308,7 +307,6 @@ TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count")
|
|||||||
int limit = 600;
|
int limit = 600;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ScopedFastFlag sff{"LuauTableUseCounterInstead", true};
|
|
||||||
ScopedFastInt sfi{"LuauCheckRecursionLimit", limit};
|
ScopedFastInt sfi{"LuauCheckRecursionLimit", limit};
|
||||||
|
|
||||||
CheckResult result = check("function f() return " + rep("{a=", limit) + "'a'" + rep("}", limit) + " end");
|
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")
|
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
|
||||||
{
|
{
|
||||||
ScopedFastFlag substituteFollowNewTypes{"LuauSubstituteFollowNewTypes", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local obj = {}
|
local obj = {}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
LUAU_FASTFLAG(LuauEqConstraint)
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user