mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/514 (#357)
This commit is contained in:
parent
aecd60371b
commit
63d5423bbb
@ -86,6 +86,8 @@ struct OwningAutocompleteResult
|
||||
};
|
||||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
|
||||
|
||||
// Deprecated, do not use in new work.
|
||||
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -49,6 +49,7 @@ struct LintWarning
|
||||
Code_DeprecatedApi = 22,
|
||||
Code_TableOperations = 23,
|
||||
Code_DuplicateCondition = 24,
|
||||
Code_MisleadingAndOr = 25,
|
||||
|
||||
Code__Count
|
||||
};
|
||||
|
@ -93,7 +93,7 @@ struct Tarjan
|
||||
|
||||
// This should never be null; ensure you initialize it before calling
|
||||
// substitution methods.
|
||||
const TxnLog* log;
|
||||
const TxnLog* log = nullptr;
|
||||
|
||||
std::vector<TypeId> edgesTy;
|
||||
std::vector<TypePackId> edgesTp;
|
||||
|
@ -307,8 +307,8 @@ private:
|
||||
//
|
||||
// We can't use a DenseHashMap here because we need a non-const iterator
|
||||
// over the map when we concatenate.
|
||||
std::unordered_map<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
|
||||
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
|
||||
std::unordered_map<TypeId, std::unique_ptr<PendingType>, DenseHashPointer> typeVarChanges;
|
||||
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>, DenseHashPointer> typePackChanges;
|
||||
|
||||
TxnLog* parent = nullptr;
|
||||
|
||||
|
@ -103,6 +103,11 @@ struct GenericTypeDefinitions
|
||||
std::vector<GenericTypePackDefinition> genericPacks;
|
||||
};
|
||||
|
||||
struct HashBoolNamePair
|
||||
{
|
||||
size_t operator()(const std::pair<bool, Name>& pair) const;
|
||||
};
|
||||
|
||||
// All TypeVars are retained via Environment::typeVars. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
@ -411,6 +416,12 @@ public:
|
||||
private:
|
||||
int checkRecursionCount = 0;
|
||||
int recursionCount = 0;
|
||||
|
||||
/**
|
||||
* We use this to avoid doing second-pass analysis of type aliases that are duplicates. We record a pair
|
||||
* (exported, name) to properly deal with the case where the two duplicates do not have the same export status.
|
||||
*/
|
||||
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
|
||||
};
|
||||
|
||||
// Unit test hook
|
||||
|
@ -54,9 +54,6 @@ struct TypePackVar
|
||||
bool persistent = false;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
};
|
||||
|
||||
|
@ -449,9 +449,6 @@ struct TypeVar final
|
||||
std::optional<std::string> documentationSymbol;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
|
||||
bool operator==(const TypeVar& rhs) const;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -115,6 +113,7 @@ declare function gcinfo(): number
|
||||
declare function error<T>(message: T, level: number?)
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number?
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
@ -200,14 +199,7 @@ declare function gcinfo(): number
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string result = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
if (FFlag::LuauFixTonumberReturnType)
|
||||
result += "declare function tonumber<T>(value: T, radix: number?): number?\n";
|
||||
else
|
||||
result += "declare function tonumber<T>(value: T, radix: number?): number\n";
|
||||
|
||||
return result;
|
||||
return kBuiltinDefinitionLuaSrc;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -43,6 +43,7 @@ static const char* kWarningNames[] = {
|
||||
"DeprecatedApi",
|
||||
"TableOperations",
|
||||
"DuplicateCondition",
|
||||
"MisleadingAndOr",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@ -2040,18 +2041,28 @@ private:
|
||||
const Property* prop = lookupClassProp(cty, node->index.value);
|
||||
|
||||
if (prop && prop->deprecated)
|
||||
{
|
||||
if (!prop->deprecatedSuggestion.empty())
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated, use '%s' instead",
|
||||
cty->name.c_str(), node->index.value, prop->deprecatedSuggestion.c_str());
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated", cty->name.c_str(),
|
||||
node->index.value);
|
||||
}
|
||||
report(node->location, *prop, cty->name.c_str(), node->index.value);
|
||||
}
|
||||
else if (const TableTypeVar* tty = get<TableTypeVar>(follow(*ty)))
|
||||
{
|
||||
auto prop = tty->props.find(node->index.value);
|
||||
|
||||
if (prop != tty->props.end() && prop->second.deprecated)
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void report(const Location& location, const Property& prop, const char* container, const char* field)
|
||||
{
|
||||
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
|
||||
|
||||
if (container)
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated%s", container, field, suggestion.c_str());
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class LintTableOperations : AstVisitor
|
||||
@ -2257,6 +2268,39 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIfElse* expr) override
|
||||
{
|
||||
if (!expr->falseExpr->is<AstExprIfElse>())
|
||||
return true;
|
||||
|
||||
// if..elseif chain detected, we need to unroll it
|
||||
std::vector<AstExpr*> conditions;
|
||||
conditions.reserve(2);
|
||||
|
||||
AstExprIfElse* head = expr;
|
||||
while (head)
|
||||
{
|
||||
head->condition->visit(this);
|
||||
head->trueExpr->visit(this);
|
||||
|
||||
conditions.push_back(head->condition);
|
||||
|
||||
if (head->falseExpr->is<AstExprIfElse>())
|
||||
{
|
||||
head = head->falseExpr->as<AstExprIfElse>();
|
||||
continue;
|
||||
}
|
||||
|
||||
head->falseExpr->visit(this);
|
||||
break;
|
||||
}
|
||||
|
||||
detectDuplicates(conditions);
|
||||
|
||||
// block recursive visits so that we only analyze each chain once
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprBinary* expr) override
|
||||
{
|
||||
if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or)
|
||||
@ -2418,6 +2462,46 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class LintMisleadingAndOr : AstVisitor
|
||||
{
|
||||
public:
|
||||
LUAU_NOINLINE static void process(LintContext& context)
|
||||
{
|
||||
LintMisleadingAndOr pass;
|
||||
pass.context = &context;
|
||||
|
||||
context.root->visit(&pass);
|
||||
}
|
||||
|
||||
private:
|
||||
LintContext* context;
|
||||
|
||||
bool visit(AstExprBinary* node) override
|
||||
{
|
||||
if (node->op != AstExprBinary::Or)
|
||||
return true;
|
||||
|
||||
AstExprBinary* and_ = node->left->as<AstExprBinary>();
|
||||
if (!and_ || and_->op != AstExprBinary::And)
|
||||
return true;
|
||||
|
||||
const char* alt = nullptr;
|
||||
|
||||
if (and_->right->is<AstExprConstantNil>())
|
||||
alt = "nil";
|
||||
else if (AstExprConstantBool* c = and_->right->as<AstExprConstantBool>(); c && c->value == false)
|
||||
alt = "false";
|
||||
|
||||
if (alt)
|
||||
emitWarning(*context, LintWarning::Code_MisleadingAndOr, node->location,
|
||||
"The and-or expression always evaluates to the second alternative because the first alternative is %s; consider using if-then-else "
|
||||
"expression instead",
|
||||
alt);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, const ScopePtr& env)
|
||||
{
|
||||
ScopePtr current = env;
|
||||
@ -2522,6 +2606,9 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
|
||||
if (context.warningEnabled(LintWarning::Code_DuplicateLocal))
|
||||
LintDuplicateLocal::process(context);
|
||||
|
||||
if (context.warningEnabled(LintWarning::Code_MisleadingAndOr))
|
||||
LintMisleadingAndOr::process(context);
|
||||
|
||||
std::sort(context.result.begin(), context.result.end(), WarningComparator());
|
||||
|
||||
return context.result;
|
||||
|
@ -12,10 +12,10 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAG(LuauTypeAliasDefaults)
|
||||
|
||||
LUAU_FASTFLAG(LuauImmutableTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false)
|
||||
|
||||
namespace Luau
|
||||
@ -66,7 +66,7 @@ TypeId TypeArena::addTV(TypeVar&& tv)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(std::move(tv));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -76,7 +76,7 @@ TypeId TypeArena::freshType(TypeLevel level)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -86,7 +86,7 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -96,7 +96,7 @@ TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -106,7 +106,7 @@ TypePackId TypeArena::addTypePack(TypePack tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -116,7 +116,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
@ -454,8 +454,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
|
||||
// TODO: Make this work when the arena of 'res' might be frozen
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
if (FFlag::LuauImmutableTypes)
|
||||
{
|
||||
// Persistent types are not being cloned and we get the original type back which might be read-only
|
||||
if (!res->persistent)
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
else
|
||||
{
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "Luau/Scope.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTwoPassAliasDefinitionFix);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -17,6 +19,8 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
|
||||
, returnType(parent->returnType)
|
||||
, level(parent->level.incr())
|
||||
{
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix)
|
||||
level = level.incr();
|
||||
level.subLevel = subLevel;
|
||||
}
|
||||
|
||||
|
@ -250,6 +250,10 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
|
||||
|
||||
PendingType* TxnLog::pending(TypeId ty) const
|
||||
{
|
||||
// This function will technically work if `this` is nullptr, but this
|
||||
// indicates a bug, so we explicitly assert.
|
||||
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
|
||||
|
||||
for (const TxnLog* current = this; current; current = current->parent)
|
||||
{
|
||||
if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end())
|
||||
@ -261,6 +265,10 @@ PendingType* TxnLog::pending(TypeId ty) const
|
||||
|
||||
PendingTypePack* TxnLog::pending(TypePackId tp) const
|
||||
{
|
||||
// This function will technically work if `this` is nullptr, but this
|
||||
// indicates a bug, so we explicitly assert.
|
||||
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
|
||||
|
||||
for (const TxnLog* current = this; current; current = current->parent)
|
||||
{
|
||||
if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end())
|
||||
|
@ -32,12 +32,13 @@ LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseBranchTypeUnion, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExpectedType2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLengthOnCompositeType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
|
||||
@ -47,7 +48,10 @@ LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
|
||||
LUAU_FASTFLAG(LuauUnionTagMatchFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -213,6 +217,11 @@ static bool isMetamethod(const Name& name)
|
||||
name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode";
|
||||
}
|
||||
|
||||
size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
|
||||
{
|
||||
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
|
||||
}
|
||||
|
||||
TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler)
|
||||
: resolver(resolver)
|
||||
, iceHandler(iceHandler)
|
||||
@ -225,6 +234,7 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
||||
, anyType(getSingletonTypes().anyType)
|
||||
, optionalNumberType(getSingletonTypes().optionalNumberType)
|
||||
, anyTypePack(getSingletonTypes().anyTypePack)
|
||||
, duplicateTypeAliases{{false, {}}}
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
@ -291,6 +301,9 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
|
||||
unifierState.skipCacheForType.clear();
|
||||
}
|
||||
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix)
|
||||
duplicateTypeAliases.clear();
|
||||
|
||||
return std::move(currentModule);
|
||||
}
|
||||
|
||||
@ -496,6 +509,9 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
|
||||
{
|
||||
if (const auto& typealias = stat->as<AstStatTypeAlias>())
|
||||
{
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == Parser::errorName)
|
||||
continue;
|
||||
|
||||
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||
|
||||
Name name = typealias->name.value;
|
||||
@ -1176,6 +1192,10 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
// Once with forwardDeclare, and once without.
|
||||
Name name = typealias.name.value;
|
||||
|
||||
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix && name == Parser::errorName)
|
||||
return;
|
||||
|
||||
std::optional<TypeFun> binding;
|
||||
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
|
||||
binding = it->second;
|
||||
@ -1192,6 +1212,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
||||
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix)
|
||||
duplicateTypeAliases.insert({typealias.exported, name});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1211,6 +1233,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
|
||||
// interesting.
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix && duplicateTypeAliases.find({typealias.exported, name}))
|
||||
return;
|
||||
|
||||
if (!binding)
|
||||
ice("Not predeclared");
|
||||
|
||||
@ -1235,7 +1262,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
if (auto ttv = getMutable<TableTypeVar>(follow(ty)))
|
||||
{
|
||||
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy
|
||||
if (ttv->name)
|
||||
// Additionally, we can't modify types that come from other modules
|
||||
if (ttv->name || (FFlag::LuauImmutableTypes && follow(ty)->owningArena != ¤tModule->internalTypes))
|
||||
{
|
||||
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
|
||||
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
|
||||
@ -1247,7 +1275,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
});
|
||||
|
||||
// Copy can be skipped if this is an identical alias
|
||||
if (ttv->name != name || !sameTys || !sameTps)
|
||||
if ((FFlag::LuauImmutableTypes && !ttv->name) || ttv->name != name || !sameTys || !sameTps)
|
||||
{
|
||||
// This is a shallow clone, original recursive links to self are not updated
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
@ -1279,9 +1307,17 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
||||
}
|
||||
}
|
||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||
mtv->syntheticName = name;
|
||||
{
|
||||
// We can't modify types that come from other modules
|
||||
if (!FFlag::LuauImmutableTypes || follow(ty)->owningArena == ¤tModule->internalTypes)
|
||||
mtv->syntheticName = name;
|
||||
}
|
||||
|
||||
unify(ty, bindingsMap[name].type, typealias.location);
|
||||
TypeId& bindingType = bindingsMap[name].type;
|
||||
bool ok = unify(ty, bindingType, typealias.location);
|
||||
|
||||
if (FFlag::LuauTwoPassAliasDefinitionFix && ok)
|
||||
bindingType = ty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1564,7 +1600,12 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
|
||||
else if (auto vtp = get<VariadicTypePack>(retPack))
|
||||
return {vtp->ty, std::move(result.predicates)};
|
||||
else if (get<Unifiable::Generic>(retPack))
|
||||
ice("Unexpected abstract type pack!", expr.location);
|
||||
{
|
||||
if (FFlag::LuauReturnAnyInsteadOfICE)
|
||||
return {anyType, std::move(result.predicates)};
|
||||
else
|
||||
ice("Unexpected abstract type pack!", expr.location);
|
||||
}
|
||||
else
|
||||
ice("Unknown TypePack type!", expr.location);
|
||||
}
|
||||
@ -1614,11 +1655,23 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
|
||||
tablify(type);
|
||||
|
||||
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
|
||||
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
|
||||
if (isString(type))
|
||||
{
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
|
||||
LUAU_ASSERT(mtIndex);
|
||||
type = *mtIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
|
||||
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
|
||||
{
|
||||
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
|
||||
type = *mtIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (TableTypeVar* tableType = getMutableTableType(type))
|
||||
@ -2476,7 +2529,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
|
||||
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
|
||||
|
||||
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy),
|
||||
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy),
|
||||
{AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||
}
|
||||
else if (expr.op == AstExprBinary::Or)
|
||||
@ -2489,7 +2542,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
|
||||
|
||||
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
|
||||
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
|
||||
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
|
||||
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||
}
|
||||
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
|
||||
@ -2497,8 +2550,8 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
|
||||
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||
return {booleanType, {std::move(*predicate)}};
|
||||
|
||||
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions);
|
||||
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions);
|
||||
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
|
||||
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
|
||||
|
||||
PredicateVec predicates;
|
||||
|
||||
@ -2785,12 +2838,16 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
}
|
||||
else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free)
|
||||
{
|
||||
TypeId resultType = freshType(scope);
|
||||
TypeId resultType = freshType(FFlag::LuauAnotherTypeLevelFix ? exprTable->level : scope->level);
|
||||
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
|
||||
return resultType;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* If we use [] indexing to fetch a property from a sealed table that has no indexer, we have no idea if it will
|
||||
* work, so we just mint a fresh type, return that, and hope for the best.
|
||||
*/
|
||||
TypeId resultType = freshType(scope);
|
||||
return resultType;
|
||||
}
|
||||
@ -4195,6 +4252,9 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
if (FFlag::LuauImmutableTypes)
|
||||
return *moduleType;
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
CloneState cloneState;
|
||||
@ -5446,7 +5506,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
|
||||
|
||||
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDiscriminableUnions);
|
||||
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2);
|
||||
|
||||
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.
|
||||
@ -5659,7 +5719,7 @@ void TypeChecker::resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, Refi
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
|
||||
if (ty && fromOr)
|
||||
@ -5772,7 +5832,7 @@ void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, Refinement
|
||||
return res;
|
||||
};
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
refineLValue(isaP.lvalue, refis, scope, predicate);
|
||||
}
|
||||
@ -5847,7 +5907,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
|
||||
|
||||
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
|
||||
{
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
|
||||
return;
|
||||
@ -5869,7 +5929,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
|
||||
}
|
||||
|
||||
auto fail = [&](const TypeErrorData& err) {
|
||||
if (!FFlag::LuauDiscriminableUnions)
|
||||
if (!FFlag::LuauDiscriminableUnions2)
|
||||
errVec.push_back(TypeError{typeguardP.location, err});
|
||||
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
};
|
||||
@ -5901,7 +5961,7 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
|
||||
return {ty};
|
||||
};
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
std::vector<TypeId> rhs = options(eqP.type);
|
||||
|
||||
|
@ -28,6 +28,7 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableAreEqualRecursion, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAG(LuauUnionTagMatchFix)
|
||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -393,7 +394,8 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
||||
if (seen.contains(ty))
|
||||
return true;
|
||||
|
||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
||||
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
|
||||
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
if (auto uty = get<UnionTypeVar>(ty))
|
||||
|
@ -15,6 +15,7 @@
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTFLAGVARIABLE(LuauCommittingTxnLogFreeTpPromote, false)
|
||||
LUAU_FASTFLAG(LuauImmutableTypes)
|
||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
||||
@ -24,6 +25,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauProperTypeLevels);
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -32,11 +34,13 @@ struct PromoteTypeLevels
|
||||
{
|
||||
DEPRECATED_TxnLog& DEPRECATED_log;
|
||||
TxnLog& log;
|
||||
const TypeArena* typeArena = nullptr;
|
||||
TypeLevel minLevel;
|
||||
|
||||
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel)
|
||||
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
|
||||
: DEPRECATED_log(DEPRECATED_log)
|
||||
, log(log)
|
||||
, typeArena(typeArena)
|
||||
, minLevel(minLevel)
|
||||
{
|
||||
}
|
||||
@ -65,8 +69,12 @@ struct PromoteTypeLevels
|
||||
}
|
||||
|
||||
template<typename TID, typename T>
|
||||
bool operator()(TID, const T&)
|
||||
bool operator()(TID ty, const T&)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -83,12 +91,20 @@ struct PromoteTypeLevels
|
||||
|
||||
bool operator()(TypeId ty, const FunctionTypeVar&)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FunctionTypeVar>(ty) : getMutable<FunctionTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv)
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
|
||||
return true;
|
||||
|
||||
@ -108,24 +124,33 @@ struct PromoteTypeLevels
|
||||
}
|
||||
};
|
||||
|
||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypeId ty)
|
||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
|
||||
{
|
||||
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
|
||||
// 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)
|
||||
return;
|
||||
|
||||
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(ty, ptl, seen);
|
||||
}
|
||||
|
||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypePackId tp)
|
||||
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
|
||||
{
|
||||
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
|
||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
|
||||
return;
|
||||
|
||||
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
visitTypeVarOnce(tp, ptl, seen);
|
||||
}
|
||||
|
||||
struct SkipCacheForType
|
||||
{
|
||||
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType)
|
||||
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
|
||||
: skipCacheForType(skipCacheForType)
|
||||
, typeArena(typeArena)
|
||||
{
|
||||
}
|
||||
|
||||
@ -152,6 +177,10 @@ struct SkipCacheForType
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar&)
|
||||
{
|
||||
// Types from other modules don't contain mutable elements and are ok to cache
|
||||
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
|
||||
|
||||
if (ttv.boundTo)
|
||||
@ -172,6 +201,10 @@ struct SkipCacheForType
|
||||
template<typename T>
|
||||
bool operator()(TypeId ty, const T& t)
|
||||
{
|
||||
// Types from other modules don't contain mutable elements and are ok to cache
|
||||
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
const bool* prev = skipCacheForType.find(ty);
|
||||
|
||||
if (prev && *prev)
|
||||
@ -184,8 +217,12 @@ struct SkipCacheForType
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(TypePackId, const T&)
|
||||
bool operator()(TypePackId tp, const T&)
|
||||
{
|
||||
// Types from other modules don't contain mutable elements and are ok to cache
|
||||
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -208,6 +245,7 @@ struct SkipCacheForType
|
||||
}
|
||||
|
||||
const DenseHashMap<TypeId, bool>& skipCacheForType;
|
||||
const TypeArena* typeArena = nullptr;
|
||||
bool result = false;
|
||||
};
|
||||
|
||||
@ -422,13 +460,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
{
|
||||
if (FFlag::LuauUseCommittingTxnLog)
|
||||
{
|
||||
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
|
||||
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
||||
log.replace(superTy, BoundTypeVar(subTy));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
|
||||
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
|
||||
else if (auto subLevel = getMutableLevel(subTy))
|
||||
{
|
||||
if (!subLevel->subsumes(superFree->level))
|
||||
@ -466,13 +504,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
{
|
||||
if (FFlag::LuauUseCommittingTxnLog)
|
||||
{
|
||||
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
|
||||
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
|
||||
log.replace(subTy, BoundTypeVar(superTy));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauProperTypeLevels)
|
||||
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
|
||||
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
|
||||
else if (auto superLevel = getMutableLevel(superTy))
|
||||
{
|
||||
if (!superLevel->subsumes(subFree->level))
|
||||
@ -849,7 +887,7 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy)
|
||||
return;
|
||||
|
||||
auto skipCacheFor = [this](TypeId ty) {
|
||||
SkipCacheForType visitor{sharedState.skipCacheForType};
|
||||
SkipCacheForType visitor{sharedState.skipCacheForType, types};
|
||||
visitTypeVarOnce(ty, visitor, sharedState.seenAny);
|
||||
|
||||
sharedState.skipCacheForType[ty] = visitor.result;
|
||||
@ -1637,32 +1675,35 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
||||
tryUnify_(subFunction->retType, superFunction->retType);
|
||||
}
|
||||
|
||||
if (FFlag::LuauUseCommittingTxnLog)
|
||||
if (!FFlag::LuauImmutableTypes)
|
||||
{
|
||||
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
||||
if (FFlag::LuauUseCommittingTxnLog)
|
||||
{
|
||||
PendingType* newSubTy = log.queue(subTy);
|
||||
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
|
||||
LUAU_ASSERT(newSubFtv);
|
||||
newSubFtv->definition = superFunction->definition;
|
||||
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
||||
{
|
||||
PendingType* newSubTy = log.queue(subTy);
|
||||
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
|
||||
LUAU_ASSERT(newSubFtv);
|
||||
newSubFtv->definition = superFunction->definition;
|
||||
}
|
||||
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
||||
{
|
||||
PendingType* newSuperTy = log.queue(superTy);
|
||||
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
|
||||
LUAU_ASSERT(newSuperFtv);
|
||||
newSuperFtv->definition = subFunction->definition;
|
||||
}
|
||||
}
|
||||
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
||||
else
|
||||
{
|
||||
PendingType* newSuperTy = log.queue(superTy);
|
||||
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
|
||||
LUAU_ASSERT(newSuperFtv);
|
||||
newSuperFtv->definition = subFunction->definition;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
||||
{
|
||||
subFunction->definition = superFunction->definition;
|
||||
}
|
||||
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
||||
{
|
||||
superFunction->definition = subFunction->definition;
|
||||
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
||||
{
|
||||
subFunction->definition = superFunction->definition;
|
||||
}
|
||||
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
||||
{
|
||||
superFunction->definition = subFunction->definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2631,7 +2672,7 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
a = follow(a);
|
||||
a = FFlag::LuauFollowWithCommittingTxnLogInAnyUnification ? state.log.follow(a) : follow(a);
|
||||
|
||||
if (seenTypePacks.find(a))
|
||||
break;
|
||||
@ -2738,7 +2779,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
||||
}
|
||||
|
||||
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
|
||||
TypeId anyType, TypePackId anyTypePack)
|
||||
const TypeArena* typeArena, TypeId anyType, TypePackId anyTypePack)
|
||||
{
|
||||
while (!queue.empty())
|
||||
{
|
||||
@ -2746,8 +2787,14 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
|
||||
{
|
||||
TypeId ty = state.log.follow(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
// Types from other modules don't have free types
|
||||
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
|
||||
continue;
|
||||
|
||||
if (seen.find(ty))
|
||||
continue;
|
||||
|
||||
seen.insert(ty);
|
||||
|
||||
if (state.log.getMutable<FreeTypeVar>(ty))
|
||||
@ -2853,7 +2900,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP);
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, getSingletonTypes().anyType, anyTP);
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
||||
@ -2869,7 +2916,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
||||
|
||||
queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp);
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, anyTp);
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
||||
|
@ -1133,7 +1133,7 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
||||
resultNames.push_back({});
|
||||
|
||||
resultNames.push_back(AstArgumentName{AstName(lexer.current().name), lexer.current().location});
|
||||
lexer.next();
|
||||
nextLexeme();
|
||||
|
||||
expectAndConsume(':');
|
||||
}
|
||||
|
68
CLI/Repl.cpp
68
CLI/Repl.cpp
@ -1,4 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Repl.h"
|
||||
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
@ -38,13 +40,14 @@ enum class CompileFormat
|
||||
struct GlobalOptions
|
||||
{
|
||||
int optimizationLevel = 1;
|
||||
int debugLevel = 1;
|
||||
} globalOptions;
|
||||
|
||||
static Luau::CompileOptions copts()
|
||||
{
|
||||
Luau::CompileOptions result = {};
|
||||
result.optimizationLevel = globalOptions.optimizationLevel;
|
||||
result.debugLevel = 1;
|
||||
result.debugLevel = globalOptions.debugLevel;
|
||||
result.coverageLevel = coverageActive() ? 2 : 0;
|
||||
|
||||
return result;
|
||||
@ -240,9 +243,8 @@ std::string runCode(lua_State* L, const std::string& source)
|
||||
return std::string();
|
||||
}
|
||||
|
||||
static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
|
||||
{
|
||||
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
|
||||
std::string_view lookup = editBuffer;
|
||||
char lastSep = 0;
|
||||
|
||||
@ -276,7 +278,7 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
// Add an opening paren for function calls by default.
|
||||
completion += "(";
|
||||
}
|
||||
ic_add_completion_ex(cenv, completion.data(), key.data(), nullptr);
|
||||
addCompletionCallback(completion, std::string(key));
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
@ -295,10 +297,11 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
{
|
||||
// Replace the string object with the string class to perform further lookups of string functions
|
||||
// Note: We retrieve the string class from _G to prevent issues if the user assigns to `string`.
|
||||
lua_pop(L, 1); // Pop the string instance
|
||||
lua_getglobal(L, "_G");
|
||||
lua_pushlstring(L, "string", 6);
|
||||
lua_rawget(L, -2);
|
||||
lua_remove(L, -2);
|
||||
lua_remove(L, -2); // Remove the global table
|
||||
LUAU_ASSERT(lua_istable(L, -1));
|
||||
}
|
||||
else if (!lua_istable(L, -1))
|
||||
@ -312,6 +315,26 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
|
||||
{
|
||||
// look the value up in current global table first
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
completeIndexer(L, editBuffer, addCompletionCallback);
|
||||
|
||||
// and in actual global table after that
|
||||
lua_getglobal(L, "_G");
|
||||
completeIndexer(L, editBuffer, addCompletionCallback);
|
||||
}
|
||||
|
||||
static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
{
|
||||
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
|
||||
|
||||
getCompletions(L, std::string(editBuffer), [cenv](const std::string& completion, const std::string& display) {
|
||||
ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
static bool isMethodOrFunctionChar(const char* s, long len)
|
||||
{
|
||||
char c = *s;
|
||||
@ -320,15 +343,7 @@ static bool isMethodOrFunctionChar(const char* s, long len)
|
||||
|
||||
static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
|
||||
{
|
||||
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
|
||||
|
||||
// look the value up in current global table first
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
|
||||
|
||||
// and in actual global table after that
|
||||
lua_getglobal(L, "_G");
|
||||
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
|
||||
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
|
||||
}
|
||||
|
||||
struct LinenoiseScopedHistory
|
||||
@ -372,19 +387,20 @@ static void runReplImpl(lua_State* L)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const char* line = ic_readline(buffer.empty() ? "" : ">");
|
||||
const char* prompt = buffer.empty() ? "" : ">";
|
||||
std::unique_ptr<char, void (*)(void*)> line(ic_readline(prompt), free);
|
||||
if (!line)
|
||||
break;
|
||||
|
||||
if (buffer.empty() && runCode(L, std::string("return ") + line) == std::string())
|
||||
if (buffer.empty() && runCode(L, std::string("return ") + line.get()) == std::string())
|
||||
{
|
||||
ic_history_add(line);
|
||||
ic_history_add(line.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!buffer.empty())
|
||||
buffer += "\n";
|
||||
buffer += line;
|
||||
buffer += line.get();
|
||||
|
||||
std::string error = runCode(L, buffer);
|
||||
|
||||
@ -400,7 +416,6 @@ static void runReplImpl(lua_State* L)
|
||||
|
||||
ic_history_add(buffer.c_str());
|
||||
buffer.clear();
|
||||
free((void*)line);
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,7 +519,7 @@ static bool compileFile(const char* name, CompileFormat format)
|
||||
|
||||
if (format == CompileFormat::Text)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source);
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
|
||||
@ -549,7 +564,8 @@ static void displayHelp(const char* argv0)
|
||||
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
|
||||
printf(" -h, --help: Display this usage message.\n");
|
||||
printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n");
|
||||
printf(" -O<n>: use compiler optimization level (n=0-2).\n");
|
||||
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
}
|
||||
@ -620,6 +636,16 @@ int replMain(int argc, char** argv)
|
||||
}
|
||||
globalOptions.optimizationLevel = level;
|
||||
}
|
||||
else if (strncmp(argv[i], "-g", 2) == 0)
|
||||
{
|
||||
int level = atoi(argv[i] + 2);
|
||||
if (level < 0 || level > 2)
|
||||
{
|
||||
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
|
||||
return 1;
|
||||
}
|
||||
globalOptions.debugLevel = level;
|
||||
}
|
||||
else if (strcmp(argv[i], "--profile") == 0)
|
||||
{
|
||||
profile = 10000; // default to 10 KHz
|
||||
|
@ -3,10 +3,15 @@
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
using AddCompletionCallback = std::function<void(const std::string& completion, const std::string& display)>;
|
||||
|
||||
// Note: These are internal functions which are being exposed in a header
|
||||
// so they can be included by unit tests.
|
||||
int replMain(int argc, char** argv);
|
||||
void setupState(lua_State* L);
|
||||
std::string runCode(lua_State* L, const std::string& source);
|
||||
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback);
|
||||
|
||||
int replMain(int argc, char** argv);
|
||||
|
@ -6,8 +6,6 @@
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Write, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -510,7 +508,7 @@ uint32_t BytecodeBuilder::getDebugPC() const
|
||||
void BytecodeBuilder::finalize()
|
||||
{
|
||||
LUAU_ASSERT(bytecode.empty());
|
||||
bytecode = char(FFlag::LuauBytecodeV2Write ? LBC_VERSION_FUTURE : LBC_VERSION);
|
||||
bytecode = char(LBC_VERSION_FUTURE);
|
||||
|
||||
writeStringTable(bytecode);
|
||||
|
||||
@ -611,9 +609,7 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
|
||||
writeVarInt(ss, child);
|
||||
|
||||
// debug info
|
||||
if (FFlag::LuauBytecodeV2Write)
|
||||
writeVarInt(ss, func.debuglinedefined);
|
||||
|
||||
writeVarInt(ss, func.debuglinedefined);
|
||||
writeVarInt(ss, func.debugname);
|
||||
|
||||
bool hasLines = true;
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileTableIndexOpt, false)
|
||||
LUAU_FASTFLAG(LuauCompileSelectBuiltin2)
|
||||
|
||||
namespace Luau
|
||||
@ -1182,18 +1181,9 @@ struct Compiler
|
||||
const AstExprTable::Item& item = expr->items.data[i];
|
||||
LUAU_ASSERT(item.key); // no list portion => all items have keys
|
||||
|
||||
if (FFlag::LuauCompileTableIndexOpt)
|
||||
{
|
||||
const Constant* ckey = constants.find(item.key);
|
||||
const Constant* ckey = constants.find(item.key);
|
||||
|
||||
indexSize += (ckey && ckey->type == Constant::Type_Number && ckey->valueNumber == double(indexSize + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
AstExprConstantNumber* ckey = item.key->as<AstExprConstantNumber>();
|
||||
|
||||
indexSize += (ckey && ckey->value == double(indexSize + 1));
|
||||
}
|
||||
indexSize += (ckey && ckey->type == Constant::Type_Number && ckey->valueNumber == double(indexSize + 1));
|
||||
}
|
||||
|
||||
// we only perform the optimization if we don't have any other []-keys
|
||||
@ -1295,43 +1285,10 @@ struct Compiler
|
||||
{
|
||||
RegScope rsi(this);
|
||||
|
||||
if (FFlag::LuauCompileTableIndexOpt)
|
||||
{
|
||||
LValue lv = compileLValueIndex(reg, key, rsi);
|
||||
uint8_t rv = compileExprAuto(value, rsi);
|
||||
LValue lv = compileLValueIndex(reg, key, rsi);
|
||||
uint8_t rv = compileExprAuto(value, rsi);
|
||||
|
||||
compileAssign(lv, rv);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Optimization: use SETTABLEKS/SETTABLEN for literal keys, this happens often as part of usual table construction syntax
|
||||
if (AstExprConstantString* ckey = key->as<AstExprConstantString>())
|
||||
{
|
||||
BytecodeBuilder::StringRef cname = sref(ckey->value);
|
||||
int32_t cid = bytecode.addConstantString(cname);
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
||||
uint8_t rv = compileExprAuto(value, rsi);
|
||||
|
||||
bytecode.emitABC(LOP_SETTABLEKS, rv, reg, uint8_t(BytecodeBuilder::getStringHash(cname)));
|
||||
bytecode.emitAux(cid);
|
||||
}
|
||||
else if (AstExprConstantNumber* ckey = key->as<AstExprConstantNumber>();
|
||||
ckey && ckey->value >= 1 && ckey->value <= 256 && double(int(ckey->value)) == ckey->value)
|
||||
{
|
||||
uint8_t rv = compileExprAuto(value, rsi);
|
||||
|
||||
bytecode.emitABC(LOP_SETTABLEN, rv, reg, uint8_t(int(ckey->value) - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t rk = compileExprAuto(key, rsi);
|
||||
uint8_t rv = compileExprAuto(value, rsi);
|
||||
|
||||
bytecode.emitABC(LOP_SETTABLE, rv, reg, rk);
|
||||
}
|
||||
}
|
||||
compileAssign(lv, rv);
|
||||
}
|
||||
// items without a key are set using SETLIST so that we can initialize large arrays quickly
|
||||
else
|
||||
@ -1439,8 +1396,7 @@ struct Compiler
|
||||
uint8_t rt = compileExprAuto(expr->expr, rs);
|
||||
uint8_t i = uint8_t(int(cv->valueNumber) - 1);
|
||||
|
||||
if (FFlag::LuauCompileTableIndexOpt)
|
||||
setDebugLine(expr->index);
|
||||
setDebugLine(expr->index);
|
||||
|
||||
bytecode.emitABC(LOP_GETTABLEN, target, rt, i);
|
||||
}
|
||||
@ -1453,8 +1409,7 @@ struct Compiler
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
||||
if (FFlag::LuauCompileTableIndexOpt)
|
||||
setDebugLine(expr->index);
|
||||
setDebugLine(expr->index);
|
||||
|
||||
bytecode.emitABC(LOP_GETTABLEKS, target, rt, uint8_t(BytecodeBuilder::getStringHash(iname)));
|
||||
bytecode.emitAux(cid);
|
||||
@ -1853,8 +1808,7 @@ struct Compiler
|
||||
|
||||
void compileLValueUse(const LValue& lv, uint8_t reg, bool set)
|
||||
{
|
||||
if (FFlag::LuauCompileTableIndexOpt)
|
||||
setDebugLine(lv.location);
|
||||
setDebugLine(lv.location);
|
||||
|
||||
switch (lv.kind)
|
||||
{
|
||||
|
@ -193,11 +193,12 @@ if(TARGET Luau.Analyze.CLI)
|
||||
CLI/Analyze.cpp)
|
||||
endif()
|
||||
|
||||
if (TARGET Luau.Ast.CLI)
|
||||
if(TARGET Luau.Ast.CLI)
|
||||
target_sources(Luau.Ast.CLI PRIVATE
|
||||
CLI/Ast.cpp
|
||||
CLI/FileUtils.h
|
||||
CLI/FileUtils.cpp
|
||||
CLI/Ast.cpp)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.UnitTest)
|
||||
|
@ -1098,7 +1098,7 @@ static int luauF_select(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
||||
int i = int(nvalue(arg0));
|
||||
|
||||
// i >= 1 && i <= n
|
||||
if (unsigned(i - 1) <= unsigned(n))
|
||||
if (unsigned(i - 1) < unsigned(n))
|
||||
{
|
||||
setobj2s(L, res, L->base - n + (i - 1));
|
||||
return 1;
|
||||
|
@ -250,6 +250,8 @@ void luaC_validate(lua_State* L)
|
||||
|
||||
if (FFlag::LuauGcPagedSweep)
|
||||
{
|
||||
validategco(L, NULL, obj2gco(g->mainthread));
|
||||
|
||||
luaM_visitgco(L, L, validategco);
|
||||
}
|
||||
else
|
||||
@ -565,6 +567,8 @@ void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State*
|
||||
|
||||
if (FFlag::LuauGcPagedSweep)
|
||||
{
|
||||
dumpgco(f, NULL, obj2gco(g->mainthread));
|
||||
|
||||
luaM_visitgco(L, f, dumpgco);
|
||||
}
|
||||
else
|
||||
|
127
VM/src/lmem.cpp
127
VM/src/lmem.cpp
@ -8,6 +8,76 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Luau heap uses a size-segregated page structure, with individual pages and large allocations
|
||||
* allocated using system heap (via frealloc callback).
|
||||
*
|
||||
* frealloc callback serves as a general, if slow, allocation callback that can allocate, free or
|
||||
* resize allocations:
|
||||
*
|
||||
* void* frealloc(void* ud, void* ptr, size_t oldsize, size_t newsize);
|
||||
*
|
||||
* frealloc(ud, NULL, 0, x) creates a new block of size x
|
||||
* frealloc(ud, p, x, 0) frees the block p (must return NULL)
|
||||
* frealloc(ud, NULL, 0, 0) does nothing, equivalent to free(NULL)
|
||||
*
|
||||
* frealloc returns NULL if it cannot create or reallocate the area
|
||||
* (any reallocation to an equal or smaller size cannot fail!)
|
||||
*
|
||||
* On top of this, Luau implements heap storage which is split into two types of allocations:
|
||||
*
|
||||
* - GCO, short for "garbage collected objects"
|
||||
* - other objects (for example, arrays stored inside table objects)
|
||||
*
|
||||
* The heap layout for these two allocation types is a bit different.
|
||||
*
|
||||
* All GCO are allocated in pages, which is a block of memory of ~16K in size that has a page header
|
||||
* (lua_Page). Each page contains 1..N blocks of the same size, where N is selected to fill the page
|
||||
* completely. This amortizes the allocation cost and increases locality. Each GCO block starts with
|
||||
* the GC header (GCheader) which contains the object type, mark bits and other GC metadata. If the
|
||||
* GCO block is free (not used), then it must have the type set to TNIL; in this case the block can
|
||||
* be part of the per-page free list, the link for that list is stored after the header (freegcolink).
|
||||
*
|
||||
* Importantly, the GCO block doesn't have any back references to the page it's allocated in, so it's
|
||||
* impossible to free it in isolation - GCO blocks are freed by sweeping the pages they belong to,
|
||||
* using luaM_freegco which must specify the page; this is called by page sweeper that traverses the
|
||||
* entire page's worth of objects. For this reason it's also important that freed GCO blocks keep the
|
||||
* GC header intact and accessible (with type = NIL) so that the sweeper can access it.
|
||||
*
|
||||
* Some GCOs are too large to fit in a 16K page without excessive fragmentation (the size threshold is
|
||||
* currently 512 bytes); in this case, we allocate a dedicated small page with just a single block's worth
|
||||
* storage space, but that requires allocating an extra page header. In effect large GCOs are a little bit
|
||||
* less memory efficient, but this allows us to uniformly sweep small and large GCOs using page lists.
|
||||
*
|
||||
* All GCO pages are linked in a large intrusive linked list (global_State::allgcopages). Additionally,
|
||||
* for each block size there's a page free list that contains pages that have at least one free block
|
||||
* (global_State::freegcopages). This free list is used to make sure object allocation is O(1).
|
||||
*
|
||||
* Compared to GCOs, regular allocations have two important differences: they can be freed in isolation,
|
||||
* and they don't start with a GC header. Because of this, each allocation is prefixed with block metadata,
|
||||
* which contains the pointer to the page for allocated blocks, and the pointer to the next free block
|
||||
* inside the page for freed blocks.
|
||||
* For regular allocations that are too large to fit in a page (using the same threshold of 512 bytes),
|
||||
* we don't allocate a separate page, instead simply using frealloc to allocate a vanilla block of memory.
|
||||
*
|
||||
* Just like GCO pages, we store a page free list (global_State::freepages) that allows O(1) allocation;
|
||||
* there is no global list for non-GCO pages since we never need to traverse them directly.
|
||||
*
|
||||
* In both cases, we pick the page by computing the size class from the block size which rounds the block
|
||||
* size up to reduce the chance that we'll allocate pages that have very few allocated blocks. The size
|
||||
* class strategy is determined by SizeClassConfig constructor.
|
||||
*
|
||||
* Note that when the last block in a page is freed, we immediately free the page with frealloc - the
|
||||
* memory manager doesn't currently attempt to keep unused memory around. This can result in excessive
|
||||
* allocation traffic and can be mitigated by adding a page cache in the future.
|
||||
*
|
||||
* For both GCO and non-GCO pages, the per-page block allocation combines bump pointer style allocation
|
||||
* (lua_Page::freeNext) and per-page free list (lua_Page::freeList). We use the bump allocator to allocate
|
||||
* the contents of the page, and the free list for further reuse; this allows shorter page setup times
|
||||
* which results in less variance between allocation cost, as well as tighter sweep bounds for newly
|
||||
* allocated pages.
|
||||
*/
|
||||
|
||||
LUAU_FASTFLAG(LuauGcPagedSweep)
|
||||
|
||||
#ifndef __has_feature
|
||||
@ -56,6 +126,7 @@ static_assert(offsetof(GCObject, ts) == 0, "TString data must be located at the
|
||||
const size_t kSizeClasses = LUA_SIZECLASSES;
|
||||
const size_t kMaxSmallSize = 512;
|
||||
const size_t kPageSize = 16 * 1024 - 24; // slightly under 16KB since that results in less fragmentation due to heap metadata
|
||||
|
||||
const size_t kBlockHeader = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); // suitable for aligning double & void* on all platforms
|
||||
const size_t kGCOLinkOffset = (sizeof(GCheader) + sizeof(void*) - 1) & ~(sizeof(void*) - 1); // GCO pages contain freelist links after the GC header
|
||||
|
||||
@ -107,24 +178,6 @@ const SizeClassConfig kSizeClassConfig;
|
||||
#define metadata(block) (*(void**)(block))
|
||||
#define freegcolink(block) (*(void**)((char*)block + kGCOLinkOffset))
|
||||
|
||||
/*
|
||||
** About the realloc function:
|
||||
** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize);
|
||||
** (`osize' is the old size, `nsize' is the new size)
|
||||
**
|
||||
** Lua ensures that (ptr == NULL) iff (osize == 0).
|
||||
**
|
||||
** * frealloc(ud, NULL, 0, x) creates a new block of size `x'
|
||||
**
|
||||
** * frealloc(ud, p, x, 0) frees the block `p'
|
||||
** (in this specific case, frealloc must return NULL).
|
||||
** particularly, frealloc(ud, NULL, 0, 0) does nothing
|
||||
** (which is equivalent to free(NULL) in ANSI C)
|
||||
**
|
||||
** frealloc returns NULL if it cannot create or reallocate the area
|
||||
** (any reallocation to an equal or smaller size cannot fail!)
|
||||
*/
|
||||
|
||||
struct lua_Page
|
||||
{
|
||||
// list of pages with free blocks
|
||||
@ -135,13 +188,12 @@ struct lua_Page
|
||||
lua_Page* gcolistprev;
|
||||
lua_Page* gcolistnext;
|
||||
|
||||
int busyBlocks;
|
||||
int blockSize;
|
||||
int pageSize; // page size in bytes, including page header
|
||||
int blockSize; // block size in bytes, including block header (for non-GCO)
|
||||
|
||||
void* freeList;
|
||||
int freeNext;
|
||||
|
||||
int pageSize;
|
||||
void* freeList; // next free block in this page; linked with metadata()/freegcolink()
|
||||
int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead
|
||||
int busyBlocks; // number of blocks allocated out of this page
|
||||
|
||||
union
|
||||
{
|
||||
@ -177,7 +229,7 @@ static lua_Page* newpageold(lua_State* L, uint8_t sizeClass)
|
||||
page->gcolistprev = NULL;
|
||||
page->gcolistnext = NULL;
|
||||
|
||||
page->busyBlocks = 0;
|
||||
page->pageSize = kPageSize;
|
||||
page->blockSize = blockSize;
|
||||
|
||||
// note: we start with the last block in the page and move downward
|
||||
@ -185,6 +237,7 @@ static lua_Page* newpageold(lua_State* L, uint8_t sizeClass)
|
||||
// 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]);
|
||||
@ -214,7 +267,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
||||
page->gcolistprev = NULL;
|
||||
page->gcolistnext = NULL;
|
||||
|
||||
page->busyBlocks = 0;
|
||||
page->pageSize = pageSize;
|
||||
page->blockSize = blockSize;
|
||||
|
||||
// note: we start with the last block in the page and move downward
|
||||
@ -222,8 +275,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
||||
// 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->pageSize = pageSize;
|
||||
page->busyBlocks = 0;
|
||||
|
||||
if (gcopageset)
|
||||
{
|
||||
@ -406,8 +458,7 @@ static void* newgcoblock(lua_State* L, int sizeClass)
|
||||
page->next = NULL;
|
||||
}
|
||||
|
||||
// the user data is right after the metadata
|
||||
return (char*)block;
|
||||
return block;
|
||||
}
|
||||
|
||||
static void freeblock(lua_State* L, int sizeClass, void* block)
|
||||
@ -421,6 +472,7 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
|
||||
lua_Page* page = (lua_Page*)metadata(block);
|
||||
LUAU_ASSERT(page && page->busyBlocks > 0);
|
||||
LUAU_ASSERT(size_t(page->blockSize) == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
|
||||
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
||||
|
||||
// if the page wasn't in the page free list, it should be now since it got a block!
|
||||
if (!page->freeList && page->freeNext < 0)
|
||||
@ -455,6 +507,9 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
|
||||
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->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
|
||||
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
|
||||
|
||||
global_State* g = L->global;
|
||||
|
||||
@ -575,6 +630,8 @@ void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat,
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(page->busyBlocks == 1);
|
||||
LUAU_ASSERT(size_t(page->blockSize) == osize);
|
||||
LUAU_ASSERT((void*)block == page->data);
|
||||
|
||||
freepage(L, &g->allgcopages, page);
|
||||
}
|
||||
@ -626,8 +683,12 @@ void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlo
|
||||
|
||||
int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize;
|
||||
|
||||
*start = page->data + page->freeNext + page->blockSize;
|
||||
*end = page->data + blockCount * page->blockSize;
|
||||
LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize);
|
||||
|
||||
char* data = page->data; // silences ubsan when indexing page->data
|
||||
|
||||
*start = data + page->freeNext + page->blockSize;
|
||||
*end = data + blockCount * page->blockSize;
|
||||
*busyBlocks = page->busyBlocks;
|
||||
*blockSize = page->blockSize;
|
||||
}
|
||||
@ -675,7 +736,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l
|
||||
|
||||
for (lua_Page* curr = g->allgcopages; curr;)
|
||||
{
|
||||
lua_Page* next = curr->gcolistnext; // page blockvisit might destroy the page
|
||||
lua_Page* next = curr->gcolistnext; // block visit might destroy the page
|
||||
|
||||
luaM_visitpage(curr, context, visitor);
|
||||
|
||||
|
@ -131,7 +131,7 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen)
|
||||
{
|
||||
size_t l;
|
||||
source++; /* skip the `@' */
|
||||
bufflen -= sizeof(" '...' ");
|
||||
bufflen -= sizeof("...");
|
||||
l = strlen(source);
|
||||
strcpy(out, "");
|
||||
if (l > bufflen)
|
||||
@ -144,7 +144,7 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen)
|
||||
else
|
||||
{ /* out = [string "string"] */
|
||||
size_t len = strcspn(source, "\n\r"); /* stop at first newline */
|
||||
bufflen -= sizeof(" [string \"...\"] ");
|
||||
bufflen -= sizeof("[string \"...\"]");
|
||||
if (len > bufflen)
|
||||
len = bufflen;
|
||||
strcpy(out, "[string \"");
|
||||
|
@ -609,7 +609,8 @@ static void luau_execute(lua_State* L)
|
||||
|
||||
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
|
||||
{
|
||||
setnvalue(ra, rb->value.v[ic]);
|
||||
const float* v = rb->value.v; // silences ubsan when indexing v[]
|
||||
setnvalue(ra, v[ic]);
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
|
@ -605,8 +605,6 @@ RETURN R0 1
|
||||
|
||||
TEST_CASE("TableLiteralsIndexConstant")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileTableIndexOpt", true);
|
||||
|
||||
// validate that we use SETTTABLEKS for constant variable keys
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local a, b = "key", "value"
|
||||
@ -2483,8 +2481,6 @@ return
|
||||
|
||||
TEST_CASE("DebugLineInfoAssignment")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileTableIndexOpt", true);
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
|
@ -492,8 +492,6 @@ TEST_CASE("DateTime")
|
||||
|
||||
TEST_CASE("Debug")
|
||||
{
|
||||
ScopedFastFlag sffw("LuauBytecodeV2Write", true);
|
||||
|
||||
runConformance("debug.lua");
|
||||
}
|
||||
|
||||
|
@ -1392,19 +1392,31 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
|
||||
{"DataCost", {typeChecker.numberType, /* deprecated= */ true}},
|
||||
{"Wait", {typeChecker.anyType, /* deprecated= */ true}},
|
||||
};
|
||||
|
||||
TypeId colorType = typeChecker.globalTypes.addType(TableTypeVar{{}, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed});
|
||||
|
||||
getMutable<TableTypeVar>(colorType)->props = {
|
||||
{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"} }
|
||||
};
|
||||
|
||||
addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}});
|
||||
|
||||
freeze(typeChecker.globalTypes);
|
||||
|
||||
LintResult result = lintTyped(R"(
|
||||
return function (i: Instance)
|
||||
i:Wait(1.0)
|
||||
print(i.Name)
|
||||
print(Color3.toHSV())
|
||||
print(Color3.doesntexist, i.doesntexist) -- type error, but this verifies we correctly handle non-existent members
|
||||
return i.DataCost
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
REQUIRE_EQ(result.warnings.size(), 3);
|
||||
CHECK_EQ(result.warnings[0].text, "Member 'Instance.Wait' is deprecated");
|
||||
CHECK_EQ(result.warnings[1].text, "Member 'Instance.DataCost' is deprecated");
|
||||
CHECK_EQ(result.warnings[1].text, "Member 'toHSV' is deprecated, use 'Color3:ToHSV' instead");
|
||||
CHECK_EQ(result.warnings[2].text, "Member 'Instance.DataCost' is deprecated");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "TableOperations")
|
||||
@ -1475,9 +1487,11 @@ _ = (true and true) or true
|
||||
_ = (true and false) and (42 and false)
|
||||
|
||||
_ = true and true or false -- no warning since this is is a common pattern used as a ternary replacement
|
||||
|
||||
_ = if true then 1 elseif true then 2 else 3
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 7);
|
||||
REQUIRE_EQ(result.warnings.size(), 8);
|
||||
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 4);
|
||||
CHECK_EQ(result.warnings[1].text, "Condition has already been checked on column 5");
|
||||
@ -1487,6 +1501,7 @@ _ = true and true or false -- no warning since this is is a common pattern used
|
||||
CHECK_EQ(result.warnings[5].text, "Condition has already been checked on column 6");
|
||||
CHECK_EQ(result.warnings[6].text, "Condition has already been checked on column 15");
|
||||
CHECK_EQ(result.warnings[6].location.begin.line + 1, 19);
|
||||
CHECK_EQ(result.warnings[7].text, "Condition has already been checked on column 8");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
|
||||
@ -1528,4 +1543,19 @@ return foo, moo, a1, a2
|
||||
CHECK_EQ(result.warnings[3].text, "Function parameter 'self' already defined implicitly");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "MisleadingAndOr")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
_ = math.random() < 0.5 and true or 42
|
||||
_ = math.random() < 0.5 and false or 42 -- misleading
|
||||
_ = math.random() < 0.5 and nil or 42 -- misleading
|
||||
_ = math.random() < 0.5 and 0 or 42
|
||||
_ = (math.random() < 0.5 and false) or 42 -- currently ignored
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(result.warnings.size(), 2);
|
||||
CHECK_EQ(result.warnings[0].text, "The and-or expression always evaluates to the second alternative because the first alternative is false; consider using if-then-else expression instead");
|
||||
CHECK_EQ(result.warnings[1].text, "The and-or expression always evaluates to the second alternative because the first alternative is nil; consider using if-then-else expression instead");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -8,9 +8,22 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Completion
|
||||
{
|
||||
std::string completion;
|
||||
std::string display;
|
||||
|
||||
bool operator<(Completion const& other) const
|
||||
{
|
||||
return std::tie(completion, display) < std::tie(other.completion, other.display);
|
||||
}
|
||||
};
|
||||
|
||||
using CompletionSet = std::set<Completion>;
|
||||
|
||||
class ReplFixture
|
||||
{
|
||||
@ -34,6 +47,27 @@ public:
|
||||
lua_pop(L, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
CompletionSet getCompletionSet(const char* inputPrefix)
|
||||
{
|
||||
CompletionSet result;
|
||||
int top = lua_gettop(L);
|
||||
getCompletions(L, inputPrefix, [&result](const std::string& completion, const std::string& display) {
|
||||
result.insert(Completion{completion, display});
|
||||
});
|
||||
// Ensure that generating completions doesn't change the position of luau's stack top.
|
||||
CHECK(top == lua_gettop(L));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool checkCompletion(const CompletionSet& completions, const std::string& prefix, const std::string& expected)
|
||||
{
|
||||
std::string expectedDisplay(expected.substr(0, expected.find_first_of('(')));
|
||||
Completion expectedCompletion{prefix + expected, expectedDisplay};
|
||||
return completions.count(expectedCompletion) == 1;
|
||||
}
|
||||
|
||||
lua_State* L;
|
||||
|
||||
private:
|
||||
@ -115,3 +149,61 @@ TEST_CASE_FIXTURE(ReplFixture, "MultipleArguments")
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("ReplCodeCompletion");
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables")
|
||||
{
|
||||
runCode(L, R"(
|
||||
myvariable1 = 5
|
||||
myvariable2 = 5
|
||||
)");
|
||||
CompletionSet completions = getCompletionSet("myvar");
|
||||
|
||||
std::string prefix = "";
|
||||
CHECK(completions.size() == 2);
|
||||
CHECK(checkCompletion(completions, prefix, "myvariable1"));
|
||||
CHECK(checkCompletion(completions, prefix, "myvariable2"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "CompleteTableKeys")
|
||||
{
|
||||
runCode(L, R"(
|
||||
t = { color = "red", size = 1, shape = "circle" }
|
||||
)");
|
||||
{
|
||||
CompletionSet completions = getCompletionSet("t.");
|
||||
|
||||
std::string prefix = "t.";
|
||||
CHECK(completions.size() == 3);
|
||||
CHECK(checkCompletion(completions, prefix, "color"));
|
||||
CHECK(checkCompletion(completions, prefix, "size"));
|
||||
CHECK(checkCompletion(completions, prefix, "shape"));
|
||||
}
|
||||
|
||||
{
|
||||
CompletionSet completions = getCompletionSet("t.s");
|
||||
|
||||
std::string prefix = "t.";
|
||||
CHECK(completions.size() == 2);
|
||||
CHECK(checkCompletion(completions, prefix, "size"));
|
||||
CHECK(checkCompletion(completions, prefix, "shape"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "StringMethods")
|
||||
{
|
||||
runCode(L, R"(
|
||||
s = ""
|
||||
)");
|
||||
{
|
||||
CompletionSet completions = getCompletionSet("s:l");
|
||||
|
||||
std::string prefix = "s:";
|
||||
CHECK(completions.size() == 2);
|
||||
CHECK(checkCompletion(completions, prefix, "len("));
|
||||
CHECK(checkCompletion(completions, prefix, "lower("));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -595,4 +595,65 @@ TEST_CASE_FIXTURE(Fixture, "generic_typevars_are_not_considered_to_escape_their_
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* The two-pass alias definition system starts by ascribing a free TypeVar to each alias. It then
|
||||
* circles back to fill in the actual type later on.
|
||||
*
|
||||
* If this free type is unified with something degenerate like `any`, we need to take extra care
|
||||
* to ensure that the alias actually binds to the type that the user expected.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauTwoPassAliasDefinitionFix", true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function x()
|
||||
local y: FutureType = {}::any
|
||||
return 1
|
||||
end
|
||||
type FutureType = { foo: typeof(x()) }
|
||||
local d: FutureType = { smth = true } -- missing error, 'd' is resolved to 'any'
|
||||
)");
|
||||
|
||||
CHECK_EQ("{| foo: number |}", toString(requireType("d"), {true}));
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauTwoPassAliasDefinitionFix", true},
|
||||
|
||||
// We also force these two flags because this surfaced an unfortunate interaction.
|
||||
{"LuauErrorRecoveryType", true},
|
||||
{"LuauQuantifyInPlace2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local B = {}
|
||||
B.bar = 4
|
||||
|
||||
function B:smth1()
|
||||
local self: FutureIntersection = self
|
||||
self.foo = 4
|
||||
return 4
|
||||
end
|
||||
|
||||
function B:smth2()
|
||||
local self: FutureIntersection = self
|
||||
self.bar = 5 -- error, even though we should have B part with bar
|
||||
end
|
||||
|
||||
type A = { foo: typeof(B.smth1({foo=3})) } -- trick toposort into sorting functions before types
|
||||
type B = typeof(B)
|
||||
|
||||
type FutureIntersection = A & B
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauFixTonumberReturnType)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
||||
@ -850,11 +848,8 @@ TEST_CASE_FIXTURE(Fixture, "tonumber_returns_optional_number_type")
|
||||
local b: number = tonumber('asdf')
|
||||
)");
|
||||
|
||||
if (FFlag::LuauFixTonumberReturnType)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
|
||||
}
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "tonumber_returns_optional_number_type2")
|
||||
@ -893,7 +888,7 @@ TEST_CASE_FIXTURE(Fixture, "assert_removes_falsy_types")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -910,7 +905,7 @@ TEST_CASE_FIXTURE(Fixture, "assert_removes_falsy_types_even_from_type_pack_tail_
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -927,7 +922,7 @@ TEST_CASE_FIXTURE(Fixture, "assert_returns_false_and_string_iff_it_knows_the_fir
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -262,7 +262,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
|
||||
// Just needs to fully support equality refinement. Which is annoying without type states.
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDiscriminableUnions", true};
|
||||
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = {x: string, y: number} | {x: nil, y: nil}
|
||||
@ -616,4 +616,76 @@ local a: Self<Table>
|
||||
CHECK_EQ(toString(requireType("a")), "Table<Table>");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_ice_when_trying_to_pick_first_of_generic_type_pack")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauQuantifyInPlace2", true},
|
||||
{"LuauReturnAnyInsteadOfICE", true},
|
||||
};
|
||||
|
||||
// In-place quantification causes these types to have the wrong types but only because of nasty interaction with prototyping.
|
||||
// The type of f is initially () -> free1...
|
||||
// Then the prototype iterator advances, and checks the function expression assigned to g, which has the type () -> free2...
|
||||
// In the body it calls f and returns what f() returns. This binds free2... with free1..., causing f and g to have same types.
|
||||
// We then quantify g, leaving it with the final type <a...>() -> a...
|
||||
// Because free1... and free2... were bound, in combination with in-place quantification, f's return type was also turned into a...
|
||||
// Then the check iterator catches up, and checks the body of f, and attempts to quantify it too.
|
||||
// Alas, one of the requirements for quantification is that a type must contain free types. () -> a... has no free types.
|
||||
// Thus the quantification for f was no-op, which explains why f does not have any type parameters.
|
||||
// Calling f() will attempt to instantiate the function type, which turns generics in type binders into to free types.
|
||||
// However, instantiations only converts generics contained within the type binders of a function, so instantiation was also no-op.
|
||||
// Which means that calling f() simply returned a... rather than an instantiation of it. And since the call site was not in tail position,
|
||||
// picking first element in a... triggers an ICE because calls returning generic packs are unexpected.
|
||||
CheckResult result = check(R"(
|
||||
local function f() end
|
||||
|
||||
local g = function() return f() end
|
||||
|
||||
local x = (f()) -- should error: no return values to assign from the call to f
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// f and g should have the type () -> ()
|
||||
CHECK_EQ("() -> (a...)", toString(requireType("f")));
|
||||
CHECK_EQ("<a...>() -> (a...)", toString(requireType("g")));
|
||||
CHECK_EQ("any", toString(requireType("x"))); // any is returned instead of ICE for now
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id(x) return x end
|
||||
local n2n: (number) -> number = id
|
||||
local s2s: (string) -> string = id
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f() return end
|
||||
local g = function() return f() end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauQuantifyInPlace2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function f(...) return ... end
|
||||
local g = function(...) return f(...) end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result); // Should not have any errors.
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDiscriminableUnions)
|
||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||
LUAU_FASTFLAG(LuauWeakEqConstraint)
|
||||
LUAU_FASTFLAG(LuauQuantifyInPlace2)
|
||||
|
||||
@ -262,7 +262,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
@ -435,7 +435,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
||||
TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
|
||||
@ -485,7 +485,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauDiscriminableUnions", true};
|
||||
ScopedFastFlag sff{"LuauDiscriminableUnions2", true};
|
||||
ScopedFastFlag sff2{"LuauWeakEqConstraint", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -589,7 +589,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
@ -1002,7 +1002,7 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauParseSingletonTypes", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
@ -1028,7 +1028,7 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauParseSingletonTypes", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
@ -1069,7 +1069,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false")
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauParseSingletonTypes", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
};
|
||||
|
||||
@ -1094,7 +1094,7 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauParseSingletonTypes", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
};
|
||||
|
||||
@ -1118,7 +1118,7 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauDiscriminableUnions", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauParseSingletonTypes", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
@ -1157,7 +1157,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauDiscriminableUnions)
|
||||
if (FFlag::LuauDiscriminableUnions2)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
{
|
||||
|
@ -5164,4 +5164,151 @@ function x:Destroy(): () end
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_2")
|
||||
{
|
||||
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Type = { x: { a: number } }
|
||||
return {}
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local types = require(game.A)
|
||||
type Type = types.Type
|
||||
local x: Type = { x = { a = 2 } }
|
||||
type Rename = typeof(x.x)
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types_3")
|
||||
{
|
||||
ScopedFastFlag immutableTypes{"LuauImmutableTypes", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local y = setmetatable({}, {})
|
||||
export type Type = { x: typeof(y) }
|
||||
return { x = y }
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local types = require(game.A)
|
||||
type Type = types.Type
|
||||
local x: Type = types
|
||||
type Rename = typeof(x.x)
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauRefactorTypeVarQuestions", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string = "hi"
|
||||
if a == "hi" then
|
||||
local x = a:byte()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 22})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauRefactorTypeVarQuestions", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string = "hi"
|
||||
if a == "hi" or a == "bye" then
|
||||
local x = a:byte()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"("bye" | "hi")", toString(requireTypeAtPosition({3, 22})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauRefactorTypeVarQuestions", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
{"LuauLengthOnCompositeType", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string = "hi"
|
||||
if a == "hi" then
|
||||
local x = #a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauRefactorTypeVarQuestions", true},
|
||||
{"LuauSingletonTypes", true},
|
||||
{"LuauLengthOnCompositeType", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string = "hi"
|
||||
if a == "hi" or a == "bye" then
|
||||
local x = #a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"("bye" | "hi")", toString(requireTypeAtPosition({3, 23})));
|
||||
}
|
||||
|
||||
/*
|
||||
* When we add new properties to an unsealed table, we should do a level check and promote the property type to be at
|
||||
* the level of the table.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "inferred_properties_of_a_table_should_start_with_the_same_TypeLevel_of_that_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local T = {}
|
||||
|
||||
local function f(prop)
|
||||
T[1] = {
|
||||
prop = prop,
|
||||
}
|
||||
end
|
||||
|
||||
local function g()
|
||||
local l = T[1].prop
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -273,4 +273,21 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
||||
state.tryUnify(&metatable, &variant);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "cli_50320_follow_in_any_unification")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauUseCommittingTxnLog", true},
|
||||
{"LuauFollowWithCommittingTxnLogInAnyUnification", true},
|
||||
};
|
||||
|
||||
TypePackVar free{FreeTypePack{TypeLevel{}}};
|
||||
TypePackVar target{TypePack{}};
|
||||
|
||||
TypeVar func{FunctionTypeVar{&free, &free}};
|
||||
|
||||
state.tryUnify(&free, &target);
|
||||
// Shouldn't assert or error.
|
||||
state.tryUnify(&func, typeChecker.anyType);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -118,9 +118,7 @@ assert((function() return #_G end)() == 0)
|
||||
assert((function() return #{1,2} end)() == 2)
|
||||
assert((function() return #'g' end)() == 1)
|
||||
|
||||
local ud = newproxy(true)
|
||||
getmetatable(ud).__len = function() return 42 end
|
||||
assert((function() return #ud end)() == 42)
|
||||
assert((function() local ud = newproxy(true) getmetatable(ud).__len = function() return 42 end return #ud end)() == 42)
|
||||
|
||||
assert((function() local a = 1 a = -a return a end)() == -1)
|
||||
|
||||
@ -325,6 +323,10 @@ assert((function() local t = {6, 9, 7} t[4.5] = 10 return t[4.5] end)() == 10)
|
||||
assert((function() local t = {6, 9, 7} t['a'] = 11 return t['a'] end)() == 11)
|
||||
assert((function() local t = {6, 9, 7} setmetatable(t, { __newindex = function(t,i,v) rawset(t, i * 10, v) end }) t[1] = 17 t[5] = 1 return concat(t[1],t[5],t[50]) end)() == "17,nil,1")
|
||||
|
||||
-- userdata access
|
||||
assert((function() local ud = newproxy(true) getmetatable(ud).__index = function(ud,i) return i * 10 end return ud[2] end)() == 20)
|
||||
assert((function() local ud = newproxy(true) getmetatable(ud).__index = function() return function(self, i) return i * 10 end end return ud:meow(2) end)() == 20)
|
||||
|
||||
-- and/or
|
||||
-- rhs is a constant
|
||||
assert((function() local a = 1 a = a and 2 return a end)() == 2)
|
||||
@ -462,7 +464,7 @@ assert((function() a = {} b = {} mt = { __eq = function(l, r) return #l == #r en
|
||||
|
||||
-- metatable ops
|
||||
local function vec3t(x, y, z)
|
||||
return setmetatable({ x=x, y=y, z=z}, {
|
||||
return setmetatable({x=x, y=y, z=z}, {
|
||||
__add = function(l, r) return vec3t(l.x + r.x, l.y + r.y, l.z + r.z) end,
|
||||
__sub = function(l, r) return vec3t(l.x - r.x, l.y - r.y, l.z - r.z) end,
|
||||
__mul = function(l, r) return type(r) == "number" and vec3t(l.x * r, l.y * r, l.z * r) or vec3t(l.x * r.x, l.y * r.y, l.z * r.z) end,
|
||||
|
@ -37,6 +37,7 @@ coroutine.resume(co2, 0 / 0, 42)
|
||||
|
||||
assert(debug.traceback(co2) == "debug.lua:31 function halp\n")
|
||||
assert(debug.info(co2, 0, "l") == 31)
|
||||
assert(debug.info(co2, 0, "f") == halp)
|
||||
|
||||
-- info errors
|
||||
function qux(...)
|
||||
|
@ -260,8 +260,7 @@ local a,b = loadstring(s)
|
||||
assert(not a)
|
||||
--assert(string.find(b, "line 2"))
|
||||
|
||||
-- Test for CLI-28786
|
||||
-- The xpcall is intentially going to cause an exception
|
||||
-- The xpcall is intentionally going to cause an exception
|
||||
-- followed by a forced exception in the error handler.
|
||||
-- If the secondary handler isn't trapped, it will cause
|
||||
-- the unit test to fail. If the xpcall captures the
|
||||
@ -281,6 +280,19 @@ coroutine.wrap(function()
|
||||
assert(not pcall(debug.getinfo, coroutine.running(), 0, ">"))
|
||||
end)()
|
||||
|
||||
-- loadstring chunk truncation
|
||||
local a,b = loadstring("nope", "@short")
|
||||
assert(not a and b:match('[^ ]+') == "short:1:")
|
||||
|
||||
local a,b = loadstring("nope", "@" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
|
||||
assert(not a and b:match('[^ ]+') == "...wontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities:1:")
|
||||
|
||||
local a,b = loadstring("nope", "=short")
|
||||
assert(not a and b:match('[^ ]+') == "short:1:")
|
||||
|
||||
local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
|
||||
assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:")
|
||||
|
||||
-- arith errors
|
||||
function ecall(fn, ...)
|
||||
local ok, err = pcall(fn, ...)
|
||||
|
@ -180,6 +180,11 @@ x,y,z=nil
|
||||
collectgarbage()
|
||||
assert(next(a) == string.rep('$', 11))
|
||||
|
||||
-- shrinking tables reduce their capacity; confirming the shrinking is difficult but we can at least test the surface level behavior
|
||||
a = {}; setmetatable(a, {__mode = 'ks'})
|
||||
for i=1,lim do a[{}] = i end
|
||||
collectgarbage()
|
||||
assert(next(a) == nil)
|
||||
|
||||
-- testing userdata
|
||||
collectgarbage("stop") -- stop collection
|
||||
@ -315,8 +320,6 @@ do
|
||||
end
|
||||
|
||||
collectgarbage()
|
||||
|
||||
end
|
||||
|
||||
|
||||
return('OK')
|
||||
|
@ -289,6 +289,7 @@ assert(math.sqrt("4") == 2)
|
||||
assert(math.tanh("0") == 0)
|
||||
assert(math.tan("0") == 0)
|
||||
assert(math.clamp("0", 2, 3) == 2)
|
||||
assert(math.clamp("4", 2, 3) == 3)
|
||||
assert(math.sign("2") == 1)
|
||||
assert(math.sign("-2") == -1)
|
||||
assert(math.sign("0") == 0)
|
||||
|
@ -139,6 +139,12 @@ assert(selectmany(1, 10, 20, 30) == "10,20,30")
|
||||
assert(selectone(2, 10, 20, 30) == 20)
|
||||
assert(selectmany(2, 10, 20, 30) == "20,30")
|
||||
|
||||
assert(selectone(3, 10, 20, 30) == 30)
|
||||
assert(selectmany(3, 10, 20, 30) == "30")
|
||||
|
||||
assert(selectone(4, 10, 20, 30) == nil)
|
||||
assert(selectmany(4, 10, 20, 30) == "")
|
||||
|
||||
assert(selectone(-2, 10, 20, 30) == 20)
|
||||
assert(selectmany(-2, 10, 20, 30) == "20,30")
|
||||
|
||||
|
@ -87,9 +87,18 @@ assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == fal
|
||||
-- make sure we cover both builtin and C impl
|
||||
assert(vector(1, 2, 4) == vector("1", "2", "4"))
|
||||
|
||||
-- validate component access (both cases)
|
||||
assert(vector(1, 2, 3).x == 1)
|
||||
assert(vector(1, 2, 3).X == 1)
|
||||
assert(vector(1, 2, 3).y == 2)
|
||||
assert(vector(1, 2, 3).Y == 2)
|
||||
assert(vector(1, 2, 3).z == 3)
|
||||
assert(vector(1, 2, 3).Z == 3)
|
||||
|
||||
-- additional checks for 4-component vectors
|
||||
if vector_size == 4 then
|
||||
assert(vector(1, 2, 3, 4).w == 4)
|
||||
assert(vector(1, 2, 3, 4).W == 4)
|
||||
end
|
||||
|
||||
return 'OK'
|
||||
|
@ -7,10 +7,20 @@
|
||||
# The result of analysis is a .svg file which can be viewed in a browser
|
||||
# To generate these dumps, use luaC_dump, ideally preceded by luaC_fullgc
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import svg
|
||||
|
||||
argumentParser = argparse.ArgumentParser(description='Luau heap snapshot analyzer')
|
||||
|
||||
argumentParser.add_argument('--split', dest = 'split', type = str, default = 'none', help = 'Perform additional root split using memory categories', choices = ['none', 'custom', 'all'])
|
||||
|
||||
argumentParser.add_argument('snapshot')
|
||||
argumentParser.add_argument('snapshotnew', nargs='?')
|
||||
|
||||
arguments = argumentParser.parse_args()
|
||||
|
||||
class Node(svg.Node):
|
||||
def __init__(self):
|
||||
svg.Node.__init__(self)
|
||||
@ -30,14 +40,14 @@ class Node(svg.Node):
|
||||
return "{} ({:,} bytes, {:.1%}); self: {:,} bytes in {:,} objects".format(self.name, self.width, self.width / root.width, self.size, self.count)
|
||||
|
||||
# load files
|
||||
if len(sys.argv) == 2:
|
||||
if arguments.snapshotnew == None:
|
||||
dumpold = None
|
||||
with open(sys.argv[1]) as f:
|
||||
with open(arguments.snapshot) as f:
|
||||
dump = json.load(f)
|
||||
else:
|
||||
with open(sys.argv[1]) as f:
|
||||
with open(arguments.snapshot) as f:
|
||||
dumpold = json.load(f)
|
||||
with open(sys.argv[2]) as f:
|
||||
with open(arguments.snapshotnew) as f:
|
||||
dump = json.load(f)
|
||||
|
||||
# reachability analysis: how much of the heap is reachable from roots?
|
||||
@ -111,12 +121,15 @@ while offset < len(queue):
|
||||
if "object" in obj:
|
||||
queue.append((obj["object"], node))
|
||||
|
||||
def annotateContainedCategories(node):
|
||||
def annotateContainedCategories(node, start):
|
||||
for obj in node.objects:
|
||||
if obj["cat"] < start:
|
||||
obj["cat"] = 0
|
||||
|
||||
node.categories.add(obj["cat"])
|
||||
|
||||
for child in node.children.values():
|
||||
annotateContainedCategories(child)
|
||||
annotateContainedCategories(child, start)
|
||||
|
||||
for cat in child.categories:
|
||||
node.categories.add(cat)
|
||||
@ -172,9 +185,11 @@ def splitIntoCategories(root):
|
||||
|
||||
return result
|
||||
|
||||
# temporarily disabled because it makes FG harder to read, maybe this should be a separate command line option?
|
||||
if dump["stats"].get("categories") and False:
|
||||
annotateContainedCategories(root)
|
||||
if dump["stats"].get("categories") and arguments.split != 'none':
|
||||
if arguments.split == 'custom':
|
||||
annotateContainedCategories(root, 128)
|
||||
else:
|
||||
annotateContainedCategories(root, 0)
|
||||
|
||||
root = splitIntoCategories(root)
|
||||
|
||||
|
@ -452,7 +452,7 @@ def display(root, title, colors, flip = False):
|
||||
.replace("$gradient-start", gradient_start)
|
||||
.replace("$gradient-end", gradient_end)
|
||||
.replace("$height", str(svgheight))
|
||||
.replace("$status", str(svgheight - 16 + 3))
|
||||
.replace("$status", str((svgheight - 16 + 3 if flip else 3 * 16 - 3)))
|
||||
.replace("$flip", str(int(flip)))
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user