mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 21:10:37 +00:00
0.531 (#532)
* 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:
parent
ca5fbbfc24
commit
b066e4c8f8
33 changed files with 536 additions and 469 deletions
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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*>>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'?");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>())
|
||||
|
|
106
VM/src/lapi.cpp
106
VM/src/lapi.cpp
|
@ -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)
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue