diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index bbc65f9..c1de242 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -48,13 +48,24 @@ struct TypePackVar explicit TypePackVar(const TypePackVariant& ty); explicit TypePackVar(TypePackVariant&& ty); TypePackVar(TypePackVariant&& ty, bool persistent); + bool operator==(const TypePackVar& rhs) const; + TypePackVar& operator=(TypePackVariant&& tp); + TypePackVar& operator=(const TypePackVar& rhs); + + // Re-assignes the content of the pack, but doesn't change the owning arena and can't make pack persistent. + void reassign(const TypePackVar& rhs) + { + ty = rhs.ty; + } + TypePackVariant ty; + bool persistent = false; - // Pointer to the type arena that allocated this type. + // Pointer to the type arena that allocated this pack. TypeArena* owningArena = nullptr; }; diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index b3c455c..b59e7c6 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -334,7 +334,6 @@ struct TableTypeVar // We need to know which is which when we stringify types. std::optional syntheticName; - std::map methodDefinitionLocations; // TODO: Remove with FFlag::LuauNoMethodLocations std::vector instantiatedTypeParams; std::vector instantiatedTypePackParams; ModuleName definitionModuleName; @@ -465,6 +464,14 @@ struct TypeVar final { } + // Re-assignes the content of the type, but doesn't change the owning arena and can't make type persistent. + void reassign(const TypeVar& rhs) + { + ty = rhs.ty; + normal = rhs.normal; + documentationSymbol = rhs.documentationSymbol; + } + TypeVariant ty; // Kludge: A persistent TypeVar is one that belongs to the global scope. @@ -486,6 +493,8 @@ struct TypeVar final TypeVar& operator=(const TypeVariant& rhs); TypeVar& operator=(TypeVariant&& rhs); + + TypeVar& operator=(const TypeVar& rhs); }; using SeenSet = std::set>; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index b988ed3..a8319c5 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -14,8 +14,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false); -LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteClassSecurityLevel, false); -LUAU_FASTFLAG(LuauSelfCallAutocompleteFix) +LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2) static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -248,7 +247,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ ty = follow(ty); auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); InternalErrorReporter iceReporter; UnifierSharedState unifierState(&iceReporter); @@ -267,7 +266,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ TypeId expectedType = follow(*typeAtPosition); auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) { - if (FFlag::LuauSelfCallAutocompleteFix) + if (FFlag::LuauSelfCallAutocompleteFix2) { if (std::optional firstRetTy = first(ftv->retType)) return checkTypeMatch(typeArena, *firstRetTy, expectedType); @@ -308,7 +307,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ } } - if (FFlag::LuauSelfCallAutocompleteFix) + if (FFlag::LuauSelfCallAutocompleteFix2) return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; else return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; @@ -325,7 +324,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId const std::vector& nodes, AutocompleteEntryMap& result, std::unordered_set& seen, std::optional containingClass = std::nullopt) { - if (FFlag::LuauSelfCallAutocompleteFix) + if (FFlag::LuauSelfCallAutocompleteFix2) rootTy = follow(rootTy); ty = follow(ty); @@ -335,7 +334,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId seen.insert(ty); auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get(ty)](Luau::TypeId type) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); if (indexType == PropIndexType::Key) return false; @@ -368,7 +367,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId } }; auto isWrongIndexer = [typeArena, rootTy, indexType](Luau::TypeId type) { - LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix); + LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix2); if (indexType == PropIndexType::Key) return false; @@ -382,10 +381,15 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId return calledWithSelf == ftv->hasSelf; } - if (std::optional firstArgTy = first(ftv->argTypes)) + // If a call is made with ':', it is invalid if a function has incompatible first argument or no arguments at all + // If a call is made with '.', but it was declared with 'self', it is considered invalid if first argument is compatible + if (calledWithSelf || ftv->hasSelf) { - if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) - return calledWithSelf; + if (std::optional firstArgTy = first(ftv->argTypes)) + { + if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) + return calledWithSelf; + } } return !calledWithSelf; @@ -427,7 +431,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryKind::Property, type, prop.deprecated, - FFlag::LuauSelfCallAutocompleteFix ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), + FFlag::LuauSelfCallAutocompleteFix2 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), typeCorrect, containingClass, &prop, @@ -462,8 +466,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId containingClass = containingClass.value_or(cls); fillProps(cls->props); if (cls->parent) - autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen, - FFlag::LuauFixAutocompleteClassSecurityLevel ? containingClass : cls); + autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass); } else if (auto tbl = get(ty)) fillProps(tbl->props); @@ -471,7 +474,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId { autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen); - if (FFlag::LuauSelfCallAutocompleteFix) + if (FFlag::LuauSelfCallAutocompleteFix2) { if (auto mtable = get(mt->metatable)) fillMetatableProps(mtable); @@ -537,7 +540,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryMap inner; std::unordered_set innerSeen; - if (!FFlag::LuauSelfCallAutocompleteFix) + if (!FFlag::LuauSelfCallAutocompleteFix2) innerSeen = seen; if (isNil(*iter)) @@ -563,7 +566,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId ++iter; } } - else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix) + else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix2) { if (pt->metatable) { @@ -571,7 +574,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId fillMetatableProps(mtable); } } - else if (FFlag::LuauSelfCallAutocompleteFix && get(get(ty))) + else if (FFlag::LuauSelfCallAutocompleteFix2 && get(get(ty))) { autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen); } @@ -1501,7 +1504,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M TypeId ty = follow(*it); PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point; - if (!FFlag::LuauSelfCallAutocompleteFix && isString(ty)) + if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty)) return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, finder.ancestry), finder.ancestry}; else diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 5ed6de6..98737b4 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -179,44 +179,13 @@ void registerBuiltinTypes(TypeChecker& typeChecker) LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen()); LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen()); - TypeId numberType = typeChecker.numberType; - TypeId booleanType = typeChecker.booleanType; TypeId nilType = typeChecker.nilType; TypeArena& arena = typeChecker.globalTypes; - TypePackId oneNumberPack = arena.addTypePack({numberType}); - TypePackId oneBooleanPack = arena.addTypePack({booleanType}); - - TypePackId numberVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{numberType}}); - TypePackId listOfAtLeastOneNumber = arena.addTypePack(TypePack{{numberType}, numberVariadicList}); - - TypeId listOfAtLeastOneNumberToNumberType = arena.addType(FunctionTypeVar{ - listOfAtLeastOneNumber, - oneNumberPack, - }); - - TypeId listOfAtLeastZeroNumbersToNumberType = arena.addType(FunctionTypeVar{numberVariadicList, oneNumberPack}); - LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau"); LUAU_ASSERT(loadResult.success); - TypeId mathLibType = getGlobalBinding(typeChecker, "math"); - if (TableTypeVar* ttv = getMutable(mathLibType)) - { - ttv->props["min"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.min"); - ttv->props["max"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.max"); - } - - TypeId bit32LibType = getGlobalBinding(typeChecker, "bit32"); - if (TableTypeVar* ttv = getMutable(bit32LibType)) - { - ttv->props["band"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.band"); - ttv->props["bor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bor"); - ttv->props["bxor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bxor"); - ttv->props["btest"] = makeProperty(arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneBooleanPack}), "@luau/global/bit32.btest"); - } - TypeId genericK = arena.addType(GenericTypeVar{"K"}); TypeId genericV = arena.addType(GenericTypeVar{"V"}); TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic}); @@ -231,7 +200,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker) addGlobalBinding(typeChecker, "string", it->second.type, "@luau"); - // next(t: Table, i: K | nil) -> (K, V) + // next(t: Table, i: K?) -> (K, V) TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}}); addGlobalBinding(typeChecker, "next", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau"); @@ -241,8 +210,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker) TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}); TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}}); - // NOTE we are missing 'i: K | nil' argument in the first return types' argument. - // pairs(t: Table) -> ((Table) -> (K, V), Table, nil) + // pairs(t: Table) -> ((Table, K?) -> (K, V), Table, nil) addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau"); TypeId genericMT = arena.addType(GenericTypeVar{"MT"}); diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 19e3383..9180f30 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -9,7 +9,6 @@ LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) -LUAU_FASTFLAG(LuauNoMethodLocations) namespace Luau { @@ -241,8 +240,6 @@ void TypeCloner::operator()(const TableTypeVar& t) arg = clone(arg, dest, cloneState); ttv->definitionModuleName = t.definitionModuleName; - if (!FFlag::LuauNoMethodLocations) - ttv->methodDefinitionLocations = t.methodDefinitionLocations; ttv->tags = t.tags; } @@ -406,8 +403,6 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) { LUAU_ASSERT(!ttv->boundTo); TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; - if (!FFlag::LuauNoMethodLocations) - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.definitionModuleName = ttv->definitionModuleName; clone.name = ttv->name; clone.syntheticName = ttv->syntheticName; diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index f184b74..2407e3e 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -7,7 +7,10 @@ namespace Luau static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC( declare bit32: { - -- band, bor, bxor, and btest are declared in C++ + band: (...number) -> number, + bor: (...number) -> number, + bxor: (...number) -> number, + btest: (number, ...number) -> boolean, rrotate: (number, number) -> number, lrotate: (number, number) -> number, lshift: (number, number) -> number, @@ -50,7 +53,8 @@ declare math: { asin: (number) -> number, atan2: (number, number) -> number, - -- min and max are declared in C++. + min: (number, ...number) -> number, + max: (number, ...number) -> number, pi: number, huge: number, diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 4a12027..f145a51 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -4,8 +4,6 @@ #include "Luau/TxnLog.h" #include "Luau/TypeArena.h" -LUAU_FASTFLAG(LuauNoMethodLocations) - namespace Luau { @@ -110,8 +108,6 @@ TypeId ReplaceGenerics::clean(TypeId ty) if (const TableTypeVar* ttv = log->getMutable(ty)) { TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free}; - if (!FFlag::LuauNoMethodLocations) - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.definitionModuleName = ttv->definitionModuleName; return addType(std::move(clone)); } diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 8f2cc8e..2177537 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -32,41 +32,6 @@ struct Quantifier final : TypeVarOnceVisitor LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); } - void cycle(TypeId) override {} - void cycle(TypePackId) override {} - - bool operator()(TypeId ty, const FreeTypeVar& ftv) - { - return visit(ty, ftv); - } - - template - bool operator()(TypeId ty, const T& t) - { - return true; - } - - template - bool operator()(TypePackId, const T&) - { - return true; - } - - bool operator()(TypeId ty, const ConstrainedTypeVar&) - { - return true; - } - - bool operator()(TypeId ty, const TableTypeVar& ttv) - { - return visit(ty, ttv); - } - - bool operator()(TypePackId tp, const FreeTypePack& ftp) - { - return visit(tp, ftp); - } - /// @return true if outer encloses inner bool subsumes(Scope2* outer, Scope2* inner) { diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index 0a362a5..011e28d 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -2,8 +2,6 @@ #include "Luau/Scope.h" -LUAU_FASTFLAG(LuauTwoPassAliasDefinitionFix); - namespace Luau { @@ -19,8 +17,7 @@ Scope::Scope(const ScopePtr& parent, int subLevel) , returnType(parent->returnType) , level(parent->level.incr()) { - if (FFlag::LuauTwoPassAliasDefinitionFix) - level = level.incr(); + level = level.incr(); level.subLevel = subLevel; } diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 50c516d..5a22dee 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -10,7 +10,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) -LUAU_FASTFLAG(LuauNoMethodLocations) namespace Luau { diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index e45c0cb..4c6d54e 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -7,6 +7,8 @@ #include #include +LUAU_FASTFLAG(LuauNonCopyableTypeVarFields) + namespace Luau { @@ -80,18 +82,32 @@ void TxnLog::commit() { for (auto& [ty, rep] : typeVarChanges) { - TypeArena* owningArena = ty->owningArena; - TypeVar* mtv = asMutable(ty); - *mtv = rep.get()->pending; - mtv->owningArena = owningArena; + if (FFlag::LuauNonCopyableTypeVarFields) + { + asMutable(ty)->reassign(rep.get()->pending); + } + else + { + TypeArena* owningArena = ty->owningArena; + TypeVar* mtv = asMutable(ty); + *mtv = rep.get()->pending; + mtv->owningArena = owningArena; + } } for (auto& [tp, rep] : typePackChanges) { - TypeArena* owningArena = tp->owningArena; - TypePackVar* mpv = asMutable(tp); - *mpv = rep.get()->pending; - mpv->owningArena = owningArena; + if (FFlag::LuauNonCopyableTypeVarFields) + { + asMutable(tp)->reassign(rep.get()->pending); + } + else + { + TypeArena* owningArena = tp->owningArena; + TypePackVar* mpv = asMutable(tp); + *mpv = rep.get()->pending; + mpv->owningArena = owningArena; + } } clear(); @@ -178,8 +194,13 @@ PendingType* TxnLog::queue(TypeId ty) // about this type, we don't want to mutate the parent's state. auto& pending = typeVarChanges[ty]; if (!pending) + { pending = std::make_unique(*ty); + if (FFlag::LuauNonCopyableTypeVarFields) + pending->pending.owningArena = nullptr; + } + return pending.get(); } @@ -191,8 +212,13 @@ PendingTypePack* TxnLog::queue(TypePackId tp) // about this type, we don't want to mutate the parent's state. auto& pending = typePackChanges[tp]; if (!pending) + { pending = std::make_unique(*tp); + if (FFlag::LuauNonCopyableTypeVarFields) + pending->pending.owningArena = nullptr; + } + return pending.get(); } @@ -229,14 +255,24 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const PendingType* TxnLog::replace(TypeId ty, TypeVar replacement) { PendingType* newTy = queue(ty); - newTy->pending = replacement; + + if (FFlag::LuauNonCopyableTypeVarFields) + newTy->pending.reassign(replacement); + else + newTy->pending = replacement; + return newTy; } PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement) { PendingTypePack* newTp = queue(tp); - newTp->pending = replacement; + + if (FFlag::LuauNonCopyableTypeVarFields) + newTp->pending.reassign(replacement); + else + newTp->pending = replacement; + return newTp; } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 4931bc5..447cd02 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -33,21 +33,20 @@ LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) -LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix, false) +LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false) LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false) LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) -LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false) LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false); LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false); -LUAU_FASTFLAGVARIABLE(LuauTypecheckIter, false); LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false) -LUAU_FASTFLAGVARIABLE(LuauNoMethodLocations, false); LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false) +LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false) +LUAU_FASTFLAGVARIABLE(LuauNonCopyableTypeVarFields, false) namespace Luau { @@ -358,8 +357,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo unifierState.cachedUnifyError.clear(); unifierState.skipCacheForType.clear(); - if (FFlag::LuauTwoPassAliasDefinitionFix) - duplicateTypeAliases.clear(); + duplicateTypeAliases.clear(); return std::move(currentModule); } @@ -610,7 +608,7 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std { if (const auto& typealias = stat->as()) { - if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == kParseNameError) + if (typealias->name == kParseNameError) continue; auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings; @@ -619,7 +617,16 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std TypeId type = bindings[name].type; if (get(follow(type))) { - *asMutable(type) = *errorRecoveryType(anyType); + if (FFlag::LuauNonCopyableTypeVarFields) + { + TypeVar* mty = asMutable(follow(type)); + mty->reassign(*errorRecoveryType(anyType)); + } + else + { + *asMutable(type) = *errorRecoveryType(anyType); + } + reportError(TypeError{typealias->location, OccursCheckFailed{}}); } } @@ -1131,45 +1138,43 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin) iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location); } - if (FFlag::LuauTypecheckIter) + if (std::optional iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location)) { - if (std::optional iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location)) + // if __iter metamethod is present, it will be called and the results are going to be called as if they are functions + // TODO: this needs to typecheck all returned values by __iter as if they were for loop arguments + // the structure of the function makes it difficult to do this especially since we don't have actual expressions, only types + for (TypeId var : varTypes) + unify(anyType, var, forin.location); + + return check(loopScope, *forin.body); + } + + if (const TableTypeVar* iterTable = get(iterTy)) + { + // TODO: note that this doesn't cleanly handle iteration over mixed tables and tables without an indexer + // this behavior is more or less consistent with what we do for pairs(), but really both are pretty wrong and need revisiting + if (iterTable->indexer) { - // if __iter metamethod is present, it will be called and the results are going to be called as if they are functions - // TODO: this needs to typecheck all returned values by __iter as if they were for loop arguments - // the structure of the function makes it difficult to do this especially since we don't have actual expressions, only types + if (varTypes.size() > 0) + unify(iterTable->indexer->indexType, varTypes[0], forin.location); + + if (varTypes.size() > 1) + unify(iterTable->indexer->indexResultType, varTypes[1], forin.location); + + for (size_t i = 2; i < varTypes.size(); ++i) + unify(nilType, varTypes[i], forin.location); + } + else + { + TypeId varTy = errorRecoveryType(loopScope); + for (TypeId var : varTypes) - unify(anyType, var, forin.location); + unify(varTy, var, forin.location); - return check(loopScope, *forin.body); + reportError(firstValue->location, GenericError{"Cannot iterate over a table without indexer"}); } - else if (const TableTypeVar* iterTable = get(iterTy)) - { - // TODO: note that this doesn't cleanly handle iteration over mixed tables and tables without an indexer - // this behavior is more or less consistent with what we do for pairs(), but really both are pretty wrong and need revisiting - if (iterTable->indexer) - { - if (varTypes.size() > 0) - unify(iterTable->indexer->indexType, varTypes[0], forin.location); - if (varTypes.size() > 1) - unify(iterTable->indexer->indexResultType, varTypes[1], forin.location); - - for (size_t i = 2; i < varTypes.size(); ++i) - unify(nilType, varTypes[i], forin.location); - } - else - { - TypeId varTy = errorRecoveryType(loopScope); - - for (TypeId var : varTypes) - unify(varTy, var, forin.location); - - reportError(firstValue->location, GenericError{"Cannot iterate over a table without indexer"}); - } - - return check(loopScope, *forin.body); - } + return check(loopScope, *forin.body); } const FunctionTypeVar* iterFunc = get(iterTy); @@ -1334,7 +1339,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias Name name = typealias.name.value; // If the alias is missing a name, we can't do anything with it. Ignore it. - if (FFlag::LuauTwoPassAliasDefinitionFix && name == kParseNameError) + if (name == kParseNameError) return; std::optional binding; @@ -1353,8 +1358,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}}); bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)}; - if (FFlag::LuauTwoPassAliasDefinitionFix) - duplicateTypeAliases.insert({typealias.exported, name}); + duplicateTypeAliases.insert({typealias.exported, name}); } else { @@ -1378,7 +1382,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias { // If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be // interesting. - if (FFlag::LuauTwoPassAliasDefinitionFix && duplicateTypeAliases.find({typealias.exported, name})) + if (duplicateTypeAliases.find({typealias.exported, name})) return; if (!binding) @@ -1422,9 +1426,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias { // This is a shallow clone, original recursive links to self are not updated TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; - - if (!FFlag::LuauNoMethodLocations) - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.definitionModuleName = ttv->definitionModuleName; clone.name = name; @@ -1462,9 +1463,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias } TypeId& bindingType = bindingsMap[name].type; - bool ok = unify(ty, bindingType, typealias.location); - if (FFlag::LuauTwoPassAliasDefinitionFix && ok) + if (unify(ty, bindingType, typealias.location)) bindingType = ty; if (FFlag::LuauLowerBoundsCalculation) @@ -1532,7 +1532,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); - if (FFlag::LuauSelfCallAutocompleteFix) + if (FFlag::LuauSelfCallAutocompleteFix2) ftv->hasSelf = true; } } @@ -3099,8 +3099,6 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T property.type = freshTy(); property.location = indexName->indexLocation; - if (!FFlag::LuauNoMethodLocations) - ttv->methodDefinitionLocations[name] = funName.location; return property.type; } else if (funName.is()) @@ -4393,8 +4391,6 @@ TypeId Anyification::clean(TypeId ty) if (const TableTypeVar* ttv = log->getMutable(ty)) { TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, TableState::Sealed}; - if (!FFlag::LuauNoMethodLocations) - clone.methodDefinitionLocations = ttv->methodDefinitionLocations; clone.definitionModuleName = ttv->definitionModuleName; clone.name = ttv->name; clone.syntheticName = ttv->syntheticName; @@ -4705,8 +4701,11 @@ TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) if (isNil(ty)) return sense ? std::nullopt : std::optional(ty); - // at this point, anything else is kept if sense is true, or eliminated otherwise - return sense ? std::optional(ty) : std::nullopt; + // at this point, anything else is kept if sense is true, or replaced by nil + if (FFlag::LuauFalsyPredicateReturnsNilInstead) + return sense ? ty : nilType; + else + return sense ? std::optional(ty) : std::nullopt; }; } diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 3050323..82451bd 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -5,6 +5,8 @@ #include +LUAU_FASTFLAG(LuauNonCopyableTypeVarFields) + namespace Luau { @@ -36,6 +38,25 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp) return *this; } +TypePackVar& TypePackVar::operator=(const TypePackVar& rhs) +{ + if (FFlag::LuauNonCopyableTypeVarFields) + { + LUAU_ASSERT(owningArena == rhs.owningArena); + LUAU_ASSERT(!rhs.persistent); + + reassign(rhs); + } + else + { + ty = rhs.ty; + persistent = rhs.persistent; + owningArena = rhs.owningArena; + } + + return *this; +} + TypePackIterator::TypePackIterator(TypePackId typePack) : TypePackIterator(typePack, TxnLog::empty()) { diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 12cbed9..33bfe25 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(LuauSubtypingAddOptPropsToUnsealedTables) +LUAU_FASTFLAG(LuauNonCopyableTypeVarFields) namespace Luau { @@ -644,6 +645,26 @@ TypeVar& TypeVar::operator=(TypeVariant&& rhs) return *this; } +TypeVar& TypeVar::operator=(const TypeVar& rhs) +{ + if (FFlag::LuauNonCopyableTypeVarFields) + { + LUAU_ASSERT(owningArena == rhs.owningArena); + LUAU_ASSERT(!rhs.persistent); + + reassign(rhs); + } + else + { + ty = rhs.ty; + persistent = rhs.persistent; + normal = rhs.normal; + owningArena = rhs.owningArena; + } + + return *this; +} + TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initializer_list generics, std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list paramNames, std::initializer_list retTypes); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index eaf1991..95bce3e 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -12,6 +12,7 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false) +LUAU_FASTFLAGVARIABLE(LuauReturnTypeTokenConfusion, false) namespace Luau { @@ -1118,8 +1119,12 @@ AstTypePack* Parser::parseTypeList(TempVector& result, TempVector Parser::parseOptionalReturnTypeAnnotation() { - if (options.allowTypeAnnotations && lexer.current().type == ':') + if (options.allowTypeAnnotations && + (lexer.current().type == ':' || (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow))) { + if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow) + report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'"); + nextLexeme(); unsigned int oldRecursionCount = recursionCounter; @@ -1350,8 +1355,12 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack) AstArray paramTypes = copy(params); + bool returnTypeIntroducer = + FFlag::LuauReturnTypeTokenConfusion ? lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':' : false; + // Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element - if (params.size() == 1 && !varargAnnotation && monomorphic && lexer.current().type != Lexeme::SkinnyArrow) + if (params.size() == 1 && !varargAnnotation && monomorphic && + (FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow)) { if (allowPack) return {{}, allocator.alloc(begin.location, AstTypeList{paramTypes, nullptr})}; @@ -1359,7 +1368,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack) return {params[0], {}}; } - if (lexer.current().type != Lexeme::SkinnyArrow && monomorphic && allowPack) + if ((FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow) && monomorphic && allowPack) return {{}, allocator.alloc(begin.location, AstTypeList{paramTypes, varargAnnotation})}; AstArray> paramNames = copy(names); @@ -1373,8 +1382,13 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray' instead of ':'"); + lexer.next(); + } // Users occasionally write '()' as the 'unit' type when they actually want to use 'nil', here we'll try to give a more specific error - if (lexer.current().type != Lexeme::SkinnyArrow && generics.size == 0 && genericPacks.size == 0 && params.size == 0) + else if (lexer.current().type != Lexeme::SkinnyArrow && generics.size == 0 && genericPacks.size == 0 && params.size == 0) { report(Location(begin.location, lexer.previousLocation()), "Expected '->' after '()' when parsing function type; did you mean 'nil'?"); diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 3aa12d9..597b2f0 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -6,8 +6,6 @@ #include #include -LUAU_FASTFLAG(LuauCompileNestedClosureO2) - namespace Luau { @@ -390,17 +388,15 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid) int16_t BytecodeBuilder::addChildFunction(uint32_t fid) { - if (FFlag::LuauCompileNestedClosureO2) - if (int16_t* cache = protoMap.find(fid)) - return *cache; + if (int16_t* cache = protoMap.find(fid)) + return *cache; uint32_t id = uint32_t(protos.size()); if (id >= kMaxClosureCount) return -1; - if (FFlag::LuauCompileNestedClosureO2) - protoMap[fid] = int16_t(id); + protoMap[fid] = int16_t(id); protos.push_back(fid); return int16_t(id); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index eea56c6..7431cde 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -16,7 +16,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauCompileIter, false) LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false) LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25) @@ -26,8 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileNestedClosureO2, false) - namespace Luau { @@ -172,30 +169,6 @@ struct Compiler return node->as(); } - bool canInlineFunctionBody(AstStat* stat) - { - if (FFlag::LuauCompileNestedClosureO2) - return true; // TODO: remove this function - - struct CanInlineVisitor : AstVisitor - { - bool result = true; - - bool visit(AstExprFunction* node) override - { - result = false; - - // short-circuit to avoid analyzing nested closure bodies - return false; - } - }; - - CanInlineVisitor canInline; - stat->visit(&canInline); - - return canInline.result; - } - uint32_t compileFunction(AstExprFunction* func) { LUAU_TIMETRACE_SCOPE("Compiler::compileFunction", "Compiler"); @@ -268,7 +241,7 @@ struct Compiler f.upvals = upvals; // record information for inlining - if (options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && !getfenvUsed && !setfenvUsed) + if (options.optimizationLevel >= 2 && !func->vararg && !getfenvUsed && !setfenvUsed) { f.canInline = true; f.stackSize = stackSize; @@ -827,110 +800,62 @@ struct Compiler if (pid < 0) CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile"); - if (FFlag::LuauCompileNestedClosureO2) - { - captures.clear(); - captures.reserve(f->upvals.size()); - - for (AstLocal* uv : f->upvals) - { - LUAU_ASSERT(uv->functionDepth < expr->functionDepth); - - if (int reg = getLocalReg(uv); reg >= 0) - { - // note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals - Variable* ul = variables.find(uv); - bool immutable = !ul || !ul->written; - - captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)}); - } - else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown) - { - // inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register - uint8_t reg = allocReg(expr, 1); - compileExprConstant(expr, uc, reg); - - captures.push_back({LCT_VAL, reg}); - } - else - { - LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1); - - // get upvalue from parent frame - // note: this will add uv to the current upvalue list if necessary - uint8_t uid = getUpval(uv); - - captures.push_back({LCT_UPVAL, uid}); - } - } - - // Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure - // objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it - // is used) - int16_t shared = -1; - - if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed) - { - int32_t cid = bytecode.addConstantClosure(f->id); - - if (cid >= 0 && cid < 32768) - shared = int16_t(cid); - } - - if (shared >= 0) - bytecode.emitAD(LOP_DUPCLOSURE, target, shared); - else - bytecode.emitAD(LOP_NEWCLOSURE, target, pid); - - for (const Capture& c : captures) - bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0); - - return; - } - - bool shared = false; - - // Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure - // objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it - // is used) - if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed) - { - int32_t cid = bytecode.addConstantClosure(f->id); - - if (cid >= 0 && cid < 32768) - { - bytecode.emitAD(LOP_DUPCLOSURE, target, int16_t(cid)); - shared = true; - } - } - - if (!shared) - bytecode.emitAD(LOP_NEWCLOSURE, target, pid); + // we use a scratch vector to reduce allocations; this is safe since compileExprFunction is not reentrant + captures.clear(); + captures.reserve(f->upvals.size()); for (AstLocal* uv : f->upvals) { LUAU_ASSERT(uv->functionDepth < expr->functionDepth); - Variable* ul = variables.find(uv); - bool immutable = !ul || !ul->written; - - if (uv->functionDepth == expr->functionDepth - 1) + if (int reg = getLocalReg(uv); reg >= 0) { - // get local variable - int reg = getLocalReg(uv); - LUAU_ASSERT(reg >= 0); + // note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals + Variable* ul = variables.find(uv); + bool immutable = !ul || !ul->written; - bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), uint8_t(reg), 0); + captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)}); + } + else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown) + { + // inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register + uint8_t reg = allocReg(expr, 1); + compileExprConstant(expr, uc, reg); + + captures.push_back({LCT_VAL, reg}); } else { + LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1); + // get upvalue from parent frame // note: this will add uv to the current upvalue list if necessary uint8_t uid = getUpval(uv); - bytecode.emitABC(LOP_CAPTURE, LCT_UPVAL, uid, 0); + captures.push_back({LCT_UPVAL, uid}); } } + + // Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure + // objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it + // is used) + int16_t shared = -1; + + if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed) + { + int32_t cid = bytecode.addConstantClosure(f->id); + + if (cid >= 0 && cid < 32768) + shared = int16_t(cid); + } + + if (shared >= 0) + bytecode.emitAD(LOP_DUPCLOSURE, target, shared); + else + bytecode.emitAD(LOP_NEWCLOSURE, target, pid); + + for (const Capture& c : captures) + bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0); } LuauOpcode getUnaryOp(AstExprUnary::Op op) @@ -2511,30 +2436,6 @@ struct Compiler pushLocal(stat->vars.data[i], uint8_t(vars + i)); } - bool canUnrollForBody(AstStatFor* stat) - { - if (FFlag::LuauCompileNestedClosureO2) - return true; // TODO: remove this function - - struct CanUnrollVisitor : AstVisitor - { - bool result = true; - - bool visit(AstExprFunction* node) override - { - result = false; - - // short-circuit to avoid analyzing nested closure bodies - return false; - } - }; - - CanUnrollVisitor canUnroll; - stat->body->visit(&canUnroll); - - return canUnroll.result; - } - bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost) { Constant one = {Constant::Type_Number}; @@ -2560,12 +2461,6 @@ struct Compiler return false; } - if (!canUnrollForBody(stat)) - { - bytecode.addDebugRemark("loop unroll failed: unsupported loop body"); - return false; - } - if (Variable* lv = variables.find(stat->var); lv && lv->written) { bytecode.addDebugRemark("loop unroll failed: mutable loop variable"); @@ -2730,12 +2625,12 @@ struct Compiler uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u)); LUAU_ASSERT(vars == regs + 3); - // Optimization: when we iterate through pairs/ipairs, we generate special bytecode that optimizes the traversal using internal iteration - // index These instructions dynamically check if generator is equal to next/inext and bail out They assume that the generator produces 2 - // variables, which is why we allocate at least 2 above (see vars assignment) - LuauOpcode skipOp = FFlag::LuauCompileIter ? LOP_FORGPREP : LOP_JUMP; + LuauOpcode skipOp = LOP_FORGPREP; LuauOpcode loopOp = LOP_FORGLOOP; + // Optimization: when we iterate via pairs/ipairs, we generate special bytecode that optimizes the traversal using internal iteration index + // These instructions dynamically check if generator is equal to next/inext and bail out + // They assume that the generator produces 2 variables, which is why we allocate at least 2 above (see vars assignment) if (options.optimizationLevel >= 1 && stat->vars.size <= 2) { if (stat->values.size == 1 && stat->values.data[0]->is()) diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index f86371d..3c3b7bd 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -14,6 +14,26 @@ #include +/* + * This file contains most implementations of core Lua APIs from lua.h. + * + * These implementations should use api_check macros to verify that stack and type contracts hold; it's the callers + * responsibility to, for example, pass a valid table index to lua_rawgetfield. Generally errors should only be raised + * for conditions caller can't predict such as an out-of-memory error. + * + * The caller is expected to handle stack reservation (by using less than LUA_MINSTACK slots or by calling lua_checkstack). + * To ensure this is handled correctly, use api_incr_top(L) when pushing values to the stack. + * + * Functions that push any collectable objects to the stack *should* call luaC_checkthreadsleep. Failure to do this can result + * in stack references that point to dead objects since sleeping threads don't get rescanned. + * + * Functions that push newly created objects to the stack *should* call luaC_checkGC in addition to luaC_checkthreadsleep. + * Failure to do this can result in OOM since GC may never run. + * + * Note that luaC_checkGC may scan the thread and put it back to sleep; functions that call both before pushing objects must + * therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread. + */ + const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; @@ -221,15 +241,13 @@ void lua_insert(lua_State* L, int idx) void lua_replace(lua_State* L, int idx) { - /* explicit test for incompatible code */ - if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci) - luaG_runerror(L, "no calling environment"); api_checknelems(L, 1); luaC_checkthreadsleep(L); StkId o = index2addr(L, idx); api_checkvalidindex(L, o); if (idx == LUA_ENVIRONINDEX) { + api_check(L, L->ci != L->base_ci); Closure* func = curr_func(L); api_check(L, ttistable(L->top - 1)); func->env = hvalue(L->top - 1); @@ -443,9 +461,7 @@ const float* lua_tovector(lua_State* L, int idx) { StkId o = index2addr(L, idx); if (!ttisvector(o)) - { return NULL; - } return vvalue(o); } @@ -460,11 +476,6 @@ int lua_objlen(lua_State* L, int idx) return uvalue(o)->len; case LUA_TTABLE: return luaH_getn(hvalue(o)); - case LUA_TNUMBER: - { - int l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0); - return l; - } default: return 0; } @@ -752,10 +763,9 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled) int lua_getmetatable(lua_State* L, int objindex) { - const TValue* obj; + luaC_checkthreadsleep(L); Table* mt = NULL; - int res; - obj = index2addr(L, objindex); + const TValue* obj = index2addr(L, objindex); switch (ttype(obj)) { case LUA_TTABLE: @@ -768,21 +778,18 @@ int lua_getmetatable(lua_State* L, int objindex) mt = L->global->mt[ttype(obj)]; break; } - if (mt == NULL) - res = 0; - else + if (mt) { sethvalue(L, L->top, mt); api_incr_top(L); - res = 1; } - return res; + return mt != NULL; } void lua_getfenv(lua_State* L, int idx) { - StkId o; - o = index2addr(L, idx); + luaC_checkthreadsleep(L); + StkId o = index2addr(L, idx); api_checkvalidindex(L, o); switch (ttype(o)) { @@ -806,9 +813,8 @@ void lua_getfenv(lua_State* L, int idx) void lua_settable(lua_State* L, int idx) { - StkId t; api_checknelems(L, 2); - t = index2addr(L, idx); + StkId t = index2addr(L, idx); api_checkvalidindex(L, t); luaV_settable(L, t, L->top - 2, L->top - 1); L->top -= 2; /* pop index and value */ @@ -817,22 +823,20 @@ void lua_settable(lua_State* L, int idx) void lua_setfield(lua_State* L, int idx, const char* k) { - StkId t; - TValue key; api_checknelems(L, 1); - t = index2addr(L, idx); + StkId t = index2addr(L, idx); api_checkvalidindex(L, t); + TValue key; setsvalue(L, &key, luaS_new(L, k)); luaV_settable(L, t, &key, L->top - 1); - L->top--; /* pop value */ + L->top--; return; } void lua_rawset(lua_State* L, int idx) { - StkId t; api_checknelems(L, 2); - t = index2addr(L, idx); + StkId t = index2addr(L, idx); api_check(L, ttistable(t)); if (hvalue(t)->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); @@ -844,9 +848,8 @@ void lua_rawset(lua_State* L, int idx) void lua_rawseti(lua_State* L, int idx, int n) { - StkId o; api_checknelems(L, 1); - o = index2addr(L, idx); + StkId o = index2addr(L, idx); api_check(L, ttistable(o)); if (hvalue(o)->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); @@ -858,14 +861,11 @@ void lua_rawseti(lua_State* L, int idx, int n) int lua_setmetatable(lua_State* L, int objindex) { - TValue* obj; - Table* mt; api_checknelems(L, 1); - obj = index2addr(L, objindex); + TValue* obj = index2addr(L, objindex); api_checkvalidindex(L, obj); - if (ttisnil(L->top - 1)) - mt = NULL; - else + Table* mt = NULL; + if (!ttisnil(L->top - 1)) { api_check(L, ttistable(L->top - 1)); mt = hvalue(L->top - 1); @@ -900,10 +900,9 @@ int lua_setmetatable(lua_State* L, int objindex) int lua_setfenv(lua_State* L, int idx) { - StkId o; int res = 1; api_checknelems(L, 1); - o = index2addr(L, idx); + StkId o = index2addr(L, idx); api_checkvalidindex(L, o); api_check(L, ttistable(L->top - 1)); switch (ttype(o)) @@ -970,24 +969,21 @@ static void f_call(lua_State* L, void* ud) int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) { - struct CallS c; - int status; - ptrdiff_t func; api_checknelems(L, nargs + 1); api_check(L, L->status == 0); checkresults(L, nargs, nresults); - if (errfunc == 0) - func = 0; - else + ptrdiff_t func = 0; + if (errfunc != 0) { StkId o = index2addr(L, errfunc); api_checkvalidindex(L, o); func = savestack(L, o); } + struct CallS c; c.func = L->top - (nargs + 1); /* function to be called */ c.nresults = nresults; - status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + int status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); adjustresults(L, nresults); return status; @@ -1247,12 +1243,10 @@ const char* lua_getupvalue(lua_State* L, int funcindex, int n) const char* lua_setupvalue(lua_State* L, int funcindex, int n) { - const char* name; - TValue* val; - StkId fi; - fi = index2addr(L, funcindex); api_checknelems(L, 1); - name = aux_upvalue(fi, n, &val); + StkId fi = index2addr(L, funcindex); + TValue* val; + const char* name = aux_upvalue(fi, n, &val); if (name) { L->top--; @@ -1319,14 +1313,16 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*)) void lua_clonefunction(lua_State* L, int idx) { + luaC_checkGC(L); + luaC_checkthreadsleep(L); 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); + Closure* newcl = luaF_newLclosure(L, cl->nupvalues, L->gt, cl->l.p); + for (int i = 0; i < cl->nupvalues; ++i) + setobj2n(L, &newcl->l.uprefs[i], &cl->l.uprefs[i]); + setclvalue(L, L->top, newcl); + api_incr_top(L); } lua_Callbacks* lua_callbacks(lua_State* L) diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index a71fce5..0642cb6 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -202,22 +202,29 @@ void luaD_growstack(lua_State* L, int n) CallInfo* luaD_growCI(lua_State* L) { - if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */ - luaD_throw(L, LUA_ERRERR); - else - { - luaD_reallocCI(L, 2 * L->size_ci); - if (L->size_ci > LUAI_MAXCALLS) - luaG_runerror(L, "stack overflow"); - } + /* allow extra stack space to handle stack overflow in xpcall */ + const int hardlimit = LUAI_MAXCALLS + (LUAI_MAXCALLS >> 3); + + if (L->size_ci >= hardlimit) + luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ + + int request = L->size_ci * 2; + luaD_reallocCI(L, L->size_ci >= LUAI_MAXCALLS ? hardlimit : request < LUAI_MAXCALLS ? request : LUAI_MAXCALLS); + + if (L->size_ci > LUAI_MAXCALLS) + luaG_runerror(L, "stack overflow"); + return ++L->ci; } void luaD_checkCstack(lua_State* L) { + /* allow extra stack space to handle stack overflow in xpcall */ + const int hardlimit = LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3); + if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); - else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3))) + else if (L->nCcalls >= hardlimit) luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ } diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index f9fd657..e0a9647 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,8 +16,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauIter, false) - // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ #if __has_warning("-Wc99-designator") @@ -2214,7 +2212,7 @@ static void luau_execute(lua_State* L) { /* will be called during FORGLOOP */ } - else if (FFlag::LuauIter) + else { Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); @@ -2259,17 +2257,6 @@ static void luau_execute(lua_State* L) StkId ra = VM_REG(LUAU_INSN_A(insn)); uint32_t aux = *pc; - if (!FFlag::LuauIter) - { - bool stop; - VM_PROTECT(stop = luau_loopFORG(L, LUAU_INSN_A(insn), aux)); - - // note that we need to increment pc by 1 to exit the loop since we need to skip over aux - pc += stop ? 1 : LUAU_INSN_D(insn); - LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); - VM_NEXT(); - } - // fast-path: builtin table iteration if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2)) { @@ -2362,7 +2349,7 @@ static void luau_execute(lua_State* L) /* ra+1 is already the table */ setpvalue(ra + 2, reinterpret_cast(uintptr_t(0))); } - else if (FFlag::LuauIter && !ttisfunction(ra)) + else if (!ttisfunction(ra)) { VM_PROTECT(luaG_typeerror(L, ra, "iterate over")); } @@ -2434,7 +2421,7 @@ static void luau_execute(lua_State* L) /* ra+1 is already the table */ setpvalue(ra + 2, reinterpret_cast(uintptr_t(0))); } - else if (FFlag::LuauIter && !ttisfunction(ra)) + else if (!ttisfunction(ra)) { VM_PROTECT(luaG_typeerror(L, ra, "iterate over")); } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index cc5b31c..dea1ab1 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -2764,8 +2764,6 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") { - ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; - check(R"( type tag = "cat" | "dog" local function f(a: tag) end @@ -2838,8 +2836,6 @@ f(@1) TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape") { - ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; - check(R"( type tag = "strange\t\"cat\"" | 'nice\t"dog"' local function f(x: tag) end @@ -2873,7 +2869,7 @@ local abc = b@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_on_class") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; loadDefinition(R"( declare class Foo @@ -2913,7 +2909,7 @@ t.@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( local t = {} @@ -2929,7 +2925,7 @@ t:@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_2") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( local f: (() -> number) & ((number) -> number) = function(x: number?) return 2 end @@ -2961,7 +2957,7 @@ t:@1 TEST_CASE_FIXTURE(ACFixture, "string_prim_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( local s = "hello" @@ -2980,7 +2976,7 @@ s:@1 TEST_CASE_FIXTURE(ACFixture, "string_prim_non_self_calls_are_avoided") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( local s = "hello" @@ -2989,17 +2985,15 @@ s.@1 auto ac = autocomplete('1'); - REQUIRE(ac.entryMap.count("byte")); - CHECK(ac.entryMap["byte"].wrongIndexType == true); REQUIRE(ac.entryMap.count("char")); CHECK(ac.entryMap["char"].wrongIndexType == false); REQUIRE(ac.entryMap.count("sub")); CHECK(ac.entryMap["sub"].wrongIndexType == true); } -TEST_CASE_FIXTURE(ACBuiltinsFixture, "string_library_non_self_calls_are_fine") +TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_non_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( string.@1 @@ -3013,11 +3007,24 @@ string.@1 CHECK(ac.entryMap["char"].wrongIndexType == false); REQUIRE(ac.entryMap.count("sub")); CHECK(ac.entryMap["sub"].wrongIndexType == false); + + check(R"( +table.@1 + )"); + + ac = autocomplete('1'); + + REQUIRE(ac.entryMap.count("remove")); + CHECK(ac.entryMap["remove"].wrongIndexType == false); + REQUIRE(ac.entryMap.count("getn")); + CHECK(ac.entryMap["getn"].wrongIndexType == false); + REQUIRE(ac.entryMap.count("insert")); + CHECK(ac.entryMap["insert"].wrongIndexType == false); } -TEST_CASE_FIXTURE(ACBuiltinsFixture, "string_library_self_calls_are_invalid") +TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_self_calls_are_invalid") { - ScopedFastFlag selfCallAutocompleteFix{"LuauSelfCallAutocompleteFix", true}; + ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; check(R"( string:@1 diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 2013965..6eee254 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -261,7 +261,6 @@ L1: RETURN R0 0 TEST_CASE("ForBytecode") { - ScopedFastFlag sff("LuauCompileIter", true); ScopedFastFlag sff2("LuauCompileIterNoPairs", false); // basic for loop: variable directly refers to internal iteration index (R2) @@ -350,8 +349,6 @@ RETURN R0 0 TEST_CASE("ForBytecodeBuiltin") { - ScopedFastFlag sff("LuauCompileIter", true); - // we generally recognize builtins like pairs/ipairs and emit special opcodes CHECK_EQ("\n" + compileFunction0("for k,v in ipairs({}) do end"), R"( GETIMPORT R0 1 @@ -2323,8 +2320,6 @@ return result TEST_CASE("DebugLineInfoFor") { - ScopedFastFlag sff("LuauCompileIter", true); - Luau::BytecodeBuilder bcb; bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines); Luau::compileOrThrow(bcb, R"( @@ -4355,8 +4350,6 @@ L1: RETURN R0 0 TEST_CASE("LoopUnrollControlFlow") { - ScopedFastFlag sff("LuauCompileNestedClosureO2", true); - ScopedFastInt sfis[] = { {"LuauCompileLoopUnrollThreshold", 50}, {"LuauCompileLoopUnrollThresholdMaxBoost", 300}, @@ -4475,8 +4468,6 @@ RETURN R0 0 TEST_CASE("LoopUnrollNestedClosure") { - ScopedFastFlag sff("LuauCompileNestedClosureO2", true); - // if the body has functions that refer to loop variables, we unroll the loop and use MOVE+CAPTURE for upvalues CHECK_EQ("\n" + compileFunction(R"( for i=1,2 do @@ -4756,8 +4747,6 @@ RETURN R1 1 TEST_CASE("InlineBasicProhibited") { - ScopedFastFlag sff("LuauCompileNestedClosureO2", true); - // we can't inline variadic functions CHECK_EQ("\n" + compileFunction(R"( local function foo(...) @@ -4833,8 +4822,6 @@ RETURN R1 1 TEST_CASE("InlineNestedClosures") { - ScopedFastFlag sff("LuauCompileNestedClosureO2", true); - // we can inline functions that contain/return functions CHECK_EQ("\n" + compileFunction(R"( local function foo(x) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f7f2b4a..96a2775 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -741,7 +741,7 @@ TEST_CASE("ApiTables") lua_pop(L, 1); } -TEST_CASE("ApiFunctionCalls") +TEST_CASE("ApiCalls") { StateRef globalState = runConformance("apicalls.lua"); lua_State* L = globalState.get(); @@ -790,6 +790,58 @@ TEST_CASE("ApiFunctionCalls") CHECK(lua_equal(L2, -1, -2) == 1); lua_pop(L2, 2); } + + // lua_clonefunction + fenv + { + lua_getfield(L, LUA_GLOBALSINDEX, "getpi"); + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 3.1415926); + lua_pop(L, 1); + + lua_getfield(L, LUA_GLOBALSINDEX, "getpi"); + + // clone & override env + lua_clonefunction(L, -1); + lua_newtable(L); + lua_pushnumber(L, 42); + lua_setfield(L, -2, "pi"); + lua_setfenv(L, -2); + + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 42); + lua_pop(L, 1); + + // this one calls original function again + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 3.1415926); + lua_pop(L, 1); + } + + // lua_clonefunction + upvalues + { + lua_getfield(L, LUA_GLOBALSINDEX, "incuv"); + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 1); + lua_pop(L, 1); + + lua_getfield(L, LUA_GLOBALSINDEX, "incuv"); + // two clones + lua_clonefunction(L, -1); + lua_clonefunction(L, -2); + + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 2); + lua_pop(L, 1); + + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 3); + lua_pop(L, 1); + + // this one calls original function again + lua_call(L, 0, 1); + CHECK(lua_tonumber(L, -1) == 4); + lua_pop(L, 1); + } } static bool endsWith(const std::string& str, const std::string& suffix) @@ -1113,11 +1165,6 @@ TEST_CASE("UserdataApi") TEST_CASE("Iter") { - ScopedFastFlag sffs[] = { - {"LuauCompileIter", true}, - {"LuauIter", true}, - }; - runConformance("iter.lua"); } diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index c7e18ef..89b13ab 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -300,4 +300,24 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit") CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException); } +TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak") +{ + ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true}; + + fileResolver.source["Module/A"] = R"( +export type A = B +type B = A + )"; + + FrontendOptions opts; + opts.retainFullTypeGraphs = false; + CheckResult result = frontend.check("Module/A", opts); + LUAU_REQUIRE_ERRORS(result); + + auto mod = frontend.moduleResolver.getModule("Module/A"); + auto it = mod->getModuleScope()->exportedTypeBindings.find("A"); + REQUIRE(it != mod->getModuleScope()->exportedTypeBindings.end()); + CHECK(toString(it->second.type) == "any"); +} + TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 87b1263..878023e 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2622,6 +2622,23 @@ type Z = { a: string | T..., b: number } REQUIRE_EQ(3, result.errors.size()); } +TEST_CASE_FIXTURE(Fixture, "recover_function_return_type_annotations") +{ + ScopedFastFlag sff{"LuauReturnTypeTokenConfusion", true}; + ParseResult result = tryParse(R"( +type Custom = { x: A, y: B, z: C } +type Packed = { x: (A...) -> () } +type F = (number): Custom +type G = Packed<(number): (string, number, boolean)> +local function f(x: number) -> Custom +end + )"); + REQUIRE_EQ(3, result.errors.size()); + CHECK_EQ(result.errors[0].getMessage(), "Return types in function type annotations are written after '->' instead of ':'"); + CHECK_EQ(result.errors[1].getMessage(), "Return types in function type annotations are written after '->' instead of ':'"); + CHECK_EQ(result.errors[2].getMessage(), "Function return type annotations are written after ':' instead of '->'"); +} + TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation") { ScopedFastFlag sff{"LuauParserFunctionKeywordAsTypeHelp", true}; diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 7562a4d..86cc970 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -615,8 +615,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_typevars_are_not_considered_to_escape_their_ */ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any") { - ScopedFastFlag sff[] = {{"LuauTwoPassAliasDefinitionFix", true}}; - CheckResult result = check(R"( local function x() local y: FutureType = {}::any @@ -633,10 +631,6 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2") { - ScopedFastFlag sff[] = { - {"LuauTwoPassAliasDefinitionFix", true}, - }; - CheckResult result = check(R"( local B = {} B.bar = 4 diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 4444cd6..1c6fe1d 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -486,8 +486,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow") TEST_CASE_FIXTURE(Fixture, "loop_iter_basic") { - ScopedFastFlag sff{"LuauTypecheckIter", true}; - CheckResult result = check(R"( local t: {string} = {} local key @@ -506,8 +504,6 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic") TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") { - ScopedFastFlag sff{"LuauTypecheckIter", true}; - CheckResult result = check(R"( local t: {string} = {} local extra @@ -522,8 +518,6 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer") { - ScopedFastFlag sff{"LuauTypecheckIter", true}; - CheckResult result = check(R"( local t = {} for k, v in t do @@ -539,8 +533,6 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_iter_metamethod") { - ScopedFastFlag sff{"LuauTypecheckIter", true}; - CheckResult result = check(R"( local t = {} setmetatable(t, { __iter = function(o) return next, o.children end }) diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 6785f27..207b3cf 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -932,6 +932,8 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { + ScopedFastFlag sff{"LuauFalsyPredicateReturnsNilInstead", true}; + CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -947,7 +949,7 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); + CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); } TEST_CASE_FIXTURE(Fixture, "discriminate_tag") @@ -1191,7 +1193,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part") TEST_CASE_FIXTURE(Fixture, "typeguard_doesnt_leak_to_elseif") { - const std::string code = R"( + CheckResult result = check(R"( function f(a) if type(a) == "boolean" then local a1 = a @@ -1201,10 +1203,30 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_doesnt_leak_to_elseif") local a3 = a end end - )"; - CheckResult result = check(code); + )"); + LUAU_REQUIRE_NO_ERRORS(result); - dumpErrors(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "falsiness_of_TruthyPredicate_narrows_into_nil") +{ + ScopedFastFlag sff{"LuauFalsyPredicateReturnsNilInstead", true}; + + CheckResult result = check(R"( + local function f(t: {number}) + local x = t[1] + if not x then + local foo = x + else + local bar = x + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28}))); + CHECK_EQ("number", toString(requireTypeAtPosition({6, 28}))); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 14a5a6a..a90f434 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -139,8 +139,6 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch") { - ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true}; - CheckResult result = check(R"( type MyEnum = "foo" | "bar" | "baz" local a : MyEnum = "bang" diff --git a/tests/TypePack.test.cpp b/tests/TypePack.test.cpp index c493157..8a5a65f 100644 --- a/tests/TypePack.test.cpp +++ b/tests/TypePack.test.cpp @@ -197,4 +197,20 @@ TEST_CASE_FIXTURE(TypePackFixture, "std_distance") CHECK_EQ(4, std::distance(b, e)); } +TEST_CASE("content_reassignment") +{ + ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true}; + + TypePackVar myError{Unifiable::Error{}, /*presistent*/ true}; + + TypeArena arena; + + TypePackId futureError = arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}); + asMutable(futureError)->reassign(myError); + + CHECK(get(futureError) != nullptr); + CHECK(!futureError->persistent); + CHECK(futureError->owningArena == &arena); +} + TEST_SUITE_END(); diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index bb2d94b..4f8fc50 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -416,4 +416,24 @@ TEST_CASE("proof_that_isBoolean_uses_all_of") CHECK(!isBoolean(&union_)); } +TEST_CASE("content_reassignment") +{ + ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true}; + + TypeVar myAny{AnyTypeVar{}, /*presistent*/ true}; + myAny.normal = true; + myAny.documentationSymbol = "@global/any"; + + TypeArena arena; + + TypeId futureAny = arena.addType(FreeTypeVar{TypeLevel{}}); + asMutable(futureAny)->reassign(myAny); + + CHECK(get(futureAny) != nullptr); + CHECK(!futureAny->persistent); + CHECK(futureAny->normal); + CHECK(futureAny->documentationSymbol == "@global/any"); + CHECK(futureAny->owningArena == &arena); +} + TEST_SUITE_END(); diff --git a/tests/conformance/apicalls.lua b/tests/conformance/apicalls.lua index 7a4058b..2741662 100644 --- a/tests/conformance/apicalls.lua +++ b/tests/conformance/apicalls.lua @@ -11,4 +11,15 @@ function create_with_tm(x) return setmetatable({ a = x }, m) end +local gen = 0 +function incuv() + gen += 1 + return gen +end + +pi = 3.1415926 +function getpi() + return pi +end + return('OK') diff --git a/tests/conformance/pcall.lua b/tests/conformance/pcall.lua index 84ac2ba..969209f 100644 --- a/tests/conformance/pcall.lua +++ b/tests/conformance/pcall.lua @@ -144,4 +144,21 @@ coroutine.resume(co) resumeerror(co, "fail") checkresults({ true, false, "fail" }, coroutine.resume(co)) +-- stack overflow needs to happen at the call limit +local calllimit = 20000 +function recurse(n) return n <= 1 and 1 or recurse(n-1) + 1 end + +-- we use one frame for top-level function and one frame is the service frame for coroutines +assert(recurse(calllimit - 2) == calllimit - 2) + +-- note that when calling through pcall, pcall eats one more frame +checkresults({ true, calllimit - 3 }, pcall(recurse, calllimit - 3)) +checkerror(pcall(recurse, calllimit - 2)) + +-- xpcall handler runs in context of the stack frame, but this works just fine since we allow extra stack consumption past stack overflow +checkresults({ false, "ok" }, xpcall(recurse, function() return string.reverse("ko") end, calllimit - 2)) + +-- however, if xpcall handler itself runs out of extra stack space, we get "error in error handling" +checkresults({ false, "error in error handling" }, xpcall(recurse, function() return recurse(calllimit) end, calllimit - 2)) + return 'OK'