diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index f238e258..f8105383 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -12,6 +12,8 @@ LUAU_FASTFLAG(LuauShareTxnSeen); namespace Luau { +using TypeOrPackId = const void*; + // Log of where what TypeIds we are rebinding and what they used to be // Remove with LuauUseCommitTxnLog struct DEPRECATED_TxnLog @@ -23,7 +25,7 @@ struct DEPRECATED_TxnLog { } - explicit DEPRECATED_TxnLog(std::vector>* sharedSeen) + explicit DEPRECATED_TxnLog(std::vector>* sharedSeen) : originalSeenSize(sharedSeen->size()) , ownedSeen() , sharedSeen(sharedSeen) @@ -48,15 +50,23 @@ struct DEPRECATED_TxnLog void pushSeen(TypeId lhs, TypeId rhs); void popSeen(TypeId lhs, TypeId rhs); + bool haveSeen(TypePackId lhs, TypePackId rhs); + void pushSeen(TypePackId lhs, TypePackId rhs); + void popSeen(TypePackId lhs, TypePackId rhs); + private: std::vector> typeVarChanges; std::vector> typePackChanges; std::vector>> tableChanges; size_t originalSeenSize; + bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs); + void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs); + void popSeen(TypeOrPackId lhs, TypeOrPackId rhs); + public: - std::vector> ownedSeen; // used to avoid infinite recursion when types are cyclic - std::vector>* sharedSeen; // shared with all the descendent logs + std::vector> ownedSeen; // used to avoid infinite recursion when types are cyclic + std::vector>* sharedSeen; // shared with all the descendent logs }; // Pending state for a TypeVar. Generated by a TxnLog and committed via @@ -127,12 +137,12 @@ struct TxnLog } } - explicit TxnLog(std::vector>* sharedSeen) + explicit TxnLog(std::vector>* sharedSeen) : sharedSeen(sharedSeen) { } - TxnLog(TxnLog* parent, std::vector>* sharedSeen) + TxnLog(TxnLog* parent, std::vector>* sharedSeen) : parent(parent) , sharedSeen(sharedSeen) { @@ -173,6 +183,10 @@ struct TxnLog void pushSeen(TypeId lhs, TypeId rhs); void popSeen(TypeId lhs, TypeId rhs); + bool haveSeen(TypePackId lhs, TypePackId rhs) const; + void pushSeen(TypePackId lhs, TypePackId rhs); + void popSeen(TypePackId lhs, TypePackId rhs); + // Queues a type for modification. The original type will not change until commit // is called. Use pending to get the pending state. // @@ -316,12 +330,16 @@ private: // TxnLogs; use sharedSeen instead. This field exists because in the tree // of TxnLogs, the root must own its seen set. In all descendant TxnLogs, // this is an empty vector. - std::vector> ownedSeen; + std::vector> ownedSeen; + + bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const; + void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs); + void popSeen(TypeOrPackId lhs, TypeOrPackId rhs); public: // Used to avoid infinite recursion when types are cyclic. // Shared with all the descendent TxnLogs. - std::vector>* sharedSeen; + std::vector>* sharedSeen; }; } // namespace Luau diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 2440c810..839043cc 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -73,24 +73,6 @@ struct Instantiation : Substitution TypePackId clean(TypePackId tp) override; }; -// A substitution which replaces free types by generic types. -struct Quantification : Substitution -{ - Quantification(TypeArena* arena, TypeLevel level) - : Substitution(TxnLog::empty(), arena) - , level(level) - { - } - - TypeLevel level; - std::vector generics; - std::vector genericPacks; - bool isDirty(TypeId ty) override; - bool isDirty(TypePackId tp) override; - TypeId clean(TypeId ty) override; - TypePackId clean(TypePackId tp) override; -}; - // A substitution which replaces free types by any struct Anyification : Substitution { diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index 8d1a9fa6..29578dcd 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -298,7 +298,7 @@ struct TableTypeVar TableTypeVar() = default; explicit TableTypeVar(TableState state, TypeLevel level); - TableTypeVar(const Props& props, const std::optional& indexer, TypeLevel level, TableState state = TableState::Unsealed); + TableTypeVar(const Props& props, const std::optional& indexer, TypeLevel level, TableState state); Props props; std::optional indexer; @@ -477,6 +477,9 @@ bool isOptional(TypeId ty); bool isTableIntersection(TypeId ty); bool isOverloadedFunction(TypeId ty); +// True when string is a subtype of ty +bool maybeString(TypeId ty); + std::optional getMetatable(TypeId type); TableTypeVar* getMutableTableType(TypeId type); const TableTypeVar* getTableType(TypeId type); diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index fe822b01..4c0462fe 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -56,7 +56,7 @@ struct Unifier Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); - Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, + Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); // Test whether the two type vars unify. Never commits the result. diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 29a2c6b5..c3de8d0e 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -16,7 +16,6 @@ LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false); -LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false); LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false); static const std::unordered_set kStatementStartingKeywords = { @@ -272,55 +271,34 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ TypeId expectedType = follow(*typeAtPosition); - if (FFlag::PreferToCallFunctionsForIntersects) - { - auto checkFunctionType = [&canUnify, &expectedType](const FunctionTypeVar* ftv) { - auto [retHead, retTail] = flatten(ftv->retType); + auto checkFunctionType = [&canUnify, &expectedType](const FunctionTypeVar* ftv) { + auto [retHead, retTail] = flatten(ftv->retType); - if (!retHead.empty() && canUnify(retHead.front(), expectedType)) + if (!retHead.empty() && canUnify(retHead.front(), expectedType)) + return true; + + // 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(vtp->ty, expectedType)) return true; - - // 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(vtp->ty, expectedType)) - return true; - } - - return false; - }; - - // We also want to suggest functions that return compatible result - if (const FunctionTypeVar* ftv = get(ty); ftv && checkFunctionType(ftv)) - { - return TypeCorrectKind::CorrectFunctionResult; } - else if (const IntersectionTypeVar* itv = get(ty)) - { - for (TypeId id : itv->parts) - { - if (const FunctionTypeVar* ftv = get(id); ftv && checkFunctionType(ftv)) - { - return TypeCorrectKind::CorrectFunctionResult; - } - } - } - } - else + + return false; + }; + + // We also want to suggest functions that return compatible result + if (const FunctionTypeVar* ftv = get(ty); ftv && checkFunctionType(ftv)) { - // We also want to suggest functions that return compatible result - if (const FunctionTypeVar* ftv = get(ty)) + return TypeCorrectKind::CorrectFunctionResult; + } + else if (const IntersectionTypeVar* itv = get(ty)) + { + for (TypeId id : itv->parts) { - auto [retHead, retTail] = flatten(ftv->retType); - - if (!retHead.empty() && canUnify(retHead.front(), expectedType)) - return TypeCorrectKind::CorrectFunctionResult; - - // We might only have a variadic tail pack, check if the element is compatible - if (retTail) + if (const FunctionTypeVar* ftv = get(id); ftv && checkFunctionType(ftv)) { - if (const VariadicTypePack* vtp = get(follow(*retTail)); vtp && canUnify(vtp->ty, expectedType)) - return TypeCorrectKind::CorrectFunctionResult; + return TypeCorrectKind::CorrectFunctionResult; } } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index d72422a5..e4e5dab8 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -9,6 +9,7 @@ #include LUAU_FASTFLAG(LuauAssertStripsFalsyTypes) +LUAU_FASTFLAGVARIABLE(LuauTableCloneType, false) /** FIXME: Many of these type definitions are not quite completely accurate. * @@ -283,8 +284,16 @@ void registerBuiltinTypes(TypeChecker& typeChecker) attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable); attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect); - auto tableLib = getMutable(getGlobalBinding(typeChecker, "table")); - attachMagicFunction(tableLib->props["pack"].type, magicFunctionPack); + if (TableTypeVar* ttv = getMutable(getGlobalBinding(typeChecker, "table"))) + { + // tabTy is a generic table type which we can't express via declaration syntax yet + ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze"); + + if (FFlag::LuauTableCloneType) + ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone"); + + attachMagicFunction(ttv->props["pack"].type, magicFunctionPack); + } attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire); } diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index bf6e1193..471b61ad 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -170,7 +170,6 @@ declare function gcinfo(): number move: ({V}, number, number, number, {V}?) -> {V}, clear: ({[K]: V}) -> (), - freeze: ({[K]: V}) -> {[K]: V}, isfrozen: ({[K]: V}) -> boolean, } diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/JsonEncoder.cpp index ec399158..811e7c24 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(LuauTypeAliasDefaults) - namespace Luau { @@ -369,38 +367,24 @@ struct AstJsonEncoder : public AstVisitor void write(const AstGenericType& genericType) { - if (FFlag::LuauTypeAliasDefaults) - { - writeRaw("{"); - bool c = pushComma(); - write("name", genericType.name); - if (genericType.defaultValue) - write("type", genericType.defaultValue); - popComma(c); - writeRaw("}"); - } - else - { - write(genericType.name); - } + writeRaw("{"); + bool c = pushComma(); + write("name", genericType.name); + if (genericType.defaultValue) + write("type", genericType.defaultValue); + popComma(c); + writeRaw("}"); } void write(const AstGenericTypePack& genericTypePack) { - if (FFlag::LuauTypeAliasDefaults) - { - writeRaw("{"); - bool c = pushComma(); - write("name", genericTypePack.name); - if (genericTypePack.defaultValue) - write("type", genericTypePack.defaultValue); - popComma(c); - writeRaw("}"); - } - else - { - write(genericTypePack.name); - } + writeRaw("{"); + bool c = pushComma(); + write("name", genericTypePack.name); + if (genericTypePack.defaultValue) + write("type", genericTypePack.defaultValue); + popComma(c); + writeRaw("}"); } void write(AstExprTable::Item::Kind kind) diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 7635dc0f..56c4e3e8 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -13,6 +13,7 @@ #include LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) +LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false) namespace Luau { @@ -233,6 +234,20 @@ public: } private: + struct FunctionInfo + { + explicit FunctionInfo(AstExprFunction* ast) + : ast(ast) + , dominatedGlobals({}) + , conditionalExecution(false) + { + } + + AstExprFunction* ast; + DenseHashSet dominatedGlobals; + bool conditionalExecution; + }; + struct Global { AstExprGlobal* firstRef = nullptr; @@ -241,6 +256,9 @@ private: bool assigned = false; bool builtin = false; + bool definedInModuleScope = false; + bool definedAsFunction = false; + bool readBeforeWritten = false; std::optional deprecated; }; @@ -248,7 +266,8 @@ private: DenseHashMap globals; std::vector globalRefs; - std::vector functionStack; + std::vector functionStack; + LintGlobalLocal() : globals(AstName()) @@ -291,12 +310,18 @@ private: "Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local", g.firstRef->name.value, top->location.begin.line + 1); } + else if (FFlag::LuauLintGlobalNeverReadBeforeWritten && g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && + g.firstRef->name != context->placeholder) + { + emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, + "Global '%s' is never read before being written. Consider changing it to local", g.firstRef->name.value); + } } } bool visit(AstExprFunction* node) override { - functionStack.push_back(node); + functionStack.emplace_back(node); node->body->visit(this); @@ -307,6 +332,11 @@ private: bool visit(AstExprGlobal* node) override { + if (FFlag::LuauLintGlobalNeverReadBeforeWritten && !functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name)) + { + Global& g = globals[node->name]; + g.readBeforeWritten = true; + } trackGlobalRef(node); if (node->name == context->placeholder) @@ -335,6 +365,21 @@ private: { Global& g = globals[gv->name]; + if (FFlag::LuauLintGlobalNeverReadBeforeWritten) + { + if (functionStack.empty()) + { + g.definedInModuleScope = true; + } + else + { + if (!functionStack.back().conditionalExecution) + { + functionStack.back().dominatedGlobals.insert(gv->name); + } + } + } + if (g.builtin) emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location, "Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value); @@ -369,7 +414,14 @@ private: emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location, "Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value); else + { g.assigned = true; + if (FFlag::LuauLintGlobalNeverReadBeforeWritten) + { + g.definedAsFunction = true; + g.definedInModuleScope = functionStack.empty(); + } + } trackGlobalRef(gv); } @@ -377,6 +429,98 @@ private: return true; } + class HoldConditionalExecution + { + public: + HoldConditionalExecution(LintGlobalLocal& p) + : p(p) + { + if (!p.functionStack.empty() && !p.functionStack.back().conditionalExecution) + { + resetToFalse = true; + p.functionStack.back().conditionalExecution = true; + } + } + ~HoldConditionalExecution() + { + if (resetToFalse) + p.functionStack.back().conditionalExecution = false; + } + + private: + bool resetToFalse = false; + LintGlobalLocal& p; + }; + + bool visit(AstStatIf* node) override + { + if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) + return true; + + HoldConditionalExecution ce(*this); + node->condition->visit(this); + node->thenbody->visit(this); + if (node->elsebody) + node->elsebody->visit(this); + + return false; + } + + bool visit(AstStatWhile* node) override + { + if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) + return true; + + HoldConditionalExecution ce(*this); + node->condition->visit(this); + node->body->visit(this); + + return false; + } + + bool visit(AstStatRepeat* node) override + { + if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) + return true; + + HoldConditionalExecution ce(*this); + node->condition->visit(this); + node->body->visit(this); + + return false; + } + + bool visit(AstStatFor* node) override + { + if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) + return true; + + HoldConditionalExecution ce(*this); + node->from->visit(this); + node->to->visit(this); + + if (node->step) + node->step->visit(this); + + node->body->visit(this); + + return false; + } + + bool visit(AstStatForIn* node) override + { + if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) + return true; + + HoldConditionalExecution ce(*this); + for (AstExpr* expr : node->values) + expr->visit(this); + + node->body->visit(this); + + return false; + } + void trackGlobalRef(AstExprGlobal* node) { Global& g = globals[node->name]; @@ -390,7 +534,12 @@ private: // to reduce the cost of tracking we only track this for user globals if (!g.builtin) { - g.functionRef = functionStack; + g.functionRef.clear(); + g.functionRef.reserve(functionStack.size()); + for (const FunctionInfo& entry : functionStack) + { + g.functionRef.push_back(entry.ast); + } } } else @@ -401,7 +550,7 @@ private: // we need to find a common prefix between all uses of a global size_t prefix = 0; - while (prefix < g.functionRef.size() && prefix < functionStack.size() && g.functionRef[prefix] == functionStack[prefix]) + while (prefix < g.functionRef.size() && prefix < functionStack.size() && g.functionRef[prefix] == functionStack[prefix].ast) prefix++; g.functionRef.resize(prefix); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 412b78bb..76dc72d2 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) -LUAU_FASTFLAG(LuauTypeAliasDefaults) LUAU_FASTFLAG(LuauImmutableTypes) namespace Luau @@ -463,7 +462,7 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, See TypeId ty = clone(param.ty, dest, seenTypes, seenTypePacks, cloneState); std::optional defaultValue; - if (FFlag::LuauTypeAliasDefaults && param.defaultValue) + if (param.defaultValue) defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState); result.typeParams.push_back({ty, defaultValue}); @@ -474,7 +473,7 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, See TypePackId tp = clone(param.tp, dest, seenTypes, seenTypePacks, cloneState); std::optional defaultValue; - if (FFlag::LuauTypeAliasDefaults && param.defaultValue) + if (param.defaultValue) defaultValue = clone(*param.defaultValue, dest, seenTypes, seenTypePacks, cloneState); result.typePackParams.push_back({tp, defaultValue}); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 5e79b841..010ca361 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -10,8 +10,6 @@ #include #include -LUAU_FASTFLAG(LuauTypeAliasDefaults) - /* * Prefix generic typenames with gen- * Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4 @@ -288,28 +286,15 @@ struct TypeVarStringifier else first = false; - if (FFlag::LuauTypeAliasDefaults) - { - bool wrap = !singleTp && get(follow(tp)); + bool wrap = !singleTp && get(follow(tp)); - if (wrap) - state.emit("("); + if (wrap) + state.emit("("); - stringify(tp); + stringify(tp); - if (wrap) - state.emit(")"); - } - else - { - if (!singleTp) - state.emit("("); - - stringify(tp); - - if (!singleTp) - state.emit(")"); - } + if (wrap) + state.emit(")"); } if (types.size() || typePacks.size()) @@ -1105,100 +1090,8 @@ std::string toString(const TypePackVar& tp, const ToStringOptions& opts) return toString(const_cast(&tp), std::move(opts)); } -std::string toStringNamedFunction_DEPRECATED(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts) -{ - std::string s = prefix; - - auto toString_ = [&opts](TypeId ty) -> std::string { - ToStringResult res = toStringDetailed(ty, opts); - opts.nameMap = std::move(res.nameMap); - return res.name; - }; - - auto toStringPack_ = [&opts](TypePackId ty) -> std::string { - ToStringResult res = toStringDetailed(ty, opts); - opts.nameMap = std::move(res.nameMap); - return res.name; - }; - - if (!opts.hideNamedFunctionTypeParameters && (!ftv.generics.empty() || !ftv.genericPacks.empty())) - { - s += "<"; - - bool first = true; - for (TypeId g : ftv.generics) - { - if (!first) - s += ", "; - first = false; - s += toString_(g); - } - - for (TypePackId gp : ftv.genericPacks) - { - if (!first) - s += ", "; - first = false; - s += toStringPack_(gp); - } - - s += ">"; - } - - s += "("; - - auto argPackIter = begin(ftv.argTypes); - auto argNameIter = ftv.argNames.begin(); - - bool first = true; - while (argPackIter != end(ftv.argTypes)) - { - if (!first) - s += ", "; - first = false; - - // 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 += "_: "; - } - - s += toString_(*argPackIter); - ++argPackIter; - } - - if (argPackIter.tail()) - { - if (auto vtp = get(*argPackIter.tail())) - s += ", ...: " + toString_(vtp->ty); - else - s += ", ...: " + toStringPack_(*argPackIter.tail()); - } - - s += "): "; - - size_t retSize = size(ftv.retType); - bool hasTail = !finite(ftv.retType); - if (retSize == 0 && !hasTail) - s += "()"; - else if ((retSize == 0 && hasTail) || (retSize == 1 && !hasTail)) - s += toStringPack_(ftv.retType); - else - s += "(" + toStringPack_(ftv.retType) + ")"; - - return s; -} - std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeVar& ftv, ToStringOptions opts) { - if (!FFlag::LuauTypeAliasDefaults) - return toStringNamedFunction_DEPRECATED(prefix, ftv, opts); - ToStringResult result; StringifierState state(opts, result, opts.nameMap); TypeVarStringifier tvs{state}; diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index a02d396b..92ed241e 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,8 +10,6 @@ #include #include -LUAU_FASTFLAG(LuauTypeAliasDefaults) - namespace { bool isIdentifierStartChar(char c) @@ -796,21 +794,14 @@ struct Printer { comma(); - if (FFlag::LuauTypeAliasDefaults) - { - writer.advance(o.location.begin); - writer.identifier(o.name.value); + writer.advance(o.location.begin); + writer.identifier(o.name.value); - if (o.defaultValue) - { - writer.maybeSpace(o.defaultValue->location.begin, 2); - writer.symbol("="); - visualizeTypeAnnotation(*o.defaultValue); - } - } - else + if (o.defaultValue) { - writer.identifier(o.name.value); + writer.maybeSpace(o.defaultValue->location.begin, 2); + writer.symbol("="); + visualizeTypeAnnotation(*o.defaultValue); } } @@ -818,23 +809,15 @@ struct Printer { comma(); - if (FFlag::LuauTypeAliasDefaults) - { - writer.advance(o.location.begin); - writer.identifier(o.name.value); - writer.symbol("..."); + writer.advance(o.location.begin); + writer.identifier(o.name.value); + writer.symbol("..."); - if (o.defaultValue) - { - writer.maybeSpace(o.defaultValue->location.begin, 2); - writer.symbol("="); - visualizeTypePackAnnotation(*o.defaultValue, false); - } - } - else + if (o.defaultValue) { - writer.identifier(o.name.value); - writer.symbol("..."); + writer.maybeSpace(o.defaultValue->location.begin, 2); + writer.symbol("="); + visualizeTypePackAnnotation(*o.defaultValue, false); } } @@ -882,18 +865,14 @@ struct Printer { comma(); - if (FFlag::LuauTypeAliasDefaults) - writer.advance(o.location.begin); - + writer.advance(o.location.begin); writer.identifier(o.name.value); } for (const auto& o : func.genericPacks) { comma(); - if (FFlag::LuauTypeAliasDefaults) - writer.advance(o.location.begin); - + writer.advance(o.location.begin); writer.identifier(o.name.value); writer.symbol("..."); } @@ -1023,18 +1002,14 @@ struct Printer { comma(); - if (FFlag::LuauTypeAliasDefaults) - writer.advance(o.location.begin); - + writer.advance(o.location.begin); writer.identifier(o.name.value); } for (const auto& o : a->genericPacks) { comma(); - if (FFlag::LuauTypeAliasDefaults) - writer.advance(o.location.begin); - + writer.advance(o.location.begin); writer.identifier(o.name.value); writer.symbol("..."); } diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 00067bdd..c7bf1e62 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -61,22 +61,52 @@ void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs) bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs) { - LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)); + return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void DEPRECATED_TxnLog::pushSeen(TypeId lhs, TypeId rhs) { - LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); - sharedSeen->push_back(sortedPair); + pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); } void DEPRECATED_TxnLog::popSeen(TypeId lhs, TypeId rhs) +{ + popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +bool DEPRECATED_TxnLog::haveSeen(TypePackId lhs, TypePackId rhs) +{ + return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void DEPRECATED_TxnLog::pushSeen(TypePackId lhs, TypePackId rhs) +{ + pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void DEPRECATED_TxnLog::popSeen(TypePackId lhs, TypePackId rhs) +{ + popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +bool DEPRECATED_TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) { LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)); +} + +void DEPRECATED_TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs) +{ + LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + sharedSeen->push_back(sortedPair); +} + +void DEPRECATED_TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs) +{ + LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); LUAU_ASSERT(sortedPair == sharedSeen->back()); sharedSeen->pop_back(); } @@ -186,10 +216,40 @@ TxnLog TxnLog::inverse() } bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const +{ + return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void TxnLog::pushSeen(TypeId lhs, TypeId rhs) +{ + pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void TxnLog::popSeen(TypeId lhs, TypeId rhs) +{ + popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +bool TxnLog::haveSeen(TypePackId lhs, TypePackId rhs) const +{ + return haveSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void TxnLog::pushSeen(TypePackId lhs, TypePackId rhs) +{ + pushSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +void TxnLog::popSeen(TypePackId lhs, TypePackId rhs) +{ + popSeen((TypeOrPackId)lhs, (TypeOrPackId)rhs); +} + +bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const { LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair)) { return true; @@ -203,19 +263,19 @@ bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const return false; } -void TxnLog::pushSeen(TypeId lhs, TypeId rhs) +void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs) { LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); sharedSeen->push_back(sortedPair); } -void TxnLog::popSeen(TypeId lhs, TypeId rhs) +void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs) { LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog); - const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); + const std::pair sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); LUAU_ASSERT(sortedPair == sharedSeen->back()); sharedSeen->pop_back(); } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index faf60eb3..8e6b3b52 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -29,22 +29,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false) -LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false) -LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) +LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) -LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. -LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false) LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree) LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false) +LUAU_FASTFLAGVARIABLE(LuauDoNotAccidentallyDependOnPointerOrdering, false) namespace Luau { @@ -445,7 +443,7 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block) // function f(x:a):a local x: number = g(37) return x end // function g(x:number):number return f(x) end // ``` - if (FFlag::LuauQuantifyInPlace2 ? containsFunctionCallOrReturn(**protoIter) : containsFunctionCall(**protoIter)) + if (containsFunctionCallOrReturn(**protoIter)) { while (checkIter != protoIter) { @@ -1676,7 +1674,7 @@ std::optional TypeChecker::getIndexTypeFromType( } else if (tableType->state == TableState::Free) { - TypeId result = FFlag::LuauAscribeCorrectLevelToInferredProperitesOfFreeTables ? freshType(tableType->level) : freshType(scope); + TypeId result = freshType(tableType->level); tableType->props[name] = {result}; return result; } @@ -1776,31 +1774,62 @@ std::optional TypeChecker::getIndexTypeFromType( std::vector TypeChecker::reduceUnion(const std::vector& types) { - std::set s; - - for (TypeId t : types) + if (FFlag::LuauDoNotAccidentallyDependOnPointerOrdering) { - if (const UnionTypeVar* utv = get(follow(t))) + std::vector result; + for (TypeId t : types) { - std::vector r = reduceUnion(utv->options); - for (TypeId ty : r) - s.insert(ty); + t = follow(t); + if (get(t) || get(t)) + return {t}; + + if (const UnionTypeVar* utv = get(t)) + { + std::vector r = reduceUnion(utv->options); + for (TypeId ty : r) + { + ty = follow(ty); + if (get(ty) || get(ty)) + return {ty}; + + if (std::find(result.begin(), result.end(), ty) == result.end()) + result.push_back(ty); + } + } + else if (std::find(result.begin(), result.end(), t) == result.end()) + result.push_back(t); } - else - s.insert(t); - } - // If any of them are ErrorTypeVars/AnyTypeVars, decay into them. - for (TypeId t : s) + return result; + } + else { - t = follow(t); - if (get(t) || get(t)) - return {t}; - } + std::set s; - std::vector r(s.begin(), s.end()); - std::sort(r.begin(), r.end()); - return r; + for (TypeId t : types) + { + if (const UnionTypeVar* utv = get(follow(t))) + { + std::vector r = reduceUnion(utv->options); + for (TypeId ty : r) + s.insert(ty); + } + else + s.insert(t); + } + + // If any of them are ErrorTypeVars/AnyTypeVars, decay into them. + for (TypeId t : s) + { + t = follow(t); + if (get(t) || get(t)) + return {t}; + } + + std::vector r(s.begin(), s.end()); + std::sort(r.begin(), r.end()); + return r; + } } std::optional TypeChecker::tryStripUnionFromNil(TypeId ty) @@ -2811,7 +2840,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex } else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free) { - TypeId resultType = freshType(FFlag::LuauAnotherTypeLevelFix ? exprTable->level : scope->level); + TypeId resultType = freshType(exprTable->level); exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)}; return resultType; } @@ -4453,51 +4482,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp) return addTypePack(TypePackVar(FreeTypePack{level})); } -bool Quantification::isDirty(TypeId ty) -{ - if (const TableTypeVar* ttv = log->getMutable(ty)) - return level.subsumes(ttv->level) && ((ttv->state == TableState::Free) || (ttv->state == TableState::Unsealed)); - else if (const FreeTypeVar* ftv = log->getMutable(ty)) - return level.subsumes(ftv->level); - else - return false; -} - -bool Quantification::isDirty(TypePackId tp) -{ - if (const FreeTypePack* ftv = log->getMutable(tp)) - return level.subsumes(ftv->level); - else - return false; -} - -TypeId Quantification::clean(TypeId ty) -{ - LUAU_ASSERT(isDirty(ty)); - if (const TableTypeVar* ttv = log->getMutable(ty)) - { - TableState state = (ttv->state == TableState::Unsealed ? TableState::Sealed : TableState::Generic); - TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, state}; - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; - clone.definitionModuleName = ttv->definitionModuleName; - return addType(std::move(clone)); - } - else - { - TypeId generic = addType(GenericTypeVar{level}); - generics.push_back(generic); - return generic; - } -} - -TypePackId Quantification::clean(TypePackId tp) -{ - LUAU_ASSERT(isDirty(tp)); - TypePackId genericPack = addTypePack(TypePackVar(GenericTypePack{level})); - genericPacks.push_back(genericPack); - return genericPack; -} - bool Anyification::isDirty(TypeId ty) { if (const TableTypeVar* ttv = log->getMutable(ty)) @@ -4550,29 +4534,8 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location if (!ftv || !ftv->generics.empty() || !ftv->genericPacks.empty()) return ty; - if (FFlag::LuauQuantifyInPlace2) - { - Luau::quantify(ty, scope->level); - return ty; - } - - Quantification quantification{¤tModule->internalTypes, scope->level}; - std::optional qty = quantification.substitute(ty); - - if (!qty.has_value()) - { - reportError(location, UnificationTooComplex{}); - return errorRecoveryType(scope); - } - - if (ty == *qty) - return ty; - - FunctionTypeVar* qftv = getMutable(*qty); - LUAU_ASSERT(qftv); - qftv->generics = std::move(quantification.generics); - qftv->genericPacks = std::move(quantification.genericPacks); - return *qty; + Luau::quantify(ty, scope->level); + return ty; } TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log) @@ -4915,35 +4878,20 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty()) return tf->type; - bool hasDefaultTypes = false; - bool hasDefaultPacks = false; bool parameterCountErrorReported = false; + bool hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) { + return el.defaultValue.has_value(); + }); + bool hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) { + return el.defaultValue.has_value(); + }); - if (FFlag::LuauTypeAliasDefaults) + if (!lit->hasParameterList) { - hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); - hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); - - if (!lit->hasParameterList) - { - if ((!tf->typeParams.empty() && !hasDefaultTypes) || (!tf->typePackParams.empty() && !hasDefaultPacks)) - { - reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}}); - parameterCountErrorReported = true; - if (!FFlag::LuauErrorRecoveryType) - return errorRecoveryType(scope); - } - } - } - else - { - if (!lit->hasParameterList && !tf->typePackParams.empty()) + if ((!tf->typeParams.empty() && !hasDefaultTypes) || (!tf->typePackParams.empty() && !hasDefaultPacks)) { reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}}); + parameterCountErrorReported = true; if (!FFlag::LuauErrorRecoveryType) return errorRecoveryType(scope); } @@ -4986,72 +4934,69 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation if (typePackParams.empty() && !extraTypes.empty()) typePackParams.push_back(addTypePack(extraTypes)); - if (FFlag::LuauTypeAliasDefaults) + size_t typesProvided = typeParams.size(); + size_t typesRequired = tf->typeParams.size(); + + size_t packsProvided = typePackParams.size(); + size_t packsRequired = tf->typePackParams.size(); + + bool notEnoughParameters = + (typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired); + bool hasDefaultParameters = hasDefaultTypes || hasDefaultPacks; + + // Add default type and type pack parameters if that's required and it's possible + if (notEnoughParameters && hasDefaultParameters) { - size_t typesProvided = typeParams.size(); - size_t typesRequired = tf->typeParams.size(); + // 'applyTypeFunction' is used to substitute default types that reference previous generic types + ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; - size_t packsProvided = typePackParams.size(); - size_t packsRequired = tf->typePackParams.size(); + for (size_t i = 0; i < typesProvided; ++i) + applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i]; - bool notEnoughParameters = - (typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired); - bool hasDefaultParameters = hasDefaultTypes || hasDefaultPacks; - - // Add default type and type pack parameters if that's required and it's possible - if (notEnoughParameters && hasDefaultParameters) + if (typesProvided < typesRequired) { - // 'applyTypeFunction' is used to substitute default types that reference previous generic types - ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; - - for (size_t i = 0; i < typesProvided; ++i) - applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i]; - - if (typesProvided < typesRequired) + for (size_t i = typesProvided; i < typesRequired; ++i) { - for (size_t i = typesProvided; i < typesRequired; ++i) + TypeId defaultTy = tf->typeParams[i].defaultValue.value_or(nullptr); + + if (!defaultTy) + break; + + std::optional maybeInstantiated = applyTypeFunction.substitute(defaultTy); + + if (!maybeInstantiated.has_value()) { - TypeId defaultTy = tf->typeParams[i].defaultValue.value_or(nullptr); - - if (!defaultTy) - break; - - std::optional maybeInstantiated = applyTypeFunction.substitute(defaultTy); - - if (!maybeInstantiated.has_value()) - { - reportError(annotation.location, UnificationTooComplex{}); - maybeInstantiated = errorRecoveryType(scope); - } - - applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated; - typeParams.push_back(*maybeInstantiated); + reportError(annotation.location, UnificationTooComplex{}); + maybeInstantiated = errorRecoveryType(scope); } + + applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated; + typeParams.push_back(*maybeInstantiated); } + } - for (size_t i = 0; i < packsProvided; ++i) - applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i]; + for (size_t i = 0; i < packsProvided; ++i) + applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i]; - if (packsProvided < packsRequired) + if (packsProvided < packsRequired) + { + for (size_t i = packsProvided; i < packsRequired; ++i) { - for (size_t i = packsProvided; i < packsRequired; ++i) + TypePackId defaultTp = tf->typePackParams[i].defaultValue.value_or(nullptr); + + if (!defaultTp) + break; + + std::optional maybeInstantiated = applyTypeFunction.substitute(defaultTp); + + if (!maybeInstantiated.has_value()) { - TypePackId defaultTp = tf->typePackParams[i].defaultValue.value_or(nullptr); - - if (!defaultTp) - break; - - std::optional maybeInstantiated = applyTypeFunction.substitute(defaultTp); - - if (!maybeInstantiated.has_value()) - { - reportError(annotation.location, UnificationTooComplex{}); - maybeInstantiated = errorRecoveryTypePack(scope); - } - - applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = *maybeInstantiated; - typePackParams.push_back(*maybeInstantiated); + reportError(annotation.location, UnificationTooComplex{}); + maybeInstantiated = errorRecoveryTypePack(scope); } + + applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = *maybeInstantiated; + typePackParams.push_back(*maybeInstantiated); } } } @@ -5343,12 +5288,12 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, TypeId instantiated = *maybeInstantiated; - // TODO: CLI-46926 it's not a good idea to rename the type here TypeId target = follow(instantiated); bool needsClone = follow(tf.type) == target; + bool shouldMutate = (!FFlag::LuauOnlyMutateInstantiatedTables || getTableType(tf.type)); TableTypeVar* ttv = getMutableTableType(target); - - if (ttv && needsClone) + + if (shouldMutate && ttv && needsClone) { // Substitution::clone is a shallow clone. If this is a metatable type, we // want to mutate its table, so we need to explicitly clone that table as @@ -5368,7 +5313,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, } } - if (ttv) + if (shouldMutate && ttv) { ttv->instantiatedTypeParams = typeParams; ttv->instantiatedTypePackParams = typePackParams; @@ -5382,7 +5327,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st { LUAU_ASSERT(scope->parent); - const TypeLevel level = (FFlag::LuauQuantifyInPlace2 && levelOpt) ? *levelOpt : scope->level; + const TypeLevel level = levelOpt.value_or(scope->level); std::vector generics; @@ -5390,7 +5335,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st { std::optional defaultValue; - if (FFlag::LuauTypeAliasDefaults && generic.defaultValue) + if (generic.defaultValue) defaultValue = resolveType(scope, *generic.defaultValue); Name n = generic.name.value; @@ -5426,7 +5371,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st { std::optional defaultValue; - if (FFlag::LuauTypeAliasDefaults && genericPack.defaultValue) + if (genericPack.defaultValue) defaultValue = resolveTypePack(scope, *genericPack.defaultValue); Name n = genericPack.name.value; diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index a1dcfdbe..5af2c8a6 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -24,6 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauErrorRecoveryType) +LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables) LUAU_FASTFLAG(LuauDiscriminableUnions2) namespace Luau @@ -157,6 +158,7 @@ bool isNumber(TypeId ty) return isPrim(ty, PrimitiveTypeVar::Number); } +// Returns true when ty is a subtype of string bool isString(TypeId ty) { if (isPrim(ty, PrimitiveTypeVar::String) || get(get(follow(ty)))) @@ -168,6 +170,27 @@ bool isString(TypeId ty) return false; } +// Returns true when ty is a supertype of string +bool maybeString(TypeId ty) +{ + if (FFlag::LuauSubtypingAddOptPropsToUnsealedTables) + { + ty = follow(ty); + + if (isPrim(ty, PrimitiveTypeVar::String) || get(ty)) + return true; + + if (auto utv = get(ty)) + return std::any_of(begin(utv), end(utv), maybeString); + + return false; + } + else + { + return isString(ty); + } +} + bool isThread(TypeId ty) { return isPrim(ty, PrimitiveTypeVar::Thread); @@ -684,7 +707,7 @@ TypeId SingletonTypes::makeStringMetatable() {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}}, {"upper", {stringToStringType}}, {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, - {arena->addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, TypeLevel{}})})}}, + {arena->addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})})}}, {"pack", {arena->addType(FunctionTypeVar{ arena->addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack, @@ -761,6 +784,8 @@ void persist(TypeId ty) } else if (auto ttv = get(t)) { + LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed); + for (const auto& [_name, prop] : ttv->props) queue.push_back(prop.type); diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index d0eba013..6c29486a 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -18,11 +18,13 @@ LUAU_FASTFLAG(LuauImmutableTypes) LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); -LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauErrorRecoveryType); +LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false) LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false) LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false) +LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter, false) +LUAU_FASTFLAGVARIABLE(LuauTxnLogSeesTypePacks2, true) namespace Luau { @@ -329,7 +331,7 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance LUAU_ASSERT(sharedState.iceHandler); } -Unifier::Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, +Unifier::Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) : types(types) , mode(mode) @@ -656,26 +658,85 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId failed = true; } - if (FFlag::LuauUseCommittingTxnLog) + if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter) { - if (i == count - 1) - { - log.concat(std::move(innerState.log)); - } + if (!FFlag::LuauUseCommittingTxnLog) + innerState.DEPRECATED_log.rollback(); } else { - if (i != count - 1) + if (FFlag::LuauUseCommittingTxnLog) { - innerState.DEPRECATED_log.rollback(); + if (i == count - 1) + { + log.concat(std::move(innerState.log)); + } } else { - DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + if (i != count - 1) + { + innerState.DEPRECATED_log.rollback(); + } + else + { + DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log)); + } } - } - ++i; + ++i; + } + } + + // even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option. + if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter) + { + auto tryBind = [this, subTy](TypeId superOption) { + superOption = FFlag::LuauUseCommittingTxnLog ? log.follow(superOption) : follow(superOption); + + // just skip if the superOption is not free-ish. + auto ttv = log.getMutable(superOption); + if (!log.is(superOption) && (!ttv || ttv->state != TableState::Free)) + return; + + // Since we have already checked if S <: T, checking it again will not queue up the type for replacement. + // So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set. + if (FFlag::LuauUseCommittingTxnLog) + { + if (log.haveSeen(subTy, superOption)) + { + // TODO: would it be nice for TxnLog::replace to do this? + if (log.is(superOption)) + log.bindTable(superOption, subTy); + else + log.replace(superOption, *subTy); + } + } + else + { + if (DEPRECATED_log.haveSeen(subTy, superOption)) + { + if (auto ttv = getMutable(superOption)) + { + DEPRECATED_log(ttv); + ttv->boundTo = subTy; + } + else + { + DEPRECATED_log(superOption); + *asMutable(superOption) = BoundTypeVar(subTy); + } + } + } + }; + + if (auto utv = (FFlag::LuauUseCommittingTxnLog ? log.getMutable(superTy) : get(superTy))) + { + for (TypeId ty : utv) + tryBind(ty); + } + else + tryBind(superTy); } if (unificationTooComplex) @@ -1163,6 +1224,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal if (superTp == subTp) return; + if (FFlag::LuauTxnLogSeesTypePacks2 && log.haveSeen(superTp, subTp)) + return; + if (log.getMutable(superTp)) { occursCheck(superTp, subTp); @@ -1365,6 +1429,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal if (superTp == subTp) return; + if (FFlag::LuauTxnLogSeesTypePacks2 && DEPRECATED_log.haveSeen(superTp, subTp)) + return; + if (get(superTp)) { occursCheck(superTp, subTp); @@ -1619,6 +1686,17 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal DEPRECATED_log.pushSeen(superFunction->generics[i], subFunction->generics[i]); } + if (FFlag::LuauTxnLogSeesTypePacks2) + { + for (size_t i = 0; i < numGenericPacks; i++) + { + if (FFlag::LuauUseCommittingTxnLog) + log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + else + DEPRECATED_log.pushSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + } + } + CountMismatch::Context context = ctx; if (!isFunctionCall) @@ -1708,6 +1786,17 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal ctx = context; + if (FFlag::LuauTxnLogSeesTypePacks2) + { + for (int i = int(numGenericPacks) - 1; 0 <= i; i--) + { + if (FFlag::LuauUseCommittingTxnLog) + log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + else + DEPRECATED_log.popSeen(superFunction->genericPacks[i], subFunction->genericPacks[i]); + } + } + for (int i = int(numGenerics) - 1; 0 <= i; i--) { if (FFlag::LuauUseCommittingTxnLog) @@ -1760,7 +1849,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) std::vector extraProperties; // Optimization: First test that the property sets are compatible without doing any recursive unification - if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer && subTable->state != TableState::Free) + if (!subTable->indexer && subTable->state != TableState::Free) { for (const auto& [propName, superProp] : superTable->props) { @@ -1769,7 +1858,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) bool isAny = FFlag::LuauUseCommittingTxnLog ? log.getMutable(log.follow(superProp.type)) : get(follow(superProp.type)); - if (subIter == subTable->props.end() && !isOptional(superProp.type) && !isAny) + if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) && !isAny) missingProperties.push_back(propName); } @@ -1781,7 +1870,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } // And vice versa if we're invariant - if (FFlag::LuauTableUnificationEarlyTest && variance == Invariant && !superTable->indexer && superTable->state != TableState::Unsealed && + if (variance == Invariant && !superTable->indexer && superTable->state != TableState::Unsealed && superTable->state != TableState::Free) { for (const auto& [propName, subProp] : subTable->props) @@ -1790,7 +1879,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) bool isAny = FFlag::LuauUseCommittingTxnLog ? log.getMutable(log.follow(subProp.type)) : get(follow(subProp.type)); - if (superIter == superTable->props.end() && !isOptional(subProp.type) && !isAny) + if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny))) extraProperties.push_back(propName); } @@ -1830,7 +1919,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) innerState.DEPRECATED_log.rollback(); } } - else if (subTable->indexer && isString(subTable->indexer->indexType)) + else if (subTable->indexer && maybeString(subTable->indexer->indexType)) { // TODO: read-only indexers don't need invariance // TODO: really we should only allow this if prop.type is optional. @@ -1855,9 +1944,11 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) innerState.DEPRECATED_log.rollback(); } } - else if (isOptional(prop.type) || get(follow(prop.type))) - // TODO: this case is unsound, but without it our test suite fails. CLI-46031 + else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && (isOptional(prop.type) || get(follow(prop.type)))) + // This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }` + // since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`. // TODO: should isOptional(anyType) be true? + // TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?) { } else if (subTable->state == TableState::Free) @@ -1887,7 +1978,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) // If both lt and rt contain the property, then // we're done since we already unified them above } - else if (superTable->indexer && isString(superTable->indexer->indexType)) + else if (superTable->indexer && maybeString(superTable->indexer->indexType)) { // TODO: read-only indexers don't need invariance // TODO: really we should only allow this if prop.type is optional. @@ -1936,9 +2027,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) else if (variance == Covariant) { } - else if (isOptional(prop.type) || get(follow(prop.type))) - // TODO: this case is unsound, but without it our test suite fails. CLI-46031 - // TODO: should isOptional(anyType) be true? + else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get(follow(prop.type)))) { } else if (superTable->state == TableState::Free) @@ -2333,7 +2422,7 @@ void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersec bool errorReported = false; // Optimization: First test that the property sets are compatible without doing any recursive unification - if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer) + if (!subTable->indexer) { for (const auto& [propName, superProp] : superTable->props) { diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 8767daa0..1cb8f134 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -11,7 +11,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) -LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false) LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false) @@ -779,7 +778,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) if (!name) name = Name(nameError, lexer.current().location); - auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ FFlag::LuauParseTypeAliasDefaults); + auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ true); expectAndConsume('=', "type alias"); diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 13304d57..5fd6d341 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -413,29 +413,22 @@ static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer) ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar); } -struct LinenoiseScopedHistory +static void loadHistory(const char* name) { - LinenoiseScopedHistory() + std::string path; + + if (const char* home = getenv("HOME")) { - const std::string name(".luau_history"); - - if (const char* home = getenv("HOME")) - { - historyFilepath = joinPaths(home, name); - } - else if (const char* userProfile = getenv("USERPROFILE")) - { - historyFilepath = joinPaths(userProfile, name); - } - - if (!historyFilepath.empty()) - ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */); + path = joinPaths(home, name); + } + else if (const char* userProfile = getenv("USERPROFILE")) + { + path = joinPaths(userProfile, name); } - ~LinenoiseScopedHistory() {} - - std::string historyFilepath; -}; + if (!path.empty()) + ic_set_history(path.c_str(), -1 /* default entries (= 200) */); +} static void runReplImpl(lua_State* L) { @@ -447,8 +440,10 @@ static void runReplImpl(lua_State* L) // Prevent auto insertion of braces ic_enable_brace_insertion(false); + // Loads history from the given file; isocline automatically saves the history on process exit + loadHistory(".luau_history"); + std::string buffer; - LinenoiseScopedHistory scopedHistory; for (;;) { diff --git a/VM/include/lua.h b/VM/include/lua.h index af0e2835..0a561f27 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -265,6 +265,8 @@ LUA_API double lua_clock(); LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*)); +LUA_API void lua_clonefunction(lua_State* L, int idx); + /* ** reference system, can be used to pin objects */ @@ -324,6 +326,7 @@ typedef struct lua_Debug lua_Debug; /* activation record */ /* Functions to be called by the debugger in specific events */ typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar); +LUA_API int lua_stackdepth(lua_State* L); LUA_API int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar); LUA_API int lua_getargument(lua_State* L, int level, int n); LUA_API const char* lua_getlocal(lua_State* L, int level, int n); diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 39c76e08..f7f15442 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -14,7 +14,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauGcForwardMetatableBarrier, false) +LUAU_FASTFLAG(LuauGcAdditionalStats) 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" @@ -876,16 +876,7 @@ int lua_setmetatable(lua_State* L, int objindex) luaG_runerror(L, "Attempt to modify a readonly table"); hvalue(obj)->metatable = mt; if (mt) - { - if (FFlag::LuauGcForwardMetatableBarrier) - { - luaC_objbarrier(L, hvalue(obj), mt); - } - else - { - luaC_objbarriert(L, hvalue(obj), mt); - } - } + luaC_objbarrier(L, hvalue(obj), mt); break; } case LUA_TUSERDATA: @@ -1069,6 +1060,8 @@ int lua_gc(lua_State* L, int what, int data) g->GCthreshold = 0; bool waspaused = g->gcstate == GCSpause; + double startmarktime = g->gcstats.currcycle.marktime; + double startsweeptime = g->gcstats.currcycle.sweeptime; // track how much work the loop will actually perform size_t actualwork = 0; @@ -1086,6 +1079,31 @@ int lua_gc(lua_State* L, int what, int data) } } + if (FFlag::LuauGcAdditionalStats) + { + // record explicit step statistics + GCCycleStats* cyclestats = g->gcstate == GCSpause ? &g->gcstats.lastcycle : &g->gcstats.currcycle; + + double totalmarktime = cyclestats->marktime - startmarktime; + double totalsweeptime = cyclestats->sweeptime - startsweeptime; + + if (totalmarktime > 0.0) + { + cyclestats->markexplicitsteps++; + + if (totalmarktime > cyclestats->markmaxexplicittime) + cyclestats->markmaxexplicittime = totalmarktime; + } + + if (totalsweeptime > 0.0) + { + cyclestats->sweepexplicitsteps++; + + if (totalsweeptime > cyclestats->sweepmaxexplicittime) + cyclestats->sweepmaxexplicittime = totalsweeptime; + } + } + // if cycle hasn't finished, advance threshold forward for the amount of extra work performed if (g->gcstate != GCSpause) { @@ -1299,6 +1317,18 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*)) L->global->udatagc[tag] = dtor; } +LUA_API void lua_clonefunction(lua_State* L, int idx) +{ + StkId p = index2addr(L, idx); + api_check(L, isLfunction(p)); + + luaC_checkthreadsleep(L); + + Closure* cl = clvalue(p); + Closure* newcl = luaF_newLclosure(L, 0, L->gt, cl->l.p); + setclvalue(L, L->top - 1, newcl); +} + lua_Callbacks* lua_callbacks(lua_State* L) { return &L->global->cb; diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 71975a52..9a6f7793 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,8 +11,6 @@ #include -LUAU_FASTFLAG(LuauSchubfach) - /* convert a stack index to positive */ #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) @@ -480,18 +478,13 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len) switch (lua_type(L, idx)) { case LUA_TNUMBER: - if (FFlag::LuauSchubfach) - { - double n = lua_tonumber(L, idx); - char s[LUAI_MAXNUM2STR]; - char* e = luai_num2str(s, n); - lua_pushlstring(L, s, e - s); - } - else - { - lua_pushstring(L, lua_tostring(L, idx)); - } + { + double n = lua_tonumber(L, idx); + char s[LUAI_MAXNUM2STR]; + char* e = luai_num2str(s, n); + lua_pushlstring(L, s, e - s); break; + } case LUA_TSTRING: lua_pushvalue(L, idx); break; @@ -505,29 +498,18 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len) { const float* v = lua_tovector(L, idx); - if (FFlag::LuauSchubfach) + char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE]; + char* e = s; + for (int i = 0; i < LUA_VECTOR_SIZE; ++i) { - char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE]; - char* e = s; - for (int i = 0; i < LUA_VECTOR_SIZE; ++i) + if (i != 0) { - if (i != 0) - { - *e++ = ','; - *e++ = ' '; - } - e = luai_num2str(e, v[i]); + *e++ = ','; + *e++ = ' '; } - lua_pushlstring(L, s, e - s); - } - else - { -#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 + e = luai_num2str(e, v[i]); } + lua_pushlstring(L, s, e - s); break; } default: diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index a4f93c62..7a9947b7 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -168,6 +168,11 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, return status; } +int lua_stackdepth(lua_State* L) +{ + return int(L->ci - L->base_ci); +} + int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar) { int status = 0; diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 8c3a2029..a656854e 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -11,6 +11,8 @@ #include "lmem.h" #include "ludata.h" +LUAU_FASTFLAGVARIABLE(LuauGcAdditionalStats, false) + #include #define GC_SWEEPMAX 40 @@ -53,17 +55,28 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds, case GCSpause: // record root mark time if we have switched to next state if (g->gcstate == GCSpropagate) + { g->gcstats.currcycle.marktime += seconds; + + if (FFlag::LuauGcAdditionalStats && assist) + g->gcstats.currcycle.markassisttime += seconds; + } break; case GCSpropagate: case GCSpropagateagain: g->gcstats.currcycle.marktime += seconds; + + if (FFlag::LuauGcAdditionalStats && assist) + g->gcstats.currcycle.markassisttime += seconds; break; case GCSatomic: g->gcstats.currcycle.atomictime += seconds; break; case GCSsweep: g->gcstats.currcycle.sweeptime += seconds; + + if (FFlag::LuauGcAdditionalStats && assist) + g->gcstats.currcycle.sweepassisttime += seconds; break; default: LUAU_ASSERT(!"Unexpected GC state"); @@ -78,7 +91,7 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds, static void startGcCycleStats(global_State* g) { g->gcstats.currcycle.starttimestamp = lua_clock(); - g->gcstats.currcycle.waittime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp; + g->gcstats.currcycle.pausetime = g->gcstats.currcycle.starttimestamp - g->gcstats.lastcycle.endtimestamp; } static void finishGcCycleStats(global_State* g) @@ -585,10 +598,21 @@ static size_t atomic(lua_State* L) LUAU_ASSERT(g->gcstate == GCSatomic); size_t work = 0; + double currts = lua_clock(); + double prevts = currts; + /* remark occasional upvalues of (maybe) dead threads */ work += remarkupvals(g); /* traverse objects caught by write barrier and by 'remarkupvals' */ work += propagateall(g); + + if (FFlag::LuauGcAdditionalStats) + { + currts = lua_clock(); + g->gcstats.currcycle.atomictimeupval += currts - prevts; + prevts = currts; + } + /* remark weak tables */ g->gray = g->weak; g->weak = NULL; @@ -596,16 +620,41 @@ static size_t atomic(lua_State* L) markobject(g, L); /* mark running thread */ markmt(g); /* mark basic metatables (again) */ work += propagateall(g); + + if (FFlag::LuauGcAdditionalStats) + { + currts = lua_clock(); + g->gcstats.currcycle.atomictimeweak += currts - prevts; + prevts = currts; + } + /* remark gray again */ g->gray = g->grayagain; g->grayagain = NULL; work += propagateall(g); - work += cleartable(L, g->weak); /* remove collected objects from weak tables */ + + if (FFlag::LuauGcAdditionalStats) + { + currts = lua_clock(); + g->gcstats.currcycle.atomictimegray += currts - prevts; + prevts = currts; + } + + /* remove collected objects from weak tables */ + work += cleartable(L, g->weak); g->weak = NULL; + + if (FFlag::LuauGcAdditionalStats) + { + currts = lua_clock(); + g->gcstats.currcycle.atomictimeclear += currts - prevts; + } + /* flip current white */ g->currentwhite = cast_byte(otherwhite(g)); g->sweepgcopage = g->allgcopages; g->gcstate = GCSsweep; + return work; } @@ -693,6 +742,9 @@ static size_t gcstep(lua_State* L, size_t limit) if (!g->gray) { + if (FFlag::LuauGcAdditionalStats) + g->gcstats.currcycle.propagatework = g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork; + // perform one iteration over 'gray again' list g->gray = g->grayagain; g->grayagain = NULL; @@ -710,6 +762,10 @@ static size_t gcstep(lua_State* L, size_t limit) if (!g->gray) /* no more `gray' objects */ { + if (FFlag::LuauGcAdditionalStats) + g->gcstats.currcycle.propagateagainwork = + g->gcstats.currcycle.explicitwork + g->gcstats.currcycle.assistwork - g->gcstats.currcycle.propagatework; + g->gcstate = GCSatomic; } break; @@ -811,6 +867,12 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal) void luaC_step(lua_State* L, bool assist) { global_State* g = L->global; + + if (assist) + g->gcstats.currcycle.assistrequests += g->gcstepsize; + else + g->gcstats.currcycle.explicitrequests += g->gcstepsize; + int lim = (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */ LUAU_ASSERT(g->totalbytes >= g->GCthreshold); size_t debt = g->totalbytes - g->GCthreshold; @@ -833,6 +895,11 @@ void luaC_step(lua_State* L, bool assist) recordGcStateTime(g, lastgcstate, lua_clock() - lasttimestamp, assist); + if (lastgcstate == GCSpropagate) + g->gcstats.currcycle.markrequests += g->gcstepsize; + else if (lastgcstate == GCSsweep) + g->gcstats.currcycle.sweeprequests += g->gcstepsize; + // at the end of the last cycle if (g->gcstate == GCSpause) { @@ -844,6 +911,9 @@ void luaC_step(lua_State* L, bool assist) finishGcCycleStats(g); + if (FFlag::LuauGcAdditionalStats) + g->gcstats.currcycle.starttotalsizebytes = g->totalbytes; + g->gcstats.currcycle.heapgoalsizebytes = heapgoal; g->gcstats.currcycle.heaptriggersizebytes = heaptrigger; } diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 253e269f..cbeeebd4 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -111,13 +111,6 @@ luaC_barrierf(L, obj2gco(p), obj2gco(o)); \ } -// TODO: remove with FFlagLuauGcForwardMetatableBarrier -#define luaC_objbarriert(L, t, o) \ - { \ - if (isblack(obj2gco(t)) && iswhite(obj2gco(o))) \ - luaC_barriertable(L, t, obj2gco(o)); \ - } - #define luaC_upvalbarrier(L, uv, tv) \ { \ if (iscollectable(tv) && iswhite(gcvalue(tv)) && (!(uv) || ((UpVal*)uv)->v != &((UpVal*)uv)->u.value)) \ diff --git a/VM/src/lnumprint.cpp b/VM/src/lnumprint.cpp index 2fd0f1bb..d64e3ca4 100644 --- a/VM/src/lnumprint.cpp +++ b/VM/src/lnumprint.cpp @@ -6,7 +6,6 @@ #include "lcommon.h" #include -#include // TODO: Remove with LuauSchubfach #ifdef _MSC_VER #include @@ -18,8 +17,6 @@ // The code uses the notation from the paper for local variables where appropriate, and refers to paper sections/figures/results. -LUAU_FASTFLAGVARIABLE(LuauSchubfach, false) - // 9.8.2. Precomputed table for 128-bit overestimates of powers of 10 (see figure 3 for table bounds) // To avoid storing 616 128-bit numbers directly we use a technique inspired by Dragonbox implementation and store 16 consecutive // powers using a 128-bit baseline and a bitvector with 1-bit scale and 3-bit offset for the delta between each entry and base*5^k @@ -275,12 +272,6 @@ inline char* trimzero(char* end) char* luai_num2str(char* buf, double n) { - if (!FFlag::LuauSchubfach) - { - snprintf(buf, LUAI_MAXNUM2STR, LUA_NUMBER_FMT, n); - return buf + strlen(buf); - } - // IEEE-754 union { diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index fba07bc3..549b4630 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -55,7 +55,6 @@ LUAU_FASTMATH_END #define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n)) #endif -#define LUA_NUMBER_FMT "%.14g" /* TODO: Remove with LuauSchubfach */ #define LUAI_MAXNUM2STR 48 LUAI_FUNC char* luai_num2str(char* buf, double n); diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 3ee96718..b2bedb48 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -77,25 +77,46 @@ typedef struct CallInfo struct GCCycleStats { + size_t starttotalsizebytes = 0; size_t heapgoalsizebytes = 0; size_t heaptriggersizebytes = 0; - double waittime = 0.0; // time from end of the last cycle to the start of a new one + double pausetime = 0.0; // time from end of the last cycle to the start of a new one double starttimestamp = 0.0; double endtimestamp = 0.0; double marktime = 0.0; + double markassisttime = 0.0; + double markmaxexplicittime = 0.0; + size_t markexplicitsteps = 0; + size_t markrequests = 0; double atomicstarttimestamp = 0.0; size_t atomicstarttotalsizebytes = 0; double atomictime = 0.0; + // specific atomic stage parts + double atomictimeupval = 0.0; + double atomictimeweak = 0.0; + double atomictimegray = 0.0; + double atomictimeclear = 0.0; + double sweeptime = 0.0; + double sweepassisttime = 0.0; + double sweepmaxexplicittime = 0.0; + size_t sweepexplicitsteps = 0; + size_t sweeprequests = 0; + + size_t assistrequests = 0; + size_t explicitrequests = 0; size_t assistwork = 0; size_t explicitwork = 0; + size_t propagatework = 0; + size_t propagateagainwork = 0; + size_t endtotalsizebytes = 0; }; diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 0412ea76..ef0b4b93 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -447,7 +447,8 @@ void luaH_free(lua_State* L, Table* t, lua_Page* page) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat); - luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat); + if (t->array) + luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat); luaM_freegco(L, t, sizeof(Table), t->memcat, page); } diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 0d3374ef..00753742 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -2,6 +2,7 @@ // This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details #include "lualib.h" +#include "lapi.h" #include "lstate.h" #include "ltable.h" #include "lstring.h" @@ -9,6 +10,8 @@ #include "ldebug.h" #include "lvm.h" +LUAU_FASTFLAGVARIABLE(LuauTableClone, false) + static int foreachi(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -507,6 +510,23 @@ static int tisfrozen(lua_State* L) return 1; } +static int tclone(lua_State* L) +{ + if (!FFlag::LuauTableClone) + luaG_runerror(L, "table.clone is not available"); + + luaL_checktype(L, 1, LUA_TTABLE); + luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable"); + + Table* tt = luaH_clone(L, hvalue(L->base)); + + TValue v; + sethvalue(L, &v, tt); + luaA_pushobject(L, &v); + + return 1; +} + static const luaL_Reg tab_funcs[] = { {"concat", tconcat}, {"foreach", foreach}, @@ -524,6 +544,7 @@ static const luaL_Reg tab_funcs[] = { {"clear", tclear}, {"freeze", tfreeze}, {"isfrozen", tisfrozen}, + {"clone", tclone}, {NULL, NULL}, }; diff --git a/bench/gc/test_GC_Boehm_Trees.lua b/bench/gc/test_GC_Boehm_Trees.lua index 1451769f..5abad1d8 100644 --- a/bench/gc/test_GC_Boehm_Trees.lua +++ b/bench/gc/test_GC_Boehm_Trees.lua @@ -74,4 +74,7 @@ function test() end end +bench.runs = 6 +bench.extraRuns = 2 + bench.runCode(test, "GC: Boehm tree") diff --git a/bench/gc/test_GC_Tree_Pruning_Eager.lua b/bench/gc/test_GC_Tree_Pruning_Eager.lua index 514766ae..2111d9ff 100644 --- a/bench/gc/test_GC_Tree_Pruning_Eager.lua +++ b/bench/gc/test_GC_Tree_Pruning_Eager.lua @@ -40,7 +40,7 @@ function test() local tree = { id = 0 } - for i = 1,1000 do + for i = 1,100 do fill_tree(tree, 10) prune_tree(tree, 0) diff --git a/bench/gc/test_GC_Tree_Pruning_Gen.lua b/bench/gc/test_GC_Tree_Pruning_Gen.lua index a8d0f40a..f88bd7f4 100644 --- a/bench/gc/test_GC_Tree_Pruning_Gen.lua +++ b/bench/gc/test_GC_Tree_Pruning_Gen.lua @@ -42,7 +42,7 @@ function test() local tree = { id = 0 } fill_tree(tree, 16) - for i = 1,1000 do + for i = 1,100 do local small_tree = { id = 0 } fill_tree(small_tree, 8) diff --git a/bench/gc/test_GC_Tree_Pruning_Lazy.lua b/bench/gc/test_GC_Tree_Pruning_Lazy.lua index 8cb69192..3ea6bbef 100644 --- a/bench/gc/test_GC_Tree_Pruning_Lazy.lua +++ b/bench/gc/test_GC_Tree_Pruning_Lazy.lua @@ -46,7 +46,7 @@ function test() local tree = { id = 0 } - for i = 1,1000 do + for i = 1,100 do fill_tree(tree, 10) prune_tree(tree, 0) diff --git a/extern/isocline/include/isocline.h b/extern/isocline/include/isocline.h index 0d46cf3f..a7e03ed2 100644 --- a/extern/isocline/include/isocline.h +++ b/extern/isocline/include/isocline.h @@ -259,7 +259,7 @@ void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_comple /// The `escape_char` is the escaping character, usually `\` but use 0 to not have escape characters. /// The `quote_chars` define the quotes, use NULL for the default `"\'\""` quotes. /// @see ic_complete_word() which uses the default values for `non_word_chars`, `quote_chars` and `\` for escape characters. -void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t fun, +void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars ); /// \} diff --git a/fuzz/number.cpp b/fuzz/number.cpp index 70447409..31c953e3 100644 --- a/fuzz/number.cpp +++ b/fuzz/number.cpp @@ -6,8 +6,6 @@ #include #include -LUAU_FASTFLAG(LuauSchubfach); - #define LUAI_MAXNUM2STR 48 char* luai_num2str(char* buf, double n); @@ -17,8 +15,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) if (Size < 8) return 0; - FFlag::LuauSchubfach.value = true; - double num; memcpy(&num, Data, 8); diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index 912fef23..1022831b 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -59,7 +59,7 @@ void interrupt(lua_State* L, int gc) } } -void* allocate(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize) +void* allocate(void* ud, void* ptr, size_t osize, size_t nsize) { if (nsize == 0) { diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 6aadef32..ce890ba8 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauUseCommittingTxnLog) +LUAU_FASTFLAG(LuauTableCloneType) using namespace Luau; @@ -262,7 +263,7 @@ TEST_CASE_FIXTURE(ACFixture, "get_member_completions") auto ac = autocomplete('1'); - CHECK_EQ(16, ac.entryMap.size()); + CHECK_EQ(FFlag::LuauTableCloneType ? 17 : 16, ac.entryMap.size()); CHECK(ac.entryMap.count("find")); CHECK(ac.entryMap.count("pack")); CHECK(!ac.entryMap.count("math")); @@ -2235,7 +2236,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocompleteSource") auto ac = autocompleteSource(frontend, source, Position{1, 24}, nullCallback).result; - CHECK_EQ(16, ac.entryMap.size()); + CHECK_EQ(FFlag::LuauTableCloneType ? 17 : 16, ac.entryMap.size()); CHECK(ac.entryMap.count("find")); CHECK(ac.entryMap.count("pack")); CHECK(!ac.entryMap.count("math")); @@ -2695,8 +2696,6 @@ local r4 = t:bar1(@4) TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_parameters") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - check(R"( type A = () -> T )"); @@ -2709,8 +2708,6 @@ type A = () -> T TEST_CASE_FIXTURE(ACFixture, "autocomplete_default_type_pack_parameters") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - check(R"( type A = () -> T )"); @@ -2768,7 +2765,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "function_in_assignment_has_parentheses_2") { ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true); - ScopedFastFlag preferToCallFunctionsForIntersects("PreferToCallFunctionsForIntersects", true); check(R"( local bar: ((number) -> number) & (number, number) -> number) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index eb6ca749..63fbb363 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -241,6 +241,8 @@ TEST_CASE("Math") TEST_CASE("Table") { + ScopedFastFlag sff("LuauTableClone", true); + runConformance("nextvar.lua"); } @@ -465,6 +467,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { + ScopedFastFlag sff("LuauTableCloneType", true); + runConformance("types.lua", [](lua_State* L) { Luau::NullModuleResolver moduleResolver; Luau::InternalErrorReporter iceHandler; @@ -959,8 +963,6 @@ TEST_CASE("Coverage") TEST_CASE("StringConversion") { - ScopedFastFlag sff{"LuauSchubfach", true}; - runConformance("strconv.lua"); } diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index ab19cea3..4d6c207c 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -157,6 +157,113 @@ return bar() CHECK_EQ(result.warnings[0].text, "Global 'foo' is only used in the enclosing function 'bar'; consider changing it to local"); } +TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx") +{ + ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; + LintResult result = lint(R"( +function bar() + foo = 6 + return foo +end + +function baz() + foo = 6 + return foo +end + +return bar() + baz() +)"); + + REQUIRE_EQ(result.warnings.size(), 1); + CHECK_EQ(result.warnings[0].text, "Global 'foo' is never read before being written. Consider changing it to local"); +} + +TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead") +{ + ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; + LintResult result = lint(R"( +function bar() + foo = 6 + return foo +end + +function baz() + foo = 6 + return foo +end + +function read() + print(foo) +end + +return bar() + baz() + read() +)"); + + CHECK_EQ(result.warnings.size(), 0); +} + +TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional") +{ + ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; + LintResult result = lint(R"( +function bar() + if true then foo = 6 end + return foo +end + +function baz() + foo = 6 + return foo +end + +return bar() + baz() +)"); + + CHECK_EQ(result.warnings.size(), 0); +} + +TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead") +{ + ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; + LintResult result = lint(R"( +function bar() + foo = 6 + return foo +end + +function baz() + foo = 6 + return foo +end + +function read() + if false then print(foo) end +end + +return bar() + baz() + read() +)"); + + CHECK_EQ(result.warnings.size(), 0); +} + +TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead") +{ + ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; + LintResult result = lint(R"( +function foo() + local f = function() return bar end + f() + bar = 42 +end + +function baz() bar = 0 end + +return foo() + baz() +)"); + + CHECK_EQ(result.warnings.size(), 0); +} + TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMulti") { LintResult result = lint(R"( diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 77e49ce3..7f6a6c0d 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -1988,8 +1988,6 @@ TEST_CASE_FIXTURE(Fixture, "function_type_matching_parenthesis") TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - AstStat* stat = parse(R"( type A = {} type B = {} @@ -2005,8 +2003,6 @@ type G = (U...) -> T... TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type_errors") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - matchParseError("type Y = {}", "Expected default type after type name", Location{{0, 20}, {0, 21}}); matchParseError("type Y = {}", "Expected default type pack after type pack name", Location{{0, 29}, {0, 30}}); matchParseError("type Y number> = {}", "Expected type pack after '=', got type", Location{{0, 14}, {0, 32}}); @@ -2574,8 +2570,6 @@ do end TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ParseResult result = tryParse(R"( type Y = (T...) -> U... )"); diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index 0ca9c994..29bdd866 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -96,8 +96,6 @@ n2 [label="number"]; TEST_CASE_FIXTURE(Fixture, "function") { - ScopedFastFlag luauQuantifyInPlace2{"LuauQuantifyInPlace2", true}; - CheckResult result = check(R"( local function f(a, ...: string) return a end )"); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index bbb26291..6713a589 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -500,8 +500,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map") TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack") { - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( local function f(a: number, b: string) end local function test(...: T...): U... diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 332aba9e..5f0295b0 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -641,9 +641,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_to_string") TEST_CASE_FIXTURE(Fixture, "transpile_type_alias_default_type_parameters") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - std::string code = R"( type Packed = (T, U, V...)->(W...) local a: Packed diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 31d7ef10..d584eb2d 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -625,9 +625,8 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni ScopedFastFlag sff[] = { {"LuauTwoPassAliasDefinitionFix", true}, - // We also force these two flags because this surfaced an unfortunate interaction. + // We also force this flag because it surfaced an unfortunate interaction. {"LuauErrorRecoveryType", true}, - {"LuauQuantifyInPlace2", true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index f3dfb214..bf990770 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -934,4 +934,31 @@ TEST_CASE_FIXTURE(Fixture, "assert_returns_false_and_string_iff_it_knows_the_fir CHECK_EQ("(nil) -> nil", toString(requireType("f"))); } +TEST_CASE_FIXTURE(Fixture, "table_freeze_is_generic") +{ + CheckResult result = check(R"( + local t1: {a: number} = {a = 42} + local t2: {b: string} = {b = "hello"} + local t3: {boolean} = {false, true} + + local tf1 = table.freeze(t1) + local tf2 = table.freeze(t2) + local tf3 = table.freeze(t3) + + local a = tf1.a + local b = tf2.b + local c = tf3[2] + + local d = tf1.b + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0])); + + CHECK_EQ("number", toString(requireType("a"))); + CHECK_EQ("string", toString(requireType("b"))); + CHECK_EQ("boolean", toString(requireType("c"))); + CHECK_EQ("*unknown*", toString(requireType("d"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index f8fccf6b..c482847b 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -697,4 +697,93 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") +{ + ScopedFastFlag sffs[] = { + { "LuauTableSubtypingVariance2", true }, + { "LuauUnsealedTableLiteral", true }, + { "LuauPropertiesGetExpectedType", true }, + { "LuauRecursiveTypeParameterRestriction", true }, + }; + + CheckResult result = check(R"( +--!strict +-- At one point this produced a UAF +type T = { a: U, b: a } +type U = { c: T?, d : a } +local x: T = { a = { c = nil, d = 5 }, b = 37 } +x.a.c = x +local y: T = { a = { c = nil, d = 5 }, b = 37 } +y.a.c = y + )"); + + LUAU_REQUIRE_ERRORS(result); + CHECK_EQ(toString(result.errors[0]), + R"(Type 'y' could not be converted into 'T' +caused by: + Property 'a' is not compatible. Type '{ c: T?, d: number }' could not be converted into 'U' +caused by: + Property 'd' is not compatible. Type 'number' could not be converted into 'string')"); +} + +TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1") +{ + ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true}; + + CheckResult result = check(R"( +--!strict +type Dispatcher = { + useMemo: (create: () -> T...) -> T... +} + +local TheDispatcher: Dispatcher = { + useMemo = function(create: () -> U...): U... + return create() + end +} + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification2") +{ + ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true}; + + CheckResult result = check(R"( +--!strict +type Dispatcher = { + useMemo: (create: () -> T...) -> T... +} + +local TheDispatcher: Dispatcher = { + useMemo = function(create) + return create() + end +} + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification3") +{ + ScopedFastFlag sff{"LuauTxnLogSeesTypePacks2", true}; + + CheckResult result = check(R"( +--!strict +type Dispatcher = { + useMemo: (arg: S, create: (S) -> T...) -> T... +} + +local TheDispatcher: Dispatcher = { + useMemo = function(arg: T, create: (T) -> U...): U... + return create(arg) + end +} + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index eee0e0f1..2e16b21e 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -8,7 +8,6 @@ #include LUAU_FASTFLAG(LuauEqConstraint) -LUAU_FASTFLAG(LuauQuantifyInPlace2) using namespace Luau; @@ -40,16 +39,6 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") end )"; - const std::string old_expected = R"( - function f(a:{fn:()->(free,free...)}): () - if type(a) == 'boolean'then - local a1:boolean=a - elseif a.fn()then - local a2:{fn:()->(free,free...)}=a - end - end - )"; - const std::string expected = R"( function f(a:{fn:()->(a,b...)}): () if type(a) == 'boolean'then @@ -60,10 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") end )"; - if (FFlag::LuauQuantifyInPlace2) - CHECK_EQ(expected, decorateWithTypes(code)); - else - CHECK_EQ(old_expected, decorateWithTypes(code)); + CHECK_EQ(expected, decorateWithTypes(code)); } TEST_CASE_FIXTURE(Fixture, "xpcall_returns_what_f_returns") @@ -135,46 +121,6 @@ TEST_CASE_FIXTURE(Fixture, "setmetatable_constrains_free_type_into_free_table") CHECK_EQ("number", toString(tm->givenType)); } -TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table") -{ - CheckResult result = check(R"( - local a: {x: number, y: number, [any]: any} | {y: number} - - function f(t) - t.y = 1 - return t - end - - local b = f(a) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // :( - // Should be the same as the type of a - REQUIRE_EQ("{| y: number |}", toString(requireType("b"))); -} - -TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2") -{ - CheckResult result = check(R"( - local a: {y: number} | {x: number, y: number, [any]: any} - - function f(t) - t.y = 1 - return t - end - - local b = f(a) - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // :( - // Should be the same as the type of a - REQUIRE_EQ("{| [any]: any, x: number, y: number |}", toString(requireType("b"))); -} - // Luau currently doesn't yet know how to allow assignments when the binding was refined. TEST_CASE_FIXTURE(Fixture, "while_body_are_also_refined") { @@ -557,25 +503,6 @@ TEST_CASE_FIXTURE(Fixture, "bail_early_on_typescript_port_of_Result_type" * doct } } -TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") -{ - CheckResult result = check(R"( - --!strict - local function setNumber(t: { p: number? }, x:number) t.p = x end - local function getString(t: { p: string? }):string return t.p or "" end - -- This shouldn't type-check! - local function oh(x:number): string - local t: {} = {} - setNumber(t, x) - return getString(t) - end - local s: string = oh(37) - )"); - - // Really this should return an error, but it doesn't - LUAU_REQUIRE_NO_ERRORS(result); -} - // Should be in TypeInfer.tables.test.cpp // It's unsound to instantiate tables containing generic methods, // since mutating properties means table properties should be invariant. @@ -600,25 +527,9 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param") -{ - // Mutability in type function application right now can create strange recursive types - // TODO: instantiation right now is problematic, in this example should either leave the Table type alone - // or it should rename the type to 'Self' so that the result will be 'Self' - CheckResult result = check(R"( -type Table = { a: number } -type Self = T -local a: Self
- )"); - - LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "Table
"); -} - TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type_pack") { ScopedFastFlag sff[]{ - {"LuauQuantifyInPlace2", true}, {"LuauReturnAnyInsteadOfICE", true}, }; @@ -664,8 +575,6 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early") TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack") { - ScopedFastFlag sff{"LuauQuantifyInPlace2", true}; - CheckResult result = check(R"( local function f() return end local g = function() return f() end @@ -676,8 +585,6 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack") TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack") { - ScopedFastFlag sff{"LuauQuantifyInPlace2", true}; - CheckResult result = check(R"( --!strict local function f(...) return ... end diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index bff8926c..a5147d56 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -8,7 +8,6 @@ LUAU_FASTFLAG(LuauDiscriminableUnions2) LUAU_FASTFLAG(LuauWeakEqConstraint) -LUAU_FASTFLAG(LuauQuantifyInPlace2) using namespace Luau; @@ -1179,20 +1178,14 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector") { LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauQuantifyInPlace2) - CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0])); - else - CHECK_EQ("Type '{- X: a, Y: b, Z: c -}' could not be converted into 'Instance'", toString(result.errors[0])); + CHECK_EQ("Type '{+ X: a, Y: b, Z: c +}' could not be converted into 'Instance'", toString(result.errors[0])); } CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector" CHECK_EQ("*unknown*", toString(requireTypeAtPosition({7, 28}))); // typeof(vec) == "Instance" - if (FFlag::LuauQuantifyInPlace2) - CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance" - else - CHECK_EQ("{- X: a, Y: b, Z: c -}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance" + CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance" } TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to_vector") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 856549bd..3ed536ea 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -465,30 +465,32 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si CHECK_EQ("((string) -> (b...), a) -> ()", toString(requireType("foo"))); } -// TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") -// { -// ScopedFastFlag sff[]{ -// {"LuauParseSingletonTypes", true}, -// {"LuauSingletonTypes", true}, -// {"LuauDiscriminableUnions2", true}, -// {"LuauEqConstraint", true}, -// {"LuauWidenIfSupertypeIsFree", true}, -// {"LuauWeakEqConstraint", false}, -// }; +TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") +{ + ScopedFastFlag sff[]{ + {"LuauParseSingletonTypes", true}, + {"LuauSingletonTypes", true}, + {"LuauDiscriminableUnions2", true}, + {"LuauEqConstraint", true}, + {"LuauWidenIfSupertypeIsFree", true}, + {"LuauWeakEqConstraint", false}, + {"LuauDoNotAccidentallyDependOnPointerOrdering", true} + }; -// CheckResult result = check(R"( -// local function foo(f, x): "hello"? -- anyone there? -// return if x == "hi" -// then f(x) -// else nil -// end -// )"); + CheckResult result = check(R"( + local function foo(f, x): "hello"? -- anyone there? + return if x == "hi" + then f(x) + else nil + end + )"); -// LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); -// CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23}))); -// CHECK_EQ(R"(((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo"))); -// } + CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23}))); + CHECK_EQ(R"(((string) -> (a, c...), b) -> "hello"?)", toString(requireType("foo"))); + // CHECK_EQ(R"(((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo"))); +} TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere") { diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index aa949789..da035ba1 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -1219,13 +1219,12 @@ TEST_CASE_FIXTURE(Fixture, "passing_compatible_unions_to_a_generic_table_without { CheckResult result = check(R"( type A = {x: number, y: number, [any]: any} | {y: number} - local a: A function f(t) t.y = 1 end - f(a) + f({y = 5} :: A) )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -2165,6 +2164,44 @@ b() CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })"); } +TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") +{ + ScopedFastFlag sffs[] = { + {"LuauTableSubtypingVariance2", true}, + {"LuauSubtypingAddOptPropsToUnsealedTables", true}, + }; + + CheckResult result = check(R"( + --!strict + local function setNumber(t: { p: number? }, x:number) t.p = x end + local function getString(t: { p: string? }):string return t.p or "" end + -- This shouldn't type-check! + local function oh(x:number): string + local t: {} = {} + setNumber(t, x) + return getString(t) + end + local s: string = oh(37) + )"); + + LUAU_REQUIRE_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "top_table_type") +{ + CheckResult result = check(R"( + --!strict + type Table = { [any] : any } + type HasTable = { p: Table? } + type HasHasTable = { p: HasTable? } + local t : Table = { p = 5 } + local u : HasTable = { p = { p = 5 } } + local v : HasHasTable = { p = { p = { p = 5 } } } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "length_operator_union") { CheckResult result = check(R"( @@ -2257,4 +2294,44 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing") CHECK_EQ("number | string", toString(requireType("foo"))); } +TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table") +{ + ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter", true}; + + CheckResult result = check(R"( + local a: {x: number, y: number, [any]: any} | {y: number} + + function f(t) + t.y = 1 + return t + end + + local b = f(a) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b"))); +} + +TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2") +{ + ScopedFastFlag sff{"LuauDifferentOrderOfUnificationDoesntMatter", true}; + + CheckResult result = check(R"( + local a: {y: number} | {x: number, y: number, [any]: any} + + function f(t) + t.y = 1 + return t + end + + local b = f(a) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index f44d9fd8..f63579b5 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -4044,6 +4044,49 @@ type t0 = any CHECK(ttv->instantiatedTypeParams.empty()); } +TEST_CASE_FIXTURE(Fixture, "instantiate_table_cloning_2") +{ + ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true}; + + CheckResult result = check(R"( +type X = T +type K = X +)"); + + LUAU_REQUIRE_NO_ERRORS(result); + + std::optional ty = requireType("math"); + REQUIRE(ty); + + const TableTypeVar* ttv = get(*ty); + REQUIRE(ttv); + CHECK(ttv->instantiatedTypeParams.empty()); +} + +TEST_CASE_FIXTURE(Fixture, "instantiate_table_cloning_3") +{ + ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true}; + + CheckResult result = check(R"( +type X = T +local a = {} +a.x = 4 +local b: X +a.y = 5 +local c: X +c = b +)"); + + LUAU_REQUIRE_NO_ERRORS(result); + + std::optional ty = requireType("a"); + REQUIRE(ty); + + const TableTypeVar* ttv = get(*ty); + REQUIRE(ttv); + CHECK(ttv->instantiatedTypeParams.empty()); +} + TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok") { CheckResult result = check(R"( @@ -4065,6 +4108,21 @@ return m LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param") +{ + ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true}; + + // Mutability in type function application right now can create strange recursive types + CheckResult result = check(R"( +type Table = { a: number } +type Self = T +local a: Self
+ )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ(toString(requireType("a")), "Table"); +} + TEST_CASE_FIXTURE(Fixture, "no_persistent_typelevel_change") { TypeId mathTy = requireType(typeChecker.globalScope, "math"); @@ -5284,4 +5342,17 @@ TEST_CASE_FIXTURE(Fixture, "inferred_properties_of_a_table_should_start_with_the LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "global_singleton_types_are_sealed") +{ + CheckResult result = check(R"( +local function f(x: string) + local p = x:split('a') + p = table.pack(table.unpack(p, 1, #p - 1)) + return p +end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index c9bf5103..f6ee3ccc 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -7,8 +7,6 @@ #include "doctest.h" -LUAU_FASTFLAG(LuauQuantifyInPlace2); - using namespace Luau; LUAU_FASTFLAG(LuauUseCommittingTxnLog) @@ -167,10 +165,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails" )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauQuantifyInPlace2) - CHECK_EQ("(number) -> boolean", toString(requireType("f"))); - else - CHECK_EQ("(number) -> (boolean)", toString(requireType("f"))); + CHECK_EQ("(number) -> boolean", toString(requireType("f"))); } TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification") diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index cbe2e48f..6b96f449 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -622,9 +622,6 @@ type Other = Packed TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: T, b: U } @@ -654,9 +651,6 @@ local c: Y = { a = "s" } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_self") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: T, b: U } @@ -682,9 +676,6 @@ local a: Y TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_chained") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: T, b: U, c: V } @@ -700,9 +691,6 @@ local b: Y TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_explicit") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: (T...) -> () } local a: Y<> @@ -715,9 +703,6 @@ local a: Y<> TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: T, b: (U...) -> T } @@ -731,9 +716,6 @@ local a: Y TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_tp") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: (T...) -> U... } local a: Y @@ -746,9 +728,6 @@ local a: Y TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_chained_tp") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: (T...) -> U..., b: (T...) -> V... } local a: Y @@ -761,9 +740,6 @@ local a: Y TEST_CASE_FIXTURE(Fixture, "type_alias_default_mixed_self") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: (T, U, V...) -> W... } local a: Y @@ -782,9 +758,6 @@ local d: Y ()> TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = { a: T } local a: Y = { a = 2 } @@ -834,9 +807,6 @@ local a: Y<...number> TEST_CASE_FIXTURE(Fixture, "type_alias_default_export") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - fileResolver.source["Module/Types"] = R"( export type A = { a: T, b: U } export type B = { a: T, b: U } @@ -882,9 +852,6 @@ local h: Types.H<> TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_skip_brackets") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type Y = (T...) -> number local a: Y @@ -897,9 +864,6 @@ local a: Y TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_confusing_types") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type A = (T, V...) -> (U, W...) type B = A @@ -914,9 +878,6 @@ type C = A TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type") { - ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; - CheckResult result = check(R"( type F ()> = (K) -> V type R = { m: F } diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 3b53ddfe..0e0b6ebb 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -400,10 +400,10 @@ local e = a.z CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3])); } -TEST_CASE_FIXTURE(Fixture, "unify_sealed_table_union_check") +TEST_CASE_FIXTURE(Fixture, "unify_unsealed_table_union_check") { CheckResult result = check(R"( -local x: { x: number } = { x = 3 } +local x = { x = 3 } type A = number? type B = string? local y: { x: number, y: A | B } @@ -413,7 +413,7 @@ y = x LUAU_REQUIRE_NO_ERRORS(result); result = check(R"( -local x: { x: number } = { x = 3 } +local x = { x = 3 } local a: number? = 2 local y = {} @@ -426,6 +426,31 @@ y = x LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "unify_sealed_table_union_check") +{ + ScopedFastFlag sffs[] = { + {"LuauTableSubtypingVariance2", true}, + {"LuauUnsealedTableLiteral", true}, + {"LuauSubtypingAddOptPropsToUnsealedTables", true}, + }; + + CheckResult result = check(R"( + -- the difference between this and unify_unsealed_table_union_check is the type annotation on x +local t = { x = 3, y = true } +local x: { x: number } = t +type A = number? +type B = string? +local y: { x: number, y: A | B } +-- Shouldn't typecheck! +y = x +-- If it does, we can convert any type to any other type +y.y = 5 +local oh : boolean = t.y + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { CheckResult result = check(R"( diff --git a/tests/conformance/nextvar.lua b/tests/conformance/nextvar.lua index 94ba5ccf..e85fcbe8 100644 --- a/tests/conformance/nextvar.lua +++ b/tests/conformance/nextvar.lua @@ -512,4 +512,42 @@ do assert(#t == 7) end +-- test clone +do + local t = {a = 1, b = 2, 3, 4, 5} + local tt = table.clone(t) + + assert(#tt == 3) + assert(tt.a == 1 and tt.b == 2) + + t.c = 3 + assert(tt.c == nil) + + t = table.freeze({"test"}) + tt = table.clone(t) + assert(table.isfrozen(t) and not table.isfrozen(tt)) + + t = setmetatable({}, {}) + tt = table.clone(t) + assert(getmetatable(t) == getmetatable(tt)) + + t = setmetatable({}, {__metatable = "protected"}) + assert(not pcall(table.clone, t)) + + function order(t) + local r = '' + for k,v in pairs(t) do + r ..= tostring(v) + end + return v + end + + t = {a = 1, b = 2, c = 3, d = 4, e = 5, f = 6} + tt = table.clone(t) + assert(order(t) == order(tt)) + + assert(not pcall(table.clone)) + assert(not pcall(table.clone, 42)) +end + return"OK"