From eed18acec8677b380fdbb7f424d79e1c7dae4273 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 2 Dec 2021 15:20:08 -0800 Subject: [PATCH] Sync to upstream/release/506 --- Analysis/include/Luau/FileResolver.h | 23 - Analysis/include/Luau/Module.h | 12 +- Analysis/include/Luau/ToDot.h | 31 ++ Analysis/include/Luau/TxnLog.h | 9 - Analysis/include/Luau/TypeInfer.h | 1 - Analysis/include/Luau/TypeVar.h | 15 - Analysis/include/Luau/Unifier.h | 25 +- Analysis/include/Luau/UnifierSharedState.h | 8 + Analysis/include/Luau/VisitTypeVar.h | 4 +- Analysis/src/Autocomplete.cpp | 72 ++- Analysis/src/BuiltinDefinitions.cpp | 6 +- Analysis/src/Error.cpp | 116 +---- Analysis/src/Frontend.cpp | 27 +- Analysis/src/IostreamHelpers.cpp | 19 +- Analysis/src/JsonEncoder.cpp | 9 +- Analysis/src/Module.cpp | 132 ++--- Analysis/src/Quantify.cpp | 13 +- Analysis/src/RequireTracer.cpp | 195 +------ Analysis/src/Substitution.cpp | 29 +- Analysis/src/ToDot.cpp | 378 ++++++++++++++ Analysis/src/ToString.cpp | 172 +++---- Analysis/src/Transpiler.cpp | 15 +- Analysis/src/TxnLog.cpp | 37 +- Analysis/src/TypeAttach.cpp | 52 +- Analysis/src/TypeInfer.cpp | 331 +++++------- Analysis/src/TypeVar.cpp | 364 -------------- Analysis/src/Unifier.cpp | 306 ++--------- Ast/src/Parser.cpp | 74 +-- CLI/Analyze.cpp | 23 +- CLI/Repl.cpp | 39 -- CLI/Web.cpp | 106 ++++ CMakeLists.txt | 57 ++- Compiler/src/Compiler.cpp | 14 +- Makefile | 6 +- Sources.cmake | 10 + VM/include/lua.h | 11 +- VM/include/luaconf.h | 38 ++ VM/include/lualib.h | 6 + VM/src/lapi.cpp | 67 ++- VM/src/laux.cpp | 95 ++-- VM/src/lbaselib.cpp | 4 +- VM/src/lbitlib.cpp | 2 +- VM/src/lbuiltins.cpp | 12 +- VM/src/lcorolib.cpp | 2 +- VM/src/ldblib.cpp | 2 +- VM/src/ldo.cpp | 71 +-- VM/src/lgc.cpp | 548 +------------------- VM/src/lgcdebug.cpp | 558 +++++++++++++++++++++ VM/src/linit.cpp | 10 +- VM/src/lmathlib.cpp | 5 +- VM/src/lmem.cpp | 8 +- VM/src/lnumutils.h | 8 + VM/src/lobject.cpp | 2 +- VM/src/lobject.h | 23 +- VM/src/loslib.cpp | 2 +- VM/src/lstrlib.cpp | 2 +- VM/src/ltable.cpp | 19 +- VM/src/ltablib.cpp | 2 +- VM/src/lutf8lib.cpp | 2 +- VM/src/lvmexecute.cpp | 30 +- VM/src/lvmload.cpp | 6 +- VM/src/lvmutils.cpp | 18 +- bench/gc/test_LB_mandel.lua | 2 +- bench/tests/shootout/mandel.lua | 2 +- bench/tests/shootout/qt.lua | 10 +- fuzz/proto.cpp | 4 +- tests/AstQuery.test.cpp | 23 + tests/Autocomplete.test.cpp | 40 +- tests/Compiler.test.cpp | 168 +++++++ tests/Conformance.test.cpp | 116 +++-- tests/Fixture.cpp | 38 +- tests/Fixture.h | 5 +- tests/Frontend.test.cpp | 17 - tests/Linter.test.cpp | 16 + tests/Module.test.cpp | 66 ++- tests/Parser.test.cpp | 2 - tests/ToDot.test.cpp | 366 ++++++++++++++ tests/Transpiler.test.cpp | 3 - tests/TypeInfer.aliases.test.cpp | 2 - tests/TypeInfer.generics.test.cpp | 21 +- tests/TypeInfer.provisional.test.cpp | 2 - tests/TypeInfer.tables.test.cpp | 70 +++ tests/TypeInfer.test.cpp | 20 + tests/TypeInfer.typePacks.cpp | 33 -- tests/TypeVar.test.cpp | 45 ++ tests/conformance/apicalls.lua | 8 +- tests/conformance/basic.lua | 5 +- tests/conformance/closure.lua | 8 +- tests/conformance/constructs.lua | 2 +- tests/conformance/coroutine.lua | 2 +- tests/conformance/datetime.lua | 2 +- tests/conformance/debug.lua | 2 +- tests/conformance/errors.lua | 32 +- tests/conformance/gc.lua | 8 +- tests/conformance/nextvar.lua | 6 +- tests/conformance/pcall.lua | 2 +- tests/conformance/utf8.lua | 2 +- tests/conformance/vector.lua | 31 +- tools/svg.py | 9 +- 99 files changed, 2905 insertions(+), 2568 deletions(-) create mode 100644 Analysis/include/Luau/ToDot.h create mode 100644 Analysis/src/ToDot.cpp create mode 100644 CLI/Web.cpp create mode 100644 VM/src/lgcdebug.cpp create mode 100644 tests/ToDot.test.cpp diff --git a/Analysis/include/Luau/FileResolver.h b/Analysis/include/Luau/FileResolver.h index 9b74fc12..0fdcce16 100644 --- a/Analysis/include/Luau/FileResolver.h +++ b/Analysis/include/Luau/FileResolver.h @@ -51,13 +51,6 @@ struct FileResolver { return std::nullopt; } - - // DEPRECATED APIS - // These are going to be removed with LuauNewRequireTrace2 - virtual bool moduleExists(const ModuleName& name) const = 0; - virtual std::optional fromAstFragment(AstExpr* expr) const = 0; - virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0; - virtual std::optional getParentModuleName(const ModuleName& name) const = 0; }; struct NullFileResolver : FileResolver @@ -66,22 +59,6 @@ struct NullFileResolver : FileResolver { return std::nullopt; } - bool moduleExists(const ModuleName& name) const override - { - return false; - } - std::optional fromAstFragment(AstExpr* expr) const override - { - return std::nullopt; - } - ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override - { - return lhs; - } - std::optional getParentModuleName(const ModuleName& name) const override - { - return std::nullopt; - } }; } // namespace Luau diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index d0844835..2e41674b 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -78,9 +78,15 @@ void unfreeze(TypeArena& arena); using SeenTypes = std::unordered_map; using SeenTypePacks = std::unordered_map; -TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr); -TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr); -TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr); +struct CloneState +{ + int recursionCount = 0; + bool encounteredFreeType = false; +}; + +TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState); +TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState); +TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState); struct Module { diff --git a/Analysis/include/Luau/ToDot.h b/Analysis/include/Luau/ToDot.h new file mode 100644 index 00000000..ce518d3a --- /dev/null +++ b/Analysis/include/Luau/ToDot.h @@ -0,0 +1,31 @@ +// 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" + +#include + +namespace Luau +{ +struct TypeVar; +using TypeId = const TypeVar*; + +struct TypePackVar; +using TypePackId = const TypePackVar*; + +struct ToDotOptions +{ + bool showPointers = true; // Show pointer value in the node label + bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes +}; + +std::string toDot(TypeId ty, const ToDotOptions& opts); +std::string toDot(TypePackId tp, const ToDotOptions& opts); + +std::string toDot(TypeId ty); +std::string toDot(TypePackId tp); + +void dumpDot(TypeId ty); +void dumpDot(TypePackId tp); + +} // namespace Luau diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index 322abd19..29988a3b 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -25,15 +25,6 @@ struct TxnLog { } - explicit TxnLog(const std::vector>& ownedSeen) - : originalSeenSize(ownedSeen.size()) - , ownedSeen(ownedSeen) - , sharedSeen(nullptr) - { - // This is deprecated! - LUAU_ASSERT(!FFlag::LuauShareTxnSeen); - } - TxnLog(const TxnLog&) = delete; TxnLog& operator=(const TxnLog&) = delete; diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 78d642c5..9f553bc1 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -297,7 +297,6 @@ private: private: Unifier mkUnifier(const Location& location); - Unifier mkUnifier(const std::vector>& seen, const Location& location); // These functions are only safe to call when we are in the process of typechecking a module. diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index 093ea431..8c4c2f34 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -517,21 +517,6 @@ extern SingletonTypes singletonTypes; void persist(TypeId ty); void persist(TypePackId tp); -struct ToDotOptions -{ - bool showPointers = true; // Show pointer value in the node label - bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes -}; - -std::string toDot(TypeId ty, const ToDotOptions& opts); -std::string toDot(TypePackId tp, const ToDotOptions& opts); - -std::string toDot(TypeId ty); -std::string toDot(TypePackId tp); - -void dumpDot(TypeId ty); -void dumpDot(TypePackId tp); - const TypeLevel* getLevel(TypeId ty); TypeLevel* getMutableLevel(TypeId ty); diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 503034a1..4588cdd8 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -19,12 +19,6 @@ enum Variance Invariant }; -struct UnifierCounters -{ - int recursionCount = 0; - int iterationCount = 0; -}; - struct Unifier { TypeArena* const types; @@ -37,20 +31,11 @@ struct Unifier Variance variance = Covariant; CountMismatch::Context ctx = CountMismatch::Arg; - UnifierCounters* counters; - UnifierCounters countersData; - - std::shared_ptr counters_DEPRECATED; - UnifierSharedState& sharedState; Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState); - Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector>& ownedSeen, const Location& location, - Variance variance, UnifierSharedState& sharedState, const std::shared_ptr& counters_DEPRECATED = nullptr, - UnifierCounters* counters = nullptr); Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector>* sharedSeen, const Location& location, - Variance variance, UnifierSharedState& sharedState, const std::shared_ptr& counters_DEPRECATED = nullptr, - UnifierCounters* counters = nullptr); + Variance variance, UnifierSharedState& sharedState); // Test whether the two type vars unify. Never commits the result. ErrorVec canUnify(TypeId superTy, TypeId subTy); @@ -92,9 +77,9 @@ private: public: // Report an "infinite type error" if the type "needle" already occurs within "haystack" void occursCheck(TypeId needle, TypeId haystack); - void occursCheck(std::unordered_set& seen_DEPRECATED, DenseHashSet& seen, TypeId needle, TypeId haystack); + void occursCheck(DenseHashSet& seen, TypeId needle, TypeId haystack); void occursCheck(TypePackId needle, TypePackId haystack); - void occursCheck(std::unordered_set& seen_DEPRECATED, DenseHashSet& seen, TypePackId needle, TypePackId haystack); + void occursCheck(DenseHashSet& seen, TypePackId needle, TypePackId haystack); Unifier makeChildUnifier(); @@ -106,10 +91,6 @@ private: [[noreturn]] void ice(const std::string& message, const Location& location); [[noreturn]] void ice(const std::string& message); - - // Remove with FFlagLuauCacheUnifyTableResults - DenseHashSet tempSeenTy_DEPRECATED{nullptr}; - DenseHashSet tempSeenTp_DEPRECATED{nullptr}; }; } // namespace Luau diff --git a/Analysis/include/Luau/UnifierSharedState.h b/Analysis/include/Luau/UnifierSharedState.h index f252a004..88997c41 100644 --- a/Analysis/include/Luau/UnifierSharedState.h +++ b/Analysis/include/Luau/UnifierSharedState.h @@ -24,6 +24,12 @@ struct TypeIdPairHash } }; +struct UnifierCounters +{ + int recursionCount = 0; + int iterationCount = 0; +}; + struct UnifierSharedState { UnifierSharedState(InternalErrorReporter* iceHandler) @@ -39,6 +45,8 @@ struct UnifierSharedState DenseHashSet tempSeenTy{nullptr}; DenseHashSet tempSeenTp{nullptr}; + + UnifierCounters counters; }; } // namespace Luau diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index a866655c..740854b3 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -5,8 +5,6 @@ #include "Luau/TypeVar.h" #include "Luau/TypePack.h" -LUAU_FASTFLAG(LuauCacheUnifyTableResults) - namespace Luau { @@ -101,7 +99,7 @@ void visit(TypeId ty, F& f, Set& seen) // Some visitors want to see bound tables, that's why we visit the original type if (apply(ty, *ttv, seen, f)) { - if (FFlag::LuauCacheUnifyTableResults && ttv->boundTo) + if (ttv->boundTo) { visit(*ttv->boundTo, f, seen); } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 6fc0b3f8..db2d1d0e 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -12,9 +12,9 @@ #include #include -LUAU_FASTFLAGVARIABLE(ElseElseIfCompletionImprovements, false); LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport) LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); +LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false); static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -203,8 +203,9 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ { SeenTypes seenTypes; SeenTypePacks seenTypePacks; - expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, nullptr); - actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, nullptr); + CloneState cloneState; + expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState); + actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState); auto errors = unifier.canUnify(expectedType, actualType); return errors.empty(); @@ -229,28 +230,51 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ TypeId expectedType = follow(*it); - if (canUnify(expectedType, ty)) - return TypeCorrectKind::Correct; - - // We also want to suggest functions that return compatible result - const FunctionTypeVar* ftv = get(ty); - - if (!ftv) - return TypeCorrectKind::None; - - auto [retHead, retTail] = flatten(ftv->retType); - - if (!retHead.empty()) - return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None; - - // We might only have a variadic tail pack, check if the element is compatible - if (retTail) + if (FFlag::LuauAutocompletePreferToCallFunctions) { - if (const VariadicTypePack* vtp = get(follow(*retTail))) - return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None; - } + // We also want to suggest functions that return compatible result + if (const FunctionTypeVar* ftv = get(ty)) + { + auto [retHead, retTail] = flatten(ftv->retType); - return TypeCorrectKind::None; + if (!retHead.empty() && canUnify(expectedType, retHead.front())) + return TypeCorrectKind::CorrectFunctionResult; + + // We might only have a variadic tail pack, check if the element is compatible + if (retTail) + { + if (const VariadicTypePack* vtp = get(follow(*retTail)); vtp && canUnify(expectedType, vtp->ty)) + return TypeCorrectKind::CorrectFunctionResult; + } + } + + return canUnify(expectedType, ty) ? TypeCorrectKind::Correct : TypeCorrectKind::None; + } + else + { + if (canUnify(expectedType, ty)) + return TypeCorrectKind::Correct; + + // We also want to suggest functions that return compatible result + const FunctionTypeVar* ftv = get(ty); + + if (!ftv) + return TypeCorrectKind::None; + + auto [retHead, retTail] = flatten(ftv->retType); + + if (!retHead.empty()) + return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None; + + // We might only have a variadic tail pack, check if the element is compatible + if (retTail) + { + if (const VariadicTypePack* vtp = get(follow(*retTail))) + return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None; + } + + return TypeCorrectKind::None; + } } enum class PropIndexType @@ -1413,7 +1437,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M else if (AstStatWhile* statWhile = extractStat(finder.ancestry); statWhile && !statWhile->hasDo) return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; - else if (AstStatIf* statIf = node->as(); FFlag::ElseElseIfCompletionImprovements && statIf && !statIf->hasElse) + else if (AstStatIf* statIf = node->as(); statIf && !statIf->hasElse) { return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 62a06a3c..bac94a2b 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -8,8 +8,6 @@ #include -LUAU_FASTFLAG(LuauNewRequireTrace2) - /** FIXME: Many of these type definitions are not quite completely accurate. * * Some of them require richer generics than we have. For instance, we do not yet have a way to talk @@ -473,9 +471,7 @@ static std::optional> magicFunctionRequire( if (!checkRequirePath(typechecker, expr.args.data[0])) return std::nullopt; - const AstExpr* require = FFlag::LuauNewRequireTrace2 ? &expr : expr.args.data[0]; - - if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require)) + if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr)) return ExprResult{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})}; return std::nullopt; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index f80d50a7..8334bd62 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -7,57 +7,14 @@ #include -LUAU_FASTFLAG(LuauTypeAliasPacks) - -static std::string wrongNumberOfArgsString_DEPRECATED(size_t expectedCount, size_t actualCount, bool isTypeArgs = false) -{ - std::string s = "expects " + std::to_string(expectedCount) + " "; - - if (isTypeArgs) - s += "type "; - - s += "argument"; - if (expectedCount != 1) - s += "s"; - - s += ", but "; - - if (actualCount == 0) - { - s += "none"; - } - else - { - if (actualCount < expectedCount) - s += "only "; - - s += std::to_string(actualCount); - } - - s += (actualCount == 1) ? " is" : " are"; - - s += " specified"; - - return s; -} - static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) { - std::string s; + std::string s = "expects "; - if (FFlag::LuauTypeAliasPacks) - { - s = "expects "; + if (isVariadic) + s += "at least "; - if (isVariadic) - s += "at least "; - - s += std::to_string(expectedCount) + " "; - } - else - { - s = "expects " + std::to_string(expectedCount) + " "; - } + s += std::to_string(expectedCount) + " "; if (argPrefix) s += std::string(argPrefix) + " "; @@ -188,10 +145,7 @@ struct ErrorConverter return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) + " are required here"; case CountMismatch::Arg: - if (FFlag::LuauTypeAliasPacks) - return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual); - else - return "Argument count mismatch. Function " + wrongNumberOfArgsString_DEPRECATED(e.expected, e.actual); + return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual); } LUAU_ASSERT(!"Unknown context"); @@ -232,7 +186,7 @@ struct ErrorConverter std::string operator()(const Luau::IncorrectGenericParameterCount& e) const { std::string name = e.name; - if (!e.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !e.typeFun.typePackParams.empty())) + if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty()) { name += "<"; bool first = true; @@ -246,36 +200,25 @@ struct ErrorConverter name += toString(t); } - if (FFlag::LuauTypeAliasPacks) + for (TypePackId t : e.typeFun.typePackParams) { - for (TypePackId t : e.typeFun.typePackParams) - { - if (first) - first = false; - else - name += ", "; + if (first) + first = false; + else + name += ", "; - name += toString(t); - } + name += toString(t); } name += ">"; } - if (FFlag::LuauTypeAliasPacks) - { - if (e.typeFun.typeParams.size() != e.actualParameters) - return "Generic type '" + name + "' " + - wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty()); + if (e.typeFun.typeParams.size() != e.actualParameters) + return "Generic type '" + name + "' " + + wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty()); - return "Generic type '" + name + "' " + - wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false); - } - else - { - return "Generic type '" + name + "' " + - wrongNumberOfArgsString_DEPRECATED(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true); - } + return "Generic type '" + name + "' " + + wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false); } std::string operator()(const Luau::SyntaxError& e) const @@ -591,11 +534,8 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size()) return false; - if (FFlag::LuauTypeAliasPacks) - { - if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size()) - return false; - } + if (typeFun.typePackParams.size() != rhs.typeFun.typePackParams.size()) + return false; for (size_t i = 0; i < typeFun.typeParams.size(); ++i) { @@ -603,13 +543,10 @@ bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterC return false; } - if (FFlag::LuauTypeAliasPacks) + for (size_t i = 0; i < typeFun.typePackParams.size(); ++i) { - for (size_t i = 0; i < typeFun.typePackParams.size(); ++i) - { - if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i]) - return false; - } + if (typeFun.typePackParams[i] != rhs.typeFun.typePackParams[i]) + return false; } return true; @@ -733,14 +670,14 @@ bool containsParseErrorName(const TypeError& error) } template -void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) +void copyError(T& e, TypeArena& destArena, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState cloneState) { auto clone = [&](auto&& ty) { - return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks); + return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks, cloneState); }; auto visitErrorData = [&](auto&& e) { - copyError(e, destArena, seenTypes, seenTypePacks); + copyError(e, destArena, seenTypes, seenTypePacks, cloneState); }; if constexpr (false) @@ -864,9 +801,10 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena) { SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; auto visitErrorData = [&](auto&& e) { - copyError(e, destArena, seenTypes, seenTypePacks); + copyError(e, destArena, seenTypes, seenTypePacks, cloneState); }; LUAU_ASSERT(!destArena.typeVars.isFrozen()); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 1e97705d..e332f07d 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -18,10 +18,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) -LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false) -LUAU_FASTFLAG(LuauTraceRequireLookupChild) LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false) -LUAU_FASTFLAG(LuauNewRequireTrace2) namespace Luau { @@ -96,10 +93,11 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; for (const auto& [name, ty] : checkedModule->declaredGlobals) { - TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks); + TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState); std::string documentationSymbol = packageName + "/global/" + name; generateDocumentationSymbols(globalTy, documentationSymbol); targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol}; @@ -110,7 +108,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings) { - TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks); + TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks, cloneState); std::string documentationSymbol = packageName + "/globaltype/" + name; generateDocumentationSymbols(globalTy.type, documentationSymbol); targetScope->exportedTypeBindings[name] = globalTy; @@ -427,15 +425,16 @@ CheckResult Frontend::check(const ModuleName& name) SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; for (const auto& [expr, strictTy] : strictModule->astTypes) - module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks); + module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState); for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes) - module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks); + module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState); for (const auto& [expr, strictTy] : strictModule->astExpectedTypes) - module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks); + module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState); } stats.timeCheck += getTimestamp() - timestamp; @@ -885,16 +884,13 @@ std::optional FrontendModuleResolver::resolveModuleInfo(const Module // If we can't find the current module name, that's because we bypassed the frontend's initializer // and called typeChecker.check directly. (This is done by autocompleteSource, for example). // In that case, requires will always fail. - if (FFlag::LuauResolveModuleNameWithoutACurrentModule) - return std::nullopt; - else - throw std::runtime_error("Frontend::resolveModuleName: Unknown currentModuleName '" + currentModuleName + "'"); + return std::nullopt; } const auto& exprs = it->second.exprs; const ModuleInfo* info = exprs.find(&pathExpr); - if (!info || (!FFlag::LuauNewRequireTrace2 && info->name.empty())) + if (!info) return std::nullopt; return *info; @@ -911,10 +907,7 @@ const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName) bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const { - if (FFlag::LuauNewRequireTrace2) - return frontend->sourceNodes.count(moduleName) != 0; - else - return frontend->fileResolver->moduleExists(moduleName); + return frontend->sourceNodes.count(moduleName) != 0; } std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 3b267121..ac46b5a4 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -2,8 +2,6 @@ #include "Luau/IostreamHelpers.h" #include "Luau/ToString.h" -LUAU_FASTFLAG(LuauTypeAliasPacks) - namespace Luau { @@ -94,7 +92,7 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo { stream << "IncorrectGenericParameterCount { name = " << error.name; - if (!error.typeFun.typeParams.empty() || (FFlag::LuauTypeAliasPacks && !error.typeFun.typePackParams.empty())) + if (!error.typeFun.typeParams.empty() || !error.typeFun.typePackParams.empty()) { stream << "<"; bool first = true; @@ -108,17 +106,14 @@ std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCo stream << toString(t); } - if (FFlag::LuauTypeAliasPacks) + for (TypePackId t : error.typeFun.typePackParams) { - for (TypePackId t : error.typeFun.typePackParams) - { - if (first) - first = false; - else - stream << ", "; + if (first) + first = false; + else + stream << ", "; - stream << toString(t); - } + stream << toString(t); } stream << ">"; diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/JsonEncoder.cpp index 064accba..c7f623ee 100644 --- a/Analysis/src/JsonEncoder.cpp +++ b/Analysis/src/JsonEncoder.cpp @@ -5,8 +5,6 @@ #include "Luau/StringUtils.h" #include "Luau/Common.h" -LUAU_FASTFLAG(LuauTypeAliasPacks) - namespace Luau { @@ -615,12 +613,7 @@ struct AstJsonEncoder : public AstVisitor writeNode(node, "AstStatTypeAlias", [&]() { PROP(name); PROP(generics); - - if (FFlag::LuauTypeAliasPacks) - { - PROP(genericPacks); - } - + PROP(genericPacks); PROP(type); PROP(exported); }); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 32a0646a..b4b6eb42 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -1,20 +1,20 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Module.h" +#include "Luau/Common.h" +#include "Luau/RecursionCounter.h" #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" #include "Luau/TypeVar.h" #include "Luau/VisitTypeVar.h" -#include "Luau/Common.h" #include LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans) -LUAU_FASTFLAG(LuauTypeAliasPacks) -LUAU_FASTFLAGVARIABLE(LuauCloneBoundTables, false) +LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0) namespace Luau { @@ -120,12 +120,6 @@ TypePackId TypeArena::addTypePack(TypePackVar tp) return allocated; } -using SeenTypes = std::unordered_map; -using SeenTypePacks = std::unordered_map; - -TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType); -TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType); - namespace { @@ -138,11 +132,12 @@ struct TypePackCloner; struct TypeCloner { - TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) + TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState) : dest(dest) , typeId(typeId) , seenTypes(seenTypes) , seenTypePacks(seenTypePacks) + , cloneState(cloneState) { } @@ -150,8 +145,7 @@ struct TypeCloner TypeId typeId; SeenTypes& seenTypes; SeenTypePacks& seenTypePacks; - - bool* encounteredFreeType = nullptr; + CloneState& cloneState; template void defaultClone(const T& t); @@ -178,13 +172,14 @@ struct TypePackCloner TypePackId typePackId; SeenTypes& seenTypes; SeenTypePacks& seenTypePacks; - bool* encounteredFreeType = nullptr; + CloneState& cloneState; - TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks) + TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState) : dest(dest) , typePackId(typePackId) , seenTypes(seenTypes) , seenTypePacks(seenTypePacks) + , cloneState(cloneState) { } @@ -197,8 +192,7 @@ struct TypePackCloner void operator()(const Unifiable::Free& t) { - if (encounteredFreeType) - *encounteredFreeType = true; + cloneState.encounteredFreeType = true; TypePackId err = singletonTypes.errorRecoveryTypePack(singletonTypes.anyTypePack); TypePackId cloned = dest.addTypePack(*err); @@ -218,13 +212,13 @@ struct TypePackCloner // We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer. void operator()(const Unifiable::Bound& t) { - TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); + TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState); seenTypePacks[typePackId] = cloned; } void operator()(const VariadicTypePack& t) { - TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)}}); + TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, cloneState)}}); seenTypePacks[typePackId] = cloned; } @@ -236,10 +230,10 @@ struct TypePackCloner seenTypePacks[typePackId] = cloned; for (TypeId ty : t.head) - destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); + destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); if (t.tail) - destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType); + destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, cloneState); } }; @@ -252,8 +246,7 @@ void TypeCloner::defaultClone(const T& t) void TypeCloner::operator()(const Unifiable::Free& t) { - if (encounteredFreeType) - *encounteredFreeType = true; + cloneState.encounteredFreeType = true; TypeId err = singletonTypes.errorRecoveryType(singletonTypes.anyType); TypeId cloned = dest.addType(*err); seenTypes[typeId] = cloned; @@ -266,7 +259,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t) void TypeCloner::operator()(const Unifiable::Bound& t) { - TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); + TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, cloneState); seenTypes[typeId] = boundTo; } @@ -294,23 +287,23 @@ void TypeCloner::operator()(const FunctionTypeVar& t) seenTypes[typeId] = result; for (TypeId generic : t.generics) - ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType)); + ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, cloneState)); for (TypePackId genericPack : t.genericPacks) - ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType)); + ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, cloneState)); ftv->tags = t.tags; - ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType); + ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, cloneState); ftv->argNames = t.argNames; - ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType); + ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, cloneState); } void TypeCloner::operator()(const TableTypeVar& t) { // If table is now bound to another one, we ignore the content of the original - if (FFlag::LuauCloneBoundTables && t.boundTo) + if (t.boundTo) { - TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); + TypeId boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, cloneState); seenTypes[typeId] = boundTo; return; } @@ -326,34 +319,21 @@ void TypeCloner::operator()(const TableTypeVar& t) ttv->level = TypeLevel{0, 0}; for (const auto& [name, prop] : t.props) - ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags}; + ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags}; if (t.indexer) - ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType), - clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)}; - - if (!FFlag::LuauCloneBoundTables) - { - if (t.boundTo) - ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType); - } + ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, cloneState), + clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, cloneState)}; for (TypeId& arg : ttv->instantiatedTypeParams) - arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType); + arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState); - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId& arg : ttv->instantiatedTypePackParams) - arg = clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType); - } + for (TypePackId& arg : ttv->instantiatedTypePackParams) + arg = clone(arg, dest, seenTypes, seenTypePacks, cloneState); if (ttv->state == TableState::Free) { - if (FFlag::LuauCloneBoundTables || !t.boundTo) - { - if (encounteredFreeType) - *encounteredFreeType = true; - } + cloneState.encounteredFreeType = true; ttv->state = TableState::Sealed; } @@ -369,8 +349,8 @@ void TypeCloner::operator()(const MetatableTypeVar& t) MetatableTypeVar* mtv = getMutable(result); seenTypes[typeId] = result; - mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType); - mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType); + mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, cloneState); + mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, cloneState); } void TypeCloner::operator()(const ClassTypeVar& t) @@ -381,13 +361,13 @@ void TypeCloner::operator()(const ClassTypeVar& t) seenTypes[typeId] = result; for (const auto& [name, prop] : t.props) - ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags}; + ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, cloneState), prop.deprecated, {}, prop.location, prop.tags}; if (t.parent) - ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType); + ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, cloneState); if (t.metatable) - ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType); + ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, cloneState); } void TypeCloner::operator()(const AnyTypeVar& t) @@ -404,7 +384,7 @@ void TypeCloner::operator()(const UnionTypeVar& t) LUAU_ASSERT(option != nullptr); for (TypeId ty : t.options) - option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); + option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); } void TypeCloner::operator()(const IntersectionTypeVar& t) @@ -416,7 +396,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t) LUAU_ASSERT(option != nullptr); for (TypeId ty : t.parts) - option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); + option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); } void TypeCloner::operator()(const LazyTypeVar& t) @@ -426,17 +406,18 @@ void TypeCloner::operator()(const LazyTypeVar& t) } // anonymous namespace -TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) +TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState) { if (tp->persistent) return tp; + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); + TypePackId& res = seenTypePacks[tp]; if (res == nullptr) { - TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks}; - cloner.encounteredFreeType = encounteredFreeType; + TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks, cloneState}; Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. } @@ -446,17 +427,18 @@ TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypeP return res; } -TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) +TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState) { if (typeId->persistent) return typeId; + RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); + TypeId& res = seenTypes[typeId]; if (res == nullptr) { - TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks}; - cloner.encounteredFreeType = encounteredFreeType; + TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState}; Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. asMutable(res)->documentationSymbol = typeId->documentationSymbol; } @@ -467,19 +449,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks return res; } -TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType) +TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, CloneState& cloneState) { TypeFun result; for (TypeId ty : typeFun.typeParams) - result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType)); + result.typeParams.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId tp : typeFun.typePackParams) - result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, encounteredFreeType)); - } + for (TypePackId tp : typeFun.typePackParams) + result.typePackParams.push_back(clone(tp, dest, seenTypes, seenTypePacks, cloneState)); - result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType); + result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, cloneState); return result; } @@ -519,19 +498,18 @@ bool Module::clonePublicInterface() LUAU_ASSERT(interfaceTypes.typeVars.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty()); - bool encounteredFreeType = false; - - SeenTypePacks seenTypePacks; SeenTypes seenTypes; + SeenTypePacks seenTypePacks; + CloneState cloneState; ScopePtr moduleScope = getModuleScope(); - moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); + moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, cloneState); if (moduleScope->varargPack) - moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); + moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, cloneState); for (auto& pair : moduleScope->exportedTypeBindings) - pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType); + pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, cloneState); for (TypeId ty : moduleScope->returnType) if (get(follow(ty))) @@ -540,7 +518,7 @@ bool Module::clonePublicInterface() freeze(internalTypes); freeze(interfaceTypes); - return encounteredFreeType; + return cloneState.encounteredFreeType; } } // namespace Luau diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index bf6d81aa..c773e208 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -4,6 +4,8 @@ #include "Luau/VisitTypeVar.h" +LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false) + namespace Luau { @@ -79,7 +81,16 @@ struct Quantifier void quantify(ModulePtr module, TypeId ty, TypeLevel level) { Quantifier q{std::move(module), level}; - visitTypeVar(ty, q); + + if (FFlag::LuauQuantifyVisitOnce) + { + DenseHashSet seen{nullptr}; + visitTypeVarOnce(ty, q, seen); + } + else + { + visitTypeVar(ty, q); + } FunctionTypeVar* ftv = getMutable(ty); LUAU_ASSERT(ftv); diff --git a/Analysis/src/RequireTracer.cpp b/Analysis/src/RequireTracer.cpp index b72f53f9..8ed245fb 100644 --- a/Analysis/src/RequireTracer.cpp +++ b/Analysis/src/RequireTracer.cpp @@ -4,182 +4,9 @@ #include "Luau/Ast.h" #include "Luau/Module.h" -LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false) -LUAU_FASTFLAGVARIABLE(LuauNewRequireTrace2, false) - namespace Luau { -namespace -{ - -struct RequireTracerOld : AstVisitor -{ - explicit RequireTracerOld(FileResolver* fileResolver, const ModuleName& currentModuleName) - : fileResolver(fileResolver) - , currentModuleName(currentModuleName) - { - LUAU_ASSERT(!FFlag::LuauNewRequireTrace2); - } - - FileResolver* const fileResolver; - ModuleName currentModuleName; - DenseHashMap locals{nullptr}; - RequireTraceResult result; - - std::optional fromAstFragment(AstExpr* expr) - { - if (auto g = expr->as(); g && g->name == "script") - return currentModuleName; - - return fileResolver->fromAstFragment(expr); - } - - bool visit(AstStatLocal* stat) override - { - for (size_t i = 0; i < stat->vars.size; ++i) - { - AstLocal* local = stat->vars.data[i]; - - if (local->annotation) - { - if (AstTypeTypeof* ann = local->annotation->as()) - ann->expr->visit(this); - } - - if (i < stat->values.size) - { - AstExpr* expr = stat->values.data[i]; - expr->visit(this); - - const ModuleInfo* info = result.exprs.find(expr); - if (info) - locals[local] = info->name; - } - } - - return false; - } - - bool visit(AstExprGlobal* global) override - { - std::optional name = fromAstFragment(global); - if (name) - result.exprs[global] = {*name}; - - return false; - } - - bool visit(AstExprLocal* local) override - { - const ModuleName* name = locals.find(local->local); - if (name) - result.exprs[local] = {*name}; - - return false; - } - - bool visit(AstExprIndexName* indexName) override - { - indexName->expr->visit(this); - - const ModuleInfo* info = result.exprs.find(indexName->expr); - if (info) - { - if (indexName->index == "parent" || indexName->index == "Parent") - { - if (auto parent = fileResolver->getParentModuleName(info->name)) - result.exprs[indexName] = {*parent}; - } - else - result.exprs[indexName] = {fileResolver->concat(info->name, indexName->index.value)}; - } - - return false; - } - - bool visit(AstExprIndexExpr* indexExpr) override - { - indexExpr->expr->visit(this); - - const ModuleInfo* info = result.exprs.find(indexExpr->expr); - const AstExprConstantString* str = indexExpr->index->as(); - if (info && str) - { - result.exprs[indexExpr] = {fileResolver->concat(info->name, std::string_view(str->value.data, str->value.size))}; - } - - indexExpr->index->visit(this); - - return false; - } - - bool visit(AstExprTypeAssertion* expr) override - { - return false; - } - - // If we see game:GetService("StringLiteral") or Game:GetService("StringLiteral"), then rewrite to game.StringLiteral. - // Else traverse arguments and trace requires to them. - bool visit(AstExprCall* call) override - { - for (AstExpr* arg : call->args) - arg->visit(this); - - call->func->visit(this); - - AstExprGlobal* globalName = call->func->as(); - if (globalName && globalName->name == "require" && call->args.size >= 1) - { - if (const ModuleInfo* moduleInfo = result.exprs.find(call->args.data[0])) - result.requires.push_back({moduleInfo->name, call->location}); - - return false; - } - - AstExprIndexName* indexName = call->func->as(); - if (!indexName) - return false; - - std::optional rootName = fromAstFragment(indexName->expr); - - if (FFlag::LuauTraceRequireLookupChild && !rootName) - { - if (const ModuleInfo* moduleInfo = result.exprs.find(indexName->expr)) - rootName = moduleInfo->name; - } - - if (!rootName) - return false; - - bool supportedLookup = indexName->index == "GetService" || - (FFlag::LuauTraceRequireLookupChild && (indexName->index == "FindFirstChild" || indexName->index == "WaitForChild")); - - if (!supportedLookup) - return false; - - if (call->args.size != 1) - return false; - - AstExprConstantString* name = call->args.data[0]->as(); - if (!name) - return false; - - std::string_view v{name->value.data, name->value.size}; - if (v.end() != std::find(v.begin(), v.end(), '/')) - return false; - - result.exprs[call] = {fileResolver->concat(*rootName, v)}; - - // 'WaitForChild' can be used on modules that are not available at the typecheck time, but will be available at runtime - // If we fail to find such module, we will not report an UnknownRequire error - if (FFlag::LuauTraceRequireLookupChild && indexName->index == "WaitForChild") - result.exprs[call].optional = true; - - return false; - } -}; - struct RequireTracer : AstVisitor { RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName) @@ -188,7 +15,6 @@ struct RequireTracer : AstVisitor , currentModuleName(currentModuleName) , locals(nullptr) { - LUAU_ASSERT(FFlag::LuauNewRequireTrace2); } bool visit(AstExprTypeAssertion* expr) override @@ -328,24 +154,13 @@ struct RequireTracer : AstVisitor std::vector requires; }; -} // anonymous namespace - RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName) { - if (FFlag::LuauNewRequireTrace2) - { - RequireTraceResult result; - RequireTracer tracer{result, fileResolver, currentModuleName}; - root->visit(&tracer); - tracer.process(); - return result; - } - else - { - RequireTracerOld tracer{fileResolver, currentModuleName}; - root->visit(&tracer); - return tracer.result; - } + RequireTraceResult result; + RequireTracer tracer{result, fileResolver, currentModuleName}; + root->visit(&tracer); + tracer.process(); + return result; } } // namespace Luau diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index ca2b30f5..3d004bee 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -7,8 +7,6 @@ #include LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 1000) -LUAU_FASTFLAGVARIABLE(LuauSubstitutionDontReplaceIgnoredTypes, false) -LUAU_FASTFLAG(LuauTypeAliasPacks) namespace Luau { @@ -39,11 +37,8 @@ void Tarjan::visitChildren(TypeId ty, int index) for (TypeId itp : ttv->instantiatedTypeParams) visitChild(itp); - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId itp : ttv->instantiatedTypePackParams) - visitChild(itp); - } + for (TypePackId itp : ttv->instantiatedTypePackParams) + visitChild(itp); } else if (const MetatableTypeVar* mtv = get(ty)) { @@ -339,10 +334,10 @@ std::optional Substitution::substitute(TypeId ty) return std::nullopt; for (auto [oldTy, newTy] : newTypes) - if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy)) + if (!ignoreChildren(oldTy)) replaceChildren(newTy); for (auto [oldTp, newTp] : newPacks) - if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp)) + if (!ignoreChildren(oldTp)) replaceChildren(newTp); TypeId newTy = replace(ty); return newTy; @@ -359,10 +354,10 @@ std::optional Substitution::substitute(TypePackId tp) return std::nullopt; for (auto [oldTy, newTy] : newTypes) - if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTy)) + if (!ignoreChildren(oldTy)) replaceChildren(newTy); for (auto [oldTp, newTp] : newPacks) - if (!FFlag::LuauSubstitutionDontReplaceIgnoredTypes || !ignoreChildren(oldTp)) + if (!ignoreChildren(oldTp)) replaceChildren(newTp); TypePackId newTp = replace(tp); return newTp; @@ -393,10 +388,7 @@ TypeId Substitution::clone(TypeId ty) clone.name = ttv->name; clone.syntheticName = ttv->syntheticName; clone.instantiatedTypeParams = ttv->instantiatedTypeParams; - - if (FFlag::LuauTypeAliasPacks) - clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams; - + clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams; clone.tags = ttv->tags; result = addType(std::move(clone)); } @@ -505,11 +497,8 @@ void Substitution::replaceChildren(TypeId ty) for (TypeId& itp : ttv->instantiatedTypeParams) itp = replace(itp); - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId& itp : ttv->instantiatedTypePackParams) - itp = replace(itp); - } + for (TypePackId& itp : ttv->instantiatedTypePackParams) + itp = replace(itp); } else if (MetatableTypeVar* mtv = getMutable(ty)) { diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp new file mode 100644 index 00000000..df9d4188 --- /dev/null +++ b/Analysis/src/ToDot.cpp @@ -0,0 +1,378 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/ToDot.h" + +#include "Luau/ToString.h" +#include "Luau/TypePack.h" +#include "Luau/TypeVar.h" +#include "Luau/StringUtils.h" + +#include +#include + +namespace Luau +{ + +namespace +{ + +struct StateDot +{ + StateDot(ToDotOptions opts) + : opts(opts) + { + } + + ToDotOptions opts; + + std::unordered_set seenTy; + std::unordered_set seenTp; + std::unordered_map tyToIndex; + std::unordered_map tpToIndex; + int nextIndex = 1; + std::string result; + + bool canDuplicatePrimitive(TypeId ty); + + void visitChildren(TypeId ty, int index); + void visitChildren(TypePackId ty, int index); + + void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr); + void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr); + + void startNode(int index); + void finishNode(); + + void startNodeLabel(); + void finishNodeLabel(TypeId ty); + void finishNodeLabel(TypePackId tp); +}; + +bool StateDot::canDuplicatePrimitive(TypeId ty) +{ + if (get(ty)) + return false; + + return get(ty) || get(ty); +} + +void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName) +{ + if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty))) + tyToIndex[ty] = nextIndex++; + + int index = tyToIndex[ty]; + + if (parentIndex != 0) + { + if (linkName) + formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName); + else + formatAppend(result, "n%d -> n%d;\n", parentIndex, index); + } + + if (opts.duplicatePrimitives && canDuplicatePrimitive(ty)) + { + if (get(ty)) + formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str()); + else if (get(ty)) + formatAppend(result, "n%d [label=\"any\"];\n", index); + } + else + { + visitChildren(ty, index); + } +} + +void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName) +{ + if (!tpToIndex.count(tp)) + tpToIndex[tp] = nextIndex++; + + if (parentIndex != 0) + { + if (linkName) + formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName); + else + formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]); + } + + visitChildren(tp, tpToIndex[tp]); +} + +void StateDot::startNode(int index) +{ + formatAppend(result, "n%d [", index); +} + +void StateDot::finishNode() +{ + formatAppend(result, "];\n"); +} + +void StateDot::startNodeLabel() +{ + formatAppend(result, "label=\""); +} + +void StateDot::finishNodeLabel(TypeId ty) +{ + if (opts.showPointers) + formatAppend(result, "\n0x%p", ty); + // additional common attributes can be added here as well + result += "\""; +} + +void StateDot::finishNodeLabel(TypePackId tp) +{ + if (opts.showPointers) + formatAppend(result, "\n0x%p", tp); + // additional common attributes can be added here as well + result += "\""; +} + +void StateDot::visitChildren(TypeId ty, int index) +{ + if (seenTy.count(ty)) + return; + seenTy.insert(ty); + + startNode(index); + startNodeLabel(); + + if (const BoundTypeVar* btv = get(ty)) + { + formatAppend(result, "BoundTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(btv->boundTo, index); + } + else if (const FunctionTypeVar* ftv = get(ty)) + { + formatAppend(result, "FunctionTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(ftv->argTypes, index, "arg"); + visitChild(ftv->retType, index, "ret"); + } + else if (const TableTypeVar* ttv = get(ty)) + { + if (ttv->name) + formatAppend(result, "TableTypeVar %s", ttv->name->c_str()); + else if (ttv->syntheticName) + formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str()); + else + formatAppend(result, "TableTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + if (ttv->boundTo) + return visitChild(*ttv->boundTo, index, "boundTo"); + + for (const auto& [name, prop] : ttv->props) + visitChild(prop.type, index, name.c_str()); + if (ttv->indexer) + { + visitChild(ttv->indexer->indexType, index, "[index]"); + visitChild(ttv->indexer->indexResultType, index, "[value]"); + } + for (TypeId itp : ttv->instantiatedTypeParams) + visitChild(itp, index, "typeParam"); + + for (TypePackId itp : ttv->instantiatedTypePackParams) + visitChild(itp, index, "typePackParam"); + } + else if (const MetatableTypeVar* mtv = get(ty)) + { + formatAppend(result, "MetatableTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + visitChild(mtv->table, index, "table"); + visitChild(mtv->metatable, index, "metatable"); + } + else if (const UnionTypeVar* utv = get(ty)) + { + formatAppend(result, "UnionTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + for (TypeId opt : utv->options) + visitChild(opt, index); + } + else if (const IntersectionTypeVar* itv = get(ty)) + { + formatAppend(result, "IntersectionTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + + for (TypeId part : itv->parts) + visitChild(part, index); + } + else if (const GenericTypeVar* gtv = get(ty)) + { + if (gtv->explicitName) + formatAppend(result, "GenericTypeVar %s", gtv->name.c_str()); + else + formatAppend(result, "GenericTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if (const FreeTypeVar* ftv = get(ty)) + { + formatAppend(result, "FreeTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if (get(ty)) + { + formatAppend(result, "AnyTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if (get(ty)) + { + formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str()); + finishNodeLabel(ty); + finishNode(); + } + else if (get(ty)) + { + formatAppend(result, "ErrorTypeVar %d", index); + finishNodeLabel(ty); + finishNode(); + } + else if (const ClassTypeVar* ctv = get(ty)) + { + formatAppend(result, "ClassTypeVar %s", ctv->name.c_str()); + finishNodeLabel(ty); + finishNode(); + + for (const auto& [name, prop] : ctv->props) + visitChild(prop.type, index, name.c_str()); + + if (ctv->parent) + visitChild(*ctv->parent, index, "[parent]"); + + if (ctv->metatable) + visitChild(*ctv->metatable, index, "[metatable]"); + } + else + { + LUAU_ASSERT(!"unknown type kind"); + finishNodeLabel(ty); + finishNode(); + } +} + +void StateDot::visitChildren(TypePackId tp, int index) +{ + if (seenTp.count(tp)) + return; + seenTp.insert(tp); + + startNode(index); + startNodeLabel(); + + if (const BoundTypePack* btp = get(tp)) + { + formatAppend(result, "BoundTypePack %d", index); + finishNodeLabel(tp); + finishNode(); + + visitChild(btp->boundTo, index); + } + else if (const TypePack* tpp = get(tp)) + { + formatAppend(result, "TypePack %d", index); + finishNodeLabel(tp); + finishNode(); + + for (TypeId tv : tpp->head) + visitChild(tv, index); + if (tpp->tail) + visitChild(*tpp->tail, index, "tail"); + } + else if (const VariadicTypePack* vtp = get(tp)) + { + formatAppend(result, "VariadicTypePack %d", index); + finishNodeLabel(tp); + finishNode(); + + visitChild(vtp->ty, index); + } + else if (const FreeTypePack* ftp = get(tp)) + { + formatAppend(result, "FreeTypePack %d", index); + finishNodeLabel(tp); + finishNode(); + } + else if (const GenericTypePack* gtp = get(tp)) + { + if (gtp->explicitName) + formatAppend(result, "GenericTypePack %s", gtp->name.c_str()); + else + formatAppend(result, "GenericTypePack %d", index); + finishNodeLabel(tp); + finishNode(); + } + else if (get(tp)) + { + formatAppend(result, "ErrorTypePack %d", index); + finishNodeLabel(tp); + finishNode(); + } + else + { + LUAU_ASSERT(!"unknown type pack kind"); + finishNodeLabel(tp); + finishNode(); + } +} + +} // namespace + +std::string toDot(TypeId ty, const ToDotOptions& opts) +{ + StateDot state{opts}; + + state.result = "digraph graphname {\n"; + state.visitChild(ty, 0); + state.result += "}"; + + return state.result; +} + +std::string toDot(TypePackId tp, const ToDotOptions& opts) +{ + StateDot state{opts}; + + state.result = "digraph graphname {\n"; + state.visitChild(tp, 0); + state.result += "}"; + + return state.result; +} + +std::string toDot(TypeId ty) +{ + return toDot(ty, {}); +} + +std::string toDot(TypePackId tp) +{ + return toDot(tp, {}); +} + +void dumpDot(TypeId ty) +{ + printf("%s\n", toDot(ty).c_str()); +} + +void dumpDot(TypePackId tp) +{ + printf("%s\n", toDot(tp).c_str()); +} + +} // namespace Luau diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 735bfa50..6322096c 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -11,7 +11,7 @@ #include LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions) -LUAU_FASTFLAG(LuauTypeAliasPacks) +LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false) namespace Luau { @@ -59,11 +59,8 @@ struct FindCyclicTypes for (TypeId itp : ttv.instantiatedTypeParams) visitTypeVar(itp, *this, seen); - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId itp : ttv.instantiatedTypePackParams) - visitTypeVar(itp, *this, seen); - } + for (TypePackId itp : ttv.instantiatedTypePackParams) + visitTypeVar(itp, *this, seen); return exhaustive; } @@ -248,58 +245,45 @@ struct TypeVarStringifier void stringify(const std::vector& types, const std::vector& typePacks) { - if (types.size() == 0 && (!FFlag::LuauTypeAliasPacks || typePacks.size() == 0)) + if (types.size() == 0 && typePacks.size() == 0) return; - if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) + if (types.size() || typePacks.size()) state.emit("<"); - if (FFlag::LuauTypeAliasPacks) - { - bool first = true; + bool first = true; - for (TypeId ty : types) - { - if (!first) - state.emit(", "); + for (TypeId ty : types) + { + if (!first) + state.emit(", "); + first = false; + + stringify(ty); + } + + bool singleTp = typePacks.size() == 1; + + for (TypePackId tp : typePacks) + { + if (isEmpty(tp) && singleTp) + continue; + + if (!first) + state.emit(", "); + else first = false; - stringify(ty); - } + if (!singleTp) + state.emit("("); - bool singleTp = typePacks.size() == 1; + stringify(tp); - for (TypePackId tp : typePacks) - { - if (isEmpty(tp) && singleTp) - continue; - - if (!first) - state.emit(", "); - else - first = false; - - if (!singleTp) - state.emit("("); - - stringify(tp); - - if (!singleTp) - state.emit(")"); - } - } - else - { - for (size_t i = 0; i < types.size(); ++i) - { - if (i > 0) - state.emit(", "); - - stringify(types[i]); - } + if (!singleTp) + state.emit(")"); } - if (types.size() || (FFlag::LuauTypeAliasPacks && typePacks.size())) + if (types.size() || typePacks.size()) state.emit(">"); } @@ -767,12 +751,23 @@ struct TypePackStringifier else state.emit(", "); - LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size()); - - if (!elemNames.empty() && elemNames[elemIndex]) + if (FFlag::LuauFunctionArgumentNameSize) { - state.emit(elemNames[elemIndex]->name); - state.emit(": "); + if (elemIndex < elemNames.size() && elemNames[elemIndex]) + { + state.emit(elemNames[elemIndex]->name); + state.emit(": "); + } + } + else + { + LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size()); + + if (!elemNames.empty() && elemNames[elemIndex]) + { + state.emit(elemNames[elemIndex]->name); + state.emit(": "); + } } elemIndex++; @@ -929,38 +924,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts) result.name += ttv->name ? *ttv->name : *ttv->syntheticName; - if (FFlag::LuauTypeAliasPacks) - { - tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams); - } - else - { - if (ttv->instantiatedTypeParams.empty() && (!FFlag::LuauTypeAliasPacks || ttv->instantiatedTypePackParams.empty())) - return result; - - result.name += "<"; - - bool first = true; - for (TypeId ty : ttv->instantiatedTypeParams) - { - if (!first) - result.name += ", "; - else - first = false; - - tvs.stringify(ty); - } - - if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) - { - result.truncated = true; - result.name += "... "; - } - else - { - result.name += ">"; - } - } + tvs.stringify(ttv->instantiatedTypeParams, ttv->instantiatedTypePackParams); return result; } @@ -1161,17 +1125,37 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV s += ", "; first = false; - // argNames is guaranteed to be equal to argTypes iff argNames is not empty. - // We don't currently respect opts.functionTypeArguments. I don't think this function should. - if (!ftv.argNames.empty()) - s += (*argNameIter ? (*argNameIter)->name : "_") + ": "; - s += toString_(*argPackIter); - - ++argPackIter; - if (!ftv.argNames.empty()) + if (FFlag::LuauFunctionArgumentNameSize) { - LUAU_ASSERT(argNameIter != ftv.argNames.end()); - ++argNameIter; + // We don't currently respect opts.functionTypeArguments. I don't think this function should. + if (argNameIter != ftv.argNames.end()) + { + s += (*argNameIter ? (*argNameIter)->name : "_") + ": "; + ++argNameIter; + } + else + { + s += "_: "; + } + } + else + { + // argNames is guaranteed to be equal to argTypes iff argNames is not empty. + // We don't currently respect opts.functionTypeArguments. I don't think this function should. + if (!ftv.argNames.empty()) + s += (*argNameIter ? (*argNameIter)->name : "_") + ": "; + } + + s += toString_(*argPackIter); + ++argPackIter; + + if (!FFlag::LuauFunctionArgumentNameSize) + { + if (!ftv.argNames.empty()) + { + LUAU_ASSERT(argNameIter != ftv.argNames.end()); + ++argNameIter; + } } } diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 6627fbe3..8e13ea5b 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,8 +10,6 @@ #include #include -LUAU_FASTFLAG(LuauTypeAliasPacks) - namespace { bool isIdentifierStartChar(char c) @@ -787,7 +785,7 @@ struct Printer writer.keyword("type"); writer.identifier(a->name.value); - if (a->generics.size > 0 || (FFlag::LuauTypeAliasPacks && a->genericPacks.size > 0)) + if (a->generics.size > 0 || a->genericPacks.size > 0) { writer.symbol("<"); CommaSeparatorInserter comma(writer); @@ -798,14 +796,11 @@ struct Printer writer.identifier(o.value); } - if (FFlag::LuauTypeAliasPacks) + for (auto o : a->genericPacks) { - for (auto o : a->genericPacks) - { - comma(); - writer.identifier(o.value); - writer.symbol("..."); - } + comma(); + writer.identifier(o.value); + writer.symbol("..."); } writer.symbol(">"); diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 383bb050..f6a61581 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -5,8 +5,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauShareTxnSeen, false) - namespace Luau { @@ -36,11 +34,8 @@ void TxnLog::rollback() for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it) std::swap(it->first->boundTo, it->second); - if (FFlag::LuauShareTxnSeen) - { - LUAU_ASSERT(originalSeenSize <= sharedSeen->size()); - sharedSeen->resize(originalSeenSize); - } + LUAU_ASSERT(originalSeenSize <= sharedSeen->size()); + sharedSeen->resize(originalSeenSize); } void TxnLog::concat(TxnLog rhs) @@ -53,45 +48,25 @@ void TxnLog::concat(TxnLog rhs) tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end()); rhs.tableChanges.clear(); - - if (!FFlag::LuauShareTxnSeen) - { - ownedSeen.swap(rhs.ownedSeen); - rhs.ownedSeen.clear(); - } } bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) { const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - if (FFlag::LuauShareTxnSeen) - return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)); - else - return (ownedSeen.end() != std::find(ownedSeen.begin(), ownedSeen.end(), sortedPair)); + return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)); } void TxnLog::pushSeen(TypeId lhs, TypeId rhs) { const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - if (FFlag::LuauShareTxnSeen) - sharedSeen->push_back(sortedPair); - else - ownedSeen.push_back(sortedPair); + sharedSeen->push_back(sortedPair); } void TxnLog::popSeen(TypeId lhs, TypeId rhs) { const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - if (FFlag::LuauShareTxnSeen) - { - LUAU_ASSERT(sortedPair == sharedSeen->back()); - sharedSeen->pop_back(); - } - else - { - LUAU_ASSERT(sortedPair == ownedSeen.back()); - ownedSeen.pop_back(); - } + LUAU_ASSERT(sortedPair == sharedSeen->back()); + sharedSeen->pop_back(); } } // namespace Luau diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index af6d2543..9e61c792 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAG(LuauTypeAliasPacks) - static char* allocateString(Luau::Allocator& allocator, std::string_view contents) { char* result = (char*)allocator.allocate(contents.size() + 1); @@ -131,12 +129,9 @@ public: parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}}; } - if (FFlag::LuauTypeAliasPacks) + for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i) { - for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i) - { - parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])}; - } + parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])}; } return allocator->alloc(Location(), std::nullopt, AstName(ttv.name->c_str()), parameters.size != 0, parameters); @@ -250,20 +245,7 @@ public: AstTypePack* argTailAnnotation = nullptr; if (argTail) - { - if (FFlag::LuauTypeAliasPacks) - { - argTailAnnotation = rehydrate(*argTail); - } - else - { - TypePackId tail = *argTail; - if (const VariadicTypePack* vtp = get(tail)) - { - argTailAnnotation = allocator->alloc(Location(), Luau::visit(*this, vtp->ty->ty)); - } - } - } + argTailAnnotation = rehydrate(*argTail); AstArray> argNames; argNames.size = ftv.argNames.size(); @@ -292,20 +274,7 @@ public: AstTypePack* retTailAnnotation = nullptr; if (retTail) - { - if (FFlag::LuauTypeAliasPacks) - { - retTailAnnotation = rehydrate(*retTail); - } - else - { - TypePackId tail = *retTail; - if (const VariadicTypePack* vtp = get(tail)) - { - retTailAnnotation = allocator->alloc(Location(), Luau::visit(*this, vtp->ty->ty)); - } - } - } + retTailAnnotation = rehydrate(*retTail); return allocator->alloc( Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}); @@ -518,18 +487,7 @@ public: const auto& [v, tail] = flatten(ret); if (tail) - { - if (FFlag::LuauTypeAliasPacks) - { - variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail); - } - else - { - TypePackId tailPack = *tail; - if (const VariadicTypePack* vtp = get(tailPack)) - variadicAnnotation = allocator->alloc(Location(), typeAst(vtp->ty)); - } - } + variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail); fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation}; } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index b2ae94c7..617bf482 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -23,22 +23,20 @@ LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) -LUAU_FASTFLAGVARIABLE(LuauClassPropertyAccessAsString, false) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. -LUAU_FASTFLAG(LuauTraceRequireLookupChild) LUAU_FASTFLAGVARIABLE(LuauCloneCorrectlyBeforeMutatingTableType, false) LUAU_FASTFLAGVARIABLE(LuauStoreMatchingOverloadFnType, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionAnalysisSupport, false) LUAU_FASTFLAGVARIABLE(LuauStrictRequire, false) -LUAU_FASTFLAG(LuauSubstitutionDontReplaceIgnoredTypes) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) -LUAU_FASTFLAG(LuauNewRequireTrace2) -LUAU_FASTFLAG(LuauTypeAliasPacks) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) +LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) +LUAU_FASTFLAGVARIABLE(LuauTailArgumentTypeInfo, false) +LUAU_FASTFLAGVARIABLE(LuauModuleRequireErrorPack, false) namespace Luau { @@ -562,12 +560,6 @@ ErrorVec TypeChecker::canUnify(TypePackId left, TypePackId right, const Location return canUnify_(left, right, location); } -ErrorVec TypeChecker::canUnify(const std::vector>& seen, TypeId superTy, TypeId subTy, const Location& location) -{ - Unifier state = mkUnifier(seen, location); - return state.canUnify(superTy, subTy); -} - template ErrorVec TypeChecker::canUnify_(Id superTy, Id subTy, const Location& location) { @@ -1152,61 +1144,20 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias Location location = scope->typeAliasLocations[name]; reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}}); - if (FFlag::LuauTypeAliasPacks) - bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)}; - else - bindingsMap[name] = TypeFun{binding->typeParams, errorRecoveryType(anyType)}; + bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)}; } else { ScopePtr aliasScope = FFlag::LuauQuantifyInPlace2 ? childScope(scope, typealias.location, subLevel) : childScope(scope, typealias.location); - if (FFlag::LuauTypeAliasPacks) - { - auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks); + auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks); - TypeId ty = freshType(aliasScope); - FreeTypeVar* ftv = getMutable(ty); - LUAU_ASSERT(ftv); - ftv->forwardedTypeAlias = true; - bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; - } - else - { - std::vector generics; - for (AstName generic : typealias.generics) - { - Name n = generic.value; - - // These generics are the only thing that will ever be added to aliasScope, so we can be certain that - // a collision can only occur when two generic typevars have the same name. - if (aliasScope->privateTypeBindings.end() != aliasScope->privateTypeBindings.find(n)) - { - // TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate. - reportError(TypeError{typealias.location, DuplicateGenericParameter{n}}); - } - - TypeId g; - if (FFlag::LuauRecursiveTypeParameterRestriction) - { - TypeId& cached = scope->typeAliasTypeParameters[n]; - if (!cached) - cached = addType(GenericTypeVar{aliasScope->level, n}); - g = cached; - } - else - g = addType(GenericTypeVar{aliasScope->level, n}); - generics.push_back(g); - aliasScope->privateTypeBindings[n] = TypeFun{{}, g}; - } - - TypeId ty = freshType(aliasScope); - FreeTypeVar* ftv = getMutable(ty); - LUAU_ASSERT(ftv); - ftv->forwardedTypeAlias = true; - bindingsMap[name] = {std::move(generics), ty}; - } + TypeId ty = freshType(aliasScope); + FreeTypeVar* ftv = getMutable(ty); + LUAU_ASSERT(ftv); + ftv->forwardedTypeAlias = true; + bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; } } else @@ -1223,14 +1174,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, ty}; } - if (FFlag::LuauTypeAliasPacks) + for (TypePackId tp : binding->typePackParams) { - for (TypePackId tp : binding->typePackParams) - { - auto generic = get(tp); - LUAU_ASSERT(generic); - aliasScope->privateTypePackBindings[generic->name] = tp; - } + auto generic = get(tp); + LUAU_ASSERT(generic); + aliasScope->privateTypePackBindings[generic->name] = tp; } TypeId ty = resolveType(aliasScope, *typealias.type); @@ -1241,19 +1189,16 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias { // Copy can be skipped if this is an identical alias if (ttv->name != name || ttv->instantiatedTypeParams != binding->typeParams || - (FFlag::LuauTypeAliasPacks && ttv->instantiatedTypePackParams != binding->typePackParams)) + ttv->instantiatedTypePackParams != binding->typePackParams) { // This is a shallow clone, original recursive links to self are not updated TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.definitionModuleName = ttv->definitionModuleName; - clone.name = name; clone.instantiatedTypeParams = binding->typeParams; - - if (FFlag::LuauTypeAliasPacks) - clone.instantiatedTypePackParams = binding->typePackParams; + clone.instantiatedTypePackParams = binding->typePackParams; ty = addType(std::move(clone)); } @@ -1262,9 +1207,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias { ttv->name = name; ttv->instantiatedTypeParams = binding->typeParams; - - if (FFlag::LuauTypeAliasPacks) - ttv->instantiatedTypePackParams = binding->typePackParams; + ttv->instantiatedTypePackParams = binding->typePackParams; } } else if (auto mtv = getMutable(follow(ty))) @@ -1289,7 +1232,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar } // We don't have generic classes, so this assertion _should_ never be hit. - LUAU_ASSERT(lookupType->typeParams.size() == 0 && (!FFlag::LuauTypeAliasPacks || lookupType->typePackParams.size() == 0)); + LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); superTy = lookupType->type; if (!get(follow(*superTy))) @@ -1851,6 +1794,24 @@ TypeId TypeChecker::checkExprTable( if (isNonstrictMode() && !getTableType(exprType) && !get(exprType)) exprType = anyType; + if (FFlag::LuauPropertiesGetExpectedType && expectedTable) + { + auto it = expectedTable->props.find(key->value.data); + if (it != expectedTable->props.end()) + { + Property expectedProp = it->second; + ErrorVec errors = tryUnify(expectedProp.type, exprType, k->location); + if (errors.empty()) + exprType = expectedProp.type; + } + else if (expectedTable->indexer && isString(expectedTable->indexer->indexType)) + { + ErrorVec errors = tryUnify(expectedTable->indexer->indexResultType, exprType, k->location); + if (errors.empty()) + exprType = expectedTable->indexer->indexResultType; + } + } + props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location}; } else @@ -3744,17 +3705,29 @@ ExprResult TypeChecker::checkExprList(const ScopePtr& scope, const L for (size_t i = 0; i < exprs.size; ++i) { AstExpr* expr = exprs.data[i]; + std::optional expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt; if (i == lastIndex && (expr->is() || expr->is())) { auto [typePack, exprPredicates] = checkExprPack(scope, *expr); insert(exprPredicates); + if (FFlag::LuauTailArgumentTypeInfo) + { + if (std::optional firstTy = first(typePack)) + { + if (!currentModule->astTypes.find(expr)) + currentModule->astTypes[expr] = follow(*firstTy); + } + + if (expectedType) + currentModule->astExpectedTypes[expr] = *expectedType; + } + tp->tail = typePack; } else { - std::optional expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt; auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType); insert(exprPredicates); @@ -3797,7 +3770,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker"); LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str()); - if (FFlag::LuauNewRequireTrace2 && moduleInfo.name.empty()) + if (moduleInfo.name.empty()) { if (FFlag::LuauStrictRequire && currentModule->mode == Mode::Strict) { @@ -3814,7 +3787,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module // There are two reasons why we might fail to find the module: // either the file does not exist or there's a cycle. If there's a cycle // we will already have reported the error. - if (!resolver->moduleExists(moduleInfo.name) && (FFlag::LuauTraceRequireLookupChild ? !moduleInfo.optional : true)) + if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional) { std::string reportedModulePath = resolver->getHumanReadableModuleName(moduleInfo.name); reportError(TypeError{location, UnknownRequire{reportedModulePath}}); @@ -3830,7 +3803,12 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module return errorRecoveryType(scope); } - std::optional moduleType = first(module->getModuleScope()->returnType); + TypePackId modulePack = module->getModuleScope()->returnType; + + if (FFlag::LuauModuleRequireErrorPack && get(modulePack)) + return errorRecoveryType(scope); + + std::optional moduleType = first(modulePack); if (!moduleType) { std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name); @@ -3840,7 +3818,8 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module SeenTypes seenTypes; SeenTypePacks seenTypePacks; - return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks); + CloneState cloneState; + return clone(*moduleType, currentModule->internalTypes, seenTypes, seenTypePacks, cloneState); } void TypeChecker::tablify(TypeId type) @@ -4326,11 +4305,6 @@ Unifier TypeChecker::mkUnifier(const Location& location) return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState}; } -Unifier TypeChecker::mkUnifier(const std::vector>& seen, const Location& location) -{ - return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, seen, location, Variance::Covariant, unifierState}; -} - TypeId TypeChecker::freshType(const ScopePtr& scope) { return freshType(scope->level); @@ -4477,117 +4451,82 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation return errorRecoveryType(scope); } - if (lit->parameters.size == 0 && tf->typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf->typePackParams.empty())) - { + if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty()) return tf->type; - } - else if (!FFlag::LuauTypeAliasPacks && lit->parameters.size != tf->typeParams.size()) + + if (!lit->hasParameterList && !tf->typePackParams.empty()) { - reportError(TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, lit->parameters.size, 0}}); + reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}}); if (!FFlag::LuauErrorRecoveryType) return errorRecoveryType(scope); } - if (FFlag::LuauTypeAliasPacks) + std::vector typeParams; + std::vector extraTypes; + std::vector typePackParams; + + for (size_t i = 0; i < lit->parameters.size; ++i) { - if (!lit->hasParameterList && !tf->typePackParams.empty()) + if (AstType* type = lit->parameters.data[i].type) { - reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}}); - if (!FFlag::LuauErrorRecoveryType) - return errorRecoveryType(scope); - } + TypeId ty = resolveType(scope, *type); - std::vector typeParams; - std::vector extraTypes; - std::vector typePackParams; - - for (size_t i = 0; i < lit->parameters.size; ++i) - { - if (AstType* type = lit->parameters.data[i].type) - { - TypeId ty = resolveType(scope, *type); - - if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty()) - typeParams.push_back(ty); - else if (typePackParams.empty()) - extraTypes.push_back(ty); - else - reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}}); - } - else if (AstTypePack* typePack = lit->parameters.data[i].typePack) - { - TypePackId tp = resolveTypePack(scope, *typePack); - - // If we have collected an implicit type pack, materialize it - if (typePackParams.empty() && !extraTypes.empty()) - typePackParams.push_back(addTypePack(extraTypes)); - - // If we need more regular types, we can use single element type packs to fill those in - if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp)) - typeParams.push_back(*first(tp)); - else - typePackParams.push_back(tp); - } - } - - // If we still haven't meterialized an implicit type pack, do it now - if (typePackParams.empty() && !extraTypes.empty()) - typePackParams.push_back(addTypePack(extraTypes)); - - // If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack - if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size()) - typePackParams.push_back(addTypePack({})); - - if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size()) - { - reportError( - TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}}); - - if (FFlag::LuauErrorRecoveryType) - { - // Pad the types out with error recovery types - while (typeParams.size() < tf->typeParams.size()) - typeParams.push_back(errorRecoveryType(scope)); - while (typePackParams.size() < tf->typePackParams.size()) - typePackParams.push_back(errorRecoveryTypePack(scope)); - } + if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty()) + typeParams.push_back(ty); + else if (typePackParams.empty()) + extraTypes.push_back(ty); else - return errorRecoveryType(scope); + reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}}); } - - if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams) + else if (AstTypePack* typePack = lit->parameters.data[i].typePack) { - // If the generic parameters and the type arguments are the same, we are about to - // perform an identity substitution, which we can just short-circuit. - return tf->type; + TypePackId tp = resolveTypePack(scope, *typePack); + + // If we have collected an implicit type pack, materialize it + if (typePackParams.empty() && !extraTypes.empty()) + typePackParams.push_back(addTypePack(extraTypes)); + + // If we need more regular types, we can use single element type packs to fill those in + if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp)) + typeParams.push_back(*first(tp)); + else + typePackParams.push_back(tp); } - - return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location); } - else - { - std::vector typeParams; - for (const auto& param : lit->parameters) - typeParams.push_back(resolveType(scope, *param.type)); + // If we still haven't meterialized an implicit type pack, do it now + if (typePackParams.empty() && !extraTypes.empty()) + typePackParams.push_back(addTypePack(extraTypes)); + + // If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack + if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size()) + typePackParams.push_back(addTypePack({})); + + if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size()) + { + reportError( + TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}}); if (FFlag::LuauErrorRecoveryType) { - // If there aren't enough type parameters, pad them out with error recovery types - // (we've already reported the error) - while (typeParams.size() < lit->parameters.size) + // Pad the types out with error recovery types + while (typeParams.size() < tf->typeParams.size()) typeParams.push_back(errorRecoveryType(scope)); + while (typePackParams.size() < tf->typePackParams.size()) + typePackParams.push_back(errorRecoveryTypePack(scope)); } - - if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams) - { - // If the generic parameters and the type arguments are the same, we are about to - // perform an identity substitution, which we can just short-circuit. - return tf->type; - } - - return instantiateTypeFun(scope, *tf, typeParams, {}, annotation.location); + else + return errorRecoveryType(scope); } + + if (FFlag::LuauRecursiveTypeParameterRestriction && typeParams == tf->typeParams && typePackParams == tf->typePackParams) + { + // If the generic parameters and the type arguments are the same, we are about to + // perform an identity substitution, which we can just short-circuit. + return tf->type; + } + + return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location); } else if (const auto& table = annotation.as()) { @@ -4757,7 +4696,7 @@ bool ApplyTypeFunction::isDirty(TypePackId tp) bool ApplyTypeFunction::ignoreChildren(TypeId ty) { - if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get(ty)) + if (get(ty)) return true; else return false; @@ -4765,7 +4704,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty) bool ApplyTypeFunction::ignoreChildren(TypePackId tp) { - if (FFlag::LuauSubstitutionDontReplaceIgnoredTypes && get(tp)) + if (get(tp)) return true; else return false; @@ -4788,36 +4727,26 @@ TypePackId ApplyTypeFunction::clean(TypePackId tp) // Really this should just replace the arguments, // but for bug-compatibility with existing code, we replace // all generics by free type variables. - if (FFlag::LuauTypeAliasPacks) - { - TypePackId& arg = typePackArguments[tp]; - if (arg) - return arg; - else - return addTypePack(FreeTypePack{level}); - } + TypePackId& arg = typePackArguments[tp]; + if (arg) + return arg; else - { return addTypePack(FreeTypePack{level}); - } } TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector& typeParams, const std::vector& typePackParams, const Location& location) { - if (tf.typeParams.empty() && (!FFlag::LuauTypeAliasPacks || tf.typePackParams.empty())) + if (tf.typeParams.empty() && tf.typePackParams.empty()) return tf.type; applyTypeFunction.typeArguments.clear(); for (size_t i = 0; i < tf.typeParams.size(); ++i) applyTypeFunction.typeArguments[tf.typeParams[i]] = typeParams[i]; - if (FFlag::LuauTypeAliasPacks) - { - applyTypeFunction.typePackArguments.clear(); - for (size_t i = 0; i < tf.typePackParams.size(); ++i) - applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i]; - } + applyTypeFunction.typePackArguments.clear(); + for (size_t i = 0; i < tf.typePackParams.size(); ++i) + applyTypeFunction.typePackArguments[tf.typePackParams[i]] = typePackParams[i]; applyTypeFunction.currentModule = currentModule; applyTypeFunction.level = scope->level; @@ -4866,9 +4795,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, if (ttv) { ttv->instantiatedTypeParams = typeParams; - - if (FFlag::LuauTypeAliasPacks) - ttv->instantiatedTypePackParams = typePackParams; + ttv->instantiatedTypePackParams = typePackParams; } } else @@ -4884,9 +4811,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, } ttv->instantiatedTypeParams = typeParams; - - if (FFlag::LuauTypeAliasPacks) - ttv->instantiatedTypePackParams = typePackParams; + ttv->instantiatedTypePackParams = typePackParams; } } @@ -4914,7 +4839,7 @@ std::pair, std::vector> TypeChecker::createGener } TypeId g; - if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) + if (FFlag::LuauRecursiveTypeParameterRestriction) { TypeId& cached = scope->parent->typeAliasTypeParameters[n]; if (!cached) @@ -4944,7 +4869,7 @@ std::pair, std::vector> TypeChecker::createGener } TypePackId g; - if (FFlag::LuauRecursiveTypeParameterRestriction && FFlag::LuauTypeAliasPacks) + if (FFlag::LuauRecursiveTypeParameterRestriction) { TypePackId& cached = scope->parent->typeAliasTypePackParameters[n]; if (!cached) @@ -5245,7 +5170,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); auto typeFun = globalScope->lookupType(typeguardP.kind); - if (!typeFun || !typeFun->typeParams.empty() || (FFlag::LuauTypeAliasPacks && !typeFun->typePackParams.empty())) + if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty()) return fail(UnknownSymbol{typeguardP.kind, UnknownSymbol::Type}); TypeId type = follow(typeFun->type); diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 924bf082..62715af5 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) -LUAU_FASTFLAG(LuauTypeAliasPacks) LUAU_FASTFLAGVARIABLE(LuauRefactorTagging, false) LUAU_FASTFLAG(LuauErrorRecoveryType) @@ -739,369 +738,6 @@ void persist(TypePackId tp) } } -namespace -{ - -struct StateDot -{ - StateDot(ToDotOptions opts) - : opts(opts) - { - } - - ToDotOptions opts; - - std::unordered_set seenTy; - std::unordered_set seenTp; - std::unordered_map tyToIndex; - std::unordered_map tpToIndex; - int nextIndex = 1; - std::string result; - - bool canDuplicatePrimitive(TypeId ty); - - void visitChildren(TypeId ty, int index); - void visitChildren(TypePackId ty, int index); - - void visitChild(TypeId ty, int parentIndex, const char* linkName = nullptr); - void visitChild(TypePackId tp, int parentIndex, const char* linkName = nullptr); - - void startNode(int index); - void finishNode(); - - void startNodeLabel(); - void finishNodeLabel(TypeId ty); - void finishNodeLabel(TypePackId tp); -}; - -bool StateDot::canDuplicatePrimitive(TypeId ty) -{ - if (get(ty)) - return false; - - return get(ty) || get(ty); -} - -void StateDot::visitChild(TypeId ty, int parentIndex, const char* linkName) -{ - if (!tyToIndex.count(ty) || (opts.duplicatePrimitives && canDuplicatePrimitive(ty))) - tyToIndex[ty] = nextIndex++; - - int index = tyToIndex[ty]; - - if (parentIndex != 0) - { - if (linkName) - formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, index, linkName); - else - formatAppend(result, "n%d -> n%d;\n", parentIndex, index); - } - - if (opts.duplicatePrimitives && canDuplicatePrimitive(ty)) - { - if (get(ty)) - formatAppend(result, "n%d [label=\"%s\"];\n", index, toStringDetailed(ty, {}).name.c_str()); - else if (get(ty)) - formatAppend(result, "n%d [label=\"any\"];\n", index); - } - else - { - visitChildren(ty, index); - } -} - -void StateDot::visitChild(TypePackId tp, int parentIndex, const char* linkName) -{ - if (!tpToIndex.count(tp)) - tpToIndex[tp] = nextIndex++; - - if (linkName) - formatAppend(result, "n%d -> n%d [label=\"%s\"];\n", parentIndex, tpToIndex[tp], linkName); - else - formatAppend(result, "n%d -> n%d;\n", parentIndex, tpToIndex[tp]); - - visitChildren(tp, tpToIndex[tp]); -} - -void StateDot::startNode(int index) -{ - formatAppend(result, "n%d [", index); -} - -void StateDot::finishNode() -{ - formatAppend(result, "];\n"); -} - -void StateDot::startNodeLabel() -{ - formatAppend(result, "label=\""); -} - -void StateDot::finishNodeLabel(TypeId ty) -{ - if (opts.showPointers) - formatAppend(result, "\n0x%p", ty); - // additional common attributes can be added here as well - result += "\""; -} - -void StateDot::finishNodeLabel(TypePackId tp) -{ - if (opts.showPointers) - formatAppend(result, "\n0x%p", tp); - // additional common attributes can be added here as well - result += "\""; -} - -void StateDot::visitChildren(TypeId ty, int index) -{ - if (seenTy.count(ty)) - return; - seenTy.insert(ty); - - startNode(index); - startNodeLabel(); - - if (const BoundTypeVar* btv = get(ty)) - { - formatAppend(result, "BoundTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - visitChild(btv->boundTo, index); - } - else if (const FunctionTypeVar* ftv = get(ty)) - { - formatAppend(result, "FunctionTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - visitChild(ftv->argTypes, index, "arg"); - visitChild(ftv->retType, index, "ret"); - } - else if (const TableTypeVar* ttv = get(ty)) - { - if (ttv->name) - formatAppend(result, "TableTypeVar %s", ttv->name->c_str()); - else if (ttv->syntheticName) - formatAppend(result, "TableTypeVar %s", ttv->syntheticName->c_str()); - else - formatAppend(result, "TableTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - if (ttv->boundTo) - return visitChild(*ttv->boundTo, index, "boundTo"); - - for (const auto& [name, prop] : ttv->props) - visitChild(prop.type, index, name.c_str()); - if (ttv->indexer) - { - visitChild(ttv->indexer->indexType, index, "[index]"); - visitChild(ttv->indexer->indexResultType, index, "[value]"); - } - for (TypeId itp : ttv->instantiatedTypeParams) - visitChild(itp, index, "typeParam"); - - if (FFlag::LuauTypeAliasPacks) - { - for (TypePackId itp : ttv->instantiatedTypePackParams) - visitChild(itp, index, "typePackParam"); - } - } - else if (const MetatableTypeVar* mtv = get(ty)) - { - formatAppend(result, "MetatableTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - visitChild(mtv->table, index, "table"); - visitChild(mtv->metatable, index, "metatable"); - } - else if (const UnionTypeVar* utv = get(ty)) - { - formatAppend(result, "UnionTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - for (TypeId opt : utv->options) - visitChild(opt, index); - } - else if (const IntersectionTypeVar* itv = get(ty)) - { - formatAppend(result, "IntersectionTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - - for (TypeId part : itv->parts) - visitChild(part, index); - } - else if (const GenericTypeVar* gtv = get(ty)) - { - if (gtv->explicitName) - formatAppend(result, "GenericTypeVar %s", gtv->name.c_str()); - else - formatAppend(result, "GenericTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (const FreeTypeVar* ftv = get(ty)) - { - formatAppend(result, "FreeTypeVar %d", ftv->index); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "AnyTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "PrimitiveTypeVar %s", toStringDetailed(ty, {}).name.c_str()); - finishNodeLabel(ty); - finishNode(); - } - else if (get(ty)) - { - formatAppend(result, "ErrorTypeVar %d", index); - finishNodeLabel(ty); - finishNode(); - } - else if (const ClassTypeVar* ctv = get(ty)) - { - formatAppend(result, "ClassTypeVar %s", ctv->name.c_str()); - finishNodeLabel(ty); - finishNode(); - - for (const auto& [name, prop] : ctv->props) - visitChild(prop.type, index, name.c_str()); - - if (ctv->parent) - visitChild(*ctv->parent, index, "[parent]"); - - if (ctv->metatable) - visitChild(*ctv->metatable, index, "[metatable]"); - } - else - { - LUAU_ASSERT(!"unknown type kind"); - finishNodeLabel(ty); - finishNode(); - } -} - -void StateDot::visitChildren(TypePackId tp, int index) -{ - if (seenTp.count(tp)) - return; - seenTp.insert(tp); - - startNode(index); - startNodeLabel(); - - if (const BoundTypePack* btp = get(tp)) - { - formatAppend(result, "BoundTypePack %d", index); - finishNodeLabel(tp); - finishNode(); - - visitChild(btp->boundTo, index); - } - else if (const TypePack* tpp = get(tp)) - { - formatAppend(result, "TypePack %d", index); - finishNodeLabel(tp); - finishNode(); - - for (TypeId tv : tpp->head) - visitChild(tv, index); - if (tpp->tail) - visitChild(*tpp->tail, index, "tail"); - } - else if (const VariadicTypePack* vtp = get(tp)) - { - formatAppend(result, "VariadicTypePack %d", index); - finishNodeLabel(tp); - finishNode(); - - visitChild(vtp->ty, index); - } - else if (const FreeTypePack* ftp = get(tp)) - { - formatAppend(result, "FreeTypePack %d", ftp->index); - finishNodeLabel(tp); - finishNode(); - } - else if (const GenericTypePack* gtp = get(tp)) - { - if (gtp->explicitName) - formatAppend(result, "GenericTypePack %s", gtp->name.c_str()); - else - formatAppend(result, "GenericTypePack %d", gtp->index); - finishNodeLabel(tp); - finishNode(); - } - else if (get(tp)) - { - formatAppend(result, "ErrorTypePack %d", index); - finishNodeLabel(tp); - finishNode(); - } - else - { - LUAU_ASSERT(!"unknown type pack kind"); - finishNodeLabel(tp); - finishNode(); - } -} - -} // namespace - -std::string toDot(TypeId ty, const ToDotOptions& opts) -{ - StateDot state{opts}; - - state.result = "digraph graphname {\n"; - state.visitChild(ty, 0); - state.result += "}"; - - return state.result; -} - -std::string toDot(TypePackId tp, const ToDotOptions& opts) -{ - StateDot state{opts}; - - state.result = "digraph graphname {\n"; - state.visitChild(tp, 0); - state.result += "}"; - - return state.result; -} - -std::string toDot(TypeId ty) -{ - return toDot(ty, {}); -} - -std::string toDot(TypePackId tp) -{ - return toDot(tp, {}); -} - -void dumpDot(TypeId ty) -{ - printf("%s\n", toDot(ty).c_str()); -} - -void dumpDot(TypePackId tp) -{ - printf("%s\n", toDot(tp).c_str()); -} - const TypeLevel* getLevel(TypeId ty) { ty = follow(ty); diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index e1a52be4..d0b18837 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -18,9 +18,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance, false); LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false) LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckOpts, false) -LUAU_FASTFLAG(LuauShareTxnSeen); -LUAU_FASTFLAGVARIABLE(LuauCacheUnifyTableResults, false) LUAU_FASTFLAGVARIABLE(LuauExtendedTypeMismatchError, false) LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAGVARIABLE(LuauExtendedClassMismatchError, false) @@ -136,38 +133,19 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati , globalScope(std::move(globalScope)) , location(location) , variance(variance) - , counters(&countersData) - , counters_DEPRECATED(std::make_shared()) - , sharedState(sharedState) -{ - LUAU_ASSERT(sharedState.iceHandler); -} - -Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector>& ownedSeen, const Location& location, - Variance variance, UnifierSharedState& sharedState, const std::shared_ptr& counters_DEPRECATED, UnifierCounters* counters) - : types(types) - , mode(mode) - , globalScope(std::move(globalScope)) - , log(ownedSeen) - , location(location) - , variance(variance) - , counters(counters ? counters : &countersData) - , counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared()) , sharedState(sharedState) { LUAU_ASSERT(sharedState.iceHandler); } Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector>* sharedSeen, const Location& location, - Variance variance, UnifierSharedState& sharedState, const std::shared_ptr& counters_DEPRECATED, UnifierCounters* counters) + Variance variance, UnifierSharedState& sharedState) : types(types) , mode(mode) , globalScope(std::move(globalScope)) , log(sharedSeen) , location(location) , variance(variance) - , counters(counters ? counters : &countersData) - , counters_DEPRECATED(counters_DEPRECATED ? counters_DEPRECATED : std::make_shared()) , sharedState(sharedState) { LUAU_ASSERT(sharedState.iceHandler); @@ -175,26 +153,18 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector< void Unifier::tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection) { - if (FFlag::LuauTypecheckOpts) - counters->iterationCount = 0; - else - counters_DEPRECATED->iterationCount = 0; + sharedState.counters.iterationCount = 0; tryUnify_(superTy, subTy, isFunctionCall, isIntersection); } void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool isIntersection) { - RecursionLimiter _ra( - FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit); + RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); - if (FFlag::LuauTypecheckOpts) - ++counters->iterationCount; - else - ++counters_DEPRECATED->iterationCount; + ++sharedState.counters.iterationCount; - if (FInt::LuauTypeInferIterationLimit > 0 && - FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount)) + if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount) { errors.push_back(TypeError{location, UnificationTooComplex{}}); return; @@ -302,7 +272,7 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool if (get(subTy) || get(subTy)) return tryUnifyWithAny(subTy, superTy); - bool cacheEnabled = FFlag::LuauCacheUnifyTableResults && !isFunctionCall && !isIntersection; + bool cacheEnabled = !isFunctionCall && !isIntersection; auto& cache = sharedState.cachedUnify; // What if the types are immutable and we proved their relation before @@ -563,8 +533,6 @@ void Unifier::tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall, bool void Unifier::cacheResult(TypeId superTy, TypeId subTy) { - LUAU_ASSERT(FFlag::LuauCacheUnifyTableResults); - bool* superTyInfo = sharedState.skipCacheForType.find(superTy); if (superTyInfo && *superTyInfo) @@ -686,10 +654,7 @@ ErrorVec Unifier::canUnify(TypePackId superTy, TypePackId subTy, bool isFunction void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall) { - if (FFlag::LuauTypecheckOpts) - counters->iterationCount = 0; - else - counters_DEPRECATED->iterationCount = 0; + sharedState.counters.iterationCount = 0; tryUnify_(superTp, subTp, isFunctionCall); } @@ -700,16 +665,11 @@ void Unifier::tryUnify(TypePackId superTp, TypePackId subTp, bool isFunctionCall */ void Unifier::tryUnify_(TypePackId superTp, TypePackId subTp, bool isFunctionCall) { - RecursionLimiter _ra( - FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit); + RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); - if (FFlag::LuauTypecheckOpts) - ++counters->iterationCount; - else - ++counters_DEPRECATED->iterationCount; + ++sharedState.counters.iterationCount; - if (FInt::LuauTypeInferIterationLimit > 0 && - FInt::LuauTypeInferIterationLimit < (FFlag::LuauTypecheckOpts ? counters->iterationCount : counters_DEPRECATED->iterationCount)) + if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount) { errors.push_back(TypeError{location, UnificationTooComplex{}}); return; @@ -1727,39 +1687,8 @@ void Unifier::tryUnify(const TableIndexer& superIndexer, const TableIndexer& sub tryUnify_(superIndexer.indexResultType, subIndexer.indexResultType); } -static void queueTypePack_DEPRECATED( - std::vector& queue, std::unordered_set& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack) -{ - LUAU_ASSERT(!FFlag::LuauTypecheckOpts); - - while (true) - { - a = follow(a); - - if (seenTypePacks.count(a)) - break; - seenTypePacks.insert(a); - - if (get(a)) - { - state.log(a); - *asMutable(a) = Unifiable::Bound{anyTypePack}; - } - else if (auto tp = get(a)) - { - queue.insert(queue.end(), tp->head.begin(), tp->head.end()); - if (tp->tail) - a = *tp->tail; - else - break; - } - } -} - static void queueTypePack(std::vector& queue, DenseHashSet& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack) { - LUAU_ASSERT(FFlag::LuauTypecheckOpts); - while (true) { a = follow(a); @@ -1837,66 +1766,9 @@ void Unifier::tryUnifyVariadics(TypePackId superTp, TypePackId subTp, bool rever } } -static void tryUnifyWithAny_DEPRECATED( - std::vector& queue, Unifier& state, std::unordered_set& seenTypePacks, TypeId anyType, TypePackId anyTypePack) -{ - LUAU_ASSERT(!FFlag::LuauTypecheckOpts); - - std::unordered_set seen; - - while (!queue.empty()) - { - TypeId ty = follow(queue.back()); - queue.pop_back(); - if (seen.count(ty)) - continue; - seen.insert(ty); - - if (get(ty)) - { - state.log(ty); - *asMutable(ty) = BoundTypeVar{anyType}; - } - else if (auto fun = get(ty)) - { - queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->argTypes, anyTypePack); - queueTypePack_DEPRECATED(queue, seenTypePacks, state, fun->retType, anyTypePack); - } - else if (auto table = get(ty)) - { - for (const auto& [_name, prop] : table->props) - queue.push_back(prop.type); - - if (table->indexer) - { - queue.push_back(table->indexer->indexType); - queue.push_back(table->indexer->indexResultType); - } - } - else if (auto mt = get(ty)) - { - queue.push_back(mt->table); - queue.push_back(mt->metatable); - } - else if (get(ty)) - { - // ClassTypeVars never contain free typevars. - } - else if (auto union_ = get(ty)) - queue.insert(queue.end(), union_->options.begin(), union_->options.end()); - else if (auto intersection = get(ty)) - queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end()); - else - { - } // Primitives, any, errors, and generics are left untouched. - } -} - static void tryUnifyWithAny(std::vector& queue, Unifier& state, DenseHashSet& seen, DenseHashSet& seenTypePacks, TypeId anyType, TypePackId anyTypePack) { - LUAU_ASSERT(FFlag::LuauTypecheckOpts); - while (!queue.empty()) { TypeId ty = follow(queue.back()); @@ -1949,43 +1821,20 @@ void Unifier::tryUnifyWithAny(TypeId any, TypeId ty) { LUAU_ASSERT(get(any) || get(any)); - if (FFlag::LuauTypecheckOpts) - { - // These types are not visited in general loop below - if (get(ty) || get(ty) || get(ty)) - return; - } + // These types are not visited in general loop below + if (get(ty) || get(ty) || get(ty)) + return; const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes.anyType}}); const TypePackId anyTP = get(any) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}}); - if (FFlag::LuauTypecheckOpts) - { - std::vector queue = {ty}; + std::vector queue = {ty}; - if (FFlag::LuauCacheUnifyTableResults) - { - sharedState.tempSeenTy.clear(); - sharedState.tempSeenTp.clear(); + sharedState.tempSeenTy.clear(); + sharedState.tempSeenTp.clear(); - Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP); - } - else - { - tempSeenTy_DEPRECATED.clear(); - tempSeenTp_DEPRECATED.clear(); - - Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, singletonTypes.anyType, anyTP); - } - } - else - { - std::unordered_set seenTypePacks; - std::vector queue = {ty}; - - Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, singletonTypes.anyType, anyTP); - } + Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, singletonTypes.anyType, anyTP); } void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty) @@ -1994,38 +1843,14 @@ void Unifier::tryUnifyWithAny(TypePackId any, TypePackId ty) const TypeId anyTy = singletonTypes.errorRecoveryType(); - if (FFlag::LuauTypecheckOpts) - { - std::vector queue; + std::vector queue; - if (FFlag::LuauCacheUnifyTableResults) - { - sharedState.tempSeenTy.clear(); - sharedState.tempSeenTp.clear(); + sharedState.tempSeenTy.clear(); + sharedState.tempSeenTp.clear(); - queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any); + queueTypePack(queue, sharedState.tempSeenTp, *this, ty, any); - Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any); - } - else - { - tempSeenTy_DEPRECATED.clear(); - tempSeenTp_DEPRECATED.clear(); - - queueTypePack(queue, tempSeenTp_DEPRECATED, *this, ty, any); - - Luau::tryUnifyWithAny(queue, *this, tempSeenTy_DEPRECATED, tempSeenTp_DEPRECATED, anyTy, any); - } - } - else - { - std::unordered_set seenTypePacks; - std::vector queue; - - queueTypePack_DEPRECATED(queue, seenTypePacks, *this, ty, any); - - Luau::tryUnifyWithAny_DEPRECATED(queue, *this, seenTypePacks, anyTy, any); - } + Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, any); } std::optional Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) @@ -2035,46 +1860,22 @@ std::optional Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N void Unifier::occursCheck(TypeId needle, TypeId haystack) { - std::unordered_set seen_DEPRECATED; + sharedState.tempSeenTy.clear(); - if (FFlag::LuauCacheUnifyTableResults) - { - if (FFlag::LuauTypecheckOpts) - sharedState.tempSeenTy.clear(); - - return occursCheck(seen_DEPRECATED, sharedState.tempSeenTy, needle, haystack); - } - else - { - if (FFlag::LuauTypecheckOpts) - tempSeenTy_DEPRECATED.clear(); - - return occursCheck(seen_DEPRECATED, tempSeenTy_DEPRECATED, needle, haystack); - } + return occursCheck(sharedState.tempSeenTy, needle, haystack); } -void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, DenseHashSet& seen, TypeId needle, TypeId haystack) +void Unifier::occursCheck(DenseHashSet& seen, TypeId needle, TypeId haystack) { - RecursionLimiter _ra( - FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit); + RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); needle = follow(needle); haystack = follow(haystack); - if (FFlag::LuauTypecheckOpts) - { - if (seen.find(haystack)) - return; + if (seen.find(haystack)) + return; - seen.insert(haystack); - } - else - { - if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack)) - return; - - seen_DEPRECATED.insert(haystack); - } + seen.insert(haystack); if (get(needle)) return; @@ -2091,7 +1892,7 @@ void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, DenseHash } auto check = [&](TypeId tv) { - occursCheck(seen_DEPRECATED, seen, needle, tv); + occursCheck(seen, needle, tv); }; if (get(haystack)) @@ -2121,43 +1922,20 @@ void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, DenseHash void Unifier::occursCheck(TypePackId needle, TypePackId haystack) { - std::unordered_set seen_DEPRECATED; + sharedState.tempSeenTp.clear(); - if (FFlag::LuauCacheUnifyTableResults) - { - if (FFlag::LuauTypecheckOpts) - sharedState.tempSeenTp.clear(); - - return occursCheck(seen_DEPRECATED, sharedState.tempSeenTp, needle, haystack); - } - else - { - if (FFlag::LuauTypecheckOpts) - tempSeenTp_DEPRECATED.clear(); - - return occursCheck(seen_DEPRECATED, tempSeenTp_DEPRECATED, needle, haystack); - } + return occursCheck(sharedState.tempSeenTp, needle, haystack); } -void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, DenseHashSet& seen, TypePackId needle, TypePackId haystack) +void Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, TypePackId haystack) { needle = follow(needle); haystack = follow(haystack); - if (FFlag::LuauTypecheckOpts) - { - if (seen.find(haystack)) - return; + if (seen.find(haystack)) + return; - seen.insert(haystack); - } - else - { - if (seen_DEPRECATED.end() != seen_DEPRECATED.find(haystack)) - return; - - seen_DEPRECATED.insert(haystack); - } + seen.insert(haystack); if (get(needle)) return; @@ -2165,8 +1943,7 @@ void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, Dense if (!get(needle)) ice("Expected needle pack to be free"); - RecursionLimiter _ra( - FFlag::LuauTypecheckOpts ? &counters->recursionCount : &counters_DEPRECATED->recursionCount, FInt::LuauTypeInferRecursionLimit); + RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit); while (!get(haystack)) { @@ -2186,8 +1963,8 @@ void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, Dense { if (auto f = get(follow(ty))) { - occursCheck(seen_DEPRECATED, seen, needle, f->argTypes); - occursCheck(seen_DEPRECATED, seen, needle, f->retType); + occursCheck(seen, needle, f->argTypes); + occursCheck(seen, needle, f->retType); } } } @@ -2204,10 +1981,7 @@ void Unifier::occursCheck(std::unordered_set& seen_DEPRECATED, Dense Unifier Unifier::makeChildUnifier() { - if (FFlag::LuauShareTxnSeen) - return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, counters_DEPRECATED, counters}; - else - return Unifier{types, mode, globalScope, log.ownedSeen, location, variance, sharedState, counters_DEPRECATED, counters}; + return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState}; } bool Unifier::isNonstrictMode() const diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index bc63e37d..3d0d5b7e 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -13,8 +13,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauCaptureBrokenCommentSpans, false) LUAU_FASTFLAGVARIABLE(LuauIfElseExpressionBaseSupport, false) LUAU_FASTFLAGVARIABLE(LuauIfStatementRecursionGuard, false) -LUAU_FASTFLAGVARIABLE(LuauTypeAliasPacks, false) -LUAU_FASTFLAGVARIABLE(LuauParseTypePackTypeParameters, false) LUAU_FASTFLAGVARIABLE(LuauFixAmbiguousErrorRecoveryInAssign, false) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseGenericFunctionTypeBegin, false) @@ -782,8 +780,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) AstType* type = parseTypeAnnotation(); - return allocator.alloc( - Location(start, type->location), name->name, generics, FFlag::LuauTypeAliasPacks ? genericPacks : AstArray{}, type, exported); + return allocator.alloc(Location(start, type->location), name->name, generics, genericPacks, type, exported); } AstDeclaredClassProp Parser::parseDeclaredClassMethod() @@ -1602,30 +1599,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) return {allocator.alloc(Location(begin, end), expr), {}}; } - if (FFlag::LuauParseTypePackTypeParameters) + bool hasParameters = false; + AstArray parameters{}; + + if (lexer.current().type == '<') { - bool hasParameters = false; - AstArray parameters{}; - - if (lexer.current().type == '<') - { - hasParameters = true; - parameters = parseTypeParams(); - } - - Location end = lexer.previousLocation(); - - return {allocator.alloc(Location(begin, end), prefix, name.name, hasParameters, parameters), {}}; + hasParameters = true; + parameters = parseTypeParams(); } - else - { - AstArray generics = parseTypeParams(); - Location end = lexer.previousLocation(); + Location end = lexer.previousLocation(); - // false in 'hasParameterList' as it is not used without FFlagLuauTypeAliasPacks - return {allocator.alloc(Location(begin, end), prefix, name.name, false, generics), {}}; - } + return {allocator.alloc(Location(begin, end), prefix, name.name, hasParameters, parameters), {}}; } else if (lexer.current().type == '{') { @@ -2414,37 +2399,24 @@ AstArray Parser::parseTypeParams() while (true) { - if (FFlag::LuauParseTypePackTypeParameters) + if (shouldParseTypePackAnnotation(lexer)) { - if (shouldParseTypePackAnnotation(lexer)) - { - auto typePack = parseTypePackAnnotation(); + auto typePack = parseTypePackAnnotation(); - if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them - parameters.push_back({{}, typePack}); - } - else if (lexer.current().type == '(') - { - auto [type, typePack] = parseTypeOrPackAnnotation(); + parameters.push_back({{}, typePack}); + } + else if (lexer.current().type == '(') + { + auto [type, typePack] = parseTypeOrPackAnnotation(); - if (typePack) - { - if (FFlag::LuauTypeAliasPacks) // Type packs are recorded only is we can handle them - parameters.push_back({{}, typePack}); - } - else - { - parameters.push_back({type, {}}); - } - } - else if (lexer.current().type == '>' && parameters.empty()) - { - break; - } + if (typePack) + parameters.push_back({{}, typePack}); else - { - parameters.push_back({parseTypeAnnotation(), {}}); - } + parameters.push_back({type, {}}); + } + else if (lexer.current().type == '>' && parameters.empty()) + { + break; } else { diff --git a/CLI/Analyze.cpp b/CLI/Analyze.cpp index ebdd7896..9230d80d 100644 --- a/CLI/Analyze.cpp +++ b/CLI/Analyze.cpp @@ -121,7 +121,7 @@ struct CliFileResolver : Luau::FileResolver if (Luau::AstExprConstantString* expr = node->as()) { Luau::ModuleName name = std::string(expr->value.data, expr->value.size) + ".luau"; - if (!moduleExists(name)) + if (!readFile(name)) { // fall back to .lua if a module with .luau doesn't exist name = std::string(expr->value.data, expr->value.size) + ".lua"; @@ -132,27 +132,6 @@ struct CliFileResolver : Luau::FileResolver return std::nullopt; } - - bool moduleExists(const Luau::ModuleName& name) const override - { - return !!readFile(name); - } - - - std::optional fromAstFragment(Luau::AstExpr* expr) const override - { - return std::nullopt; - } - - Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override - { - return lhs + "/" + std::string(rhs); - } - - std::optional getParentModuleName(const Luau::ModuleName& name) const override - { - return std::nullopt; - } }; struct CliConfigResolver : Luau::ConfigResolver diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index b29cd6f9..2cdd0062 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -198,11 +198,6 @@ static std::string runCode(lua_State* L, const std::string& source) error += "\nstack backtrace:\n"; error += lua_debugtrace(T); -#ifdef __EMSCRIPTEN__ - // nicer formatting for errors in web repl - error = "Error:" + error; -#endif - fprintf(stdout, "%s", error.c_str()); } @@ -210,39 +205,6 @@ static std::string runCode(lua_State* L, const std::string& source) return std::string(); } -#ifdef __EMSCRIPTEN__ -extern "C" -{ - const char* executeScript(const char* source) - { - // setup flags - for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) - if (strncmp(flag->name, "Luau", 4) == 0) - flag->value = true; - - // create new state - std::unique_ptr globalState(luaL_newstate(), lua_close); - lua_State* L = globalState.get(); - - // setup state - setupState(L); - - // sandbox thread - luaL_sandboxthread(L); - - // static string for caching result (prevents dangling ptr on function exit) - static std::string result; - - // run code + collect error - result = runCode(L, source); - - return result.empty() ? NULL : result.c_str(); - } -} -#endif - -// Excluded from emscripten compilation to avoid -Wunused-function errors. -#ifndef __EMSCRIPTEN__ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector& completions) { std::string_view lookup = editBuffer + start; @@ -564,6 +526,5 @@ int main(int argc, char** argv) return failed; } } -#endif diff --git a/CLI/Web.cpp b/CLI/Web.cpp new file mode 100644 index 00000000..cf5c831e --- /dev/null +++ b/CLI/Web.cpp @@ -0,0 +1,106 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "lua.h" +#include "lualib.h" +#include "luacode.h" + +#include "Luau/Common.h" + +#include + +#include + +static void setupState(lua_State* L) +{ + luaL_openlibs(L); + + luaL_sandbox(L); +} + +static std::string runCode(lua_State* L, const std::string& source) +{ + size_t bytecodeSize = 0; + char* bytecode = luau_compile(source.data(), source.length(), nullptr, &bytecodeSize); + int result = luau_load(L, "=stdin", bytecode, bytecodeSize, 0); + free(bytecode); + + if (result != 0) + { + size_t len; + const char* msg = lua_tolstring(L, -1, &len); + + std::string error(msg, len); + lua_pop(L, 1); + + return error; + } + + lua_State* T = lua_newthread(L); + + lua_pushvalue(L, -2); + lua_remove(L, -3); + lua_xmove(L, T, 1); + + int status = lua_resume(T, NULL, 0); + + if (status == 0) + { + int n = lua_gettop(T); + + if (n) + { + luaL_checkstack(T, LUA_MINSTACK, "too many results to print"); + lua_getglobal(T, "print"); + lua_insert(T, 1); + lua_pcall(T, n, 0, 0); + } + } + else + { + std::string error; + + if (status == LUA_YIELD) + { + error = "thread yielded unexpectedly"; + } + else if (const char* str = lua_tostring(T, -1)) + { + error = str; + } + + error += "\nstack backtrace:\n"; + error += lua_debugtrace(T); + + error = "Error:" + error; + + fprintf(stdout, "%s", error.c_str()); + } + + lua_pop(L, 1); + return std::string(); +} + +extern "C" const char* executeScript(const char* source) +{ + // setup flags + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + + // create new state + std::unique_ptr globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + // setup state + setupState(L); + + // sandbox thread + luaL_sandboxthread(L); + + // static string for caching result (prevents dangling ptr on function exit) + static std::string result; + + // run code + collect error + result = runCode(L, source); + + return result.empty() ? NULL : result.c_str(); +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c69521e..bafc59e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ project(Luau LANGUAGES CXX) option(LUAU_BUILD_CLI "Build CLI" ON) option(LUAU_BUILD_TESTS "Build tests" ON) +option(LUAU_BUILD_WEB "Build Web module" OFF) option(LUAU_WERROR "Warnings as errors" OFF) add_library(Luau.Ast STATIC) @@ -18,26 +19,22 @@ add_library(Luau.VM STATIC) if(LUAU_BUILD_CLI) add_executable(Luau.Repl.CLI) - if(NOT EMSCRIPTEN) - add_executable(Luau.Analyze.CLI) - else() - # add -fexceptions for emscripten to allow exceptions to be caught in C++ - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") - endif() + add_executable(Luau.Analyze.CLI) # This also adds target `name` on Linux/macOS and `name.exe` on Windows set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau) - - if(NOT EMSCRIPTEN) - set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze) - endif() + set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze) endif() -if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) +if(LUAU_BUILD_TESTS) add_executable(Luau.UnitTest) add_executable(Luau.Conformance) endif() +if(LUAU_BUILD_WEB) + add_executable(Luau.Web) +endif() + include(Sources.cmake) target_compile_features(Luau.Ast PUBLIC cxx_std_17) @@ -72,16 +69,18 @@ if(LUAU_WERROR) endif() endif() +if(LUAU_BUILD_WEB) + # add -fexceptions for emscripten to allow exceptions to be caught in C++ + list(APPEND LUAU_OPTIONS -fexceptions) +endif() + target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) if(LUAU_BUILD_CLI) target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS}) - - if(NOT EMSCRIPTEN) - target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) - endif() + target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.Repl.CLI PRIVATE extern) target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM) @@ -93,20 +92,10 @@ if(LUAU_BUILD_CLI) endif() endif() - if(NOT EMSCRIPTEN) - target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis) - endif() - - if(EMSCRIPTEN) - # declare exported functions to emscripten - target_link_options(Luau.Repl.CLI PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -fexceptions) - - # custom output directory for wasm + js file - set_target_properties(Luau.Repl.CLI PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/assets/luau) - endif() + target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis) endif() -if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) +if(LUAU_BUILD_TESTS) target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.UnitTest PRIVATE extern) target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler) @@ -115,3 +104,17 @@ if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) target_include_directories(Luau.Conformance PRIVATE extern) target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM) endif() + +if(LUAU_BUILD_WEB) + target_compile_options(Luau.Web PRIVATE ${LUAU_OPTIONS}) + target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM) + + # declare exported functions to emscripten + target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']) + + # add -fexceptions for emscripten to allow exceptions to be caught in C++ + target_link_options(Luau.Web PRIVATE -fexceptions) + + # the output is a single .js file with an embedded wasm blob + target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1) +endif() diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 5b93c1dc..2c1e85ff 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -321,13 +321,15 @@ struct Compiler compileExprTempTop(expr->args.data[i], uint8_t(regs + 1 + expr->self + i)); } - setDebugLine(expr->func); + setDebugLineEnd(expr->func); if (expr->self) { AstExprIndexName* fi = expr->func->as(); LUAU_ASSERT(fi); + setDebugLine(fi->indexLocation); + BytecodeBuilder::StringRef iname = sref(fi->index); int32_t cid = bytecode.addConstantString(iname); if (cid < 0) @@ -1313,6 +1315,8 @@ struct Compiler RegScope rs(this); uint8_t reg = compileExprAuto(expr->expr, rs); + setDebugLine(expr->indexLocation); + BytecodeBuilder::StringRef iname = sref(expr->index); int32_t cid = bytecode.addConstantString(iname); if (cid < 0) @@ -2710,6 +2714,12 @@ struct Compiler bytecode.setDebugLine(node->location.begin.line + 1); } + void setDebugLine(const Location& location) + { + if (options.debugLevel >= 1) + bytecode.setDebugLine(location.begin.line + 1); + } + void setDebugLineEnd(AstNode* node) { if (options.debugLevel >= 1) @@ -3650,7 +3660,7 @@ struct Compiler { if (options.vectorLib) { - if (builtin.object == options.vectorLib && builtin.method == options.vectorCtor) + if (builtin.isMethod(options.vectorLib, options.vectorCtor)) return LBF_VECTOR; } else diff --git a/Makefile b/Makefile index cab3d43f..15c7ff7a 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o) ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze -FUZZ_SOURCES=$(wildcard fuzz/*.cpp) +FUZZ_SOURCES=$(wildcard fuzz/*.cpp) fuzz/luau.pb.cpp FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o) TESTS_ARGS= @@ -167,8 +167,8 @@ fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=. mv fuzz/luau.pb.cc fuzz/luau.pb.cpp -$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator -$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator +$(BUILD)/fuzz/proto.cpp.o: fuzz/luau.pb.cpp +$(BUILD)/fuzz/protoprint.cpp.o: fuzz/luau.pb.cpp build/libprotobuf-mutator: git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator diff --git a/Sources.cmake b/Sources.cmake index 23b931c6..57df9b91 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -54,6 +54,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Scope.h Analysis/include/Luau/Substitution.h Analysis/include/Luau/Symbol.h + Analysis/include/Luau/ToDot.h Analysis/include/Luau/TopoSortStatements.h Analysis/include/Luau/ToString.h Analysis/include/Luau/Transpiler.h @@ -86,6 +87,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Scope.cpp Analysis/src/Substitution.cpp Analysis/src/Symbol.cpp + Analysis/src/ToDot.cpp Analysis/src/TopoSortStatements.cpp Analysis/src/ToString.cpp Analysis/src/Transpiler.cpp @@ -118,6 +120,7 @@ target_sources(Luau.VM PRIVATE VM/src/ldo.cpp VM/src/lfunc.cpp VM/src/lgc.cpp + VM/src/lgcdebug.cpp VM/src/linit.cpp VM/src/lmathlib.cpp VM/src/lmem.cpp @@ -194,6 +197,7 @@ if(TARGET Luau.UnitTest) tests/RequireTracer.test.cpp tests/StringUtils.test.cpp tests/Symbol.test.cpp + tests/ToDot.test.cpp tests/TopoSort.test.cpp tests/ToString.test.cpp tests/Transpiler.test.cpp @@ -224,3 +228,9 @@ if(TARGET Luau.Conformance) tests/Conformance.test.cpp tests/main.cpp) endif() + +if(TARGET Luau.Web) + # Luau.Web Sources + target_sources(Luau.Web PRIVATE + CLI/Web.cpp) +endif() diff --git a/VM/include/lua.h b/VM/include/lua.h index 1568d191..7078acd0 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -21,6 +21,7 @@ #define LUA_ENVIRONINDEX (-10001) #define LUA_GLOBALSINDEX (-10002) #define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i)) +#define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX) /* thread status; 0 is OK */ enum lua_Status @@ -108,6 +109,7 @@ LUA_API int lua_isthreadreset(lua_State* L); /* ** basic stack manipulation */ +LUA_API int lua_absindex(lua_State* L, int idx); LUA_API int lua_gettop(lua_State* L); LUA_API void lua_settop(lua_State* L, int idx); LUA_API void lua_pushvalue(lua_State* L, int idx); @@ -159,7 +161,11 @@ LUA_API void lua_pushnil(lua_State* L); LUA_API void lua_pushnumber(lua_State* L, double n); LUA_API void lua_pushinteger(lua_State* L, int n); LUA_API void lua_pushunsigned(lua_State* L, unsigned n); +#if LUA_VECTOR_SIZE == 4 +LUA_API void lua_pushvector(lua_State* L, float x, float y, float z, float w); +#else LUA_API void lua_pushvector(lua_State* L, float x, float y, float z); +#endif LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l); LUA_API void lua_pushstring(lua_State* L, const char* s); LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp); @@ -183,7 +189,7 @@ LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled); LUA_API int lua_getreadonly(lua_State* L, int idx); LUA_API void lua_setsafeenv(lua_State* L, int idx, int enabled); -LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag); +LUA_API void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag); LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*)); LUA_API int lua_getmetatable(lua_State* L, int objindex); LUA_API void lua_getfenv(lua_State* L, int idx); @@ -227,6 +233,7 @@ enum lua_GCOp LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, + LUA_GCCOUNTB, LUA_GCISRUNNING, // garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation @@ -281,6 +288,7 @@ LUA_API void lua_unref(lua_State* L, int ref); #define lua_pop(L, n) lua_settop(L, -(n)-1) #define lua_newtable(L) lua_createtable(L, 0, 0) +#define lua_newuserdata(L, s) lua_newuserdatatagged(L, s, 0) #define lua_strlen(L, i) lua_objlen(L, (i)) @@ -289,6 +297,7 @@ LUA_API void lua_unref(lua_State* L, int ref); #define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) #define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isvector(L, n) (lua_type(L, (n)) == LUA_TVECTOR) #define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD) #define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL) diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index aa008a24..a01a1481 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -34,7 +34,10 @@ #endif /* Can be used to reconfigure visibility/exports for public APIs */ +#ifndef LUA_API #define LUA_API extern +#endif + #define LUALIB_API LUA_API /* Can be used to reconfigure visibility for internal APIs */ @@ -47,10 +50,14 @@ #endif /* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */ +#ifndef LUA_USE_LONGJMP #define LUA_USE_LONGJMP 0 +#endif /* LUA_IDSIZE gives the maximum size for the description of the source */ +#ifndef LUA_IDSIZE #define LUA_IDSIZE 256 +#endif /* @@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap @@ -59,7 +66,9 @@ ** mean larger GC pauses which mean slower collection.) You can also change ** this value dynamically. */ +#ifndef LUAI_GCGOAL #define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */ +#endif /* @@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection @@ -69,38 +78,63 @@ ** CHANGE it if you want to change the granularity of the garbage ** collection. */ +#ifndef LUAI_GCSTEPMUL #define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */ +#endif + +#ifndef LUAI_GCSTEPSIZE #define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */ +#endif /* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */ +#ifndef LUA_MINSTACK #define LUA_MINSTACK 20 +#endif /* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */ +#ifndef LUAI_MAXCSTACK #define LUAI_MAXCSTACK 8000 +#endif /* LUAI_MAXCALLS limits the number of nested calls */ +#ifndef LUAI_MAXCALLS #define LUAI_MAXCALLS 20000 +#endif /* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */ +#ifndef LUAI_MAXCCALLS #define LUAI_MAXCCALLS 200 +#endif /* buffer size used for on-stack string operations; this limit depends on native stack size */ +#ifndef LUA_BUFFERSIZE #define LUA_BUFFERSIZE 512 +#endif /* number of valid Lua userdata tags */ +#ifndef LUA_UTAG_LIMIT #define LUA_UTAG_LIMIT 128 +#endif /* upper bound for number of size classes used by page allocator */ +#ifndef LUA_SIZECLASSES #define LUA_SIZECLASSES 32 +#endif /* available number of separate memory categories */ +#ifndef LUA_MEMORY_CATEGORIES #define LUA_MEMORY_CATEGORIES 256 +#endif /* minimum size for the string table (must be power of 2) */ +#ifndef LUA_MINSTRTABSIZE #define LUA_MINSTRTABSIZE 32 +#endif /* maximum number of captures supported by pattern matching */ +#ifndef LUA_MAXCAPTURES #define LUA_MAXCAPTURES 32 +#endif /* }================================================================== */ @@ -122,3 +156,7 @@ void* s; \ long l; \ } + +#define LUA_VECTOR_SIZE 3 /* must be 3 or 4 */ + +#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2 diff --git a/VM/include/lualib.h b/VM/include/lualib.h index fa836955..baf27b47 100644 --- a/VM/include/lualib.h +++ b/VM/include/lualib.h @@ -25,11 +25,17 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def LUALIB_API double luaL_checknumber(lua_State* L, int numArg); LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def); +LUALIB_API int luaL_checkboolean(lua_State* L, int narg); +LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def); + LUALIB_API int luaL_checkinteger(lua_State* L, int numArg); LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def); LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg); LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def); +LUALIB_API const float* luaL_checkvector(lua_State* L, int narg); +LUALIB_API const float* luaL_optvector(lua_State* L, int narg, const float* def); + LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg); LUALIB_API void luaL_checktype(lua_State* L, int narg, int t); LUALIB_API void luaL_checkany(lua_State* L, int narg); diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index a79ba0d4..76043b9c 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -13,6 +13,8 @@ #include +LUAU_FASTFLAG(LuauActivateBeforeExec) + const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; @@ -170,6 +172,12 @@ lua_State* lua_mainthread(lua_State* L) ** basic stack manipulation */ +int lua_absindex(lua_State* L, int idx) +{ + api_check(L, (idx > 0 && idx <= L->top - L->base) || (idx < 0 && -idx <= L->top - L->base) || lua_ispseudo(idx)); + return idx > 0 || lua_ispseudo(idx) ? idx : cast_int(L->top - L->base) + idx + 1; +} + int lua_gettop(lua_State* L) { return cast_int(L->top - L->base); @@ -550,12 +558,21 @@ void lua_pushunsigned(lua_State* L, unsigned u) return; } -void lua_pushvector(lua_State* L, float x, float y, float z) +#if LUA_VECTOR_SIZE == 4 +void lua_pushvector(lua_State* L, float x, float y, float z, float w) { - setvvalue(L->top, x, y, z); + setvvalue(L->top, x, y, z, w); api_incr_top(L); return; } +#else +void lua_pushvector(lua_State* L, float x, float y, float z) +{ + setvvalue(L->top, x, y, z, 0.0f); + api_incr_top(L); + return; +} +#endif void lua_pushlstring(lua_State* L, const char* s, size_t len) { @@ -922,14 +939,21 @@ void lua_call(lua_State* L, int nargs, int nresults) checkresults(L, nargs, nresults); func = L->top - (nargs + 1); - int wasActive = luaC_threadactive(L); - l_setbit(L->stackstate, THREAD_ACTIVEBIT); - luaC_checkthreadsleep(L); + if (FFlag::LuauActivateBeforeExec) + { + luaD_call(L, func, nresults); + } + else + { + int oldactive = luaC_threadactive(L); + l_setbit(L->stackstate, THREAD_ACTIVEBIT); + luaC_checkthreadsleep(L); - luaD_call(L, func, nresults); + luaD_call(L, func, nresults); - if (!wasActive) - resetbit(L->stackstate, THREAD_ACTIVEBIT); + if (!oldactive) + resetbit(L->stackstate, THREAD_ACTIVEBIT); + } adjustresults(L, nresults); return; @@ -970,14 +994,21 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) c.func = L->top - (nargs + 1); /* function to be called */ c.nresults = nresults; - int wasActive = luaC_threadactive(L); - l_setbit(L->stackstate, THREAD_ACTIVEBIT); - luaC_checkthreadsleep(L); + if (FFlag::LuauActivateBeforeExec) + { + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + } + else + { + int oldactive = luaC_threadactive(L); + l_setbit(L->stackstate, THREAD_ACTIVEBIT); + luaC_checkthreadsleep(L); - status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); - if (!wasActive) - resetbit(L->stackstate, THREAD_ACTIVEBIT); + if (!oldactive) + resetbit(L->stackstate, THREAD_ACTIVEBIT); + } adjustresults(L, nresults); return status; @@ -1030,6 +1061,11 @@ int lua_gc(lua_State* L, int what, int data) res = cast_int(g->totalbytes >> 10); break; } + case LUA_GCCOUNTB: + { + res = cast_int(g->totalbytes & 1023); + break; + } case LUA_GCISRUNNING: { res = (g->GCthreshold != SIZE_MAX); @@ -1146,7 +1182,7 @@ void lua_concat(lua_State* L, int n) return; } -void* lua_newuserdata(lua_State* L, size_t sz, int tag) +void* lua_newuserdatatagged(lua_State* L, size_t sz, int tag) { api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); luaC_checkGC(L); @@ -1231,6 +1267,7 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p) int lua_ref(lua_State* L, int idx) { + api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */ int ref = LUA_REFNIL; global_State* g = L->global; StkId p = index2adr(L, idx); diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 2a684ee4..7ed2a62e 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -30,7 +30,7 @@ static const char* currfuncname(lua_State* L) return debugname; } -LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg) +l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg) { const char* fname = currfuncname(L); @@ -40,7 +40,7 @@ LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg) luaL_error(L, "invalid argument #%d (%s)", narg, extramsg); } -LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname) +l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname) { const char* fname = currfuncname(L); const TValue* obj = luaA_toobject(L, narg); @@ -66,7 +66,7 @@ static l_noret tag_error(lua_State* L, int narg, int tag) luaL_typeerrorL(L, narg, lua_typename(L, tag)); } -LUALIB_API void luaL_where(lua_State* L, int level) +void luaL_where(lua_State* L, int level) { lua_Debug ar; if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0) @@ -77,7 +77,7 @@ LUALIB_API void luaL_where(lua_State* L, int level) lua_pushliteral(L, ""); /* else, no information available... */ } -LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...) +l_noret luaL_errorL(lua_State* L, const char* fmt, ...) { va_list argp; va_start(argp, fmt); @@ -90,7 +90,7 @@ LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...) /* }====================================================== */ -LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]) +int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]) { const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg); int i; @@ -101,7 +101,7 @@ LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const c luaL_argerrorL(L, narg, msg); } -LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname) +int luaL_newmetatable(lua_State* L, const char* tname) { lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ if (!lua_isnil(L, -1)) /* name already in use? */ @@ -113,7 +113,7 @@ LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname) return 1; } -LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname) +void* luaL_checkudata(lua_State* L, int ud, const char* tname) { void* p = lua_touserdata(L, ud); if (p != NULL) @@ -131,25 +131,25 @@ LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname) luaL_typeerrorL(L, ud, tname); /* else error */ } -LUALIB_API void luaL_checkstack(lua_State* L, int space, const char* mes) +void luaL_checkstack(lua_State* L, int space, const char* mes) { if (!lua_checkstack(L, space)) luaL_error(L, "stack overflow (%s)", mes); } -LUALIB_API void luaL_checktype(lua_State* L, int narg, int t) +void luaL_checktype(lua_State* L, int narg, int t) { if (lua_type(L, narg) != t) tag_error(L, narg, t); } -LUALIB_API void luaL_checkany(lua_State* L, int narg) +void luaL_checkany(lua_State* L, int narg) { if (lua_type(L, narg) == LUA_TNONE) luaL_error(L, "missing argument #%d", narg); } -LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len) +const char* luaL_checklstring(lua_State* L, int narg, size_t* len) { const char* s = lua_tolstring(L, narg, len); if (!s) @@ -157,7 +157,7 @@ LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len) return s; } -LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len) +const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len) { if (lua_isnoneornil(L, narg)) { @@ -169,7 +169,7 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, return luaL_checklstring(L, narg, len); } -LUALIB_API double luaL_checknumber(lua_State* L, int narg) +double luaL_checknumber(lua_State* L, int narg) { int isnum; double d = lua_tonumberx(L, narg, &isnum); @@ -178,12 +178,28 @@ LUALIB_API double luaL_checknumber(lua_State* L, int narg) return d; } -LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def) +double luaL_optnumber(lua_State* L, int narg, double def) { return luaL_opt(L, luaL_checknumber, narg, def); } -LUALIB_API int luaL_checkinteger(lua_State* L, int narg) +int luaL_checkboolean(lua_State* L, int narg) +{ + // This checks specifically for boolean values, ignoring + // all other truthy/falsy values. If the desired result + // is true if value is present then lua_toboolean should + // directly be used instead. + if (!lua_isboolean(L, narg)) + tag_error(L, narg, LUA_TBOOLEAN); + return lua_toboolean(L, narg); +} + +int luaL_optboolean(lua_State* L, int narg, int def) +{ + return luaL_opt(L, luaL_checkboolean, narg, def); +} + +int luaL_checkinteger(lua_State* L, int narg) { int isnum; int d = lua_tointegerx(L, narg, &isnum); @@ -192,12 +208,12 @@ LUALIB_API int luaL_checkinteger(lua_State* L, int narg) return d; } -LUALIB_API int luaL_optinteger(lua_State* L, int narg, int def) +int luaL_optinteger(lua_State* L, int narg, int def) { return luaL_opt(L, luaL_checkinteger, narg, def); } -LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg) +unsigned luaL_checkunsigned(lua_State* L, int narg) { int isnum; unsigned d = lua_tounsignedx(L, narg, &isnum); @@ -206,12 +222,25 @@ LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg) return d; } -LUALIB_API unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def) +unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def) { return luaL_opt(L, luaL_checkunsigned, narg, def); } -LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event) +const float* luaL_checkvector(lua_State* L, int narg) +{ + const float* v = lua_tovector(L, narg); + if (!v) + tag_error(L, narg, LUA_TVECTOR); + return v; +} + +const float* luaL_optvector(lua_State* L, int narg, const float* def) +{ + return luaL_opt(L, luaL_checkvector, narg, def); +} + +int luaL_getmetafield(lua_State* L, int obj, const char* event) { if (!lua_getmetatable(L, obj)) /* no metatable? */ return 0; @@ -229,7 +258,7 @@ LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event) } } -LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* event) +int luaL_callmeta(lua_State* L, int obj, const char* event) { obj = abs_index(L, obj); if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ @@ -247,7 +276,7 @@ static int libsize(const luaL_Reg* l) return size; } -LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l) +void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l) { if (libname) { @@ -273,7 +302,7 @@ LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* } } -LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint) +const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint) { const char* e; lua_pushvalue(L, idx); @@ -324,7 +353,7 @@ static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desired return newsize; } -LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B) +void luaL_buffinit(lua_State* L, luaL_Buffer* B) { // start with an internal buffer B->p = B->buffer; @@ -334,14 +363,14 @@ LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B) B->storage = nullptr; } -LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size) +char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size) { luaL_buffinit(L, B); luaL_reservebuffer(B, size, -1); return B->p; } -LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc) +char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc) { lua_State* L = B->L; @@ -372,13 +401,13 @@ LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int bo return B->p; } -LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc) +void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc) { if (size_t(B->end - B->p) < size) luaL_extendbuffer(B, size - (B->end - B->p), boxloc); } -LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len) +void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len) { if (size_t(B->end - B->p) < len) luaL_extendbuffer(B, len - (B->end - B->p), -1); @@ -387,7 +416,7 @@ LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len) B->p += len; } -LUALIB_API void luaL_addvalue(luaL_Buffer* B) +void luaL_addvalue(luaL_Buffer* B) { lua_State* L = B->L; @@ -404,7 +433,7 @@ LUALIB_API void luaL_addvalue(luaL_Buffer* B) } } -LUALIB_API void luaL_pushresult(luaL_Buffer* B) +void luaL_pushresult(luaL_Buffer* B) { lua_State* L = B->L; @@ -428,7 +457,7 @@ LUALIB_API void luaL_pushresult(luaL_Buffer* B) } } -LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size) +void luaL_pushresultsize(luaL_Buffer* B, size_t size) { B->p += size; luaL_pushresult(B); @@ -436,7 +465,7 @@ LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size) /* }====================================================== */ -LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len) +const char* luaL_tolstring(lua_State* L, int idx, size_t* len) { if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */ { @@ -462,7 +491,11 @@ LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len) case LUA_TVECTOR: { const float* v = lua_tovector(L, idx); +#if LUA_VECTOR_SIZE == 4 + lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]); +#else lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]); +#endif break; } default: diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index 61798e2b..881c804d 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -401,7 +401,7 @@ static int luaB_newproxy(lua_State* L) bool needsmt = lua_toboolean(L, 1); - lua_newuserdata(L, 0, 0); + lua_newuserdata(L, 0); if (needsmt) { @@ -441,7 +441,7 @@ static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFuncti lua_setfield(L, -2, name); } -LUALIB_API int luaopen_base(lua_State* L) +int luaopen_base(lua_State* L) { /* set global _G */ lua_pushvalue(L, LUA_GLOBALSINDEX); diff --git a/VM/src/lbitlib.cpp b/VM/src/lbitlib.cpp index 907c43c4..8b511edf 100644 --- a/VM/src/lbitlib.cpp +++ b/VM/src/lbitlib.cpp @@ -236,7 +236,7 @@ static const luaL_Reg bitlib[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_bit32(lua_State* L) +int luaopen_bit32(lua_State* L) { luaL_register(L, LUA_BITLIBNAME, bitlib); diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 9ab57ac9..34e9ebc1 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -1018,13 +1018,23 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { +#if LUA_VECTOR_SIZE == 4 + if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2)) +#else if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) +#endif { double x = nvalue(arg0); double y = nvalue(args); double z = nvalue(args + 1); - setvvalue(res, float(x), float(y), float(z)); +#if LUA_VECTOR_SIZE == 4 + double w = nvalue(args + 2); + setvvalue(res, float(x), float(y), float(z), float(w)); +#else + setvvalue(res, float(x), float(y), float(z), 0.0f); +#endif + return 1; } diff --git a/VM/src/lcorolib.cpp b/VM/src/lcorolib.cpp index 0178fae8..abcde779 100644 --- a/VM/src/lcorolib.cpp +++ b/VM/src/lcorolib.cpp @@ -272,7 +272,7 @@ static const luaL_Reg co_funcs[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_coroutine(lua_State* L) +int luaopen_coroutine(lua_State* L) { luaL_register(L, LUA_COLIBNAME, co_funcs); diff --git a/VM/src/ldblib.cpp b/VM/src/ldblib.cpp index 965d2b3d..93d8703a 100644 --- a/VM/src/ldblib.cpp +++ b/VM/src/ldblib.cpp @@ -160,7 +160,7 @@ static const luaL_Reg dblib[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_debug(lua_State* L) +int luaopen_debug(lua_State* L) { luaL_register(L, LUA_DBLIBNAME, dblib); return 1; diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 1259d461..62bbdb7c 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,9 +17,9 @@ #include -LUAU_FASTFLAGVARIABLE(LuauExceptionMessageFix, false) LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false) LUAU_FASTFLAG(LuauCoroutineClose) +LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, false) /* ** {====================================================== @@ -74,35 +74,28 @@ public: const char* what() const throw() override { - if (FFlag::LuauExceptionMessageFix) + // LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error. + if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX) { - // LUA_ERRRUN/LUA_ERRSYNTAX pass an object on the stack which is intended to describe the error. - if (status == LUA_ERRRUN || status == LUA_ERRSYNTAX) + // Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`. + if (const char* str = lua_tostring(L, -1)) { - // Conversion to a string could still fail. For example if a user passes a non-string/non-number argument to `error()`. - if (const char* str = lua_tostring(L, -1)) - { - return str; - } - } - - switch (status) - { - case LUA_ERRRUN: - return "lua_exception: LUA_ERRRUN (no string/number provided as description)"; - case LUA_ERRSYNTAX: - return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)"; - case LUA_ERRMEM: - return "lua_exception: " LUA_MEMERRMSG; - case LUA_ERRERR: - return "lua_exception: " LUA_ERRERRMSG; - default: - return "lua_exception: unexpected exception status"; + return str; } } - else + + switch (status) { - return lua_tostring(L, -1); + case LUA_ERRRUN: + return "lua_exception: LUA_ERRRUN (no string/number provided as description)"; + case LUA_ERRSYNTAX: + return "lua_exception: LUA_ERRSYNTAX (no string/number provided as description)"; + case LUA_ERRMEM: + return "lua_exception: " LUA_MEMERRMSG; + case LUA_ERRERR: + return "lua_exception: " LUA_ERRERRMSG; + default: + return "lua_exception: unexpected exception status"; } } @@ -234,7 +227,22 @@ void luaD_call(lua_State* L, StkId func, int nResults) if (luau_precall(L, func, nResults) == PCRLUA) { /* is a Lua function? */ L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */ - luau_execute(L); /* call it */ + + if (FFlag::LuauActivateBeforeExec) + { + int oldactive = luaC_threadactive(L); + l_setbit(L->stackstate, THREAD_ACTIVEBIT); + luaC_checkthreadsleep(L); + + luau_execute(L); /* call it */ + + if (!oldactive) + resetbit(L->stackstate, THREAD_ACTIVEBIT); + } + else + { + luau_execute(L); /* call it */ + } } L->nCcalls--; luaC_checkGC(L); @@ -527,10 +535,10 @@ static void restore_stack_limit(lua_State* L) int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef) { - int status; unsigned short oldnCcalls = L->nCcalls; ptrdiff_t old_ci = saveci(L, L->ci); - status = luaD_rawrunprotected(L, func, u); + int oldactive = luaC_threadactive(L); + int status = luaD_rawrunprotected(L, func, u); if (status != 0) { // call user-defined error function (used in xpcall) @@ -541,6 +549,13 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e status = LUA_ERRERR; } + if (FFlag::LuauActivateBeforeExec) + { + // since the call failed with an error, we might have to reset the 'active' thread state + if (!oldactive) + resetbit(L->stackstate, THREAD_ACTIVEBIT); + } + if (FFlag::LuauCcallRestoreFix) { // Restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 11f79d1a..ab416041 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -10,9 +10,7 @@ #include "ldo.h" #include -#include -LUAU_FASTFLAGVARIABLE(LuauRescanGrayAgainForwardBarrier, false) LUAU_FASTFLAGVARIABLE(LuauSeparateAtomic, false) LUAU_FASTFLAG(LuauArrayBoundary) @@ -988,7 +986,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v) GCObject* o = obj2gco(t); // in the second propagation stage, table assignment barrier works as a forward barrier - if (FFlag::LuauRescanGrayAgainForwardBarrier && g->gcstate == GCSpropagateagain) + if (g->gcstate == GCSpropagateagain) { LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); reallymarkobject(g, v); @@ -1044,550 +1042,6 @@ void luaC_linkupval(lua_State* L, UpVal* uv) } } -static void validateobjref(global_State* g, GCObject* f, GCObject* t) -{ - LUAU_ASSERT(!isdead(g, t)); - - if (keepinvariant(g)) - { - /* basic incremental invariant: black can't point to white */ - LUAU_ASSERT(!(isblack(f) && iswhite(t))); - } -} - -static void validateref(global_State* g, GCObject* f, TValue* v) -{ - if (iscollectable(v)) - { - LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt); - validateobjref(g, f, gcvalue(v)); - } -} - -static void validatetable(global_State* g, Table* h) -{ - int sizenode = 1 << h->lsizenode; - - if (FFlag::LuauArrayBoundary) - LUAU_ASSERT(h->lastfree <= sizenode); - else - LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode); - - if (h->metatable) - validateobjref(g, obj2gco(h), obj2gco(h->metatable)); - - for (int i = 0; i < h->sizearray; ++i) - validateref(g, obj2gco(h), &h->array[i]); - - for (int i = 0; i < sizenode; ++i) - { - LuaNode* n = &h->node[i]; - - LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); - LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode); - - if (!ttisnil(gval(n))) - { - TValue k = {}; - k.tt = gkey(n)->tt; - k.value = gkey(n)->value; - - validateref(g, obj2gco(h), &k); - validateref(g, obj2gco(h), gval(n)); - } - } -} - -static void validateclosure(global_State* g, Closure* cl) -{ - validateobjref(g, obj2gco(cl), obj2gco(cl->env)); - - if (cl->isC) - { - for (int i = 0; i < cl->nupvalues; ++i) - validateref(g, obj2gco(cl), &cl->c.upvals[i]); - } - else - { - LUAU_ASSERT(cl->nupvalues == cl->l.p->nups); - - validateobjref(g, obj2gco(cl), obj2gco(cl->l.p)); - - for (int i = 0; i < cl->nupvalues; ++i) - validateref(g, obj2gco(cl), &cl->l.uprefs[i]); - } -} - -static void validatestack(global_State* g, lua_State* l) -{ - validateref(g, obj2gco(l), gt(l)); - - for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci) - { - LUAU_ASSERT(l->stack <= ci->base); - LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top); - LUAU_ASSERT(ci->top <= l->stack_last); - } - - // note: stack refs can violate gc invariant so we only check for liveness - for (StkId o = l->stack; o < l->top; ++o) - checkliveness(g, o); - - if (l->namecall) - validateobjref(g, obj2gco(l), obj2gco(l->namecall)); - - for (GCObject* uv = l->openupval; uv; uv = uv->gch.next) - { - LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL); - LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value); - } -} - -static void validateproto(global_State* g, Proto* f) -{ - if (f->source) - validateobjref(g, obj2gco(f), obj2gco(f->source)); - - if (f->debugname) - validateobjref(g, obj2gco(f), obj2gco(f->debugname)); - - for (int i = 0; i < f->sizek; ++i) - validateref(g, obj2gco(f), &f->k[i]); - - for (int i = 0; i < f->sizeupvalues; ++i) - if (f->upvalues[i]) - validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i])); - - for (int i = 0; i < f->sizep; ++i) - if (f->p[i]) - validateobjref(g, obj2gco(f), obj2gco(f->p[i])); - - for (int i = 0; i < f->sizelocvars; i++) - if (f->locvars[i].varname) - validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname)); -} - -static void validateobj(global_State* g, GCObject* o) -{ - /* dead objects can only occur during sweep */ - if (isdead(g, o)) - { - LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); - return; - } - - switch (o->gch.tt) - { - case LUA_TSTRING: - break; - - case LUA_TTABLE: - validatetable(g, gco2h(o)); - break; - - case LUA_TFUNCTION: - validateclosure(g, gco2cl(o)); - break; - - case LUA_TUSERDATA: - if (gco2u(o)->metatable) - validateobjref(g, o, obj2gco(gco2u(o)->metatable)); - break; - - case LUA_TTHREAD: - validatestack(g, gco2th(o)); - break; - - case LUA_TPROTO: - validateproto(g, gco2p(o)); - break; - - case LUA_TUPVAL: - validateref(g, o, gco2uv(o)->v); - break; - - default: - LUAU_ASSERT(!"unexpected object type"); - } -} - -static void validatelist(global_State* g, GCObject* o) -{ - while (o) - { - validateobj(g, o); - - o = o->gch.next; - } -} - -static void validategraylist(global_State* g, GCObject* o) -{ - if (!keepinvariant(g)) - return; - - while (o) - { - LUAU_ASSERT(isgray(o)); - - switch (o->gch.tt) - { - case LUA_TTABLE: - o = gco2h(o)->gclist; - break; - case LUA_TFUNCTION: - o = gco2cl(o)->gclist; - break; - case LUA_TTHREAD: - o = gco2th(o)->gclist; - break; - case LUA_TPROTO: - o = gco2p(o)->gclist; - break; - default: - LUAU_ASSERT(!"unknown object in gray list"); - return; - } - } -} - -void luaC_validate(lua_State* L) -{ - global_State* g = L->global; - - LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread))); - checkliveness(g, &g->registry); - - for (int i = 0; i < LUA_T_COUNT; ++i) - if (g->mt[i]) - LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i]))); - - validategraylist(g, g->weak); - validategraylist(g, g->gray); - validategraylist(g, g->grayagain); - - for (int i = 0; i < g->strt.size; ++i) - validatelist(g, g->strt.hash[i]); - - validatelist(g, g->rootgc); - validatelist(g, g->strbufgc); - - for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) - { - LUAU_ASSERT(uv->tt == LUA_TUPVAL); - LUAU_ASSERT(uv->v != &uv->u.value); - LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); - } -} - -inline bool safejson(char ch) -{ - return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"'; -} - -static void dumpref(FILE* f, GCObject* o) -{ - fprintf(f, "\"%p\"", o); -} - -static void dumprefs(FILE* f, TValue* data, size_t size) -{ - bool first = true; - - for (size_t i = 0; i < size; ++i) - { - if (iscollectable(&data[i])) - { - if (!first) - fputc(',', f); - first = false; - - dumpref(f, gcvalue(&data[i])); - } - } -} - -static void dumpstringdata(FILE* f, const char* data, size_t len) -{ - for (size_t i = 0; i < len; ++i) - fputc(safejson(data[i]) ? data[i] : '?', f); -} - -static void dumpstring(FILE* f, TString* ts) -{ - fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len))); - dumpstringdata(f, ts->data, ts->len); - fprintf(f, "\"}"); -} - -static void dumptable(FILE* f, Table* h) -{ - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); - - fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size)); - - if (h->node != &luaH_dummynode) - { - fprintf(f, ",\"pairs\":["); - - bool first = true; - - for (int i = 0; i < sizenode(h); ++i) - { - const LuaNode& n = h->node[i]; - - if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val))) - { - if (!first) - fputc(',', f); - first = false; - - if (iscollectable(&n.key)) - dumpref(f, gcvalue(&n.key)); - else - fprintf(f, "null"); - - fputc(',', f); - - if (iscollectable(&n.val)) - dumpref(f, gcvalue(&n.val)); - else - fprintf(f, "null"); - } - } - - fprintf(f, "]"); - } - if (h->sizearray) - { - fprintf(f, ",\"array\":["); - dumprefs(f, h->array, h->sizearray); - fprintf(f, "]"); - } - if (h->metatable) - { - fprintf(f, ",\"metatable\":"); - dumpref(f, obj2gco(h->metatable)); - } - fprintf(f, "}"); -} - -static void dumpclosure(FILE* f, Closure* cl) -{ - fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat, - cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues))); - - fprintf(f, ",\"env\":"); - dumpref(f, obj2gco(cl->env)); - if (cl->isC) - { - if (cl->nupvalues) - { - fprintf(f, ",\"upvalues\":["); - dumprefs(f, cl->c.upvals, cl->nupvalues); - fprintf(f, "]"); - } - } - else - { - fprintf(f, ",\"proto\":"); - dumpref(f, obj2gco(cl->l.p)); - if (cl->nupvalues) - { - fprintf(f, ",\"upvalues\":["); - dumprefs(f, cl->l.uprefs, cl->nupvalues); - fprintf(f, "]"); - } - } - fprintf(f, "}"); -} - -static void dumpudata(FILE* f, Udata* u) -{ - fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag); - - if (u->metatable) - { - fprintf(f, ",\"metatable\":"); - dumpref(f, obj2gco(u->metatable)); - } - fprintf(f, "}"); -} - -static void dumpthread(FILE* f, lua_State* th) -{ - size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; - - fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size)); - - if (iscollectable(&th->l_gt)) - { - fprintf(f, ",\"env\":"); - dumpref(f, gcvalue(&th->l_gt)); - } - - Closure* tcl = 0; - for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci) - { - if (ttisfunction(ci->func)) - { - tcl = clvalue(ci->func); - break; - } - } - - if (tcl && !tcl->isC && tcl->l.p->source) - { - Proto* p = tcl->l.p; - - fprintf(f, ",\"source\":\""); - dumpstringdata(f, p->source->data, p->source->len); - fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0); - } - - if (th->top > th->stack) - { - fprintf(f, ",\"stack\":["); - dumprefs(f, th->stack, th->top - th->stack); - fprintf(f, "]"); - } - fprintf(f, "}"); -} - -static void dumpproto(FILE* f, Proto* p) -{ - size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo + - sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues; - - fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size)); - - if (p->source) - { - fprintf(f, ",\"source\":\""); - dumpstringdata(f, p->source->data, p->source->len); - fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0); - } - - if (p->sizek) - { - fprintf(f, ",\"constants\":["); - dumprefs(f, p->k, p->sizek); - fprintf(f, "]"); - } - - if (p->sizep) - { - fprintf(f, ",\"protos\":["); - for (int i = 0; i < p->sizep; ++i) - { - if (i != 0) - fputc(',', f); - dumpref(f, obj2gco(p->p[i])); - } - fprintf(f, "]"); - } - - fprintf(f, "}"); -} - -static void dumpupval(FILE* f, UpVal* uv) -{ - fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal))); - - if (iscollectable(uv->v)) - { - fprintf(f, ",\"object\":"); - dumpref(f, gcvalue(uv->v)); - } - fprintf(f, "}"); -} - -static void dumpobj(FILE* f, GCObject* o) -{ - switch (o->gch.tt) - { - case LUA_TSTRING: - return dumpstring(f, gco2ts(o)); - - case LUA_TTABLE: - return dumptable(f, gco2h(o)); - - case LUA_TFUNCTION: - return dumpclosure(f, gco2cl(o)); - - case LUA_TUSERDATA: - return dumpudata(f, gco2u(o)); - - case LUA_TTHREAD: - return dumpthread(f, gco2th(o)); - - case LUA_TPROTO: - return dumpproto(f, gco2p(o)); - - case LUA_TUPVAL: - return dumpupval(f, gco2uv(o)); - - default: - LUAU_ASSERT(0); - } -} - -static void dumplist(FILE* f, GCObject* o) -{ - while (o) - { - dumpref(f, o); - fputc(':', f); - dumpobj(f, o); - fputc(',', f); - fputc('\n', f); - - // thread has additional list containing collectable objects that are not present in rootgc - if (o->gch.tt == LUA_TTHREAD) - dumplist(f, gco2th(o)->openupval); - - o = o->gch.next; - } -} - -void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)) -{ - global_State* g = L->global; - FILE* f = static_cast(file); - - fprintf(f, "{\"objects\":{\n"); - dumplist(f, g->rootgc); - dumplist(f, g->strbufgc); - for (int i = 0; i < g->strt.size; ++i) - dumplist(f, g->strt.hash[i]); - - fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing , - fprintf(f, "},\"roots\":{\n"); - fprintf(f, "\"mainthread\":"); - dumpref(f, obj2gco(g->mainthread)); - fprintf(f, ",\"registry\":"); - dumpref(f, gcvalue(&g->registry)); - - fprintf(f, "},\"stats\":{\n"); - - fprintf(f, "\"size\":%d,\n", int(g->totalbytes)); - - fprintf(f, "\"categories\":{\n"); - for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++) - { - if (size_t bytes = g->memcatbytes[i]) - { - if (categoryName) - fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes)); - else - fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes)); - } - } - fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing , - fprintf(f, "}\n"); - fprintf(f, "}}\n"); -} - // measure the allocation rate in bytes/sec // returns -1 if allocation rate cannot be measured int64_t luaC_allocationrate(lua_State* L) diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp new file mode 100644 index 00000000..a79e7b95 --- /dev/null +++ b/VM/src/lgcdebug.cpp @@ -0,0 +1,558 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details +#include "lgc.h" + +#include "lobject.h" +#include "lstate.h" +#include "ltable.h" +#include "lfunc.h" +#include "lstring.h" + +#include +#include + +LUAU_FASTFLAG(LuauArrayBoundary) + +static void validateobjref(global_State* g, GCObject* f, GCObject* t) +{ + LUAU_ASSERT(!isdead(g, t)); + + if (keepinvariant(g)) + { + /* basic incremental invariant: black can't point to white */ + LUAU_ASSERT(!(isblack(f) && iswhite(t))); + } +} + +static void validateref(global_State* g, GCObject* f, TValue* v) +{ + if (iscollectable(v)) + { + LUAU_ASSERT(ttype(v) == gcvalue(v)->gch.tt); + validateobjref(g, f, gcvalue(v)); + } +} + +static void validatetable(global_State* g, Table* h) +{ + int sizenode = 1 << h->lsizenode; + + if (FFlag::LuauArrayBoundary) + LUAU_ASSERT(h->lastfree <= sizenode); + else + LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode); + + if (h->metatable) + validateobjref(g, obj2gco(h), obj2gco(h->metatable)); + + for (int i = 0; i < h->sizearray; ++i) + validateref(g, obj2gco(h), &h->array[i]); + + for (int i = 0; i < sizenode; ++i) + { + LuaNode* n = &h->node[i]; + + LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); + LUAU_ASSERT(i + gnext(n) >= 0 && i + gnext(n) < sizenode); + + if (!ttisnil(gval(n))) + { + TValue k = {}; + k.tt = gkey(n)->tt; + k.value = gkey(n)->value; + + validateref(g, obj2gco(h), &k); + validateref(g, obj2gco(h), gval(n)); + } + } +} + +static void validateclosure(global_State* g, Closure* cl) +{ + validateobjref(g, obj2gco(cl), obj2gco(cl->env)); + + if (cl->isC) + { + for (int i = 0; i < cl->nupvalues; ++i) + validateref(g, obj2gco(cl), &cl->c.upvals[i]); + } + else + { + LUAU_ASSERT(cl->nupvalues == cl->l.p->nups); + + validateobjref(g, obj2gco(cl), obj2gco(cl->l.p)); + + for (int i = 0; i < cl->nupvalues; ++i) + validateref(g, obj2gco(cl), &cl->l.uprefs[i]); + } +} + +static void validatestack(global_State* g, lua_State* l) +{ + validateref(g, obj2gco(l), gt(l)); + + for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci) + { + LUAU_ASSERT(l->stack <= ci->base); + LUAU_ASSERT(ci->func <= ci->base && ci->base <= ci->top); + LUAU_ASSERT(ci->top <= l->stack_last); + } + + // note: stack refs can violate gc invariant so we only check for liveness + for (StkId o = l->stack; o < l->top; ++o) + checkliveness(g, o); + + if (l->namecall) + validateobjref(g, obj2gco(l), obj2gco(l->namecall)); + + for (GCObject* uv = l->openupval; uv; uv = uv->gch.next) + { + LUAU_ASSERT(uv->gch.tt == LUA_TUPVAL); + LUAU_ASSERT(gco2uv(uv)->v != &gco2uv(uv)->u.value); + } +} + +static void validateproto(global_State* g, Proto* f) +{ + if (f->source) + validateobjref(g, obj2gco(f), obj2gco(f->source)); + + if (f->debugname) + validateobjref(g, obj2gco(f), obj2gco(f->debugname)); + + for (int i = 0; i < f->sizek; ++i) + validateref(g, obj2gco(f), &f->k[i]); + + for (int i = 0; i < f->sizeupvalues; ++i) + if (f->upvalues[i]) + validateobjref(g, obj2gco(f), obj2gco(f->upvalues[i])); + + for (int i = 0; i < f->sizep; ++i) + if (f->p[i]) + validateobjref(g, obj2gco(f), obj2gco(f->p[i])); + + for (int i = 0; i < f->sizelocvars; i++) + if (f->locvars[i].varname) + validateobjref(g, obj2gco(f), obj2gco(f->locvars[i].varname)); +} + +static void validateobj(global_State* g, GCObject* o) +{ + /* dead objects can only occur during sweep */ + if (isdead(g, o)) + { + LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); + return; + } + + switch (o->gch.tt) + { + case LUA_TSTRING: + break; + + case LUA_TTABLE: + validatetable(g, gco2h(o)); + break; + + case LUA_TFUNCTION: + validateclosure(g, gco2cl(o)); + break; + + case LUA_TUSERDATA: + if (gco2u(o)->metatable) + validateobjref(g, o, obj2gco(gco2u(o)->metatable)); + break; + + case LUA_TTHREAD: + validatestack(g, gco2th(o)); + break; + + case LUA_TPROTO: + validateproto(g, gco2p(o)); + break; + + case LUA_TUPVAL: + validateref(g, o, gco2uv(o)->v); + break; + + default: + LUAU_ASSERT(!"unexpected object type"); + } +} + +static void validatelist(global_State* g, GCObject* o) +{ + while (o) + { + validateobj(g, o); + + o = o->gch.next; + } +} + +static void validategraylist(global_State* g, GCObject* o) +{ + if (!keepinvariant(g)) + return; + + while (o) + { + LUAU_ASSERT(isgray(o)); + + switch (o->gch.tt) + { + case LUA_TTABLE: + o = gco2h(o)->gclist; + break; + case LUA_TFUNCTION: + o = gco2cl(o)->gclist; + break; + case LUA_TTHREAD: + o = gco2th(o)->gclist; + break; + case LUA_TPROTO: + o = gco2p(o)->gclist; + break; + default: + LUAU_ASSERT(!"unknown object in gray list"); + return; + } + } +} + +void luaC_validate(lua_State* L) +{ + global_State* g = L->global; + + LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread))); + checkliveness(g, &g->registry); + + for (int i = 0; i < LUA_T_COUNT; ++i) + if (g->mt[i]) + LUAU_ASSERT(!isdead(g, obj2gco(g->mt[i]))); + + validategraylist(g, g->weak); + validategraylist(g, g->gray); + validategraylist(g, g->grayagain); + + for (int i = 0; i < g->strt.size; ++i) + validatelist(g, g->strt.hash[i]); + + validatelist(g, g->rootgc); + validatelist(g, g->strbufgc); + + for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) + { + LUAU_ASSERT(uv->tt == LUA_TUPVAL); + LUAU_ASSERT(uv->v != &uv->u.value); + LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + } +} + +inline bool safejson(char ch) +{ + return unsigned(ch) < 128 && ch >= 32 && ch != '\\' && ch != '\"'; +} + +static void dumpref(FILE* f, GCObject* o) +{ + fprintf(f, "\"%p\"", o); +} + +static void dumprefs(FILE* f, TValue* data, size_t size) +{ + bool first = true; + + for (size_t i = 0; i < size; ++i) + { + if (iscollectable(&data[i])) + { + if (!first) + fputc(',', f); + first = false; + + dumpref(f, gcvalue(&data[i])); + } + } +} + +static void dumpstringdata(FILE* f, const char* data, size_t len) +{ + for (size_t i = 0; i < len; ++i) + fputc(safejson(data[i]) ? data[i] : '?', f); +} + +static void dumpstring(FILE* f, TString* ts) +{ + fprintf(f, "{\"type\":\"string\",\"cat\":%d,\"size\":%d,\"data\":\"", ts->memcat, int(sizestring(ts->len))); + dumpstringdata(f, ts->data, ts->len); + fprintf(f, "\"}"); +} + +static void dumptable(FILE* f, Table* h) +{ + size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + + fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size)); + + if (h->node != &luaH_dummynode) + { + fprintf(f, ",\"pairs\":["); + + bool first = true; + + for (int i = 0; i < sizenode(h); ++i) + { + const LuaNode& n = h->node[i]; + + if (!ttisnil(&n.val) && (iscollectable(&n.key) || iscollectable(&n.val))) + { + if (!first) + fputc(',', f); + first = false; + + if (iscollectable(&n.key)) + dumpref(f, gcvalue(&n.key)); + else + fprintf(f, "null"); + + fputc(',', f); + + if (iscollectable(&n.val)) + dumpref(f, gcvalue(&n.val)); + else + fprintf(f, "null"); + } + } + + fprintf(f, "]"); + } + if (h->sizearray) + { + fprintf(f, ",\"array\":["); + dumprefs(f, h->array, h->sizearray); + fprintf(f, "]"); + } + if (h->metatable) + { + fprintf(f, ",\"metatable\":"); + dumpref(f, obj2gco(h->metatable)); + } + fprintf(f, "}"); +} + +static void dumpclosure(FILE* f, Closure* cl) +{ + fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat, + cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues))); + + fprintf(f, ",\"env\":"); + dumpref(f, obj2gco(cl->env)); + if (cl->isC) + { + if (cl->nupvalues) + { + fprintf(f, ",\"upvalues\":["); + dumprefs(f, cl->c.upvals, cl->nupvalues); + fprintf(f, "]"); + } + } + else + { + fprintf(f, ",\"proto\":"); + dumpref(f, obj2gco(cl->l.p)); + if (cl->nupvalues) + { + fprintf(f, ",\"upvalues\":["); + dumprefs(f, cl->l.uprefs, cl->nupvalues); + fprintf(f, "]"); + } + } + fprintf(f, "}"); +} + +static void dumpudata(FILE* f, Udata* u) +{ + fprintf(f, "{\"type\":\"userdata\",\"cat\":%d,\"size\":%d,\"tag\":%d", u->memcat, int(sizeudata(u->len)), u->tag); + + if (u->metatable) + { + fprintf(f, ",\"metatable\":"); + dumpref(f, obj2gco(u->metatable)); + } + fprintf(f, "}"); +} + +static void dumpthread(FILE* f, lua_State* th) +{ + size_t size = sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; + + fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size)); + + if (iscollectable(&th->l_gt)) + { + fprintf(f, ",\"env\":"); + dumpref(f, gcvalue(&th->l_gt)); + } + + Closure* tcl = 0; + for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci) + { + if (ttisfunction(ci->func)) + { + tcl = clvalue(ci->func); + break; + } + } + + if (tcl && !tcl->isC && tcl->l.p->source) + { + Proto* p = tcl->l.p; + + fprintf(f, ",\"source\":\""); + dumpstringdata(f, p->source->data, p->source->len); + fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0); + } + + if (th->top > th->stack) + { + fprintf(f, ",\"stack\":["); + dumprefs(f, th->stack, th->top - th->stack); + fprintf(f, "]"); + } + fprintf(f, "}"); +} + +static void dumpproto(FILE* f, Proto* p) +{ + size_t size = sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo + + sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues; + + fprintf(f, "{\"type\":\"proto\",\"cat\":%d,\"size\":%d", p->memcat, int(size)); + + if (p->source) + { + fprintf(f, ",\"source\":\""); + dumpstringdata(f, p->source->data, p->source->len); + fprintf(f, "\",\"line\":%d", p->abslineinfo ? p->abslineinfo[0] : 0); + } + + if (p->sizek) + { + fprintf(f, ",\"constants\":["); + dumprefs(f, p->k, p->sizek); + fprintf(f, "]"); + } + + if (p->sizep) + { + fprintf(f, ",\"protos\":["); + for (int i = 0; i < p->sizep; ++i) + { + if (i != 0) + fputc(',', f); + dumpref(f, obj2gco(p->p[i])); + } + fprintf(f, "]"); + } + + fprintf(f, "}"); +} + +static void dumpupval(FILE* f, UpVal* uv) +{ + fprintf(f, "{\"type\":\"upvalue\",\"cat\":%d,\"size\":%d", uv->memcat, int(sizeof(UpVal))); + + if (iscollectable(uv->v)) + { + fprintf(f, ",\"object\":"); + dumpref(f, gcvalue(uv->v)); + } + fprintf(f, "}"); +} + +static void dumpobj(FILE* f, GCObject* o) +{ + switch (o->gch.tt) + { + case LUA_TSTRING: + return dumpstring(f, gco2ts(o)); + + case LUA_TTABLE: + return dumptable(f, gco2h(o)); + + case LUA_TFUNCTION: + return dumpclosure(f, gco2cl(o)); + + case LUA_TUSERDATA: + return dumpudata(f, gco2u(o)); + + case LUA_TTHREAD: + return dumpthread(f, gco2th(o)); + + case LUA_TPROTO: + return dumpproto(f, gco2p(o)); + + case LUA_TUPVAL: + return dumpupval(f, gco2uv(o)); + + default: + LUAU_ASSERT(0); + } +} + +static void dumplist(FILE* f, GCObject* o) +{ + while (o) + { + dumpref(f, o); + fputc(':', f); + dumpobj(f, o); + fputc(',', f); + fputc('\n', f); + + // thread has additional list containing collectable objects that are not present in rootgc + if (o->gch.tt == LUA_TTHREAD) + dumplist(f, gco2th(o)->openupval); + + o = o->gch.next; + } +} + +void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)) +{ + global_State* g = L->global; + FILE* f = static_cast(file); + + fprintf(f, "{\"objects\":{\n"); + dumplist(f, g->rootgc); + dumplist(f, g->strbufgc); + for (int i = 0; i < g->strt.size; ++i) + dumplist(f, g->strt.hash[i]); + + fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing , + fprintf(f, "},\"roots\":{\n"); + fprintf(f, "\"mainthread\":"); + dumpref(f, obj2gco(g->mainthread)); + fprintf(f, ",\"registry\":"); + dumpref(f, gcvalue(&g->registry)); + + fprintf(f, "},\"stats\":{\n"); + + fprintf(f, "\"size\":%d,\n", int(g->totalbytes)); + + fprintf(f, "\"categories\":{\n"); + for (int i = 0; i < LUA_MEMORY_CATEGORIES; i++) + { + if (size_t bytes = g->memcatbytes[i]) + { + if (categoryName) + fprintf(f, "\"%d\":{\"name\":\"%s\", \"size\":%d},\n", i, categoryName(L, i), int(bytes)); + else + fprintf(f, "\"%d\":{\"size\":%d},\n", i, int(bytes)); + } + } + fprintf(f, "\"none\":{}\n"); // to avoid issues with trailing , + fprintf(f, "}\n"); + fprintf(f, "}}\n"); +} diff --git a/VM/src/linit.cpp b/VM/src/linit.cpp index 4e40165a..c93f431f 100644 --- a/VM/src/linit.cpp +++ b/VM/src/linit.cpp @@ -17,7 +17,7 @@ static const luaL_Reg lualibs[] = { {NULL, NULL}, }; -LUALIB_API void luaL_openlibs(lua_State* L) +void luaL_openlibs(lua_State* L) { const luaL_Reg* lib = lualibs; for (; lib->func; lib++) @@ -28,7 +28,7 @@ LUALIB_API void luaL_openlibs(lua_State* L) } } -LUALIB_API void luaL_sandbox(lua_State* L) +void luaL_sandbox(lua_State* L) { // set all libraries to read-only lua_pushnil(L); @@ -44,14 +44,14 @@ LUALIB_API void luaL_sandbox(lua_State* L) lua_pushliteral(L, ""); lua_getmetatable(L, -1); lua_setreadonly(L, -1, true); - lua_pop(L, 1); + lua_pop(L, 2); // set globals to readonly and activate safeenv since the env is immutable lua_setreadonly(L, LUA_GLOBALSINDEX, true); lua_setsafeenv(L, LUA_GLOBALSINDEX, true); } -LUALIB_API void luaL_sandboxthread(lua_State* L) +void luaL_sandboxthread(lua_State* L) { // create new global table that proxies reads to original table lua_newtable(L); @@ -81,7 +81,7 @@ static void* l_alloc(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsi return realloc(ptr, nsize); } -LUALIB_API lua_State* luaL_newstate(void) +lua_State* luaL_newstate(void) { return lua_newstate(l_alloc, NULL); } diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 8e476a52..a6e7b494 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -385,8 +385,7 @@ static int math_sign(lua_State* L) static int math_round(lua_State* L) { - double v = luaL_checknumber(L, 1); - lua_pushnumber(L, round(v)); + lua_pushnumber(L, round(luaL_checknumber(L, 1))); return 1; } @@ -429,7 +428,7 @@ static const luaL_Reg mathlib[] = { /* ** Open math library */ -LUALIB_API int luaopen_math(lua_State* L) +int luaopen_math(lua_State* L) { uint64_t seed = uintptr_t(L); seed ^= time(NULL); diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index d8b265cb..9f9d4a98 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -33,11 +33,17 @@ #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32) #endif +#if LUA_VECTOR_SIZE == 4 +static_assert(sizeof(TValue) == ABISWITCH(24, 24, 24), "size mismatch for value"); +static_assert(sizeof(LuaNode) == ABISWITCH(48, 48, 48), "size mismatch for table entry"); +#else static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value"); +static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); +#endif + static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header"); static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header"); -static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kMaxSmallSize = 512; diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index 43f8014b..67f832dc 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -18,12 +18,20 @@ inline bool luai_veceq(const float* a, const float* b) { +#if LUA_VECTOR_SIZE == 4 + return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; +#else return a[0] == b[0] && a[1] == b[1] && a[2] == b[2]; +#endif } inline bool luai_vecisnan(const float* a) { +#if LUA_VECTOR_SIZE == 4 + return a[0] != a[0] || a[1] != a[1] || a[2] != a[2] || a[3] != a[3]; +#else return a[0] != a[0] || a[1] != a[1] || a[2] != a[2]; +#endif } LUAU_FASTMATH_BEGIN diff --git a/VM/src/lobject.cpp b/VM/src/lobject.cpp index bf13e6e9..370c7b28 100644 --- a/VM/src/lobject.cpp +++ b/VM/src/lobject.cpp @@ -15,7 +15,7 @@ -const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; +const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL}; int luaO_log2(unsigned int x) { diff --git a/VM/src/lobject.h b/VM/src/lobject.h index c5f2e2f4..ba040af6 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -47,7 +47,7 @@ typedef union typedef struct lua_TValue { Value value; - int extra; + int extra[LUA_EXTRA_SIZE]; int tt; } TValue; @@ -105,7 +105,19 @@ typedef struct lua_TValue i_o->tt = LUA_TNUMBER; \ } -#define setvvalue(obj, x, y, z) \ +#if LUA_VECTOR_SIZE == 4 +#define setvvalue(obj, x, y, z, w) \ + { \ + TValue* i_o = (obj); \ + float* i_v = i_o->value.v; \ + i_v[0] = (x); \ + i_v[1] = (y); \ + i_v[2] = (z); \ + i_v[3] = (w); \ + i_o->tt = LUA_TVECTOR; \ + } +#else +#define setvvalue(obj, x, y, z, w) \ { \ TValue* i_o = (obj); \ float* i_v = i_o->value.v; \ @@ -114,6 +126,7 @@ typedef struct lua_TValue i_v[2] = (z); \ i_o->tt = LUA_TVECTOR; \ } +#endif #define setpvalue(obj, x) \ { \ @@ -364,7 +377,7 @@ typedef struct Closure typedef struct TKey { ::Value value; - int extra; + int extra[LUA_EXTRA_SIZE]; unsigned tt : 4; int next : 28; /* for chaining */ } TKey; @@ -381,7 +394,7 @@ typedef struct LuaNode LuaNode* n_ = (node); \ const TValue* i_o = (obj); \ n_->key.value = i_o->value; \ - n_->key.extra = i_o->extra; \ + memcpy(n_->key.extra, i_o->extra, sizeof(n_->key.extra)); \ n_->key.tt = i_o->tt; \ checkliveness(L->global, i_o); \ } @@ -392,7 +405,7 @@ typedef struct LuaNode TValue* i_o = (obj); \ const LuaNode* n_ = (node); \ i_o->value = n_->key.value; \ - i_o->extra = n_->key.extra; \ + memcpy(i_o->extra, n_->key.extra, sizeof(i_o->extra)); \ i_o->tt = n_->key.tt; \ checkliveness(L->global, i_o); \ } diff --git a/VM/src/loslib.cpp b/VM/src/loslib.cpp index 8eaef60c..b5901865 100644 --- a/VM/src/loslib.cpp +++ b/VM/src/loslib.cpp @@ -186,7 +186,7 @@ static const luaL_Reg syslib[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_os(lua_State* L) +int luaopen_os(lua_State* L) { luaL_register(L, LUA_OSLIBNAME, syslib); return 1; diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index b576f809..0b3054ae 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -1657,7 +1657,7 @@ static void createmetatable(lua_State* L) /* ** Open string library */ -LUALIB_API int luaopen_string(lua_State* L) +int luaopen_string(lua_State* L) { luaL_register(L, LUA_STRLIBNAME, strlib); createmetatable(L); diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 07d22d59..0b55fcea 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -31,18 +31,19 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false) #define MAXSIZE (1 << MAXBITS) static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect"); + // TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case -static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); +static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); +static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); +static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->flags = 0 // empty hash data points to dummynode so that we can always dereference it const LuaNode luaH_dummynode = { - {{NULL}, 0, LUA_TNIL}, /* value */ - {{NULL}, 0, LUA_TNIL, 0} /* key */ + {{NULL}, {0}, LUA_TNIL}, /* value */ + {{NULL}, {0}, LUA_TNIL, 0} /* key */ }; #define dummynode (&luaH_dummynode) @@ -96,7 +97,7 @@ static LuaNode* hashnum(const Table* t, double n) static LuaNode* hashvec(const Table* t, const float* v) { - unsigned int i[3]; + unsigned int i[LUA_VECTOR_SIZE]; memcpy(i, v, sizeof(i)); // convert -0 to 0 to make sure they hash to the same value @@ -112,6 +113,12 @@ static LuaNode* hashvec(const Table* t, const float* v) // Optimized Spatial Hashing for Collision Detection of Deformable Objects unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791); +#if LUA_VECTOR_SIZE == 4 + i[3] = (i[3] == 0x8000000) ? 0 : i[3]; + i[3] ^= i[3] >> 17; + h ^= i[3] * 39916801; +#endif + return hashpow2(t, h); } diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 37025818..0d3374ef 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -527,7 +527,7 @@ static const luaL_Reg tab_funcs[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_table(lua_State* L) +int luaopen_table(lua_State* L) { luaL_register(L, LUA_TABLIBNAME, tab_funcs); diff --git a/VM/src/lutf8lib.cpp b/VM/src/lutf8lib.cpp index 378de3d0..8bc8200a 100644 --- a/VM/src/lutf8lib.cpp +++ b/VM/src/lutf8lib.cpp @@ -283,7 +283,7 @@ static const luaL_Reg funcs[] = { {NULL, NULL}, }; -LUALIB_API int luaopen_utf8(lua_State* L) +int luaopen_utf8(lua_State* L) { luaL_register(L, LUA_UTF8LIBNAME, funcs); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index eed2862b..bf8d493e 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -601,7 +601,13 @@ static void luau_execute(lua_State* L) const char* name = getstr(tsvalue(kv)); int ic = (name[0] | ' ') - 'x'; - if (unsigned(ic) < 3 && name[1] == '\0') +#if LUA_VECTOR_SIZE == 4 + // 'w' is before 'x' in ascii, so ic is -1 when indexing with 'w' + if (ic == -1) + ic = 3; +#endif + + if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0') { setnvalue(ra, rb->value.v[ic]); VM_NEXT(); @@ -1526,7 +1532,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); + setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]); VM_NEXT(); } else @@ -1572,7 +1578,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); + setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]); VM_NEXT(); } else @@ -1618,21 +1624,21 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(rc)); - setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); + setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc); VM_NEXT(); } else if (ttisvector(rb) && ttisvector(rc)) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); + setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]); VM_NEXT(); } else if (ttisnumber(rb) && ttisvector(rc)) { float vb = cast_to(float, nvalue(rb)); const float* vc = rc->value.v; - setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2]); + setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]); VM_NEXT(); } else @@ -1679,21 +1685,21 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(rc)); - setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); + setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc); VM_NEXT(); } else if (ttisvector(rb) && ttisvector(rc)) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); + setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); VM_NEXT(); } else if (ttisnumber(rb) && ttisvector(rc)) { float vb = cast_to(float, nvalue(rb)); const float* vc = rc->value.v; - setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2]); + setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]); VM_NEXT(); } else @@ -1826,7 +1832,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(kv)); - setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); + setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc); VM_NEXT(); } else @@ -1872,7 +1878,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(kv)); - setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); + setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc); VM_NEXT(); } else @@ -2037,7 +2043,7 @@ static void luau_execute(lua_State* L) else if (ttisvector(rb)) { const float* vb = rb->value.v; - setvvalue(ra, -vb[0], -vb[1], -vb[2]); + setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); VM_NEXT(); } else diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index a168b652..add3588d 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -9,6 +9,7 @@ #include "lgc.h" #include "lmem.h" #include "lbytecode.h" +#include "lapi.h" #include @@ -162,9 +163,8 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size size_t GCthreshold = L->global->GCthreshold; L->global->GCthreshold = SIZE_MAX; - // env is 0 for current environment and a stack relative index otherwise - LUAU_ASSERT(env <= 0 && L->top - L->base >= -env); - Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(L->top + env); + // env is 0 for current environment and a stack index otherwise + Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(luaA_toobject(L, env)); TString* source = luaS_new(L, chunkname); diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index f52e8e74..740a4cfd 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -401,19 +401,19 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_ADD: - setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); + setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]); return; case TM_SUB: - setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); + setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]); return; case TM_MUL: - setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); + setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]); return; case TM_DIV: - setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); + setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); return; case TM_UNM: - setvvalue(ra, -vb[0], -vb[1], -vb[2]); + setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); return; default: break; @@ -430,10 +430,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_MUL: - setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc); + setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc); return; case TM_DIV: - setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc); + setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc); return; default: break; @@ -451,10 +451,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_MUL: - setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2]); + setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]); return; case TM_DIV: - setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2]); + setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]); return; default: break; diff --git a/bench/gc/test_LB_mandel.lua b/bench/gc/test_LB_mandel.lua index 4be78502..fe5b4eb2 100644 --- a/bench/gc/test_LB_mandel.lua +++ b/bench/gc/test_LB_mandel.lua @@ -88,7 +88,7 @@ for i=1,N do local y=ymin+(j-1)*dy S = S + level(x,y) end - -- if i % 10 == 0 then print(collectgarbage"count") end + -- if i % 10 == 0 then print(collectgarbage("count")) end end print(S) diff --git a/bench/tests/shootout/mandel.lua b/bench/tests/shootout/mandel.lua index 4be78502..fe5b4eb2 100644 --- a/bench/tests/shootout/mandel.lua +++ b/bench/tests/shootout/mandel.lua @@ -88,7 +88,7 @@ for i=1,N do local y=ymin+(j-1)*dy S = S + level(x,y) end - -- if i % 10 == 0 then print(collectgarbage"count") end + -- if i % 10 == 0 then print(collectgarbage("count")) end end print(S) diff --git a/bench/tests/shootout/qt.lua b/bench/tests/shootout/qt.lua index de962a74..79cbe38b 100644 --- a/bench/tests/shootout/qt.lua +++ b/bench/tests/shootout/qt.lua @@ -275,7 +275,7 @@ local function memory(s) local t=os.clock() --local dt=string.format("%f",t-t0) local dt=t-t0 - --io.stdout:write(s,"\t",dt," sec\t",t," sec\t",math.floor(collectgarbage"count"/1024),"M\n") + --io.stdout:write(s,"\t",dt," sec\t",t," sec\t",math.floor(collectgarbage("count")/1024),"M\n") t0=t end @@ -286,7 +286,7 @@ local function do_(f,s) end local function julia(l,a,b) -memory"begin" +memory("begin") cx=a cy=b root=newcell() exterior=newcell() exterior.color=white @@ -297,14 +297,14 @@ memory"begin" do_(update,"update") repeat N=0 color(root,Rxmin,Rxmax,Rymin,Rymax) --print("color",N) - until N==0 memory"color" + until N==0 memory("color") repeat N=0 prewhite(root,Rxmin,Rxmax,Rymin,Rymax) --print("prewhite",N) - until N==0 memory"prewhite" + until N==0 memory("prewhite") do_(recolor,"recolor") do_(colorup,"colorup") --print("colorup",N) local g,b=do_(area,"area") --print("area",g,b,g+b) - show(i) memory"output" + show(i) memory("output") --print("edges",nE) end end diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index ae2399e4..27e53492 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -23,9 +23,11 @@ const bool kFuzzCompiler = true; const bool kFuzzLinter = true; const bool kFuzzTypeck = true; const bool kFuzzVM = true; -const bool kFuzzTypes = true; const bool kFuzzTranspile = true; +// Should we generate type annotations? +const bool kFuzzTypes = true; + static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); std::string protoprint(const luau::StatBlock& stat, bool types); diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index aa53a92b..2090b014 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -78,3 +78,26 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn") } TEST_SUITE_END(); + +TEST_SUITE_BEGIN("AstQuery"); + +TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type") +{ + ScopedFastFlag luauTailArgumentTypeInfo{"LuauTailArgumentTypeInfo", true}; + + check(R"( +local function foo() return 2 end +local function bar(a: number) return -a end +bar(foo()) + )"); + + auto oty = findTypeAtPosition(Position(3, 7)); + REQUIRE(oty); + CHECK_EQ("number", toString(*oty)); + + auto expectedOty = findExpectedTypeAtPosition(Position(3, 7)); + REQUIRE(expectedOty); + CHECK_EQ("number", toString(*expectedOty)); +} + +TEST_SUITE_END(); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 5a7c8602..3b74a99e 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -1935,6 +1935,39 @@ return target(b@1 CHECK(ac.entryMap["bar2"].typeCorrect == TypeCorrectKind::None); } +TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses") +{ + ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true); + ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true); + + check(R"( +local function bar(a: number) return -a end +local abc = b@1 + )"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("bar")); + CHECK(ac.entryMap["bar"].parens == ParenthesesRecommendation::CursorInside); +} + +TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses") +{ + ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true); + ScopedFastFlag luauAutocompletePreferToCallFunctions("LuauAutocompletePreferToCallFunctions", true); + + check(R"( +local function foo() return 1 end +local function bar(a: number) return -a end +local abc = bar(@1) + )"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("foo")); + CHECK(ac.entryMap["foo"].parens == ParenthesesRecommendation::CursorAfter); +} + TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table") { check(R"( @@ -2210,8 +2243,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource") TEST_CASE_FIXTURE(ACFixture, "autocompleteSource_require") { - ScopedFastFlag luauResolveModuleNameWithoutACurrentModule("LuauResolveModuleNameWithoutACurrentModule", true); - std::string_view source = R"( local a = require(w -- Line 1 -- | Column 27 @@ -2287,8 +2318,6 @@ until TEST_CASE_FIXTURE(ACFixture, "if_then_else_elseif_completions") { - ScopedFastFlag sff{"ElseElseIfCompletionImprovements", true}; - check(R"( local elsewhere = false @@ -2585,9 +2614,6 @@ a = if temp then even elseif true then temp else e@9 TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - check(R"( type A = () -> T... local a: A<(number, s@1> diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 4ce8d08a..6ba39ada 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -1057,6 +1057,18 @@ RETURN R0 1 CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"( LOADN R0 20 RETURN R0 1 +)"); + + // codegen for a true constant condition with non-constant expressions + CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"( +NEWTABLE R0 0 0 +RETURN R0 1 +)"); + + // codegen for a false constant condition with non-constant expressions + CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"( +NEWTABLE R0 0 0 +RETURN R0 1 )"); // codegen for a false (in this case 'nil') constant condition @@ -2360,6 +2372,58 @@ Foo:Bar( )"); } +TEST_CASE("DebugLineInfoCallChain") +{ + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines); + Luau::compileOrThrow(bcb, R"( +local Foo = ... + +Foo +:Bar(1) +:Baz(2) +.Qux(3) +)"); + + CHECK_EQ("\n" + bcb.dumpFunction(0), R"( +2: GETVARARGS R0 1 +5: LOADN R4 1 +5: NAMECALL R2 R0 K0 +5: CALL R2 2 1 +6: LOADN R4 2 +6: NAMECALL R2 R2 K1 +6: CALL R2 2 1 +7: GETTABLEKS R1 R2 K2 +7: LOADN R2 3 +7: CALL R1 1 0 +8: RETURN R0 0 +)"); +} + +TEST_CASE("DebugLineInfoFastCall") +{ + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines); + Luau::compileOrThrow(bcb, R"( +local Foo, Bar = ... + +return + math.max( + Foo, + Bar) +)"); + + CHECK_EQ("\n" + bcb.dumpFunction(0), R"( +2: GETVARARGS R0 2 +5: FASTCALL2 18 R0 R1 +5 +5: MOVE R3 R0 +5: MOVE R4 R1 +5: GETIMPORT R2 2 +5: CALL R2 2 -1 +5: RETURN R2 -1 +)"); +} + TEST_CASE("DebugSource") { const char* source = R"( @@ -3742,4 +3806,108 @@ RETURN R0 0 )"); } +TEST_CASE("ConstantsNoFolding") +{ + const char* source = "return nil, true, 42, 'hello'"; + + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); + Luau::CompileOptions options; + options.optimizationLevel = 0; + Luau::compileOrThrow(bcb, source, options); + + CHECK_EQ("\n" + bcb.dumpFunction(0), R"( +LOADNIL R0 +LOADB R1 1 +LOADK R2 K0 +LOADK R3 K1 +RETURN R0 4 +)"); +} + +TEST_CASE("VectorFastCall") +{ + const char* source = "return Vector3.new(1, 2, 3)"; + + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); + Luau::CompileOptions options; + options.vectorLib = "Vector3"; + options.vectorCtor = "new"; + Luau::compileOrThrow(bcb, source, options); + + CHECK_EQ("\n" + bcb.dumpFunction(0), R"( +LOADN R1 1 +LOADN R2 2 +LOADN R3 3 +FASTCALL 54 +2 +GETIMPORT R0 2 +CALL R0 3 -1 +RETURN R0 -1 +)"); +} + +TEST_CASE("TypeAssertion") +{ + // validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated + CHECK_EQ("\n" + compileFunction0(R"( +print(foo() :: typeof(error("compile time"))) +)"), + R"( +GETIMPORT R0 1 +GETIMPORT R1 3 +CALL R1 0 1 +CALL R0 1 0 +RETURN R0 0 +)"); + + // note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode + CHECK_EQ("\n" + compileFunction0(R"( +print(foo()) +)"), + R"( +GETIMPORT R0 1 +GETIMPORT R1 3 +CALL R1 0 -1 +CALL R0 -1 0 +RETURN R0 0 +)"); +} + +TEST_CASE("Arithmetics") +{ + // basic arithmetics codegen with non-constants + CHECK_EQ("\n" + compileFunction0(R"( +local a, b = ... +return a + b, a - b, a / b, a * b, a % b, a ^ b +)"), + R"( +GETVARARGS R0 2 +ADD R2 R0 R1 +SUB R3 R0 R1 +DIV R4 R0 R1 +MUL R5 R0 R1 +MOD R6 R0 R1 +POW R7 R0 R1 +RETURN R2 6 +)"); + + // basic arithmetics codegen with constants on the right side + // note that we don't simplify these expressions as we don't know the type of a + CHECK_EQ("\n" + compileFunction0(R"( +local a = ... +return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1 +)"), + R"( +GETVARARGS R0 1 +ADDK R1 R0 K0 +SUBK R2 R0 K0 +DIVK R3 R0 K0 +MULK R4 R0 K0 +MODK R5 R0 K0 +POWK R6 R0 K0 +RETURN R1 6 +)"); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index e495a213..b2aad316 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -67,44 +67,42 @@ static int lua_vector(lua_State* L) double y = luaL_checknumber(L, 2); double z = luaL_checknumber(L, 3); +#if LUA_VECTOR_SIZE == 4 + double w = luaL_optnumber(L, 4, 0.0); + lua_pushvector(L, float(x), float(y), float(z), float(w)); +#else lua_pushvector(L, float(x), float(y), float(z)); +#endif return 1; } static int lua_vector_dot(lua_State* L) { - const float* a = lua_tovector(L, 1); - const float* b = lua_tovector(L, 2); + const float* a = luaL_checkvector(L, 1); + const float* b = luaL_checkvector(L, 2); - if (a && b) - { - lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]); - return 1; - } - - throw std::runtime_error("invalid arguments to vector:Dot"); + lua_pushnumber(L, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]); + return 1; } static int lua_vector_index(lua_State* L) { + const float* v = luaL_checkvector(L, 1); const char* name = luaL_checkstring(L, 2); - if (const float* v = lua_tovector(L, 1)) + if (strcmp(name, "Magnitude") == 0) { - if (strcmp(name, "Magnitude") == 0) - { - lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])); - return 1; - } - - if (strcmp(name, "Dot") == 0) - { - lua_pushcfunction(L, lua_vector_dot, "Dot"); - return 1; - } + lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])); + return 1; } - throw std::runtime_error(Luau::format("%s is not a valid member of vector", name)); + if (strcmp(name, "Dot") == 0) + { + lua_pushcfunction(L, lua_vector_dot, "Dot"); + return 1; + } + + luaL_error(L, "%s is not a valid member of vector", name); } static int lua_vector_namecall(lua_State* L) @@ -115,7 +113,7 @@ static int lua_vector_namecall(lua_State* L) return lua_vector_dot(L); } - throw std::runtime_error(Luau::format("%s is not a valid method of vector", luaL_checkstring(L, 1))); + luaL_error(L, "%s is not a valid method of vector", luaL_checkstring(L, 1)); } int lua_silence(lua_State* L) @@ -373,11 +371,17 @@ TEST_CASE("Pack") TEST_CASE("Vector") { + ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true}; + runConformance("vector.lua", [](lua_State* L) { lua_pushcfunction(L, lua_vector, "vector"); lua_setglobal(L, "vector"); +#if LUA_VECTOR_SIZE == 4 + lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); +#else lua_pushvector(L, 0.0f, 0.0f, 0.0f); +#endif luaL_newmetatable(L, "vector"); lua_pushstring(L, "__index"); @@ -504,6 +508,9 @@ TEST_CASE("Debugger") cb->debugbreak = [](lua_State* L, lua_Debug* ar) { breakhits++; + // make sure we can trace the stack for every breakpoint we hit + lua_debugtrace(L); + // for every breakpoint, we break on the first invocation and continue on second // this allows us to easily step off breakpoints // (real implementaiton may require singlestepping) @@ -524,7 +531,7 @@ TEST_CASE("Debugger") L, [](lua_State* L) -> int { int line = luaL_checkinteger(L, 1); - bool enabled = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true; + bool enabled = luaL_optboolean(L, 2, true); lua_Debug ar = {}; lua_getinfo(L, 1, "f", &ar); @@ -699,21 +706,52 @@ TEST_CASE("ApiFunctionCalls") StateRef globalState = runConformance("apicalls.lua"); lua_State* L = globalState.get(); - lua_getfield(L, LUA_GLOBALSINDEX, "add"); - lua_pushnumber(L, 40); - lua_pushnumber(L, 2); - lua_call(L, 2, 1); - CHECK(lua_isnumber(L, -1)); - CHECK(lua_tonumber(L, -1) == 42); - lua_pop(L, 1); + // lua_call + { + lua_getfield(L, LUA_GLOBALSINDEX, "add"); + lua_pushnumber(L, 40); + lua_pushnumber(L, 2); + lua_call(L, 2, 1); + CHECK(lua_isnumber(L, -1)); + CHECK(lua_tonumber(L, -1) == 42); + lua_pop(L, 1); + } - lua_getfield(L, LUA_GLOBALSINDEX, "add"); - lua_pushnumber(L, 40); - lua_pushnumber(L, 2); - lua_pcall(L, 2, 1, 0); - CHECK(lua_isnumber(L, -1)); - CHECK(lua_tonumber(L, -1) == 42); - lua_pop(L, 1); + // lua_pcall + { + lua_getfield(L, LUA_GLOBALSINDEX, "add"); + lua_pushnumber(L, 40); + lua_pushnumber(L, 2); + lua_pcall(L, 2, 1, 0); + CHECK(lua_isnumber(L, -1)); + CHECK(lua_tonumber(L, -1) == 42); + lua_pop(L, 1); + } + + // lua_equal with a sleeping thread wake up + { + ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true); + + lua_State* L2 = lua_newthread(L); + + lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm"); + lua_pushnumber(L2, 42); + lua_pcall(L2, 1, 1, 0); + + lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm"); + lua_pushnumber(L2, 42); + lua_pcall(L2, 1, 1, 0); + + // Reset GC + lua_gc(L2, LUA_GCCOLLECT, 0); + + // Try to mark 'L2' as sleeping + // Can't control GC precisely, even in tests + lua_gc(L2, LUA_GCSTEP, 8); + + CHECK(lua_equal(L2, -1, -2) == 1); + lua_pop(L2, 2); + } } static bool endsWith(const std::string& str, const std::string& suffix) @@ -727,8 +765,6 @@ static bool endsWith(const std::string& str, const std::string& suffix) #if !LUA_USE_LONGJMP TEST_CASE("ExceptionObject") { - ScopedFastFlag sff("LuauExceptionMessageFix", true); - struct ExceptionResult { bool exceptionGenerated; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 29c33f7c..36d6f561 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -19,19 +19,6 @@ static const char* mainModuleName = "MainModule"; namespace Luau { -std::optional TestFileResolver::fromAstFragment(AstExpr* expr) const -{ - auto g = expr->as(); - if (!g) - return std::nullopt; - - std::string_view value = g->name.value; - if (value == "game" || value == "Game" || value == "workspace" || value == "Workspace" || value == "script" || value == "Script") - return ModuleName(value); - - return std::nullopt; -} - std::optional TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr) { if (AstExprGlobal* g = expr->as()) @@ -81,24 +68,6 @@ std::optional TestFileResolver::resolveModule(const ModuleInfo* cont return std::nullopt; } -ModuleName TestFileResolver::concat(const ModuleName& lhs, std::string_view rhs) const -{ - return lhs + "/" + ModuleName(rhs); -} - -std::optional TestFileResolver::getParentModuleName(const ModuleName& name) const -{ - std::string_view view = name; - const size_t lastSeparatorIndex = view.find_last_of('/'); - - if (lastSeparatorIndex != std::string_view::npos) - { - return ModuleName(view.substr(0, lastSeparatorIndex)); - } - - return std::nullopt; -} - std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const { return name; @@ -324,6 +293,13 @@ std::optional Fixture::findTypeAtPosition(Position position) return Luau::findTypeAtPosition(*module, *sourceModule, position); } +std::optional Fixture::findExpectedTypeAtPosition(Position position) +{ + ModulePtr module = getMainModule(); + SourceModule* sourceModule = getMainSourceModule(); + return Luau::findExpectedTypeAtPosition(*module, *sourceModule, position); +} + TypeId Fixture::requireTypeAtPosition(Position position) { auto ty = findTypeAtPosition(position); diff --git a/tests/Fixture.h b/tests/Fixture.h index 1480a7f6..de2b7381 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -64,12 +64,8 @@ struct TestFileResolver return SourceCode{it->second, sourceType}; } - std::optional fromAstFragment(AstExpr* expr) const override; std::optional resolveModule(const ModuleInfo* context, AstExpr* expr) override; - ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override; - std::optional getParentModuleName(const ModuleName& name) const override; - std::string getHumanReadableModuleName(const ModuleName& name) const override; std::optional getEnvironmentForModule(const ModuleName& name) const override; @@ -126,6 +122,7 @@ struct Fixture std::optional findTypeAtPosition(Position position); TypeId requireTypeAtPosition(Position position); + std::optional findExpectedTypeAtPosition(Position position); std::optional lookupType(const std::string& name); std::optional lookupImportedType(const std::string& moduleAlias, const std::string& name); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index fbfec636..51fcd3d6 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -46,18 +46,6 @@ NaiveModuleResolver naiveModuleResolver; struct NaiveFileResolver : NullFileResolver { - std::optional fromAstFragment(AstExpr* expr) const override - { - AstExprGlobal* g = expr->as(); - if (g && g->name == "Modules") - return "Modules"; - - if (g && g->name == "game") - return "game"; - - return std::nullopt; - } - std::optional resolveModule(const ModuleInfo* context, AstExpr* expr) override { if (AstExprGlobal* g = expr->as()) @@ -86,11 +74,6 @@ struct NaiveFileResolver : NullFileResolver return std::nullopt; } - - ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override - { - return lhs + "/" + ModuleName(rhs); - } }; } // namespace diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 7ba40c50..1d13df28 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1469,6 +1469,22 @@ _ = true and true or false -- no warning since this is is a common pattern used CHECK_EQ(result.warnings[6].location.begin.line + 1, 19); } +TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr") +{ + LintResult result = lint(R"( +local correct, opaque = ... + +if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then +elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls")}) then +elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then +end +)"); + + REQUIRE_EQ(result.warnings.size(), 1); + CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4"); + CHECK_EQ(result.warnings[0].location.begin.line + 1, 5); +} + TEST_CASE_FIXTURE(Fixture, "DuplicateLocal") { LintResult result = lint(R"( diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 7a3543c7..2800d2fe 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -44,9 +44,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; // numberType is persistent. We leave it as-is. - TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks); + TypeId newNumber = clone(typeChecker.numberType, dest, seenTypes, seenTypePacks, cloneState); CHECK_EQ(newNumber, typeChecker.numberType); } @@ -56,12 +57,13 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; // Create a new number type that isn't persistent unfreeze(typeChecker.globalTypes); TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveTypeVar{PrimitiveTypeVar::Number}); freeze(typeChecker.globalTypes); - TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks); + TypeId newNumber = clone(oldNumber, dest, seenTypes, seenTypePacks, cloneState); CHECK_NE(newNumber, oldNumber); CHECK_EQ(*oldNumber, *newNumber); @@ -89,9 +91,10 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; TypeArena dest; - TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks); + TypeId counterCopy = clone(counterType, dest, seenTypes, seenTypePacks, cloneState); TableTypeVar* ttv = getMutable(counterCopy); REQUIRE(ttv != nullptr); @@ -142,11 +145,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; unfreeze(typeChecker.globalTypes); TypeId oldUnion = typeChecker.globalTypes.addType(UnionTypeVar{{typeChecker.numberType, typeChecker.stringType}}); freeze(typeChecker.globalTypes); - TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks); + TypeId newUnion = clone(oldUnion, dest, seenTypes, seenTypePacks, cloneState); CHECK_NE(newUnion, oldUnion); CHECK_EQ("number | string", toString(newUnion)); @@ -159,11 +163,12 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; unfreeze(typeChecker.globalTypes); TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionTypeVar{{typeChecker.numberType, typeChecker.stringType}}); freeze(typeChecker.globalTypes); - TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks); + TypeId newIntersection = clone(oldIntersection, dest, seenTypes, seenTypePacks, cloneState); CHECK_NE(newIntersection, oldIntersection); CHECK_EQ("number & string", toString(newIntersection)); @@ -188,8 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; - TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks); + TypeId cloned = clone(&exampleClass, dest, seenTypes, seenTypePacks, cloneState); const ClassTypeVar* ctv = get(cloned); REQUIRE(ctv != nullptr); @@ -211,16 +217,16 @@ TEST_CASE_FIXTURE(Fixture, "clone_sanitize_free_types") TypeArena dest; SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; - bool encounteredFreeType = false; - TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, &encounteredFreeType); + TypeId clonedTy = clone(&freeTy, dest, seenTypes, seenTypePacks, cloneState); CHECK_EQ("any", toString(clonedTy)); - CHECK(encounteredFreeType); + CHECK(cloneState.encounteredFreeType); - encounteredFreeType = false; - TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, &encounteredFreeType); + cloneState = {}; + TypePackId clonedTp = clone(&freeTp, dest, seenTypes, seenTypePacks, cloneState); CHECK_EQ("...any", toString(clonedTp)); - CHECK(encounteredFreeType); + CHECK(cloneState.encounteredFreeType); } TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables") @@ -232,12 +238,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_seal_free_tables") TypeArena dest; SeenTypes seenTypes; SeenTypePacks seenTypePacks; + CloneState cloneState; - bool encounteredFreeType = false; - TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, &encounteredFreeType); + TypeId cloned = clone(&tableTy, dest, seenTypes, seenTypePacks, cloneState); const TableTypeVar* clonedTtv = get(cloned); CHECK_EQ(clonedTtv->state, TableState::Sealed); - CHECK(encounteredFreeType); + CHECK(cloneState.encounteredFreeType); } TEST_CASE_FIXTURE(Fixture, "clone_self_property") @@ -267,4 +273,34 @@ TEST_CASE_FIXTURE(Fixture, "clone_self_property") "dot or pass 1 extra nil to suppress this warning"); } +TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") +{ +#if defined(_DEBUG) || defined(_NOOPT) + int limit = 250; +#else + int limit = 500; +#endif + ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit}; + + TypeArena src; + + TypeId table = src.addType(TableTypeVar{}); + TypeId nested = table; + + for (unsigned i = 0; i < limit + 100; i++) + { + TableTypeVar* ttv = getMutable(nested); + + ttv->props["a"].type = src.addType(TableTypeVar{}); + nested = ttv->props["a"].type; + } + + TypeArena dest; + SeenTypes seenTypes; + SeenTypePacks seenTypePacks; + CloneState cloneState; + + CHECK_THROWS_AS(clone(table, dest, seenTypes, seenTypePacks, cloneState), std::runtime_error); +} + TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index e3e6ce6d..72d3a9a6 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2518,8 +2518,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_if_else_expression") TEST_CASE_FIXTURE(Fixture, "parse_type_pack_type_parameters") { - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - AstStat* stat = parse(R"( type Packed = () -> T... diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp new file mode 100644 index 00000000..0ca9c994 --- /dev/null +++ b/tests/ToDot.test.cpp @@ -0,0 +1,366 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/Scope.h" +#include "Luau/ToDot.h" + +#include "Fixture.h" + +#include "doctest.h" + +using namespace Luau; + +struct ToDotClassFixture : Fixture +{ + ToDotClassFixture() + { + TypeArena& arena = typeChecker.globalTypes; + + unfreeze(arena); + + TypeId baseClassMetaType = arena.addType(TableTypeVar{}); + + TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}}); + getMutable(baseClassInstanceType)->props = { + {"BaseField", {typeChecker.numberType}}, + }; + typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; + + TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}}); + getMutable(childClassInstanceType)->props = { + {"ChildField", {typeChecker.stringType}}, + }; + typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; + + freeze(arena); + } +}; + +TEST_SUITE_BEGIN("ToDot"); + +TEST_CASE_FIXTURE(Fixture, "primitive") +{ + CheckResult result = check(R"( +local a: nil +local b: number +local c: any +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_NE("nil", toDot(requireType("a"))); + + CHECK_EQ(R"(digraph graphname { +n1 [label="number"]; +})", + toDot(requireType("b"))); + + CHECK_EQ(R"(digraph graphname { +n1 [label="any"]; +})", + toDot(requireType("c"))); + + ToDotOptions opts; + opts.showPointers = false; + opts.duplicatePrimitives = false; + + CHECK_EQ(R"(digraph graphname { +n1 [label="PrimitiveTypeVar number"]; +})", + toDot(requireType("b"), opts)); + + CHECK_EQ(R"(digraph graphname { +n1 [label="AnyTypeVar 1"]; +})", + toDot(requireType("c"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "bound") +{ + CheckResult result = check(R"( +local a = 444 +local b = a +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + std::optional ty = getType("b"); + REQUIRE(bool(ty)); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="BoundTypeVar 1"]; +n1 -> n2; +n2 [label="number"]; +})", + toDot(*ty, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "function") +{ + ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true}; + + CheckResult result = check(R"( +local function f(a, ...: string) return a end +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="FunctionTypeVar 1"]; +n1 -> n2 [label="arg"]; +n2 [label="TypePack 2"]; +n2 -> n3; +n3 [label="GenericTypeVar 3"]; +n2 -> n4 [label="tail"]; +n4 [label="VariadicTypePack 4"]; +n4 -> n5; +n5 [label="string"]; +n1 -> n6 [label="ret"]; +n6 [label="BoundTypePack 6"]; +n6 -> n7; +n7 [label="TypePack 7"]; +n7 -> n3; +})", + toDot(requireType("f"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "union") +{ + CheckResult result = check(R"( +local a: string | number +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="UnionTypeVar 1"]; +n1 -> n2; +n2 [label="string"]; +n1 -> n3; +n3 [label="number"]; +})", + toDot(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "intersection") +{ + CheckResult result = check(R"( +local a: string & number -- uninhabited +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="IntersectionTypeVar 1"]; +n1 -> n2; +n2 [label="string"]; +n1 -> n3; +n3 [label="number"]; +})", + toDot(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "table") +{ + CheckResult result = check(R"( +type A = { x: T, y: (U...) -> (), [string]: any } +local a: A +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="TableTypeVar A"]; +n1 -> n2 [label="x"]; +n2 [label="number"]; +n1 -> n3 [label="y"]; +n3 [label="FunctionTypeVar 3"]; +n3 -> n4 [label="arg"]; +n4 [label="VariadicTypePack 4"]; +n4 -> n5; +n5 [label="string"]; +n3 -> n6 [label="ret"]; +n6 [label="TypePack 6"]; +n1 -> n7 [label="[index]"]; +n7 [label="string"]; +n1 -> n8 [label="[value]"]; +n8 [label="any"]; +n1 -> n9 [label="typeParam"]; +n9 [label="number"]; +n1 -> n4 [label="typePackParam"]; +})", + toDot(requireType("a"), opts)); + + // Extra coverage with pointers (unstable values) + (void)toDot(requireType("a")); +} + +TEST_CASE_FIXTURE(Fixture, "metatable") +{ + CheckResult result = check(R"( +local a: typeof(setmetatable({}, {})) +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="MetatableTypeVar 1"]; +n1 -> n2 [label="table"]; +n2 [label="TableTypeVar 2"]; +n1 -> n3 [label="metatable"]; +n3 [label="TableTypeVar 3"]; +})", + toDot(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "free") +{ + TypeVar type{TypeVariant{FreeTypeVar{TypeLevel{0, 0}}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="FreeTypeVar 1"]; +})", + toDot(&type, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "error") +{ + TypeVar type{TypeVariant{ErrorTypeVar{}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="ErrorTypeVar 1"]; +})", + toDot(&type, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "generic") +{ + TypeVar type{TypeVariant{GenericTypeVar{"T"}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="GenericTypeVar T"]; +})", + toDot(&type, opts)); +} + +TEST_CASE_FIXTURE(ToDotClassFixture, "class") +{ + CheckResult result = check(R"( +local a: ChildClass +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="ClassTypeVar ChildClass"]; +n1 -> n2 [label="ChildField"]; +n2 [label="string"]; +n1 -> n3 [label="[parent]"]; +n3 [label="ClassTypeVar BaseClass"]; +n3 -> n4 [label="BaseField"]; +n4 [label="number"]; +n3 -> n5 [label="[metatable]"]; +n5 [label="TableTypeVar 5"]; +})", + toDot(requireType("a"), opts)); +} + +TEST_CASE_FIXTURE(Fixture, "free_pack") +{ + TypePackVar pack{TypePackVariant{FreeTypePack{TypeLevel{0, 0}}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="FreeTypePack 1"]; +})", + toDot(&pack, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "error_pack") +{ + TypePackVar pack{TypePackVariant{Unifiable::Error{}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="ErrorTypePack 1"]; +})", + toDot(&pack, opts)); + + // Extra coverage with pointers (unstable values) + (void)toDot(&pack); +} + +TEST_CASE_FIXTURE(Fixture, "generic_pack") +{ + TypePackVar pack1{TypePackVariant{GenericTypePack{}}}; + TypePackVar pack2{TypePackVariant{GenericTypePack{"T"}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="GenericTypePack 1"]; +})", + toDot(&pack1, opts)); + + CHECK_EQ(R"(digraph graphname { +n1 [label="GenericTypePack T"]; +})", + toDot(&pack2, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "bound_pack") +{ + TypePackVar pack{TypePackVariant{TypePack{{typeChecker.numberType}, {}}}}; + TypePackVar bound{TypePackVariant{BoundTypePack{&pack}}}; + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="BoundTypePack 1"]; +n1 -> n2; +n2 [label="TypePack 2"]; +n2 -> n3; +n3 [label="number"]; +})", + toDot(&bound, opts)); +} + +TEST_CASE_FIXTURE(Fixture, "bound_table") +{ + CheckResult result = check(R"( +local a = {x=2} +local b +b.x = 2 +b = a +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + std::optional ty = getType("b"); + REQUIRE(bool(ty)); + + ToDotOptions opts; + opts.showPointers = false; + CHECK_EQ(R"(digraph graphname { +n1 [label="TableTypeVar 1"]; +n1 -> n2 [label="boundTo"]; +n2 [label="TableTypeVar a"]; +n2 -> n3 [label="x"]; +n3 [label="number"]; +})", + toDot(*ty, opts)); +} + +TEST_SUITE_END(); diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 928c03a3..327fa0bb 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -445,9 +445,6 @@ local a: Import.Type TEST_CASE_FIXTURE(Fixture, "transpile_type_packs") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - std::string code = R"( type Packed = (T...)->(T...) local a: Packed<> diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 74ce155c..822bd727 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -537,8 +537,6 @@ TEST_CASE_FIXTURE(Fixture, "free_variables_from_typeof_in_aliases") TEST_CASE_FIXTURE(Fixture, "non_recursive_aliases_that_reuse_a_generic_name") { - ScopedFastFlag sff1{"LuauSubstitutionDontReplaceIgnoredTypes", true}; - CheckResult result = check(R"( type Array = { [number]: T } type Tuple = Array diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 88c2dc85..aba50891 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -609,8 +609,6 @@ TEST_CASE_FIXTURE(Fixture, "typefuns_sharing_types") TEST_CASE_FIXTURE(Fixture, "bound_tables_do_not_clone_original_fields") { - ScopedFastFlag luauCloneBoundTables{"LuauCloneBoundTables", true}; - CheckResult result = check(R"( local exports = {} local nested = {} @@ -627,4 +625,23 @@ return exports LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names") +{ + ScopedFastFlag luauFunctionArgumentNameSize{"LuauFunctionArgumentNameSize", true}; + + CheckResult result = check(R"( +local function f(a: T, ...: U...) end + +f(1, 2, 3) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + auto ty = findTypeAtPosition(Position(3, 0)); + REQUIRE(ty); + ToStringOptions opts; + opts.functionTypeArguments = true; + CHECK_EQ(toString(*ty, opts), "(a: number, number, number) -> ()"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index e5c14dde..e6d3d4d4 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -31,8 +31,6 @@ TEST_SUITE_BEGIN("ProvisionalTests"); */ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - const std::string code = R"( function f(a) if type(a) == "boolean" then diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index c3694be7..cb72faaf 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2022,4 +2022,74 @@ caused by: Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '(a) -> ()')"); } +TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table") +{ + ScopedFastFlag sffs[] { + {"LuauPropertiesGetExpectedType", true}, + {"LuauExpectedTypesOfProperties", true}, + {"LuauTableSubtypingVariance", true}, + }; + + CheckResult result = check(R"( +--!strict +type Super = { x : number } +type Sub = { x : number, y: number } +type HasSuper = { p : Super } +type HasSub = { p : Sub } +local a: HasSuper = { p = { x = 5, y = 7 }} +a.p = { x = 9 } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") +{ + ScopedFastFlag sffs[] { + {"LuauPropertiesGetExpectedType", true}, + {"LuauExpectedTypesOfProperties", true}, + {"LuauTableSubtypingVariance", true}, + {"LuauExtendedTypeMismatchError", true}, + }; + + CheckResult result = check(R"( +--!strict +type Super = { x : number } +type Sub = { x : number, y: number } +type HasSuper = { p : Super } +type HasSub = { p : Sub } +local tmp = { p = { x = 5, y = 7 }} +local a: HasSuper = tmp +a.p = { x = 9 } +-- needs to be an error because +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' +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')"); +} + +TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") +{ + ScopedFastFlag sffs[] { + {"LuauPropertiesGetExpectedType", true}, + {"LuauExpectedTypesOfProperties", true}, + {"LuauTableSubtypingVariance", true}, + }; + + CheckResult result = check(R"( +--!strict +type Super = { x : number } +type Sub = { x : number, y: number } +type HasSuper = { [string] : Super } +type HasSub = { [string] : Sub } +local a: HasSuper = { p = { x = 5, y = 7 }} +a.p = { x = 9 } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 99fd8339..e3222a41 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -4779,4 +4779,24 @@ local bar = foo.nutrition + 100 // CHECK_EQ("Unknown type used in + operation; consider adding a type annotation to 'foo'", toString(result.errors[1])); } +TEST_CASE_FIXTURE(Fixture, "require_failed_module") +{ + ScopedFastFlag luauModuleRequireErrorPack{"LuauModuleRequireErrorPack", true}; + + fileResolver.source["game/A"] = R"( +return unfortunately() + )"; + + CheckResult aResult = frontend.check("game/A"); + LUAU_REQUIRE_ERRORS(aResult); + + CheckResult result = check(R"( +local ModuleA = require(game.A) + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + std::optional oty = requireType("ModuleA"); + CHECK_EQ("*unknown*", toString(*oty)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index c6de0abf..3f4420cd 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -296,9 +296,6 @@ end TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Packed = (T...) -> T... local a: Packed<> @@ -360,9 +357,6 @@ local c: Packed TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_import") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - fileResolver.source["game/A"] = R"( export type Packed = { a: T, b: (U...) -> () } return {} @@ -393,9 +387,6 @@ local d: { a: typeof(c) } TEST_CASE_FIXTURE(Fixture, "type_pack_type_parameters") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - fileResolver.source["game/A"] = R"( export type Packed = { a: T, b: (U...) -> () } return {} @@ -431,9 +422,6 @@ type C = Import.Packed TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Packed1 = (T...) -> (T...) type Packed2 = (Packed1, T...) -> (Packed1, T...) @@ -452,9 +440,6 @@ type Packed4 = (Packed3, T...) -> (Packed3, T...) TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_variadic") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type X = (T...) -> (string, T...) @@ -470,9 +455,6 @@ type E = X<(number, ...string)> TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Y = (T...) -> (U...) type A = Y @@ -501,9 +483,6 @@ type I = W TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type X = (T...) -> (T...) @@ -527,9 +506,6 @@ type F = X<(string, ...number)> TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Y = (T...) -> (U...) @@ -549,9 +525,6 @@ type D = Y TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi_tostring") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Y = { f: (T...) -> (U...) } @@ -567,9 +540,6 @@ local b: Y<(), ()> TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type X = () -> T type Y = (T) -> U @@ -588,9 +558,6 @@ type C = Y<(number), boolean> TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_errors") { - ScopedFastFlag luauTypeAliasPacks("LuauTypeAliasPacks", true); - ScopedFastFlag luauParseTypePackTypeParameters("LuauParseTypePackTypeParameters", true); - CheckResult result = check(R"( type Packed = (T, U) -> (V...) local b: Packed diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 91efa818..13db923e 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -3,6 +3,7 @@ #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" +#include "Luau/VisitTypeVar.h" #include "Fixture.h" #include "ScopedFlags.h" @@ -323,4 +324,48 @@ TEST_CASE("tagging_props") CHECK(Luau::hasTag(prop, "foo")); } +struct VisitCountTracker +{ + std::unordered_map tyVisits; + std::unordered_map tpVisits; + + void cycle(TypeId) {} + void cycle(TypePackId) {} + + template + bool operator()(TypeId ty, const T& t) + { + tyVisits[ty]++; + return true; + } + + template + bool operator()(TypePackId tp, const T&) + { + tpVisits[tp]++; + return true; + } +}; + +TEST_CASE_FIXTURE(Fixture, "visit_once") +{ + CheckResult result = check(R"( +type T = { a: number, b: () -> () } +local b: (T, T, T) -> T +)"); + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId bType = requireType("b"); + + VisitCountTracker tester; + DenseHashSet seen{nullptr}; + visitTypeVarOnce(bType, tester, seen); + + for (auto [_, count] : tester.tyVisits) + CHECK_EQ(count, 1); + + for (auto [_, count] : tester.tpVisits) + CHECK_EQ(count, 1); +} + TEST_SUITE_END(); diff --git a/tests/conformance/apicalls.lua b/tests/conformance/apicalls.lua index 5e03b055..7a4058b5 100644 --- a/tests/conformance/apicalls.lua +++ b/tests/conformance/apicalls.lua @@ -2,7 +2,13 @@ print('testing function calls through API') function add(a, b) - return a + b + return a + b +end + +local m = { __eq = function(a, b) return a.a == b.a end } + +function create_with_tm(x) + return setmetatable({ a = x }, m) end return('OK') diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index 687fff1e..188b8ebc 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -441,7 +441,8 @@ assert((function() a = {} b = {} mt = { __eq = function(l, r) return #l == #r en assert((function() a = {} b = {} function eq(l, r) return #l == #r end setmetatable(a, {__eq = eq}) setmetatable(b, {__eq = eq}) return concat(a == b, a ~= b) end)() == "true,false") assert((function() a = {} b = {} setmetatable(a, {__eq = function(l, r) return #l == #r end}) setmetatable(b, {__eq = function(l, r) return #l == #r end}) return concat(a == b, a ~= b) end)() == "false,true") --- userdata, reference equality (no mt) +-- userdata, reference equality (no mt or mt.__eq) +assert((function() a = newproxy() return concat(a == newproxy(),a ~= newproxy()) end)() == "false,true") assert((function() a = newproxy(true) return concat(a == newproxy(true),a ~= newproxy(true)) end)() == "false,true") -- rawequal @@ -876,4 +877,4 @@ assert(concat(typeof(5), typeof(nil), typeof({}), typeof(newproxy())) == "number testgetfenv() -- DONT MOVE THIS LINE -return'OK' +return 'OK' diff --git a/tests/conformance/closure.lua b/tests/conformance/closure.lua index aac42c56..f32d5bdc 100644 --- a/tests/conformance/closure.lua +++ b/tests/conformance/closure.lua @@ -419,11 +419,5 @@ co = coroutine.create(function () return loadstring("return a")() end) -a = {a = 15} --- debug.setfenv(co, a) --- assert(debug.getfenv(co) == a) --- assert(select(2, coroutine.resume(co)) == a) --- assert(select(2, coroutine.resume(co)) == a.a) - -return'OK' +return 'OK' diff --git a/tests/conformance/constructs.lua b/tests/conformance/constructs.lua index 16c63b00..f133501f 100644 --- a/tests/conformance/constructs.lua +++ b/tests/conformance/constructs.lua @@ -237,4 +237,4 @@ repeat i = i+1 until i==c -return'OK' +return 'OK' diff --git a/tests/conformance/coroutine.lua b/tests/conformance/coroutine.lua index 4d9b1295..f2ecc96b 100644 --- a/tests/conformance/coroutine.lua +++ b/tests/conformance/coroutine.lua @@ -373,4 +373,4 @@ do assert(f() == 42) end -return'OK' +return 'OK' diff --git a/tests/conformance/datetime.lua b/tests/conformance/datetime.lua index 21ef60d7..ca35cf2f 100644 --- a/tests/conformance/datetime.lua +++ b/tests/conformance/datetime.lua @@ -74,4 +74,4 @@ assert(os.difftime(t1,t2) == 60*2-19) assert(os.time({ year = 1970, day = 1, month = 1, hour = 0}) == 0) -return'OK' +return 'OK' diff --git a/tests/conformance/debug.lua b/tests/conformance/debug.lua index ee79a14f..9cf3c742 100644 --- a/tests/conformance/debug.lua +++ b/tests/conformance/debug.lua @@ -98,4 +98,4 @@ assert(quuz(function(...) end) == "0 true") assert(quuz(function(a, b) end) == "2 false") assert(quuz(function(a, b, ...) end) == "2 true") -return'OK' +return 'OK' diff --git a/tests/conformance/errors.lua b/tests/conformance/errors.lua index eded14e9..d5ff215b 100644 --- a/tests/conformance/errors.lua +++ b/tests/conformance/errors.lua @@ -34,15 +34,15 @@ assert(doit("error('hi', 0)") == 'hi') assert(doit("unpack({}, 1, n=2^30)")) assert(doit("a=math.sin()")) assert(not doit("tostring(1)") and doit("tostring()")) -assert(doit"tonumber()") -assert(doit"repeat until 1; a") +assert(doit("tonumber()")) +assert(doit("repeat until 1; a")) checksyntax("break label", "", "label", 1) -assert(doit";") -assert(doit"a=1;;") -assert(doit"return;;") -assert(doit"assert(false)") -assert(doit"assert(nil)") -assert(doit"a=math.sin\n(3)") +assert(doit(";")) +assert(doit("a=1;;")) +assert(doit("return;;")) +assert(doit("assert(false)")) +assert(doit("assert(nil)")) +assert(doit("a=math.sin\n(3)")) assert(doit("function a (... , ...) end")) assert(doit("function a (, ...) end")) @@ -59,7 +59,7 @@ checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)", "local 'bbbb'") checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'") checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'") -assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'")) +assert(not string.find(doit("a={13}; local bbbb=1; a[bbbb](3)"), "'bbbb'")) checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number") aaa = nil @@ -67,14 +67,14 @@ checkmessage("aaa.bbb:ddd(9)", "global 'aaa'") checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'") checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'") checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'") -assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)") +assert(not doit("local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)")) checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'") checkmessage("aaa={}; x=3/aaa", "global 'aaa'") checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'") checkmessage("aaa={}; x=-aaa", "global 'aaa'") -assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'")) -assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'")) +assert(not string.find(doit("aaa={}; x=(aaa or aaa)+(aaa and aaa)"), "'aaa'")) +assert(not string.find(doit("aaa={}; (aaa or aaa)()"), "'aaa'")) checkmessage([[aaa=9 repeat until 3==3 @@ -122,10 +122,10 @@ function lineerror (s) return line and line+0 end -assert(lineerror"local a\n for i=1,'a' do \n print(i) \n end" == 2) --- assert(lineerror"\n local a \n for k,v in 3 \n do \n print(k) \n end" == 3) --- assert(lineerror"\n\n for k,v in \n 3 \n do \n print(k) \n end" == 4) -assert(lineerror"function a.x.y ()\na=a+1\nend" == 1) +assert(lineerror("local a\n for i=1,'a' do \n print(i) \n end") == 2) +-- assert(lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end") == 3) +-- assert(lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end") == 4) +assert(lineerror("function a.x.y ()\na=a+1\nend") == 1) local p = [[ function g() f() end diff --git a/tests/conformance/gc.lua b/tests/conformance/gc.lua index 4263dfda..6d9eb854 100644 --- a/tests/conformance/gc.lua +++ b/tests/conformance/gc.lua @@ -77,7 +77,7 @@ end local function dosteps (siz) collectgarbage() - collectgarbage"stop" + collectgarbage("stop") local a = {} for i=1,100 do a[i] = {{}}; local b = {} end local x = gcinfo() @@ -99,11 +99,11 @@ assert(dosteps(10000) == 1) do local x = gcinfo() collectgarbage() - collectgarbage"stop" + collectgarbage("stop") repeat local a = {} until gcinfo() > 1000 - collectgarbage"restart" + collectgarbage("restart") repeat local a = {} until gcinfo() < 1000 @@ -123,7 +123,7 @@ for n in pairs(b) do end b = nil collectgarbage() -for n in pairs(a) do error'cannot be here' end +for n in pairs(a) do error("cannot be here") end for i=1,lim do a[i] = i end for i=1,lim do assert(a[i] == i) end diff --git a/tests/conformance/nextvar.lua b/tests/conformance/nextvar.lua index 7f9b7596..94ba5ccf 100644 --- a/tests/conformance/nextvar.lua +++ b/tests/conformance/nextvar.lua @@ -368,9 +368,9 @@ assert(next(a,nil) == 1000 and next(a,1000) == nil) assert(next({}) == nil) assert(next({}, nil) == nil) -for a,b in pairs{} do error"not here" end -for i=1,0 do error'not here' end -for i=0,1,-1 do error'not here' end +for a,b in pairs{} do error("not here") end +for i=1,0 do error("not here") end +for i=0,1,-1 do error("not here") end a = nil; for i=1,1 do assert(not a); a=1 end; assert(a) a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a) diff --git a/tests/conformance/pcall.lua b/tests/conformance/pcall.lua index a2072d2c..84ac2ba1 100644 --- a/tests/conformance/pcall.lua +++ b/tests/conformance/pcall.lua @@ -144,4 +144,4 @@ coroutine.resume(co) resumeerror(co, "fail") checkresults({ true, false, "fail" }, coroutine.resume(co)) -return'OK' +return 'OK' diff --git a/tests/conformance/utf8.lua b/tests/conformance/utf8.lua index 024cb16d..bfd7a1ac 100644 --- a/tests/conformance/utf8.lua +++ b/tests/conformance/utf8.lua @@ -205,4 +205,4 @@ for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do end end -return'OK' +return 'OK' diff --git a/tests/conformance/vector.lua b/tests/conformance/vector.lua index 620f646a..7d18bda3 100644 --- a/tests/conformance/vector.lua +++ b/tests/conformance/vector.lua @@ -1,6 +1,9 @@ -- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details print('testing vectors') +-- detect vector size +local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3 + -- equality assert(vector(1, 2, 3) == vector(1, 2, 3)) assert(vector(0, 1, 2) == vector(-0, 1, 2)) @@ -13,8 +16,14 @@ assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4))) -- type & tostring assert(type(vector(1, 2, 3)) == "vector") -assert(tostring(vector(1, 2, 3)) == "1, 2, 3") -assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") + +if vector_size == 4 then + assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4") + assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0") +else + assert(tostring(vector(1, 2, 3)) == "1, 2, 3") + assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") +end local t = {} @@ -42,12 +51,19 @@ assert(8 * vector(8, 16, 24) == vector(64, 128, 192)); assert(vector(1, 2, 4) * '8' == vector(8, 16, 32)); assert('8' * vector(8, 16, 24) == vector(64, 128, 192)); -assert(vector(1, 2, 4) / vector(8, 16, 24) == vector(1/8, 2/16, 4/24)); +if vector_size == 4 then + assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32)); + assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); + assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); +else + assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24)); + assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3)); + assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3)); +end + assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2)); assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8)); -assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3)); assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); -assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3)); assert(-vector(1, 2, 4) == vector(-1, -2, -4)); @@ -71,4 +87,9 @@ assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == fal -- make sure we cover both builtin and C impl assert(vector(1, 2, 4) == vector("1", "2", "4")) +-- additional checks for 4-component vectors +if vector_size == 4 then + assert(vector(1, 2, 3, 4).w == 4) +end + return 'OK' diff --git a/tools/svg.py b/tools/svg.py index 3b3bb28c..99853fb6 100644 --- a/tools/svg.py +++ b/tools/svg.py @@ -458,13 +458,16 @@ def display(root, title, colors, flip = False): framewidth = 1200 - 20 + def pixels(x): + return float(x) / root.width * framewidth if root.width > 0 else 0 + for n in root.subtree(): - if n.width / root.width * framewidth < 0.1: + if pixels(n.width) < 0.1: continue - x = 10 + n.offset / root.width * framewidth + x = 10 + pixels(n.offset) y = (maxdepth - 1 - n.depth if flip else n.depth) * 16 + 3 * 16 - width = n.width / root.width * framewidth + width = pixels(n.width) height = 15 if colors == "cold":