* Fix free Luau type being fully overwritten by 'any' and causing UAF
* Fix lua_clonefunction implementation replacing top instead of pushing
* Falsey values other than false can now narrow refinements
* Fix lua_getmetatable, lua_getfenv not waking thread up
* FIx a case where lua_objlen could push a new string without thread wakeup or GC
* Moved Luau math and bit32 definitions to definition file 
* Improve Luau parse recovery of incorrect return type token
This commit is contained in:
rblanckaert 2022-06-10 09:58:21 -07:00 committed by GitHub
parent ca5fbbfc24
commit b066e4c8f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 536 additions and 469 deletions

View file

@ -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;
};

View file

@ -334,7 +334,6 @@ struct TableTypeVar
// We need to know which is which when we stringify types.
std::optional<std::string> syntheticName;
std::map<Name, Location> methodDefinitionLocations; // TODO: Remove with FFlag::LuauNoMethodLocations
std::vector<TypeId> instantiatedTypeParams;
std::vector<TypePackId> 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<std::pair<const void*, const void*>>;

View file

@ -14,8 +14,7 @@
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteClassSecurityLevel, false);
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix)
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2)
static const std::unordered_set<std::string> 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<TypeId> 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<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> 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<ClassTypeVar>(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<TypeId> 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<TypeId> 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<TableTypeVar>(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<TableTypeVar>(mt->metatable))
fillMetatableProps(mtable);
@ -537,7 +540,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryMap inner;
std::unordered_set<TypeId> 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<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix)
else if (auto pt = get<PrimitiveTypeVar>(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<StringSingleton>(get<SingletonTypeVar>(ty)))
else if (FFlag::LuauSelfCallAutocompleteFix2 && get<StringSingleton>(get<SingletonTypeVar>(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

View file

@ -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<TableTypeVar>(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<TableTypeVar>(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<K, V>(t: Table<K, V>, i: K | nil) -> (K, V)
// next<K, V>(t: Table<K, V>, 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<K, V>(t: Table<K, V>) -> ((Table<K, V>) -> (K, V), Table<K, V>, nil)
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});

View file

@ -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;

View file

@ -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,

View file

@ -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<TableTypeVar>(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));
}

View file

@ -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<typename T>
bool operator()(TypeId ty, const T& t)
{
return true;
}
template<typename T>
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)
{

View file

@ -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;
}

View file

@ -10,7 +10,6 @@
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauNoMethodLocations)
namespace Luau
{

View file

@ -7,6 +7,8 @@
#include <algorithm>
#include <stdexcept>
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<PendingType>(*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<PendingTypePack>(*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;
}

View file

@ -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<AstStatTypeAlias>())
{
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<FreeTypeVar>(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<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location))
{
if (std::optional<TypeId> 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<TableTypeVar>(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<TableTypeVar>(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<FunctionTypeVar>(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<TypeFun> 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<AstExprError>())
@ -4393,8 +4391,6 @@ TypeId Anyification::clean(TypeId ty)
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(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<TypeId>(ty);
// at this point, anything else is kept if sense is true, or eliminated otherwise
return sense ? std::optional<TypeId>(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<TypeId>(ty) : std::nullopt;
};
}

View file

@ -5,6 +5,8 @@
#include <stdexcept>
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())
{

View file

@ -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<TypeId> selfType, std::initializer_list<TypeId> generics,
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
std::initializer_list<TypeId> retTypes);

View file

@ -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<AstType*>& result, TempVector<std:
std::optional<AstTypeList> 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<AstType*> 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<AstTypePackExplicit>(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<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation})};
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
@ -1373,8 +1382,13 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<A
{
incrementRecursionCounter("type annotation");
if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == ':')
{
report(lexer.current().location, "Return types in function type annotations are written after '->' 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'?");

View file

@ -6,8 +6,6 @@
#include <algorithm>
#include <string.h>
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);

View file

@ -16,7 +16,6 @@
#include <bitset>
#include <math.h>
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<AstExprFunction>();
}
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<AstExprCall>())

View file

@ -14,6 +14,26 @@
#include <string.h>
/*
* 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)

View file

@ -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 */
}

View file

@ -16,8 +16,6 @@
#include <string.h>
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<void*>(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<void*>(uintptr_t(0)));
}
else if (FFlag::LuauIter && !ttisfunction(ra))
else if (!ttisfunction(ra))
{
VM_PROTECT(luaG_typeerror(L, ra, "iterate over"));
}

View file

@ -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

View file

@ -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)

View file

@ -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");
}

View file

@ -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();

View file

@ -2622,6 +2622,23 @@ type Z<T> = { 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<A, B, C> = { x: A, y: B, z: C }
type Packed<A...> = { x: (A...) -> () }
type F = (number): Custom<boolean, number, string>
type G = Packed<(number): (string, number, boolean)>
local function f(x: number) -> Custom<string, boolean, number>
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};

View file

@ -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

View file

@ -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 })

View file

@ -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();

View file

@ -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"

View file

@ -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<ErrorTypeVar>(futureError) != nullptr);
CHECK(!futureError->persistent);
CHECK(futureError->owningArena == &arena);
}
TEST_SUITE_END();

View file

@ -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<AnyTypeVar>(futureAny) != nullptr);
CHECK(!futureAny->persistent);
CHECK(futureAny->normal);
CHECK(futureAny->documentationSymbol == "@global/any");
CHECK(futureAny->owningArena == &arena);
}
TEST_SUITE_END();

View file

@ -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')

View file

@ -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'