Sync to upstream/release/527

This commit is contained in:
Arseny Kapoulkine 2022-05-19 16:46:52 -07:00
parent 298b33859b
commit 7e9e697489
55 changed files with 1213 additions and 1372 deletions

View File

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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);

View File

@ -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;
}; };

View File

@ -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);

View File

@ -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

View File

@ -1,8 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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"

View File

@ -28,6 +28,7 @@ struct ToStringOptions
bool functionTypeArguments = false; // If true, output function type argument names when they are available bool functionTypeArguments = false; // If true, output function type argument names when they are available
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}' bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level. bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool indent = false; bool indent = false;
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);

View File

@ -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;

View File

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

View File

@ -187,7 +187,6 @@ struct TypeChecker
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr); ExprResult<TypeId> checkExpr(const ScopePtr& scope, const 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;

View File

@ -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);

View File

@ -10,6 +10,7 @@
LUAU_FASTFLAG(LuauUseVisitRecursionLimit) LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
namespace Luau namespace Luau
{ {
@ -471,18 +472,21 @@ 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)
traverse(ty);
for (TypeId ty : pack->head) if (pack->tail)
traverse(ty); traverse(*pack->tail);
}
if (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);
traverse(pack->ty); if (!FFlag::LuauNormalizeFlagIsConservative || res)
traverse(pack->ty);
} }
else else
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"); LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");

View File

@ -71,9 +71,11 @@ struct FindFullAncestry final : public AstVisitor
{ {
std::vector<AstNode*> nodes; std::vector<AstNode*> nodes;
Position pos; Position pos;
Position documentEnd;
explicit FindFullAncestry(Position pos) explicit FindFullAncestry(Position pos, Position documentEnd)
: pos(pos) : pos(pos)
, documentEnd(documentEnd)
{ {
} }
@ -84,6 +86,16 @@ struct FindFullAncestry final : public AstVisitor
nodes.push_back(node); nodes.push_back(node);
return true; return true;
} }
// Edge case: If we ask for the node at the position that is the very end of the document
// return the innermost AST element that ends at that position.
if (node->location.end == documentEnd && pos >= documentEnd)
{
nodes.push_back(node);
return true;
}
return false; return false;
} }
}; };
@ -92,7 +104,11 @@ struct FindFullAncestry final : public AstVisitor
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos) std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
{ {
FindFullAncestry finder(pos); const Position end = source.root->location.end;
if (pos > end)
pos = end;
FindFullAncestry finder(pos, end);
source.root->visit(&finder); source.root->visit(&finder);
return std::move(finder.nodes); return std::move(finder.nodes);
} }

View File

@ -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,41 +407,29 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
{ {
auto [paramPack, predicates] = exprResult; auto [paramPack, predicates] = exprResult;
if (FFlag::LuauAssertStripsFalsyTypes) TypeArena& arena = typechecker.currentModule->internalTypes;
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{ {
TypeArena& arena = typechecker.currentModule->internalTypes; std::optional<TypeId> fst = first(*tail);
if (!fst)
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{
std::optional<TypeId> fst = first(*tail);
if (!fst)
return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
}
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
else
{
if (expr.args.size < 1)
return ExprResult<TypePackId>{paramPack}; return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
return ExprResult<TypePackId>{paramPack};
} }
typechecker.resolve(predicates, scope, true);
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
} }
static std::optional<ExprResult<TypePackId>> magicFunctionPack( static std::optional<ExprResult<TypePackId>> magicFunctionPack(

View File

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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
@ -89,20 +86,8 @@ 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,9 +201,7 @@ 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);
ftv->hasNoGenerics = t.hasNoGenerics;
if (FFlag::LuauTypecheckOptPass)
ftv->hasNoGenerics = t.hasNoGenerics;
} }
void TypeCloner::operator()(const TableTypeVar& t) void TypeCloner::operator()(const TableTypeVar& t)
@ -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;

View File

@ -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,15 +177,7 @@ struct ErrorConverter
std::string operator()(const Luau::FunctionRequiresSelf& e) const std::string operator()(const Luau::FunctionRequiresSelf& e) const
{ {
if (e.requiredExtraNils) return "This function must be called with self. Did you mean to use a colon instead of a dot?";
{
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?";
} }
std::string operator()(const Luau::OccursCheckFailed&) const std::string operator()(const Luau::OccursCheckFailed&) const
@ -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

View File

@ -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>)

View File

@ -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);

View File

@ -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

View File

@ -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");
// The follow is here because a bound type may not be normal, but the bound type is normal. if (FFlag::LuauNormalizeFlagIsConservative)
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end(); return ty->normal;
else
{
// The follow is here because a bound type may not be normal, but the bound type is normal.
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
}
}; };
return std::all_of(begin(t), end(t), isNormal); 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};
std::unordered_set<void*> seen; if (FFlag::LuauNormalizeFlagIsConservative)
DEPRECATED_visitTypeVar(ty, n, seen); {
DEPRECATED_visitTypeVar(ty, n);
}
else
{
std::unordered_set<void*> 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};
std::unordered_set<void*> seen; if (FFlag::LuauNormalizeFlagIsConservative)
DEPRECATED_visitTypeVar(tp, n, seen); {
DEPRECATED_visitTypeVar(tp, n);
}
else
{
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(tp, n, seen);
}
return {tp, !n.limitExceeded}; return {tp, !n.limitExceeded};
} }

View File

@ -4,7 +4,7 @@
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
LUAU_FASTFLAG(LuauTypecheckOptPass) LUAU_FASTFLAG(LuauAlwaysQuantify)
namespace Luau namespace Luau
{ {
@ -59,8 +59,7 @@ 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))
return false; return false;
@ -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)
{ seenGenericType = true;
if (ttv.state == TableState::Generic)
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,9 +93,7 @@ struct Quantifier final : TypeVarOnceVisitor
if (ttv.state == TableState::Free) if (ttv.state == TableState::Free)
{ {
ttv.state = TableState::Generic; ttv.state = TableState::Generic;
seenGenericType = true;
if (FFlag::LuauTypecheckOptPass)
seenGenericType = true;
} }
else if (ttv.state == TableState::Unsealed) else if (ttv.state == TableState::Unsealed)
ttv.state = TableState::Sealed; ttv.state = TableState::Sealed;
@ -111,8 +105,7 @@ 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))
return false; return false;
@ -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);
ftv->generics = q.generics; if (FFlag::LuauAlwaysQuantify)
ftv->genericPacks = q.genericPacks; {
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
if (FFlag::LuauTypecheckOptPass && ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType) if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true; ftv->hasNoGenerics = true;
} }

View File

@ -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))
{ ty = &pty->pending;
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty)) if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{ {
visitChild(ftv->argTypes); visitChild(ftv->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))
{ tp = &ptp->pending;
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp)) if (const TypePack* tpp = get<TypePack>(tp))
{ {
for (TypeId tv : tpp->head) 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,10 +93,7 @@ void Tarjan::visitChildren(TypePackId tp, int index)
std::pair<int, bool> Tarjan::indexify(TypeId ty) std::pair<int, bool> Tarjan::indexify(TypeId ty)
{ {
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
bool fresh = !typeToIndex.contains(ty); bool fresh = !typeToIndex.contains(ty);
int& index = typeToIndex[ty]; int& index = typeToIndex[ty];
@ -129,10 +111,7 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
std::pair<int, bool> Tarjan::indexify(TypePackId tp) std::pair<int, bool> Tarjan::indexify(TypePackId tp)
{ {
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
bool fresh = !packToIndex.contains(tp); bool fresh = !packToIndex.contains(tp);
int& index = packToIndex[tp]; int& index = packToIndex[tp];
@ -150,8 +129,7 @@ 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);
edgesTp.push_back(nullptr); edgesTp.push_back(nullptr);
@ -159,8 +137,7 @@ 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);
edgesTp.push_back(tp); edgesTp.push_back(tp);
@ -389,13 +366,10 @@ TypeId Substitution::clone(TypeId ty)
TypeId result = ty; TypeId result = ty;
if (FFlag::LuauTypecheckOptPass) if (auto pty = log->pending(ty))
{ ty = &pty->pending;
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty)) if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{ {
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf}; 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))
{ tp = &ptp->pending;
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp)) if (const TypePack* tpp = get<TypePack>(tp))
{ {
TypePack clone; 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) ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
else
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) tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
else
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;

View File

@ -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);
} }
@ -745,7 +749,10 @@ struct TypeVarStringifier
for (std::string& ss : results) for (std::string& ss : results)
{ {
if (!first) if (!first)
state.emit(" | "); {
state.newline();
state.emit("| ");
}
state.emit(ss); state.emit(ss);
first = false; first = false;
} }
@ -798,7 +805,10 @@ struct TypeVarStringifier
for (std::string& ss : results) for (std::string& ss : results)
{ {
if (!first) if (!first)
state.emit(" & "); {
state.newline();
state.emit("& ");
}
state.emit(ss); state.emit(ss);
first = false; first = false;
} }
@ -937,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);
} }
@ -1230,6 +1241,14 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
size_t idx = 0; size_t idx = 0;
while (argPackIter != end(ftv.argTypes)) while (argPackIter != end(ftv.argTypes))
{ {
// ftv takes a self parameter as the first argument, skip it if specified in option
if (idx == 0 && ftv.hasSelf && opts.hideFunctionSelfArgument)
{
++argPackIter;
++idx;
continue;
}
if (!first) if (!first)
state.emit(", "); state.emit(", ");
first = false; first = false;

View File

@ -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) const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{ {
// This function will technically work if `this` is nullptr, but this return true;
// 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);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
return true;
}
if (!FFlag::LuauTypecheckOptPass && parent) return false;
{
return parent->haveSeen(lhs, rhs);
}
return false;
}
} }
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs) void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)

View File

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

View File

@ -32,32 +32,25 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSeparateTypechecks) LUAU_FASTFLAG(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,23 +1834,11 @@ 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);
{ LUAU_ASSERT(mtIndex);
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location); type = *mtIndex;
LUAU_ASSERT(mtIndex);
type = *mtIndex;
}
}
else
{
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
{
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
type = *mtIndex;
}
} }
if (TableTypeVar* tableType = getMutableTableType(type)) if (TableTypeVar* tableType = getMutableTableType(type))
@ -1966,23 +1939,10 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
return std::nullopt; return std::nullopt;
} }
if (FFlag::LuauDoNotTryToReduce) if (parts.size() == 1)
{ return parts[0];
if (parts.size() == 1)
return parts[0];
return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct. 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)
@ -1993,103 +1953,55 @@ 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;
for (TypeId t : types)
{ {
std::vector<TypeId> result; t = follow(t);
for (TypeId t : types) if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
{ {
t = follow(t); if (FFlag::LuauReduceUnionRecursion)
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
return {t};
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
{ {
if (FFlag::LuauReduceUnionRecursion) for (TypeId ty : utv)
{ {
for (TypeId ty : utv) if (FFlag::LuauNormalizeFlagIsConservative)
{
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (result.end() == std::find(result.begin(), result.end(), ty))
result.push_back(ty);
}
}
else
{
std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r)
{
ty = follow(ty); ty = follow(ty);
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty)) if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty}; return {ty};
if (std::find(result.begin(), result.end(), ty) == result.end()) if (result.end() == std::find(result.begin(), result.end(), ty))
result.push_back(ty); result.push_back(ty);
}
} }
} }
else if (std::find(result.begin(), result.end(), t) == result.end()) else
result.push_back(t);
}
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); std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r) for (TypeId ty : r)
s.insert(ty); {
ty = follow(ty);
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (std::find(result.begin(), result.end(), ty) == result.end())
result.push_back(ty);
}
} }
else
s.insert(t);
} }
else if (std::find(result.begin(), result.end(), t) == result.end())
// If any of them are ErrorTypeVars/AnyTypeVars, decay into them. result.push_back(t);
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;
} }
return result;
} }
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))
{ return ty;
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
else
{
bool hasNil = false;
for (TypeId option : utv)
{
if (isNil(option))
{
hasNil = true;
break;
}
}
if (!hasNil)
return ty;
}
std::vector<TypeId> result; std::vector<TypeId> result;
@ -2110,32 +2022,18 @@ 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);
if (auto utv = get<UnionTypeVar>(ty))
{ {
ty = follow(ty); if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
if (auto utv = get<UnionTypeVar>(ty))
{
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
{
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
}
} }
else
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
{ {
if (isOptional(ty)) reportError(location, OptionalValueAccess{ty});
{ return follow(*strippedUnion);
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(follow(ty)))
{
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
}
}
} }
return ty; return ty;
@ -2194,8 +2092,7 @@ 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);
} }
else else
@ -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,26 +2157,13 @@ 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);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
{ {
RecursionCounter _rc(&checkRecursionCount); reportErrorCodeTooComplex(expr.location);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit) return {errorRecoveryType(scope)};
{
reportErrorCodeTooComplex(expr.location);
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 (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
Luau::quantify(ty, scope->level); if (FFlag::LuauAlwaysQuantify)
{
if (ftv)
Luau::quantify(ty, scope->level);
}
else
{
if (ftv && ftv->generics.empty() && ftv->genericPacks.empty())
Luau::quantify(ty, scope->level);
}
if (FFlag::LuauLowerBoundsCalculation && ftv) 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);
{ if (ftv && ftv->hasNoGenerics)
const FunctionTypeVar* ftv = get<FunctionTypeVar>(FFlag::LuauInstantiateFollows ? ty : follow(ty)); return ty;
if (ftv && ftv->hasNoGenerics)
return ty;
}
Instantiation instantiation{log, &currentModule->internalTypes, scope->level}; Instantiation instantiation{log, &currentModule->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);
{ if (ty && fromOr)
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue); return addRefinement(refis, truthyP.lvalue, *ty);
if (ty && fromOr)
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)
@ -6060,52 +5800,29 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
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));
{ return;
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return;
}
else
{
if (std::optional<TypeId> result = filterMap(*ty, it->second(sense)))
addRefinement(refis, typeguardP.lvalue, *result);
else
{
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
if (sense)
errVec.push_back(
TypeError{typeguardP.location, GenericError{"Type '" + toString(*ty) + "' has no overlap with '" + typeguardP.kind + "'"}});
}
return;
}
} }
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,82 +5831,33 @@ 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))
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
auto predicate = [&](TypeId option) -> std::optional<TypeId> { auto predicate = [&](TypeId option) -> std::optional<TypeId> {
if (sense && isUndecidable(option)) if (sense && isUndecidable(option))
return FFlag::LuauWeakEqConstraint ? option : eqP.type; return FFlag::LuauWeakEqConstraint ? option : eqP.type;
if (!sense && isNil(eqP.type)) if (!sense && isNil(eqP.type))
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt; return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;
if (maybeSingleton(eqP.type)) if (maybeSingleton(eqP.type))
{
// Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this.
if (!sense || canUnify(eqP.type, option, eqP.location).empty())
return sense ? eqP.type : option;
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
}
return option;
};
refineLValue(eqP.lvalue, refis, scope, predicate);
}
else
{
if (FFlag::LuauWeakEqConstraint)
{ {
if (!sense && isNil(eqP.type)) // Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this.
resolve(TruthyPredicate{std::move(eqP.lvalue), eqP.location}, errVec, refis, scope, true, /* fromOr= */ false); if (!sense || canUnify(eqP.type, option, eqP.location).empty())
return sense ? eqP.type : option;
return; // local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
} }
if (FFlag::LuauEqConstraint) return option;
{ };
std::optional<TypeId> ty = resolveLValue(refis, scope, eqP.lvalue);
if (!ty)
return;
std::vector<TypeId> lhs = options(*ty); refineLValue(eqP.lvalue, refis, scope, predicate);
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

View File

@ -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)
{ return std::nullopt;
if (count >= 100)
return std::nullopt;
++count; ++count;
}
if (const auto& itt = getTableType(index)) if (const auto& itt = getTableType(index))
{ {

View File

@ -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))

View File

@ -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,21 +1431,9 @@ 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() && missingProperties.push_back(propName);
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type))
missingProperties.push_back(propName);
}
else
{
bool isAny = log.getMutable<AnyTypeVar>(log.follow(superProp.type));
if (subIter == subTable->props.end() &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) &&
!isAny)
missingProperties.push_back(propName);
}
} }
if (!missingProperties.empty()) if (!missingProperties.empty())
@ -1485,18 +1450,8 @@ 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)))
{ extraProperties.push_back(propName);
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
extraProperties.push_back(propName);
}
else
{
bool isAny = log.is<AnyTypeVar>(log.follow(subProp.type));
if (superIter == superTable->props.end() &&
(FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny)))
extraProperties.push_back(propName);
}
} }
if (!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,14 +2607,7 @@ 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;
return u;
}
Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop; u.anyIsTop = anyIsTop;
return u; return u;
} }

View File

@ -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;

View File

@ -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);

View File

@ -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);
compileExprTemp(arg, reg);
if (arg)
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,20 +2458,17 @@ 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();
} }

View File

@ -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;

View File

@ -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

View File

@ -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");

View File

@ -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);

View File

@ -92,4 +92,17 @@ bar(foo())
CHECK_EQ("number", toString(*expectedOty)); CHECK_EQ("number", toString(*expectedOty));
} }
TEST_CASE_FIXTURE(Fixture, "ast_ancestry_at_eof")
{
check(R"(
if true then
)");
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(*getMainSourceModule(), Position(2, 4));
REQUIRE_GE(ancestry.size(), 2);
AstStat* parentStat = ancestry[ancestry.size() - 2]->asStat();
REQUIRE(bool(parentStat));
REQUIRE(parentStat->is<AstStatIf>());
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -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

View File

@ -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
)"); )");
} }

View File

@ -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 = {}

View File

@ -150,8 +150,6 @@ TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional")
TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any") 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 = {}

View File

@ -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();

View File

@ -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>

View File

@ -126,6 +126,39 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_always_parenthesized_in_unions_or_inte
CHECK_EQ(toString(&itv), "((number, string) -> (string, number)) & ((string, number) -> (number, string))"); CHECK_EQ(toString(&itv), "((number, string) -> (string, number)) & ((string, number) -> (number, string))");
} }
TEST_CASE_FIXTURE(Fixture, "intersections_respects_use_line_breaks")
{
CheckResult result = check(R"(
local a: ((string) -> string) & ((number) -> number)
)");
ToStringOptions opts;
opts.useLineBreaks = true;
//clang-format off
CHECK_EQ("((number) -> number)\n"
"& ((string) -> string)",
toString(requireType("a"), opts));
//clang-format on
}
TEST_CASE_FIXTURE(Fixture, "unions_respects_use_line_breaks")
{
CheckResult result = check(R"(
local a: string | number | boolean
)");
ToStringOptions opts;
opts.useLineBreaks = true;
//clang-format off
CHECK_EQ("boolean\n"
"| number\n"
"| string",
toString(requireType("a"), opts));
//clang-format on
}
TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded") TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded")
{ {
TableTypeVar ttv{}; TableTypeVar ttv{};
@ -617,4 +650,52 @@ 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")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local foo = {}
function foo:method(arg: string): ()
end
)");
TypeId parentTy = requireType("foo");
auto ttv = get<TableTypeVar>(follow(parentTy));
auto ftv = get<FunctionTypeVar>(ttv->props.at("method").type);
CHECK_EQ("foo:method<a>(self: a, arg: string): ()", toStringNamedFunction("foo:method", *ftv));
}
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
{
ScopedFastFlag flag{"LuauDocFuncParameters", true};
CheckResult result = check(R"(
local foo = {}
function foo:method(arg: string): ()
end
)");
TypeId parentTy = requireType("foo");
auto ttv = get<TableTypeVar>(follow(parentTy));
auto ftv = get<FunctionTypeVar>(ttv->props.at("method").type);
ToStringOptions opts;
opts.hideFunctionSelfArgument = true;
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -878,8 +878,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types") 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")

View File

@ -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()

View File

@ -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();

View File

@ -177,8 +177,6 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") 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}}}}

View File

@ -475,8 +475,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional")
TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow") 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

View File

@ -728,8 +728,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_verifies_types_do_intersect")
TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap") 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

View File

@ -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();

View File

@ -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, 36})), "boolean?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b CHECK_EQ(toString(requireTypeAtPosition({5, 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({5, 28})), "(number | string)?"); // a ~= 1
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1
}
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({5, 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
}
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, 36})), "{| x: number |}?"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b
}
else
{
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}"); // a ~= b
}
} }
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, 32})), "string?"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b
}
else
{
// This is technically not wrong, but it's also wrong at the same time.
// The refinement code is none the wiser about the fact we pulled a string out of an array, so it has no choice but to narrow as just string.
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string"); // a == b
}
} }
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"

View File

@ -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

View File

@ -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();

View File

@ -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 = {}

View File

@ -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;