diff --git a/.gitignore b/.gitignore index af77f73c..528ab204 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ /luau /luau-tests /luau-analyze +/luau-compile __pycache__ diff --git a/Analysis/include/Luau/Clone.h b/Analysis/include/Luau/Clone.h index b3cbe467..28ab9931 100644 --- a/Analysis/include/Luau/Clone.h +++ b/Analysis/include/Luau/Clone.h @@ -16,6 +16,8 @@ using SeenTypePacks = std::unordered_map; struct CloneState { + NotNull builtinTypes; + SeenTypes seenTypes; SeenTypePacks seenTypePacks; diff --git a/Analysis/include/Luau/Config.h b/Analysis/include/Luau/Config.h index 88c10554..ae865b64 100644 --- a/Analysis/include/Luau/Config.h +++ b/Analysis/include/Luau/Config.h @@ -1,7 +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/Linter.h" +#include "Luau/LinterConfig.h" #include "Luau/ParseOptions.h" #include diff --git a/Analysis/include/Luau/Differ.h b/Analysis/include/Luau/Differ.h index e9656ad4..43c44b71 100644 --- a/Analysis/include/Luau/Differ.h +++ b/Analysis/include/Luau/Differ.h @@ -128,10 +128,10 @@ struct DiffError checkValidInitialization(left, right); } - std::string toString() const; + std::string toString(bool multiLine = false) const; private: - std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const; + std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const; void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right); void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const; }; @@ -152,12 +152,17 @@ struct DifferEnvironment { TypeId rootLeft; TypeId rootRight; + std::optional externalSymbolLeft; + std::optional externalSymbolRight; DenseHashMap genericMatchedPairs; DenseHashMap genericTpMatchedPairs; - DifferEnvironment(TypeId rootLeft, TypeId rootRight) + DifferEnvironment( + TypeId rootLeft, TypeId rootRight, std::optional externalSymbolLeft, std::optional externalSymbolRight) : rootLeft(rootLeft) , rootRight(rootRight) + , externalSymbolLeft(externalSymbolLeft) + , externalSymbolRight(externalSymbolRight) , genericMatchedPairs(nullptr) , genericTpMatchedPairs(nullptr) { @@ -170,6 +175,8 @@ struct DifferEnvironment void popVisiting(); std::vector>::const_reverse_iterator visitingBegin() const; std::vector>::const_reverse_iterator visitingEnd() const; + std::string getDevFixFriendlyNameLeft() const; + std::string getDevFixFriendlyNameRight() const; private: // TODO: consider using DenseHashSet @@ -179,6 +186,7 @@ private: std::vector> visitingStack; }; DifferResult diff(TypeId ty1, TypeId ty2); +DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional symbol1, std::optional symbol2); /** * True if ty is a "simple" type, i.e. cannot contain types. diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 13758b37..db3e8e55 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Location.h" +#include "Luau/NotNull.h" #include "Luau/Type.h" #include "Luau/Variant.h" @@ -432,7 +433,7 @@ std::string toString(const TypeError& error, TypeErrorToStringOptions options); bool containsParseErrorName(const TypeError& error); // Copy any types named in the error into destArena. -void copyErrors(ErrorVec& errors, struct TypeArena& destArena); +void copyErrors(ErrorVec& errors, struct TypeArena& destArena, NotNull builtinTypes); // Internal Compiler Error struct InternalErrorReporter diff --git a/Analysis/include/Luau/Linter.h b/Analysis/include/Luau/Linter.h index 6bbc3d66..303cd9e9 100644 --- a/Analysis/include/Luau/Linter.h +++ b/Analysis/include/Luau/Linter.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/LinterConfig.h" #include "Luau/Location.h" #include @@ -15,86 +16,15 @@ class AstStat; class AstNameTable; struct TypeChecker; struct Module; -struct HotComment; using ScopePtr = std::shared_ptr; -struct LintWarning -{ - // Make sure any new lint codes are documented here: https://luau-lang.org/lint - // Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints - enum Code - { - Code_Unknown = 0, - - Code_UnknownGlobal = 1, // superseded by type checker - Code_DeprecatedGlobal = 2, - Code_GlobalUsedAsLocal = 3, - Code_LocalShadow = 4, // disabled in Studio - Code_SameLineStatement = 5, // disabled in Studio - Code_MultiLineStatement = 6, - Code_LocalUnused = 7, // disabled in Studio - Code_FunctionUnused = 8, // disabled in Studio - Code_ImportUnused = 9, // disabled in Studio - Code_BuiltinGlobalWrite = 10, - Code_PlaceholderRead = 11, - Code_UnreachableCode = 12, - Code_UnknownType = 13, - Code_ForRange = 14, - Code_UnbalancedAssignment = 15, - Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode - Code_DuplicateLocal = 17, - Code_FormatString = 18, - Code_TableLiteral = 19, - Code_UninitializedLocal = 20, - Code_DuplicateFunction = 21, - Code_DeprecatedApi = 22, - Code_TableOperations = 23, - Code_DuplicateCondition = 24, - Code_MisleadingAndOr = 25, - Code_CommentDirective = 26, - Code_IntegerParsing = 27, - Code_ComparisonPrecedence = 28, - - Code__Count - }; - - Code code; - Location location; - std::string text; - - static const char* getName(Code code); - static Code parseName(const char* name); - static uint64_t parseMask(const std::vector& hotcomments); -}; - struct LintResult { std::vector errors; std::vector warnings; }; -struct LintOptions -{ - uint64_t warningMask = 0; - - void enableWarning(LintWarning::Code code) - { - warningMask |= 1ull << code; - } - void disableWarning(LintWarning::Code code) - { - warningMask &= ~(1ull << code); - } - - bool isEnabled(LintWarning::Code code) const - { - return 0 != (warningMask & (1ull << code)); - } - - void setDefaults(); -}; - std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const std::vector& hotcomments, const LintOptions& options); diff --git a/Analysis/include/Luau/LinterConfig.h b/Analysis/include/Luau/LinterConfig.h new file mode 100644 index 00000000..6bda930a --- /dev/null +++ b/Analysis/include/Luau/LinterConfig.h @@ -0,0 +1,121 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Location.h" + +#include +#include + +namespace Luau +{ + +struct HotComment; + +struct LintWarning +{ + // Make sure any new lint codes are documented here: https://luau-lang.org/lint + // Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints + enum Code + { + Code_Unknown = 0, + + Code_UnknownGlobal = 1, // superseded by type checker + Code_DeprecatedGlobal = 2, + Code_GlobalUsedAsLocal = 3, + Code_LocalShadow = 4, // disabled in Studio + Code_SameLineStatement = 5, // disabled in Studio + Code_MultiLineStatement = 6, + Code_LocalUnused = 7, // disabled in Studio + Code_FunctionUnused = 8, // disabled in Studio + Code_ImportUnused = 9, // disabled in Studio + Code_BuiltinGlobalWrite = 10, + Code_PlaceholderRead = 11, + Code_UnreachableCode = 12, + Code_UnknownType = 13, + Code_ForRange = 14, + Code_UnbalancedAssignment = 15, + Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode + Code_DuplicateLocal = 17, + Code_FormatString = 18, + Code_TableLiteral = 19, + Code_UninitializedLocal = 20, + Code_DuplicateFunction = 21, + Code_DeprecatedApi = 22, + Code_TableOperations = 23, + Code_DuplicateCondition = 24, + Code_MisleadingAndOr = 25, + Code_CommentDirective = 26, + Code_IntegerParsing = 27, + Code_ComparisonPrecedence = 28, + + Code__Count + }; + + Code code; + Location location; + std::string text; + + static const char* getName(Code code); + static Code parseName(const char* name); + static uint64_t parseMask(const std::vector& hotcomments); +}; + +struct LintOptions +{ + uint64_t warningMask = 0; + + void enableWarning(LintWarning::Code code) + { + warningMask |= 1ull << code; + } + void disableWarning(LintWarning::Code code) + { + warningMask &= ~(1ull << code); + } + + bool isEnabled(LintWarning::Code code) const + { + return 0 != (warningMask & (1ull << code)); + } + + void setDefaults(); +}; + +// clang-format off +static const char* kWarningNames[] = { + "Unknown", + + "UnknownGlobal", + "DeprecatedGlobal", + "GlobalUsedAsLocal", + "LocalShadow", + "SameLineStatement", + "MultiLineStatement", + "LocalUnused", + "FunctionUnused", + "ImportUnused", + "BuiltinGlobalWrite", + "PlaceholderRead", + "UnreachableCode", + "UnknownType", + "ForRange", + "UnbalancedAssignment", + "ImplicitReturn", + "DuplicateLocal", + "FormatString", + "TableLiteral", + "UninitializedLocal", + "DuplicateFunction", + "DeprecatedApi", + "TableOperations", + "DuplicateCondition", + "MisleadingAndOr", + "CommentDirective", + "IntegerParsing", + "ComparisonPrecedence", +}; +// clang-format on + +static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?"); + +} // namespace Luau diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index 398d0ab6..b18c6484 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -147,9 +147,6 @@ struct Tarjan void visitEdge(int index, int parentIndex); void visitSCC(int index); - TarjanResult loop_DEPRECATED(); - void visitSCC_DEPRECATED(int index); - // Each subclass can decide to ignore some nodes. virtual bool ignoreChildren(TypeId ty) { @@ -178,13 +175,6 @@ struct Tarjan virtual bool isDirty(TypePackId tp) = 0; virtual void foundDirty(TypeId ty) = 0; virtual void foundDirty(TypePackId tp) = 0; - - // TODO: remove with FFlagLuauTarjanSingleArr - std::vector indexToType; - std::vector indexToPack; - std::vector onStack; - std::vector lowlink; - std::vector dirty; }; // And finally substitution, which finds all the reachable dirty vertices diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 9080b1fc..66bd5e8e 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -1,6 +1,7 @@ // 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/NotNull.h" #include "Luau/RecursionCounter.h" #include "Luau/TxnLog.h" #include "Luau/TypePack.h" @@ -13,12 +14,424 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false) +LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone, false) +LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) + namespace Luau { namespace { +using Kind = Variant; + +template +const T* get(const Kind& kind) +{ + return get_if(&kind); +} + +class TypeCloner2 +{ + NotNull arena; + NotNull builtinTypes; + + // A queue of kinds where we cloned it, but whose interior types hasn't + // been updated to point to new clones. Once all of its interior types + // has been updated, it gets removed from the queue. + std::vector queue; + + NotNull types; + NotNull packs; + + int steps = 0; + +public: + TypeCloner2(NotNull arena, NotNull builtinTypes, NotNull types, NotNull packs) + : arena(arena) + , builtinTypes(builtinTypes) + , types(types) + , packs(packs) + { + } + + TypeId clone(TypeId ty) + { + shallowClone(ty); + run(); + + if (hasExceededIterationLimit()) + { + TypeId error = builtinTypes->errorRecoveryType(); + (*types)[ty] = error; + return error; + } + + return find(ty).value_or(builtinTypes->errorRecoveryType()); + } + + TypePackId clone(TypePackId tp) + { + shallowClone(tp); + run(); + + if (hasExceededIterationLimit()) + { + TypePackId error = builtinTypes->errorRecoveryTypePack(); + (*packs)[tp] = error; + return error; + } + + return find(tp).value_or(builtinTypes->errorRecoveryTypePack()); + } + +private: + bool hasExceededIterationLimit() const + { + if (FInt::LuauTypeCloneIterationLimit == 0) + return false; + + return steps + queue.size() >= size_t(FInt::LuauTypeCloneIterationLimit); + } + + void run() + { + while (!queue.empty()) + { + ++steps; + + if (hasExceededIterationLimit()) + break; + + Kind kind = queue.back(); + queue.pop_back(); + + if (find(kind)) + continue; + + cloneChildren(kind); + } + } + + std::optional find(TypeId ty) const + { + if (auto it = types->find(ty); it != types->end()) + return it->second; + return std::nullopt; + } + + std::optional find(TypePackId tp) const + { + if (auto it = packs->find(tp); it != packs->end()) + return it->second; + return std::nullopt; + } + + std::optional find(Kind kind) const + { + if (auto ty = get(kind)) + return find(*ty); + else if (auto tp = get(kind)) + return find(*tp); + else + { + LUAU_ASSERT(!"Unknown kind?"); + return std::nullopt; + } + } + +private: + TypeId shallowClone(TypeId ty) + { + if (auto clone = find(ty)) + return *clone; + else if (ty->persistent) + return ty; + + // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. + TypeId target = nullptr; + if (auto bt = get(ty)) + target = bt->boundTo; + else if (auto tt = get(ty); tt && tt->boundTo) + target = *tt->boundTo; + else + { + target = arena->addType(ty->ty); + asMutable(target)->documentationSymbol = ty->documentationSymbol; + } + + LUAU_ASSERT(target); + (*types)[ty] = target; + queue.push_back(target); + return target; + } + + TypePackId shallowClone(TypePackId tp) + { + if (auto clone = find(tp)) + return *clone; + else if (tp->persistent) + return tp; + + TypePackId target; + if (auto btp = get(tp)) + target = btp->boundTo; + else + target = arena->addTypePack(tp->ty); + + LUAU_ASSERT(target); + (*packs)[tp] = target; + queue.push_back(target); + return target; + } + + Property shallowClone(const Property& p) + { + if (FFlag::DebugLuauReadWriteProperties) + { + std::optional cloneReadTy; + if (auto ty = p.readType()) + cloneReadTy = shallowClone(*ty); + + std::optional cloneWriteTy; + if (auto ty = p.writeType()) + cloneWriteTy = shallowClone(*ty); + + std::optional cloned = Property::create(cloneReadTy, cloneWriteTy); + LUAU_ASSERT(cloned); + cloned->deprecated = p.deprecated; + cloned->deprecatedSuggestion = p.deprecatedSuggestion; + cloned->location = p.location; + cloned->tags = p.tags; + cloned->documentationSymbol = p.documentationSymbol; + return *cloned; + } + else + { + return Property{ + shallowClone(p.type()), + p.deprecated, + p.deprecatedSuggestion, + p.location, + p.tags, + p.documentationSymbol, + }; + } + } + + void cloneChildren(TypeId ty) + { + return visit( + [&](auto&& t) { + return cloneChildren(&t); + }, + asMutable(ty)->ty); + } + + void cloneChildren(TypePackId tp) + { + return visit( + [&](auto&& t) { + return cloneChildren(&t); + }, + asMutable(tp)->ty); + } + + void cloneChildren(Kind kind) + { + if (auto ty = get(kind)) + return cloneChildren(*ty); + else if (auto tp = get(kind)) + return cloneChildren(*tp); + else + LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?"); + } + + // ErrorType and ErrorTypePack is an alias to this type. + void cloneChildren(Unifiable::Error* t) + { + // noop. + } + + void cloneChildren(BoundType* t) + { + t->boundTo = shallowClone(t->boundTo); + } + + void cloneChildren(FreeType* t) + { + // TODO: clone lower and upper bounds. + // TODO: In the new solver, we should ice. + } + + void cloneChildren(GenericType* t) + { + // TOOD: clone upper bounds. + } + + void cloneChildren(PrimitiveType* t) + { + // noop. + } + + void cloneChildren(BlockedType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(PendingExpansionType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(SingletonType* t) + { + // noop. + } + + void cloneChildren(FunctionType* t) + { + for (TypeId& g : t->generics) + g = shallowClone(g); + + for (TypePackId& gp : t->genericPacks) + gp = shallowClone(gp); + + t->argTypes = shallowClone(t->argTypes); + t->retTypes = shallowClone(t->retTypes); + } + + void cloneChildren(TableType* t) + { + if (t->indexer) + { + t->indexer->indexType = shallowClone(t->indexer->indexType); + t->indexer->indexResultType = shallowClone(t->indexer->indexResultType); + } + + for (auto& [_, p] : t->props) + p = shallowClone(p); + + for (TypeId& ty : t->instantiatedTypeParams) + ty = shallowClone(ty); + + for (TypePackId& tp : t->instantiatedTypePackParams) + tp = shallowClone(tp); + } + + void cloneChildren(MetatableType* t) + { + t->table = shallowClone(t->table); + t->metatable = shallowClone(t->metatable); + } + + void cloneChildren(ClassType* t) + { + for (auto& [_, p] : t->props) + p = shallowClone(p); + + if (t->parent) + t->parent = shallowClone(*t->parent); + + if (t->metatable) + t->metatable = shallowClone(*t->metatable); + + if (t->indexer) + { + t->indexer->indexType = shallowClone(t->indexer->indexType); + t->indexer->indexResultType = shallowClone(t->indexer->indexResultType); + } + } + + void cloneChildren(AnyType* t) + { + // noop. + } + + void cloneChildren(UnionType* t) + { + for (TypeId& ty : t->options) + ty = shallowClone(ty); + } + + void cloneChildren(IntersectionType* t) + { + for (TypeId& ty : t->parts) + ty = shallowClone(ty); + } + + void cloneChildren(LazyType* t) + { + if (auto unwrapped = t->unwrapped.load()) + t->unwrapped.store(shallowClone(unwrapped)); + } + + void cloneChildren(UnknownType* t) + { + // noop. + } + + void cloneChildren(NeverType* t) + { + // noop. + } + + void cloneChildren(NegationType* t) + { + t->ty = shallowClone(t->ty); + } + + void cloneChildren(TypeFamilyInstanceType* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(FreeTypePack* t) + { + // TODO: clone lower and upper bounds. + // TODO: In the new solver, we should ice. + } + + void cloneChildren(GenericTypePack* t) + { + // TOOD: clone upper bounds. + } + + void cloneChildren(BlockedTypePack* t) + { + // TODO: In the new solver, we should ice. + } + + void cloneChildren(BoundTypePack* t) + { + t->boundTo = shallowClone(t->boundTo); + } + + void cloneChildren(VariadicTypePack* t) + { + t->ty = shallowClone(t->ty); + } + + void cloneChildren(TypePack* t) + { + for (TypeId& ty : t->head) + ty = shallowClone(ty); + + if (t->tail) + t->tail = shallowClone(*t->tail); + } + + void cloneChildren(TypeFamilyInstanceTypePack* t) + { + // TODO: In the new solver, we should ice. + } +}; + +} // namespace + +namespace +{ + Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState) { if (FFlag::DebugLuauReadWriteProperties) @@ -470,17 +883,25 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState) if (tp->persistent) return tp; - RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - - TypePackId& res = cloneState.seenTypePacks[tp]; - - if (res == nullptr) + if (FFlag::LuauStacklessTypeClone) { - TypePackCloner cloner{dest, tp, cloneState}; - Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; + return cloner.clone(tp); } + else + { + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - return res; + TypePackId& res = cloneState.seenTypePacks[tp]; + + if (res == nullptr) + { + TypePackCloner cloner{dest, tp, cloneState}; + Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. + } + + return res; + } } TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) @@ -488,54 +909,91 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState) if (typeId->persistent) return typeId; - RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - - TypeId& res = cloneState.seenTypes[typeId]; - - if (res == nullptr) + if (FFlag::LuauStacklessTypeClone) { - TypeCloner cloner{dest, typeId, cloneState}; - Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. - - // Persistent types are not being cloned and we get the original type back which might be read-only - if (!res->persistent) - { - asMutable(res)->documentationSymbol = typeId->documentationSymbol; - } + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; + return cloner.clone(typeId); } + else + { + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); - return res; + TypeId& res = cloneState.seenTypes[typeId]; + + if (res == nullptr) + { + TypeCloner cloner{dest, typeId, cloneState}; + Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. + + // Persistent types are not being cloned and we get the original type back which might be read-only + if (!res->persistent) + { + asMutable(res)->documentationSymbol = typeId->documentationSymbol; + } + } + + return res; + } } TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState) { - TypeFun result; - - for (auto param : typeFun.typeParams) + if (FFlag::LuauStacklessTypeClone) { - TypeId ty = clone(param.ty, dest, cloneState); - std::optional defaultValue; + TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}}; - if (param.defaultValue) - defaultValue = clone(*param.defaultValue, dest, cloneState); + TypeFun copy = typeFun; - result.typeParams.push_back({ty, defaultValue}); + for (auto& param : copy.typeParams) + { + param.ty = cloner.clone(param.ty); + + if (param.defaultValue) + param.defaultValue = cloner.clone(*param.defaultValue); + } + + for (auto& param : copy.typePackParams) + { + param.tp = cloner.clone(param.tp); + + if (param.defaultValue) + param.defaultValue = cloner.clone(*param.defaultValue); + } + + copy.type = cloner.clone(copy.type); + + return copy; } - - for (auto param : typeFun.typePackParams) + else { - TypePackId tp = clone(param.tp, dest, cloneState); - std::optional defaultValue; + TypeFun result; - if (param.defaultValue) - defaultValue = clone(*param.defaultValue, dest, cloneState); + for (auto param : typeFun.typeParams) + { + TypeId ty = clone(param.ty, dest, cloneState); + std::optional defaultValue; - result.typePackParams.push_back({tp, defaultValue}); + if (param.defaultValue) + defaultValue = clone(*param.defaultValue, dest, cloneState); + + result.typeParams.push_back({ty, defaultValue}); + } + + for (auto param : typeFun.typePackParams) + { + TypePackId tp = clone(param.tp, dest, cloneState); + std::optional defaultValue; + + if (param.defaultValue) + defaultValue = clone(*param.defaultValue, dest, cloneState); + + result.typePackParams.push_back({tp, defaultValue}); + } + + result.type = clone(typeFun.type, dest, cloneState); + + return result; } - - result.type = clone(typeFun.type, dest, cloneState); - - return result; } } // namespace Luau diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index 20f059d3..84505071 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -107,15 +107,19 @@ std::string DiffPath::toString(bool prependDot) const } return pathStr; } -std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const +std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const { + std::string conditionalNewline = multiLine ? "\n" : " "; + std::string conditionalIndent = multiLine ? " " : ""; std::string pathStr{rootName + diffPath.toString(true)}; switch (kind) { case DiffError::Kind::Normal: { checkNonMissingPropertyLeavesHaveNulloptTableProperty(); - return pathStr + " has type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "has type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } case DiffError::Kind::MissingTableProperty: { @@ -123,13 +127,17 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.tableProperty.has_value()) throw InternalCompilerError{"leaf.tableProperty is nullopt"}; - return pathStr + "." + *leaf.tableProperty + " has type " + Luau::toString(*leaf.ty); + return pathStr + "." + *leaf.tableProperty + conditionalNewline + + "has type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { if (!otherLeaf.tableProperty.has_value()) throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"}; - return pathStr + " is missing the property " + *otherLeaf.tableProperty; + return pathStr + conditionalNewline + + "is missing the property" + conditionalNewline + + conditionalIndent + *otherLeaf.tableProperty; } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -140,11 +148,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + " is a union containing type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "is a union containing type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + " is a union missing type " + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + + "is a union missing type" + conditionalNewline + + conditionalIndent + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -157,11 +169,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.unionIndex.has_value()) throw InternalCompilerError{"leaf.unionIndex is nullopt"}; - return pathStr + " is an intersection containing type " + Luau::toString(*leaf.ty); + return pathStr + conditionalNewline + + "is an intersection containing type" + conditionalNewline + + conditionalIndent + Luau::toString(*leaf.ty); } else if (otherLeaf.ty.has_value()) { - return pathStr + " is an intersection missing type " + Luau::toString(*otherLeaf.ty); + return pathStr + conditionalNewline + + "is an intersection missing type" + conditionalNewline + + conditionalIndent + Luau::toString(*otherLeaf.ty); } throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; } @@ -169,13 +185,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + " takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; + return pathStr + conditionalNewline + + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments"; } case DiffError::Kind::LengthMismatchInFnRets: { if (!leaf.minLength.has_value()) throw InternalCompilerError{"leaf.minLength is nullopt"}; - return pathStr + " returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; + return pathStr + conditionalNewline + + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values"; } default: { @@ -190,8 +208,11 @@ void DiffError::checkNonMissingPropertyLeavesHaveNulloptTableProperty() const throw InternalCompilerError{"Non-MissingProperty DiffError should have nullopt tableProperty in both leaves"}; } -std::string getDevFixFriendlyName(TypeId ty) +std::string getDevFixFriendlyName(const std::optional& maybeSymbol, TypeId ty) { + if (maybeSymbol.has_value()) + return *maybeSymbol; + if (auto table = get(ty)) { if (table->name.has_value()) @@ -206,27 +227,39 @@ std::string getDevFixFriendlyName(TypeId ty) return *metatable->syntheticName; } } - // else if (auto primitive = get(ty)) - //{ - // return ""; - //} return ""; } -std::string DiffError::toString() const +std::string DifferEnvironment::getDevFixFriendlyNameLeft() const { + return getDevFixFriendlyName(externalSymbolLeft, rootLeft); +} + +std::string DifferEnvironment::getDevFixFriendlyNameRight() const +{ + return getDevFixFriendlyName(externalSymbolRight, rootRight); +} + +std::string DiffError::toString(bool multiLine) const +{ + std::string conditionalNewline = multiLine ? "\n" : " "; + std::string conditionalIndent = multiLine ? " " : ""; switch (kind) { case DiffError::Kind::IncompatibleGeneric: { std::string diffPathStr{diffPath.toString(true)}; - return "DiffError: these two types are not equal because the left generic at " + leftRootName + diffPathStr + - " cannot be the same type parameter as the right generic at " + rightRootName + diffPathStr; + return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + + conditionalIndent + leftRootName + diffPathStr + conditionalNewline + + "cannot be the same type parameter as the right generic at" + conditionalNewline + + conditionalIndent + rightRootName + diffPathStr; } default: { - return "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) + - ", while the right type at " + toStringALeaf(rightRootName, right, left); + return "DiffError: these two types are not equal because the left type at" + conditionalNewline + + conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + + "while the right type at" + conditionalNewline + + conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine); } } } @@ -296,8 +329,8 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::detailsTableProperty(value.type(), field), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } } @@ -307,8 +340,7 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) { // right has a field the left doesn't return DifferResult{DiffError{DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::nullopts(), - DiffPathNodeLeaf::detailsTableProperty(value.type(), field), getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight)}}; + DiffPathNodeLeaf::detailsTableProperty(value.type(), field), env.getDevFixFriendlyNameLeft(), env.getDevFixFriendlyNameRight()}}; } } // left and right have the same set of keys @@ -360,8 +392,8 @@ static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId ri DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } return DifferResult{}; @@ -380,8 +412,8 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } return DifferResult{}; @@ -419,8 +451,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -432,8 +464,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -468,8 +500,8 @@ static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -521,16 +553,16 @@ static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right) DiffError::Kind::MissingUnionMember, DiffPathNodeLeaf::detailsUnionIndex(leftUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; else return DifferResult{DiffError{ DiffError::Kind::MissingUnionMember, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::detailsUnionIndex(rightUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -554,16 +586,16 @@ static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId DiffError::Kind::MissingIntersectionMember, DiffPathNodeLeaf::detailsUnionIndex(leftIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; else return DifferResult{DiffError{ DiffError::Kind::MissingIntersectionMember, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::detailsUnionIndex(rightIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -583,8 +615,8 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(left), DiffPathNodeLeaf::detailsNormal(right), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -753,8 +785,8 @@ static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, DiffPathNodeLeaf::detailsLength(int(left.first.size()), left.second.has_value()), DiffPathNodeLeaf::detailsLength(int(right.first.size()), right.second.has_value()), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -769,8 +801,8 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K DiffError::Kind::Normal, DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->first), DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->second), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -847,8 +879,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -860,8 +892,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP DiffError::Kind::IncompatibleGeneric, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::nullopts(), - getDevFixFriendlyName(env.rootLeft), - getDevFixFriendlyName(env.rootRight), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight(), }}; } @@ -910,7 +942,13 @@ std::vector>::const_reverse_iterator DifferEnvironment DifferResult diff(TypeId ty1, TypeId ty2) { - DifferEnvironment differEnv{ty1, ty2}; + DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt}; + return diffUsingEnv(differEnv, ty1, ty2); +} + +DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional symbol1, std::optional symbol2) +{ + DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2}; return diffUsingEnv(differEnv, ty1, ty2); } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 1a690a50..615ce41e 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -4,12 +4,16 @@ #include "Luau/Clone.h" #include "Luau/Common.h" #include "Luau/FileResolver.h" +#include "Luau/NotNull.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" +#include #include #include +LUAU_FASTFLAGVARIABLE(LuauIndentTypeMismatch, false) +LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) static std::string wrongNumberOfArgsString( size_t expectedCount, std::optional maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) @@ -66,6 +70,20 @@ struct ErrorConverter std::string result; + auto quote = [&](std::string s) { + return "'" + s + "'"; + }; + + auto constructErrorMessage = [&](std::string givenType, std::string wantedType, std::optional givenModule, + std::optional wantedModule) -> std::string { + std::string given = givenModule ? quote(givenType) + " from " + quote(*givenModule) : quote(givenType); + std::string wanted = wantedModule ? quote(wantedType) + " from " + quote(*wantedModule) : quote(wantedType); + size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); + if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) + return "Type " + given + " could not be converted into " + wanted; + return "Type\n " + given + "\ncould not be converted into\n " + wanted; + }; + if (givenTypeName == wantedTypeName) { if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType)) @@ -76,20 +94,31 @@ struct ErrorConverter { std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule); std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule); - result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + - "' from '" + wantedModuleName + "'"; + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName); + else + result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName + + "' from '" + wantedModuleName + "'"; } else { - result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName + - "' from '" + *wantedDefinitionModule + "'"; + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule); + else + result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + + wantedTypeName + "' from '" + *wantedDefinitionModule + "'"; } } } } if (result.empty()) - result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; + { + if (FFlag::LuauIndentTypeMismatch) + result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt); + else + result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'"; + } if (tm.error) @@ -97,7 +126,7 @@ struct ErrorConverter result += "\ncaused by:\n "; if (!tm.reason.empty()) - result += tm.reason + " "; + result += tm.reason + (FFlag::LuauIndentTypeMismatch ? " \n" : " "); result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver}); } @@ -845,7 +874,7 @@ bool containsParseErrorName(const TypeError& error) } template -void copyError(T& e, TypeArena& destArena, CloneState cloneState) +void copyError(T& e, TypeArena& destArena, CloneState& cloneState) { auto clone = [&](auto&& ty) { return ::Luau::clone(ty, destArena, cloneState); @@ -998,9 +1027,9 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState) static_assert(always_false_v, "Non-exhaustive type switch"); } -void copyErrors(ErrorVec& errors, TypeArena& destArena) +void copyErrors(ErrorVec& errors, TypeArena& destArena, NotNull builtinTypes) { - CloneState cloneState; + CloneState cloneState{builtinTypes}; auto visitErrorData = [&](auto&& e) { copyError(e, destArena, cloneState); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 52eedece..4dd815fb 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -35,7 +35,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckCancellation, false) namespace Luau { @@ -126,7 +125,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName) { - CloneState cloneState; + CloneState cloneState{globals.builtinTypes}; std::vector typesToPersist; typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size()); @@ -462,7 +461,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalcancelled) + if (item.module->cancelled) return {}; checkResult.errors.insert(checkResult.errors.end(), item.module->errors.begin(), item.module->errors.end()); @@ -635,7 +634,7 @@ std::vector Frontend::checkQueuedModules(std::optionalcancelled) + if (item.module && item.module->cancelled) cancelled = true; if (itemWithException || cancelled) @@ -677,7 +676,7 @@ std::vector Frontend::checkQueuedModules(std::optionalinterfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); module->internalTypes.clear(); @@ -1014,7 +1011,7 @@ void Frontend::checkBuildQueueItems(std::vector& items) { checkBuildQueueItem(item); - if (FFlag::LuauTypecheckCancellation && item.module && item.module->cancelled) + if (item.module && item.module->cancelled) break; recordItemResult(item); @@ -1295,9 +1292,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect typeChecker.finishTime = typeCheckLimits.finishTime; typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit; typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit; - - if (FFlag::LuauTypecheckCancellation) - typeChecker.cancellationToken = typeCheckLimits.cancellationToken; + typeChecker.cancellationToken = typeCheckLimits.cancellationToken; return typeChecker.check(sourceModule, mode, environmentScope); } diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 4abc1aa1..e8cd7dbd 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -14,48 +14,9 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) -LUAU_FASTFLAGVARIABLE(LuauLintNativeComment, false) - namespace Luau { -// clang-format off -static const char* kWarningNames[] = { - "Unknown", - - "UnknownGlobal", - "DeprecatedGlobal", - "GlobalUsedAsLocal", - "LocalShadow", - "SameLineStatement", - "MultiLineStatement", - "LocalUnused", - "FunctionUnused", - "ImportUnused", - "BuiltinGlobalWrite", - "PlaceholderRead", - "UnreachableCode", - "UnknownType", - "ForRange", - "UnbalancedAssignment", - "ImplicitReturn", - "DuplicateLocal", - "FormatString", - "TableLiteral", - "UninitializedLocal", - "DuplicateFunction", - "DeprecatedApi", - "TableOperations", - "DuplicateCondition", - "MisleadingAndOr", - "CommentDirective", - "IntegerParsing", - "ComparisonPrecedence", -}; -// clang-format on - -static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?"); - struct LintContext { struct Global @@ -2827,7 +2788,7 @@ static void lintComments(LintContext& context, const std::vector& ho "optimize directive uses unknown optimization level '%s', 0..2 expected", level); } } - else if (FFlag::LuauLintNativeComment && first == "native") + else if (first == "native") { if (space != std::string::npos) emitWarning(context, LintWarning::Code_CommentDirective, hc.location, @@ -2855,12 +2816,6 @@ static void lintComments(LintContext& context, const std::vector& ho } } -void LintOptions::setDefaults() -{ - // By default, we enable all warnings - warningMask = ~0ull; -} - std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const std::vector& hotcomments, const LintOptions& options) { @@ -2952,54 +2907,6 @@ std::vector lint(AstStat* root, const AstNameTable& names, const Sc return context.result; } -const char* LintWarning::getName(Code code) -{ - LUAU_ASSERT(unsigned(code) < Code__Count); - - return kWarningNames[code]; -} - -LintWarning::Code LintWarning::parseName(const char* name) -{ - for (int code = Code_Unknown; code < Code__Count; ++code) - if (strcmp(name, getName(Code(code))) == 0) - return Code(code); - - return Code_Unknown; -} - -uint64_t LintWarning::parseMask(const std::vector& hotcomments) -{ - uint64_t result = 0; - - for (const HotComment& hc : hotcomments) - { - if (!hc.header) - continue; - - if (hc.content.compare(0, 6, "nolint") != 0) - continue; - - size_t name = hc.content.find_first_not_of(" \t", 6); - - // --!nolint disables everything - if (name == std::string::npos) - return ~0ull; - - // --!nolint needs to be followed by a whitespace character - if (name == 6) - continue; - - // --!nolint name disables the specific lint - LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name); - - if (code != LintWarning::Code_Unknown) - result |= 1ull << int(code); - } - - return result; -} - std::vector getDeprecatedGlobals(const AstNameTable& names) { LintContext context; diff --git a/Analysis/src/LinterConfig.cpp b/Analysis/src/LinterConfig.cpp new file mode 100644 index 00000000..d63969d5 --- /dev/null +++ b/Analysis/src/LinterConfig.cpp @@ -0,0 +1,63 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/LinterConfig.h" + +#include "Luau/ParseResult.h" + +namespace Luau +{ + +void LintOptions::setDefaults() +{ + // By default, we enable all warnings + warningMask = ~0ull; +} + +const char* LintWarning::getName(Code code) +{ + LUAU_ASSERT(unsigned(code) < Code__Count); + + return kWarningNames[code]; +} + +LintWarning::Code LintWarning::parseName(const char* name) +{ + for (int code = Code_Unknown; code < Code__Count; ++code) + if (strcmp(name, getName(Code(code))) == 0) + return Code(code); + + return Code_Unknown; +} + +uint64_t LintWarning::parseMask(const std::vector& hotcomments) +{ + uint64_t result = 0; + + for (const HotComment& hc : hotcomments) + { + if (!hc.header) + continue; + + if (hc.content.compare(0, 6, "nolint") != 0) + continue; + + size_t name = hc.content.find_first_not_of(" \t", 6); + + // --!nolint disables everything + if (name == std::string::npos) + return ~0ull; + + // --!nolint needs to be followed by a whitespace character + if (name == 6) + continue; + + // --!nolint name disables the specific lint + LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name); + + if (code != LintWarning::Code_Unknown) + result |= 1ull << int(code); + } + + return result; +} + +} // namespace Luau diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index cb2114ab..580f59f3 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -199,7 +199,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr LUAU_ASSERT(interfaceTypes.types.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty()); - CloneState cloneState; + CloneState cloneState{builtinTypes}; ScopePtr moduleScope = getModuleScope(); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 4c6c35b0..a34a45c8 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -10,7 +10,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(DebugLuauReadWriteProperties) -LUAU_FASTFLAGVARIABLE(LuauTarjanSingleArr, false) namespace Luau { @@ -269,67 +268,30 @@ std::pair Tarjan::indexify(TypeId ty) { ty = log->follow(ty); - if (FFlag::LuauTarjanSingleArr) + auto [index, fresh] = typeToIndex.try_insert(ty, false); + + if (fresh) { - auto [index, fresh] = typeToIndex.try_insert(ty, false); - - if (fresh) - { - index = int(nodes.size()); - nodes.push_back({ty, nullptr, false, false, index}); - } - - return {index, fresh}; + index = int(nodes.size()); + nodes.push_back({ty, nullptr, false, false, index}); } - else - { - bool fresh = !typeToIndex.contains(ty); - int& index = typeToIndex[ty]; - if (fresh) - { - index = int(indexToType.size()); - indexToType.push_back(ty); - indexToPack.push_back(nullptr); - onStack.push_back(false); - lowlink.push_back(index); - } - return {index, fresh}; - } + return {index, fresh}; } std::pair Tarjan::indexify(TypePackId tp) { tp = log->follow(tp); - if (FFlag::LuauTarjanSingleArr) + auto [index, fresh] = packToIndex.try_insert(tp, false); + + if (fresh) { - auto [index, fresh] = packToIndex.try_insert(tp, false); - - if (fresh) - { - index = int(nodes.size()); - nodes.push_back({nullptr, tp, false, false, index}); - } - - return {index, fresh}; + index = int(nodes.size()); + nodes.push_back({nullptr, tp, false, false, index}); } - else - { - bool fresh = !packToIndex.contains(tp); - int& index = packToIndex[tp]; - - if (fresh) - { - index = int(indexToPack.size()); - indexToType.push_back(nullptr); - indexToPack.push_back(tp); - onStack.push_back(false); - lowlink.push_back(index); - } - return {index, fresh}; - } + return {index, fresh}; } void Tarjan::visitChild(TypeId ty) @@ -350,9 +312,6 @@ void Tarjan::visitChild(TypePackId tp) TarjanResult Tarjan::loop() { - if (!FFlag::LuauTarjanSingleArr) - return loop_DEPRECATED(); - // Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing while (!worklist.empty()) { @@ -476,27 +435,11 @@ TarjanResult Tarjan::visitRoot(TypePackId tp) void Tarjan::clearTarjan() { - if (FFlag::LuauTarjanSingleArr) - { - typeToIndex.clear(); - packToIndex.clear(); - nodes.clear(); + typeToIndex.clear(); + packToIndex.clear(); + nodes.clear(); - stack.clear(); - } - else - { - dirty.clear(); - - typeToIndex.clear(); - packToIndex.clear(); - indexToType.clear(); - indexToPack.clear(); - - stack.clear(); - onStack.clear(); - lowlink.clear(); - } + stack.clear(); edgesTy.clear(); edgesTp.clear(); @@ -505,32 +448,14 @@ void Tarjan::clearTarjan() bool Tarjan::getDirty(int index) { - if (FFlag::LuauTarjanSingleArr) - { - LUAU_ASSERT(size_t(index) < nodes.size()); - return nodes[index].dirty; - } - else - { - if (dirty.size() <= size_t(index)) - dirty.resize(index + 1, false); - return dirty[index]; - } + LUAU_ASSERT(size_t(index) < nodes.size()); + return nodes[index].dirty; } void Tarjan::setDirty(int index, bool d) { - if (FFlag::LuauTarjanSingleArr) - { - LUAU_ASSERT(size_t(index) < nodes.size()); - nodes[index].dirty = d; - } - else - { - if (dirty.size() <= size_t(index)) - dirty.resize(index + 1, false); - dirty[index] = d; - } + LUAU_ASSERT(size_t(index) < nodes.size()); + nodes[index].dirty = d; } void Tarjan::visitEdge(int index, int parentIndex) @@ -541,9 +466,6 @@ void Tarjan::visitEdge(int index, int parentIndex) void Tarjan::visitSCC(int index) { - if (!FFlag::LuauTarjanSingleArr) - return visitSCC_DEPRECATED(index); - bool d = getDirty(index); for (auto it = stack.rbegin(); !d && it != stack.rend(); it++) @@ -588,132 +510,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp) return visitRoot(tp); } -TarjanResult Tarjan::loop_DEPRECATED() -{ - // Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing - while (!worklist.empty()) - { - auto [index, currEdge, lastEdge] = worklist.back(); - - // First visit - if (currEdge == -1) - { - ++childCount; - if (childLimit > 0 && childLimit <= childCount) - return TarjanResult::TooManyChildren; - - stack.push_back(index); - onStack[index] = true; - - currEdge = int(edgesTy.size()); - - // Fill in edge list of this vertex - if (TypeId ty = indexToType[index]) - visitChildren(ty, index); - else if (TypePackId tp = indexToPack[index]) - visitChildren(tp, index); - - lastEdge = int(edgesTy.size()); - } - - // Visit children - bool foundFresh = false; - - for (; currEdge < lastEdge; currEdge++) - { - int childIndex = -1; - bool fresh = false; - - if (auto ty = edgesTy[currEdge]) - std::tie(childIndex, fresh) = indexify(ty); - else if (auto tp = edgesTp[currEdge]) - std::tie(childIndex, fresh) = indexify(tp); - else - LUAU_ASSERT(false); - - if (fresh) - { - // Original recursion point, update the parent continuation point and start the new element - worklist.back() = {index, currEdge + 1, lastEdge}; - worklist.push_back({childIndex, -1, -1}); - - // We need to continue the top-level loop from the start with the new worklist element - foundFresh = true; - break; - } - else if (onStack[childIndex]) - { - lowlink[index] = std::min(lowlink[index], childIndex); - } - - visitEdge(childIndex, index); - } - - if (foundFresh) - continue; - - if (lowlink[index] == index) - { - visitSCC(index); - while (!stack.empty()) - { - int popped = stack.back(); - stack.pop_back(); - onStack[popped] = false; - if (popped == index) - break; - } - } - - worklist.pop_back(); - - // Original return from recursion into a child - if (!worklist.empty()) - { - auto [parentIndex, _, parentEndEdge] = worklist.back(); - - // No need to keep child edges around - edgesTy.resize(parentEndEdge); - edgesTp.resize(parentEndEdge); - - lowlink[parentIndex] = std::min(lowlink[parentIndex], lowlink[index]); - visitEdge(index, parentIndex); - } - } - - return TarjanResult::Ok; -} - - -void Tarjan::visitSCC_DEPRECATED(int index) -{ - bool d = getDirty(index); - - for (auto it = stack.rbegin(); !d && it != stack.rend(); it++) - { - if (TypeId ty = indexToType[*it]) - d = isDirty(ty); - else if (TypePackId tp = indexToPack[*it]) - d = isDirty(tp); - if (*it == index) - break; - } - - if (!d) - return; - - for (auto it = stack.rbegin(); it != stack.rend(); it++) - { - setDirty(*it, true); - if (TypeId ty = indexToType[*it]) - foundDirty(ty); - else if (TypePackId tp = indexToPack[*it]) - foundDirty(tp); - if (*it == index) - return; - } -} - std::optional Substitution::substitute(TypeId ty) { ty = log->follow(ty); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 08b1ffbc..a7653b7c 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -2688,7 +2688,7 @@ void check( typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index d0ae4133..00cf4cd0 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -38,11 +38,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) LUAU_FASTFLAGVARIABLE(LuauFixCyclicModuleExports, false) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) -LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) -LUAU_FASTFLAGVARIABLE(LuauIndexTableIntersectionStringExpr, false) namespace Luau { @@ -3110,22 +3108,13 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp } else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) { - if (!FFlag::LuauTypecheckTypeguards) - { - if (auto predicate = tryGetTypeGuardPredicate(expr)) - return {booleanType, {std::move(*predicate)}}; - } - // For these, passing expectedType is worse than simply forcing them, because their implementation // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. WithPredicate lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true); WithPredicate rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true); - if (FFlag::LuauTypecheckTypeguards) - { - if (auto predicate = tryGetTypeGuardPredicate(expr)) - return {booleanType, {std::move(*predicate)}}; - } + if (auto predicate = tryGetTypeGuardPredicate(expr)) + return {booleanType, {std::move(*predicate)}}; PredicateVec predicates; @@ -3405,7 +3394,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}}); return errorRecoveryType(scope); } - else if (FFlag::LuauIndexTableIntersectionStringExpr && get(exprType)) + else if (get(exprType)) { Name name = std::string(value->value.data, value->value.size); diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index e0537b64..d7e43272 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/CodeGen.h" + #include #include @@ -16,6 +18,7 @@ constexpr uint32_t kCodeAlignment = 32; struct CodeAllocator { CodeAllocator(size_t blockSize, size_t maxTotalSize); + CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); ~CodeAllocator(); // Places data and code into the executable page area @@ -24,7 +27,7 @@ struct CodeAllocator bool allocate( const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart); - // Provided to callbacks + // Provided to unwind info callbacks void* context = nullptr; // Called when new block is created to create and setup the unwinding information for all the code in the block @@ -34,12 +37,16 @@ struct CodeAllocator // Called to destroy unwinding information returned by 'createBlockUnwindInfo' void (*destroyBlockUnwindInfo)(void* context, void* unwindData) = nullptr; +private: // Unwind information can be placed inside the block with some implementation-specific reservations at the beginning // But to simplify block space checks, we limit the max size of all that data static const size_t kMaxReservedDataSize = 256; bool allocateNewBlock(size_t& unwindInfoSize); + uint8_t* allocatePages(size_t size) const; + void freePages(uint8_t* mem, size_t size) const; + // Current block we use for allocations uint8_t* blockPos = nullptr; uint8_t* blockEnd = nullptr; @@ -50,6 +57,9 @@ struct CodeAllocator size_t blockSize = 0; size_t maxTotalSize = 0; + + AllocationCallback* allocationCallback = nullptr; + void* allocationCallbackContext = nullptr; }; } // namespace CodeGen diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 002b4a99..c11f9628 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -18,12 +18,25 @@ enum CodeGenFlags CodeGen_OnlyNativeModules = 1 << 0, }; +struct CompilationStats +{ + size_t bytecodeSizeBytes = 0; + size_t nativeCodeSizeBytes = 0; + size_t nativeDataSizeBytes = 0; + size_t nativeMetadataSizeBytes = 0; + + uint32_t functionsCompiled = 0; +}; + +using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); + bool isSupported(); +void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext); void create(lua_State* L); // Builds target function and all inner functions -void compile(lua_State* L, int idx, unsigned int flags = 0); +void compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr); using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); diff --git a/CodeGen/include/Luau/ConditionX64.h b/CodeGen/include/Luau/ConditionX64.h index 78c3fda2..4432641a 100644 --- a/CodeGen/include/Luau/ConditionX64.h +++ b/CodeGen/include/Luau/ConditionX64.h @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Common.h" + namespace Luau { namespace CodeGen @@ -43,5 +45,68 @@ enum class ConditionX64 : uint8_t Count }; +inline ConditionX64 getReverseCondition(ConditionX64 cond) +{ + switch (cond) + { + case ConditionX64::Overflow: + return ConditionX64::NoOverflow; + case ConditionX64::NoOverflow: + return ConditionX64::Overflow; + case ConditionX64::Carry: + return ConditionX64::NoCarry; + case ConditionX64::NoCarry: + return ConditionX64::Carry; + case ConditionX64::Below: + return ConditionX64::NotBelow; + case ConditionX64::BelowEqual: + return ConditionX64::NotBelowEqual; + case ConditionX64::Above: + return ConditionX64::NotAbove; + case ConditionX64::AboveEqual: + return ConditionX64::NotAboveEqual; + case ConditionX64::Equal: + return ConditionX64::NotEqual; + case ConditionX64::Less: + return ConditionX64::NotLess; + case ConditionX64::LessEqual: + return ConditionX64::NotLessEqual; + case ConditionX64::Greater: + return ConditionX64::NotGreater; + case ConditionX64::GreaterEqual: + return ConditionX64::NotGreaterEqual; + case ConditionX64::NotBelow: + return ConditionX64::Below; + case ConditionX64::NotBelowEqual: + return ConditionX64::BelowEqual; + case ConditionX64::NotAbove: + return ConditionX64::Above; + case ConditionX64::NotAboveEqual: + return ConditionX64::AboveEqual; + case ConditionX64::NotEqual: + return ConditionX64::Equal; + case ConditionX64::NotLess: + return ConditionX64::Less; + case ConditionX64::NotLessEqual: + return ConditionX64::LessEqual; + case ConditionX64::NotGreater: + return ConditionX64::Greater; + case ConditionX64::NotGreaterEqual: + return ConditionX64::GreaterEqual; + case ConditionX64::Zero: + return ConditionX64::NotZero; + case ConditionX64::NotZero: + return ConditionX64::Zero; + case ConditionX64::Parity: + return ConditionX64::NotParity; + case ConditionX64::NotParity: + return ConditionX64::Parity; + case ConditionX64::Count: + LUAU_ASSERT(!"invalid ConditionX64 value"); + } + + return ConditionX64::Count; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 67909b13..1684b478 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -176,7 +176,7 @@ enum class IrCmd : uint8_t CMP_ANY, // Unconditional jump - // A: block/vmexit + // A: block/vmexit/undef JUMP, // Jump if TValue is truthy @@ -369,10 +369,8 @@ enum class IrCmd : uint8_t // Guard against tag mismatch // A, B: tag // C: block/vmexit/undef - // D: bool (finish execution in VM on failure) // In final x64 lowering, A can also be Rn - // When undef is specified instead of a block, execution is aborted on check failure; if D is true, execution is continued in VM interpreter - // instead. + // When undef is specified instead of a block, execution is aborted on check failure CHECK_TAG, // Guard against a falsy tag+value @@ -689,6 +687,10 @@ enum class IrOpKind : uint32_t VmExit, }; +// VmExit uses a special value to indicate that pcpos update should be skipped +// This is only used during type checking at function entry +constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1; + struct IrOp { IrOpKind kind : 4; @@ -851,6 +853,8 @@ struct IrFunction std::vector constants; std::vector bcMapping; + uint32_t entryBlock = 0; + uint32_t entryLocation = 0; // For each instruction, an operand that can be used to recompute the value std::vector valueRestoreOps; @@ -1037,5 +1041,11 @@ inline int vmUpvalueOp(IrOp op) return op.index; } +inline uint32_t vmExitOp(IrOp op) +{ + LUAU_ASSERT(op.kind == IrOpKind::VmExit); + return op.index; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index c62d797a..e2e713ee 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -5,6 +5,7 @@ #include "ByteUtils.h" #include +#include namespace Luau { diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 2a8bc92e..bd2568d2 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -5,7 +5,6 @@ #include #include -#include namespace Luau { diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index 880a3244..fe45b9a4 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -33,13 +33,17 @@ static size_t alignToPageSize(size_t size) } #if defined(_WIN32) -static uint8_t* allocatePages(size_t size) +static uint8_t* allocatePagesImpl(size_t size) { - return (uint8_t*)VirtualAlloc(nullptr, alignToPageSize(size), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + LUAU_ASSERT(size == alignToPageSize(size)); + + return (uint8_t*)VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); } -static void freePages(uint8_t* mem, size_t size) +static void freePagesImpl(uint8_t* mem, size_t size) { + LUAU_ASSERT(size == alignToPageSize(size)); + if (VirtualFree(mem, 0, MEM_RELEASE) == 0) LUAU_ASSERT(!"failed to deallocate block memory"); } @@ -62,14 +66,22 @@ static void flushInstructionCache(uint8_t* mem, size_t size) #endif } #else -static uint8_t* allocatePages(size_t size) +static uint8_t* allocatePagesImpl(size_t size) { - return (uint8_t*)mmap(nullptr, alignToPageSize(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + LUAU_ASSERT(size == alignToPageSize(size)); + +#ifdef __APPLE__ + return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); +#else + return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); +#endif } -static void freePages(uint8_t* mem, size_t size) +static void freePagesImpl(uint8_t* mem, size_t size) { - if (munmap(mem, alignToPageSize(size)) != 0) + LUAU_ASSERT(size == alignToPageSize(size)); + + if (munmap(mem, size) != 0) LUAU_ASSERT(!"Failed to deallocate block memory"); } @@ -94,8 +106,15 @@ namespace CodeGen { CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize) - : blockSize(blockSize) - , maxTotalSize(maxTotalSize) + : CodeAllocator(blockSize, maxTotalSize, nullptr, nullptr) +{ +} + +CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + : blockSize{blockSize} + , maxTotalSize{maxTotalSize} + , allocationCallback{allocationCallback} + , allocationCallbackContext{allocationCallbackContext} { LUAU_ASSERT(blockSize > kMaxReservedDataSize); LUAU_ASSERT(maxTotalSize >= blockSize); @@ -207,5 +226,29 @@ bool CodeAllocator::allocateNewBlock(size_t& unwindInfoSize) return true; } +uint8_t* CodeAllocator::allocatePages(size_t size) const +{ + const size_t pageAlignedSize = alignToPageSize(size); + + uint8_t* const mem = allocatePagesImpl(pageAlignedSize); + if (mem == nullptr) + return nullptr; + + if (allocationCallback) + allocationCallback(allocationCallbackContext, nullptr, 0, mem, pageAlignedSize); + + return mem; +} + +void CodeAllocator::freePages(uint8_t* mem, size_t size) const +{ + const size_t pageAlignedSize = alignToPageSize(size); + + if (allocationCallback) + allocationCallback(allocationCallbackContext, mem, pageAlignedSize, nullptr, 0); + + freePagesImpl(mem, pageAlignedSize); +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 602130f1..10c3dc79 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -65,7 +65,7 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir) int sizecode = proto->sizecode; uint32_t* instOffsets = new uint32_t[sizecode]; - uint32_t instTarget = ir.function.bcMapping[0].asmLocation; + uint32_t instTarget = ir.function.entryLocation; for (int i = 0; i < sizecode; i++) { @@ -74,6 +74,9 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir) instOffsets[i] = ir.function.bcMapping[i].asmLocation - instTarget; } + // Set first instruction offset to 0 so that entering this function still executes any generated entry code. + instOffsets[0] = 0; + // entry target will be relocated when assembly is finalized return {proto, instOffsets, instTarget}; } @@ -202,11 +205,11 @@ bool isSupported() #endif } -void create(lua_State* L) +void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext) { LUAU_ASSERT(isSupported()); - std::unique_ptr data = std::make_unique(); + std::unique_ptr data = std::make_unique(allocationCallback, allocationCallbackContext); #if defined(_WIN32) data->unwindBuilder = std::make_unique(); @@ -239,7 +242,12 @@ void create(lua_State* L) ecb->enter = onEnter; } -void compile(lua_State* L, int idx, unsigned int flags) +void create(lua_State* L) +{ + create(L, nullptr, nullptr); +} + +void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats) { LUAU_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); @@ -318,13 +326,28 @@ void compile(lua_State* L, int idx, unsigned int flags) } } - for (NativeProto result : results) + for (const NativeProto& result : results) { // the memory is now managed by VM and will be freed via onDestroyFunction result.p->execdata = result.execdata; result.p->exectarget = uintptr_t(codeStart) + result.exectarget; result.p->codeentry = &kCodeEntryInsn; } + + if (stats != nullptr) + { + for (const NativeProto& result : results) + { + stats->bytecodeSizeBytes += result.p->sizecode * sizeof(Instruction); + + // Account for the native -> bytecode instruction offsets mapping: + stats->nativeMetadataSizeBytes += result.p->sizecode * sizeof(uint32_t); + } + + stats->functionsCompiled += uint32_t(results.size()); + stats->nativeCodeSizeBytes += build.code.size(); + stats->nativeDataSizeBytes += build.data.size(); + } } void setPerfLog(void* context, PerfLogFn logFn) diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 37af59aa..6271e376 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -24,15 +24,6 @@ struct EntryLocations Label epilogueStart; }; -static void emitClearNativeFlag(AssemblyBuilderA64& build) -{ - build.ldr(x0, mem(rState, offsetof(lua_State, ci))); - build.ldr(w1, mem(x0, offsetof(CallInfo, flags))); - build.mov(w2, ~LUA_CALLINFO_NATIVE); - build.and_(w1, w1, w2); - build.str(w1, mem(x0, offsetof(CallInfo, flags))); -} - static void emitExit(AssemblyBuilderA64& build, bool continueInVm) { build.mov(x0, continueInVm); @@ -40,14 +31,21 @@ static void emitExit(AssemblyBuilderA64& build, bool continueInVm) build.br(x1); } -static void emitUpdatePcAndContinueInVm(AssemblyBuilderA64& build) +static void emitUpdatePcForExit(AssemblyBuilderA64& build) { // x0 = pcpos * sizeof(Instruction) build.add(x0, rCode, x0); build.ldr(x1, mem(rState, offsetof(lua_State, ci))); build.str(x0, mem(x1, offsetof(CallInfo, savedpc))); +} - emitExit(build, /* continueInVm */ true); +static void emitClearNativeFlag(AssemblyBuilderA64& build) +{ + build.ldr(x0, mem(rState, offsetof(lua_State, ci))); + build.ldr(w1, mem(x0, offsetof(CallInfo, flags))); + build.mov(w2, ~LUA_CALLINFO_NATIVE); + build.and_(w1, w1, w2); + build.str(w1, mem(x0, offsetof(CallInfo, flags))); } static void emitInterrupt(AssemblyBuilderA64& build) @@ -305,6 +303,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcForExit(build); + if (build.logText) build.logAppend("; exitContinueVmClearNativeFlag\n"); build.setLabel(helpers.exitContinueVmClearNativeFlag); @@ -320,11 +323,6 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; updatePcAndContinueInVm\n"); - build.setLabel(helpers.updatePcAndContinueInVm); - emitUpdatePcAndContinueInVm(build); - if (build.logText) build.logAppend("; reentry\n"); build.setLabel(helpers.reentry); diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index a7352bce..e709a89c 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -130,6 +130,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& build.setLabel(block.label); + if (blockIndex == function.entryBlock) + { + function.entryLocation = build.getLabelOffset(block.label); + } + IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i); for (uint32_t index = block.start; index <= block.finish; index++) diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 1e62a4d4..ef655a24 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -180,6 +180,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcForExit(build); + if (build.logText) build.logAppend("; exitContinueVmClearNativeFlag\n"); build.setLabel(helpers.exitContinueVmClearNativeFlag); @@ -195,11 +200,6 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; updatePcAndContinueInVm\n"); - build.setLabel(helpers.updatePcAndContinueInVm); - emitUpdatePcAndContinueInVm(build); - if (build.logText) build.logAppend("; continueCallInVm\n"); build.setLabel(helpers.continueCallInVm); diff --git a/CodeGen/src/EmitBuiltinsX64.cpp b/CodeGen/src/EmitBuiltinsX64.cpp index efc480e0..87e4e795 100644 --- a/CodeGen/src/EmitBuiltinsX64.cpp +++ b/CodeGen/src/EmitBuiltinsX64.cpp @@ -1,8 +1,9 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "EmitBuiltinsX64.h" -#include "Luau/AssemblyBuilderX64.h" #include "Luau/Bytecode.h" + +#include "Luau/AssemblyBuilderX64.h" #include "Luau/IrCallWrapperX64.h" #include "Luau/IrRegAllocX64.h" diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index 214cfd6d..8ac746ee 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -25,7 +25,7 @@ struct ModuleHelpers Label exitContinueVm; Label exitNoContinueVm; Label exitContinueVmClearNativeFlag; - Label updatePcAndContinueInVm; + Label updatePcAndContinueInVm; // no reentry Label return_; Label interrupt; diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index e6fae4cc..43568035 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -328,14 +328,12 @@ void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, in emitUpdateBase(build); } -void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build) +void emitUpdatePcForExit(AssemblyBuilderX64& build) { // edx = pcpos * sizeof(Instruction) build.add(rdx, sCode); build.mov(rax, qword[rState + offsetof(lua_State, ci)]); build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx); - - emitExit(build, /* continueInVm */ true); } void emitContinueCallInVm(AssemblyBuilderX64& build) diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index d8c68da4..888f537f 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -180,7 +180,7 @@ void emitUpdateBase(AssemblyBuilderX64& build); void emitInterrupt(AssemblyBuilderX64& build); void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos); -void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build); +void emitUpdatePcForExit(AssemblyBuilderX64& build); void emitContinueCallInVm(AssemblyBuilderX64& build); void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index 23f2dd21..fa108af1 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -186,75 +186,12 @@ void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, ui } } -static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs) +template +static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& block) { - RegisterSet inRs; - - auto def = [&](IrOp op, int offset = 0) { - defRs.regs.set(vmRegOp(op) + offset, true); - }; - - auto use = [&](IrOp op, int offset = 0) { - if (!defRs.regs.test(vmRegOp(op) + offset)) - inRs.regs.set(vmRegOp(op) + offset, true); - }; - - auto maybeDef = [&](IrOp op) { - if (op.kind == IrOpKind::VmReg) - defRs.regs.set(vmRegOp(op), true); - }; - - auto maybeUse = [&](IrOp op) { - if (op.kind == IrOpKind::VmReg) - { - if (!defRs.regs.test(vmRegOp(op))) - inRs.regs.set(vmRegOp(op), true); - } - }; - - auto defVarargs = [&](uint8_t varargStart) { - defRs.varargSeq = true; - defRs.varargStart = varargStart; - }; - - auto useVarargs = [&](uint8_t varargStart) { - requireVariadicSequence(inRs, defRs, varargStart); - - // Variadic sequence has been consumed - defRs.varargSeq = false; - defRs.varargStart = 0; - }; - - auto defRange = [&](int start, int count) { - if (count == -1) - { - defVarargs(start); - } - else - { - for (int i = start; i < start + count; i++) - defRs.regs.set(i, true); - } - }; - - auto useRange = [&](int start, int count) { - if (count == -1) - { - useVarargs(start); - } - else - { - for (int i = start; i < start + count; i++) - { - if (!defRs.regs.test(i)) - inRs.regs.set(i, true); - } - } - }; - for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++) { - const IrInst& inst = function.instructions[instIdx]; + IrInst& inst = function.instructions[instIdx]; // For correct analysis, all instruction uses must be handled before handling the definitions switch (inst.cmd) @@ -264,7 +201,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& case IrCmd::LOAD_DOUBLE: case IrCmd::LOAD_INT: case IrCmd::LOAD_TVALUE: - maybeUse(inst.a); // Argument can also be a VmConst + visitor.maybeUse(inst.a); // Argument can also be a VmConst break; case IrCmd::STORE_TAG: case IrCmd::STORE_POINTER: @@ -272,63 +209,63 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& case IrCmd::STORE_INT: case IrCmd::STORE_VECTOR: case IrCmd::STORE_TVALUE: - maybeDef(inst.a); // Argument can also be a pointer value + visitor.maybeDef(inst.a); // Argument can also be a pointer value break; case IrCmd::CMP_ANY: - use(inst.a); - use(inst.b); + visitor.use(inst.a); + visitor.use(inst.b); break; case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: - use(inst.a); + visitor.use(inst.a); break; // A <- B, C case IrCmd::DO_ARITH: case IrCmd::GET_TABLE: - use(inst.b); - maybeUse(inst.c); // Argument can also be a VmConst + visitor.use(inst.b); + visitor.maybeUse(inst.c); // Argument can also be a VmConst - def(inst.a); + visitor.def(inst.a); break; case IrCmd::SET_TABLE: - use(inst.a); - use(inst.b); - maybeUse(inst.c); // Argument can also be a VmConst + visitor.use(inst.a); + visitor.use(inst.b); + visitor.maybeUse(inst.c); // Argument can also be a VmConst break; // A <- B case IrCmd::DO_LEN: - use(inst.b); + visitor.use(inst.b); - def(inst.a); + visitor.def(inst.a); break; case IrCmd::GET_IMPORT: - def(inst.a); + visitor.def(inst.a); break; case IrCmd::CONCAT: - useRange(vmRegOp(inst.a), function.uintOp(inst.b)); + visitor.useRange(vmRegOp(inst.a), function.uintOp(inst.b)); - defRange(vmRegOp(inst.a), function.uintOp(inst.b)); + visitor.defRange(vmRegOp(inst.a), function.uintOp(inst.b)); break; case IrCmd::GET_UPVALUE: - def(inst.a); + visitor.def(inst.a); break; case IrCmd::SET_UPVALUE: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::PREPARE_FORN: - use(inst.a); - use(inst.b); - use(inst.c); + visitor.use(inst.a); + visitor.use(inst.b); + visitor.use(inst.c); - def(inst.a); - def(inst.b); - def(inst.c); + visitor.def(inst.a); + visitor.def(inst.b); + visitor.def(inst.c); break; case IrCmd::INTERRUPT: break; case IrCmd::BARRIER_OBJ: case IrCmd::BARRIER_TABLE_FORWARD: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::CLOSE_UPVALS: // Closing an upvalue should be counted as a register use (it copies the fresh register value) @@ -336,23 +273,23 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& // Because we don't plan to optimize captured registers atm, we skip full dataflow analysis for them right now break; case IrCmd::CAPTURE: - maybeUse(inst.a); + visitor.maybeUse(inst.a); if (function.uintOp(inst.b) == 1) - capturedRegs.set(vmRegOp(inst.a), true); + visitor.capture(vmRegOp(inst.a)); break; case IrCmd::SETLIST: - use(inst.b); - useRange(vmRegOp(inst.c), function.intOp(inst.d)); + visitor.use(inst.b); + visitor.useRange(vmRegOp(inst.c), function.intOp(inst.d)); break; case IrCmd::CALL: - use(inst.a); - useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b)); + visitor.use(inst.a); + visitor.useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b)); - defRange(vmRegOp(inst.a), function.intOp(inst.c)); + visitor.defRange(vmRegOp(inst.a), function.intOp(inst.c)); break; case IrCmd::RETURN: - useRange(vmRegOp(inst.a), function.intOp(inst.b)); + visitor.useRange(vmRegOp(inst.a), function.intOp(inst.b)); break; // TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it @@ -364,89 +301,89 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& { LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1); - useRange(vmRegOp(inst.c), count); + visitor.useRange(vmRegOp(inst.c), count); } else { if (count >= 1) - use(inst.c); + visitor.use(inst.c); if (count >= 2) - maybeUse(inst.d); // Argument can also be a VmConst + visitor.maybeUse(inst.d); // Argument can also be a VmConst } } else { - useVarargs(vmRegOp(inst.c)); + visitor.useVarargs(vmRegOp(inst.c)); } // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG if (int count = function.intOp(inst.f); count != -1) - defRange(vmRegOp(inst.b), count); + visitor.defRange(vmRegOp(inst.b), count); break; case IrCmd::FORGLOOP: // First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG - use(inst.a, 1); - use(inst.a, 2); + visitor.use(inst.a, 1); + visitor.use(inst.a, 2); - def(inst.a, 2); - defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b)); + visitor.def(inst.a, 2); + visitor.defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b)); break; case IrCmd::FORGLOOP_FALLBACK: - useRange(vmRegOp(inst.a), 3); + visitor.useRange(vmRegOp(inst.a), 3); - def(inst.a, 2); - defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit + visitor.def(inst.a, 2); + visitor.defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit break; case IrCmd::FORGPREP_XNEXT_FALLBACK: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::FALLBACK_GETGLOBAL: - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_SETGLOBAL: - use(inst.b); + visitor.use(inst.b); break; case IrCmd::FALLBACK_GETTABLEKS: - use(inst.c); + visitor.use(inst.c); - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_SETTABLEKS: - use(inst.b); - use(inst.c); + visitor.use(inst.b); + visitor.use(inst.c); break; case IrCmd::FALLBACK_NAMECALL: - use(inst.c); + visitor.use(inst.c); - defRange(vmRegOp(inst.b), 2); + visitor.defRange(vmRegOp(inst.b), 2); break; case IrCmd::FALLBACK_PREPVARARGS: // No effect on explicitly referenced registers break; case IrCmd::FALLBACK_GETVARARGS: - defRange(vmRegOp(inst.b), function.intOp(inst.c)); + visitor.defRange(vmRegOp(inst.b), function.intOp(inst.c)); break; case IrCmd::FALLBACK_DUPCLOSURE: - def(inst.b); + visitor.def(inst.b); break; case IrCmd::FALLBACK_FORGPREP: - use(inst.b); + visitor.use(inst.b); - defRange(vmRegOp(inst.b), 3); + visitor.defRange(vmRegOp(inst.b), 3); break; case IrCmd::ADJUST_STACK_TO_REG: - defRange(vmRegOp(inst.a), -1); + visitor.defRange(vmRegOp(inst.a), -1); break; case IrCmd::ADJUST_STACK_TO_TOP: // While this can be considered to be a vararg consumer, it is already handled in fastcall instructions break; case IrCmd::GET_TYPEOF: - use(inst.a); + visitor.use(inst.a); break; case IrCmd::FINDUPVAL: - use(inst.a); + visitor.use(inst.a); break; default: @@ -460,8 +397,102 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& break; } } +} - return inRs; +struct BlockVmRegLiveInComputation +{ + BlockVmRegLiveInComputation(RegisterSet& defRs, std::bitset<256>& capturedRegs) + : defRs(defRs) + , capturedRegs(capturedRegs) + { + } + + RegisterSet& defRs; + std::bitset<256>& capturedRegs; + + RegisterSet inRs; + + void def(IrOp op, int offset = 0) + { + defRs.regs.set(vmRegOp(op) + offset, true); + } + + void use(IrOp op, int offset = 0) + { + if (!defRs.regs.test(vmRegOp(op) + offset)) + inRs.regs.set(vmRegOp(op) + offset, true); + } + + void maybeDef(IrOp op) + { + if (op.kind == IrOpKind::VmReg) + defRs.regs.set(vmRegOp(op), true); + } + + void maybeUse(IrOp op) + { + if (op.kind == IrOpKind::VmReg) + { + if (!defRs.regs.test(vmRegOp(op))) + inRs.regs.set(vmRegOp(op), true); + } + } + + void defVarargs(uint8_t varargStart) + { + defRs.varargSeq = true; + defRs.varargStart = varargStart; + } + + void useVarargs(uint8_t varargStart) + { + requireVariadicSequence(inRs, defRs, varargStart); + + // Variadic sequence has been consumed + defRs.varargSeq = false; + defRs.varargStart = 0; + } + + void defRange(int start, int count) + { + if (count == -1) + { + defVarargs(start); + } + else + { + for (int i = start; i < start + count; i++) + defRs.regs.set(i, true); + } + } + + void useRange(int start, int count) + { + if (count == -1) + { + useVarargs(start); + } + else + { + for (int i = start; i < start + count; i++) + { + if (!defRs.regs.test(i)) + inRs.regs.set(i, true); + } + } + } + + void capture(int reg) + { + capturedRegs.set(reg, true); + } +}; + +static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs) +{ + BlockVmRegLiveInComputation visitor(defRs, capturedRegs); + visitVmRegDefsUses(visitor, function, block); + return visitor.inRs; } // The algorithm used here is commonly known as backwards data-flow analysis. diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 52d0a0b5..0dd6f3c6 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrBuilder.h" -#include "Luau/IrAnalysis.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" #include "IrTranslation.h" @@ -22,10 +22,14 @@ IrBuilder::IrBuilder() { } +static bool hasTypedParameters(Proto* proto) +{ + return proto->typeinfo && proto->numparams != 0; +} + static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto) { - if (!proto->typeinfo || proto->numparams == 0) - return; + LUAU_ASSERT(hasTypedParameters(proto)); for (int i = 0; i < proto->numparams; ++i) { @@ -53,31 +57,31 @@ static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto) switch (tag) { case LBC_TYPE_NIL: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_BOOLEAN: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_NUMBER: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_STRING: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_TABLE: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_FUNCTION: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_THREAD: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_USERDATA: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.vmExit(kVmExitEntryGuardPc)); break; case LBC_TYPE_VECTOR: - build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.undef(), build.constInt(1)); + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.vmExit(kVmExitEntryGuardPc)); break; } @@ -103,11 +107,28 @@ void IrBuilder::buildFunctionIr(Proto* proto) function.proto = proto; function.variadic = proto->is_vararg != 0; + // Reserve entry block + bool generateTypeChecks = hasTypedParameters(proto); + IrOp entry = generateTypeChecks ? block(IrBlockKind::Internal) : IrOp{}; + // Rebuild original control flow blocks rebuildBytecodeBasicBlocks(proto); function.bcMapping.resize(proto->sizecode, {~0u, ~0u}); + if (generateTypeChecks) + { + beginBlock(entry); + buildArgumentTypeChecks(*this, proto); + inst(IrCmd::JUMP, blockAtInst(0)); + } + else + { + entry = blockAtInst(0); + } + + function.entryBlock = entry.index; + // Translate all instructions to IR inside blocks for (int i = 0; i < proto->sizecode;) { @@ -123,9 +144,6 @@ void IrBuilder::buildFunctionIr(Proto* proto) if (instIndexToBlock[i] != kNoAssociatedBlockIndex) beginBlock(blockAtInst(i)); - if (i == 0) - buildArgumentTypeChecks(*this, proto); - // We skip dead bytecode instructions when they appear after block was already terminated if (!inTerminatedBlock) { diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 12b75bcc..bda36582 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -402,7 +402,7 @@ void toString(IrToStringContext& ctx, IrOp op) append(ctx.result, "U%d", vmUpvalueOp(op)); break; case IrOpKind::VmExit: - append(ctx.result, "exit(%d)", op.index); + append(ctx.result, "exit(%d)", vmExitOp(op)); break; } } diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 16796a28..63d03135 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -1,10 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "IrLoweringA64.h" -#include "Luau/CodeGen.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" -#include "Luau/IrDump.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" #include "EmitCommonA64.h" @@ -189,7 +187,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, }); } -void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) +void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { valueTracker.beforeInstLowering(inst); @@ -566,7 +564,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::JUMP: - if (inst.a.kind == IrOpKind::VmExit) + if (inst.a.kind == IrOpKind::Undef || inst.a.kind == IrOpKind::VmExit) { Label fresh; build.b(getTargetLabel(inst.a, fresh)); @@ -1047,9 +1045,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::CHECK_TAG: { - bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); Label fresh; // used when guard aborts execution or jumps to a VM exit - Label& fail = continueInVm ? helpers.exitContinueVmClearNativeFlag : getTargetLabel(inst.c, fresh); + Label& fail = getTargetLabel(inst.c, fresh); // To support DebugLuauAbortingChecks, CHECK_TAG with VmReg has to be handled RegisterA64 tag = inst.a.kind == IrOpKind::VmReg ? regs.allocTemp(KindA64::w) : regOp(inst.a); @@ -1066,8 +1063,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.cmp(tag, tagOp(inst.b)); build.b(ConditionA64::NotEqual, fail); } - if (!continueInVm) - finalizeTargetLabel(inst.c, fresh); + + finalizeTargetLabel(inst.c, fresh); break; } case IrCmd::CHECK_TRUTHY: @@ -1862,7 +1859,10 @@ void IrLoweringA64::finishFunction() for (ExitHandler& handler : exitHandlers) { + LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc); + build.setLabel(handler.self); + build.mov(x0, handler.pcpos * sizeof(Instruction)); build.b(helpers.updatePcAndContinueInVm); } @@ -1873,12 +1873,12 @@ bool IrLoweringA64::hasError() const return error || regs.error; } -bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next) +bool IrLoweringA64::isFallthroughBlock(const IrBlock& target, const IrBlock& next) { return target.start == next.start; } -void IrLoweringA64::jumpOrFallthrough(IrBlock& target, IrBlock& next) +void IrLoweringA64::jumpOrFallthrough(IrBlock& target, const IrBlock& next) { if (!isFallthroughBlock(target, next)) build.b(target.label); @@ -1891,7 +1891,11 @@ Label& IrLoweringA64::getTargetLabel(IrOp op, Label& fresh) if (op.kind == IrOpKind::VmExit) { - if (uint32_t* index = exitHandlerMap.find(op.index)) + // Special exit case that doesn't have to update pcpos + if (vmExitOp(op) == kVmExitEntryGuardPc) + return helpers.exitContinueVmClearNativeFlag; + + if (uint32_t* index = exitHandlerMap.find(vmExitOp(op))) return exitHandlers[*index].self; return fresh; @@ -1906,10 +1910,10 @@ void IrLoweringA64::finalizeTargetLabel(IrOp op, Label& fresh) { emitAbort(build, fresh); } - else if (op.kind == IrOpKind::VmExit && fresh.id != 0) + else if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id) { - exitHandlerMap[op.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({fresh, op.index}); + exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size()); + exitHandlers.push_back({fresh, vmExitOp(op)}); } } diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 72b0da2f..344f18b7 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -25,14 +25,14 @@ struct IrLoweringA64 { IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function); - void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); + void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(); void finishFunction(); bool hasError() const; - bool isFallthroughBlock(IrBlock target, IrBlock next); - void jumpOrFallthrough(IrBlock& target, IrBlock& next); + bool isFallthroughBlock(const IrBlock& target, const IrBlock& next); + void jumpOrFallthrough(IrBlock& target, const IrBlock& next); Label& getTargetLabel(IrOp op, Label& fresh); void finalizeTargetLabel(IrOp op, Label& fresh); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 6fe1e771..2ea48404 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -1,19 +1,19 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "IrLoweringX64.h" -#include "Luau/CodeGen.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" -#include "Luau/IrCallWrapperX64.h" -#include "Luau/IrDump.h" +#include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/IrCallWrapperX64.h" + #include "EmitBuiltinsX64.h" #include "EmitCommonX64.h" #include "EmitInstructionX64.h" #include "NativeState.h" #include "lstate.h" +#include "lgc.h" namespace Luau { @@ -59,7 +59,7 @@ void IrLoweringX64::storeDoubleAsFloat(OperandX64 dst, IrOp src) build.vmovss(dst, tmp.reg); } -void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) +void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { regs.currInstIdx = index; @@ -565,24 +565,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::JUMP: - if (inst.a.kind == IrOpKind::VmExit) - { - if (uint32_t* index = exitHandlerMap.find(inst.a.index)) - { - build.jmp(exitHandlers[*index].self); - } - else - { - Label self; - build.jmp(self); - exitHandlerMap[inst.a.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({self, inst.a.index}); - } - } - else - { - jumpOrFallthrough(blockOp(inst.a), next); - } + jumpOrAbortOnUndef(inst.a, next); break; case IrCmd::JUMP_IF_TRUTHY: jumpIfTruthy(build, vmRegOp(inst.a), labelOp(inst.b), labelOp(inst.c)); @@ -975,12 +958,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::CHECK_TAG: - { - bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); build.cmp(memRegTagOp(inst.a), tagOp(inst.b)); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c, continueInVm); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.c, next); break; - } case IrCmd::CHECK_TRUTHY: { // Constant tags which don't require boolean value check should've been removed in constant folding @@ -992,7 +972,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) { // Fail to fallback on 'nil' (falsy) build.cmp(memRegTagOp(inst.a), LUA_TNIL); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); // Skip value test if it's not a boolean (truthy) build.cmp(memRegTagOp(inst.a), LUA_TBOOLEAN); @@ -1001,7 +981,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) // fail to fallback on 'false' boolean value (falsy) build.cmp(memRegUintOp(inst.b), 0); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next); if (inst.a.kind != IrOpKind::Constant) build.setLabel(skip); @@ -1009,11 +989,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::CHECK_READONLY: build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_NO_METATABLE: build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_SAFE_ENV: { @@ -1023,7 +1003,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.a); + jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next); break; } case IrCmd::CHECK_ARRAY_SIZE: @@ -1034,7 +1014,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) else LUAU_ASSERT(!"Unsupported instruction form"); - jumpOrAbortOnUndef(ConditionX64::BelowEqual, ConditionX64::NotBelowEqual, inst.c); + jumpOrAbortOnUndef(ConditionX64::BelowEqual, inst.c, next); break; case IrCmd::JUMP_SLOT_MATCH: case IrCmd::CHECK_SLOT_MATCH: @@ -1080,7 +1060,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(tmp.reg, dword[regOp(inst.a) + offsetof(LuaNode, key) + kOffsetOfTKeyTagNext]); build.shr(tmp.reg, kTKeyTagBits); - jumpOrAbortOnUndef(ConditionX64::NotZero, ConditionX64::Zero, inst.b); + jumpOrAbortOnUndef(ConditionX64::NotZero, inst.b, next); break; } case IrCmd::INTERRUPT: @@ -1583,7 +1563,10 @@ void IrLoweringX64::finishFunction() for (ExitHandler& handler : exitHandlers) { + LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc); + build.setLabel(handler.self); + build.mov(edx, handler.pcpos * sizeof(Instruction)); build.jmp(helpers.updatePcAndContinueInVm); } @@ -1598,50 +1581,81 @@ bool IrLoweringX64::hasError() const return false; } -bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next) +bool IrLoweringX64::isFallthroughBlock(const IrBlock& target, const IrBlock& next) { return target.start == next.start; } -void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next) +Label& IrLoweringX64::getTargetLabel(IrOp op, Label& fresh) +{ + if (op.kind == IrOpKind::Undef) + return fresh; + + if (op.kind == IrOpKind::VmExit) + { + // Special exit case that doesn't have to update pcpos + if (vmExitOp(op) == kVmExitEntryGuardPc) + return helpers.exitContinueVmClearNativeFlag; + + if (uint32_t* index = exitHandlerMap.find(vmExitOp(op))) + return exitHandlers[*index].self; + + return fresh; + } + + return labelOp(op); +} + +void IrLoweringX64::finalizeTargetLabel(IrOp op, Label& fresh) +{ + if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id) + { + exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size()); + exitHandlers.push_back({fresh, vmExitOp(op)}); + } +} + +void IrLoweringX64::jumpOrFallthrough(IrBlock& target, const IrBlock& next) { if (!isFallthroughBlock(target, next)) build.jmp(target.label); } -void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm) +void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next) { - if (targetOrUndef.kind == IrOpKind::Undef) - { - if (continueInVm) - { - build.jcc(cond, helpers.exitContinueVmClearNativeFlag); - return; - } + Label fresh; + Label& label = getTargetLabel(target, fresh); - Label skip; - build.jcc(condInverse, skip); - build.ud2(); - build.setLabel(skip); - } - else if (targetOrUndef.kind == IrOpKind::VmExit) + if (target.kind == IrOpKind::Undef) { - if (uint32_t* index = exitHandlerMap.find(targetOrUndef.index)) + if (cond == ConditionX64::Count) { - build.jcc(cond, exitHandlers[*index].self); + build.ud2(); // Unconditional jump to abort is just an abort } else { - Label self; - build.jcc(cond, self); - exitHandlerMap[targetOrUndef.index] = uint32_t(exitHandlers.size()); - exitHandlers.push_back({self, targetOrUndef.index}); + build.jcc(getReverseCondition(cond), label); + build.ud2(); + build.setLabel(label); } } + else if (cond == ConditionX64::Count) + { + // Unconditional jump can be skipped if it's a fallthrough + if (target.kind == IrOpKind::VmExit || !isFallthroughBlock(blockOp(target), next)) + build.jmp(label); + } else { - build.jcc(cond, labelOp(targetOrUndef)); + build.jcc(cond, label); } + + finalizeTargetLabel(target, fresh); +} + +void IrLoweringX64::jumpOrAbortOnUndef(IrOp target, const IrBlock& next) +{ + jumpOrAbortOnUndef(ConditionX64::Count, target, next); } OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op) diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index a8dab3c9..c32e3e61 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -27,15 +27,20 @@ struct IrLoweringX64 { IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function); - void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); + void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void finishBlock(); void finishFunction(); bool hasError() const; - bool isFallthroughBlock(IrBlock target, IrBlock next); - void jumpOrFallthrough(IrBlock& target, IrBlock& next); - void jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm = false); + bool isFallthroughBlock(const IrBlock& target, const IrBlock& next); + void jumpOrFallthrough(IrBlock& target, const IrBlock& next); + + Label& getTargetLabel(IrOp op, Label& fresh); + void finalizeTargetLabel(IrOp op, Label& fresh); + + void jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next); + void jumpOrAbortOnUndef(IrOp target, const IrBlock& next); void storeDoubleAsFloat(OperandX64 dst, IrOp src); diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index e7a0c424..5a71345e 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -23,7 +23,12 @@ constexpr unsigned kBlockSize = 4 * 1024 * 1024; constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024; NativeState::NativeState() - : codeAllocator(kBlockSize, kMaxTotalSize) + : NativeState(nullptr, nullptr) +{ +} + +NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext) + : codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext} { } diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 4aa5c8a2..a9ba7cfd 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -112,6 +112,7 @@ using GateFn = int (*)(lua_State*, Proto*, uintptr_t, NativeContext*); struct NativeState { NativeState(); + NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext); ~NativeState(); CodeAllocator codeAllocator; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index a5e20b16..d0ecd7dd 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -2,7 +2,7 @@ #include "Luau/OptimizeConstProp.h" #include "Luau/DenseHash.h" -#include "Luau/IrAnalysis.h" +#include "Luau/IrData.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" diff --git a/Compiler/include/Luau/Compiler.h b/Compiler/include/Luau/Compiler.h index 36a21a72..9a661db2 100644 --- a/Compiler/include/Luau/Compiler.h +++ b/Compiler/include/Luau/Compiler.h @@ -39,7 +39,7 @@ struct CompileOptions const char* vectorType = nullptr; // null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these - const char** mutableGlobals = nullptr; + const char* const* mutableGlobals = nullptr; }; class CompileError : public std::exception diff --git a/Compiler/include/luacode.h b/Compiler/include/luacode.h index 7c59ce0b..60cdeee7 100644 --- a/Compiler/include/luacode.h +++ b/Compiler/include/luacode.h @@ -35,7 +35,7 @@ struct lua_CompileOptions const char* vectorType; // null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these - const char** mutableGlobals; + const char* const* mutableGlobals; }; // compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 6ae31825..226cd2ee 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,9 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileFunctionType, false) -LUAU_FASTFLAGVARIABLE(LuauCompileNativeComment, false) - LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false) LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false) @@ -209,12 +206,9 @@ struct Compiler setDebugLine(func); - if (FFlag::LuauCompileFunctionType) - { - // note: we move types out of typeMap which is safe because compileFunction is only called once per function - if (std::string* funcType = typeMap.find(func)) - bytecode.setFunctionTypeInfo(std::move(*funcType)); - } + // note: we move types out of typeMap which is safe because compileFunction is only called once per function + if (std::string* funcType = typeMap.find(func)) + bytecode.setFunctionTypeInfo(std::move(*funcType)); if (func->vararg) bytecode.emitABC(LOP_PREPVARARGS, uint8_t(self + func->args.size), 0, 0); @@ -3620,9 +3614,8 @@ struct Compiler { node->body->visit(this); - if (FFlag::LuauCompileFunctionType) - for (AstLocal* arg : node->args) - hasTypes |= arg->annotation != nullptr; + for (AstLocal* arg : node->args) + hasTypes |= arg->annotation != nullptr; // this makes sure all functions that are used when compiling this one have been already added to the vector functions.push_back(node); @@ -3863,7 +3856,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c if (hc.header && hc.content.compare(0, 9, "optimize ") == 0) options.optimizationLevel = std::max(0, std::min(2, atoi(hc.content.c_str() + 9))); - if (FFlag::LuauCompileNativeComment && hc.header && hc.content == "native") + if (hc.header && hc.content == "native") { mainFlags |= LPF_NATIVE_MODULE; options.optimizationLevel = 2; // note: this might be removed in the future in favor of --!optimize @@ -3916,7 +3909,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c root->visit(&functionVisitor); // computes type information for all functions based on type annotations - if (FFlag::LuauCompileFunctionType && functionVisitor.hasTypes) + if (functionVisitor.hasTypes) buildTypeMap(compiler.typeMap, root, options.vectorType); for (AstExprFunction* expr : functions) diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index e85cc92c..8ac74d02 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/BytecodeBuilder.h" - #include "Types.h" +#include "Luau/BytecodeBuilder.h" + namespace Luau { diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h index cad55ab5..62f9e916 100644 --- a/Compiler/src/Types.h +++ b/Compiler/src/Types.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Ast.h" +#include "Luau/DenseHash.h" #include diff --git a/Compiler/src/ValueTracking.cpp b/Compiler/src/ValueTracking.cpp index 0bfaf9b3..616fca99 100644 --- a/Compiler/src/ValueTracking.cpp +++ b/Compiler/src/ValueTracking.cpp @@ -82,13 +82,13 @@ struct ValueVisitor : AstVisitor } }; -void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char** mutableGlobals) +void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char* const* mutableGlobals) { if (AstName name = names.get("_G"); name.value) globals[name] = Global::Mutable; if (mutableGlobals) - for (const char** ptr = mutableGlobals; *ptr; ++ptr) + for (const char* const* ptr = mutableGlobals; *ptr; ++ptr) if (AstName name = names.get(*ptr); name.value) globals[name] = Global::Mutable; } diff --git a/Compiler/src/ValueTracking.h b/Compiler/src/ValueTracking.h index fc74c84a..f8ecc6b8 100644 --- a/Compiler/src/ValueTracking.h +++ b/Compiler/src/ValueTracking.h @@ -28,7 +28,7 @@ struct Variable bool constant = false; // is the variable's value a compile-time constant? filled by constantFold }; -void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char** mutableGlobals); +void assignMutable(DenseHashMap& globals, const AstNameTable& names, const char* const* mutableGlobals); void trackValues(DenseHashMap& globals, DenseHashMap& variables, AstNode* root); inline Global getGlobalState(const DenseHashMap& globals, AstName name) diff --git a/Sources.cmake b/Sources.cmake index 88f3bb01..8dcd2431 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -163,6 +163,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/IostreamHelpers.h Analysis/include/Luau/JsonEmitter.h Analysis/include/Luau/Linter.h + Analysis/include/Luau/LinterConfig.h Analysis/include/Luau/LValue.h Analysis/include/Luau/Metamethods.h Analysis/include/Luau/Module.h @@ -221,6 +222,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/IostreamHelpers.cpp Analysis/src/JsonEmitter.cpp Analysis/src/Linter.cpp + Analysis/src/LinterConfig.cpp Analysis/src/LValue.cpp Analysis/src/Module.cpp Analysis/src/Normalize.cpp diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index fe7b1a12..254fc9db 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -7,8 +7,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauFasterNoise, false) - #undef PI #define PI (3.14159265358979323846) #define RADIANS_PER_DEGREE (PI / 180.0) @@ -277,27 +275,6 @@ static int math_randomseed(lua_State* L) return 0; } -// TODO: Delete with LuauFasterNoise -static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, - 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, - 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, - 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, - 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, - 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, - 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, - 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, - 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, - - 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, - 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, - 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, - 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, - 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, - 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, - 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, - 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, - 156, 180}; - static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, @@ -321,61 +298,14 @@ static float perlin_lerp(float t, float a, float b) return a + t * (b - a); } -static float grad(unsigned char hash, float x, float y, float z) -{ - LUAU_ASSERT(!FFlag::LuauFasterNoise); - unsigned char h = hash & 15; - float u = (h < 8) ? x : y; - float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z; - - return (h & 1 ? -u : u) + (h & 2 ? -v : v); -} - static float perlin_grad(int hash, float x, float y, float z) { const float* g = kPerlinGrad[hash & 15]; return g[0] * x + g[1] * y + g[2] * z; } -static float perlin_dep(float x, float y, float z) -{ - LUAU_ASSERT(!FFlag::LuauFasterNoise); - float xflr = floorf(x); - float yflr = floorf(y); - float zflr = floorf(z); - - int xi = int(xflr) & 255; - int yi = int(yflr) & 255; - int zi = int(zflr) & 255; - - float xf = x - xflr; - float yf = y - yflr; - float zf = z - zflr; - - float u = perlin_fade(xf); - float v = perlin_fade(yf); - float w = perlin_fade(zf); - - const unsigned char* p = kPerlin; - - int a = p[xi] + yi; - int aa = p[a] + zi; - int ab = p[a + 1] + zi; - - int b = p[xi + 1] + yi; - int ba = p[b] + zi; - int bb = p[b + 1] + zi; - - return perlin_lerp(w, - perlin_lerp(v, perlin_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)), - perlin_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))), - perlin_lerp(v, perlin_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)), - perlin_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1)))); -} - static float perlin(float x, float y, float z) { - LUAU_ASSERT(FFlag::LuauFasterNoise); float xflr = floorf(x); float yflr = floorf(y); float zflr = floorf(z); @@ -412,33 +342,19 @@ static float perlin(float x, float y, float z) static int math_noise(lua_State* L) { - if (FFlag::LuauFasterNoise) - { - int nx, ny, nz; - double x = lua_tonumberx(L, 1, &nx); - double y = lua_tonumberx(L, 2, &ny); - double z = lua_tonumberx(L, 3, &nz); + int nx, ny, nz; + double x = lua_tonumberx(L, 1, &nx); + double y = lua_tonumberx(L, 2, &ny); + double z = lua_tonumberx(L, 3, &nz); - luaL_argexpected(L, nx, 1, "number"); - luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number"); - luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number"); + luaL_argexpected(L, nx, 1, "number"); + luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number"); + luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number"); - double r = perlin((float)x, (float)y, (float)z); + double r = perlin((float)x, (float)y, (float)z); - lua_pushnumber(L, r); - return 1; - } - else - { - double x = luaL_checknumber(L, 1); - double y = luaL_optnumber(L, 2, 0.0); - double z = luaL_optnumber(L, 3, 0.0); - - double r = perlin_dep((float)x, (float)y, (float)z); - - lua_pushnumber(L, r); - return 1; - } + lua_pushnumber(L, r); + return 1; } static int math_clamp(lua_State* L) diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index d9ce71f9..90b30ead 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -9,6 +9,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauFasterInterp, false) +LUAU_FASTFLAGVARIABLE(LuauFasterFormatS, false) // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -1028,18 +1029,35 @@ static int str_format(lua_State* L) { size_t l; const char* s = luaL_checklstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) + if (FFlag::LuauFasterFormatS) { - /* no precision and string is too long to be formatted; - keep original string */ - lua_pushvalue(L, arg); - luaL_addvalue(&b); - continue; // skip the `luaL_addlstring' at the end + // no precision and string is too long to be formatted, or no format necessary to begin with + if (form[2] == '\0' || (!strchr(form, '.') && l >= 100)) + { + luaL_addlstring(&b, s, l, -1); + continue; // skip the `luaL_addlstring' at the end + } + else + { + snprintf(buff, sizeof(buff), form, s); + break; + } } else { - snprintf(buff, sizeof(buff), form, s); - break; + if (!strchr(form, '.') && l >= 100) + { + /* no precision and string is too long to be formatted; + keep original string */ + lua_pushvalue(L, arg); + luaL_addvalue(&b); + continue; // skip the `luaL_addlstring' at the end + } + else + { + snprintf(buff, sizeof(buff), form, s); + break; + } } } case '*': diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index fbf03deb..cc44ee19 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -10,8 +10,6 @@ #include "ldebug.h" #include "lvm.h" -LUAU_FASTFLAGVARIABLE(LuauFasterTableConcat, false) - static int foreachi(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -222,7 +220,7 @@ static int tmove(lua_State* L) static void addfield(lua_State* L, luaL_Buffer* b, int i) { int tt = lua_rawgeti(L, 1, i); - if (FFlag::LuauFasterTableConcat ? (tt != LUA_TSTRING && tt != LUA_TNUMBER) : !lua_isstring(L, -1)) + if (tt != LUA_TSTRING && tt != LUA_TNUMBER) luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i); luaL_addvalue(b); } diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 7a065383..365aa5d3 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLoadCheckGC, false) - // TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens template struct TempBuffer @@ -181,8 +179,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size } // we will allocate a fair amount of memory so check GC before we do - if (FFlag::LuauLoadCheckGC) - luaC_checkGC(L); + luaC_checkGC(L); // pause GC for the duration of deserialization - some objects we're creating aren't rooted // TODO: if an allocation error happens mid-load, we do not unpause GC! diff --git a/bench/micro_tests/test_StringInterp.lua b/bench/micro_tests/test_StringInterp.lua index 33d5ecea..1e7ccbc7 100644 --- a/bench/micro_tests/test_StringInterp.lua +++ b/bench/micro_tests/test_StringInterp.lua @@ -36,6 +36,13 @@ bench.runCode(function() end end, "interp: interp number") +bench.runCode(function() + local ok = "hello!" + for j=1,1e6 do + local _ = string.format("j=%s", ok) + end +end, "interp: %s format") + bench.runCode(function() local ok = "hello!" for j=1,1e6 do diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index c5777ec1..a08c8a9f 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -47,6 +47,56 @@ TEST_CASE("CodeAllocation") CHECK(nativeEntry == nativeData + kCodeAlignment); } +TEST_CASE("CodeAllocationCallbacks") +{ + struct AllocationData + { + size_t bytesAllocated = 0; + size_t bytesFreed = 0; + }; + + AllocationData allocationData{}; + + const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) + { + AllocationData& allocationData = *static_cast(context); + if (oldPointer != nullptr) + { + CHECK(oldSize != 0); + + allocationData.bytesFreed += oldSize; + } + + if (newPointer != nullptr) + { + CHECK(newSize != 0); + + allocationData.bytesAllocated += newSize; + } + }; + + const size_t blockSize = 1024 * 1024; + const size_t maxTotalSize = 1024 * 1024; + + { + CodeAllocator allocator(blockSize, maxTotalSize, allocationCallback, &allocationData); + + uint8_t* nativeData = nullptr; + size_t sizeNativeData = 0; + uint8_t* nativeEntry = nullptr; + + std::vector code; + code.resize(128); + + REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry)); + CHECK(allocationData.bytesAllocated == blockSize); + CHECK(allocationData.bytesFreed == 0); + } + + CHECK(allocationData.bytesAllocated == blockSize); + CHECK(allocationData.bytesFreed == blockSize); +} + TEST_CASE("CodeAllocationFailure") { size_t blockSize = 3000; diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index d368af66..5e58865b 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -4213,7 +4213,7 @@ RETURN R0 0 bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); Luau::CompileOptions options; const char* mutableGlobals[] = {"Game", "Workspace", "game", "plugin", "script", "shared", "workspace", NULL}; - options.mutableGlobals = &mutableGlobals[0]; + options.mutableGlobals = mutableGlobals; Luau::compileOrThrow(bcb, source, options); CHECK_EQ("\n" + bcb.dumpFunction(0), R"( @@ -7100,8 +7100,6 @@ L1: RETURN R3 1 TEST_CASE("EncodedTypeTable") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) @@ -7153,8 +7151,6 @@ Str:test(234) TEST_CASE("HostTypesAreUserdata") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) @@ -7181,8 +7177,6 @@ end TEST_CASE("HostTypesVector") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: Instance, pos: Vector3) end @@ -7206,8 +7200,6 @@ end TEST_CASE("TypeAliasScoping") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( do type Part = number @@ -7242,8 +7234,6 @@ type Instance = string TEST_CASE("TypeAliasResolve") { - ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileTypeTable(R"( type Foo1 = number type Foo2 = { number } diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f4a74b66..97d4e031 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -266,6 +266,12 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize) TEST_SUITE_BEGIN("Conformance"); +TEST_CASE("CodegenSupported") +{ + if (codegen && !luau_codegen_supported()) + MESSAGE("Native code generation is not supported by the current configuration and will be disabled"); +} + TEST_CASE("Assert") { runConformance("assert.lua"); @@ -1726,7 +1732,6 @@ TEST_CASE("Native") TEST_CASE("NativeTypeAnnotations") { ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true); - ScopedFastFlag luauCompileFunctionType("LuauCompileFunctionType", true); // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp index c2b09bbd..84a383c1 100644 --- a/tests/Differ.test.cpp +++ b/tests/Differ.test.cpp @@ -1528,4 +1528,43 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic") compareTypesEq("foo", "almostFoo"); } +TEST_CASE_FIXTURE(DifferFixture, "symbol_forward") +{ + CheckResult result = check(R"( + local foo = 5 + local almostFoo = "five" + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + INFO(Luau::toString(requireType("foo"))); + INFO(Luau::toString(requireType("almostFoo"))); + + compareTypesNe("foo", "almostFoo", + R"(DiffError: these two types are not equal because the left type at foo has type number, while the right type at almostFoo has type string)", + true); +} + +TEST_CASE_FIXTURE(DifferFixture, "newlines") +{ + CheckResult result = check(R"( + local foo = 5 + local almostFoo = "five" + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + INFO(Luau::toString(requireType("foo"))); + INFO(Luau::toString(requireType("almostFoo"))); + + compareTypesNe("foo", "almostFoo", + R"(DiffError: these two types are not equal because the left type at + foo +has type + number, +while the right type at + almostFoo +has type + string)", + true, true); +} + TEST_SUITE_END(); diff --git a/tests/Fixture.h b/tests/Fixture.h index a9c5d9b0..8d997140 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -160,14 +160,37 @@ void createSomeClasses(Frontend* frontend); template struct DifferFixtureGeneric : BaseFixture { - void compareNe(TypeId left, TypeId right, const std::string& expectedMessage) + std::string normalizeWhitespace(std::string msg) + { + std::string normalizedMsg = ""; + bool wasWhitespace = true; + for (char c : msg) + { + bool isWhitespace = c == ' ' || c == '\n'; + if (wasWhitespace && isWhitespace) + continue; + normalizedMsg += isWhitespace ? ' ' : c; + wasWhitespace = isWhitespace; + } + if (wasWhitespace) + normalizedMsg.pop_back(); + return normalizedMsg; + } + + void compareNe(TypeId left, TypeId right, const std::string& expectedMessage, bool multiLine) + { + compareNe(left, std::nullopt, right, std::nullopt, expectedMessage, multiLine); + } + + void compareNe(TypeId left, std::optional symbolLeft, TypeId right, std::optional symbolRight, + const std::string& expectedMessage, bool multiLine) { std::string diffMessage; try { - DifferResult diffRes = diff(left, right); + DifferResult diffRes = diffWithSymbols(left, right, symbolLeft, symbolRight); REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal"); - diffMessage = diffRes.diffError->toString(); + diffMessage = diffRes.diffError->toString(multiLine); } catch (const InternalCompilerError& e) { @@ -176,9 +199,19 @@ struct DifferFixtureGeneric : BaseFixture CHECK_EQ(expectedMessage, diffMessage); } - void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage) + void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage, bool forwardSymbol = false, + bool multiLine = false) { - compareNe(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol), expectedMessage); + if (forwardSymbol) + { + compareNe( + BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine); + } + else + { + compareNe( + BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine); + } } void compareEq(TypeId left, TypeId right) diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index e906c224..6c728da9 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1657,8 +1657,6 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored TEST_CASE_FIXTURE(Fixture, "WrongComment") { - ScopedFastFlag sff("LuauLintNativeComment", true); - LintResult result = lint(R"( --!strict --!struct diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 5b1849a7..54a96861 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -1,5 +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/Common.h" #include "Luau/Module.h" #include "Luau/Scope.h" #include "Luau/RecursionCounter.h" @@ -7,11 +8,13 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(LuauStacklessTypeClone) TEST_SUITE_BEGIN("ModuleTests"); @@ -78,7 +81,7 @@ TEST_CASE_FIXTURE(Fixture, "is_within_comment_parse_result") TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; // numberType is persistent. We leave it as-is. TypeId newNumber = clone(builtinTypes->numberType, dest, cloneState); @@ -88,7 +91,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive") TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; // Create a new number type that isn't persistent unfreeze(frontend.globals.globalTypes); @@ -129,7 +132,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") TypeId ty = requireType("Cyclic"); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloneTy = clone(ty, dest, cloneState); TableType* ttv = getMutable(cloneTy); @@ -147,8 +150,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") REQUIRE(methodReturnType); CHECK_MESSAGE(methodReturnType == cloneTy, toString(methodType, {true}) << " should be pointer identical to " << toString(cloneTy, {true})); - CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type - CHECK_EQ(2, dest.types.size()); // One table and one function + CHECK_EQ(FFlag::LuauStacklessTypeClone ? 1 : 2, dest.typePacks.size()); // one for the function args, and another for its return type + CHECK_EQ(2, dest.types.size()); // One table and one function } TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2") @@ -165,7 +168,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2") TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloneTy = clone(tableTy, dest, cloneState); TableType* ctt = getMutable(cloneTy); REQUIRE(ctt); @@ -209,7 +212,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena") TEST_CASE_FIXTURE(Fixture, "deepClone_union") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; unfreeze(frontend.globals.globalTypes); TypeId oldUnion = frontend.globals.globalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}}); @@ -224,7 +227,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union") TEST_CASE_FIXTURE(Fixture, "deepClone_intersection") { TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; unfreeze(frontend.globals.globalTypes); TypeId oldIntersection = frontend.globals.globalTypes.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}); @@ -251,7 +254,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") std::nullopt, &exampleMetaClass, {}, {}, "Test"}}; TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(&exampleClass, dest, cloneState); const ClassType* ctv = get(cloned); @@ -274,12 +277,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types") TypePackVar freeTp(FreeTypePack{TypeLevel{}}); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId clonedTy = clone(freeTy, dest, cloneState); CHECK(get(clonedTy)); - cloneState = {}; + cloneState = {builtinTypes}; TypePackId clonedTp = clone(&freeTp, dest, cloneState); CHECK(get(clonedTp)); } @@ -291,7 +294,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_tables") ttv->state = TableState::Free; TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(&tableTy, dest, cloneState); const TableType* clonedTtv = get(cloned); @@ -332,6 +335,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") #else int limit = 400; #endif + + ScopedFastFlag sff{"LuauStacklessTypeClone", false}; ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit}; TypeArena src; @@ -348,11 +353,39 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") } TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException); } +TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit") +{ + ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500}; + + TypeArena src; + + TypeId table = src.addType(TableType{}); + TypeId nested = table; + + for (int i = 0; i < 2500; i++) + { + TableType* ttv = getMutable(nested); + ttv->props["a"].setType(src.addType(TableType{})); + nested = ttv->props["a"].type(); + } + + TypeArena dest; + CloneState cloneState{builtinTypes}; + + TypeId ty = clone(table, dest, cloneState); + CHECK(get(ty)); + + // Cloning it again is an important test. + TypeId ty2 = clone(table, dest, cloneState); + CHECK(get(ty2)); +} + // Unions should never be cyclic, but we should clone them correctly even if // they are. TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union") @@ -368,7 +401,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union") uu->options.push_back(u); TypeArena dest; - CloneState cloneState; + CloneState cloneState{builtinTypes}; TypeId cloned = clone(u, dest, cloneState); REQUIRE(cloned); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index d4a25f80..b0592b4c 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -831,4 +831,39 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") CHECK(toString(requireType("y")) == "({string}, {string}) -> ()"); } +TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") +{ + ScopedFastFlag sff[] = { + {"LuauIndentTypeMismatch", true}, + }; + + ScopedFastInt sfi[] = { + {"LuauIndentTypeMismatchMaxTypeLength", 10}, + }; + + CheckResult result = check(R"( +--!strict + function f1() : {a : number, b : string, c : { d : number}} + return { a = 1, b = "a", c = {d = "a"}} + end + +)"); + std::string expected = R"(Type + '{ a: number, b: string, c: { d: string } }' +could not be converted into + '{| a: number, b: string, c: {| d: number |} |}' +caused by: + Property 'c' is not compatible. +Type + '{ d: string }' +could not be converted into + '{| d: number |}' +caused by: + Property 'd' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; + std::string actual = toString(result.errors[0]); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(expected == actual); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 55f4caec..bebc8942 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -187,8 +187,11 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases") TEST_CASE_FIXTURE(Fixture, "generic_aliases") { - ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; - + ScopedFastFlag sff[] = { + {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type T = { v: a } local x: T = { v = 123 } @@ -197,18 +200,22 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - const char* expectedError = "Type 'bad' could not be converted into 'T'\n" - "caused by:\n" - " Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context"; - + const std::string expected = R"(Type 'bad' could not be converted into 'T' +caused by: + Property 'v' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); - CHECK(toString(result.errors[0]) == expectedError); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") { - ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; + ScopedFastFlag sff[] = { + {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type T = { v: a } @@ -218,15 +225,16 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - std::string expectedError = "Type 'bad' could not be converted into 'U'\n" - "caused by:\n" - " Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T'\n" - "caused by:\n" - " Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context"; + const std::string expected = R"(Type 'bad' could not be converted into 'U' +caused by: + Property 't' is not compatible. +Type '{ v: string }' could not be converted into 'T' +caused by: + Property 'v' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); - CHECK(toString(result.errors[0]) == expectedError); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") @@ -261,7 +269,7 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_errors") // We had a UAF in this example caused by not cloning type function arguments ModulePtr module = frontend.moduleResolver.getModule("MainModule"); unfreeze(module->interfaceTypes); - copyErrors(module->errors, module->interfaceTypes); + copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); module->internalTypes.clear(); module->astTypes.clear(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 4e0b7a7e..e64bf9e9 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -132,7 +132,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate") TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( --!strict @@ -142,12 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '(number, number) -> boolean' could not be converted into '((string, string) -> boolean)?' + const std::string expected = R"(Type + '(number, number) -> boolean' +could not be converted into + '((string, string) -> boolean)?' caused by: - None of the union options are compatible. For example: Type '(number, number) -> boolean' could not be converted into '(string, string) -> boolean' + None of the union options are compatible. For example: +Type + '(number, number) -> boolean' +could not be converted into + '(string, string) -> boolean' caused by: - Argument #1 type is not compatible. Type 'string' could not be converted into 'number')", - toString(result.errors[0])); + Argument #1 type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "strings_have_methods") diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index a4df1be7..7055c27e 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -367,8 +367,11 @@ b.X = 2 -- real Vector2.X is also read-only TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; - + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) @@ -380,10 +383,11 @@ b(a) )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}' + const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}' caused by: - Property 'Y' is not compatible. Type 'number' could not be converted into 'string')"); + Property 'Y' is not compatible. +Type 'number' could not be converted into 'string')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict") @@ -453,6 +457,8 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = { x: ChildClass } type B = { x: BaseClass } @@ -462,9 +468,11 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'x' is not compatible. Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)"); + Property 'x' is not compatible. +Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(ClassFixture, "callable_classes") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 379ecac6..846d5b53 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1095,6 +1095,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + // Simple direct arg to arg propagation CheckResult result = check(R"( type Table = { x: number, y: number } @@ -1150,129 +1153,27 @@ f(function(a, b, c, ...) return a + b end) LUAU_REQUIRE_ERRORS(result); + std::string expected; if (FFlag::LuauInstantiateInSubtyping) { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' + expected = R"(Type + '(number, number, a) -> number' +could not be converted into + '(number, number) -> number' caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); + Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; } else { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' + expected = R"(Type + '(number, number, a) -> number' +could not be converted into + '(number, number) -> number' caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); + Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; } - // Infer from variadic packs into elements - result = check(R"( -function f(a: (...number) -> number) return a(1, 2) end -f(function(a, b) return a + b end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Infer from variadic packs into variadic packs - result = check(R"( -type Table = { x: number, y: number } -function f(a: (...Table) -> number) return a({x = 1, y = 2}, {x = 3, y = 4}) end -f(function(a, ...) local b = ... return b.z end) - )"); - - LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0])); - - // Return type inference - result = check(R"( -type Table = { x: number, y: number } -function f(a: (number) -> Table) return a(4) end -f(function(x) return x * 2 end) - )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0])); - - // Return type doesn't inference 'nil' - result = check(R"( - function f(a: (number) -> nil) return a(4) end - f(function(x) print(x) end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); -} - -TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") -{ - // Simple direct arg to arg propagation - CheckResult result = check(R"( -type Table = { x: number, y: number } -local function f(a: (Table) -> number) return a({x = 1, y = 2}) end -f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // An optional function is accepted, but since we already provide a function, nil can be ignored - result = check(R"( -type Table = { x: number, y: number } -local function f(a: ((Table) -> number)?) if a then return a({x = 1, y = 2}) else return 0 end end -f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Make sure self calls match correct index - result = check(R"( -type Table = { x: number, y: number } -local x = {} -x.b = {x = 1, y = 2} -function x:f(a: (Table) -> number) return a(self.b) end -x:f(function(a) return a.x + a.y end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Mix inferred and explicit argument types - result = check(R"( -function f(a: (a: number, b: number, c: boolean) -> number) return a(1, 2, true) end -f(function(a: number, b, c) return c and a + b or b - a end) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // Anonymous function has a variadic pack - result = check(R"( -type Table = { x: number, y: number } -local function f(a: (Table) -> number) return a({x = 1, y = 2}) end -f(function(...) return select(1, ...).z end) - )"); - - LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0])); - - // Can't accept more arguments than provided - result = check(R"( -function f(a: (a: number, b: number) -> number) return a(1, 2) end -f(function(a, b, c, ...) return a + b end) - )"); - - LUAU_REQUIRE_ERRORS(result); - - if (FFlag::LuauInstantiateInSubtyping) - { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); - } - else - { - CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)", - toString(result.errors[0])); - } + CHECK_EQ(expected, toString(result.errors[0])); // Infer from variadic packs into elements result = check(R"( @@ -1376,6 +1277,8 @@ end TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type A = (number, number) -> string type B = (number) -> string @@ -1385,13 +1288,20 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number) -> string' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> string type B = (number, string) -> string @@ -1401,13 +1311,21 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, string) -> string' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number, string) -> string' caused by: - Argument #2 type is not compatible. Type 'string' could not be converted into 'number')"); + Argument #2 type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> (number) type B = (number, number) -> (number, boolean) @@ -1417,13 +1335,20 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> number' could not be converted into '(number, number) -> (number, boolean)' + const std::string expected = R"(Type + '(number, number) -> number' +could not be converted into + '(number, number) -> (number, boolean)' caused by: - Function only returns 1 value, but 2 are required here)"); + Function only returns 1 value, but 2 are required here)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> string type B = (number, number) -> number @@ -1433,13 +1358,21 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, number) -> number' + const std::string expected = R"(Type + '(number, number) -> string' +could not be converted into + '(number, number) -> number' caused by: - Return type is not compatible. Type 'string' could not be converted into 'number')"); + Return type is not compatible. +Type 'string' could not be converted into 'number')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = (number, number) -> (number, string) type B = (number, number) -> (number, boolean) @@ -1449,10 +1382,14 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - R"(Type '(number, number) -> (number, string)' could not be converted into '(number, number) -> (number, boolean)' + const std::string expected = R"(Type + '(number, number) -> (number, string)' +could not be converted into + '(number, number) -> (number, boolean)' caused by: - Return #2 type is not compatible. Type 'string' could not be converted into 'boolean')"); + Return #2 type is not compatible. +Type 'string' could not be converted into 'boolean')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_quantify_right_type") @@ -1561,6 +1498,9 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t = { f = nil :: ((x: number) -> number)? } @@ -1575,11 +1515,19 @@ end )"); LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string) -> string' could not be converted into '((number) -> number)?' + CHECK_EQ(toString(result.errors[0]), R"(Type + '(string) -> string' +could not be converted into + '((number) -> number)?' caused by: - None of the union options are compatible. For example: Type '(string) -> string' could not be converted into '(number) -> number' + None of the union options are compatible. For example: +Type + '(string) -> string' +could not be converted into + '(number) -> number' caused by: - Argument #1 type is not compatible. Type 'number' could not be converted into 'string')"); + Argument #1 type is not compatible. +Type 'number' could not be converted into 'string')"); CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); } @@ -1595,6 +1543,9 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t: {[string]: () -> number} = {} @@ -1603,7 +1554,10 @@ function t:b() return 2 end -- not OK )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '(*error-type*) -> number' could not be converted into '() -> number' + CHECK_EQ(R"(Type + '(*error-type*) -> number' +could not be converted into + '() -> number' caused by: Argument count mismatch. Function expects 1 argument, but none are specified)", toString(result.errors[0])); @@ -1800,6 +1754,9 @@ foo(string.find("hello", "e")) TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict @@ -1832,11 +1789,11 @@ z = y -- Not OK, so the line is colorable )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((\"blue\" | \"red\") -> (\"blue\" | \"red\") -> (\"blue\" | \"red\") -> boolean) & ((\"blue\" | \"red\") -> (\"blue\") -> (\"blue\") " - "-> false) & ((\"blue\" | \"red\") -> (\"red\") -> (\"red\") -> false) & ((\"blue\") -> (\"blue\") -> (\"blue\" | \"red\") -> false) & " - "((\"red\") -> (\"red\") -> (\"blue\" | \"red\") -> false)' could not be converted into '(\"blue\" | \"red\") -> (\"blue\" | \"red\") -> " - "(\"blue\" | \"red\") -> false'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)' +could not be converted into + '("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions") @@ -1994,7 +1951,11 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastFlag sff[] = { + {"LuauIndentTypeMismatch", true}, + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local function foo(x: a, y: a?) @@ -2038,11 +1999,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '{ x: number }' could not be converted into 'vec2?' + const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?' caused by: - None of the union options are compatible. For example: Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"); - - CHECK_EQ(toString(result.errors[1]), "Type 'vec2' could not be converted into 'number'"); + None of the union options are compatible. For example: +Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"; + CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1])); } TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 6b933616..f7904d45 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -713,6 +713,9 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict -- At one point this produced a UAF @@ -725,12 +728,14 @@ y.a.c = y )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), - R"(Type 'y' could not be converted into 'T' + const std::string expected = R"(Type 'y' could not be converted into 'T' caused by: - Property 'a' is not compatible. Type '{ c: T?, d: number }' could not be converted into 'U' + Property 'a' is not compatible. +Type '{ c: T?, d: number }' could not be converted into 'U' caused by: - Property 'd' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'd' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1") diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 954d9858..b390a816 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -317,6 +317,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } @@ -333,9 +336,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string' + const std::string expected = R"(Type + '(string, number) -> string' +could not be converted into + '(string) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'"); @@ -343,6 +350,9 @@ caused by: TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + // After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one CheckResult result = check(R"( type XY = { x: (number) -> number, y: (string) -> string } @@ -357,9 +367,14 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string' + const std::string expected = R"(Type + '(string, number) -> string' +could not be converted into + '(string) -> string' caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"); + Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'XY'"); @@ -377,6 +392,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable") TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -386,9 +404,11 @@ local a: XYZ = 3 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z' + const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z' caused by: - Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"); + Not all intersection parts are compatible. +Type 'number' could not be converted into 'X')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") @@ -462,6 +482,9 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number?) -> number?) & ((string?) -> string?) local y : (nil) -> nil = x -- OK @@ -469,12 +492,18 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; " - "none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> number?) & ((string?) -> string?)' +could not be converted into + '(number) -> number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) local y : ((number | string) -> (number | string)) = x -- OK @@ -482,12 +511,18 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> " - "boolean | number'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number) -> number) & ((string) -> string)' +could not be converted into + '(boolean | number) -> boolean | number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local y : { p : number?, q : nil, r : number? } = x -- OK @@ -495,12 +530,18 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into " - "'{| p: nil |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' +could not be converted into + '{| p: nil |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : { p : number?, q : any } & { p : unknown, q : string? } local y : { p : number?, q : string? } = x -- OK @@ -532,9 +573,11 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") else { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, " - "q: number? |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '{| p: number?, q: any |} & {| p: unknown, q: string? |}' +could not be converted into + '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } } @@ -551,6 +594,9 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local y : (nil) -> { p : number, q : number, r : number} = x -- OK @@ -558,13 +604,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into " - "'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' +could not be converted into + '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) @@ -574,12 +625,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; " - "none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> a | number) & ((string?) -> a | string)' +could not be converted into + '(number?) -> a'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((a?) -> (a | b)) & ((c?) -> (b | c)) @@ -589,12 +646,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((a?) -> a | b) & ((c?) -> b | c)' +could not be converted into + '(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)) @@ -604,12 +667,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted " - "into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' +could not be converted into + '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> unknown) @@ -619,12 +688,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((nil) -> unknown) & ((number) -> number)' +could not be converted into + '(number?) -> number?'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((unknown) -> string?) @@ -634,12 +709,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number) -> number?) & ((unknown) -> string?)' +could not be converted into + '(number?) -> nil'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> never) @@ -649,12 +730,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of " - "the intersection parts are compatible"); + const std::string expected = R"(Type + '((nil) -> never) & ((number) -> number)' +could not be converted into + '(number?) -> never'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((never) -> string?) @@ -664,12 +751,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none " - "of the intersection parts are compatible"); + const std::string expected = R"(Type + '((never) -> string?) & ((number) -> number?)' +could not be converted into + '(number?) -> nil'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : ((string?) -> (string | number)) & ((number?) -> ...number) local y : ((nil) -> (number, number?)) = x -- OK @@ -677,8 +770,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_ )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | " - "string) -> (number, number?)'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '((number?) -> (...number)) & ((string?) -> number | string)' +could not be converted into + '(number | string) -> (number, number?)'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") @@ -713,6 +809,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : (() -> a...) & (() -> (number?,a...)) @@ -722,12 +821,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible"); + const std::string expected = R"(Type + '(() -> (a...)) & (() -> (number?, a...))' +could not be converted into + '() -> number'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( function f() local x : ((a...) -> ()) & ((number,a...) -> number) @@ -737,8 +842,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of " - "the intersection parts are compatible"); + const std::string expected = R"(Type + '((a...) -> ()) & ((number, a...) -> number)' +could not be converted into + '(number?) -> ()'; none of the intersection parts are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") @@ -897,8 +1005,6 @@ local y = x.Bar TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_2") { - ScopedFastFlag sff{"LuauIndexTableIntersectionStringExpr", true}; - CheckResult result = check(R"( type Foo = { Bar: string, diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index b75f909a..7c3d4808 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -389,6 +389,9 @@ type Table = typeof(tbl) TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + fileResolver.source["game/A"] = R"( export type T = { x: number } return {} @@ -407,15 +410,19 @@ local b: B.T = a )"; CheckResult result = frontend.check("game/C"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' + const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' caused by: - Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'x' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + fileResolver.source["game/A"] = R"( export type Wrap = { x: T } return {} @@ -441,11 +448,12 @@ local b: B.T = a )"; CheckResult result = frontend.check("game/D"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' + const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' caused by: - Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'x' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "constrained_anyification_clone_immutable_types") diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 08c0f7ca..cc0a79f8 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -9,6 +9,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" using namespace Luau; @@ -404,4 +405,92 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias") CHECK_MESSAGE(get(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType)); } +TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex") +{ + ScopedFastFlag sff{"LuauStacklessTypeClone", true}; + + frontend.options.retainFullTypeGraphs = false; + + // Used `luau-reduce` tool to extract a minimal reproduction. + // Credit: https://github.com/evaera/roblox-lua-promise/blob/v4.0.0/lib/init.lua + CheckResult result = check(R"( + --!strict + + local Promise = {} + Promise.prototype = {} + Promise.__index = Promise.prototype + + function Promise._new(traceback, callback, parent) + if parent ~= nil and not Promise.is(parent)then + end + + local self = { + _parent = parent, + } + + parent._consumers[self] = true + setmetatable(self, Promise) + self:_reject() + + return self + end + + function Promise.resolve(...) + return Promise._new(debug.traceback(nil, 2), function(resolve) + end) + end + + function Promise.reject(...) + return Promise._new(debug.traceback(nil, 2), function(_, reject) + end) + end + + function Promise._try(traceback, callback, ...) + return Promise._new(traceback, function(resolve) + end) + end + + function Promise.try(callback, ...) + return Promise._try(debug.traceback(nil, 2), callback, ...) + end + + function Promise._all(traceback, promises, amount) + if #promises == 0 or amount == 0 then + return Promise.resolve({}) + end + return Promise._new(traceback, function(resolve, reject, onCancel) + end) + end + + function Promise.all(promises) + return Promise._all(debug.traceback(nil, 2), promises) + end + + function Promise.allSettled(promises) + return Promise.resolve({}) + end + + function Promise.race(promises) + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + end) + end + + function Promise.each(list, predicate) + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local predicatePromise = Promise.resolve(predicate(value, index)) + local success, result = predicatePromise:await() + end) + end + + function Promise.is(object) + end + + function Promise.prototype:_reject(...) + self:_finalize() + end + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index da6e391b..c33fe170 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -787,6 +787,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local t: {x: number?} = {x = nil} @@ -796,11 +799,14 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ(R"(Type '{| x: number? |}' could not be converted into '{| x: number |}' + const std::string expected = R"(Type + '{| x: number? |}' +could not be converted into + '{| x: number |}' caused by: - Property 'x' is not compatible. Type 'number?' could not be converted into 'number' in an invariant context)", - toString(result.errors[0])); + Property 'x' is not compatible. +Type 'number?' could not be converted into 'number' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") @@ -1005,6 +1011,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_infinite_recursion") { ScopedFastFlag luauTableUnifyRecursionLimit{"LuauTableUnifyRecursionLimit", true}; +#if defined(_NOOPT) || defined(_DEBUG) + ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100}; +#endif + fileResolver.source["game/A"] = R"( local tbl = {} diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 84e9fc7c..2156d1c6 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -316,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -325,14 +328,18 @@ local a: Animal = { tag = 'cat', cafood = 'something' } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 'a' could not be converted into 'Cat | Dog' + const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')", - toString(result.errors[0])); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type Good = { success: true, result: string } type Bad = { success: false, error: string } @@ -342,18 +349,20 @@ local a: Result = { success = false, result = 'something' } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 'a' could not be converted into 'Bad | Good' + const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')", - toString(result.errors[0])); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") { ScopedFastFlag sff[] = { {"DebugLuauDeferredConstraintResolution", true}, + {"LuauIndentTypeMismatch", true}, }; - + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type Ok = {success: true, result: T} type Err = {success: false, error: T} @@ -365,10 +374,10 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expectedError = "Type 'a' could not be converted into 'Err | Ok'\n" - "caused by:\n" - " None of the union options are compatible. For example: Table type 'a'" - " not compatible with type 'Err' because the former is missing field 'error'"; + const std::string expectedError = R"(Type 'a' could not be converted into 'Err | Ok' +caused by: + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'Err' because the former is missing field 'error')"; CHECK(toString(result.errors[0]) == expectedError); } diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 8d93561f..ca137b15 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2083,6 +2083,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { x: number, y: number } type B = { x: number, y: string } @@ -2092,13 +2094,17 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'y' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type AS = { x: number, y: number } type BS = { x: number, y: string } @@ -2111,15 +2117,21 @@ local b: B = a )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property 'b' is not compatible. Type 'AS' could not be converted into 'BS' + Property 'b' is not compatible. +Type 'AS' could not be converted into 'BS' caused by: - Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property 'y' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end }); local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end }); @@ -2130,33 +2142,68 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end }); local c2: typeof(a2) = b2 )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'b1' could not be converted into 'a1' + const std::string expected1 = R"(Type 'b1' could not be converted into 'a1' caused by: - Type '{ x: number, y: string }' could not be converted into '{ x: number, y: number }' + Type + '{ x: number, y: string }' +could not be converted into + '{ x: number, y: number }' caused by: - Property 'y' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)"); + Property 'y' is not compatible. +Type 'string' could not be converted into 'number' in an invariant context)"; + const std::string expected2 = R"(Type 'b2' could not be converted into 'a2' +caused by: + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' +caused by: + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' +caused by: + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' +caused by: + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ(expected1, toString(result.errors[0])); if (FFlag::LuauInstantiateInSubtyping) { - CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2' -caused by: - Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: (a) -> () }' -caused by: - Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '(a) -> ()'; different number of generic type parameters)"); + CHECK_EQ(expected2, toString(result.errors[1])); } else { - CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2' + std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: - Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: (a) -> () }' + Type + '{ __call: (a, b) -> () }' +could not be converted into + '{ __call: (a) -> () }' caused by: - Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '(a) -> ()'; different number of generic type parameters)"); + Property '__call' is not compatible. +Type + '(a, b) -> ()' +could not be converted into + '(a) -> ()'; different number of generic type parameters)"; + CHECK_EQ(expected3, toString(result.errors[1])); } } TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: string } type B = { [string]: string } @@ -2166,13 +2213,17 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property '[indexer key]' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( type A = { [number]: number } type B = { [number]: string } @@ -2182,9 +2233,11 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B' + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: - Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"); + Property '[indexer value]' is not compatible. +Type 'number' could not be converted into 'string' in an invariant context)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table") @@ -2204,6 +2257,8 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") { + ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; CheckResult result = check(R"( --!strict type Super = { x : number } @@ -2218,9 +2273,11 @@ local y: number = tmp.p.y )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'tmp' could not be converted into 'HasSuper' + const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' caused by: - Property 'p' is not compatible. Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"); + Property 'p' is not compatible. +Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") @@ -3302,7 +3359,13 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true}; + ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}}; + ScopedFastFlag sff[] = { + {"LuauAlwaysCommitInferencesOfFunctionCalls", true}, + {"LuauIndentTypeMismatch", true}, + }; + + CheckResult result = check(R"( local function f(s) @@ -3316,22 +3379,32 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ LUAU_REQUIRE_ERROR_COUNT(3, result); - CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + const std::string expected1 = + R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[0])); + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected1, toString(result.errors[0])); - CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[1])); - CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + const std::string expected2 = + R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected2, toString(result.errors[1])); + + const std::string expected3 = R"(Type + '"bar" | "baz"' +could not be converted into + 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[2])); + Not all union options are compatible. +Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' +caused by: + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + CHECK_EQ(expected3, toString(result.errors[2])); } TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible") @@ -3349,6 +3422,9 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local function f(s): string local foo = s:absolutely_no_scalar_has_this_method() @@ -3356,11 +3432,13 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_ end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string' + const std::string expected = + R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string' caused by: - The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')", - toString(result.errors[0])); + The former's metatable does not satisfy the requirements. +Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ("(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f"))); } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 4f40b386..cb7429aa 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -992,6 +992,9 @@ end TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( --!strict --!nolint @@ -1028,16 +1031,23 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") // unsound. LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_EQ( - R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule' + const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule' caused by: - Property 'getStoreFieldName' is not compatible. Type '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' could not be converted into '(Policies, FieldSpecifier) -> string' + Property 'getStoreFieldName' is not compatible. +Type + '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' +could not be converted into + '(Policies, FieldSpecifier) -> string' caused by: - Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}' + Argument #2 type is not compatible. +Type + 'FieldSpecifier' +could not be converted into + 'FieldSpecifier & {| from: number? |}' caused by: - Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')", - toString(result.errors[0])); + Not all intersection parts are compatible. +Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')"; + CHECK_EQ(expected, toString(result.errors[0])); } else { @@ -1205,8 +1215,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "typechecking_in_type_guards") { - ScopedFastFlag sff{"LuauTypecheckTypeguards", true}; - CheckResult result = check(R"( local a = type(foo) == 'nil' local b = typeof(foo) ~= 'nil' diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 5656e871..7910cf6d 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -345,7 +345,9 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table { ScopedFastFlag sff[] = { {"LuauTransitiveSubtyping", true}, + {"LuauIndentTypeMismatch", true}, }; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; TableType::Props freeProps{ {"foo", {builtinTypes->numberType}}, @@ -373,11 +375,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table state.log.commit(); REQUIRE_EQ(state.errors.size(), 1); - - std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n" - "caused by:\n" - " Type 'number' could not be converted into 'string'"; - CHECK_EQ(toString(state.errors[0]), expected); + const std::string expected = R"(Type + '{ @metatable {| __index: {| foo: string |} |}, { } }' +could not be converted into + '{- foo: number -}' +caused by: + Type 'number' could not be converted into 'string')"; + CHECK_EQ(expected, toString(state.errors[0])); } TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index d2ae166b..37aaea7a 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -872,6 +872,9 @@ type R = { m: F } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) @@ -879,9 +882,13 @@ a = b )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)' + const std::string expected = R"(Type + '() -> (number, ...boolean)' +could not be converted into + '() -> (number, ...string)' caused by: - Type 'boolean' could not be converted into 'string')"); + Type 'boolean' could not be converted into 'string')"; + CHECK_EQ(expected, toString(result.errors[0])); } // TODO: File a Jira about this diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 3ab7bebb..271841e9 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -459,6 +459,8 @@ local oh : boolean = t.y TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -471,9 +473,11 @@ local b: { w: number } = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' + const std::string expected = R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' caused by: - Not all union options are compatible. Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')"); + Not all union options are compatible. +Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all") @@ -494,6 +498,8 @@ local a: XYZ = { w = 4 } TEST_CASE_FIXTURE(Fixture, "error_detailed_optional") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( type X = { x: number } @@ -501,9 +507,11 @@ local a: X? = { w = 4 } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'a' could not be converted into 'X?' + const std::string expected = R"(Type 'a' could not be converted into 'X?' caused by: - None of the union options are compatible. For example: Table type 'a' not compatible with type 'X' because the former is missing field 'x')"); + None of the union options are compatible. For example: +Table type 'a' not compatible with type 'X' because the former is missing field 'x')"; + CHECK_EQ(expected, toString(result.errors[0])); } // We had a bug where a cyclic union caused a stack overflow. @@ -524,6 +532,9 @@ TEST_CASE_FIXTURE(Fixture, "dont_allow_cyclic_unions_to_be_inferred") TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string } @@ -540,8 +551,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") LUAU_REQUIRE_ERROR_COUNT(1, result); // NOTE: union normalization will improve this message - CHECK_EQ(toString(result.errors[0]), - R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)"); + const std::string expected = R"(Type + '(string) -> number' +could not be converted into + '((number) -> string) | ((number) -> string)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_true_and_false") @@ -606,6 +620,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( function f() local x : (number, a...) -> (number?, a...) @@ -615,12 +631,17 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, " - "a...) -> (number?, a...))'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number, a...) -> (number?, a...)' +could not be converted into + '((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK @@ -628,12 +649,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> " - "number)'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number) -> number?' +could not be converted into + '((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : () -> (number | string) local y : (() -> number) | (() -> string) = x -- OK @@ -641,12 +668,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none " - "of the union options are compatible"); + const std::string expected = R"(Type + '() -> number | string' +could not be converted into + '(() -> (string, string)) | (() -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : (...nil) -> (...number?) local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK @@ -654,12 +687,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) " - "-> nil)'; none of the union options are compatible"); + const std::string expected = R"(Type + '(...nil) -> (...number?)' +could not be converted into + '((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : (number) -> () local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK @@ -667,12 +706,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible"); + const std::string expected = R"(Type + '(number) -> ()' +could not be converted into + '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") { + ScopedFastFlag sff{"LuauIndentTypeMismatch", true}; + ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10}; + CheckResult result = check(R"( local x : () -> (number?, ...number) local y : (() -> (...number)) | (() -> nil) = x -- OK @@ -680,8 +725,11 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none " - "of the union options are compatible"); + const std::string expected = R"(Type + '() -> (number?, ...number)' +could not be converted into + '(() -> (...number)) | (() -> number)'; none of the union options are compatible)"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 6c0e0e0e..ed985c49 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -14,6 +14,17 @@ assert((function(x, y) return c, b, t, t1, t2 end)(5, 10) == 50) +assert((function(x) + local oops -- split to prevent inlining + function oops() + end + + -- x is checked to be a number here; we can not execute a reentry from oops() because optimizer assumes this holds until return + local y = math.abs(x) + oops() + return y * x +end)("42") == 1764) + local function fuzzfail1(...) repeat _ = nil diff --git a/tests/conformance/native_types.lua b/tests/conformance/native_types.lua index 67779230..c375ab81 100644 --- a/tests/conformance/native_types.lua +++ b/tests/conformance/native_types.lua @@ -68,5 +68,16 @@ ecall(checkuserdata, 2) call(checkvector, vector(1, 2, 3)) ecall(checkvector, 2) +local function mutation_causes_bad_exit(a: number, count: number, sum: number) + repeat + a = 's' + sum += count + pcall(function() end) + count -= 1 + until count == 0 + return sum +end + +assert(call(mutation_causes_bad_exit, 5, 10, 0) == 55) return('OK') diff --git a/tests/main.cpp b/tests/main.cpp index cfa1f9db..9435c61a 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -161,8 +161,10 @@ struct BoostLikeReporter : doctest::IReporter } void log_message(const doctest::MessageData& md) override - { // - printf("%s(%d): ERROR: %s\n", md.m_file, md.m_line, md.m_string.c_str()); + { + const char* severity = (md.m_severity & doctest::assertType::is_warn) ? "WARNING" : "ERROR"; + + printf("%s(%d): %s: %s\n", md.m_file, md.m_line, severity, md.m_string.c_str()); } // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator