diff --git a/Analysis/include/Luau/Clone.h b/Analysis/include/Luau/Clone.h index 9b6ffa62c..9fcbce047 100644 --- a/Analysis/include/Luau/Clone.h +++ b/Analysis/include/Luau/Clone.h @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/TypeArena.h" #include "Luau/TypeVar.h" #include @@ -18,7 +19,6 @@ struct CloneState SeenTypePacks seenTypePacks; int recursionCount = 0; - bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone. }; TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState); diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 70683141f..b45306749 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -5,6 +5,7 @@ #include "Luau/Location.h" #include "Luau/TypeVar.h" #include "Luau/Variant.h" +#include "Luau/TypeArena.h" namespace Luau { @@ -108,9 +109,6 @@ struct FunctionDoesNotTakeSelf struct FunctionRequiresSelf { - // TODO: Delete with LuauAnyInIsOptionalIsOptional - int requiredExtraNils = 0; - bool operator==(const FunctionRequiresSelf& rhs) const; }; diff --git a/Analysis/include/Luau/LValue.h b/Analysis/include/Luau/LValue.h index afb714153..1a92d52d0 100644 --- a/Analysis/include/Luau/LValue.h +++ b/Analysis/include/Luau/LValue.h @@ -34,10 +34,6 @@ const LValue* baseof(const LValue& lvalue); std::optional 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> getFullName(const LValue& lvalue); - // Utility function: breaks down an LValue to get at the Symbol Symbol getBaseSymbol(const LValue& lvalue); diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 0dd441886..00e1e6353 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -2,11 +2,10 @@ #pragma once #include "Luau/FileResolver.h" -#include "Luau/TypePack.h" -#include "Luau/TypedAllocator.h" #include "Luau/ParseOptions.h" #include "Luau/Error.h" #include "Luau/ParseResult.h" +#include "Luau/TypeArena.h" #include #include @@ -54,35 +53,6 @@ struct RequireCycle std::vector path; // one of the paths for a require() to go all the way back to the originating module }; -struct TypeArena -{ - TypedAllocator typeVars; - TypedAllocator typePacks; - - void clear(); - - template - TypeId addType(T tv) - { - if constexpr (std::is_same_v) - 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 types); - TypePackId addTypePack(std::vector types); - TypePackId addTypePack(TypePack pack); - TypePackId addTypePack(TypePackVar pack); -}; - -void freeze(TypeArena& arena); -void unfreeze(TypeArena& arena); - struct Module { ~Module(); @@ -111,9 +81,7 @@ struct Module // Once a module has been typechecked, we clone its public interface into a separate arena. // This helps us to force TypeVar ownership into a DAG rather than a DCG. - // Returns true if there were any free types encountered in the public interface. This - // indicates a bug in the type checker that we want to surface. - bool clonePublicInterface(InternalErrorReporter& ice); + void clonePublicInterface(InternalErrorReporter& ice); }; } // namespace Luau diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index 6f5931e10..f3c3ae9aa 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -1,8 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/Module.h" -#include "Luau/ModuleResolver.h" +#include "Luau/TypeArena.h" #include "Luau/TypePack.h" #include "Luau/TypeVar.h" #include "Luau/DenseHash.h" diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index 995ed6c65..cd115e3b0 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -7,8 +7,6 @@ #include "Luau/TypeVar.h" #include "Luau/TypePack.h" -LUAU_FASTFLAG(LuauTypecheckOptPass) - namespace Luau { @@ -93,15 +91,6 @@ struct TxnLog { } - TxnLog(TxnLog* parent, std::vector>* sharedSeen) - : typeVarChanges(nullptr) - , typePackChanges(nullptr) - , parent(parent) - , sharedSeen(sharedSeen) - { - LUAU_ASSERT(!FFlag::LuauTypecheckOptPass); - } - TxnLog(const TxnLog&) = delete; TxnLog& operator=(const TxnLog&) = delete; diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h new file mode 100644 index 000000000..7c74158b2 --- /dev/null +++ b/Analysis/include/Luau/TypeArena.h @@ -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 + +namespace Luau +{ + +struct TypeArena +{ + TypedAllocator typeVars; + TypedAllocator typePacks; + + void clear(); + + template + TypeId addType(T tv) + { + if constexpr (std::is_same_v) + 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 types); + TypePackId addTypePack(std::vector types); + TypePackId addTypePack(TypePack pack); + TypePackId addTypePack(TypePackVar pack); +}; + +void freeze(TypeArena& arena); +void unfreeze(TypeArena& arena); + +} diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index ac8801356..fcaf5baa3 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -187,7 +187,6 @@ struct TypeChecker ExprResult checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr); ExprResult checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional expectedType = std::nullopt); ExprResult checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional expectedType = std::nullopt); - ExprResult checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional expectedType = std::nullopt); ExprResult checkExpr(const ScopePtr& scope, const AstExprUnary& expr); TypeId checkRelationalOperation( const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {}); @@ -395,7 +394,7 @@ struct TypeChecker const AstArray& genericNames, const AstArray& genericPackNames, bool useCache = false); public: - ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); + void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); private: void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate); @@ -403,14 +402,14 @@ struct TypeChecker std::optional resolveLValue(const ScopePtr& scope, const LValue& lvalue); std::optional resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue); - void resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false); - void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr); - void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr); - void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense); - void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense); - void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense); - void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense); - void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense); + void resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false); + void resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr); + void resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr); + void resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense); + void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense); + void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense); + void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense); + void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense); bool isNonstrictMode() const; bool useConstrainedIntersections() const; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 418d4ca42..0e24c8b01 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -5,7 +5,7 @@ #include "Luau/Location.h" #include "Luau/TxnLog.h" #include "Luau/TypeInfer.h" -#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header. +#include "Luau/TypeArena.h" #include "Luau/UnifierSharedState.h" #include @@ -55,8 +55,6 @@ struct Unifier UnifierSharedState& sharedState; Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); - Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, Variance variance, - UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); // Test whether the two type vars unify. Never commits the result. ErrorVec canUnify(TypeId subTy, TypeId superTy); diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index 67fce5edd..2e98f526a 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -10,6 +10,7 @@ LUAU_FASTFLAG(LuauUseVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit) +LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) namespace Luau { @@ -471,18 +472,21 @@ struct GenericTypeVarVisitor else if (auto pack = get(tp)) { - visit(tp, *pack); - - for (TypeId ty : pack->head) - traverse(ty); + bool res = visit(tp, *pack); + if (!FFlag::LuauNormalizeFlagIsConservative || res) + { + for (TypeId ty : pack->head) + traverse(ty); - if (pack->tail) - traverse(*pack->tail); + if (pack->tail) + traverse(*pack->tail); + } } else if (auto pack = get(tp)) { - visit(tp, *pack); - traverse(pack->ty); + bool res = visit(tp, *pack); + if (!FFlag::LuauNormalizeFlagIsConservative || res) + traverse(pack->ty); } else LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"); diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 3895b01b7..5ed6de673 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -8,7 +8,6 @@ #include -LUAU_FASTFLAG(LuauAssertStripsFalsyTypes) LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false) /** FIXME: Many of these type definitions are not quite completely accurate. @@ -408,41 +407,29 @@ static std::optional> magicFunctionAssert( { auto [paramPack, predicates] = exprResult; - if (FFlag::LuauAssertStripsFalsyTypes) - { - TypeArena& arena = typechecker.currentModule->internalTypes; - - auto [head, tail] = flatten(paramPack); - if (head.empty() && tail) - { - std::optional fst = first(*tail); - if (!fst) - return ExprResult{paramPack}; - head.push_back(*fst); - } - - typechecker.reportErrors(typechecker.resolve(predicates, scope, true)); - - if (head.size() > 0) - { - std::optional newhead = typechecker.pickTypesFromSense(head[0], true); - if (!newhead) - head = {typechecker.nilType}; - else - head[0] = *newhead; - } + TypeArena& arena = typechecker.currentModule->internalTypes; - return ExprResult{arena.addTypePack(TypePack{std::move(head), tail})}; - } - else + auto [head, tail] = flatten(paramPack); + if (head.empty() && tail) { - if (expr.args.size < 1) + std::optional fst = first(*tail); + if (!fst) return ExprResult{paramPack}; + head.push_back(*fst); + } - typechecker.reportErrors(typechecker.resolve(predicates, scope, true)); + typechecker.resolve(predicates, scope, true); - return ExprResult{paramPack}; + if (head.size() > 0) + { + std::optional newhead = typechecker.pickTypesFromSense(head[0], true); + if (!newhead) + head = {typechecker.nilType}; + else + head[0] = *newhead; } + + return ExprResult{arena.addTypePack(TypePack{std::move(head), tail})}; } static std::optional> magicFunctionPack( diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 1aa556eb8..a3611f532 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -1,7 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Clone.h" -#include "Luau/Module.h" #include "Luau/RecursionCounter.h" #include "Luau/TypePack.h" #include "Luau/Unifiable.h" @@ -9,8 +8,6 @@ LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) -LUAU_FASTFLAG(LuauTypecheckOptPass) -LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false) LUAU_FASTFLAG(LuauNoMethodLocations) namespace Luau @@ -89,20 +86,8 @@ struct TypePackCloner void operator()(const Unifiable::Free& t) { - if (FFlag::LuauLosslessClone) - { - defaultClone(t); - } - else - { - cloneState.encounteredFreeType = true; - - TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack); - TypePackId cloned = dest.addTypePack(*err); - seenTypePacks[typePackId] = cloned; - } + defaultClone(t); } - void operator()(const Unifiable::Generic& t) { defaultClone(t); @@ -152,18 +137,7 @@ void TypeCloner::defaultClone(const T& t) void TypeCloner::operator()(const Unifiable::Free& t) { - if (FFlag::LuauLosslessClone) - { - defaultClone(t); - } - else - { - cloneState.encounteredFreeType = true; - - TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType); - TypeId cloned = dest.addType(*err); - seenTypes[typeId] = cloned; - } + defaultClone(t); } void TypeCloner::operator()(const Unifiable::Generic& t) @@ -191,9 +165,6 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t) void TypeCloner::operator()(const ConstrainedTypeVar& t) { - if (!FFlag::LuauLosslessClone) - cloneState.encounteredFreeType = true; - TypeId res = dest.addType(ConstrainedTypeVar{t.level}); ConstrainedTypeVar* ctv = getMutable(res); LUAU_ASSERT(ctv); @@ -230,9 +201,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t) ftv->argTypes = clone(t.argTypes, dest, cloneState); ftv->argNames = t.argNames; ftv->retType = clone(t.retType, dest, cloneState); - - if (FFlag::LuauTypecheckOptPass) - ftv->hasNoGenerics = t.hasNoGenerics; + ftv->hasNoGenerics = t.hasNoGenerics; } void TypeCloner::operator()(const TableTypeVar& t) @@ -270,13 +239,6 @@ void TypeCloner::operator()(const TableTypeVar& t) for (TypePackId& arg : ttv->instantiatedTypePackParams) arg = clone(arg, dest, cloneState); - if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free) - { - cloneState.encounteredFreeType = true; - - ttv->state = TableState::Sealed; - } - ttv->definitionModuleName = t.definitionModuleName; if (!FFlag::LuauNoMethodLocations) ttv->methodDefinitionLocations = t.methodDefinitionLocations; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 24ed4ac1e..f443a3cc5 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -2,7 +2,6 @@ #include "Luau/Error.h" #include "Luau/Clone.h" -#include "Luau/Module.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" @@ -178,15 +177,7 @@ struct ErrorConverter std::string operator()(const Luau::FunctionRequiresSelf& e) const { - if (e.requiredExtraNils) - { - const char* plural = e.requiredExtraNils == 1 ? "" : "s"; - return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or " - "pass %i extra nil%s to suppress this warning", - e.requiredExtraNils, plural); - } - else - return "This function must be called with self. Did you mean to use a colon instead of a dot?"; + return "This function must be called with self. Did you mean to use a colon instead of a dot?"; } std::string operator()(const Luau::OccursCheckFailed&) const @@ -539,7 +530,7 @@ bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const { - return requiredExtraNils == e.requiredExtraNils; + return true; } bool OccursCheckFailed::operator==(const OccursCheckFailed&) const diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 0eaa485e7..048167aef 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -48,7 +48,7 @@ static void errorToString(std::ostream& stream, const T& err) else if constexpr (std::is_same_v) stream << "FunctionDoesNotTakeSelf { }"; else if constexpr (std::is_same_v) - stream << "FunctionRequiresSelf { extraNils " << err.requiredExtraNils << " }"; + stream << "FunctionRequiresSelf { }"; else if constexpr (std::is_same_v) stream << "OccursCheckFailed { }"; else if constexpr (std::is_same_v) diff --git a/Analysis/src/LValue.cpp b/Analysis/src/LValue.cpp index 72555ab4d..38dfe1ae5 100644 --- a/Analysis/src/LValue.cpp +++ b/Analysis/src/LValue.cpp @@ -5,8 +5,6 @@ #include -LUAU_FASTFLAG(LuauTypecheckOptPass) - namespace Luau { @@ -79,27 +77,8 @@ std::optional tryGetLValue(const AstExpr& node) return std::nullopt; } -std::pair> getFullName(const LValue& lvalue) -{ - LUAU_ASSERT(!FFlag::LuauTypecheckOptPass); - - const LValue* current = &lvalue; - std::vector keys; - while (auto field = get(*current)) - { - keys.push_back(field->key); - current = baseof(*current); - } - - const Symbol* symbol = get(*current); - LUAU_ASSERT(symbol); - return {*symbol, std::vector(keys.rbegin(), keys.rend())}; -} - Symbol getBaseSymbol(const LValue& lvalue) { - LUAU_ASSERT(FFlag::LuauTypecheckOptPass); - const LValue* current = &lvalue; while (auto field = get(*current)) current = baseof(*current); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index bafd4371d..074a41e64 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -13,9 +13,8 @@ #include -LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) -LUAU_FASTFLAG(LuauLowerBoundsCalculation) -LUAU_FASTFLAG(LuauLosslessClone) +LUAU_FASTFLAG(LuauLowerBoundsCalculation); +LUAU_FASTFLAG(LuauNormalizeFlagIsConservative); namespace Luau { @@ -55,89 +54,25 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos) return contains(pos, *iter); } -void TypeArena::clear() +struct ForceNormal : TypeVarOnceVisitor { - typeVars.clear(); - typePacks.clear(); -} - -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 types) -{ - TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); - - asMutable(allocated)->owningArena = this; - - return allocated; -} - -TypePackId TypeArena::addTypePack(std::vector 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(); -} + bool visit(TypeId ty) override + { + asMutable(ty)->normal = true; + return true; + } -void unfreeze(TypeArena& arena) -{ - if (!FFlag::DebugLuauFreezeArena) - return; + bool visit(TypeId ty, const FreeTypeVar& ftv) override + { + visit(ty); + return true; + } - arena.typeVars.unfreeze(); - arena.typePacks.unfreeze(); -} + bool visit(TypePackId tp, const FreeTypePack& ftp) override + { + return true; + } +}; Module::~Module() { @@ -145,7 +80,7 @@ Module::~Module() unfreeze(internalTypes); } -bool Module::clonePublicInterface(InternalErrorReporter& ice) +void Module::clonePublicInterface(InternalErrorReporter& ice) { LUAU_ASSERT(interfaceTypes.typeVars.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty()); @@ -165,11 +100,22 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice) normalize(*moduleScope->varargPack, interfaceTypes, ice); } + ForceNormal forceNormal; + for (auto& [name, tf] : moduleScope->exportedTypeBindings) { tf = clone(tf, interfaceTypes, cloneState); if (FFlag::LuauLowerBoundsCalculation) + { normalize(tf.type, interfaceTypes, ice); + + if (FFlag::LuauNormalizeFlagIsConservative) + { + // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables + // won't be marked normal. If the types aren't normal by now, they never will be. + forceNormal.traverse(tf.type); + } + } } for (TypeId ty : moduleScope->returnType) @@ -191,11 +137,12 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice) freeze(internalTypes); freeze(interfaceTypes); +} - if (FFlag::LuauLosslessClone) - return false; // TODO: make function return void. - else - return cloneState.encounteredFreeType; +ScopePtr Module::getModuleScope() const +{ + LUAU_ASSERT(!scopes.empty()); + return scopes.front().second; } } // namespace Luau diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index ef5377a1b..30fd4af2f 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -14,6 +14,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false) // This could theoretically be 2000 on amd64, but x86 requires this. LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); +LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false); namespace Luau { @@ -260,8 +261,13 @@ static bool areNormal_(const T& t, const std::unordered_set& seen, Intern if (count >= FInt::LuauNormalizeIterationLimit) ice.ice("Luau::areNormal hit iteration limit"); - // The follow is here because a bound type may not be normal, but the bound type is normal. - return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end(); + if (FFlag::LuauNormalizeFlagIsConservative) + return ty->normal; + else + { + // The follow is here because a bound type may not be normal, but the bound type is normal. + return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end(); + } }; return std::all_of(begin(t), end(t), isNormal); @@ -1003,8 +1009,15 @@ std::pair normalize(TypeId ty, TypeArena& arena, InternalErrorRepo (void)clone(ty, arena, state); Normalize n{arena, ice}; - std::unordered_set seen; - DEPRECATED_visitTypeVar(ty, n, seen); + if (FFlag::LuauNormalizeFlagIsConservative) + { + DEPRECATED_visitTypeVar(ty, n); + } + else + { + std::unordered_set seen; + DEPRECATED_visitTypeVar(ty, n, seen); + } return {ty, !n.limitExceeded}; } @@ -1028,8 +1041,15 @@ std::pair normalize(TypePackId tp, TypeArena& arena, InternalE (void)clone(tp, arena, state); Normalize n{arena, ice}; - std::unordered_set seen; - DEPRECATED_visitTypeVar(tp, n, seen); + if (FFlag::LuauNormalizeFlagIsConservative) + { + DEPRECATED_visitTypeVar(tp, n); + } + else + { + std::unordered_set seen; + DEPRECATED_visitTypeVar(tp, n, seen); + } return {tp, !n.limitExceeded}; } diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 4f3e4469d..018d5632c 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -4,7 +4,7 @@ #include "Luau/VisitTypeVar.h" -LUAU_FASTFLAG(LuauTypecheckOptPass) +LUAU_FASTFLAG(LuauAlwaysQuantify) namespace Luau { @@ -59,8 +59,7 @@ struct Quantifier final : TypeVarOnceVisitor bool visit(TypeId ty, const FreeTypeVar& ftv) override { - if (FFlag::LuauTypecheckOptPass) - seenMutableType = true; + seenMutableType = true; if (!level.subsumes(ftv.level)) return false; @@ -76,20 +75,17 @@ struct Quantifier final : TypeVarOnceVisitor LUAU_ASSERT(getMutable(ty)); TableTypeVar& ttv = *getMutable(ty); - if (FFlag::LuauTypecheckOptPass) - { - if (ttv.state == TableState::Generic) - seenGenericType = true; + if (ttv.state == TableState::Generic) + seenGenericType = true; - if (ttv.state == TableState::Free) - seenMutableType = true; - } + if (ttv.state == TableState::Free) + seenMutableType = true; if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic) return false; if (!level.subsumes(ttv.level)) { - if (FFlag::LuauTypecheckOptPass && ttv.state == TableState::Unsealed) + if (ttv.state == TableState::Unsealed) seenMutableType = true; return false; } @@ -97,9 +93,7 @@ struct Quantifier final : TypeVarOnceVisitor if (ttv.state == TableState::Free) { ttv.state = TableState::Generic; - - if (FFlag::LuauTypecheckOptPass) - seenGenericType = true; + seenGenericType = true; } else if (ttv.state == TableState::Unsealed) ttv.state = TableState::Sealed; @@ -111,8 +105,7 @@ struct Quantifier final : TypeVarOnceVisitor bool visit(TypePackId tp, const FreeTypePack& ftp) override { - if (FFlag::LuauTypecheckOptPass) - seenMutableType = true; + seenMutableType = true; if (!level.subsumes(ftp.level)) return false; @@ -131,10 +124,18 @@ void quantify(TypeId ty, TypeLevel level) FunctionTypeVar* ftv = getMutable(ty); LUAU_ASSERT(ftv); - ftv->generics = q.generics; - ftv->genericPacks = q.genericPacks; + if (FFlag::LuauAlwaysQuantify) + { + ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); + ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); + } + else + { + ftv->generics = q.generics; + ftv->genericPacks = q.genericPacks; + } - if (FFlag::LuauTypecheckOptPass && ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType) + if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType) ftv->hasNoGenerics = true; } diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index c5c7977ae..e40bedb0d 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -9,9 +9,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) -LUAU_FASTFLAG(LuauTypecheckOptPass) -LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false) -LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false) LUAU_FASTFLAG(LuauNoMethodLocations) namespace Luau @@ -19,26 +16,20 @@ namespace Luau void Tarjan::visitChildren(TypeId ty, int index) { - if (FFlag::LuauTypecheckOptPass) - LUAU_ASSERT(ty == log->follow(ty)); - else - ty = log->follow(ty); + LUAU_ASSERT(ty == log->follow(ty)); if (ignoreChildren(ty)) return; - if (FFlag::LuauTypecheckOptPass) - { - if (auto pty = log->pending(ty)) - ty = &pty->pending; - } + if (auto pty = log->pending(ty)) + ty = &pty->pending; - if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + if (const FunctionTypeVar* ftv = get(ty)) { visitChild(ftv->argTypes); visitChild(ftv->retType); } - else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const TableTypeVar* ttv = get(ty)) { LUAU_ASSERT(!ttv->boundTo); for (const auto& [name, prop] : ttv->props) @@ -55,17 +46,17 @@ void Tarjan::visitChildren(TypeId ty, int index) for (TypePackId itp : ttv->instantiatedTypePackParams) visitChild(itp); } - else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const MetatableTypeVar* mtv = get(ty)) { visitChild(mtv->table); visitChild(mtv->metatable); } - else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const UnionTypeVar* utv = get(ty)) { for (TypeId opt : utv->options) visitChild(opt); } - else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const IntersectionTypeVar* itv = get(ty)) { for (TypeId part : itv->parts) visitChild(part); @@ -79,28 +70,22 @@ void Tarjan::visitChildren(TypeId ty, int index) void Tarjan::visitChildren(TypePackId tp, int index) { - if (FFlag::LuauTypecheckOptPass) - LUAU_ASSERT(tp == log->follow(tp)); - else - tp = log->follow(tp); + LUAU_ASSERT(tp == log->follow(tp)); if (ignoreChildren(tp)) return; - if (FFlag::LuauTypecheckOptPass) - { - if (auto ptp = log->pending(tp)) - tp = &ptp->pending; - } + if (auto ptp = log->pending(tp)) + tp = &ptp->pending; - if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get(tp) : log->getMutable(tp)) + if (const TypePack* tpp = get(tp)) { for (TypeId tv : tpp->head) visitChild(tv); if (tpp->tail) visitChild(*tpp->tail); } - else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get(tp) : log->getMutable(tp)) + else if (const VariadicTypePack* vtp = get(tp)) { visitChild(vtp->ty); } @@ -108,10 +93,7 @@ void Tarjan::visitChildren(TypePackId tp, int index) std::pair Tarjan::indexify(TypeId ty) { - if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) - LUAU_ASSERT(ty == log->follow(ty)); - else - ty = log->follow(ty); + ty = log->follow(ty); bool fresh = !typeToIndex.contains(ty); int& index = typeToIndex[ty]; @@ -129,10 +111,7 @@ std::pair Tarjan::indexify(TypeId ty) std::pair Tarjan::indexify(TypePackId tp) { - if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) - LUAU_ASSERT(tp == log->follow(tp)); - else - tp = log->follow(tp); + tp = log->follow(tp); bool fresh = !packToIndex.contains(tp); int& index = packToIndex[tp]; @@ -150,8 +129,7 @@ std::pair Tarjan::indexify(TypePackId tp) void Tarjan::visitChild(TypeId ty) { - if (!FFlag::LuauSubstituteFollowPossibleMutations) - ty = log->follow(ty); + ty = log->follow(ty); edgesTy.push_back(ty); edgesTp.push_back(nullptr); @@ -159,8 +137,7 @@ void Tarjan::visitChild(TypeId ty) void Tarjan::visitChild(TypePackId tp) { - if (!FFlag::LuauSubstituteFollowPossibleMutations) - tp = log->follow(tp); + tp = log->follow(tp); edgesTy.push_back(nullptr); edgesTp.push_back(tp); @@ -389,13 +366,10 @@ TypeId Substitution::clone(TypeId ty) TypeId result = ty; - if (FFlag::LuauTypecheckOptPass) - { - if (auto pty = log->pending(ty)) - ty = &pty->pending; - } + if (auto pty = log->pending(ty)) + ty = &pty->pending; - if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + if (const FunctionTypeVar* ftv = get(ty)) { FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf}; clone.generics = ftv->generics; @@ -405,7 +379,7 @@ TypeId Substitution::clone(TypeId ty) clone.argNames = ftv->argNames; result = addType(std::move(clone)); } - else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const TableTypeVar* ttv = get(ty)) { LUAU_ASSERT(!ttv->boundTo); TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; @@ -419,19 +393,19 @@ TypeId Substitution::clone(TypeId ty) clone.tags = ttv->tags; result = addType(std::move(clone)); } - else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const MetatableTypeVar* mtv = get(ty)) { MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable}; clone.syntheticName = mtv->syntheticName; result = addType(std::move(clone)); } - else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const UnionTypeVar* utv = get(ty)) { UnionTypeVar clone; clone.options = utv->options; result = addType(std::move(clone)); } - else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get(ty) : log->getMutable(ty)) + else if (const IntersectionTypeVar* itv = get(ty)) { IntersectionTypeVar clone; clone.parts = itv->parts; @@ -451,20 +425,17 @@ TypePackId Substitution::clone(TypePackId tp) { tp = log->follow(tp); - if (FFlag::LuauTypecheckOptPass) - { - if (auto ptp = log->pending(tp)) - tp = &ptp->pending; - } + if (auto ptp = log->pending(tp)) + tp = &ptp->pending; - if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get(tp) : log->getMutable(tp)) + if (const TypePack* tpp = get(tp)) { TypePack clone; clone.head = tpp->head; clone.tail = tpp->tail; return addTypePack(std::move(clone)); } - else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get(tp) : log->getMutable(tp)) + else if (const VariadicTypePack* vtp = get(tp)) { VariadicTypePack clone; clone.ty = vtp->ty; @@ -476,28 +447,22 @@ TypePackId Substitution::clone(TypePackId tp) void Substitution::foundDirty(TypeId ty) { - if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) - LUAU_ASSERT(ty == log->follow(ty)); - else - ty = log->follow(ty); + ty = log->follow(ty); if (isDirty(ty)) - newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(ty)) : clean(ty); + newTypes[ty] = follow(clean(ty)); else - newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(ty)) : clone(ty); + newTypes[ty] = follow(clone(ty)); } void Substitution::foundDirty(TypePackId tp) { - if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations) - LUAU_ASSERT(tp == log->follow(tp)); - else - tp = log->follow(tp); + tp = log->follow(tp); if (isDirty(tp)) - newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(tp)) : clean(tp); + newPacks[tp] = follow(clean(tp)); else - newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(tp)) : clone(tp); + newPacks[tp] = follow(clone(tp)); } TypeId Substitution::replace(TypeId ty) @@ -525,10 +490,7 @@ void Substitution::replaceChildren(TypeId ty) if (BoundTypeVar* btv = log->getMutable(ty); FFlag::LuauLowerBoundsCalculation && btv) btv->boundTo = replace(btv->boundTo); - if (FFlag::LuauTypecheckOptPass) - LUAU_ASSERT(ty == log->follow(ty)); - else - ty = log->follow(ty); + LUAU_ASSERT(ty == log->follow(ty)); if (ignoreChildren(ty)) return; @@ -579,10 +541,7 @@ void Substitution::replaceChildren(TypeId ty) void Substitution::replaceChildren(TypePackId tp) { - if (FFlag::LuauTypecheckOptPass) - LUAU_ASSERT(tp == log->follow(tp)); - else - tp = log->follow(tp); + LUAU_ASSERT(tp == log->follow(tp)); if (ignoreChildren(tp)) return; diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 380ac4562..f90f7019a 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -219,6 +219,8 @@ struct StringifierState return generateName(s); } + int previousNameIndex = 0; + std::string getName(TypePackId ty) { const size_t s = result.nameMap.typePacks.size(); @@ -228,9 +230,10 @@ struct StringifierState for (int count = 0; count < 256; ++count) { - std::string candidate = generateName(usedNames.size() + count); + std::string candidate = generateName(previousNameIndex + count); if (!usedNames.count(candidate)) { + previousNameIndex += count; usedNames.insert(candidate); n = candidate; return candidate; @@ -399,6 +402,7 @@ struct TypeVarStringifier { if (gtv.explicitName) { + state.usedNames.insert(gtv.name); state.result.nameMap.typeVars[ty] = gtv.name; state.emit(gtv.name); } @@ -943,6 +947,7 @@ struct TypePackStringifier state.emit("gen-"); if (pack.explicitName) { + state.usedNames.insert(pack.name); state.result.nameMap.typePacks[tp] = pack.name; state.emit(pack.name); } diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 1fb5a61a6..e45c0cbd2 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -7,8 +7,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauJustOneCallFrameForHaveSeen, false) - namespace Luau { @@ -150,37 +148,13 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs) bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const { - if (FFlag::LuauJustOneCallFrameForHaveSeen && !FFlag::LuauTypecheckOptPass) - { - // This function will technically work if `this` is nullptr, but this - // indicates a bug, so we explicitly assert. - LUAU_ASSERT(static_cast(this) != nullptr); - - const std::pair 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 sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)) { - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)) - { - return true; - } - - if (!FFlag::LuauTypecheckOptPass && parent) - { - return parent->haveSeen(lhs, rhs); - } - - return false; + return true; } + + return false; } void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs) diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp new file mode 100644 index 000000000..673b002d9 --- /dev/null +++ b/Analysis/src/TypeArena.cpp @@ -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 types) +{ + TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); + + asMutable(allocated)->owningArena = this; + + return allocated; +} + +TypePackId TypeArena::addTypePack(std::vector 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(); +} + +} diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index a13abd537..208b3f2f6 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,32 +32,25 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauSeparateTypechecks) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false) -LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) +LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false) LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) -LUAU_FASTFLAGVARIABLE(LuauInstantiateFollows, false) LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false) -LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false) LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false) LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckOptPass, false) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) -LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. +LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree2) -LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false) -LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false) -LUAU_FASTFLAGVARIABLE(LuauCheckImplicitNumbericKeys, false) -LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional) -LUAU_FASTFLAGVARIABLE(LuauTableUseCounterInstead, false) LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false) LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false); -LUAU_FASTFLAG(LuauLosslessClone) +LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false); LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false); LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false) LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false); +LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); namespace Luau { @@ -371,12 +364,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo prepareErrorsForDisplay(currentModule->errors); - bool encounteredFreeType = currentModule->clonePublicInterface(*iceHandler); - if (!FFlag::LuauLosslessClone && encounteredFreeType) - { - reportError(TypeError{module.root->location, - GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}}); - } + currentModule->clonePublicInterface(*iceHandler); // Clear unifier cache since it's keyed off internal types that get deallocated // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. @@ -701,7 +689,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement) ExprResult result = checkExpr(scope, *statement.condition); ScopePtr ifScope = childScope(scope, statement.thenbody->location); - reportErrors(resolve(result.predicates, ifScope, true)); + resolve(result.predicates, ifScope, true); check(ifScope, *statement.thenbody); if (statement.elsebody) @@ -734,7 +722,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement) ExprResult result = checkExpr(scope, *statement.condition); ScopePtr whileScope = childScope(scope, statement.body->location); - reportErrors(resolve(result.predicates, whileScope, true)); + resolve(result.predicates, whileScope, true); check(whileScope, *statement.body); } @@ -1154,10 +1142,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin) } else { - if (FFlag::LuauInstantiateFollows) - iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location); - else - iterTy = follow(instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location)); + iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location); } if (FFlag::LuauTypecheckIter) @@ -1849,23 +1834,11 @@ std::optional TypeChecker::getIndexTypeFromType( tablify(type); - if (FFlag::LuauDiscriminableUnions2) + if (isString(type)) { - if (isString(type)) - { - std::optional mtIndex = findMetatableEntry(stringType, "__index", location); - LUAU_ASSERT(mtIndex); - type = *mtIndex; - } - } - else - { - const PrimitiveTypeVar* primitiveType = get(type); - if (primitiveType && primitiveType->type == PrimitiveTypeVar::String) - { - if (std::optional mtIndex = findMetatableEntry(type, "__index", location)) - type = *mtIndex; - } + std::optional mtIndex = findMetatableEntry(stringType, "__index", location); + LUAU_ASSERT(mtIndex); + type = *mtIndex; } if (TableTypeVar* tableType = getMutableTableType(type)) @@ -1966,23 +1939,10 @@ std::optional TypeChecker::getIndexTypeFromType( return std::nullopt; } - if (FFlag::LuauDoNotTryToReduce) - { - if (parts.size() == 1) - return parts[0]; - - return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct. - } - else - { - // TODO(amccord): Write some logic to correctly handle intersections. CLI-34659 - std::vector result = reduceUnion(parts); + if (parts.size() == 1) + return parts[0]; - if (result.size() == 1) - return result[0]; - - return addType(IntersectionTypeVar{result}); - } + return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct. } if (addErrors) @@ -1993,103 +1953,55 @@ std::optional TypeChecker::getIndexTypeFromType( std::vector TypeChecker::reduceUnion(const std::vector& types) { - if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering) + std::vector result; + for (TypeId t : types) { - std::vector result; - for (TypeId t : types) - { - t = follow(t); - if (get(t) || get(t)) - return {t}; + t = follow(t); + if (get(t) || get(t)) + return {t}; - if (const UnionTypeVar* utv = get(t)) + if (const UnionTypeVar* utv = get(t)) + { + if (FFlag::LuauReduceUnionRecursion) { - if (FFlag::LuauReduceUnionRecursion) + for (TypeId ty : utv) { - for (TypeId ty : utv) - { - if (get(ty) || get(ty)) - return {ty}; - - if (result.end() == std::find(result.begin(), result.end(), ty)) - result.push_back(ty); - } - } - else - { - std::vector r = reduceUnion(utv->options); - for (TypeId ty : r) - { + if (FFlag::LuauNormalizeFlagIsConservative) ty = follow(ty); - if (get(ty) || get(ty)) - return {ty}; + if (get(ty) || get(ty)) + return {ty}; - if (std::find(result.begin(), result.end(), ty) == result.end()) - result.push_back(ty); - } + if (result.end() == std::find(result.begin(), result.end(), ty)) + result.push_back(ty); } } - else if (std::find(result.begin(), result.end(), t) == result.end()) - result.push_back(t); - } - - return result; - } - else - { - std::set s; - - for (TypeId t : types) - { - if (const UnionTypeVar* utv = get(follow(t))) + else { std::vector r = reduceUnion(utv->options); for (TypeId ty : r) - s.insert(ty); - } - else - s.insert(t); - } + { + ty = follow(ty); + if (get(ty) || get(ty)) + return {ty}; - // If any of them are ErrorTypeVars/AnyTypeVars, decay into them. - for (TypeId t : s) - { - t = follow(t); - if (get(t) || get(t)) - return {t}; + if (std::find(result.begin(), result.end(), ty) == result.end()) + result.push_back(ty); + } + } } - - std::vector r(s.begin(), s.end()); - std::sort(r.begin(), r.end()); - return r; + else if (std::find(result.begin(), result.end(), t) == result.end()) + result.push_back(t); } + + return result; } std::optional TypeChecker::tryStripUnionFromNil(TypeId ty) { if (const UnionTypeVar* utv = get(ty)) { - if (FFlag::LuauAnyInIsOptionalIsOptional) - { - if (!std::any_of(begin(utv), end(utv), isNil)) - return ty; - } - else - { - bool hasNil = false; - - for (TypeId option : utv) - { - if (isNil(option)) - { - hasNil = true; - break; - } - } - - if (!hasNil) - return ty; - } + if (!std::any_of(begin(utv), end(utv), isNil)) + return ty; std::vector result; @@ -2110,32 +2022,18 @@ std::optional TypeChecker::tryStripUnionFromNil(TypeId ty) TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location) { - if (FFlag::LuauAnyInIsOptionalIsOptional) - { - ty = follow(ty); - - if (auto utv = get(ty)) - { - if (!std::any_of(begin(utv), end(utv), isNil)) - return ty; - } + ty = follow(ty); - if (std::optional strippedUnion = tryStripUnionFromNil(ty)) - { - reportError(location, OptionalValueAccess{ty}); - return follow(*strippedUnion); - } + if (auto utv = get(ty)) + { + if (!std::any_of(begin(utv), end(utv), isNil)) + return ty; } - else + + if (std::optional strippedUnion = tryStripUnionFromNil(ty)) { - if (isOptional(ty)) - { - if (std::optional strippedUnion = tryStripUnionFromNil(follow(ty))) - { - reportError(location, OptionalValueAccess{ty}); - return follow(*strippedUnion); - } - } + reportError(location, OptionalValueAccess{ty}); + return follow(*strippedUnion); } return ty; @@ -2194,8 +2092,7 @@ TypeId TypeChecker::checkExprTable( if (indexer) { - if (FFlag::LuauCheckImplicitNumbericKeys) - unify(numberType, indexer->indexType, value->location); + unify(numberType, indexer->indexType, value->location); unify(valueType, indexer->indexResultType, value->location); } else @@ -2219,7 +2116,8 @@ TypeId TypeChecker::checkExprTable( if (errors.empty()) exprType = expectedProp.type; } - else if (expectedTable->indexer && isString(expectedTable->indexer->indexType)) + else if (expectedTable->indexer && (FFlag::LuauExpectedPropTypeFromIndexer ? maybeString(expectedTable->indexer->indexType) + : isString(expectedTable->indexer->indexType))) { ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, k->location); if (errors.empty()) @@ -2259,26 +2157,13 @@ TypeId TypeChecker::checkExprTable( ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional expectedType) { - if (FFlag::LuauTableUseCounterInstead) - { - RecursionCounter _rc(&checkRecursionCount); - if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit) - { - reportErrorCodeTooComplex(expr.location); - return {errorRecoveryType(scope)}; - } - - return checkExpr_(scope, expr, expectedType); - } - else + RecursionCounter _rc(&checkRecursionCount); + if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit) { - RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit, "checkExpr for tables"); - return checkExpr_(scope, expr, expectedType); + reportErrorCodeTooComplex(expr.location); + return {errorRecoveryType(scope)}; } -} -ExprResult TypeChecker::checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional expectedType) -{ std::vector> fieldTypes(expr.items.size); const TableTypeVar* expectedTable = nullptr; @@ -2324,6 +2209,8 @@ ExprResult TypeChecker::checkExpr_(const ScopePtr& scope, const AstExprT { if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end()) expectedResultType = prop->second.type; + else if (FFlag::LuauExpectedPropTypeFromIndexer && expectedIndexType && maybeString(*expectedIndexType)) + expectedResultType = expectedIndexResultType; } else if (expectedUnion) { @@ -2529,7 +2416,7 @@ TypeId TypeChecker::checkRelationalOperation( if (expr.op == AstExprBinary::Or && subexp->op == AstExprBinary::And) { ScopePtr subScope = childScope(scope, subexp->location); - reportErrors(resolve(predicates, subScope, true)); + resolve(predicates, subScope, true); return unionOfTypes(rhsType, stripNil(checkExpr(subScope, *subexp->right).type, true), expr.location); } } @@ -2851,8 +2738,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right); - return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy), - {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; + return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; } else if (expr.op == AstExprBinary::Or) { @@ -2864,7 +2750,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right); // Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation. - TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates); + TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates); return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; } else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) @@ -2872,8 +2758,8 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi if (auto predicate = tryGetTypeGuardPredicate(expr)) return {booleanType, {std::move(*predicate)}}; - ExprResult lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2); - ExprResult rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2); + ExprResult lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true); + ExprResult rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true); PredicateVec predicates; @@ -2931,12 +2817,12 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprEr ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional expectedType) { ExprResult result = checkExpr(scope, *expr.condition); + ScopePtr trueScope = childScope(scope, expr.trueExpr->location); - reportErrors(resolve(result.predicates, trueScope, true)); + resolve(result.predicates, trueScope, true); ExprResult trueType = checkExpr(trueScope, *expr.trueExpr, expectedType); ScopePtr falseScope = childScope(scope, expr.falseExpr->location); - // Don't report errors for this scope to avoid potentially duplicating errors reported for the first scope. resolve(result.predicates, falseScope, false); ExprResult falseType = checkExpr(falseScope, *expr.falseExpr, expectedType); @@ -3668,9 +3554,6 @@ void TypeChecker::checkArgumentList( else if (state.log.getMutable(t)) { } // ok - else if (!FFlag::LuauAnyInIsOptionalIsOptional && isNonstrictMode() && state.log.get(t)) - { - } // ok else { size_t minParams = getMinParameterCount(&state.log, paramPack); @@ -3823,9 +3706,6 @@ ExprResult TypeChecker::checkExprPack(const ScopePtr& scope, const A actualFunctionType = instantiate(scope, functionType, expr.func->location); } - if (!FFlag::LuauInstantiateFollows) - actualFunctionType = follow(actualFunctionType); - TypePackId retPack; if (FFlag::LuauLowerBoundsCalculation || !FFlag::LuauWidenIfSupertypeIsFree2) { @@ -4096,32 +3976,6 @@ std::optional> TypeChecker::checkCallOverload(const Scope { state.log.commit(); - if (!FFlag::LuauAnyInIsOptionalIsOptional && isNonstrictMode() && !expr.self && expr.func->is() && 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 actualArgs = flatten(ftv->argTypes).first; - - size_t providedCount = providedArgs.size(); - size_t requiredCount = actualArgs.size(); - - // Ignore optional arguments - while (providedCount < requiredCount && requiredCount != 0 && isOptional(actualArgs[requiredCount - 1])) - requiredCount--; - - if (providedCount < requiredCount) - { - int requiredExtraNils = int(requiredCount - providedCount); - reportError(TypeError{expr.func->location, FunctionRequiresSelf{requiredExtraNils}}); - } - } - } - currentModule->astOverloadResolvedTypes[&expr] = fn; // We select this overload @@ -4525,7 +4379,7 @@ bool Instantiation::isDirty(TypeId ty) { if (const FunctionTypeVar* ftv = log->getMutable(ty)) { - if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics) + if (ftv->hasNoGenerics) return false; return true; @@ -4582,7 +4436,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty) { if (const FunctionTypeVar* ftv = log->getMutable(ty)) { - if (FFlag::LuauTypecheckOptPass && ftv->hasNoGenerics) + if (ftv->hasNoGenerics) return true; // We aren't recursing in the case of a generic function which @@ -4701,8 +4555,17 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location ty = follow(ty); const FunctionTypeVar* ftv = get(ty); - if (ftv && ftv->generics.empty() && ftv->genericPacks.empty()) - Luau::quantify(ty, scope->level); + + if (FFlag::LuauAlwaysQuantify) + { + if (ftv) + Luau::quantify(ty, scope->level); + } + else + { + if (ftv && ftv->generics.empty() && ftv->genericPacks.empty()) + Luau::quantify(ty, scope->level); + } if (FFlag::LuauLowerBoundsCalculation && ftv) { @@ -4717,15 +4580,11 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log) { - if (FFlag::LuauInstantiateFollows) - ty = follow(ty); + ty = follow(ty); - if (FFlag::LuauTypecheckOptPass) - { - const FunctionTypeVar* ftv = get(FFlag::LuauInstantiateFollows ? ty : follow(ty)); - if (ftv && ftv->hasNoGenerics) - return ty; - } + const FunctionTypeVar* ftv = get(ty); + if (ftv && ftv->hasNoGenerics) + return ty; Instantiation instantiation{log, ¤tModule->internalTypes, scope->level}; @@ -5392,10 +5251,9 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack bool ApplyTypeFunction::isDirty(TypeId ty) { - // Really this should just replace the arguments, - // but for bug-compatibility with existing code, we replace - // all generics. - if (get(ty)) + if (FFlag::LuauApplyTypeFunctionFix && typeArguments.count(ty)) + return true; + else if (!FFlag::LuauApplyTypeFunctionFix && get(ty)) return true; else if (const FreeTypeVar* ftv = get(ty)) { @@ -5409,10 +5267,9 @@ bool ApplyTypeFunction::isDirty(TypeId ty) bool ApplyTypeFunction::isDirty(TypePackId tp) { - // Really this should just replace the arguments, - // but for bug-compatibility with existing code, we replace - // all generics. - if (get(tp)) + if (FFlag::LuauApplyTypeFunctionFix && typePackArguments.count(tp)) + return true; + else if (!FFlag::LuauApplyTypeFunctionFix && get(tp)) return true; else return false; @@ -5436,11 +5293,13 @@ bool ApplyTypeFunction::ignoreChildren(TypePackId tp) TypeId ApplyTypeFunction::clean(TypeId ty) { - // Really this should just replace the arguments, - // but for bug-compatibility with existing code, we replace - // all generics by free type variables. TypeId& arg = typeArguments[ty]; - if (arg) + if (FFlag::LuauApplyTypeFunctionFix) + { + LUAU_ASSERT(arg); + return arg; + } + else if (arg) return arg; else return addType(FreeTypeVar{level}); @@ -5448,11 +5307,13 @@ TypeId ApplyTypeFunction::clean(TypeId ty) TypePackId ApplyTypeFunction::clean(TypePackId tp) { - // Really this should just replace the arguments, - // but for bug-compatibility with existing code, we replace - // all generics by free type variables. TypePackId& arg = typePackArguments[tp]; - if (arg) + if (FFlag::LuauApplyTypeFunctionFix) + { + LUAU_ASSERT(arg); + return arg; + } + else if (arg) return arg; else return addTypePack(FreeTypePack{level}); @@ -5596,8 +5457,6 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate) { - LUAU_ASSERT(FFlag::LuauDiscriminableUnions2 || FFlag::LuauAssertStripsFalsyTypes); - const LValue* target = &lvalue; std::optional 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 TypeChecker::resolveLValue(const ScopePtr& scope, const LV // We need to search in the provided Scope. Find t.x.y first. // We fail to find t.x.y. Try t.x. We found it. Now we must return the type of the property y from the mapped-to type of t.x. // If we completely fail to find the Symbol t but the Scope has that entry, then we should walk that all the way through and terminate. - if (!FFlag::LuauTypecheckOptPass) - { - const auto& [symbol, keys] = getFullName(lvalue); - - ScopePtr currentScope = scope; - while (currentScope) - { - std::optional found; - - std::vector 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(key)) - continue; - else if (auto field = get(key)) - { - found = getIndexTypeFromType(scope, *found, field->key, Location(), false); - if (!found) - return std::nullopt; // Turns out this type doesn't have the property at all. We're done. - } - else - LUAU_ASSERT(!"New LValue alternative not handled here."); - } - - return found; - } - - // No entry for it at all. Can happen when LValue root is a global. - return std::nullopt; - } - const Symbol symbol = getBaseSymbol(lvalue); ScopePtr currentScope = scope; @@ -5820,85 +5619,47 @@ static bool isUndecidable(TypeId ty) return get(ty) || get(ty) || get(ty); } -ErrorVec TypeChecker::resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense) { - ErrorVec errVec; - resolve(predicates, errVec, scope->refinements, scope, sense); - return errVec; + resolve(predicates, scope->refinements, scope, sense); } -void TypeChecker::resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) +void TypeChecker::resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) { for (const Predicate& c : predicates) - resolve(c, errVec, refis, scope, sense, fromOr); + resolve(c, refis, scope, sense, fromOr); } -void TypeChecker::resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) +void TypeChecker::resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) { if (auto truthyP = get(predicate)) - resolve(*truthyP, errVec, refis, scope, sense, fromOr); + resolve(*truthyP, refis, scope, sense, fromOr); else if (auto andP = get(predicate)) - resolve(*andP, errVec, refis, scope, sense); + resolve(*andP, refis, scope, sense); else if (auto orP = get(predicate)) - resolve(*orP, errVec, refis, scope, sense); + resolve(*orP, refis, scope, sense); else if (auto notP = get(predicate)) - resolve(notP->predicates, errVec, refis, scope, !sense, fromOr); + resolve(notP->predicates, refis, scope, !sense, fromOr); else if (auto isaP = get(predicate)) - resolve(*isaP, errVec, refis, scope, sense); + resolve(*isaP, refis, scope, sense); else if (auto typeguardP = get(predicate)) - resolve(*typeguardP, errVec, refis, scope, sense); + resolve(*typeguardP, refis, scope, sense); else if (auto eqP = get(predicate)) - resolve(*eqP, errVec, refis, scope, sense); + resolve(*eqP, refis, scope, sense); else ice("Unhandled predicate kind"); } -void TypeChecker::resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) +void TypeChecker::resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr) { - if (FFlag::LuauAssertStripsFalsyTypes) - { - std::optional ty = resolveLValue(refis, scope, truthyP.lvalue); - if (ty && fromOr) - return addRefinement(refis, truthyP.lvalue, *ty); - - refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense)); - } - else - { - auto predicate = [sense](TypeId option) -> std::optional { - if (isUndecidable(option) || isBoolean(option) || isNil(option) != sense) - return option; - - return std::nullopt; - }; - - if (FFlag::LuauDiscriminableUnions2) - { - std::optional ty = resolveLValue(refis, scope, truthyP.lvalue); - if (ty && fromOr) - return addRefinement(refis, truthyP.lvalue, *ty); + std::optional ty = resolveLValue(refis, scope, truthyP.lvalue); + if (ty && fromOr) + return addRefinement(refis, truthyP.lvalue, *ty); - refineLValue(truthyP.lvalue, refis, scope, predicate); - } - else - { - std::optional 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 result = filterMap(*ty, predicate)) - addRefinement(refis, truthyP.lvalue, *result); - } - } + refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense)); } -void TypeChecker::resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense) { if (!sense) { @@ -5907,14 +5668,14 @@ void TypeChecker::resolve(const AndPredicate& andP, ErrorVec& errVec, Refinement {NotPredicate{std::move(andP.rhs)}}, }; - return resolve(orP, errVec, refis, scope, !sense); + return resolve(orP, refis, scope, !sense); } - resolve(andP.lhs, errVec, refis, scope, sense); - resolve(andP.rhs, errVec, refis, scope, sense); + resolve(andP.lhs, refis, scope, sense); + resolve(andP.rhs, refis, scope, sense); } -void TypeChecker::resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense) { if (!sense) { @@ -5923,28 +5684,24 @@ void TypeChecker::resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMa {NotPredicate{std::move(orP.rhs)}}, }; - return resolve(andP, errVec, refis, scope, !sense); + return resolve(andP, refis, scope, !sense); } - ErrorVec discarded; - RefinementMap leftRefis; - resolve(orP.lhs, errVec, leftRefis, scope, sense); + resolve(orP.lhs, leftRefis, scope, sense); RefinementMap rightRefis; - resolve(orP.lhs, discarded, rightRefis, scope, !sense); - resolve(orP.rhs, errVec, rightRefis, scope, sense, true); // :( + resolve(orP.lhs, rightRefis, scope, !sense); + resolve(orP.rhs, rightRefis, scope, sense, true); // :( merge(refis, leftRefis); merge(refis, rightRefis); } -void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense) { auto predicate = [&](TypeId option) -> std::optional { // This by itself is not truly enough to determine that A is stronger than B or vice versa. - // The best unambiguous way about this would be to have a function that returns the relationship ordering of a pair. - // i.e. TypeRelationship relationshipOf(TypeId superTy, TypeId subTy) bool optionIsSubtype = canUnify(option, isaP.ty, isaP.location).empty(); bool targetIsSubtype = canUnify(isaP.ty, option, isaP.location).empty(); @@ -5985,32 +5742,15 @@ void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, Refinement return res; }; - if (FFlag::LuauDiscriminableUnions2) - { - refineLValue(isaP.lvalue, refis, scope, predicate); - } - else - { - std::optional ty = resolveLValue(refis, scope, isaP.lvalue); - if (!ty) - return; - - if (std::optional result = filterMap(*ty, predicate)) - addRefinement(refis, isaP.lvalue, *result); - else - { - addRefinement(refis, isaP.lvalue, errorRecoveryType(scope)); - errVec.push_back(TypeError{isaP.location, TypeMismatch{isaP.ty, *ty}}); - } - } + refineLValue(isaP.lvalue, refis, scope, predicate); } -void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense) { // Rewrite the predicate 'type(foo) == "vector"' to be 'typeof(foo) == "Vector3"'. They're exactly identical. // This allows us to avoid writing in edge cases. if (!typeguardP.isTypeof && typeguardP.kind == "vector") - return resolve(TypeGuardPredicate{std::move(typeguardP.lvalue), typeguardP.location, "Vector3", true}, errVec, refis, scope, sense); + return resolve(TypeGuardPredicate{std::move(typeguardP.lvalue), typeguardP.location, "Vector3", true}, refis, scope, sense); std::optional ty = resolveLValue(refis, scope, typeguardP.lvalue); if (!ty) @@ -6060,52 +5800,29 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec if (auto it = primitives.find(typeguardP.kind); it != primitives.end()) { - if (FFlag::LuauDiscriminableUnions2) - { - refineLValue(typeguardP.lvalue, refis, scope, it->second(sense)); - return; - } - else - { - if (std::optional result = filterMap(*ty, it->second(sense))) - addRefinement(refis, typeguardP.lvalue, *result); - else - { - addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); - if (sense) - errVec.push_back( - TypeError{typeguardP.location, GenericError{"Type '" + toString(*ty) + "' has no overlap with '" + typeguardP.kind + "'"}}); - } - - return; - } + refineLValue(typeguardP.lvalue, refis, scope, it->second(sense)); + return; } - auto fail = [&](const TypeErrorData& err) { - if (!FFlag::LuauDiscriminableUnions2) - errVec.push_back(TypeError{typeguardP.location, err}); - addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); - }; - if (!typeguardP.isTypeof) - return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); + return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); auto typeFun = globalScope->lookupType(typeguardP.kind); if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty()) - return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); + return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); TypeId type = follow(typeFun->type); // We're only interested in the root class of any classes. if (auto ctv = get(type); !ctv || ctv->parent) - return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); + return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); // This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA. // Until then, we rewrite this to be the same as using IsA. - return resolve(IsAPredicate{std::move(typeguardP.lvalue), typeguardP.location, type}, errVec, refis, scope, sense); + return resolve(IsAPredicate{std::move(typeguardP.lvalue), typeguardP.location, type}, refis, scope, sense); } -void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense) +void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense) { // This refinement will require success typing to do everything correctly. For now, we can get most of the way there. auto options = [](TypeId ty) -> std::vector { @@ -6114,82 +5831,33 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa return {ty}; }; - if (FFlag::LuauDiscriminableUnions2) - { - std::vector rhs = options(eqP.type); + std::vector rhs = options(eqP.type); - if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable)) - return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. + if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable)) + return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. - auto predicate = [&](TypeId option) -> std::optional { - if (sense && isUndecidable(option)) - return FFlag::LuauWeakEqConstraint ? option : eqP.type; - - if (!sense && isNil(eqP.type)) - return (isUndecidable(option) || !isNil(option)) ? std::optional(option) : std::nullopt; - - if (maybeSingleton(eqP.type)) - { - // Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this. - if (!sense || canUnify(eqP.type, option, eqP.location).empty()) - return sense ? eqP.type : option; - - // local variable works around an odd gcc 9.3 warning: may be used uninitialized - std::optional res = std::nullopt; - return res; - } + auto predicate = [&](TypeId option) -> std::optional { + if (sense && isUndecidable(option)) + return FFlag::LuauWeakEqConstraint ? option : eqP.type; - return option; - }; + if (!sense && isNil(eqP.type)) + return (isUndecidable(option) || !isNil(option)) ? std::optional(option) : std::nullopt; - refineLValue(eqP.lvalue, refis, scope, predicate); - } - else - { - if (FFlag::LuauWeakEqConstraint) + if (maybeSingleton(eqP.type)) { - if (!sense && isNil(eqP.type)) - resolve(TruthyPredicate{std::move(eqP.lvalue), eqP.location}, errVec, refis, scope, true, /* fromOr= */ false); + // Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this. + if (!sense || canUnify(eqP.type, option, eqP.location).empty()) + return sense ? eqP.type : option; - return; + // local variable works around an odd gcc 9.3 warning: may be used uninitialized + std::optional res = std::nullopt; + return res; } - if (FFlag::LuauEqConstraint) - { - std::optional ty = resolveLValue(refis, scope, eqP.lvalue); - if (!ty) - return; - - std::vector lhs = options(*ty); - std::vector 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 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; + return option; + }; - std::vector viable(set.begin(), set.end()); - TypeId result = viable.size() == 1 ? viable[0] : addType(UnionTypeVar{std::move(viable)}); - addRefinement(refis, eqP.lvalue, result); - } - } + refineLValue(eqP.lvalue, refis, scope, predicate); } bool TypeChecker::isNonstrictMode() const diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index c24358900..ba09df5f5 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -5,8 +5,6 @@ #include "Luau/ToString.h" #include "Luau/TypeInfer.h" -LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false) - namespace Luau { @@ -55,13 +53,10 @@ std::optional findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t { TypeId index = follow(*mtIndex); - if (FFlag::LuauTerminateCyclicMetatableIndexLookup) - { - if (count >= 100) - return std::nullopt; + if (count >= 100) + return std::nullopt; - ++count; - } + ++count; if (const auto& itt = getTableType(index)) { diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 463b4651a..2355dab2c 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -24,8 +24,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables) -LUAU_FASTFLAG(LuauDiscriminableUnions2) -LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false) LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false) namespace Luau @@ -204,14 +202,14 @@ bool isOptional(TypeId ty) ty = follow(ty); - if (FFlag::LuauAnyInIsOptionalIsOptional && get(ty)) + if (get(ty)) return true; auto utv = get(ty); if (!utv) return false; - return std::any_of(begin(utv), end(utv), FFlag::LuauAnyInIsOptionalIsOptional ? isOptional : isNil); + return std::any_of(begin(utv), end(utv), isOptional); } bool isTableIntersection(TypeId ty) @@ -378,8 +376,7 @@ bool hasLength(TypeId ty, DenseHashSet& seen, int* recursionCount) if (seen.contains(ty)) return true; - bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String); - if (isStr || get(ty) || get(ty) || get(ty)) + if (isString(ty) || get(ty) || get(ty) || get(ty)) return true; if (auto uty = get(ty)) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index f5c1dde95..9308e9ff6 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -24,8 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false) LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false) LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false) -LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional) -LUAU_FASTFLAG(LuauTypecheckOptPass) namespace Luau { @@ -382,19 +380,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance LUAU_ASSERT(sharedState.iceHandler); } -Unifier::Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, - Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) - : types(types) - , mode(mode) - , log(parentLog, sharedSeen) - , location(location) - , variance(variance) - , sharedState(sharedState) -{ - LUAU_ASSERT(!FFlag::LuauTypecheckOptPass); - LUAU_ASSERT(sharedState.iceHandler); -} - void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection) { sharedState.counters.iterationCount = 0; @@ -1219,14 +1204,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal continue; } - // In nonstrict mode, any also marks an optional argument. - else if (!FFlag::LuauAnyInIsOptionalIsOptional && superIter.good() && isNonstrictMode() && - log.getMutable(log.follow(*superIter))) - { - superIter.advance(); - continue; - } - if (log.getMutable(superIter.packId)) { tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index)); @@ -1454,21 +1431,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) { auto subIter = subTable->props.find(propName); - if (FFlag::LuauAnyInIsOptionalIsOptional) - { - if (subIter == subTable->props.end() && - (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type)) - missingProperties.push_back(propName); - } - else - { - bool isAny = log.getMutable(log.follow(superProp.type)); - - if (subIter == subTable->props.end() && - (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) && - !isAny) - missingProperties.push_back(propName); - } + if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && + !isOptional(superProp.type)) + missingProperties.push_back(propName); } if (!missingProperties.empty()) @@ -1485,18 +1450,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) { auto superIter = superTable->props.find(propName); - if (FFlag::LuauAnyInIsOptionalIsOptional) - { - if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type))) - extraProperties.push_back(propName); - } - else - { - bool isAny = log.is(log.follow(subProp.type)); - if (superIter == superTable->props.end() && - (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny))) - extraProperties.push_back(propName); - } + if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type))) + extraProperties.push_back(propName); } if (!extraProperties.empty()) @@ -1540,18 +1495,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) if (innerState.errors.empty()) log.concat(std::move(innerState.log)); } - else if (FFlag::LuauAnyInIsOptionalIsOptional && - (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type)) - // This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }` - // since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`. - // TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?) - { - } - else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && - (isOptional(prop.type) || get(follow(prop.type)))) + else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type)) // This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }` // since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`. - // TODO: should isOptional(anyType) be true? // TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?) { } @@ -1618,10 +1564,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) else if (variance == Covariant) { } - else if (FFlag::LuauAnyInIsOptionalIsOptional && !FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type)) - { - } - else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get(follow(prop.type)))) + else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type)) { } else if (superTable->state == TableState::Free) @@ -1753,9 +1696,7 @@ TypePackId Unifier::widen(TypePackId tp) TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map seen) { ty = follow(ty); - if (!FFlag::LuauAnyInIsOptionalIsOptional && get(ty)) - return ty; - else if (isOptional(ty)) + if (isOptional(ty)) return ty; else if (const TableTypeVar* ttv = get(ty)) { @@ -2666,14 +2607,7 @@ void Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, Typ Unifier Unifier::makeChildUnifier() { - if (FFlag::LuauTypecheckOptPass) - { - Unifier u = Unifier{types, mode, location, variance, sharedState, &log}; - u.anyIsTop = anyIsTop; - return u; - } - - Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log}; + Unifier u = Unifier{types, mode, location, variance, sharedState, &log}; u.anyIsTop = anyIsTop; return u; } diff --git a/Compiler/include/Luau/BytecodeBuilder.h b/Compiler/include/Luau/BytecodeBuilder.h index b00440aef..12465377f 100644 --- a/Compiler/include/Luau/BytecodeBuilder.h +++ b/Compiler/include/Luau/BytecodeBuilder.h @@ -224,6 +224,7 @@ class BytecodeBuilder DenseHashMap constantMap; DenseHashMap tableShapeMap; + DenseHashMap protoMap; int debugLine = 0; diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index fb70392e2..beeda295b 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -6,6 +6,8 @@ #include #include +LUAU_FASTFLAG(LuauCompileNestedClosureO2) + namespace Luau { @@ -181,6 +183,7 @@ size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder) : constantMap({Constant::Type_Nil, ~0ull}) , tableShapeMap(TableShape()) + , protoMap(~0u) , stringTable({nullptr, 0}) , encoder(encoder) { @@ -250,6 +253,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues) constantMap.clear(); tableShapeMap.clear(); + protoMap.clear(); debugRemarks.clear(); debugRemarkBuffer.clear(); @@ -372,11 +376,17 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid) int16_t BytecodeBuilder::addChildFunction(uint32_t fid) { + if (FFlag::LuauCompileNestedClosureO2) + if (int16_t* cache = protoMap.find(fid)) + return *cache; + uint32_t id = uint32_t(protos.size()); if (id >= kMaxClosureCount) return -1; + if (FFlag::LuauCompileNestedClosureO2) + protoMap[fid] = int16_t(id); protos.push_back(fid); return int16_t(id); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index e177e9281..4f26ceb90 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -17,8 +17,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauCompileSupportInlining, false) - LUAU_FASTFLAGVARIABLE(LuauCompileIter, false) LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false) LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false) @@ -30,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) +LUAU_FASTFLAGVARIABLE(LuauCompileNestedClosureO2, false) + namespace Luau { @@ -100,13 +100,11 @@ struct Compiler upvals.reserve(16); } - uint8_t getLocal(AstLocal* local) + int getLocalReg(AstLocal* local) { Local* l = locals.find(local); - LUAU_ASSERT(l); - LUAU_ASSERT(l->allocated); - return l->reg; + return l && l->allocated ? l->reg : -1; } uint8_t getUpval(AstLocal* local) @@ -159,17 +157,19 @@ struct Compiler AstExprFunction* getFunctionExpr(AstExpr* node) { - if (AstExprLocal* le = node->as()) + if (AstExprLocal* expr = node->as()) { - Variable* lv = variables.find(le->local); + Variable* lv = variables.find(expr->local); if (!lv || lv->written || !lv->init) return nullptr; return getFunctionExpr(lv->init); } - else if (AstExprGroup* ge = node->as()) - return getFunctionExpr(ge->expr); + else if (AstExprGroup* expr = node->as()) + return getFunctionExpr(expr->expr); + else if (AstExprTypeAssertion* expr = node->as()) + return getFunctionExpr(expr->expr); else return node->as(); } @@ -180,13 +180,13 @@ struct Compiler { bool result = true; - bool visit(AstExpr* node) override + bool visit(AstExprFunction* node) override { - // nested functions may capture function arguments, and our upval handling doesn't handle elided variables (constant) - // TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues - // TODO: additionally we would need to change upvalue handling in compileExprFunction to handle upvalue->local migration - result = result && !node->is(); - return result; + if (!FFlag::LuauCompileNestedClosureO2) + result = false; + + // short-circuit to avoid analyzing nested closure bodies + return false; } bool visit(AstStat* node) override @@ -275,8 +275,7 @@ struct Compiler f.upvals = upvals; // record information for inlining - if (FFlag::LuauCompileSupportInlining && options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && - !getfenvUsed && !setfenvUsed) + if (options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && !getfenvUsed && !setfenvUsed) { f.canInline = true; f.stackSize = stackSize; @@ -346,8 +345,8 @@ struct Compiler uint8_t argreg; - if (isExprLocalReg(arg)) - argreg = getLocal(arg->as()->local); + if (int reg = getExprLocalReg(arg); reg >= 0) + argreg = uint8_t(reg); else { argreg = uint8_t(regs + 1); @@ -403,8 +402,8 @@ struct Compiler } } - if (isExprLocalReg(expr->args.data[i])) - args[i] = getLocal(expr->args.data[i]->as()->local); + if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0) + args[i] = uint8_t(reg); else { args[i] = uint8_t(regs + 1 + i); @@ -489,19 +488,18 @@ struct Compiler return false; } - // TODO: we can compile functions with mismatching arity at call site but it's more annoying - if (func->args.size != expr->args.size) - { - bytecode.addDebugRemark("inlining failed: argument count mismatch (expected %d, got %d)", int(func->args.size), int(expr->args.size)); - return false; - } - - // we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining + // compute constant bitvector for all arguments to feed the cost model bool varc[8] = {}; - for (size_t i = 0; i < expr->args.size && i < 8; ++i) + for (size_t i = 0; i < func->args.size && i < expr->args.size && i < 8; ++i) varc[i] = isConstant(expr->args.data[i]); - int inlinedCost = computeCost(fi->costModel, varc, std::min(int(expr->args.size), 8)); + // if the last argument only returns a single value, all following arguments are nil + if (expr->args.size != 0 && !(expr->args.data[expr->args.size - 1]->is() || expr->args.data[expr->args.size - 1]->is())) + for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i) + varc[i] = true; + + // we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining + int inlinedCost = computeCost(fi->costModel, varc, std::min(int(func->args.size), 8)); int baselineCost = computeCost(fi->costModel, nullptr, 0) + 3; int inlineProfit = (inlinedCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / inlinedCost); @@ -533,15 +531,44 @@ struct Compiler for (size_t i = 0; i < func->args.size; ++i) { AstLocal* var = func->args.data[i]; - AstExpr* arg = expr->args.data[i]; + AstExpr* arg = i < expr->args.size ? expr->args.data[i] : nullptr; - if (Variable* vv = variables.find(var); vv && vv->written) + if (i + 1 == expr->args.size && func->args.size > expr->args.size && (arg->is() || arg->is())) + { + // 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()) + compileExprCall(expr, reg, tail, /* targetTop= */ true); + else if (AstExprVarargs* expr = arg->as()) + compileExprVarargs(expr, reg, tail); + else + LUAU_ASSERT(!"Unexpected expression type"); + + for (size_t j = i; j < func->args.size; ++j) + pushLocal(func->args.data[j], uint8_t(reg + (j - i))); + + // all remaining function arguments have been allocated and assigned to + break; + } + else if (Variable* vv = variables.find(var); vv && vv->written) { // if the argument is mutated, we need to allocate a fresh register even if it's a constant uint8_t reg = allocReg(arg, 1); - compileExprTemp(arg, reg); + + if (arg) + compileExprTemp(arg, reg); + else + bytecode.emitABC(LOP_LOADNIL, reg, 0, 0); + pushLocal(var, reg); } + else if (arg == nullptr) + { + // since the argument is not mutated, we can simply fold the value into the expressions that need it + locstants[var] = {Constant::Type_Nil}; + } else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown) { // since the argument is not mutated, we can simply fold the value into the expressions that need it @@ -553,20 +580,26 @@ struct Compiler Variable* lv = le ? variables.find(le->local) : nullptr; // if the argument is a local that isn't mutated, we will simply reuse the existing register - if (isExprLocalReg(arg) && (!lv || !lv->written)) + if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written)) { - uint8_t reg = getLocal(le->local); - pushLocal(var, reg); + pushLocal(var, uint8_t(reg)); } else { - uint8_t reg = allocReg(arg, 1); - compileExprTemp(arg, reg); - pushLocal(var, reg); + uint8_t temp = allocReg(arg, 1); + compileExprTemp(arg, temp); + pushLocal(var, temp); } } } + // evaluate extra expressions for side effects + for (size_t i = func->args.size; i < expr->args.size; ++i) + { + RegScope rsi(this); + compileExprAuto(expr->args.data[i], rsi); + } + // fold constant values updated above into expressions in the function body foldConstants(constants, variables, locstants, func->body); @@ -627,12 +660,15 @@ struct Compiler FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth)) return; - if (fi && !fi->canInline) + // add a debug remark for cases when we didn't even call tryCompileInlinedCall + if (func && !(fi && fi->canInline)) { if (func->vararg) bytecode.addDebugRemark("inlining failed: function is variadic"); - else + else if (fi) bytecode.addDebugRemark("inlining failed: complex constructs in function body"); + else + bytecode.addDebugRemark("inlining failed: can't inline recursive calls"); } } @@ -677,9 +713,9 @@ struct Compiler LUAU_ASSERT(fi); // Optimization: use local register directly in NAMECALL if possible - if (isExprLocalReg(fi->expr)) + if (int reg = getExprLocalReg(fi->expr); reg >= 0) { - selfreg = getLocal(fi->expr->as()->local); + selfreg = uint8_t(reg); } else { @@ -785,6 +821,8 @@ struct Compiler void compileExprFunction(AstExprFunction* expr, uint8_t target) { + RegScope rs(this); + const Function* f = functions.find(expr); LUAU_ASSERT(f); @@ -795,6 +833,67 @@ struct Compiler if (pid < 0) CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile"); + if (FFlag::LuauCompileNestedClosureO2) + { + captures.clear(); + captures.reserve(f->upvals.size()); + + for (AstLocal* uv : f->upvals) + { + LUAU_ASSERT(uv->functionDepth < expr->functionDepth); + + if (int reg = getLocalReg(uv); reg >= 0) + { + // note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals + Variable* ul = variables.find(uv); + bool immutable = !ul || !ul->written; + + captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)}); + } + else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown) + { + // inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register + uint8_t reg = allocReg(expr, 1); + compileExprConstant(expr, uc, reg); + + captures.push_back({LCT_VAL, reg}); + } + else + { + LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1); + + // get upvalue from parent frame + // note: this will add uv to the current upvalue list if necessary + uint8_t uid = getUpval(uv); + + captures.push_back({LCT_UPVAL, uid}); + } + } + + // Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure + // objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it + // is used) + int16_t shared = -1; + + if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed) + { + int32_t cid = bytecode.addConstantClosure(f->id); + + if (cid >= 0 && cid < 32768) + shared = int16_t(cid); + } + + if (shared >= 0) + bytecode.emitAD(LOP_DUPCLOSURE, target, shared); + else + bytecode.emitAD(LOP_NEWCLOSURE, target, pid); + + for (const Capture& c : captures) + bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0); + + return; + } + bool shared = false; // Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure @@ -824,9 +923,10 @@ struct Compiler if (uv->functionDepth == expr->functionDepth - 1) { // get local variable - uint8_t reg = getLocal(uv); + int reg = getLocalReg(uv); + LUAU_ASSERT(reg >= 0); - bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), reg, 0); + bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), uint8_t(reg), 0); } else { @@ -1213,10 +1313,10 @@ struct Compiler if (!isConditionFast(expr->left)) { // Optimization: when right hand side is a local variable, we can use AND/OR - if (isExprLocalReg(expr->right)) + if (int reg = getExprLocalReg(expr->right); reg >= 0) { uint8_t lr = compileExprAuto(expr->left, rs); - uint8_t rr = getLocal(expr->right->as()->local); + uint8_t rr = uint8_t(reg); bytecode.emitABC(and_ ? LOP_AND : LOP_OR, target, lr, rr); return; @@ -1803,18 +1903,17 @@ struct Compiler } else if (AstExprLocal* expr = node->as()) { - 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); - uint8_t uid = getUpval(expr->local); - - bytecode.emitABC(LOP_GETUPVAL, target, uid, 0); + bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0); } else { - uint8_t reg = getLocal(expr->local); + LUAU_ASSERT(expr->upvalue); + uint8_t uid = getUpval(expr->local); - bytecode.emitABC(LOP_MOVE, target, reg, 0); + bytecode.emitABC(LOP_GETUPVAL, target, uid, 0); } } else if (AstExprGlobal* expr = node->as()) @@ -1879,8 +1978,8 @@ struct Compiler uint8_t compileExprAuto(AstExpr* node, RegScope&) { // Optimization: directly return locals instead of copying them to a temporary - if (isExprLocalReg(node)) - return getLocal(node->as()->local); + if (int reg = getExprLocalReg(node); reg >= 0) + return uint8_t(reg); // note: the register is owned by the parent scope uint8_t reg = allocReg(node, 1); @@ -1910,7 +2009,7 @@ struct Compiler for (size_t i = 0; i < targetCount; ++i) compileExprTemp(list.data[i], uint8_t(target + i)); - // compute expressions with values that go nowhere; this is required to run side-effecting code if any + // evaluate extra expressions for side effects for (size_t i = targetCount; i < list.size; ++i) { RegScope rsi(this); @@ -2008,20 +2107,21 @@ struct Compiler if (AstExprLocal* expr = node->as()) { - if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue) + // note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining + if (int reg = getExprLocalReg(expr); reg >= 0) { - LUAU_ASSERT(expr->upvalue); - - LValue result = {LValue::Kind_Upvalue}; - result.upval = getUpval(expr->local); + LValue result = {LValue::Kind_Local}; + result.reg = uint8_t(reg); result.location = node->location; return result; } else { - LValue result = {LValue::Kind_Local}; - result.reg = getLocal(expr->local); + LUAU_ASSERT(expr->upvalue); + + LValue result = {LValue::Kind_Upvalue}; + result.upval = getUpval(expr->local); result.location = node->location; return result; @@ -2115,15 +2215,21 @@ struct Compiler compileLValueUse(lv, source, /* set= */ true); } - bool isExprLocalReg(AstExpr* expr) + int getExprLocalReg(AstExpr* node) { - AstExprLocal* le = expr->as(); - if (!le || (!FFlag::LuauCompileSupportInlining && le->upvalue)) - return false; - - Local* l = locals.find(le->local); + if (AstExprLocal* expr = node->as()) + { + // note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining + Local* l = locals.find(expr->local); - return l && l->allocated; + return l && l->allocated ? l->reg : -1; + } + else if (AstExprGroup* expr = node->as()) + return getExprLocalReg(expr->expr); + else if (AstExprTypeAssertion* expr = node->as()) + return getExprLocalReg(expr->expr); + else + return -1; } bool isStatBreak(AstStat* node) @@ -2352,20 +2458,17 @@ struct Compiler // Optimization: return locals directly instead of copying them into a temporary // this is very important for a single return value and occasionally effective for multiple values - if (stat->list.size > 0 && isExprLocalReg(stat->list.data[0])) + if (int reg = stat->list.size > 0 ? getExprLocalReg(stat->list.data[0]) : -1; reg >= 0) { - temp = getLocal(stat->list.data[0]->as()->local); + temp = uint8_t(reg); consecutive = true; for (size_t i = 1; i < stat->list.size; ++i) - { - AstExpr* v = stat->list.data[i]; - if (!isExprLocalReg(v) || getLocal(v->as()->local) != temp + i) + if (getExprLocalReg(stat->list.data[i]) != int(temp + i)) { consecutive = false; break; } - } } if (!consecutive && stat->list.size > 0) @@ -2438,12 +2541,13 @@ struct Compiler { bool result = true; - bool visit(AstExpr* node) override + bool visit(AstExprFunction* node) override { - // functions may capture loop variable, and our upval handling doesn't handle elided variables (constant) - // TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues - result = result && !node->is(); - return result; + if (!FFlag::LuauCompileNestedClosureO2) + result = false; + + // short-circuit to avoid analyzing nested closure bodies + return false; } bool visit(AstStat* node) override @@ -2874,12 +2978,9 @@ struct Compiler void compileStatFunction(AstStatFunction* stat) { // Optimization: compile value expresion directly into target local register - if (isExprLocalReg(stat->name)) + if (int reg = getExprLocalReg(stat->name); reg >= 0) { - AstExprLocal* le = stat->name->as(); - LUAU_ASSERT(le); - - compileExpr(stat->func, getLocal(le->local)); + compileExpr(stat->func, uint8_t(reg)); return; } @@ -3399,6 +3500,12 @@ struct Compiler std::vector returnJumps; }; + struct Capture + { + LuauCaptureType type; + uint8_t data; + }; + BytecodeBuilder& bytecode; CompileOptions options; @@ -3422,6 +3529,7 @@ struct Compiler std::vector loopJumps; std::vector loops; std::vector inlineFrames; + std::vector captures; }; void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options) @@ -3465,6 +3573,9 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName /* self= */ nullptr, AstArray(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName()); uint32_t mainid = compiler.compileFunction(&main); + const Compiler::Function* mainf = compiler.functions.find(&main); + LUAU_ASSERT(mainf && mainf->upvals.empty()); + bytecode.setMainFunction(mainid); bytecode.finalize(); } diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index e4d59ea13..a62beeb1b 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -3,8 +3,6 @@ #include -LUAU_FASTFLAG(LuauCompileSupportInlining) - namespace Luau { namespace Compile @@ -330,7 +328,7 @@ struct ConstantVisitor : AstVisitor { if (value.type != Constant::Type_Unknown) map[key] = value; - else if (!FFlag::LuauCompileSupportInlining || wasEmpty) + else if (wasEmpty) ; else if (Constant* old = map.find(key)) old->type = Constant::Type_Unknown; diff --git a/Sources.cmake b/Sources.cmake index d2430cc99..297f561a8 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -73,6 +73,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/ToString.h Analysis/include/Luau/Transpiler.h Analysis/include/Luau/TxnLog.h + Analysis/include/Luau/TypeArena.h Analysis/include/Luau/TypeAttach.h Analysis/include/Luau/TypedAllocator.h Analysis/include/Luau/TypeInfer.h @@ -108,6 +109,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/ToString.cpp Analysis/src/Transpiler.cpp Analysis/src/TxnLog.cpp + Analysis/src/TypeArena.cpp Analysis/src/TypeAttach.cpp Analysis/src/TypedAllocator.cpp Analysis/src/TypeInfer.cpp diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 9c1f387e1..27187c614 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -10,10 +10,6 @@ #include "ldebug.h" #include "lvm.h" -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauTableMoveTelemetry2, false) - -void (*lua_table_move_telemetry)(lua_State* L, int f, int e, int t, int nf, int nt); - static int foreachi(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -199,29 +195,6 @@ static int tmove(lua_State* L) int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ luaL_checktype(L, tt, LUA_TTABLE); - void (*telemetrycb)(lua_State * L, int f, int e, int t, int nf, int nt) = lua_table_move_telemetry; - - if (DFFlag::LuauTableMoveTelemetry2 && telemetrycb && e >= f) - { - int nf = lua_objlen(L, 1); - int nt = lua_objlen(L, tt); - - bool report = false; - - // source index range must be in bounds in source table unless the table is empty (permits 1..#t moves) - if (!(f == 1 || (f >= 1 && f <= nf))) - report = true; - if (!(e == nf || (e >= 1 && e <= nf))) - report = true; - - // destination index must be in bounds in dest table or be exactly at the first empty element (permits concats) - if (!(t == nt + 1 || (t >= 1 && t <= nt))) - report = true; - - if (report) - telemetrycb(L, f, e, t, nf, nt); - } - if (e >= f) { /* otherwise, nothing to move */ luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move"); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 3c7c276a6..9e2eb2687 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -17,9 +17,6 @@ #include LUAU_FASTFLAGVARIABLE(LuauIter, false) -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauIterCallTelemetry, false) - -void (*lua_iter_call_telemetry)(lua_State* L); // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ @@ -157,17 +154,6 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c) StkId ra = &L->base[a]; LUAU_ASSERT(ra + 3 <= L->top); - if (DFFlag::LuauIterCallTelemetry) - { - /* TODO: we might be able to stop supporting this depending on whether it's used in practice */ - void (*telemetrycb)(lua_State* L) = lua_iter_call_telemetry; - - if (telemetrycb && ttistable(ra) && fasttm(L, hvalue(ra)->metatable, TM_CALL)) - telemetrycb(L); - if (telemetrycb && ttisuserdata(ra) && fasttm(L, uvalue(ra)->metatable, TM_CALL)) - telemetrycb(L); - } - setobjs2s(L, ra + 3 + 2, ra + 2); setobjs2s(L, ra + 3 + 1, ra + 1); setobjs2s(L, ra + 3, ra); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index b4e9340c0..caaccf4e4 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -2772,6 +2772,8 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") { + ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; + check(R"( type tag = "cat" | "dog" local function f(a: tag) end @@ -2844,6 +2846,8 @@ f(@1) TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape") { + ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; + check(R"( type tag = "strange\t\"cat\"" | 'nice\t"dog"' local function f(x: tag) end diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index b032060e5..cf27d1915 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -4269,22 +4269,26 @@ FORNLOOP R3 -6 FORNLOOP R0 -11 RETURN R0 0 )"); +} + +TEST_CASE("LoopUnrollNestedClosure") +{ + ScopedFastFlag sff("LuauCompileNestedClosureO2", true); - // can't unroll loops if the body has functions that refer to loop variables + // if the body has functions that refer to loop variables, we unroll the loop and use MOVE+CAPTURE for upvalues CHECK_EQ("\n" + compileFunction(R"( -for i=1,1 do +for i=1,2 do local x = function() return i end end )", 1, 2), R"( -LOADN R2 1 -LOADN R0 1 LOADN R1 1 -FORNPREP R0 +3 -NEWCLOSURE R3 P0 -CAPTURE VAL R2 -FORNLOOP R0 -3 +NEWCLOSURE R0 P0 +CAPTURE VAL R1 +LOADN R1 2 +NEWCLOSURE R0 P0 +CAPTURE VAL R1 RETURN R0 0 )"); } @@ -4469,8 +4473,6 @@ RETURN R0 0 TEST_CASE("InlineBasic") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // inline function that returns a constant CHECK_EQ("\n" + compileFunction(R"( local function foo() @@ -4550,10 +4552,72 @@ RETURN R1 1 )"); } -TEST_CASE("InlineMutate") +TEST_CASE("InlineBasicProhibited") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); + 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") +{ // if the argument is mutated, it gets a register even if the value is constant CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -4636,8 +4700,6 @@ RETURN R1 1 TEST_CASE("InlineUpval") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // if the argument is an upvalue, we naturally need to copy it to a local CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -4705,8 +4767,6 @@ RETURN R1 1 TEST_CASE("InlineFallthrough") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // if the function doesn't return, we still fill the results with nil CHECK_EQ("\n" + compileFunction(R"( local function foo() @@ -4759,8 +4819,6 @@ RETURN R1 -1 TEST_CASE("InlineCapture") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // can't inline function with nested functions that capture locals because they might be constants CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -4782,12 +4840,9 @@ RETURN R2 -1 TEST_CASE("InlineArgMismatch") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // when inlining a function, we must respect all the usual rules // caller might not have enough arguments - // TODO: we don't inline this atm CHECK_EQ("\n" + compileFunction(R"( local function foo(a) return a @@ -4799,13 +4854,11 @@ return x 1, 2), R"( DUPCLOSURE R0 K0 -MOVE R1 R0 -CALL R1 0 1 +LOADNIL R1 RETURN R1 1 )"); // caller might be using multret for arguments - // TODO: we don't inline this atm CHECK_EQ("\n" + compileFunction(R"( local function foo(a, b) return a + b @@ -4817,17 +4870,32 @@ return x 1, 2), R"( DUPCLOSURE R0 K0 -MOVE R1 R0 LOADK R3 K1 FASTCALL1 20 R3 +2 GETIMPORT R2 4 -CALL R2 1 -1 -CALL R1 -1 1 +CALL R2 1 2 +ADD R1 R2 R3 +RETURN R1 1 +)"); + + // caller might be using varargs for arguments + CHECK_EQ("\n" + compileFunction(R"( +local function foo(a, b) + return a + b +end + +local x = foo(...) +return x +)", + 1, 2), + R"( +DUPCLOSURE R0 K0 +GETVARARGS R2 2 +ADD R1 R2 R3 RETURN R1 1 )"); // caller might have too many arguments, but we still need to compute them for side effects - // TODO: we don't inline this atm CHECK_EQ("\n" + compileFunction(R"( local function foo(a) return a @@ -4839,19 +4907,34 @@ return x 1, 2), R"( DUPCLOSURE R0 K0 -MOVE R1 R0 +GETIMPORT R2 2 +CALL R2 0 1 +LOADN R1 42 +RETURN R1 1 +)"); + + // caller might not have enough arguments, and the arg might be mutated so it needs a register + CHECK_EQ("\n" + compileFunction(R"( +local function foo(a) + a = 42 + return a +end + +local x = foo() +return x +)", + 1, 2), + R"( +DUPCLOSURE R0 K0 +LOADNIL R2 LOADN R2 42 -GETIMPORT R3 2 -CALL R3 0 -1 -CALL R1 -1 1 +MOVE R1 R2 RETURN R1 1 )"); } TEST_CASE("InlineMultiple") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // we call this with a different set of variable/constant args CHECK_EQ("\n" + compileFunction(R"( local function foo(a, b) @@ -4880,8 +4963,6 @@ RETURN R3 4 TEST_CASE("InlineChain") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // inline a chain of functions CHECK_EQ("\n" + compileFunction(R"( local function foo(a, b) @@ -4912,8 +4993,6 @@ RETURN R3 1 TEST_CASE("InlineThresholds") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - ScopedFastInt sfis[] = { {"LuauCompileInlineThreshold", 25}, {"LuauCompileInlineThresholdMaxBoost", 300}, @@ -4988,8 +5067,6 @@ RETURN R3 1 TEST_CASE("InlineIIFE") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // IIFE with arguments CHECK_EQ("\n" + compileFunction(R"( function choose(a, b, c) @@ -5025,8 +5102,6 @@ RETURN R3 1 TEST_CASE("InlineRecurseArguments") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - // we can't inline a function if it's used to compute its own arguments CHECK_EQ("\n" + compileFunction(R"( local function foo(a, b) @@ -5036,22 +5111,20 @@ foo(foo(foo,foo(foo,foo))[foo]) 1, 2), R"( DUPCLOSURE R0 K0 -MOVE R1 R0 +MOVE R2 R0 +MOVE R3 R0 MOVE R4 R0 MOVE R5 R0 MOVE R6 R0 -CALL R4 2 1 -LOADNIL R3 -GETTABLE R2 R3 R0 -CALL R1 1 0 +CALL R4 2 -1 +CALL R2 -1 1 +GETTABLE R1 R2 R0 RETURN R0 0 )"); } TEST_CASE("InlineFastCallK") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - CHECK_EQ("\n" + compileFunction(R"( local function set(l0) rawset({}, l0) @@ -5080,8 +5153,6 @@ RETURN R0 0 TEST_CASE("InlineExprIndexK") { - ScopedFastFlag sff("LuauCompileSupportInlining", true); - CHECK_EQ("\n" + compileFunction(R"( local _ = function(l0) local _ = nil @@ -5141,6 +5212,58 @@ RETURN R0 0 )"); } +TEST_CASE("InlineHiddenMutation") +{ + // when the argument is assigned inside the function, we can't reuse the local + CHECK_EQ("\n" + compileFunction(R"( +local function foo(a) + a = 42 + return a +end + +local x = ... +local y = foo(x :: number) +return y +)", + 1, 2), + R"( +DUPCLOSURE R0 K0 +GETVARARGS R1 1 +MOVE R3 R1 +LOADN R3 42 +MOVE R2 R3 +RETURN R2 1 +)"); + + // and neither can we do that when it's assigned outside the function + CHECK_EQ("\n" + compileFunction(R"( +local function foo(a) + mutator() + return a +end + +local x = ... +mutator = function() x = 42 end + +local y = foo(x :: number) +return y +)", + 2, 2), + R"( +DUPCLOSURE R0 K0 +GETVARARGS R1 1 +NEWCLOSURE R2 P1 +CAPTURE REF R1 +SETGLOBAL R2 K1 +MOVE R3 R1 +GETGLOBAL R4 K1 +CALL R4 0 0 +MOVE R2 R3 +CLOSEUPVALS R1 +RETURN R2 1 +)"); +} + TEST_CASE("ReturnConsecutive") { // we can return a single local directly @@ -5193,6 +5316,16 @@ return )"), R"( RETURN R0 0 +)"); + + // this optimization also works in presence of group / type casts + CHECK_EQ("\n" + compileFunction0(R"( +local x, y = ... +return (x), y :: number +)"), + R"( +GETVARARGS R0 2 +RETURN R0 2 )"); } diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 4a9998613..c7e18efdf 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -198,10 +198,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") TEST_CASE_FIXTURE(Fixture, "clone_free_types") { - ScopedFastFlag sff[]{ - {"LuauLosslessClone", true}, - }; - TypeVar freeTy(FreeTypeVar{TypeLevel{}}); TypePackVar freeTp(FreeTypePack{TypeLevel{}}); @@ -218,8 +214,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types") TEST_CASE_FIXTURE(Fixture, "clone_free_tables") { - ScopedFastFlag sff{"LuauLosslessClone", true}; - TypeVar tableTy{TableTypeVar{}}; TableTypeVar* ttv = getMutable(&tableTy); ttv->state = TableState::Free; @@ -252,8 +246,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection") TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property") { - ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true}; - fileResolver.source["Module/A"] = R"( --!nonstrict local a = {} diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index 69430b1c4..83c526ef1 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -150,8 +150,6 @@ TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional") TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any") { - ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true}; - CheckResult result = check(R"( --!nonstrict local T = {} @@ -169,8 +167,6 @@ TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any") TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon") { - ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true}; - CheckResult result = check(R"( --!nonstrict local T = {} diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 418306821..dd49eb01b 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -683,6 +683,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal") { ScopedFastFlag flags[] = { {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", false} }; check(R"( @@ -697,6 +698,26 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal") CHECK(t->normal); } +// Unfortunately, getting this right in the general case is difficult. +TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal") +{ + ScopedFastFlag flags[] = { + {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", true} + }; + + check(R"( + type Fiber = { + return_: Fiber? + } + + local f: Fiber + )"); + + TypeId t = requireType("f"); + CHECK(!t->normal); +} + TEST_CASE_FIXTURE(Fixture, "variadic_tail_is_marked_normal") { ScopedFastFlag flags[] = { @@ -997,4 +1018,28 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounde LUAU_REQUIRE_ERRORS(result); } +// We had an issue where a normal BoundTypeVar might point at a non-normal BoundTypeVar if it in turn pointed to a +// normal TypeVar because we were calling follow() in an improper place. +TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their_pointee_is_normal") +{ + ScopedFastFlag sff[]{ + {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", true}, + }; + + CheckResult result = check(R"( + local T = {} + + function T:M() + local function f(a) + print(self.prop) + self:g(a) + self.prop = a + end + end + + return T + )"); +} + TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index c16f60d5a..14c17614d 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -22,8 +22,6 @@ struct LimitFixture : BuiltinsFixture #if defined(_NOOPT) || defined(_DEBUG) ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100}; #endif - - ScopedFastFlag LuauJustOneCallFrameForHaveSeen{"LuauJustOneCallFrameForHaveSeen", true}; }; template diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index f38dd10aa..b854bc51d 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -650,6 +650,19 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names") CHECK_EQ("test(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(x: a, y) end + )"); + + CHECK("(a, b) -> ()" == toString(requireType("foo"))); +} + TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param") { ScopedFastFlag flag{"LuauDocFuncParameters", true}; @@ -685,5 +698,4 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param") CHECK_EQ("foo:method(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts)); } - TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index b710ea0db..aa4ca415f 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -878,8 +878,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types") { ScopedFastFlag sff[]{ - {"LuauAssertStripsFalsyTypes", true}, - {"LuauDiscriminableUnions2", true}, {"LuauWidenIfSupertypeIsFree2", true}, }; @@ -899,8 +897,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2") { ScopedFastFlag sff[]{ - {"LuauAssertStripsFalsyTypes", true}, - {"LuauDiscriminableUnions2", true}, {"LuauWidenIfSupertypeIsFree2", true}, }; @@ -916,11 +912,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type") { - ScopedFastFlag sff[]{ - {"LuauAssertStripsFalsyTypes", true}, - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local function f(...: number?) return assert(...) @@ -933,11 +924,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pa TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy") { - ScopedFastFlag sff[]{ - {"LuauAssertStripsFalsyTypes", true}, - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local function f(x: nil) return assert(x, "hmm") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 14f1f7032..a28ba49e3 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1496,8 +1496,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") { - ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true}; - CheckResult result = check(R"( local function f(x: any) end f() diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index de0c93910..78a5fee73 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -1121,4 +1121,78 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1") +{ + ScopedFastFlag sff{"LuauApplyTypeFunctionFix", true}; + + // https://github.com/Roblox/luau/issues/484 + CheckResult result = check(R"( +--!strict +type MyObject = { + getReturnValue: (cb: () -> V) -> V +} +local object: MyObject = { + getReturnValue = function(cb: () -> U): U + return cb() + end, +} + +type ComplexObject = { + id: T, + nested: MyObject +} + +local complex: ComplexObject = { + 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: (cb: () -> V) -> V +} +type ComplexObject = { + id: T, + nested: MyObject +} + +local complex2: ComplexObject = 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(f, x: X) + return f(x) + end + )"); + + CHECK("((X) -> (a...), X) -> (a...)" == toString(requireType("foo"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 41bc0c219..f75b2d11a 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -177,8 +177,6 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") { - ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; - CheckResult result = check(R"( type A = {x: {y: {z: {thing: string}}}} type B = {x: {y: {z: {thing: string}}}} diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 765419c61..a3cae3de2 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -475,8 +475,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional") TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow") { - ScopedFastFlag luauInstantiateFollows{"LuauInstantiateFollows", true}; - // Just check that this doesn't assert check(R"( --!nonstrict diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 51f6fdfbf..036149380 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -728,8 +728,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_verifies_types_do_intersect") TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: string | number, b: boolean | number) return a == b diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index ee3ae9726..9d227895d 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -7,7 +7,6 @@ #include -LUAU_FASTFLAG(LuauEqConstraint) LUAU_FASTFLAG(LuauLowerBoundsCalculation) using namespace Luau; @@ -183,8 +182,6 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_completely_incompatible") // We'll need to not only report an error on `a == b`, but also to refine both operands as `never` in the `==` branch. TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: string, b: boolean?) if a == b then @@ -208,8 +205,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap") // Just needs to fully support equality refinement. Which is annoying without type states. TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil") { - ScopedFastFlag sff{"LuauDiscriminableUnions2", true}; - CheckResult result = check(R"( type T = {x: string, y: number} | {x: nil, y: nil} @@ -471,4 +466,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_many_things_but_first_of_it CHECK_EQ("boolean", toString(requireType("b"))); } +TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent") +{ + ScopedFastFlag sff[]{ + {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", true}, + }; + + CheckResult result = check(R"( + local function f(o) + local t = {} + t[o] = true + + local function foo(o) + o:m1() + t[o] = nil + end + + local function bar(o) + o:m2() + t[o] = true + end + + return t + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + // TODO: We're missing generics a... and b... + CHECK_EQ("(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 8c1304902..85a3334cf 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -7,7 +7,6 @@ #include "doctest.h" -LUAU_FASTFLAG(LuauDiscriminableUnions2) LUAU_FASTFLAG(LuauWeakEqConstraint) LUAU_FASTFLAG(LuauLowerBoundsCalculation) @@ -268,18 +267,10 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope") end )"); - if (FFlag::LuauDiscriminableUnions2) - { - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44}))); - CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38}))); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Type 'number' has no overlap with 'string'", toString(result.errors[0])); - } + CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44}))); + CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38}))); } TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard") @@ -378,8 +369,6 @@ caused by: TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: (string | number)?, b: boolean?) if a == b then @@ -392,28 +381,15 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauWeakEqConstraint) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "nil"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "nil"); // a == b - - CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b - } + CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b } TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: (string | number)?) if a == 1 then @@ -426,24 +402,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauWeakEqConstraint) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1 - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1 - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number"); // a == 1 - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1 - } + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1 + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1 } TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue") { - ScopedFastFlag sff[] = { - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local function f(a: (string | number)?) if "hello" == a then @@ -462,8 +426,6 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue") TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: (string | number)?) if a ~= nil then @@ -476,21 +438,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauWeakEqConstraint) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil - } + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil } TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") { - ScopedFastFlag sff{"LuauDiscriminableUnions2", true}; ScopedFastFlag sff2{"LuauWeakEqConstraint", true}; CheckResult result = check(R"( @@ -509,8 +462,6 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local function f(a: any, b: {x: number}?) if a ~= b then @@ -521,22 +472,12 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauWeakEqConstraint) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}"); // a ~= b - } + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b } TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") { - ScopedFastFlag sff1{"LuauEqConstraint", true}; - CheckResult result = check(R"( local t: {string} = {"hello"} @@ -554,18 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b - if (FFlag::LuauWeakEqConstraint) - { - CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b - } - else - { - // This is technically not wrong, but it's also wrong at the same time. - // The refinement code is none the wiser about the fact we pulled a string out of an array, so it has no choice but to narrow as just string. - CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string"); // a == b - } + CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b } TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable") @@ -594,16 +525,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector") end )"); - if (FFlag::LuauDiscriminableUnions2) - { - LUAU_REQUIRE_NO_ERRORS(result); - } - else - { - // This is kinda weird to see, but this actually only happens in Luau without Roblox type bindings because we don't have a Vector3 type. - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Unknown type 'Vector3'", toString(result.errors[0])); - } + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("*unknown*", toString(requireTypeAtPosition({3, 28}))); } @@ -1009,10 +931,6 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { - ScopedFastFlag sff[] = { - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -1033,10 +951,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") TEST_CASE_FIXTURE(Fixture, "discriminate_tag") { - ScopedFastFlag sff[] = { - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( type Cat = {tag: "Cat", name: string, catfood: string} type Dog = {tag: "Dog", name: string, dogfood: string} @@ -1070,11 +984,6 @@ TEST_CASE_FIXTURE(Fixture, "and_or_peephole_refinement") TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - {"LuauAssertStripsFalsyTypes", true}, - }; - CheckResult result = check(R"( local function is_true(b: true) end local function is_false(b: false) end @@ -1093,11 +1002,6 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false") TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - {"LuauAssertStripsFalsyTypes", true}, - }; - CheckResult result = check(R"( type Ok = { ok: true, value: T } type Err = { ok: false, error: E } @@ -1117,8 +1021,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_ TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersection_table") { - ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; - CheckResult result = check(R"( type T = {} & {f: ((string) -> string)?} local function f(t: T, x) @@ -1133,10 +1035,6 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersect TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x") { - ScopedFastFlag sff[] = { - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( type T = {tag: "Part", x: Part} | {tag: "Folder", x: Folder} @@ -1171,14 +1069,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector") end )"); - if (FFlag::LuauDiscriminableUnions2) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0])); - } + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector" diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 79eeb824a..d90dfbb54 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -139,6 +139,8 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch") { + ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; + CheckResult result = check(R"( type MyEnum = "foo" | "bar" | "baz" local a : MyEnum = "bang" @@ -325,8 +327,6 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton") { ScopedFastFlag sff[]{ - {"LuauEqConstraint", true}, - {"LuauDiscriminableUnions2", true}, {"LuauWidenIfSupertypeIsFree2", true}, {"LuauWeakEqConstraint", false}, }; @@ -350,11 +350,8 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") { ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - {"LuauEqConstraint", true}, {"LuauWidenIfSupertypeIsFree2", true}, {"LuauWeakEqConstraint", false}, - {"LuauDoNotAccidentallyDependOnPointerOrdering", true}, }; CheckResult result = check(R"( @@ -390,7 +387,6 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere") TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables") { ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, {"LuauWidenIfSupertypeIsFree2", true}, }; @@ -419,6 +415,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") { ScopedFastFlag sff[]{ {"LuauWidenIfSupertypeIsFree2", true}, + {"LuauWeakEqConstraint", true}, }; CheckResult result = check(R"( @@ -456,10 +453,6 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_not_to_be_widened") TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local a: string = "hi" if a == "hi" then @@ -474,10 +467,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons") TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local a: string = "hi" if a == "hi" or a == "bye" then @@ -492,10 +481,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons") TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local a: string = "hi" if a == "hi" then @@ -510,10 +495,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton") TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton") { - ScopedFastFlag sff[]{ - {"LuauDiscriminableUnions2", true}, - }; - CheckResult result = check(R"( local a: string = "hi" if a == "hi" or a == "bye" then diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 5078b0bf5..c924484ac 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2279,8 +2279,6 @@ local y = #x TEST_CASE_FIXTURE(BuiltinsFixture, "dont_hang_when_trying_to_look_up_in_cyclic_metatable_index") { - ScopedFastFlag sff{"LuauTerminateCyclicMetatableIndexLookup", true}; - // t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}} CheckResult result = check(R"( local mt = {} @@ -2313,8 +2311,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "give_up_after_one_metatable_index_look_up") TEST_CASE_FIXTURE(Fixture, "confusing_indexing") { - ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; - CheckResult result = check(R"( type T = {} & {p: number | string} local function f(t: T) @@ -2971,8 +2967,6 @@ TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table") TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") { - ScopedFastFlag sff{"LuauCheckImplicitNumbericKeys", true}; - CheckResult result = check(R"( local t: { [string]: number } = { 5, 6, 7 } )"); @@ -2984,4 +2978,32 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2])); } +TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra") +{ + ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true}; + ScopedFastFlag luauSubtypingAddOptPropsToUnsealedTables{"LuauSubtypingAddOptPropsToUnsealedTables", true}; + + CheckResult result = check(R"( + type X = { { x: boolean?, y: boolean? } } + + local l1: {[string]: X} = { key = { { x = true }, { y = true } } } + local l2: {[any]: X} = { key = { { x = true }, { y = true } } } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2") +{ + ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true}; + + CheckResult result = check(R"( + type X = {[any]: string | boolean} + + local x: X = { key = "str" } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 48cd1c3df..1d144db75 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr) -LUAU_FASTFLAG(LuauEqConstraint) using namespace Luau; @@ -308,7 +307,6 @@ TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count") int limit = 600; #endif - ScopedFastFlag sff{"LuauTableUseCounterInstead", true}; ScopedFastInt sfi{"LuauCheckRecursionLimit", limit}; CheckResult result = check("function f() return " + rep("{a=", limit) + "'a'" + rep("}", limit) + " end"); @@ -1011,8 +1009,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice") TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution") { - ScopedFastFlag substituteFollowNewTypes{"LuauSubstituteFollowNewTypes", true}; - CheckResult result = check(R"( local obj = {} diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 277f3887f..d19d80cbb 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -7,7 +7,6 @@ #include "doctest.h" LUAU_FASTFLAG(LuauLowerBoundsCalculation) -LUAU_FASTFLAG(LuauEqConstraint) using namespace Luau;