mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/516
This commit is contained in:
parent
4930409516
commit
a8eabedd57
@ -13,8 +13,6 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauPrepopulateUnionOptionsBeforeAllocation)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -60,11 +58,8 @@ struct TypeArena
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
TypeId addType(T tv)
|
TypeId addType(T tv)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauPrepopulateUnionOptionsBeforeAllocation)
|
if constexpr (std::is_same_v<T, UnionTypeVar>)
|
||||||
{
|
LUAU_ASSERT(tv.options.size() >= 2);
|
||||||
if constexpr (std::is_same_v<T, UnionTypeVar>)
|
|
||||||
LUAU_ASSERT(tv.options.size() >= 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return addTV(TypeVar(std::move(tv)));
|
return addTV(TypeVar(std::move(tv)));
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ bool doesCallError(const AstExprCall* call);
|
|||||||
bool hasBreak(AstStat* node);
|
bool hasBreak(AstStat* node);
|
||||||
const AstStat* getFallthrough(const AstStat* node);
|
const AstStat* getFallthrough(const AstStat* node);
|
||||||
|
|
||||||
|
struct UnifierOptions;
|
||||||
struct Unifier;
|
struct Unifier;
|
||||||
|
|
||||||
// A substitution which replaces generic types in a given set by free types.
|
// A substitution which replaces generic types in a given set by free types.
|
||||||
@ -245,6 +246,7 @@ struct TypeChecker
|
|||||||
* Treat any failures as type errors in the final typecheck report.
|
* Treat any failures as type errors in the final typecheck report.
|
||||||
*/
|
*/
|
||||||
bool unify(TypeId subTy, TypeId superTy, const Location& location);
|
bool unify(TypeId subTy, TypeId superTy, const Location& location);
|
||||||
|
bool unify(TypeId subTy, TypeId superTy, const Location& location, const UnifierOptions& options);
|
||||||
bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||||
|
|
||||||
/** Attempt to unify the types.
|
/** Attempt to unify the types.
|
||||||
|
@ -13,7 +13,7 @@ namespace Luau
|
|||||||
|
|
||||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||||
|
|
||||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location);
|
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location);
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location);
|
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, Name name, Location location);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -19,11 +19,31 @@ enum Variance
|
|||||||
Invariant
|
Invariant
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A substitution which replaces singleton types by their wider types
|
||||||
|
struct Widen : Substitution
|
||||||
|
{
|
||||||
|
Widen(TypeArena* arena)
|
||||||
|
: Substitution(TxnLog::empty(), arena)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty(TypeId ty) override;
|
||||||
|
bool isDirty(TypePackId ty) override;
|
||||||
|
TypeId clean(TypeId ty) override;
|
||||||
|
TypePackId clean(TypePackId ty) override;
|
||||||
|
bool ignoreChildren(TypeId ty) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Use this more widely.
|
||||||
|
struct UnifierOptions
|
||||||
|
{
|
||||||
|
bool isFunctionCall = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct Unifier
|
struct Unifier
|
||||||
{
|
{
|
||||||
TypeArena* const types;
|
TypeArena* const types;
|
||||||
Mode mode;
|
Mode mode;
|
||||||
ScopePtr globalScope; // sigh. Needed solely to get at string's metatable.
|
|
||||||
|
|
||||||
DEPRECATED_TxnLog DEPRECATED_log;
|
DEPRECATED_TxnLog DEPRECATED_log;
|
||||||
TxnLog log;
|
TxnLog log;
|
||||||
@ -34,9 +54,9 @@ struct Unifier
|
|||||||
|
|
||||||
UnifierSharedState& sharedState;
|
UnifierSharedState& sharedState;
|
||||||
|
|
||||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||||
TxnLog* parentLog = nullptr);
|
TxnLog* parentLog = nullptr);
|
||||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
||||||
|
|
||||||
// Test whether the two type vars unify. Never commits the result.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
@ -65,7 +85,10 @@ private:
|
|||||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||||
void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||||
|
|
||||||
|
TypeId widen(TypeId ty);
|
||||||
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
|
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
|
||||||
|
|
||||||
void cacheResult(TypeId subTy, TypeId superTy);
|
void cacheResult(TypeId subTy, TypeId superTy);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -236,10 +236,10 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
|||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
auto canUnify = [&typeArena, &module](TypeId subTy, TypeId superTy) {
|
auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) {
|
||||||
InternalErrorReporter iceReporter;
|
InternalErrorReporter iceReporter;
|
||||||
UnifierSharedState unifierState(&iceReporter);
|
UnifierSharedState unifierState(&iceReporter);
|
||||||
Unifier unifier(typeArena, Mode::Strict, module.getModuleScope(), Location(), Variance::Covariant, unifierState);
|
Unifier unifier(typeArena, Mode::Strict, Location(), Variance::Covariant, unifierState);
|
||||||
|
|
||||||
if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog)
|
||||||
{
|
{
|
||||||
|
@ -201,6 +201,7 @@ static bool similar(AstExpr* lhs, AstExpr* rhs)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
CASE(AstExprIfElse) return similar(le->condition, re->condition) && similar(le->trueExpr, re->trueExpr) && similar(le->falseExpr, re->falseExpr);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!"Unknown expression type");
|
LUAU_ASSERT(!"Unknown expression type");
|
||||||
|
@ -16,7 +16,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuau
|
|||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
||||||
LUAU_FASTFLAG(LuauImmutableTypes)
|
LUAU_FASTFLAG(LuauImmutableTypes)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -379,28 +378,14 @@ void TypeCloner::operator()(const AnyTypeVar& t)
|
|||||||
|
|
||||||
void TypeCloner::operator()(const UnionTypeVar& t)
|
void TypeCloner::operator()(const UnionTypeVar& t)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauPrepopulateUnionOptionsBeforeAllocation)
|
std::vector<TypeId> options;
|
||||||
{
|
options.reserve(t.options.size());
|
||||||
std::vector<TypeId> options;
|
|
||||||
options.reserve(t.options.size());
|
|
||||||
|
|
||||||
for (TypeId ty : t.options)
|
for (TypeId ty : t.options)
|
||||||
options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
||||||
|
|
||||||
TypeId result = dest.addType(UnionTypeVar{std::move(options)});
|
TypeId result = dest.addType(UnionTypeVar{std::move(options)});
|
||||||
seenTypes[typeId] = result;
|
seenTypes[typeId] = result;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TypeId result = dest.addType(UnionTypeVar{});
|
|
||||||
seenTypes[typeId] = result;
|
|
||||||
|
|
||||||
UnionTypeVar* option = getMutable<UnionTypeVar>(result);
|
|
||||||
LUAU_ASSERT(option != nullptr);
|
|
||||||
|
|
||||||
for (TypeId ty : t.options)
|
|
||||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeCloner::operator()(const IntersectionTypeVar& t)
|
void TypeCloner::operator()(const IntersectionTypeVar& t)
|
||||||
|
@ -1153,6 +1153,14 @@ struct Printer
|
|||||||
writer.symbol(")");
|
writer.symbol(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
|
||||||
|
{
|
||||||
|
writer.keyword(a->value ? "true" : "false");
|
||||||
|
}
|
||||||
|
else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>())
|
||||||
|
{
|
||||||
|
writer.string(std::string_view(a->value.data, a->value.size));
|
||||||
|
}
|
||||||
else if (typeAnnotation.is<AstTypeError>())
|
else if (typeAnnotation.is<AstTypeError>())
|
||||||
{
|
{
|
||||||
writer.symbol("%error-type%");
|
writer.symbol("%error-type%");
|
||||||
|
@ -29,7 +29,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
|
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
|
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
||||||
@ -38,14 +37,14 @@ LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
|
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
|
||||||
LUAU_FASTFLAG(LuauUnionTagMatchFix)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
|
||||||
|
LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -1125,7 +1124,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
|||||||
|
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (tableSelf && (FFlag::LuauNoSealedTypeMod ? tableSelf->state != TableState::Sealed : !selfTy->persistent))
|
if (tableSelf && tableSelf->state != TableState::Sealed)
|
||||||
tableSelf->props[indexName->index.value] = {ty, /* deprecated */ false, {}, indexName->indexLocation};
|
tableSelf->props[indexName->index.value] = {ty, /* deprecated */ false, {}, indexName->indexLocation};
|
||||||
|
|
||||||
const FunctionTypeVar* funTy = get<FunctionTypeVar>(ty);
|
const FunctionTypeVar* funTy = get<FunctionTypeVar>(ty);
|
||||||
@ -1138,7 +1137,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
|||||||
|
|
||||||
checkFunctionBody(funScope, ty, *function.func);
|
checkFunctionBody(funScope, ty, *function.func);
|
||||||
|
|
||||||
if (tableSelf && (FFlag::LuauNoSealedTypeMod ? tableSelf->state != TableState::Sealed : !selfTy->persistent))
|
if (tableSelf && tableSelf->state != TableState::Sealed)
|
||||||
tableSelf->props[indexName->index.value] = {
|
tableSelf->props[indexName->index.value] = {
|
||||||
follow(quantify(funScope, ty, indexName->indexLocation)), /* deprecated */ false, {}, indexName->indexLocation};
|
follow(quantify(funScope, ty, indexName->indexLocation)), /* deprecated */ false, {}, indexName->indexLocation};
|
||||||
}
|
}
|
||||||
@ -1210,8 +1209,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||||||
{
|
{
|
||||||
ScopePtr aliasScope = childScope(scope, typealias.location);
|
ScopePtr aliasScope = childScope(scope, typealias.location);
|
||||||
aliasScope->level = scope->level.incr();
|
aliasScope->level = scope->level.incr();
|
||||||
if (FFlag::LuauProperTypeLevels)
|
aliasScope->level.subLevel = subLevel;
|
||||||
aliasScope->level.subLevel = subLevel;
|
|
||||||
|
|
||||||
auto [generics, genericPacks] =
|
auto [generics, genericPacks] =
|
||||||
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
|
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
|
||||||
@ -1624,7 +1622,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIn
|
|||||||
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location)
|
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location)
|
||||||
{
|
{
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
auto result = Luau::findTablePropertyRespectingMeta(errors, globalScope, lhsType, name, location);
|
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
|
||||||
reportErrors(errors);
|
reportErrors(errors);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -1632,7 +1630,7 @@ std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsTyp
|
|||||||
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location)
|
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location)
|
||||||
{
|
{
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
auto result = Luau::findMetatableEntry(errors, globalScope, type, entry, location);
|
auto result = Luau::findMetatableEntry(errors, type, entry, location);
|
||||||
reportErrors(errors);
|
reportErrors(errors);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -1751,13 +1749,23 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(amccord): Write some logic to correctly handle intersections. CLI-34659
|
if (FFlag::LuauDoNotTryToReduce)
|
||||||
std::vector<TypeId> result = reduceUnion(parts);
|
{
|
||||||
|
if (parts.size() == 1)
|
||||||
|
return parts[0];
|
||||||
|
|
||||||
if (result.size() == 1)
|
return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
|
||||||
return result[0];
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO(amccord): Write some logic to correctly handle intersections. CLI-34659
|
||||||
|
std::vector<TypeId> result = reduceUnion(parts);
|
||||||
|
|
||||||
return addType(IntersectionTypeVar{result});
|
if (result.size() == 1)
|
||||||
|
return result[0];
|
||||||
|
|
||||||
|
return addType(IntersectionTypeVar{result});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addErrors)
|
if (addErrors)
|
||||||
@ -2823,10 +2831,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
|||||||
TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level)
|
TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level)
|
||||||
{
|
{
|
||||||
auto freshTy = [&]() {
|
auto freshTy = [&]() {
|
||||||
if (FFlag::LuauProperTypeLevels)
|
return freshType(level);
|
||||||
return freshType(level);
|
|
||||||
else
|
|
||||||
return freshType(scope);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto globalName = funName.as<AstExprGlobal>())
|
if (auto globalName = funName.as<AstExprGlobal>())
|
||||||
@ -3790,7 +3795,14 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
|||||||
// has been instantiated, so is a monotype. We can therefore
|
// has been instantiated, so is a monotype. We can therefore
|
||||||
// unify it with a monomorphic function.
|
// unify it with a monomorphic function.
|
||||||
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
|
TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack));
|
||||||
unify(fn, r, expr.location);
|
if (FFlag::LuauWidenIfSupertypeIsFree)
|
||||||
|
{
|
||||||
|
UnifierOptions options;
|
||||||
|
options.isFunctionCall = true;
|
||||||
|
unify(r, fn, expr.location, options);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
unify(fn, r, expr.location);
|
||||||
return {{retPack}};
|
return {{retPack}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4243,9 +4255,15 @@ TypeId TypeChecker::anyIfNonstrict(TypeId ty) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location)
|
bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location)
|
||||||
|
{
|
||||||
|
UnifierOptions options;
|
||||||
|
return unify(subTy, superTy, location, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location, const UnifierOptions& options)
|
||||||
{
|
{
|
||||||
Unifier state = mkUnifier(location);
|
Unifier state = mkUnifier(location);
|
||||||
state.tryUnify(subTy, superTy);
|
state.tryUnify(subTy, superTy, options.isFunctionCall);
|
||||||
|
|
||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
state.log.commit();
|
state.log.commit();
|
||||||
@ -4654,7 +4672,7 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto ttv = getTableType(FFlag::LuauUnionTagMatchFix ? utk->table : follow(utk->table)))
|
if (auto ttv = getTableType(utk->table))
|
||||||
accumulate(ttv->props);
|
accumulate(ttv->props);
|
||||||
else if (auto ctv = get<ClassTypeVar>(follow(utk->table)))
|
else if (auto ctv = get<ClassTypeVar>(follow(utk->table)))
|
||||||
{
|
{
|
||||||
@ -4691,8 +4709,7 @@ ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location&
|
|||||||
ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location)
|
ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location)
|
||||||
{
|
{
|
||||||
ScopePtr scope = std::make_shared<Scope>(parent);
|
ScopePtr scope = std::make_shared<Scope>(parent);
|
||||||
if (FFlag::LuauProperTypeLevels)
|
scope->level = parent->level;
|
||||||
scope->level = parent->level;
|
|
||||||
scope->varargPack = parent->varargPack;
|
scope->varargPack = parent->varargPack;
|
||||||
|
|
||||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||||
@ -4724,7 +4741,7 @@ void TypeChecker::merge(RefinementMap& l, const RefinementMap& r)
|
|||||||
|
|
||||||
Unifier TypeChecker::mkUnifier(const Location& location)
|
Unifier TypeChecker::mkUnifier(const Location& location)
|
||||||
{
|
{
|
||||||
return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState};
|
return Unifier{¤tModule->internalTypes, currentModule->mode, location, Variance::Covariant, unifierState};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeChecker::freshType(const ScopePtr& scope)
|
TypeId TypeChecker::freshType(const ScopePtr& scope)
|
||||||
@ -5444,7 +5461,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
|||||||
|
|
||||||
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
|
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2);
|
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2 || FFlag::LuauAssertStripsFalsyTypes);
|
||||||
|
|
||||||
const LValue* target = &lvalue;
|
const LValue* target = &lvalue;
|
||||||
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
|
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
|
||||||
|
@ -10,7 +10,7 @@ LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location)
|
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location)
|
||||||
{
|
{
|
||||||
type = follow(type);
|
type = follow(type);
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globa
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location)
|
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, Name name, Location location)
|
||||||
{
|
{
|
||||||
if (get<AnyTypeVar>(ty))
|
if (get<AnyTypeVar>(ty))
|
||||||
return ty;
|
return ty;
|
||||||
@ -49,7 +49,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const Sc
|
|||||||
return it->second.type;
|
return it->second.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location);
|
std::optional<TypeId> mtIndex = findMetatableEntry(errors, ty, "__index", location);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (mtIndex)
|
while (mtIndex)
|
||||||
{
|
{
|
||||||
@ -82,7 +82,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const Sc
|
|||||||
else
|
else
|
||||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||||
|
|
||||||
mtIndex = findMetatableEntry(errors, globalScope, *mtIndex, "__index", location);
|
mtIndex = findMetatableEntry(errors, *mtIndex, "__index", location);
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -23,9 +23,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
|||||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false)
|
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||||
LUAU_FASTFLAG(LuauUnionTagMatchFix)
|
|
||||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -145,20 +143,13 @@ bool isNil(TypeId ty)
|
|||||||
|
|
||||||
bool isBoolean(TypeId ty)
|
bool isBoolean(TypeId ty)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauRefactorTypeVarQuestions)
|
if (isPrim(ty, PrimitiveTypeVar::Boolean) || get<BooleanSingleton>(get<SingletonTypeVar>(follow(ty))))
|
||||||
{
|
return true;
|
||||||
if (isPrim(ty, PrimitiveTypeVar::Boolean) || get<BooleanSingleton>(get<SingletonTypeVar>(follow(ty))))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (auto utv = get<UnionTypeVar>(follow(ty)))
|
if (auto utv = get<UnionTypeVar>(follow(ty)))
|
||||||
return std::all_of(begin(utv), end(utv), isBoolean);
|
return std::all_of(begin(utv), end(utv), isBoolean);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return isPrim(ty, PrimitiveTypeVar::Boolean);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isNumber(TypeId ty)
|
bool isNumber(TypeId ty)
|
||||||
@ -168,20 +159,13 @@ bool isNumber(TypeId ty)
|
|||||||
|
|
||||||
bool isString(TypeId ty)
|
bool isString(TypeId ty)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauRefactorTypeVarQuestions)
|
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
|
||||||
{
|
return true;
|
||||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (auto utv = get<UnionTypeVar>(follow(ty)))
|
if (auto utv = get<UnionTypeVar>(follow(ty)))
|
||||||
return std::all_of(begin(utv), end(utv), isString);
|
return std::all_of(begin(utv), end(utv), isString);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return isPrim(ty, PrimitiveTypeVar::String);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isThread(TypeId ty)
|
bool isThread(TypeId ty)
|
||||||
@ -194,45 +178,11 @@ bool isOptional(TypeId ty)
|
|||||||
if (isNil(ty))
|
if (isNil(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (FFlag::LuauRefactorTypeVarQuestions)
|
auto utv = get<UnionTypeVar>(follow(ty));
|
||||||
{
|
if (!utv)
|
||||||
auto utv = get<UnionTypeVar>(follow(ty));
|
|
||||||
if (!utv)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return std::any_of(begin(utv), end(utv), isNil);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::unordered_set<TypeId> seen;
|
|
||||||
std::deque<TypeId> queue{ty};
|
|
||||||
while (!queue.empty())
|
|
||||||
{
|
|
||||||
TypeId current = follow(queue.front());
|
|
||||||
queue.pop_front();
|
|
||||||
|
|
||||||
if (seen.count(current))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
seen.insert(current);
|
|
||||||
|
|
||||||
if (isNil(current))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (auto u = get<UnionTypeVar>(current))
|
|
||||||
{
|
|
||||||
for (TypeId option : u->options)
|
|
||||||
{
|
|
||||||
if (isNil(option))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
queue.push_back(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
return std::any_of(begin(utv), end(utv), isNil);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isTableIntersection(TypeId ty)
|
bool isTableIntersection(TypeId ty)
|
||||||
@ -263,38 +213,24 @@ std::optional<TypeId> getMetatable(TypeId type)
|
|||||||
return mtType->metatable;
|
return mtType->metatable;
|
||||||
else if (const ClassTypeVar* classType = get<ClassTypeVar>(type))
|
else if (const ClassTypeVar* classType = get<ClassTypeVar>(type))
|
||||||
return classType->metatable;
|
return classType->metatable;
|
||||||
else if (FFlag::LuauRefactorTypeVarQuestions)
|
else if (isString(type))
|
||||||
{
|
{
|
||||||
if (isString(type))
|
auto ptv = get<PrimitiveTypeVar>(getSingletonTypes().stringType);
|
||||||
{
|
LUAU_ASSERT(ptv && ptv->metatable);
|
||||||
auto ptv = get<PrimitiveTypeVar>(getSingletonTypes().stringType);
|
return ptv->metatable;
|
||||||
LUAU_ASSERT(ptv && ptv->metatable);
|
|
||||||
return ptv->metatable;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type); primitiveType && primitiveType->metatable)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(primitiveType->type == PrimitiveTypeVar::String);
|
|
||||||
return primitiveType->metatable;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableTypeVar* getTableType(TypeId type)
|
const TableTypeVar* getTableType(TypeId type)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauUnionTagMatchFix)
|
type = follow(type);
|
||||||
type = follow(type);
|
|
||||||
|
|
||||||
if (const TableTypeVar* ttv = get<TableTypeVar>(type))
|
if (const TableTypeVar* ttv = get<TableTypeVar>(type))
|
||||||
return ttv;
|
return ttv;
|
||||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(type))
|
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(type))
|
||||||
return get<TableTypeVar>(FFlag::LuauUnionTagMatchFix ? follow(mtv->table) : mtv->table);
|
return get<TableTypeVar>(follow(mtv->table));
|
||||||
else
|
else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -311,7 +247,7 @@ const std::string* getName(TypeId type)
|
|||||||
{
|
{
|
||||||
if (mtv->syntheticName)
|
if (mtv->syntheticName)
|
||||||
return &*mtv->syntheticName;
|
return &*mtv->syntheticName;
|
||||||
type = FFlag::LuauUnionTagMatchFix ? follow(mtv->table) : mtv->table;
|
type = follow(mtv->table);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto ttv = get<TableTypeVar>(type))
|
if (auto ttv = get<TableTypeVar>(type))
|
||||||
|
@ -21,9 +21,8 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
||||||
LUAU_FASTFLAG(LuauSingletonTypes)
|
LUAU_FASTFLAG(LuauSingletonTypes)
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||||
LUAU_FASTFLAG(LuauProperTypeLevels);
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
|
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -122,7 +121,7 @@ struct PromoteTypeLevels
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
|
static void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
|
||||||
{
|
{
|
||||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||||
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
|
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
|
||||||
@ -133,6 +132,7 @@ void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const Typ
|
|||||||
visitTypeVarOnce(ty, ptl, seen);
|
visitTypeVarOnce(ty, ptl, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: use this and make it static.
|
||||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
|
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
|
||||||
{
|
{
|
||||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||||
@ -247,6 +247,48 @@ struct SkipCacheForType
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool Widen::isDirty(TypeId ty)
|
||||||
|
{
|
||||||
|
return FFlag::LuauUseCommittingTxnLog ? log->is<SingletonTypeVar>(ty) : bool(get<SingletonTypeVar>(ty));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Widen::isDirty(TypePackId)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId Widen::clean(TypeId ty)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(isDirty(ty));
|
||||||
|
auto stv = FFlag::LuauUseCommittingTxnLog ? log->getMutable<SingletonTypeVar>(ty) : getMutable<SingletonTypeVar>(ty);
|
||||||
|
LUAU_ASSERT(stv);
|
||||||
|
|
||||||
|
if (get<StringSingleton>(stv))
|
||||||
|
return getSingletonTypes().stringType;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If this assert trips, it's likely we now have number singletons.
|
||||||
|
LUAU_ASSERT(get<BooleanSingleton>(stv));
|
||||||
|
return getSingletonTypes().booleanType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId Widen::clean(TypePackId)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Widen attempted to clean a dirty type pack?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Widen::ignoreChildren(TypeId ty)
|
||||||
|
{
|
||||||
|
// Sometimes we unify ("hi") -> free1 with (free2) -> free3, so don't ignore functions.
|
||||||
|
// TODO: should we be doing this? we would need to rework how checkCallOverload does the unification.
|
||||||
|
if (FFlag::LuauUseCommittingTxnLog ? log->is<FunctionTypeVar>(ty) : bool(get<FunctionTypeVar>(ty)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We only care about unions.
|
||||||
|
return !(FFlag::LuauUseCommittingTxnLog ? log->is<UnionTypeVar>(ty) : bool(get<UnionTypeVar>(ty)));
|
||||||
|
}
|
||||||
|
|
||||||
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
||||||
{
|
{
|
||||||
auto isUnificationTooComplex = [](const TypeError& te) {
|
auto isUnificationTooComplex = [](const TypeError& te) {
|
||||||
@ -263,43 +305,22 @@ static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
|||||||
// Used for tagged union matching heuristic, returns first singleton type field
|
// Used for tagged union matching heuristic, returns first singleton type field
|
||||||
static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type)
|
static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauUnionTagMatchFix)
|
if (auto ttv = getTableType(type))
|
||||||
{
|
{
|
||||||
if (auto ttv = getTableType(type))
|
for (auto&& [name, prop] : ttv->props)
|
||||||
{
|
{
|
||||||
for (auto&& [name, prop] : ttv->props)
|
if (auto sing = get<SingletonTypeVar>(follow(prop.type)))
|
||||||
{
|
return {{name, sing}};
|
||||||
if (auto sing = get<SingletonTypeVar>(follow(prop.type)))
|
|
||||||
return {{name, sing}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
type = follow(type);
|
|
||||||
|
|
||||||
if (auto ttv = get<TableTypeVar>(type))
|
|
||||||
{
|
|
||||||
for (auto&& [name, prop] : ttv->props)
|
|
||||||
{
|
|
||||||
if (auto sing = get<SingletonTypeVar>(follow(prop.type)))
|
|
||||||
return {{name, sing}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (auto mttv = get<MetatableTypeVar>(type))
|
|
||||||
{
|
|
||||||
return getTableMatchTag(mttv->table);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||||
TxnLog* parentLog)
|
TxnLog* parentLog)
|
||||||
: types(types)
|
: types(types)
|
||||||
, mode(mode)
|
, mode(mode)
|
||||||
, globalScope(std::move(globalScope))
|
|
||||||
, log(parentLog)
|
, log(parentLog)
|
||||||
, location(location)
|
, location(location)
|
||||||
, variance(variance)
|
, variance(variance)
|
||||||
@ -308,11 +329,10 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati
|
|||||||
LUAU_ASSERT(sharedState.iceHandler);
|
LUAU_ASSERT(sharedState.iceHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
||||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
||||||
: types(types)
|
: types(types)
|
||||||
, mode(mode)
|
, mode(mode)
|
||||||
, globalScope(std::move(globalScope))
|
|
||||||
, DEPRECATED_log(sharedSeen)
|
, DEPRECATED_log(sharedSeen)
|
||||||
, log(parentLog, sharedSeen)
|
, log(parentLog, sharedSeen)
|
||||||
, location(location)
|
, location(location)
|
||||||
@ -435,6 +455,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
}
|
}
|
||||||
else if (superFree)
|
else if (superFree)
|
||||||
{
|
{
|
||||||
|
TypeLevel superLevel = superFree->level;
|
||||||
|
|
||||||
occursCheck(superTy, subTy);
|
occursCheck(superTy, subTy);
|
||||||
bool occursFailed = false;
|
bool occursFailed = false;
|
||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
@ -442,8 +464,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
else
|
else
|
||||||
occursFailed = bool(get<ErrorTypeVar>(superTy));
|
occursFailed = bool(get<ErrorTypeVar>(superTy));
|
||||||
|
|
||||||
TypeLevel superLevel = superFree->level;
|
|
||||||
|
|
||||||
// Unification can't change the level of a generic.
|
// Unification can't change the level of a generic.
|
||||||
auto subGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable<GenericTypeVar>(subTy) : get<GenericTypeVar>(subTy);
|
auto subGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable<GenericTypeVar>(subTy) : get<GenericTypeVar>(subTy);
|
||||||
if (subGeneric && !subGeneric->level.subsumes(superLevel))
|
if (subGeneric && !subGeneric->level.subsumes(superLevel))
|
||||||
@ -459,20 +479,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
{
|
{
|
||||||
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
||||||
log.replace(superTy, BoundTypeVar(subTy));
|
log.replace(superTy, BoundTypeVar(widen(subTy)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FFlag::LuauProperTypeLevels)
|
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
||||||
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
|
||||||
else if (auto subLevel = getMutableLevel(subTy))
|
|
||||||
{
|
|
||||||
if (!subLevel->subsumes(superFree->level))
|
|
||||||
*subLevel = superFree->level;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEPRECATED_log(superTy);
|
DEPRECATED_log(superTy);
|
||||||
*asMutable(superTy) = BoundTypeVar(subTy);
|
*asMutable(superTy) = BoundTypeVar(widen(subTy));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,16 +521,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FFlag::LuauProperTypeLevels)
|
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
|
||||||
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
|
|
||||||
else if (auto superLevel = getMutableLevel(superTy))
|
|
||||||
{
|
|
||||||
if (!superLevel->subsumes(subFree->level))
|
|
||||||
{
|
|
||||||
DEPRECATED_log(superTy);
|
|
||||||
*superLevel = subFree->level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEPRECATED_log(subTy);
|
DEPRECATED_log(subTy);
|
||||||
*asMutable(subTy) = BoundTypeVar(superTy);
|
*asMutable(subTy) = BoundTypeVar(superTy);
|
||||||
@ -2064,6 +2069,17 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId Unifier::widen(TypeId ty)
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauWidenIfSupertypeIsFree)
|
||||||
|
return ty;
|
||||||
|
|
||||||
|
Widen widen{types};
|
||||||
|
std::optional<TypeId> result = widen.substitute(ty);
|
||||||
|
// TODO: what does it mean for substitution to fail to widen?
|
||||||
|
return result.value_or(ty);
|
||||||
|
}
|
||||||
|
|
||||||
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
|
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
@ -2915,7 +2931,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
|||||||
|
|
||||||
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
||||||
{
|
{
|
||||||
return Luau::findTablePropertyRespectingMeta(errors, globalScope, lhsType, name, location);
|
return Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unifier::occursCheck(TypeId needle, TypeId haystack)
|
void Unifier::occursCheck(TypeId needle, TypeId haystack)
|
||||||
@ -3096,9 +3112,9 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
|||||||
Unifier Unifier::makeChildUnifier()
|
Unifier Unifier::makeChildUnifier()
|
||||||
{
|
{
|
||||||
if (FFlag::LuauUseCommittingTxnLog)
|
if (FFlag::LuauUseCommittingTxnLog)
|
||||||
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, &log};
|
return Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
|
||||||
else
|
else
|
||||||
return Unifier{types, mode, globalScope, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log};
|
return Unifier{types, mode, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Unifier::isNonstrictMode() const
|
bool Unifier::isNonstrictMode() const
|
||||||
|
@ -12,7 +12,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
|||||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseRecoverTypePackEllipsis, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
|
LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
|
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
|
||||||
|
|
||||||
@ -2372,11 +2371,11 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||||||
{
|
{
|
||||||
Location nameLocation = lexer.current().location;
|
Location nameLocation = lexer.current().location;
|
||||||
AstName name = parseName().name;
|
AstName name = parseName().name;
|
||||||
if (lexer.current().type == Lexeme::Dot3 || (FFlag::LuauParseRecoverTypePackEllipsis && seenPack))
|
if (lexer.current().type == Lexeme::Dot3 || seenPack)
|
||||||
{
|
{
|
||||||
seenPack = true;
|
seenPack = true;
|
||||||
|
|
||||||
if (FFlag::LuauParseRecoverTypePackEllipsis && lexer.current().type != Lexeme::Dot3)
|
if (lexer.current().type != Lexeme::Dot3)
|
||||||
report(lexer.current().location, "Generic types come before generic type packs");
|
report(lexer.current().location, "Generic types come before generic type packs");
|
||||||
else
|
else
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
@ -2414,9 +2413,6 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauParseRecoverTypePackEllipsis && seenPack)
|
|
||||||
report(lexer.current().location, "Generic types come before generic type packs");
|
|
||||||
|
|
||||||
if (withDefaultValues && lexer.current().type == '=')
|
if (withDefaultValues && lexer.current().type == '=')
|
||||||
{
|
{
|
||||||
seenDefault = true;
|
seenDefault = true;
|
||||||
|
@ -44,7 +44,7 @@ typedef int (*lua_Continuation)(lua_State* L, int status);
|
|||||||
** prototype for memory-allocation functions
|
** prototype for memory-allocation functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef void* (*lua_Alloc)(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize);
|
typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize);
|
||||||
|
|
||||||
/* non-return type */
|
/* non-return type */
|
||||||
#define l_noret void LUA_NORETURN
|
#define l_noret void LUA_NORETURN
|
||||||
@ -178,11 +178,11 @@ LUA_API int lua_pushthread(lua_State* L);
|
|||||||
/*
|
/*
|
||||||
** get functions (Lua -> stack)
|
** get functions (Lua -> stack)
|
||||||
*/
|
*/
|
||||||
LUA_API void lua_gettable(lua_State* L, int idx);
|
LUA_API int lua_gettable(lua_State* L, int idx);
|
||||||
LUA_API void lua_getfield(lua_State* L, int idx, const char* k);
|
LUA_API int lua_getfield(lua_State* L, int idx, const char* k);
|
||||||
LUA_API void lua_rawgetfield(lua_State* L, int idx, const char* k);
|
LUA_API int lua_rawgetfield(lua_State* L, int idx, const char* k);
|
||||||
LUA_API void lua_rawget(lua_State* L, int idx);
|
LUA_API int lua_rawget(lua_State* L, int idx);
|
||||||
LUA_API void lua_rawgeti(lua_State* L, int idx, int n);
|
LUA_API int lua_rawgeti(lua_State* L, int idx, int n);
|
||||||
LUA_API void lua_createtable(lua_State* L, int narr, int nrec);
|
LUA_API void lua_createtable(lua_State* L, int narr, int nrec);
|
||||||
|
|
||||||
LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
|
LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);
|
||||||
|
@ -659,16 +659,16 @@ int lua_pushthread(lua_State* L)
|
|||||||
** get functions (Lua -> stack)
|
** get functions (Lua -> stack)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void lua_gettable(lua_State* L, int idx)
|
int lua_gettable(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_checkthreadsleep(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, t);
|
api_checkvalidindex(L, t);
|
||||||
luaV_gettable(L, t, L->top - 1, L->top - 1);
|
luaV_gettable(L, t, L->top - 1, L->top - 1);
|
||||||
return;
|
return ttype(L->top - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_getfield(lua_State* L, int idx, const char* k)
|
int lua_getfield(lua_State* L, int idx, const char* k)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_checkthreadsleep(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
@ -677,10 +677,10 @@ void lua_getfield(lua_State* L, int idx, const char* k)
|
|||||||
setsvalue(L, &key, luaS_new(L, k));
|
setsvalue(L, &key, luaS_new(L, k));
|
||||||
luaV_gettable(L, t, &key, L->top);
|
luaV_gettable(L, t, &key, L->top);
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return ttype(L->top - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_rawgetfield(lua_State* L, int idx, const char* k)
|
int lua_rawgetfield(lua_State* L, int idx, const char* k)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_checkthreadsleep(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
@ -689,26 +689,26 @@ void lua_rawgetfield(lua_State* L, int idx, const char* k)
|
|||||||
setsvalue(L, &key, luaS_new(L, k));
|
setsvalue(L, &key, luaS_new(L, k));
|
||||||
setobj2s(L, L->top, luaH_getstr(hvalue(t), tsvalue(&key)));
|
setobj2s(L, L->top, luaH_getstr(hvalue(t), tsvalue(&key)));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return ttype(L->top - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_rawget(lua_State* L, int idx)
|
int lua_rawget(lua_State* L, int idx)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_checkthreadsleep(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
|
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
|
||||||
return;
|
return ttype(L->top - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_rawgeti(lua_State* L, int idx, int n)
|
int lua_rawgeti(lua_State* L, int idx, int n)
|
||||||
{
|
{
|
||||||
luaC_checkthreadsleep(L);
|
luaC_checkthreadsleep(L);
|
||||||
StkId t = index2addr(L, idx);
|
StkId t = index2addr(L, idx);
|
||||||
api_check(L, ttistable(t));
|
api_check(L, ttistable(t));
|
||||||
setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
|
setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return ttype(L->top - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_createtable(lua_State* L, int narray, int nrec)
|
void lua_createtable(lua_State* L, int narray, int nrec)
|
||||||
|
@ -151,8 +151,7 @@ l_noret luaD_throw(lua_State* L, int errcode)
|
|||||||
static void correctstack(lua_State* L, TValue* oldstack)
|
static void correctstack(lua_State* L, TValue* oldstack)
|
||||||
{
|
{
|
||||||
L->top = (L->top - oldstack) + L->stack;
|
L->top = (L->top - oldstack) + L->stack;
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
for (UpVal* up = L->openupval; up != NULL; up = up->u.l.threadnext)
|
||||||
for (UpVal* up = L->openupval; up != NULL; up = (UpVal*)up->next)
|
|
||||||
up->v = (up->v - oldstack) + L->stack;
|
up->v = (up->v - oldstack) + L->stack;
|
||||||
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
|
||||||
{
|
{
|
||||||
|
@ -6,13 +6,10 @@
|
|||||||
#include "lmem.h"
|
#include "lmem.h"
|
||||||
#include "lgc.h"
|
#include "lgc.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNoDirectUpvalRemoval, false)
|
|
||||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
|
||||||
|
|
||||||
Proto* luaF_newproto(lua_State* L)
|
Proto* luaF_newproto(lua_State* L)
|
||||||
{
|
{
|
||||||
Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat);
|
Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat);
|
||||||
luaC_link(L, f, LUA_TPROTO);
|
luaC_init(L, f, LUA_TPROTO);
|
||||||
f->k = NULL;
|
f->k = NULL;
|
||||||
f->sizek = 0;
|
f->sizek = 0;
|
||||||
f->p = NULL;
|
f->p = NULL;
|
||||||
@ -40,7 +37,7 @@ Proto* luaF_newproto(lua_State* L)
|
|||||||
Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p)
|
Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p)
|
||||||
{
|
{
|
||||||
Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat);
|
Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat);
|
||||||
luaC_link(L, c, LUA_TFUNCTION);
|
luaC_init(L, c, LUA_TFUNCTION);
|
||||||
c->isC = 0;
|
c->isC = 0;
|
||||||
c->env = e;
|
c->env = e;
|
||||||
c->nupvalues = cast_byte(nelems);
|
c->nupvalues = cast_byte(nelems);
|
||||||
@ -55,7 +52,7 @@ Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p)
|
|||||||
Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e)
|
Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e)
|
||||||
{
|
{
|
||||||
Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat);
|
Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat);
|
||||||
luaC_link(L, c, LUA_TFUNCTION);
|
luaC_init(L, c, LUA_TFUNCTION);
|
||||||
c->isC = 1;
|
c->isC = 1;
|
||||||
c->env = e;
|
c->env = e;
|
||||||
c->nupvalues = cast_byte(nelems);
|
c->nupvalues = cast_byte(nelems);
|
||||||
@ -82,8 +79,7 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
pp = &p->u.l.threadnext;
|
||||||
pp = (UpVal**)&p->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); /* not found: create a new one */
|
UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); /* not found: create a new one */
|
||||||
@ -94,19 +90,10 @@ UpVal* luaF_findupval(lua_State* L, StkId level)
|
|||||||
|
|
||||||
// chain the upvalue in the threads open upvalue list at the proper position
|
// chain the upvalue in the threads open upvalue list at the proper position
|
||||||
UpVal* next = *pp;
|
UpVal* next = *pp;
|
||||||
|
uv->u.l.threadnext = next;
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
uv->u.l.threadprev = pp;
|
||||||
uv->next = (GCObject*)next;
|
if (next)
|
||||||
|
next->u.l.threadprev = &uv->u.l.threadnext;
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
uv->u.l.threadprev = pp;
|
|
||||||
if (next)
|
|
||||||
{
|
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
|
||||||
next->u.l.threadprev = (UpVal**)&uv->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*pp = uv;
|
*pp = uv;
|
||||||
|
|
||||||
@ -125,15 +112,11 @@ void luaF_unlinkupval(UpVal* uv)
|
|||||||
uv->u.l.next->u.l.prev = uv->u.l.prev;
|
uv->u.l.next->u.l.prev = uv->u.l.prev;
|
||||||
uv->u.l.prev->u.l.next = uv->u.l.next;
|
uv->u.l.prev->u.l.next = uv->u.l.next;
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
// unlink upvalue from the thread open upvalue list
|
||||||
{
|
*uv->u.l.threadprev = uv->u.l.threadnext;
|
||||||
// unlink upvalue from the thread open upvalue list
|
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and this and the following cast will not be required
|
|
||||||
*uv->u.l.threadprev = (UpVal*)uv->next;
|
|
||||||
|
|
||||||
if (UpVal* next = (UpVal*)uv->next)
|
if (UpVal* next = uv->u.l.threadnext)
|
||||||
next->u.l.threadprev = uv->u.l.threadprev;
|
next->u.l.threadprev = uv->u.l.threadprev;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
||||||
@ -145,34 +128,27 @@ void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page)
|
|||||||
|
|
||||||
void luaF_close(lua_State* L, StkId level)
|
void luaF_close(lua_State* L, StkId level)
|
||||||
{
|
{
|
||||||
global_State* g = L->global; // TODO: remove with FFlagLuauNoDirectUpvalRemoval
|
global_State* g = L->global;
|
||||||
UpVal* uv;
|
UpVal* uv;
|
||||||
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
|
while (L->openupval != NULL && (uv = L->openupval)->v >= level)
|
||||||
{
|
{
|
||||||
GCObject* o = obj2gco(uv);
|
GCObject* o = obj2gco(uv);
|
||||||
LUAU_ASSERT(!isblack(o) && uv->v != &uv->u.value);
|
LUAU_ASSERT(!isblack(o) && uv->v != &uv->u.value);
|
||||||
|
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
|
||||||
L->openupval = (UpVal*)uv->next; /* remove from `open' list */
|
luaF_unlinkupval(uv);
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep && isdead(g, o))
|
if (isdead(g, o))
|
||||||
{
|
{
|
||||||
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
|
|
||||||
luaF_unlinkupval(uv);
|
|
||||||
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
|
// close the upvalue without copying the dead data so that luaF_freeupval will not unlink again
|
||||||
uv->v = &uv->u.value;
|
uv->v = &uv->u.value;
|
||||||
}
|
}
|
||||||
else if (!FFlag::LuauNoDirectUpvalRemoval && isdead(g, o))
|
|
||||||
{
|
|
||||||
luaF_freeupval(L, uv, NULL); /* free upvalue */
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue
|
|
||||||
luaF_unlinkupval(uv);
|
|
||||||
setobj(L, &uv->u.value, uv->v);
|
setobj(L, &uv->u.value, uv->v);
|
||||||
uv->v = &uv->u.value; /* now current value lives here */
|
uv->v = &uv->u.value;
|
||||||
luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */
|
// GC state of a new closed upvalue has to be initialized
|
||||||
|
luaC_initupval(L, uv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
218
VM/src/lgc.cpp
218
VM/src/lgc.cpp
@ -13,8 +13,6 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauGcPagedSweep, false)
|
|
||||||
|
|
||||||
#define GC_SWEEPMAX 40
|
#define GC_SWEEPMAX 40
|
||||||
#define GC_SWEEPCOST 10
|
#define GC_SWEEPCOST 10
|
||||||
#define GC_SWEEPPAGESTEPCOST 4
|
#define GC_SWEEPPAGESTEPCOST 4
|
||||||
@ -64,7 +62,6 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds,
|
|||||||
case GCSatomic:
|
case GCSatomic:
|
||||||
g->gcstats.currcycle.atomictime += seconds;
|
g->gcstats.currcycle.atomictime += seconds;
|
||||||
break;
|
break;
|
||||||
case GCSsweepstring:
|
|
||||||
case GCSsweep:
|
case GCSsweep:
|
||||||
g->gcstats.currcycle.sweeptime += seconds;
|
g->gcstats.currcycle.sweeptime += seconds;
|
||||||
break;
|
break;
|
||||||
@ -490,65 +487,6 @@ static void freeobj(lua_State* L, GCObject* o, lua_Page* page)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define sweepwholelist(L, p) sweeplist(L, p, SIZE_MAX)
|
|
||||||
|
|
||||||
static GCObject** sweeplist(lua_State* L, GCObject** p, size_t count)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
GCObject* curr;
|
|
||||||
global_State* g = L->global;
|
|
||||||
int deadmask = otherwhite(g);
|
|
||||||
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); /* make sure we never sweep fixed objects */
|
|
||||||
while ((curr = *p) != NULL && count-- > 0)
|
|
||||||
{
|
|
||||||
int alive = (curr->gch.marked ^ WHITEBITS) & deadmask;
|
|
||||||
if (curr->gch.tt == LUA_TTHREAD)
|
|
||||||
{
|
|
||||||
sweepwholelist(L, (GCObject**)&gco2th(curr)->openupval); /* sweep open upvalues */
|
|
||||||
|
|
||||||
lua_State* th = gco2th(curr);
|
|
||||||
|
|
||||||
if (alive)
|
|
||||||
{
|
|
||||||
resetbit(th->stackstate, THREAD_SLEEPINGBIT);
|
|
||||||
shrinkstack(th);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (alive)
|
|
||||||
{ /* not dead? */
|
|
||||||
LUAU_ASSERT(!isdead(g, curr));
|
|
||||||
makewhite(g, curr); /* make it white (for next cycle) */
|
|
||||||
p = &curr->gch.next;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ /* must erase `curr' */
|
|
||||||
LUAU_ASSERT(isdead(g, curr));
|
|
||||||
*p = curr->gch.next;
|
|
||||||
if (curr == g->rootgc) /* is the first element of the list? */
|
|
||||||
g->rootgc = curr->gch.next; /* adjust first */
|
|
||||||
freeobj(L, curr, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void deletelist(lua_State* L, GCObject** p, GCObject* limit)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
GCObject* curr;
|
|
||||||
while ((curr = *p) != limit)
|
|
||||||
{
|
|
||||||
if (curr->gch.tt == LUA_TTHREAD) /* delete open upvalues of each thread */
|
|
||||||
deletelist(L, (GCObject**)&gco2th(curr)->openupval, NULL);
|
|
||||||
|
|
||||||
*p = curr->gch.next;
|
|
||||||
freeobj(L, curr, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void shrinkbuffers(lua_State* L)
|
static void shrinkbuffers(lua_State* L)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
@ -570,8 +508,6 @@ static void shrinkbuffersfull(lua_State* L)
|
|||||||
|
|
||||||
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
|
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
// we are in the process of deleting everything
|
// we are in the process of deleting everything
|
||||||
// threads with open upvalues will attempt to close them all on removal
|
// threads with open upvalues will attempt to close them all on removal
|
||||||
// but those upvalues might point to stack values that were already deleted
|
// but those upvalues might point to stack values that were already deleted
|
||||||
@ -598,32 +534,13 @@ void luaC_freeall(lua_State* L)
|
|||||||
|
|
||||||
LUAU_ASSERT(L == g->mainthread);
|
LUAU_ASSERT(L == g->mainthread);
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
luaM_visitgco(L, L, deletegco);
|
||||||
{
|
|
||||||
luaM_visitgco(L, L, deletegco);
|
|
||||||
|
|
||||||
for (int i = 0; i < g->strt.size; i++) /* free all string lists */
|
for (int i = 0; i < g->strt.size; i++) /* free all string lists */
|
||||||
LUAU_ASSERT(g->strt.hash[i] == NULL);
|
LUAU_ASSERT(g->strt.hash[i] == NULL);
|
||||||
|
|
||||||
LUAU_ASSERT(L->global->strt.nuse == 0);
|
LUAU_ASSERT(L->global->strt.nuse == 0);
|
||||||
LUAU_ASSERT(g->strbufgc == NULL);
|
LUAU_ASSERT(g->strbufgc == NULL);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(L->next == NULL); /* mainthread is at the end of rootgc list */
|
|
||||||
|
|
||||||
deletelist(L, &g->rootgc, obj2gco(L));
|
|
||||||
|
|
||||||
for (int i = 0; i < g->strt.size; i++) /* free all string lists */
|
|
||||||
deletelist(L, (GCObject**)&g->strt.hash[i], NULL);
|
|
||||||
|
|
||||||
LUAU_ASSERT(L->global->strt.nuse == 0);
|
|
||||||
deletelist(L, (GCObject**)&g->strbufgc, NULL);
|
|
||||||
|
|
||||||
// unfortunately, when string objects are freed, the string table use count is decremented
|
|
||||||
// even when the string is a buffer that wasn't placed into the table
|
|
||||||
L->global->strt.nuse = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void markmt(global_State* g)
|
static void markmt(global_State* g)
|
||||||
@ -687,26 +604,13 @@ static size_t atomic(lua_State* L)
|
|||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
/* flip current white */
|
/* flip current white */
|
||||||
g->currentwhite = cast_byte(otherwhite(g));
|
g->currentwhite = cast_byte(otherwhite(g));
|
||||||
g->sweepstrgc = 0;
|
g->sweepgcopage = g->allgcopages;
|
||||||
|
g->gcstate = GCSsweep;
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
g->sweepgcopage = g->allgcopages;
|
|
||||||
g->gcstate = GCSsweep;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g->sweepgc = &g->rootgc;
|
|
||||||
g->gcstate = GCSsweepstring;
|
|
||||||
}
|
|
||||||
|
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
int deadmask = otherwhite(g);
|
int deadmask = otherwhite(g);
|
||||||
@ -740,8 +644,6 @@ static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
|||||||
// a version of generic luaM_visitpage specialized for the main sweep stage
|
// a version of generic luaM_visitpage specialized for the main sweep stage
|
||||||
static int sweepgcopage(lua_State* L, lua_Page* page)
|
static int sweepgcopage(lua_State* L, lua_Page* page)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
char* start;
|
char* start;
|
||||||
char* end;
|
char* end;
|
||||||
int busyBlocks;
|
int busyBlocks;
|
||||||
@ -819,75 +721,29 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||||||
|
|
||||||
cost = atomic(L); /* finish mark phase */
|
cost = atomic(L); /* finish mark phase */
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
LUAU_ASSERT(g->gcstate == GCSsweep);
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweep);
|
|
||||||
else
|
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweepstring);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GCSsweepstring:
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
while (g->sweepstrgc < g->strt.size && cost < limit)
|
|
||||||
{
|
|
||||||
sweepwholelist(L, (GCObject**)&g->strt.hash[g->sweepstrgc++]);
|
|
||||||
|
|
||||||
cost += GC_SWEEPCOST;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing more to sweep?
|
|
||||||
if (g->sweepstrgc >= g->strt.size)
|
|
||||||
{
|
|
||||||
// sweep string buffer list and preserve used string count
|
|
||||||
uint32_t nuse = L->global->strt.nuse;
|
|
||||||
|
|
||||||
sweepwholelist(L, (GCObject**)&g->strbufgc);
|
|
||||||
|
|
||||||
L->global->strt.nuse = nuse;
|
|
||||||
|
|
||||||
g->gcstate = GCSsweep; // end sweep-string phase
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GCSsweep:
|
case GCSsweep:
|
||||||
{
|
{
|
||||||
if (FFlag::LuauGcPagedSweep)
|
while (g->sweepgcopage && cost < limit)
|
||||||
{
|
{
|
||||||
while (g->sweepgcopage && cost < limit)
|
lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page
|
||||||
{
|
|
||||||
lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page
|
|
||||||
|
|
||||||
int steps = sweepgcopage(L, g->sweepgcopage);
|
int steps = sweepgcopage(L, g->sweepgcopage);
|
||||||
|
|
||||||
g->sweepgcopage = next;
|
g->sweepgcopage = next;
|
||||||
cost += steps * GC_SWEEPPAGESTEPCOST;
|
cost += steps * GC_SWEEPPAGESTEPCOST;
|
||||||
}
|
|
||||||
|
|
||||||
// nothing more to sweep?
|
|
||||||
if (g->sweepgcopage == NULL)
|
|
||||||
{
|
|
||||||
// don't forget to visit main thread
|
|
||||||
sweepgco(L, NULL, obj2gco(g->mainthread));
|
|
||||||
|
|
||||||
shrinkbuffers(L);
|
|
||||||
g->gcstate = GCSpause; /* end collection */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// nothing more to sweep?
|
||||||
|
if (g->sweepgcopage == NULL)
|
||||||
{
|
{
|
||||||
while (*g->sweepgc && cost < limit)
|
// don't forget to visit main thread
|
||||||
{
|
sweepgco(L, NULL, obj2gco(g->mainthread));
|
||||||
g->sweepgc = sweeplist(L, g->sweepgc, GC_SWEEPMAX);
|
|
||||||
|
|
||||||
cost += GC_SWEEPMAX * GC_SWEEPCOST;
|
shrinkbuffers(L);
|
||||||
}
|
g->gcstate = GCSpause; /* end collection */
|
||||||
|
|
||||||
if (*g->sweepgc == NULL)
|
|
||||||
{ /* nothing more to sweep? */
|
|
||||||
shrinkbuffers(L);
|
|
||||||
g->gcstate = GCSpause; /* end collection */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1013,26 +869,18 @@ void luaC_fullgc(lua_State* L)
|
|||||||
if (g->gcstate <= GCSatomic)
|
if (g->gcstate <= GCSatomic)
|
||||||
{
|
{
|
||||||
/* reset sweep marks to sweep all elements (returning them to white) */
|
/* reset sweep marks to sweep all elements (returning them to white) */
|
||||||
g->sweepstrgc = 0;
|
g->sweepgcopage = g->allgcopages;
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
g->sweepgcopage = g->allgcopages;
|
|
||||||
else
|
|
||||||
g->sweepgc = &g->rootgc;
|
|
||||||
/* reset other collector lists */
|
/* reset other collector lists */
|
||||||
g->gray = NULL;
|
g->gray = NULL;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
|
g->gcstate = GCSsweep;
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
g->gcstate = GCSsweep;
|
|
||||||
else
|
|
||||||
g->gcstate = GCSsweepstring;
|
|
||||||
}
|
}
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
|
LUAU_ASSERT(g->gcstate == GCSsweep);
|
||||||
/* finish any pending sweep phase */
|
/* finish any pending sweep phase */
|
||||||
while (g->gcstate != GCSpause)
|
while (g->gcstate != GCSpause)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
|
LUAU_ASSERT(g->gcstate == GCSsweep);
|
||||||
gcstep(L, SIZE_MAX);
|
gcstep(L, SIZE_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,30 +968,19 @@ void luaC_barrierback(lua_State* L, Table* t)
|
|||||||
g->grayagain = o;
|
g->grayagain = o;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_linkobj(lua_State* L, GCObject* o, uint8_t tt)
|
void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
o->gch.next = g->rootgc;
|
|
||||||
g->rootgc = o;
|
|
||||||
}
|
|
||||||
o->gch.marked = luaC_white(g);
|
o->gch.marked = luaC_white(g);
|
||||||
o->gch.tt = tt;
|
o->gch.tt = tt;
|
||||||
o->gch.memcat = L->activememcat;
|
o->gch.memcat = L->activememcat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_linkupval(lua_State* L, UpVal* uv)
|
void luaC_initupval(lua_State* L, UpVal* uv)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
GCObject* o = obj2gco(uv);
|
GCObject* o = obj2gco(uv);
|
||||||
|
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */
|
|
||||||
g->rootgc = o;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isgray(o))
|
if (isgray(o))
|
||||||
{
|
{
|
||||||
if (keepinvariant(g))
|
if (keepinvariant(g))
|
||||||
@ -1221,9 +1058,6 @@ const char* luaC_statename(int state)
|
|||||||
case GCSatomic:
|
case GCSatomic:
|
||||||
return "atomic";
|
return "atomic";
|
||||||
|
|
||||||
case GCSsweepstring:
|
|
||||||
return "sweepstring";
|
|
||||||
|
|
||||||
case GCSsweep:
|
case GCSsweep:
|
||||||
return "sweep";
|
return "sweep";
|
||||||
|
|
||||||
|
10
VM/src/lgc.h
10
VM/src/lgc.h
@ -13,9 +13,7 @@
|
|||||||
#define GCSpropagate 1
|
#define GCSpropagate 1
|
||||||
#define GCSpropagateagain 2
|
#define GCSpropagateagain 2
|
||||||
#define GCSatomic 3
|
#define GCSatomic 3
|
||||||
// TODO: remove with FFlagLuauGcPagedSweep
|
#define GCSsweep 4
|
||||||
#define GCSsweepstring 4
|
|
||||||
#define GCSsweep 5
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** macro to tell when main invariant (white objects cannot point to black
|
** macro to tell when main invariant (white objects cannot point to black
|
||||||
@ -132,13 +130,13 @@
|
|||||||
luaC_wakethread(L); \
|
luaC_wakethread(L); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define luaC_link(L, o, tt) luaC_linkobj(L, cast_to(GCObject*, (o)), tt)
|
#define luaC_init(L, o, tt) luaC_initobj(L, cast_to(GCObject*, (o)), tt)
|
||||||
|
|
||||||
LUAI_FUNC void luaC_freeall(lua_State* L);
|
LUAI_FUNC void luaC_freeall(lua_State* L);
|
||||||
LUAI_FUNC void luaC_step(lua_State* L, bool assist);
|
LUAI_FUNC void luaC_step(lua_State* L, bool assist);
|
||||||
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
LUAI_FUNC void luaC_fullgc(lua_State* L);
|
||||||
LUAI_FUNC void luaC_linkobj(lua_State* L, GCObject* o, uint8_t tt);
|
LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt);
|
||||||
LUAI_FUNC void luaC_linkupval(lua_State* L, UpVal* uv);
|
LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv);
|
||||||
LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v);
|
LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v);
|
||||||
LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v);
|
LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v);
|
||||||
LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v);
|
LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v);
|
||||||
|
@ -13,8 +13,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
|
||||||
|
|
||||||
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
|
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!isdead(g, t));
|
LUAU_ASSERT(!isdead(g, t));
|
||||||
@ -104,8 +102,7 @@ static void validatestack(global_State* g, lua_State* l)
|
|||||||
if (l->namecall)
|
if (l->namecall)
|
||||||
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
validateobjref(g, obj2gco(l), obj2gco(l->namecall));
|
||||||
|
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
for (UpVal* uv = l->openupval; uv; uv = uv->u.l.threadnext)
|
||||||
for (UpVal* uv = l->openupval; uv; uv = (UpVal*)uv->next)
|
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
LUAU_ASSERT(uv->tt == LUA_TUPVAL);
|
||||||
LUAU_ASSERT(uv->v != &uv->u.value);
|
LUAU_ASSERT(uv->v != &uv->u.value);
|
||||||
@ -141,7 +138,7 @@ static void validateobj(global_State* g, GCObject* o)
|
|||||||
/* dead objects can only occur during sweep */
|
/* dead objects can only occur during sweep */
|
||||||
if (isdead(g, o))
|
if (isdead(g, o))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
|
LUAU_ASSERT(g->gcstate == GCSsweep);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,18 +177,6 @@ static void validateobj(global_State* g, GCObject* o)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void validatelist(global_State* g, GCObject* o)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
while (o)
|
|
||||||
{
|
|
||||||
validateobj(g, o);
|
|
||||||
|
|
||||||
o = o->gch.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void validategraylist(global_State* g, GCObject* o)
|
static void validategraylist(global_State* g, GCObject* o)
|
||||||
{
|
{
|
||||||
if (!keepinvariant(g))
|
if (!keepinvariant(g))
|
||||||
@ -224,8 +209,6 @@ static void validategraylist(global_State* g, GCObject* o)
|
|||||||
|
|
||||||
static bool validategco(void* context, lua_Page* page, GCObject* gco)
|
static bool validategco(void* context, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
lua_State* L = (lua_State*)context;
|
lua_State* L = (lua_State*)context;
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
@ -248,20 +231,9 @@ void luaC_validate(lua_State* L)
|
|||||||
validategraylist(g, g->gray);
|
validategraylist(g, g->gray);
|
||||||
validategraylist(g, g->grayagain);
|
validategraylist(g, g->grayagain);
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
validategco(L, NULL, obj2gco(g->mainthread));
|
||||||
{
|
|
||||||
validategco(L, NULL, obj2gco(g->mainthread));
|
|
||||||
|
|
||||||
luaM_visitgco(L, L, validategco);
|
luaM_visitgco(L, L, validategco);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < g->strt.size; ++i)
|
|
||||||
validatelist(g, (GCObject*)(g->strt.hash[i]));
|
|
||||||
|
|
||||||
validatelist(g, g->rootgc);
|
|
||||||
validatelist(g, (GCObject*)(g->strbufgc));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next)
|
||||||
{
|
{
|
||||||
@ -521,30 +493,8 @@ static void dumpobj(FILE* f, GCObject* o)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dumplist(FILE* f, GCObject* o)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
while (o)
|
|
||||||
{
|
|
||||||
dumpref(f, o);
|
|
||||||
fputc(':', f);
|
|
||||||
dumpobj(f, o);
|
|
||||||
fputc(',', f);
|
|
||||||
fputc('\n', f);
|
|
||||||
|
|
||||||
// thread has additional list containing collectable objects that are not present in rootgc
|
|
||||||
if (o->gch.tt == LUA_TTHREAD)
|
|
||||||
dumplist(f, (GCObject*)gco2th(o)->openupval);
|
|
||||||
|
|
||||||
o = o->gch.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool dumpgco(void* context, lua_Page* page, GCObject* gco)
|
static bool dumpgco(void* context, lua_Page* page, GCObject* gco)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
FILE* f = (FILE*)context;
|
FILE* f = (FILE*)context;
|
||||||
|
|
||||||
dumpref(f, gco);
|
dumpref(f, gco);
|
||||||
@ -563,19 +513,9 @@ void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State*
|
|||||||
|
|
||||||
fprintf(f, "{\"objects\":{\n");
|
fprintf(f, "{\"objects\":{\n");
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
dumpgco(f, NULL, obj2gco(g->mainthread));
|
||||||
{
|
|
||||||
dumpgco(f, NULL, obj2gco(g->mainthread));
|
|
||||||
|
|
||||||
luaM_visitgco(L, f, dumpgco);
|
luaM_visitgco(L, f, dumpgco);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dumplist(f, g->rootgc);
|
|
||||||
dumplist(f, (GCObject*)(g->strbufgc));
|
|
||||||
for (int i = 0; i < g->strt.size; ++i)
|
|
||||||
dumplist(f, (GCObject*)(g->strt.hash[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
|
fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing ,
|
||||||
fprintf(f, "},\"roots\":{\n");
|
fprintf(f, "},\"roots\":{\n");
|
||||||
|
@ -68,7 +68,7 @@ void luaL_sandboxthread(lua_State* L)
|
|||||||
lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
|
lua_setsafeenv(L, LUA_GLOBALSINDEX, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* l_alloc(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize)
|
static void* l_alloc(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||||
{
|
{
|
||||||
(void)ud;
|
(void)ud;
|
||||||
(void)osize;
|
(void)osize;
|
||||||
|
127
VM/src/lmem.cpp
127
VM/src/lmem.cpp
@ -78,8 +78,6 @@
|
|||||||
* allocated pages.
|
* allocated pages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
|
||||||
|
|
||||||
#ifndef __has_feature
|
#ifndef __has_feature
|
||||||
#define __has_feature(x) 0
|
#define __has_feature(x) 0
|
||||||
#endif
|
#endif
|
||||||
@ -98,8 +96,10 @@ LUAU_FASTFLAG(LuauGcPagedSweep)
|
|||||||
* To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock
|
* To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock
|
||||||
* the sizes of all critical structures down.
|
* the sizes of all critical structures down.
|
||||||
*/
|
*/
|
||||||
#if defined(__APPLE__) && !defined(__MACH__)
|
#if defined(__APPLE__)
|
||||||
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32)
|
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32)
|
||||||
|
#elif defined(__i386__)
|
||||||
|
#define ABISWITCH(x64, ms32, gcc32) (gcc32)
|
||||||
#else
|
#else
|
||||||
// Android somehow uses a similar ABI to MSVC, *not* to iOS...
|
// Android somehow uses a similar ABI to MSVC, *not* to iOS...
|
||||||
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32)
|
#define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32)
|
||||||
@ -114,14 +114,8 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
|
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
|
||||||
// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(16, 16, 16)
|
static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header");
|
||||||
static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header");
|
static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header");
|
||||||
// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(48, 32, 32)
|
|
||||||
static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header");
|
|
||||||
|
|
||||||
// TODO (FFlagLuauGcPagedSweep): new code with old 'next' pointer requires that GCObject start at the same point as TString/UpVal
|
|
||||||
static_assert(offsetof(GCObject, uv) == 0, "UpVal data must be located at the start of the GCObject");
|
|
||||||
static_assert(offsetof(GCObject, ts) == 0, "TString data must be located at the start of the GCObject");
|
|
||||||
|
|
||||||
const size_t kSizeClasses = LUA_SIZECLASSES;
|
const size_t kSizeClasses = LUA_SIZECLASSES;
|
||||||
const size_t kMaxSmallSize = 512;
|
const size_t kMaxSmallSize = 512;
|
||||||
@ -208,53 +202,13 @@ l_noret luaM_toobig(lua_State* L)
|
|||||||
luaG_runerror(L, "memory allocation error: block too big");
|
luaG_runerror(L, "memory allocation error: block too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
static lua_Page* newpageold(lua_State* L, uint8_t sizeClass)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
|
||||||
lua_Page* page = (lua_Page*)(*g->frealloc)(L, g->ud, NULL, 0, kPageSize);
|
|
||||||
if (!page)
|
|
||||||
luaD_throw(L, LUA_ERRMEM);
|
|
||||||
|
|
||||||
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader;
|
|
||||||
int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize;
|
|
||||||
|
|
||||||
ASAN_POISON_MEMORY_REGION(page->data, blockSize * blockCount);
|
|
||||||
|
|
||||||
// setup page header
|
|
||||||
page->prev = NULL;
|
|
||||||
page->next = NULL;
|
|
||||||
|
|
||||||
page->gcolistprev = NULL;
|
|
||||||
page->gcolistnext = NULL;
|
|
||||||
|
|
||||||
page->pageSize = kPageSize;
|
|
||||||
page->blockSize = blockSize;
|
|
||||||
|
|
||||||
// note: we start with the last block in the page and move downward
|
|
||||||
// either order would work, but that way we don't need to store the block count in the page
|
|
||||||
// additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order
|
|
||||||
page->freeList = NULL;
|
|
||||||
page->freeNext = (blockCount - 1) * blockSize;
|
|
||||||
page->busyBlocks = 0;
|
|
||||||
|
|
||||||
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
|
||||||
LUAU_ASSERT(!g->freepages[sizeClass]);
|
|
||||||
g->freepages[sizeClass] = page;
|
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount)
|
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
LUAU_ASSERT(pageSize - int(offsetof(lua_Page, data)) >= blockSize * blockCount);
|
LUAU_ASSERT(pageSize - int(offsetof(lua_Page, data)) >= blockSize * blockCount);
|
||||||
|
|
||||||
lua_Page* page = (lua_Page*)(*g->frealloc)(L, g->ud, NULL, 0, pageSize);
|
lua_Page* page = (lua_Page*)(*g->frealloc)(g->ud, NULL, 0, pageSize);
|
||||||
if (!page)
|
if (!page)
|
||||||
luaD_throw(L, LUA_ERRMEM);
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
@ -290,8 +244,6 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
|||||||
|
|
||||||
static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata)
|
static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
|
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
|
||||||
int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize;
|
int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize;
|
||||||
|
|
||||||
@ -304,29 +256,8 @@ static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** g
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freepageold(lua_State* L, lua_Page* page, uint8_t sizeClass)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
|
||||||
|
|
||||||
// remove page from freelist
|
|
||||||
if (page->next)
|
|
||||||
page->next->prev = page->prev;
|
|
||||||
|
|
||||||
if (page->prev)
|
|
||||||
page->prev->next = page->next;
|
|
||||||
else if (g->freepages[sizeClass] == page)
|
|
||||||
g->freepages[sizeClass] = page->next;
|
|
||||||
|
|
||||||
// so long
|
|
||||||
(*g->frealloc)(L, g->ud, page, kPageSize, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
|
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
if (gcopageset)
|
if (gcopageset)
|
||||||
@ -342,13 +273,11 @@ static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// so long
|
// so long
|
||||||
(*g->frealloc)(L, g->ud, page, page->pageSize, 0);
|
(*g->frealloc)(g->ud, page, page->pageSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass)
|
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
// remove page from freelist
|
// remove page from freelist
|
||||||
if (page->next)
|
if (page->next)
|
||||||
page->next->prev = page->prev;
|
page->next->prev = page->prev;
|
||||||
@ -368,12 +297,7 @@ static void* newblock(lua_State* L, int sizeClass)
|
|||||||
|
|
||||||
// slow path: no page in the freelist, allocate a new one
|
// slow path: no page in the freelist, allocate a new one
|
||||||
if (!page)
|
if (!page)
|
||||||
{
|
page = newclasspage(L, g->freepages, NULL, sizeClass, true);
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
page = newclasspage(L, g->freepages, NULL, sizeClass, true);
|
|
||||||
else
|
|
||||||
page = newpageold(L, sizeClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_ASSERT(!page->prev);
|
LUAU_ASSERT(!page->prev);
|
||||||
LUAU_ASSERT(page->freeList || page->freeNext >= 0);
|
LUAU_ASSERT(page->freeList || page->freeNext >= 0);
|
||||||
@ -416,8 +340,6 @@ static void* newblock(lua_State* L, int sizeClass)
|
|||||||
|
|
||||||
static void* newgcoblock(lua_State* L, int sizeClass)
|
static void* newgcoblock(lua_State* L, int sizeClass)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
lua_Page* page = g->freegcopages[sizeClass];
|
lua_Page* page = g->freegcopages[sizeClass];
|
||||||
|
|
||||||
@ -496,17 +418,11 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
|
|||||||
|
|
||||||
// if it's the last block in the page, we don't need the page
|
// if it's the last block in the page, we don't need the page
|
||||||
if (page->busyBlocks == 0)
|
if (page->busyBlocks == 0)
|
||||||
{
|
freeclasspage(L, g->freepages, NULL, page, sizeClass);
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
freeclasspage(L, g->freepages, NULL, page, sizeClass);
|
|
||||||
else
|
|
||||||
freepageold(L, page, sizeClass);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
|
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
LUAU_ASSERT(page && page->busyBlocks > 0);
|
LUAU_ASSERT(page && page->busyBlocks > 0);
|
||||||
LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
|
LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
|
||||||
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
||||||
@ -544,7 +460,7 @@ void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat)
|
|||||||
|
|
||||||
int nclass = sizeclass(nsize);
|
int nclass = sizeclass(nsize);
|
||||||
|
|
||||||
void* block = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(L, g->ud, NULL, 0, nsize);
|
void* block = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize);
|
||||||
if (block == NULL && nsize > 0)
|
if (block == NULL && nsize > 0)
|
||||||
luaD_throw(L, LUA_ERRMEM);
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
@ -556,9 +472,6 @@ void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat)
|
|||||||
|
|
||||||
GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat)
|
GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
return (GCObject*)luaM_new_(L, nsize, memcat);
|
|
||||||
|
|
||||||
// we need to accommodate space for link for free blocks (freegcolink)
|
// we need to accommodate space for link for free blocks (freegcolink)
|
||||||
LUAU_ASSERT(nsize >= kGCOLinkOffset + sizeof(void*));
|
LUAU_ASSERT(nsize >= kGCOLinkOffset + sizeof(void*));
|
||||||
|
|
||||||
@ -602,7 +515,7 @@ void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat)
|
|||||||
if (oclass >= 0)
|
if (oclass >= 0)
|
||||||
freeblock(L, oclass, block);
|
freeblock(L, oclass, block);
|
||||||
else
|
else
|
||||||
(*g->frealloc)(L, g->ud, block, osize, 0);
|
(*g->frealloc)(g->ud, block, osize, 0);
|
||||||
|
|
||||||
g->totalbytes -= osize;
|
g->totalbytes -= osize;
|
||||||
g->memcatbytes[memcat] -= osize;
|
g->memcatbytes[memcat] -= osize;
|
||||||
@ -610,12 +523,6 @@ void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat)
|
|||||||
|
|
||||||
void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat, lua_Page* page)
|
void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat, lua_Page* page)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
luaM_free_(L, block, osize, memcat);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
LUAU_ASSERT((osize == 0) == (block == NULL));
|
LUAU_ASSERT((osize == 0) == (block == NULL));
|
||||||
|
|
||||||
@ -652,7 +559,7 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8
|
|||||||
// if either block needs to be allocated using a block allocator, we can't use realloc directly
|
// if either block needs to be allocated using a block allocator, we can't use realloc directly
|
||||||
if (nclass >= 0 || oclass >= 0)
|
if (nclass >= 0 || oclass >= 0)
|
||||||
{
|
{
|
||||||
result = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(L, g->ud, NULL, 0, nsize);
|
result = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize);
|
||||||
if (result == NULL && nsize > 0)
|
if (result == NULL && nsize > 0)
|
||||||
luaD_throw(L, LUA_ERRMEM);
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
|
|
||||||
@ -662,11 +569,11 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8
|
|||||||
if (oclass >= 0)
|
if (oclass >= 0)
|
||||||
freeblock(L, oclass, block);
|
freeblock(L, oclass, block);
|
||||||
else
|
else
|
||||||
(*g->frealloc)(L, g->ud, block, osize, 0);
|
(*g->frealloc)(g->ud, block, osize, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = (*g->frealloc)(L, g->ud, block, osize, nsize);
|
result = (*g->frealloc)(g->ud, block, osize, nsize);
|
||||||
if (result == NULL && nsize > 0)
|
if (result == NULL && nsize > 0)
|
||||||
luaD_throw(L, LUA_ERRMEM);
|
luaD_throw(L, LUA_ERRMEM);
|
||||||
}
|
}
|
||||||
@ -679,8 +586,6 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8
|
|||||||
|
|
||||||
void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize)
|
void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize;
|
int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize;
|
||||||
|
|
||||||
LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize);
|
LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize);
|
||||||
@ -700,8 +605,6 @@ lua_Page* luaM_getnextgcopage(lua_Page* page)
|
|||||||
|
|
||||||
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
char* start;
|
char* start;
|
||||||
char* end;
|
char* end;
|
||||||
int busyBlocks;
|
int busyBlocks;
|
||||||
@ -730,8 +633,6 @@ void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context
|
|||||||
|
|
||||||
void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
for (lua_Page* curr = g->allgcopages; curr;)
|
for (lua_Page* curr = g->allgcopages; curr;)
|
||||||
|
@ -7,11 +7,7 @@
|
|||||||
struct lua_Page;
|
struct lua_Page;
|
||||||
union GCObject;
|
union GCObject;
|
||||||
|
|
||||||
// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_newgco to luaM_new
|
|
||||||
#define luaM_new(L, t, size, memcat) cast_to(t*, luaM_new_(L, size, memcat))
|
|
||||||
#define luaM_newgco(L, t, size, memcat) cast_to(t*, luaM_newgco_(L, size, memcat))
|
#define luaM_newgco(L, t, size, memcat) cast_to(t*, luaM_newgco_(L, size, memcat))
|
||||||
// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_freegco to luaM_free
|
|
||||||
#define luaM_free(L, p, size, memcat) luaM_free_(L, (p), size, memcat)
|
|
||||||
#define luaM_freegco(L, p, size, memcat, page) luaM_freegco_(L, obj2gco(p), size, memcat, page)
|
#define luaM_freegco(L, p, size, memcat, page) luaM_freegco_(L, obj2gco(p), size, memcat, page)
|
||||||
|
|
||||||
#define luaM_arraysize_(n, e) ((cast_to(size_t, (n)) <= SIZE_MAX / (e)) ? (n) * (e) : (luaM_toobig(L), SIZE_MAX))
|
#define luaM_arraysize_(n, e) ((cast_to(size_t, (n)) <= SIZE_MAX / (e)) ? (n) * (e) : (luaM_toobig(L), SIZE_MAX))
|
||||||
|
@ -15,7 +15,6 @@ typedef union GCObject GCObject;
|
|||||||
*/
|
*/
|
||||||
// clang-format off
|
// clang-format off
|
||||||
#define CommonHeader \
|
#define CommonHeader \
|
||||||
GCObject* next; /* TODO: remove with FFlagLuauGcPagedSweep */ \
|
|
||||||
uint8_t tt; uint8_t marked; uint8_t memcat
|
uint8_t tt; uint8_t marked; uint8_t memcat
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@ -233,6 +232,8 @@ typedef struct TString
|
|||||||
int16_t atom;
|
int16_t atom;
|
||||||
// 2 byte padding
|
// 2 byte padding
|
||||||
|
|
||||||
|
TString* next; // next string in the hash table bucket or the string buffer linked list
|
||||||
|
|
||||||
unsigned int hash;
|
unsigned int hash;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
|
||||||
@ -327,7 +328,7 @@ typedef struct UpVal
|
|||||||
struct UpVal* next;
|
struct UpVal* next;
|
||||||
|
|
||||||
/* thread double linked list (when open) */
|
/* thread double linked list (when open) */
|
||||||
// TODO: when FFlagLuauGcPagedSweep is removed, old outer 'next' value will be placed here
|
struct UpVal* threadnext;
|
||||||
/* note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State */
|
/* note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State */
|
||||||
struct UpVal** threadprev;
|
struct UpVal** threadprev;
|
||||||
} l;
|
} l;
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
#include "ldo.h"
|
#include "ldo.h"
|
||||||
#include "ldebug.h"
|
#include "ldebug.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false)
|
LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -90,8 +89,6 @@ static void close_state(lua_State* L)
|
|||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
luaF_close(L, L->stack); /* close all upvalues for this thread */
|
luaF_close(L, L->stack); /* close all upvalues for this thread */
|
||||||
luaC_freeall(L); /* collect all objects */
|
luaC_freeall(L); /* collect all objects */
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
LUAU_ASSERT(g->rootgc == obj2gco(L));
|
|
||||||
LUAU_ASSERT(g->strbufgc == NULL);
|
LUAU_ASSERT(g->strbufgc == NULL);
|
||||||
LUAU_ASSERT(g->strt.nuse == 0);
|
LUAU_ASSERT(g->strt.nuse == 0);
|
||||||
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
|
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
|
||||||
@ -99,22 +96,20 @@ static void close_state(lua_State* L)
|
|||||||
for (int i = 0; i < LUA_SIZECLASSES; i++)
|
for (int i = 0; i < LUA_SIZECLASSES; i++)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(g->freepages[i] == NULL);
|
LUAU_ASSERT(g->freepages[i] == NULL);
|
||||||
if (FFlag::LuauGcPagedSweep)
|
LUAU_ASSERT(g->freegcopages[i] == NULL);
|
||||||
LUAU_ASSERT(g->freegcopages[i] == NULL);
|
|
||||||
}
|
}
|
||||||
if (FFlag::LuauGcPagedSweep)
|
LUAU_ASSERT(g->allgcopages == NULL);
|
||||||
LUAU_ASSERT(g->allgcopages == NULL);
|
|
||||||
LUAU_ASSERT(g->totalbytes == sizeof(LG));
|
LUAU_ASSERT(g->totalbytes == sizeof(LG));
|
||||||
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
|
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
|
||||||
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
|
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
|
||||||
LUAU_ASSERT(g->memcatbytes[i] == 0);
|
LUAU_ASSERT(g->memcatbytes[i] == 0);
|
||||||
(*g->frealloc)(L, g->ud, L, sizeof(LG), 0);
|
(*g->frealloc)(g->ud, L, sizeof(LG), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_State* luaE_newthread(lua_State* L)
|
lua_State* luaE_newthread(lua_State* L)
|
||||||
{
|
{
|
||||||
lua_State* L1 = luaM_newgco(L, lua_State, sizeof(lua_State), L->activememcat);
|
lua_State* L1 = luaM_newgco(L, lua_State, sizeof(lua_State), L->activememcat);
|
||||||
luaC_link(L, L1, LUA_TTHREAD);
|
luaC_init(L, L1, LUA_TTHREAD);
|
||||||
preinit_state(L1, L->global);
|
preinit_state(L1, L->global);
|
||||||
L1->activememcat = L->activememcat; // inherit the active memory category
|
L1->activememcat = L->activememcat; // inherit the active memory category
|
||||||
stack_init(L1, L); /* init stack */
|
stack_init(L1, L); /* init stack */
|
||||||
@ -184,13 +179,11 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
|||||||
int i;
|
int i;
|
||||||
lua_State* L;
|
lua_State* L;
|
||||||
global_State* g;
|
global_State* g;
|
||||||
void* l = (*f)(NULL, ud, NULL, 0, sizeof(LG));
|
void* l = (*f)(ud, NULL, 0, sizeof(LG));
|
||||||
if (l == NULL)
|
if (l == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
L = (lua_State*)l;
|
L = (lua_State*)l;
|
||||||
g = &((LG*)L)->g;
|
g = &((LG*)L)->g;
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
L->next = NULL;
|
|
||||||
L->tt = LUA_TTHREAD;
|
L->tt = LUA_TTHREAD;
|
||||||
L->marked = g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);
|
L->marked = g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);
|
||||||
L->memcat = 0;
|
L->memcat = 0;
|
||||||
@ -214,11 +207,6 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
|||||||
setnilvalue(&g->pseudotemp);
|
setnilvalue(&g->pseudotemp);
|
||||||
setnilvalue(registry(L));
|
setnilvalue(registry(L));
|
||||||
g->gcstate = GCSpause;
|
g->gcstate = GCSpause;
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
g->rootgc = obj2gco(L);
|
|
||||||
g->sweepstrgc = 0;
|
|
||||||
if (!FFlag::LuauGcPagedSweep)
|
|
||||||
g->sweepgc = &g->rootgc;
|
|
||||||
g->gray = NULL;
|
g->gray = NULL;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
@ -230,14 +218,10 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
|||||||
for (i = 0; i < LUA_SIZECLASSES; i++)
|
for (i = 0; i < LUA_SIZECLASSES; i++)
|
||||||
{
|
{
|
||||||
g->freepages[i] = NULL;
|
g->freepages[i] = NULL;
|
||||||
if (FFlag::LuauGcPagedSweep)
|
g->freegcopages[i] = NULL;
|
||||||
g->freegcopages[i] = NULL;
|
|
||||||
}
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
|
||||||
{
|
|
||||||
g->allgcopages = NULL;
|
|
||||||
g->sweepgcopage = NULL;
|
|
||||||
}
|
}
|
||||||
|
g->allgcopages = NULL;
|
||||||
|
g->sweepgcopage = NULL;
|
||||||
for (i = 0; i < LUA_T_COUNT; i++)
|
for (i = 0; i < LUA_T_COUNT; i++)
|
||||||
g->mt[i] = NULL;
|
g->mt[i] = NULL;
|
||||||
for (i = 0; i < LUA_UTAG_LIMIT; i++)
|
for (i = 0; i < LUA_UTAG_LIMIT; i++)
|
||||||
|
@ -142,11 +142,6 @@ typedef struct global_State
|
|||||||
uint8_t gcstate; /* state of garbage collector */
|
uint8_t gcstate; /* state of garbage collector */
|
||||||
|
|
||||||
|
|
||||||
int sweepstrgc; /* position of sweep in `strt' */
|
|
||||||
// TODO: remove with FFlagLuauGcPagedSweep
|
|
||||||
GCObject* rootgc; /* list of all collectable objects */
|
|
||||||
// TODO: remove with FFlagLuauGcPagedSweep
|
|
||||||
GCObject** sweepgc; /* position of sweep in `rootgc' */
|
|
||||||
GCObject* gray; /* list of gray objects */
|
GCObject* gray; /* list of gray objects */
|
||||||
GCObject* grayagain; /* list of objects to be traversed atomically */
|
GCObject* grayagain; /* list of objects to be traversed atomically */
|
||||||
GCObject* weak; /* list of weak tables (to be cleared) */
|
GCObject* weak; /* list of weak tables (to be cleared) */
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
|
||||||
|
|
||||||
unsigned int luaS_hash(const char* str, size_t len)
|
unsigned int luaS_hash(const char* str, size_t len)
|
||||||
{
|
{
|
||||||
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
||||||
@ -46,8 +44,6 @@ unsigned int luaS_hash(const char* str, size_t len)
|
|||||||
|
|
||||||
void luaS_resize(lua_State* L, int newsize)
|
void luaS_resize(lua_State* L, int newsize)
|
||||||
{
|
{
|
||||||
if (L->global->gcstate == GCSsweepstring)
|
|
||||||
return; /* cannot resize during GC traverse */
|
|
||||||
TString** newhash = luaM_newarray(L, newsize, TString*, 0);
|
TString** newhash = luaM_newarray(L, newsize, TString*, 0);
|
||||||
stringtable* tb = &L->global->strt;
|
stringtable* tb = &L->global->strt;
|
||||||
for (int i = 0; i < newsize; i++)
|
for (int i = 0; i < newsize; i++)
|
||||||
@ -58,13 +54,11 @@ void luaS_resize(lua_State* L, int newsize)
|
|||||||
TString* p = tb->hash[i];
|
TString* p = tb->hash[i];
|
||||||
while (p)
|
while (p)
|
||||||
{ /* for each node in the list */
|
{ /* for each node in the list */
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
TString* next = p->next; /* save next */
|
||||||
TString* next = (TString*)p->next; /* save next */
|
|
||||||
unsigned int h = p->hash;
|
unsigned int h = p->hash;
|
||||||
int h1 = lmod(h, newsize); /* new position */
|
int h1 = lmod(h, newsize); /* new position */
|
||||||
LUAU_ASSERT(cast_int(h % newsize) == lmod(h, newsize));
|
LUAU_ASSERT(cast_int(h % newsize) == lmod(h, newsize));
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
p->next = newhash[h1]; /* chain it */
|
||||||
p->next = (GCObject*)newhash[h1]; /* chain it */
|
|
||||||
newhash[h1] = p;
|
newhash[h1] = p;
|
||||||
p = next;
|
p = next;
|
||||||
}
|
}
|
||||||
@ -91,8 +85,7 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
|
|||||||
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1;
|
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1;
|
||||||
tb = &L->global->strt;
|
tb = &L->global->strt;
|
||||||
h = lmod(h, tb->size);
|
h = lmod(h, tb->size);
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the case will not be required
|
ts->next = tb->hash[h]; /* chain new entry */
|
||||||
ts->next = (GCObject*)tb->hash[h]; /* chain new entry */
|
|
||||||
tb->hash[h] = ts;
|
tb->hash[h] = ts;
|
||||||
tb->nuse++;
|
tb->nuse++;
|
||||||
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
|
if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2)
|
||||||
@ -104,20 +97,9 @@ static void linkstrbuf(lua_State* L, TString* ts)
|
|||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
if (FFlag::LuauGcPagedSweep)
|
ts->next = g->strbufgc;
|
||||||
{
|
g->strbufgc = ts;
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
ts->marked = luaC_white(g);
|
||||||
ts->next = (GCObject*)g->strbufgc;
|
|
||||||
g->strbufgc = ts;
|
|
||||||
ts->marked = luaC_white(g);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GCObject* o = obj2gco(ts);
|
|
||||||
o->gch.next = (GCObject*)g->strbufgc;
|
|
||||||
g->strbufgc = gco2ts(o);
|
|
||||||
o->gch.marked = luaC_white(g);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unlinkstrbuf(lua_State* L, TString* ts)
|
static void unlinkstrbuf(lua_State* L, TString* ts)
|
||||||
@ -130,14 +112,12 @@ static void unlinkstrbuf(lua_State* L, TString* ts)
|
|||||||
{
|
{
|
||||||
if (curr == ts)
|
if (curr == ts)
|
||||||
{
|
{
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
*p = curr->next;
|
||||||
*p = (TString*)curr->next;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
p = &curr->next;
|
||||||
p = (TString**)&curr->next;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,8 +147,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts)
|
|||||||
int bucket = lmod(h, tb->size);
|
int bucket = lmod(h, tb->size);
|
||||||
|
|
||||||
// search if we already have this string in the hash table
|
// search if we already have this string in the hash table
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
for (TString* el = tb->hash[bucket]; el != NULL; el = el->next)
|
||||||
for (TString* el = tb->hash[bucket]; el != NULL; el = (TString*)el->next)
|
|
||||||
{
|
{
|
||||||
if (el->len == ts->len && memcmp(el->data, ts->data, ts->len) == 0)
|
if (el->len == ts->len && memcmp(el->data, ts->data, ts->len) == 0)
|
||||||
{
|
{
|
||||||
@ -187,8 +166,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts)
|
|||||||
|
|
||||||
// Complete string object
|
// Complete string object
|
||||||
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1;
|
ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1;
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
ts->next = tb->hash[bucket]; // chain new entry
|
||||||
ts->next = (GCObject*)tb->hash[bucket]; // chain new entry
|
|
||||||
tb->hash[bucket] = ts;
|
tb->hash[bucket] = ts;
|
||||||
|
|
||||||
tb->nuse++;
|
tb->nuse++;
|
||||||
@ -201,8 +179,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts)
|
|||||||
TString* luaS_newlstr(lua_State* L, const char* str, size_t l)
|
TString* luaS_newlstr(lua_State* L, const char* str, size_t l)
|
||||||
{
|
{
|
||||||
unsigned int h = luaS_hash(str, l);
|
unsigned int h = luaS_hash(str, l);
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = el->next)
|
||||||
for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = (TString*)el->next)
|
|
||||||
{
|
{
|
||||||
if (el->len == l && (memcmp(str, getstr(el), l) == 0))
|
if (el->len == l && (memcmp(str, getstr(el), l) == 0))
|
||||||
{
|
{
|
||||||
@ -217,8 +194,6 @@ TString* luaS_newlstr(lua_State* L, const char* str, size_t l)
|
|||||||
|
|
||||||
static bool unlinkstr(lua_State* L, TString* ts)
|
static bool unlinkstr(lua_State* L, TString* ts)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauGcPagedSweep);
|
|
||||||
|
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
TString** p = &g->strt.hash[lmod(ts->hash, g->strt.size)];
|
TString** p = &g->strt.hash[lmod(ts->hash, g->strt.size)];
|
||||||
@ -227,14 +202,12 @@ static bool unlinkstr(lua_State* L, TString* ts)
|
|||||||
{
|
{
|
||||||
if (curr == ts)
|
if (curr == ts)
|
||||||
{
|
{
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
*p = curr->next;
|
||||||
*p = (TString*)curr->next;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required
|
p = &curr->next;
|
||||||
p = (TString**)&curr->next;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,20 +216,11 @@ static bool unlinkstr(lua_State* L, TString* ts)
|
|||||||
|
|
||||||
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauGcPagedSweep)
|
// Unchain from the string table
|
||||||
{
|
if (!unlinkstr(L, ts))
|
||||||
// Unchain from the string table
|
unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands
|
||||||
if (!unlinkstr(L, ts))
|
|
||||||
unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands
|
|
||||||
else
|
|
||||||
L->global->strt.nuse--;
|
|
||||||
|
|
||||||
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
L->global->strt.nuse--;
|
L->global->strt.nuse--;
|
||||||
|
|
||||||
luaM_free(L, ts, sizestring(ts->len), ts->memcat);
|
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -425,7 +425,7 @@ static void rehash(lua_State* L, Table* t, const TValue* ek)
|
|||||||
Table* luaH_new(lua_State* L, int narray, int nhash)
|
Table* luaH_new(lua_State* L, int narray, int nhash)
|
||||||
{
|
{
|
||||||
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
luaC_link(L, t, LUA_TTABLE);
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
t->metatable = NULL;
|
t->metatable = NULL;
|
||||||
t->flags = cast_byte(~0);
|
t->flags = cast_byte(~0);
|
||||||
t->array = NULL;
|
t->array = NULL;
|
||||||
@ -742,7 +742,7 @@ int luaH_getn(Table* t)
|
|||||||
Table* luaH_clone(lua_State* L, Table* tt)
|
Table* luaH_clone(lua_State* L, Table* tt)
|
||||||
{
|
{
|
||||||
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
luaC_link(L, t, LUA_TTABLE);
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
t->metatable = tt->metatable;
|
t->metatable = tt->metatable;
|
||||||
t->flags = tt->flags;
|
t->flags = tt->flags;
|
||||||
t->array = NULL;
|
t->array = NULL;
|
||||||
|
@ -12,7 +12,7 @@ Udata* luaU_newudata(lua_State* L, size_t s, int tag)
|
|||||||
if (s > INT_MAX - sizeof(Udata))
|
if (s > INT_MAX - sizeof(Udata))
|
||||||
luaM_toobig(L);
|
luaM_toobig(L);
|
||||||
Udata* u = luaM_newgco(L, Udata, sizeudata(s), L->activememcat);
|
Udata* u = luaM_newgco(L, Udata, sizeudata(s), L->activememcat);
|
||||||
luaC_link(L, u, LUA_TUSERDATA);
|
luaC_init(L, u, LUA_TUSERDATA);
|
||||||
u->len = int(s);
|
u->len = int(s);
|
||||||
u->metatable = NULL;
|
u->metatable = NULL;
|
||||||
LUAU_ASSERT(tag >= 0 && tag <= 255);
|
LUAU_ASSERT(tag >= 0 && tag <= 255);
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "Luau/TypeInfer.h"
|
|
||||||
#include "Luau/Linter.h"
|
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/Linter.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "Luau/Parser.h"
|
||||||
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
||||||
{
|
{
|
||||||
|
@ -96,11 +96,13 @@ message ExprIndexExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ExprFunction {
|
message ExprFunction {
|
||||||
repeated Local args = 1;
|
repeated Typename generics = 1;
|
||||||
required bool vararg = 2;
|
repeated Typename genericpacks = 2;
|
||||||
required StatBlock body = 3;
|
repeated Local args = 3;
|
||||||
repeated Type types = 4;
|
required bool vararg = 4;
|
||||||
repeated Type rettypes = 5;
|
required StatBlock body = 5;
|
||||||
|
repeated Type types = 6;
|
||||||
|
repeated Type rettypes = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TableItem {
|
message TableItem {
|
||||||
@ -153,7 +155,10 @@ message ExprBinary {
|
|||||||
message ExprIfElse {
|
message ExprIfElse {
|
||||||
required Expr cond = 1;
|
required Expr cond = 1;
|
||||||
required Expr then = 2;
|
required Expr then = 2;
|
||||||
required Expr else = 3;
|
oneof else_oneof {
|
||||||
|
Expr else = 3;
|
||||||
|
ExprIfElse elseif = 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message LValue {
|
message LValue {
|
||||||
@ -183,6 +188,7 @@ message Stat {
|
|||||||
StatFunction function = 14;
|
StatFunction function = 14;
|
||||||
StatLocalFunction local_function = 15;
|
StatLocalFunction local_function = 15;
|
||||||
StatTypeAlias type_alias = 16;
|
StatTypeAlias type_alias = 16;
|
||||||
|
StatRequireIntoLocalHelper require_into_local = 17;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,9 +282,16 @@ message StatLocalFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message StatTypeAlias {
|
message StatTypeAlias {
|
||||||
required Typename name = 1;
|
required bool export = 1;
|
||||||
required Type type = 2;
|
required Typename name = 2;
|
||||||
repeated Typename generics = 3;
|
required Type type = 3;
|
||||||
|
repeated Typename generics = 4;
|
||||||
|
repeated Typename genericpacks = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatRequireIntoLocalHelper {
|
||||||
|
required Local var = 1;
|
||||||
|
required int32 modulenum = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Type {
|
message Type {
|
||||||
@ -292,6 +305,8 @@ message Type {
|
|||||||
TypeIntersection intersection = 7;
|
TypeIntersection intersection = 7;
|
||||||
TypeClass class = 8;
|
TypeClass class = 8;
|
||||||
TypeRef ref = 9;
|
TypeRef ref = 9;
|
||||||
|
TypeBoolean boolean = 10;
|
||||||
|
TypeString string = 11;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +316,8 @@ message TypePrimitive {
|
|||||||
|
|
||||||
message TypeLiteral {
|
message TypeLiteral {
|
||||||
required Typename name = 1;
|
required Typename name = 1;
|
||||||
repeated Typename generics = 2;
|
repeated Type generics = 2;
|
||||||
|
repeated Typename genericpacks = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TypeTableItem {
|
message TypeTableItem {
|
||||||
@ -320,8 +336,10 @@ message TypeTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message TypeFunction {
|
message TypeFunction {
|
||||||
repeated Type args = 1;
|
repeated Typename generics = 1;
|
||||||
repeated Type rets = 2;
|
repeated Typename genericpacks = 2;
|
||||||
|
repeated Type args = 3;
|
||||||
|
repeated Type rets = 4;
|
||||||
// TODO: vararg?
|
// TODO: vararg?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,3 +365,16 @@ message TypeRef {
|
|||||||
required Local prefix = 1;
|
required Local prefix = 1;
|
||||||
required Typename index = 2;
|
required Typename index = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message TypeBoolean {
|
||||||
|
required bool val = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TypeString {
|
||||||
|
required string val = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ModuleSet {
|
||||||
|
optional StatBlock module = 1;
|
||||||
|
required StatBlock program = 2;
|
||||||
|
}
|
||||||
|
237
fuzz/proto.cpp
237
fuzz/proto.cpp
@ -2,16 +2,17 @@
|
|||||||
#include "src/libfuzzer/libfuzzer_macro.h"
|
#include "src/libfuzzer/libfuzzer_macro.h"
|
||||||
#include "luau.pb.h"
|
#include "luau.pb.h"
|
||||||
|
|
||||||
#include "Luau/TypeInfer.h"
|
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
|
||||||
#include "Luau/ModuleResolver.h"
|
|
||||||
#include "Luau/Compiler.h"
|
|
||||||
#include "Luau/Linter.h"
|
|
||||||
#include "Luau/BytecodeBuilder.h"
|
#include "Luau/BytecodeBuilder.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/Compiler.h"
|
||||||
|
#include "Luau/Frontend.h"
|
||||||
|
#include "Luau/Linter.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Transpiler.h"
|
#include "Luau/Transpiler.h"
|
||||||
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
#include "lua.h"
|
#include "lua.h"
|
||||||
#include "lualib.h"
|
#include "lualib.h"
|
||||||
@ -30,7 +31,7 @@ const bool kFuzzTypes = true;
|
|||||||
|
|
||||||
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
|
static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!");
|
||||||
|
|
||||||
std::string protoprint(const luau::StatBlock& stat, bool types);
|
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types);
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||||
@ -38,6 +39,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
|
|||||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
|
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||||
|
|
||||||
std::chrono::milliseconds kInterruptTimeout(10);
|
std::chrono::milliseconds kInterruptTimeout(10);
|
||||||
std::chrono::time_point<std::chrono::system_clock> interruptDeadline;
|
std::chrono::time_point<std::chrono::system_clock> interruptDeadline;
|
||||||
@ -135,10 +137,58 @@ int registerTypes(Luau::TypeChecker& env)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
struct FuzzFileResolver : Luau::FileResolver
|
||||||
|
{
|
||||||
|
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
|
||||||
|
{
|
||||||
|
auto it = source.find(name);
|
||||||
|
if (it == source.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
static std::string debugsource;
|
return Luau::SourceCode{it->second, Luau::SourceCode::Module};
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_PROTO_FUZZER(const luau::StatBlock& message)
|
std::optional<Luau::ModuleInfo> resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* expr) override
|
||||||
|
{
|
||||||
|
if (Luau::AstExprGlobal* g = expr->as<Luau::AstExprGlobal>())
|
||||||
|
return Luau::ModuleInfo{g->name.value};
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getHumanReadableModuleName(const Luau::ModuleName& name) const override
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> getEnvironmentForModule(const Luau::ModuleName& name) const override
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<Luau::ModuleName, std::string> source;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FuzzConfigResolver : Luau::ConfigResolver
|
||||||
|
{
|
||||||
|
FuzzConfigResolver()
|
||||||
|
{
|
||||||
|
defaultConfig.mode = Luau::Mode::Nonstrict; // typecheckTwice option will cover Strict mode
|
||||||
|
defaultConfig.enabledLint.warningMask = ~0ull;
|
||||||
|
defaultConfig.parseOptions.captureComments = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const Luau::Config& getConfig(const Luau::ModuleName& name) const override
|
||||||
|
{
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
Luau::Config defaultConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::string> debugsources;
|
||||||
|
|
||||||
|
DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||||
{
|
{
|
||||||
FInt::LuauTypeInferRecursionLimit.value = 100;
|
FInt::LuauTypeInferRecursionLimit.value = 100;
|
||||||
FInt::LuauTypeInferTypePackLoopLimit.value = 100;
|
FInt::LuauTypeInferTypePackLoopLimit.value = 100;
|
||||||
@ -151,91 +201,90 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message)
|
|||||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||||
flag->value = true;
|
flag->value = true;
|
||||||
|
|
||||||
Luau::Allocator allocator;
|
FFlag::DebugLuauFreezeArena.value = true;
|
||||||
Luau::AstNameTable names(allocator);
|
|
||||||
|
|
||||||
std::string source = protoprint(message, kFuzzTypes);
|
std::vector<std::string> sources = protoprint(message, kFuzzTypes);
|
||||||
|
|
||||||
// stash source in a global for easier crash dump debugging
|
// stash source in a global for easier crash dump debugging
|
||||||
debugsource = source;
|
debugsources = sources;
|
||||||
|
|
||||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.c_str(), source.size(), names, allocator);
|
|
||||||
|
|
||||||
// "static" here is to accelerate fuzzing process by only creating and populating the type environment once
|
|
||||||
static Luau::NullModuleResolver moduleResolver;
|
|
||||||
static Luau::InternalErrorReporter iceHandler;
|
|
||||||
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
|
||||||
static int once = registerTypes(sharedEnv);
|
|
||||||
(void)once;
|
|
||||||
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 0);
|
|
||||||
(void)once2;
|
|
||||||
|
|
||||||
iceHandler.onInternalError = [](const char* error) {
|
|
||||||
printf("ICE: %s\n", error);
|
|
||||||
LUAU_ASSERT(!"ICE");
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool debug = getenv("LUAU_DEBUG") != 0;
|
static bool debug = getenv("LUAU_DEBUG") != 0;
|
||||||
|
|
||||||
if (debug)
|
if (debug)
|
||||||
{
|
{
|
||||||
fprintf(stdout, "--\n%s\n", source.c_str());
|
for (std::string& source : sources)
|
||||||
|
fprintf(stdout, "--\n%s\n", source.c_str());
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bytecode;
|
// parse all sources
|
||||||
|
std::vector<std::unique_ptr<Luau::Allocator>> parseAllocators;
|
||||||
|
std::vector<std::unique_ptr<Luau::AstNameTable>> parseNameTables;
|
||||||
|
|
||||||
// compile
|
Luau::ParseOptions parseOptions;
|
||||||
if (kFuzzCompiler && parseResult.errors.empty())
|
parseOptions.captureComments = true;
|
||||||
|
|
||||||
|
std::vector<Luau::ParseResult> parseResults;
|
||||||
|
|
||||||
|
for (std::string& source : sources)
|
||||||
{
|
{
|
||||||
Luau::CompileOptions compileOptions;
|
parseAllocators.push_back(std::make_unique<Luau::Allocator>());
|
||||||
|
parseNameTables.push_back(std::make_unique<Luau::AstNameTable>(*parseAllocators.back()));
|
||||||
|
|
||||||
try
|
parseResults.push_back(Luau::Parser::parse(source.c_str(), source.size(), *parseNameTables.back(), *parseAllocators.back(), parseOptions));
|
||||||
{
|
|
||||||
Luau::BytecodeBuilder bcb;
|
|
||||||
Luau::compileOrThrow(bcb, parseResult.root, names, compileOptions);
|
|
||||||
bytecode = bcb.getBytecode();
|
|
||||||
}
|
|
||||||
catch (const Luau::CompileError&)
|
|
||||||
{
|
|
||||||
// not all valid ASTs can be compiled due to limits on number of registers
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typecheck
|
// typecheck all sources
|
||||||
if (kFuzzTypeck && parseResult.root)
|
|
||||||
{
|
|
||||||
Luau::SourceModule sourceModule;
|
|
||||||
sourceModule.root = parseResult.root;
|
|
||||||
sourceModule.mode = Luau::Mode::Nonstrict;
|
|
||||||
|
|
||||||
Luau::TypeChecker typeck(&moduleResolver, &iceHandler);
|
|
||||||
typeck.globalScope = sharedEnv.globalScope;
|
|
||||||
|
|
||||||
Luau::ModulePtr module = nullptr;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
module = typeck.check(sourceModule, Luau::Mode::Nonstrict);
|
|
||||||
}
|
|
||||||
catch (std::exception&)
|
|
||||||
{
|
|
||||||
// This catches internal errors that the type checker currently (unfortunately) throws in some cases
|
|
||||||
}
|
|
||||||
|
|
||||||
// lint (note that we need access to types so we need to do this with typeck in scope)
|
|
||||||
if (kFuzzLinter)
|
|
||||||
{
|
|
||||||
Luau::LintOptions lintOptions = {~0u};
|
|
||||||
Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), {}, lintOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down
|
|
||||||
// note: it's important for typeck to be destroyed at this point!
|
|
||||||
if (kFuzzTypeck)
|
if (kFuzzTypeck)
|
||||||
{
|
{
|
||||||
for (auto& p : sharedEnv.globalScope->bindings)
|
static FuzzFileResolver fileResolver;
|
||||||
|
static Luau::NullConfigResolver configResolver;
|
||||||
|
static Luau::FrontendOptions options{true, true};
|
||||||
|
static Luau::Frontend frontend(&fileResolver, &configResolver, options);
|
||||||
|
|
||||||
|
static int once = registerTypes(frontend.typeChecker);
|
||||||
|
(void)once;
|
||||||
|
static int once2 = (Luau::freeze(frontend.typeChecker.globalTypes), 0);
|
||||||
|
(void)once2;
|
||||||
|
|
||||||
|
frontend.iceHandler.onInternalError = [](const char* error) {
|
||||||
|
printf("ICE: %s\n", error);
|
||||||
|
LUAU_ASSERT(!"ICE");
|
||||||
|
};
|
||||||
|
|
||||||
|
// restart
|
||||||
|
frontend.clear();
|
||||||
|
fileResolver.source.clear();
|
||||||
|
|
||||||
|
// load sources
|
||||||
|
for (size_t i = 0; i < sources.size(); i++)
|
||||||
|
{
|
||||||
|
std::string name = "module" + std::to_string(i);
|
||||||
|
fileResolver.source[name] = sources[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check sources
|
||||||
|
for (size_t i = 0; i < sources.size(); i++)
|
||||||
|
{
|
||||||
|
std::string name = "module" + std::to_string(i);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Luau::CheckResult result = frontend.check(name, std::nullopt);
|
||||||
|
|
||||||
|
// lint (note that we need access to types so we need to do this with typeck in scope)
|
||||||
|
if (kFuzzLinter && result.errors.empty())
|
||||||
|
frontend.lint(name, std::nullopt);
|
||||||
|
}
|
||||||
|
catch (std::exception&)
|
||||||
|
{
|
||||||
|
// This catches internal errors that the type checker currently (unfortunately) throws in some cases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down
|
||||||
|
// note: it's important for typeck to be destroyed at this point!
|
||||||
|
for (auto& p : frontend.typeChecker.globalScope->bindings)
|
||||||
{
|
{
|
||||||
Luau::ToStringOptions opts;
|
Luau::ToStringOptions opts;
|
||||||
opts.exhaustive = true;
|
opts.exhaustive = true;
|
||||||
@ -246,12 +295,44 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kFuzzTranspile && parseResult.root)
|
if (kFuzzTranspile)
|
||||||
{
|
{
|
||||||
transpileWithTypes(*parseResult.root);
|
for (Luau::ParseResult& parseResult : parseResults)
|
||||||
|
{
|
||||||
|
if (parseResult.root)
|
||||||
|
transpileWithTypes(*parseResult.root);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run resulting bytecode
|
std::string bytecode;
|
||||||
|
|
||||||
|
// compile
|
||||||
|
if (kFuzzCompiler)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < parseResults.size(); i++)
|
||||||
|
{
|
||||||
|
Luau::ParseResult& parseResult = parseResults[i];
|
||||||
|
Luau::AstNameTable& parseNameTable = *parseNameTables[i];
|
||||||
|
|
||||||
|
if (parseResult.errors.empty())
|
||||||
|
{
|
||||||
|
Luau::CompileOptions compileOptions;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Luau::BytecodeBuilder bcb;
|
||||||
|
Luau::compileOrThrow(bcb, parseResult.root, parseNameTable, compileOptions);
|
||||||
|
bytecode = bcb.getBytecode();
|
||||||
|
}
|
||||||
|
catch (const Luau::CompileError&)
|
||||||
|
{
|
||||||
|
// not all valid ASTs can be compiled due to limits on number of registers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run resulting bytecode (from last successfully compiler module)
|
||||||
if (kFuzzVM && bytecode.size())
|
if (kFuzzVM && bytecode.size())
|
||||||
{
|
{
|
||||||
static lua_State* globalState = createGlobalState();
|
static lua_State* globalState = createGlobalState();
|
||||||
|
@ -208,6 +208,35 @@ struct ProtoToLuau
|
|||||||
source += std::to_string(name.index() & 0xff);
|
source += std::to_string(name.index() & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void genericidents(const T& node)
|
||||||
|
{
|
||||||
|
if (node.generics_size() || node.genericpacks_size())
|
||||||
|
{
|
||||||
|
source += '<';
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < node.generics_size(); ++i)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
source += ',';
|
||||||
|
first = false;
|
||||||
|
ident(node.generics(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < node.genericpacks_size(); ++i)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
source += ',';
|
||||||
|
first = false;
|
||||||
|
ident(node.genericpacks(i));
|
||||||
|
source += "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
source += '>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void print(const luau::Expr& expr)
|
void print(const luau::Expr& expr)
|
||||||
{
|
{
|
||||||
if (expr.has_group())
|
if (expr.has_group())
|
||||||
@ -240,6 +269,8 @@ struct ProtoToLuau
|
|||||||
print(expr.unary());
|
print(expr.unary());
|
||||||
else if (expr.has_binary())
|
else if (expr.has_binary())
|
||||||
print(expr.binary());
|
print(expr.binary());
|
||||||
|
else if (expr.has_ifelse())
|
||||||
|
print(expr.ifelse());
|
||||||
else
|
else
|
||||||
source += "_";
|
source += "_";
|
||||||
}
|
}
|
||||||
@ -350,6 +381,7 @@ struct ProtoToLuau
|
|||||||
|
|
||||||
void function(const luau::ExprFunction& expr)
|
void function(const luau::ExprFunction& expr)
|
||||||
{
|
{
|
||||||
|
genericidents(expr);
|
||||||
source += "(";
|
source += "(";
|
||||||
for (int i = 0; i < expr.args_size(); ++i)
|
for (int i = 0; i < expr.args_size(); ++i)
|
||||||
{
|
{
|
||||||
@ -478,12 +510,21 @@ struct ProtoToLuau
|
|||||||
|
|
||||||
void print(const luau::ExprIfElse& expr)
|
void print(const luau::ExprIfElse& expr)
|
||||||
{
|
{
|
||||||
source += " if ";
|
source += "if ";
|
||||||
print(expr.cond());
|
print(expr.cond());
|
||||||
source += " then ";
|
source += " then ";
|
||||||
print(expr.then());
|
print(expr.then());
|
||||||
source += " else ";
|
|
||||||
print(expr.else_());
|
if (expr.has_else_())
|
||||||
|
{
|
||||||
|
source += " else ";
|
||||||
|
print(expr.else_());
|
||||||
|
}
|
||||||
|
else if (expr.has_elseif())
|
||||||
|
{
|
||||||
|
source += " else";
|
||||||
|
print(expr.elseif());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void print(const luau::LValue& expr)
|
void print(const luau::LValue& expr)
|
||||||
@ -534,6 +575,8 @@ struct ProtoToLuau
|
|||||||
print(stat.local_function());
|
print(stat.local_function());
|
||||||
else if (stat.has_type_alias())
|
else if (stat.has_type_alias())
|
||||||
print(stat.type_alias());
|
print(stat.type_alias());
|
||||||
|
else if (stat.has_require_into_local())
|
||||||
|
print(stat.require_into_local());
|
||||||
else
|
else
|
||||||
source += "do end\n";
|
source += "do end\n";
|
||||||
}
|
}
|
||||||
@ -804,26 +847,24 @@ struct ProtoToLuau
|
|||||||
|
|
||||||
void print(const luau::StatTypeAlias& stat)
|
void print(const luau::StatTypeAlias& stat)
|
||||||
{
|
{
|
||||||
|
if (stat.export_())
|
||||||
|
source += "export ";
|
||||||
|
|
||||||
source += "type ";
|
source += "type ";
|
||||||
ident(stat.name());
|
ident(stat.name());
|
||||||
|
genericidents(stat);
|
||||||
if (stat.generics_size())
|
|
||||||
{
|
|
||||||
source += '<';
|
|
||||||
for (size_t i = 0; i < stat.generics_size(); ++i)
|
|
||||||
{
|
|
||||||
if (i != 0)
|
|
||||||
source += ',';
|
|
||||||
ident(stat.generics(i));
|
|
||||||
}
|
|
||||||
source += '>';
|
|
||||||
}
|
|
||||||
|
|
||||||
source += " = ";
|
source += " = ";
|
||||||
print(stat.type());
|
print(stat.type());
|
||||||
source += '\n';
|
source += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print(const luau::StatRequireIntoLocalHelper& stat)
|
||||||
|
{
|
||||||
|
source += "local ";
|
||||||
|
print(stat.var());
|
||||||
|
source += " = require(module" + std::to_string(stat.modulenum() % 2) + ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
void print(const luau::Type& type)
|
void print(const luau::Type& type)
|
||||||
{
|
{
|
||||||
if (type.has_primitive())
|
if (type.has_primitive())
|
||||||
@ -844,6 +885,10 @@ struct ProtoToLuau
|
|||||||
print(type.class_());
|
print(type.class_());
|
||||||
else if (type.has_ref())
|
else if (type.has_ref())
|
||||||
print(type.ref());
|
print(type.ref());
|
||||||
|
else if (type.has_boolean())
|
||||||
|
print(type.boolean());
|
||||||
|
else if (type.has_string())
|
||||||
|
print(type.string());
|
||||||
else
|
else
|
||||||
source += "any";
|
source += "any";
|
||||||
}
|
}
|
||||||
@ -858,15 +903,28 @@ struct ProtoToLuau
|
|||||||
{
|
{
|
||||||
ident(type.name());
|
ident(type.name());
|
||||||
|
|
||||||
if (type.generics_size())
|
if (type.generics_size() || type.genericpacks_size())
|
||||||
{
|
{
|
||||||
source += '<';
|
source += '<';
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
for (size_t i = 0; i < type.generics_size(); ++i)
|
for (size_t i = 0; i < type.generics_size(); ++i)
|
||||||
{
|
{
|
||||||
if (i != 0)
|
if (!first)
|
||||||
source += ',';
|
source += ',';
|
||||||
ident(type.generics(i));
|
first = false;
|
||||||
|
print(type.generics(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < type.genericpacks_size(); ++i)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
source += ',';
|
||||||
|
first = false;
|
||||||
|
ident(type.genericpacks(i));
|
||||||
|
source += "...";
|
||||||
|
}
|
||||||
|
|
||||||
source += '>';
|
source += '>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -893,6 +951,7 @@ struct ProtoToLuau
|
|||||||
|
|
||||||
void print(const luau::TypeFunction& type)
|
void print(const luau::TypeFunction& type)
|
||||||
{
|
{
|
||||||
|
genericidents(type);
|
||||||
source += '(';
|
source += '(';
|
||||||
for (size_t i = 0; i < type.args_size(); ++i)
|
for (size_t i = 0; i < type.args_size(); ++i)
|
||||||
{
|
{
|
||||||
@ -950,12 +1009,38 @@ struct ProtoToLuau
|
|||||||
source += '.';
|
source += '.';
|
||||||
ident(type.index());
|
ident(type.index());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print(const luau::TypeBoolean& type)
|
||||||
|
{
|
||||||
|
source += type.val() ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(const luau::TypeString& type)
|
||||||
|
{
|
||||||
|
source += '"';
|
||||||
|
for (char ch : type.val())
|
||||||
|
if (isgraph(ch))
|
||||||
|
source += ch;
|
||||||
|
source += '"';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string protoprint(const luau::StatBlock& stat, bool types)
|
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types)
|
||||||
{
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
if (stat.has_module())
|
||||||
|
{
|
||||||
|
ProtoToLuau printer;
|
||||||
|
printer.types = types;
|
||||||
|
printer.print(stat.module());
|
||||||
|
result.push_back(printer.source);
|
||||||
|
}
|
||||||
|
|
||||||
ProtoToLuau printer;
|
ProtoToLuau printer;
|
||||||
printer.types = types;
|
printer.types = types;
|
||||||
printer.print(stat);
|
printer.print(stat.program());
|
||||||
return printer.source;
|
result.push_back(printer.source);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
#include "src/libfuzzer/libfuzzer_macro.h"
|
#include "src/libfuzzer/libfuzzer_macro.h"
|
||||||
#include "luau.pb.h"
|
#include "luau.pb.h"
|
||||||
|
|
||||||
std::string protoprint(const luau::StatBlock& stat, bool types);
|
std::vector<std::string> protoprint(const luau::ModuleSet& stat, bool types);
|
||||||
|
|
||||||
DEFINE_PROTO_FUZZER(const luau::StatBlock& message)
|
DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||||
{
|
{
|
||||||
std::string source = protoprint(message, true);
|
std::vector<std::string> sources = protoprint(message, true);
|
||||||
|
|
||||||
printf("%s\n", source.c_str());
|
for (size_t i = 0; i < sources.size(); i++)
|
||||||
|
{
|
||||||
|
printf("Module 'l%d':\n", int(i));
|
||||||
|
printf("%s\n", sources[i].c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "Luau/TypeInfer.h"
|
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "Luau/Parser.h"
|
||||||
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||||
|
@ -2752,7 +2752,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons")
|
|||||||
ScopedFastFlag sffs[] = {
|
ScopedFastFlag sffs[] = {
|
||||||
{"LuauParseSingletonTypes", true},
|
{"LuauParseSingletonTypes", true},
|
||||||
{"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
{"LuauRefactorTypeVarQuestions", true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
check(R"(
|
check(R"(
|
||||||
|
@ -712,6 +712,47 @@ TEST_CASE("Reference")
|
|||||||
CHECK(dtorhits == 2);
|
CHECK(dtorhits == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ApiTables")
|
||||||
|
{
|
||||||
|
StateRef globalState(luaL_newstate(), lua_close);
|
||||||
|
lua_State* L = globalState.get();
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_pushnumber(L, 123.0);
|
||||||
|
lua_setfield(L, -2, "key");
|
||||||
|
lua_pushstring(L, "test");
|
||||||
|
lua_rawseti(L, -2, 5);
|
||||||
|
|
||||||
|
// lua_gettable
|
||||||
|
lua_pushstring(L, "key");
|
||||||
|
CHECK(lua_gettable(L, -2) == LUA_TNUMBER);
|
||||||
|
CHECK(lua_tonumber(L, -1) == 123.0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// lua_getfield
|
||||||
|
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
|
||||||
|
CHECK(lua_tonumber(L, -1) == 123.0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// lua_rawgetfield
|
||||||
|
CHECK(lua_rawgetfield(L, -1, "key") == LUA_TNUMBER);
|
||||||
|
CHECK(lua_tonumber(L, -1) == 123.0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// lua_rawget
|
||||||
|
lua_pushstring(L, "key");
|
||||||
|
CHECK(lua_rawget(L, -2) == LUA_TNUMBER);
|
||||||
|
CHECK(lua_tonumber(L, -1) == 123.0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// lua_rawgeti
|
||||||
|
CHECK(lua_rawgeti(L, -1, 5) == LUA_TSTRING);
|
||||||
|
CHECK(strcmp(lua_tostring(L, -1), "test") == 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("ApiFunctionCalls")
|
TEST_CASE("ApiFunctionCalls")
|
||||||
{
|
{
|
||||||
StateRef globalState = runConformance("apicalls.lua");
|
StateRef globalState = runConformance("apicalls.lua");
|
||||||
@ -796,7 +837,7 @@ TEST_CASE("ExceptionObject")
|
|||||||
return ExceptionResult{false, ""};
|
return ExceptionResult{false, ""};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto reallocFunc = [](lua_State* L, void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* {
|
auto reallocFunc = [](void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* {
|
||||||
if (nsize == 0)
|
if (nsize == 0)
|
||||||
{
|
{
|
||||||
free(ptr);
|
free(ptr);
|
||||||
@ -923,4 +964,53 @@ TEST_CASE("StringConversion")
|
|||||||
runConformance("strconv.lua");
|
runConformance("strconv.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("GCDump")
|
||||||
|
{
|
||||||
|
// internal function, declared in lgc.h - not exposed via lua.h
|
||||||
|
extern void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
|
||||||
|
|
||||||
|
StateRef globalState(luaL_newstate(), lua_close);
|
||||||
|
lua_State* L = globalState.get();
|
||||||
|
|
||||||
|
// push various objects on stack to cover different paths
|
||||||
|
lua_createtable(L, 1, 2);
|
||||||
|
lua_pushstring(L, "value");
|
||||||
|
lua_setfield(L, -2, "key");
|
||||||
|
|
||||||
|
lua_pushinteger(L, 42);
|
||||||
|
lua_rawseti(L, -2, 1000);
|
||||||
|
|
||||||
|
lua_pushinteger(L, 42);
|
||||||
|
lua_rawseti(L, -2, 1);
|
||||||
|
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
|
||||||
|
lua_newuserdata(L, 42);
|
||||||
|
lua_pushvalue(L, -2);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
|
||||||
|
lua_pushinteger(L, 1);
|
||||||
|
lua_pushcclosure(L, lua_silence, "test", 1);
|
||||||
|
|
||||||
|
lua_State* CL = lua_newthread(L);
|
||||||
|
|
||||||
|
lua_pushstring(CL, "local x x = {} local function f() x[1] = math.abs(42) end function foo() coroutine.yield() end foo() return f");
|
||||||
|
lua_loadstring(CL);
|
||||||
|
lua_resume(CL, nullptr, 0);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* path = "NUL";
|
||||||
|
#else
|
||||||
|
const char* path = "/dev/null";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FILE* f = fopen(path, "w");
|
||||||
|
REQUIRE(f);
|
||||||
|
|
||||||
|
luaC_dump(L, f, nullptr);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -1594,4 +1594,17 @@ TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf")
|
|||||||
REQUIRE_EQ(result.warnings.size(), 0); // --!nolint disables WrongComment lint :)
|
REQUIRE_EQ(result.warnings.size(), 0); // --!nolint disables WrongComment lint :)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsIfStatAndExpr")
|
||||||
|
{
|
||||||
|
LintResult result = lint(R"(
|
||||||
|
if if 1 then 2 else 3 then
|
||||||
|
elseif if 1 then 2 else 3 then
|
||||||
|
elseif if 0 then 5 else 4 then
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
REQUIRE_EQ(result.warnings.size(), 1);
|
||||||
|
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -2575,7 +2575,6 @@ do end
|
|||||||
TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack")
|
TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true};
|
||||||
ScopedFastFlag luauParseRecoverTypePackEllipsis{"LuauParseRecoverTypePackEllipsis", true};
|
|
||||||
|
|
||||||
ParseResult result = tryParse(R"(
|
ParseResult result = tryParse(R"(
|
||||||
type Y<T..., U = T...> = (T...) -> U...
|
type Y<T..., U = T...> = (T...) -> U...
|
||||||
|
@ -651,4 +651,19 @@ local a: Packed<number>
|
|||||||
|
|
||||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauParseSingletonTypes{"LuauParseSingletonTypes", true};
|
||||||
|
|
||||||
|
std::string code = R"(
|
||||||
|
type t1 = 'hello'
|
||||||
|
type t2 = true
|
||||||
|
type t3 = ''
|
||||||
|
type t4 = false
|
||||||
|
)";
|
||||||
|
|
||||||
|
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -175,6 +175,8 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
|
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {x: {y: {z: {thing: string}}}}
|
type A = {x: {y: {z: {thing: string}}}}
|
||||||
type B = {x: {y: {z: {thing: string}}}}
|
type B = {x: {y: {z: {thing: string}}}}
|
||||||
@ -184,7 +186,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_dep
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ(*typeChecker.stringType, *requireType("r"));
|
CHECK_EQ("string & string", toString(requireType("r")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
||||||
@ -218,7 +220,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_
|
|||||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_type_any")
|
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_type_any")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {x: number}
|
type A = {y: number}
|
||||||
type B = {x: any}
|
type B = {x: any}
|
||||||
local t: A & B
|
local t: A & B
|
||||||
|
|
||||||
|
@ -1115,6 +1115,22 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersection_table")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type T = {} & {f: ((string) -> string)?}
|
||||||
|
local function f(t: T, x)
|
||||||
|
if t.f then
|
||||||
|
t.f(x)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {
|
||||||
|
@ -439,4 +439,128 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauSingletonTypes", true},
|
||||||
|
{"LuauEqConstraint", true},
|
||||||
|
{"LuauDiscriminableUnions2", true},
|
||||||
|
{"LuauWidenIfSupertypeIsFree", true},
|
||||||
|
{"LuauWeakEqConstraint", false},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function foo(f, x)
|
||||||
|
if x == "hi" then
|
||||||
|
f(x)
|
||||||
|
f("foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 18})));
|
||||||
|
// should be <a...>((string) -> a..., string) -> () but needs lower bounds calculation
|
||||||
|
CHECK_EQ("<a, b...>((string) -> (b...), a) -> ()", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
||||||
|
// {
|
||||||
|
// ScopedFastFlag sff[]{
|
||||||
|
// {"LuauParseSingletonTypes", true},
|
||||||
|
// {"LuauSingletonTypes", true},
|
||||||
|
// {"LuauDiscriminableUnions2", true},
|
||||||
|
// {"LuauEqConstraint", true},
|
||||||
|
// {"LuauWidenIfSupertypeIsFree", true},
|
||||||
|
// {"LuauWeakEqConstraint", false},
|
||||||
|
// };
|
||||||
|
|
||||||
|
// CheckResult result = check(R"(
|
||||||
|
// local function foo(f, x): "hello"? -- anyone there?
|
||||||
|
// return if x == "hi"
|
||||||
|
// then f(x)
|
||||||
|
// else nil
|
||||||
|
// end
|
||||||
|
// )");
|
||||||
|
|
||||||
|
// LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
// CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
||||||
|
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
|
||||||
|
// }
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauParseSingletonTypes", true},
|
||||||
|
{"LuauSingletonTypes", true},
|
||||||
|
{"LuauWidenIfSupertypeIsFree", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local foo: "foo" = "foo"
|
||||||
|
local copy = foo
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("string", toString(requireType("copy")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauParseSingletonTypes", true},
|
||||||
|
{"LuauSingletonTypes", true},
|
||||||
|
{"LuauDiscriminableUnions2", true},
|
||||||
|
{"LuauWidenIfSupertypeIsFree", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Cat = {tag: "Cat", meows: boolean}
|
||||||
|
type Dog = {tag: "Dog", barks: boolean}
|
||||||
|
type Animal = Cat | Dog
|
||||||
|
|
||||||
|
local function f(tag: "Cat" | "Dog"): Animal?
|
||||||
|
if tag == "Cat" then
|
||||||
|
local result = {tag = tag, meows = true}
|
||||||
|
return result
|
||||||
|
elseif tag == "Dog" then
|
||||||
|
local result = {tag = tag, barks = true}
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "table_insert_with_a_singleton_argument")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauParseSingletonTypes", true},
|
||||||
|
{"LuauSingletonTypes", true},
|
||||||
|
{"LuauWidenIfSupertypeIsFree", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function foo(t, x)
|
||||||
|
if x == "hi" or x == "bye" then
|
||||||
|
table.insert(t, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = foo({}, "hi")
|
||||||
|
table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type")
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("{string}", toString(requireType("t")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -2239,4 +2239,22 @@ TEST_CASE_FIXTURE(Fixture, "give_up_after_one_metatable_index_look_up")
|
|||||||
CHECK_EQ("Type 't2' does not have key 'x'", toString(result.errors[0]));
|
CHECK_EQ("Type 't2' does not have key 'x'", toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "confusing_indexing")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauDoNotTryToReduce", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type T = {} & {p: number | string}
|
||||||
|
local function f(t: T)
|
||||||
|
return t.p
|
||||||
|
end
|
||||||
|
|
||||||
|
local foo = f({p = "string"})
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("number | string", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -5127,8 +5127,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types")
|
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag noSealedTypeMod{"LuauNoSealedTypeMod", true};
|
|
||||||
|
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
export type Type = { unrelated: boolean }
|
export type Type = { unrelated: boolean }
|
||||||
return {}
|
return {}
|
||||||
@ -5190,7 +5188,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
{"LuauDiscriminableUnions2", true},
|
||||||
{"LuauRefactorTypeVarQuestions", true},
|
|
||||||
{"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -5210,7 +5207,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
{"LuauDiscriminableUnions2", true},
|
||||||
{"LuauRefactorTypeVarQuestions", true},
|
|
||||||
{"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -5230,7 +5226,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
{"LuauDiscriminableUnions2", true},
|
||||||
{"LuauRefactorTypeVarQuestions", true},
|
|
||||||
{"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -5250,7 +5245,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauDiscriminableUnions2", true},
|
{"LuauDiscriminableUnions2", true},
|
||||||
{"LuauRefactorTypeVarQuestions", true},
|
|
||||||
{"LuauSingletonTypes", true},
|
{"LuauSingletonTypes", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ struct TryUnifyFixture : Fixture
|
|||||||
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
||||||
InternalErrorReporter iceHandler;
|
InternalErrorReporter iceHandler;
|
||||||
UnifierSharedState unifierState{&iceHandler};
|
UnifierSharedState unifierState{&iceHandler};
|
||||||
Unifier state{&arena, Mode::Strict, globalScope, Location{}, Variance::Covariant, unifierState};
|
Unifier state{&arena, Mode::Strict, Location{}, Variance::Covariant, unifierState};
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TryUnifyTests");
|
TEST_SUITE_BEGIN("TryUnifyTests");
|
||||||
@ -261,8 +261,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauUnionTagMatchFix{"LuauUnionTagMatchFix", true};
|
|
||||||
|
|
||||||
TypeVar redirect{FreeTypeVar{TypeLevel{}}};
|
TypeVar redirect{FreeTypeVar{TypeLevel{}}};
|
||||||
TypeVar table{TableTypeVar{}};
|
TypeVar table{TableTypeVar{}};
|
||||||
TypeVar metatable{MetatableTypeVar{&redirect, &table}};
|
TypeVar metatable{MetatableTypeVar{&redirect, &table}};
|
||||||
|
@ -361,16 +361,12 @@ local b: (T, T, T) -> T
|
|||||||
|
|
||||||
TEST_CASE("isString_on_string_singletons")
|
TEST_CASE("isString_on_string_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
||||||
CHECK(isString(&helloString));
|
CHECK(isString(&helloString));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("isString_on_unions_of_various_string_singletons")
|
TEST_CASE("isString_on_unions_of_various_string_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
||||||
TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}};
|
TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}};
|
||||||
TypeVar union_{UnionTypeVar{{&helloString, &byeString}}};
|
TypeVar union_{UnionTypeVar{{&helloString, &byeString}}};
|
||||||
@ -380,8 +376,6 @@ TEST_CASE("isString_on_unions_of_various_string_singletons")
|
|||||||
|
|
||||||
TEST_CASE("proof_that_isString_uses_all_of")
|
TEST_CASE("proof_that_isString_uses_all_of")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}};
|
||||||
TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}};
|
TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}};
|
||||||
TypeVar booleanType{PrimitiveTypeVar{PrimitiveTypeVar::Boolean}};
|
TypeVar booleanType{PrimitiveTypeVar{PrimitiveTypeVar::Boolean}};
|
||||||
@ -392,16 +386,12 @@ TEST_CASE("proof_that_isString_uses_all_of")
|
|||||||
|
|
||||||
TEST_CASE("isBoolean_on_boolean_singletons")
|
TEST_CASE("isBoolean_on_boolean_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
||||||
CHECK(isBoolean(&trueBool));
|
CHECK(isBoolean(&trueBool));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("isBoolean_on_unions_of_true_or_false_singletons")
|
TEST_CASE("isBoolean_on_unions_of_true_or_false_singletons")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
||||||
TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}};
|
TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}};
|
||||||
TypeVar union_{UnionTypeVar{{&trueBool, &falseBool}}};
|
TypeVar union_{UnionTypeVar{{&trueBool, &falseBool}}};
|
||||||
@ -411,8 +401,6 @@ TEST_CASE("isBoolean_on_unions_of_true_or_false_singletons")
|
|||||||
|
|
||||||
TEST_CASE("proof_that_isBoolean_uses_all_of")
|
TEST_CASE("proof_that_isBoolean_uses_all_of")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true};
|
|
||||||
|
|
||||||
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}};
|
||||||
TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}};
|
TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}};
|
||||||
TypeVar stringType{PrimitiveTypeVar{PrimitiveTypeVar::String}};
|
TypeVar stringType{PrimitiveTypeVar{PrimitiveTypeVar::String}};
|
||||||
|
@ -183,13 +183,12 @@
|
|||||||
<Expand>
|
<Expand>
|
||||||
<LinkedListItems>
|
<LinkedListItems>
|
||||||
<HeadPointer>openupval</HeadPointer>
|
<HeadPointer>openupval</HeadPointer>
|
||||||
<NextPointer>u.l.next</NextPointer>
|
<NextPointer>u.l.threadnext</NextPointer>
|
||||||
<ValueNode>this</ValueNode>
|
<ValueNode>this</ValueNode>
|
||||||
</LinkedListItems>
|
</LinkedListItems>
|
||||||
</Expand>
|
</Expand>
|
||||||
</Synthetic>
|
</Synthetic>
|
||||||
<Item Name="l_gt">l_gt</Item>
|
<Item Name="globals">gt</Item>
|
||||||
<Item Name="env">env</Item>
|
|
||||||
<Item Name="userdata">userdata</Item>
|
<Item Name="userdata">userdata</Item>
|
||||||
</Expand>
|
</Expand>
|
||||||
</Type>
|
</Type>
|
||||||
|
Loading…
Reference in New Issue
Block a user