Sync to upstream/release/609 (#1150)

### What's changed?
* Syntax for [read-only and write-only
properties](https://github.com/luau-lang/rfcs/pull/15) is now parsed,
but is not yet supported in typechecking

### New Type Solver
* `keyof` and `rawkeyof` type operators have been updated to match final
text of the [RFC](https://github.com/luau-lang/rfcs/pull/16)
* Fixed issues with cyclic type families that were generated for mutable
loop variables

### Native Code Generation
* Fixed inference for number / vector operation that caused an
unnecessary VM assist

---
### Internal Contributors
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
vegorov-rbx 2024-01-19 10:04:46 -08:00 committed by GitHub
parent 73360e5399
commit cdd1a380db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 575 additions and 227 deletions

View file

@ -241,8 +241,8 @@ private:
* Generate constraints to assign assignedTy to the expression expr
* @returns the type of the expression. This may or may not be assignedTy itself.
*/
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy);

View file

@ -235,6 +235,7 @@ public:
FrontendOptions options;
InternalErrorReporter iceHandler;
std::function<void(const ModuleName& name, const ScopePtr& scope, bool forAutocomplete)> prepareModuleScope;
std::function<void(const ModuleName& name, std::string log)> writeJsonLog = {};
std::unordered_map<ModuleName, std::shared_ptr<SourceNode>> sourceNodes;
std::unordered_map<ModuleName, std::shared_ptr<SourceModule>> sourceModules;
@ -253,6 +254,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits, bool recordJsonLog);
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog);
} // namespace Luau

View file

@ -1008,12 +1008,54 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
std::vector<TypeId> assignees;
assignees.reserve(assign->vars.size);
size_t i = 0;
for (AstExpr* lvalue : assign->vars)
{
TypeId assignee = arena->addType(BlockedType{});
checkLValue(scope, lvalue, assignee);
// This is a really weird thing to do, but it's critically important for some kinds of
// assignments with the current type state behavior. Consider this code:
// local function f(l, r)
// local i = l
// for _ = l, r do
// i = i + 1
// end
// end
//
// With type states now, we will not create a new state for `i` within the loop. This means
// that, in the absence of the analysis below, we would infer a too-broad bound for i: the
// cyclic type t1 where t1 = add<t1 | number, number>. In order to stop this, we say that
// assignments to a definition with a self-referential binary expression do not transform
// the type of the definition. This will only apply for loops, where the definition is
// shared in more places; for non-loops, there will be a separate DefId for the lvalue in
// the assignment, so we will deem the expression to be transformative.
//
// Deeming the addition in the code sample above as non-transformative means that i is known
// to be exactly number further on, ensuring the type family reduces down to number, as is
// expected for this code snippet.
//
// There is a potential for spurious errors here if the expression is more complex than a
// simple binary expression, e.g. i = (i + 1) * 2. At the time of writing, this case hasn't
// materialized.
bool transform = true;
if (assign->values.size > i)
{
AstExpr* value = assign->values.data[i];
if (auto bexp = value->as<AstExprBinary>())
{
DefId lvalueDef = dfg->getDef(lvalue);
DefId lDef = dfg->getDef(bexp->left);
DefId rDef = dfg->getDef(bexp->right);
if (lvalueDef == lDef || lvalueDef == rDef)
transform = false;
}
}
checkLValue(scope, lvalue, assignee, transform);
assignees.push_back(assignee);
++i;
}
TypePackId resultPack = checkPack(scope, assign->values).tp;
@ -1027,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
TypeId resultTy = check(scope, &binop).ty;
checkLValue(scope, assign->var, resultTy);
checkLValue(scope, assign->var, resultTy, true);
DefId def = dfg->getDef(assign->var);
scope->lvalueTypes[def] = resultTy;
@ -2210,10 +2252,10 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
}
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy)
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform)
{
if (auto local = expr->as<AstExprLocal>())
return checkLValue(scope, local, assignedTy);
return checkLValue(scope, local, assignedTy, transform);
else if (auto global = expr->as<AstExprGlobal>())
return checkLValue(scope, global, assignedTy);
else if (auto indexName = expr->as<AstExprIndexName>())
@ -2229,7 +2271,7 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
ice->ice("checkLValue is inexhaustive");
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy)
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform)
{
std::optional<TypeId> annotatedTy = scope->lookup(local->local);
LUAU_ASSERT(annotatedTy);
@ -2241,8 +2283,11 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
if (ty)
{
if (auto lt = getMutable<LocalType>(*ty))
++lt->blockCount;
if (transform)
{
if (auto lt = getMutable<LocalType>(*ty))
++lt->blockCount;
}
}
else
{
@ -2251,13 +2296,17 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
scope->lvalueTypes[defId] = *ty;
}
addConstraint(scope, local->location, UnpackConstraint{
arena->addTypePack({*ty}),
arena->addTypePack({assignedTy}),
/*resultIsLValue*/ true
});
if (transform)
{
addConstraint(scope, local->location, UnpackConstraint{
arena->addTypePack({*ty}),
arena->addTypePack({assignedTy}),
/*resultIsLValue*/ true
});
recordInferredBinding(local->local, *ty);
}
recordInferredBinding(local->local, *ty);
return ty;
}
@ -2821,20 +2870,38 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
for (const AstTableProp& prop : tab->props)
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
// TODO: Fill in location.
props[name] = {propTy};
if (prop.access == AstTableAccess::Read)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (prop.access == AstTableAccess::Write)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (prop.access == AstTableAccess::ReadWrite)
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
props[name] = {propTy};
props[name].typeLocation = prop.location;
}
else
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
}
if (tab->indexer)
if (AstTableIndexer* astIndexer = tab->indexer)
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, tab->indexer->indexType, inTypeArguments),
resolveType(scope, tab->indexer->resultType, inTypeArguments),
};
if (astIndexer->access == AstTableAccess::Read)
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (astIndexer->access == AstTableAccess::Write)
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (astIndexer->access == AstTableAccess::ReadWrite)
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, astIndexer->indexType, inTypeArguments),
resolveType(scope, astIndexer->resultType, inTypeArguments),
};
}
else
ice->ice("Unexpected property access " + std::to_string(int(astIndexer->access)));
}
result = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
@ -3174,11 +3241,16 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
const auto& [scope, location, types] = p;
std::vector<TypeId> tys(types.begin(), types.end());
if (tys.size() == 1)
scope->bindings[symbol] = Binding{tys.front(), location};
else
{
TypeId ty = arena->addType(BlockedType{});
addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});
TypeId ty = arena->addType(BlockedType{});
addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});
scope->bindings[symbol] = Binding{ty, location};
}
scope->bindings[symbol] = Binding{ty, location};
}
}

View file

@ -1,47 +1,13 @@
// 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(LuauBufferTypeck, false)
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions, false);
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions2, false);
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
namespace Luau
{
static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type
declare class buffer end
declare buffer: {
create: (size: number) -> buffer,
fromstring: (str: string) -> buffer,
tostring: () -> string,
len: (b: buffer) -> number,
copy: (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: (b: buffer, offset: number) -> number,
readu8: (b: buffer, offset: number) -> number,
readi16: (b: buffer, offset: number) -> number,
readu16: (b: buffer, offset: number) -> number,
readi32: (b: buffer, offset: number) -> number,
readu32: (b: buffer, offset: number) -> number,
readf32: (b: buffer, offset: number) -> number,
readf64: (b: buffer, offset: number) -> number,
writei8: (b: buffer, offset: number, value: number) -> (),
writeu8: (b: buffer, offset: number, value: number) -> (),
writei16: (b: buffer, offset: number, value: number) -> (),
writeu16: (b: buffer, offset: number, value: number) -> (),
writei32: (b: buffer, offset: number, value: number) -> (),
writeu32: (b: buffer, offset: number, value: number) -> (),
writef32: (b: buffer, offset: number, value: number) -> (),
writef64: (b: buffer, offset: number, value: number) -> (),
readstring: (b: buffer, offset: number, count: number) -> string,
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare buffer: {
create: (size: number) -> buffer,
@ -70,9 +36,6 @@ declare buffer: {
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare bit32: {
band: (...number) -> number,
bor: (...number) -> number,
@ -488,12 +451,8 @@ std::string getBuiltinDefinitionSource()
{
std::string result = kBuiltinDefinitionLuaSrc;
if (FFlag::LuauBufferTypeck)
result = kBuiltinDefinitionBufferSrc + result;
else
result = kBuiltinDefinitionBufferSrc_DEPRECATED + result;
// Annotates each non generic function as checked
if (FFlag::LuauCheckedEmbeddedDefinitions)
if (FFlag::LuauCheckedEmbeddedDefinitions2 && FFlag::LuauCheckedFunctionSyntax)
result = kBuiltinDefinitionLuaSrcChecked;
return result;

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
namespace Luau
{
@ -872,6 +873,7 @@ void Frontend::addBuildQueueItems(std::vector<BuildQueueItem>& items, std::vecto
data.config = configResolver->getConfig(moduleName);
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
Mode mode = sourceModule->mode.value_or(data.config.mode);
@ -1169,17 +1171,17 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits)
TypeCheckLimits limits, std::function<void(const ModuleName&, std::string)> writeJsonLog)
{
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
return check(sourceModule, mode, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope,
std::move(prepareModuleScope), options, limits, recordJsonLog);
std::move(prepareModuleScope), options, limits, recordJsonLog, writeJsonLog);
}
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits, bool recordJsonLog)
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog)
{
ModulePtr result = std::make_shared<Module>();
result->name = sourceModule.name;
@ -1281,7 +1283,10 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
if (recordJsonLog)
{
std::string output = logger->compileOutput();
printf("%s\n", output.c_str());
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
writeJsonLog(sourceModule.name, std::move(output));
else
printf("%s\n", output.c_str());
}
return result;
@ -1301,7 +1306,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
{
return Luau::check(sourceModule, mode, requireCycles, builtinTypes, NotNull{&iceHandler},
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog);
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog, writeJsonLog);
}
catch (const InternalCompilerError& err)
{

View file

@ -2,8 +2,6 @@
#include "Luau/GlobalTypes.h"
LUAU_FASTFLAG(LuauBufferTypeck)
namespace Luau
{
@ -18,8 +16,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType});
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType});
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType});
if (FFlag::LuauBufferTypeck)
globalScope->addBuiltinTypeBinding("buffer", TypeFun{{}, builtinTypes->bufferType});
globalScope->addBuiltinTypeBinding("buffer", TypeFun{{}, builtinTypes->bufferType});
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});

View file

@ -14,8 +14,6 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(LuauBufferTypeck)
namespace Luau
{
@ -1107,7 +1105,7 @@ private:
TypeKind getTypeKind(const std::string& name)
{
if (name == "nil" || name == "boolean" || name == "userdata" || name == "number" || name == "string" || name == "table" ||
name == "function" || name == "thread" || (FFlag::LuauBufferTypeck && name == "buffer"))
name == "function" || name == "thread" || name == "buffer")
return Kind_Primitive;
if (name == "vector")

View file

@ -22,7 +22,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAG(LuauTransitiveSubtyping)
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauBufferTypeck)
namespace Luau
{
@ -312,13 +311,13 @@ bool NormalizedType::isUnknown() const
bool NormalizedType::isExactlyNumber() const
{
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
(!FFlag::LuauBufferTypeck || !hasBuffers()) && !hasTables() && !hasFunctions() && !hasTyvars();
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfString() const
{
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
(!FFlag::LuauBufferTypeck || !hasBuffers()) && !hasTables() && !hasFunctions() && !hasTyvars();
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::shouldSuppressErrors() const
@ -377,7 +376,6 @@ bool NormalizedType::hasThreads() const
bool NormalizedType::hasBuffers() const
{
LUAU_ASSERT(FFlag::LuauBufferTypeck);
return !get<NeverType>(buffers);
}
@ -401,7 +399,7 @@ static bool isShallowInhabited(const NormalizedType& norm)
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
(FFlag::LuauBufferTypeck && !get<NeverType>(norm.buffers)) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
bool Normalizer::isInhabited(const NormalizedType* norm, Set<TypeId> seen)
@ -411,8 +409,8 @@ bool Normalizer::isInhabited(const NormalizedType* norm, Set<TypeId> seen)
return true;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || (FFlag::LuauBufferTypeck && !get<NeverType>(norm->buffers)) ||
!norm->classes.isNever() || !norm->strings.isNever() || !norm->functions.isNever())
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever())
return true;
for (const auto& [_, intersect] : norm->tyvars)
@ -638,8 +636,6 @@ static bool isNormalizedThread(TypeId ty)
static bool isNormalizedBuffer(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauBufferTypeck);
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
@ -768,8 +764,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
LUAU_ASSERT(isNormalizedString(norm.strings));
LUAU_ASSERT(isNormalizedThread(norm.threads));
if (FFlag::LuauBufferTypeck)
LUAU_ASSERT(isNormalizedBuffer(norm.buffers));
LUAU_ASSERT(isNormalizedBuffer(norm.buffers));
LUAU_ASSERT(areNormalizedFunctions(norm.functions));
LUAU_ASSERT(areNormalizedTables(norm.tables));
LUAU_ASSERT(isNormalizedTyvar(norm.tyvars));
@ -840,8 +835,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
norm.numbers = builtinTypes->neverType;
norm.strings.resetToNever();
norm.threads = builtinTypes->neverType;
if (FFlag::LuauBufferTypeck)
norm.buffers = builtinTypes->neverType;
norm.buffers = builtinTypes->neverType;
norm.tables.clear();
norm.functions.resetToNever();
norm.tyvars.clear();
@ -1527,8 +1521,7 @@ bool Normalizer::unionNormals(NormalizedType& here, const NormalizedType& there,
here.numbers = (get<NeverType>(there.numbers) ? here.numbers : there.numbers);
unionStrings(here.strings, there.strings);
here.threads = (get<NeverType>(there.threads) ? here.threads : there.threads);
if (FFlag::LuauBufferTypeck)
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables);
return true;
@ -1649,7 +1642,7 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, Set<TypeI
here.strings.resetToString();
else if (ptv->type == PrimitiveType::Thread)
here.threads = there;
else if (FFlag::LuauBufferTypeck && ptv->type == PrimitiveType::Buffer)
else if (ptv->type == PrimitiveType::Buffer)
here.buffers = there;
else if (ptv->type == PrimitiveType::Function)
{
@ -1770,8 +1763,7 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.strings.isCofinite = !result.strings.isCofinite;
result.threads = get<NeverType>(here.threads) ? builtinTypes->threadType : builtinTypes->neverType;
if (FFlag::LuauBufferTypeck)
result.buffers = get<NeverType>(here.buffers) ? builtinTypes->bufferType : builtinTypes->neverType;
result.buffers = get<NeverType>(here.buffers) ? builtinTypes->bufferType : builtinTypes->neverType;
/*
* Things get weird and so, so complicated if we allow negations of
@ -1862,8 +1854,7 @@ void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty)
here.threads = builtinTypes->neverType;
break;
case PrimitiveType::Buffer:
if (FFlag::LuauBufferTypeck)
here.buffers = builtinTypes->neverType;
here.buffers = builtinTypes->neverType;
break;
case PrimitiveType::Function:
here.functions.resetToNever();
@ -2695,8 +2686,7 @@ bool Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& th
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
intersectStrings(here.strings, there.strings);
here.threads = (get<NeverType>(there.threads) ? there.threads : here.threads);
if (FFlag::LuauBufferTypeck)
here.buffers = (get<NeverType>(there.buffers) ? there.buffers : here.buffers);
here.buffers = (get<NeverType>(there.buffers) ? there.buffers : here.buffers);
intersectFunctions(here.functions, there.functions);
intersectTables(here.tables, there.tables);
@ -2837,7 +2827,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
here.strings = std::move(strings);
else if (ptv->type == PrimitiveType::Thread)
here.threads = threads;
else if (FFlag::LuauBufferTypeck && ptv->type == PrimitiveType::Buffer)
else if (ptv->type == PrimitiveType::Buffer)
here.buffers = buffers;
else if (ptv->type == PrimitiveType::Function)
here.functions = std::move(functions);
@ -3009,7 +2999,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
}
if (!get<NeverType>(norm.threads))
result.push_back(builtinTypes->threadType);
if (FFlag::LuauBufferTypeck && !get<NeverType>(norm.buffers))
if (!get<NeverType>(norm.buffers))
result.push_back(builtinTypes->bufferType);
result.insert(result.end(), norm.tables.begin(), norm.tables.end());

View file

@ -33,7 +33,7 @@ LUAU_FASTFLAGVARIABLE(LuauToStringSimpleCompositeTypesSingleLine, false)
* 0: Disabled, no changes.
*
* 1: Prefix free/generic types with free- and gen-, respectively. Also reveal
* hidden variadic tails.
* hidden variadic tails. Display block count for local types.
*
* 2: Suffix free/generic types with their scope depth.
*
@ -516,6 +516,12 @@ struct TypeStringifier
{
state.emit("l-");
state.emit(lt.name);
if (FInt::DebugLuauVerboseTypeNames >= 1)
{
state.emit("[");
state.emit(lt.blockCount);
state.emit("]");
}
state.emit("=[");
stringify(lt.domain);
state.emit("]");

View file

@ -27,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
LUAU_FASTFLAG(LuauBufferTypeck)
namespace Luau
{
@ -217,8 +216,6 @@ bool isThread(TypeId ty)
bool isBuffer(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauBufferTypeck);
return isPrim(ty, PrimitiveType::Buffer);
}

View file

@ -1055,7 +1055,7 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& type
// computes the keys of `ty` into `result`
// `isRaw` parameter indicates whether or not we should follow __index metamethods
// returns `false` if `result` should be ignored because the answer is "all strings"
bool computeKeysOf(TypeId ty, DenseHashSet<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFamilyContext> ctx)
bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFamilyContext> ctx)
{
// if the type is the top table type, the answer is just "all strings"
if (get<PrimitiveType>(ty))
@ -1069,6 +1069,13 @@ bool computeKeysOf(TypeId ty, DenseHashSet<std::string>& result, DenseHashSet<Ty
// if we have a particular table type, we can insert the keys
if (auto tableTy = get<TableType>(ty))
{
if (tableTy->indexer)
{
// if we have a string indexer, the answer is, again, "all strings"
if (isString(tableTy->indexer->indexType))
return false;
}
for (auto [key, _] : tableTy->props)
result.insert(key);
return true;
@ -1126,7 +1133,7 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(const std::vector<TypeId>& typ
return {std::nullopt, true, {}, {}};
// we're going to collect the keys in here
DenseHashSet<std::string> keys{{}};
Set<std::string> keys{{}};
// computing the keys for classes
if (normTy->hasClasses())
@ -1147,7 +1154,7 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(const std::vector<TypeId>& typ
for (auto [key, _] : classTy->props)
keys.insert(key);
// we need to check that if there are multiple classes, they have the same set of keys
// we need to look at each class to remove any keys that are not common amongst them all
while (++classesIter != classesIterEnd)
{
auto classTy = get<ClassType>(*classesIter);
@ -1157,11 +1164,11 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(const std::vector<TypeId>& typ
return {std::nullopt, true, {}, {}};
}
for (auto [key, _] : classTy->props)
for (auto key : keys)
{
// we will refuse to reduce if the keys are not exactly the same
if (!keys.contains(key))
return {std::nullopt, true, {}, {}};
// remove any keys that are not present in each class
if (classTy->props.find(key) == classTy->props.end())
keys.erase(key);
}
}
}
@ -1181,20 +1188,23 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(const std::vector<TypeId>& typ
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, false, {}, {}}; // if it failed, we have the top table type!
// we need to check that if there are multiple tables, they have the same set of keys
// we need to look at each tables to remove any keys that are not common amongst them all
while (++tablesIter != normTy->tables.end())
{
seen.clear(); // we'll reuse the same seen set
DenseHashSet<std::string> localKeys{{}};
Set<std::string> localKeys{{}};
// the type family is irreducible if there's _also_ the top table type in here
// we can skip to the next table if this one is the top table type
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
return {std::nullopt, true, {}, {}};
continue;
// the type family is irreducible if the key sets are not equal.
if (localKeys != keys)
return {std::nullopt, true, {}, {}};
for (auto key : keys)
{
// remove any keys that are not present in each table
if (!localKeys.contains(key))
keys.erase(key);
}
}
}

View file

@ -38,7 +38,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAG(LuauBufferTypeck)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauForbidAliasNamedTypeof, false)
@ -5409,10 +5408,28 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
std::optional<TableIndexer> tableIndexer;
for (const auto& prop : table->props)
props[prop.name.value] = {resolveType(scope, *prop.type), /* deprecated: */ false, {}, std::nullopt, {}, std::nullopt, prop.location};
{
if (prop.access == AstTableAccess::Read)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (prop.access == AstTableAccess::Write)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (prop.access == AstTableAccess::ReadWrite)
props[prop.name.value] = {resolveType(scope, *prop.type), /* deprecated: */ false, {}, std::nullopt, {}, std::nullopt, prop.location};
else
ice("Unexpected property access " + std::to_string(int(prop.access)));
}
if (const auto& indexer = table->indexer)
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
{
if (indexer->access == AstTableAccess::Read)
reportError(indexer->accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (indexer->access == AstTableAccess::Write)
reportError(indexer->accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (indexer->access == AstTableAccess::ReadWrite)
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
else
ice("Unexpected property access " + std::to_string(int(indexer->access)));
}
TableType ttv{props, tableIndexer, scope->level, TableState::Sealed};
ttv.definitionModuleName = currentModule->name;
@ -6031,7 +6048,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine(isBoolean, booleanType);
else if (typeguardP.kind == "thread")
return refine(isThread, threadType);
else if (FFlag::LuauBufferTypeck && typeguardP.kind == "buffer")
else if (typeguardP.kind == "buffer")
return refine(isBuffer, bufferType);
else if (typeguardP.kind == "table")
{

View file

@ -847,11 +847,21 @@ struct AstDeclaredClassProp
bool isMethod = false;
};
enum class AstTableAccess
{
Read = 0b01,
Write = 0b10,
ReadWrite = 0b11,
};
struct AstTableIndexer
{
AstType* indexType;
AstType* resultType;
Location location;
AstTableAccess access = AstTableAccess::ReadWrite;
std::optional<Location> accessLocation;
};
class AstStatDeclareClass : public AstStat
@ -915,6 +925,8 @@ struct AstTableProp
AstName name;
Location location;
AstType* type;
AstTableAccess access = AstTableAccess::ReadWrite;
std::optional<Location> accessLocation;
};
class AstTypeTable : public AstType

View file

@ -174,7 +174,7 @@ private:
std::optional<AstTypeList> parseOptionalReturnType();
std::pair<Location, AstTypeList> parseReturnType();
AstTableIndexer* parseTableIndexer();
AstTableIndexer* parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
AstTypeOrPack parseFunctionType(bool allowPack, bool isCheckedFunction = false);
AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,

View file

@ -18,6 +18,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauClipExtraHasEndProps, false)
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false)
namespace Luau
{
@ -945,14 +946,14 @@ AstStat* Parser::parseDeclaration(const Location& start)
{
// maybe we don't need to parse the entire badIndexer...
// however, we either have { or [ to lint, not the entire table type or the bad indexer.
AstTableIndexer* badIndexer = parseTableIndexer();
AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt);
// we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
indexer = parseTableIndexer();
indexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt);
}
}
else
@ -1317,7 +1318,7 @@ std::pair<Location, AstTypeList> Parser::parseReturnType()
}
// TableIndexer ::= `[' Type `]' `:' Type
AstTableIndexer* Parser::parseTableIndexer()
AstTableIndexer* Parser::parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation)
{
const Lexeme begin = lexer.current();
nextLexeme(); // [
@ -1330,7 +1331,7 @@ AstTableIndexer* Parser::parseTableIndexer()
AstType* result = parseType();
return allocator.alloc<AstTableIndexer>(AstTableIndexer{index, result, Location(begin.location, result->location)});
return allocator.alloc<AstTableIndexer>(AstTableIndexer{index, result, Location(begin.location, result->location), access, accessLocation});
}
// TableProp ::= Name `:' Type
@ -1351,6 +1352,28 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
while (lexer.current().type != '}')
{
AstTableAccess access = AstTableAccess::ReadWrite;
std::optional<Location> accessLocation;
if (FFlag::LuauReadWritePropertySyntax)
{
if (lexer.current().type == Lexeme::Name && lexer.lookahead().type != ':')
{
if (AstName(lexer.current().name) == "read")
{
accessLocation = lexer.current().location;
access = AstTableAccess::Read;
lexer.next();
}
else if (AstName(lexer.current().name) == "write")
{
accessLocation = lexer.current().location;
access = AstTableAccess::Write;
lexer.next();
}
}
}
if (lexer.current().type == '[' && (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
{
const Lexeme begin = lexer.current();
@ -1366,7 +1389,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size);
if (chars && !containsNull)
props.push_back({AstName(chars->data), begin.location, type});
props.push_back(AstTableProp{AstName(chars->data), begin.location, type, access, accessLocation});
else
report(begin.location, "String literal contains malformed escape sequence or \\0");
}
@ -1376,14 +1399,14 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
{
// maybe we don't need to parse the entire badIndexer...
// however, we either have { or [ to lint, not the entire table type or the bad indexer.
AstTableIndexer* badIndexer = parseTableIndexer();
AstTableIndexer* badIndexer = parseTableIndexer(access, accessLocation);
// we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one table indexer");
}
else
{
indexer = parseTableIndexer();
indexer = parseTableIndexer(access, accessLocation);
}
}
else if (props.empty() && !indexer && !(lexer.current().type == Lexeme::Name && lexer.lookahead().type == ':'))
@ -1392,7 +1415,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
// array-like table type: {T} desugars into {[number]: T}
AstType* index = allocator.alloc<AstTypeReference>(type->location, std::nullopt, nameNumber, std::nullopt, type->location);
indexer = allocator.alloc<AstTableIndexer>(AstTableIndexer{index, type, type->location});
indexer = allocator.alloc<AstTableIndexer>(AstTableIndexer{index, type, type->location, access, accessLocation});
break;
}
@ -1407,7 +1430,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
AstType* type = parseType(inDeclarationContext);
props.push_back({name->name, name->location, type});
props.push_back(AstTableProp{name->name, name->location, type, access, accessLocation});
}
if (lexer.current().type == ',' || lexer.current().type == ';')

View file

@ -15,12 +15,14 @@
#include <queue>
#include <thread>
#include <utility>
#include <fstream>
#ifdef CALLGRIND
#include <valgrind/callgrind.h>
#endif
LUAU_FASTFLAG(DebugLuauTimeTracing)
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
enum class ReportFormat
{
@ -306,6 +308,7 @@ int main(int argc, char** argv)
Luau::Mode mode = Luau::Mode::Nonstrict;
bool annotate = false;
int threadCount = 0;
std::string basePath = "";
for (int i = 1; i < argc; ++i)
{
@ -326,6 +329,8 @@ int main(int argc, char** argv)
setLuauFlags(argv[i] + 9);
else if (strncmp(argv[i], "-j", 2) == 0)
threadCount = int(strtol(argv[i] + 2, nullptr, 10));
else if (strncmp(argv[i], "--logbase=", 10) == 0)
basePath = std::string{argv[i] + 10};
}
#if !defined(LUAU_ENABLE_TIME_TRACE)
@ -344,6 +349,24 @@ int main(int argc, char** argv)
CliConfigResolver configResolver(mode);
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
if (FFlag::DebugLuauLogSolverToJsonFile)
{
frontend.writeJsonLog = [&basePath](const Luau::ModuleName& moduleName, std::string log) {
std::string path = moduleName + ".log.json";
size_t pos = moduleName.find_last_of('/');
if (pos != std::string::npos)
path = moduleName.substr(pos + 1);
if (!basePath.empty())
path = joinPaths(basePath, path);
std::ofstream os(path);
os << log << std::endl;
printf("Wrote JSON log to %s\n", path.c_str());
};
}
Luau::registerBuiltinGlobals(frontend, frontend.globals);
Luau::freeze(frontend.globals.globalTypes);

View file

@ -76,6 +76,11 @@ struct AssemblyOptions
bool includeIr = false;
bool includeOutlinedCode = false;
bool includeIrPrefix = true; // "#" before IR blocks and instructions
bool includeUseInfo = true;
bool includeCfgInfo = true;
bool includeRegFlowInfo = true;
// Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id
AnnotatorFn annotator = nullptr;
void* annotatorContext = nullptr;

View file

@ -132,7 +132,7 @@ enum class IrCmd : uint8_t
ADD_INT,
SUB_INT,
// Add/Sub/Mul/Div/Mod two double numbers
// Add/Sub/Mul/Div/Idiv/Mod two double numbers
// A, B: double
// In final x64 lowering, B can also be Rn or Kn
ADD_NUM,

View file

@ -32,7 +32,8 @@ void toString(std::string& result, IrConst constant);
void toString(std::string& result, const BytecodeTypes& bcTypes);
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo);
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo); // Block title
void toStringDetailed(
IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, bool includeUseInfo, bool includeCfgInfo, bool includeRegFlowInfo);
std::string toString(const IrFunction& function, bool includeUseInfo);

View file

@ -7,6 +7,8 @@
#include "lobject.h"
LUAU_FASTFLAGVARIABLE(LuauFixDivrkInference, false)
namespace Luau
{
namespace CodeGen
@ -680,11 +682,23 @@ void analyzeBytecodeTypes(IrFunction& function)
case LOP_DIVRK:
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
if (FFlag::LuauFixDivrkInference)
{
int kb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
bcType.a = getBytecodeConstantTag(proto, kb);
bcType.b = regTags[rc];
}
else
{
int rb = LUAU_INSN_B(*pc);
int kc = LUAU_INSN_C(*pc);
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
}
regTags[ra] = LBC_TYPE_ANY;

View file

@ -108,8 +108,10 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
if (options.includeIr)
{
build.logAppend("# ");
toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true);
if (options.includeIrPrefix)
build.logAppend("# ");
toStringDetailed(ctx, block, blockIndex, options.includeUseInfo, options.includeCfgInfo, options.includeRegFlowInfo);
}
// Values can only reference restore operands in the current block chain
@ -172,8 +174,10 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
if (options.includeIr)
{
build.logAppend("# ");
toStringDetailed(ctx, block, blockIndex, inst, index, /* includeUseInfo */ true);
if (options.includeIrPrefix)
build.logAppend("# ");
toStringDetailed(ctx, block, blockIndex, inst, index, options.includeUseInfo);
}
lowering.lowerInst(inst, index, nextBlock);
@ -197,7 +201,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
lowering.finishBlock(block, nextBlock);
if (options.includeIr)
if (options.includeIr && options.includeIrPrefix)
build.logAppend("#\n");
if (block.expectedNextBlock == ~0u)

View file

@ -624,10 +624,11 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blo
}
}
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo)
void toStringDetailed(
IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, bool includeUseInfo, bool includeCfgInfo, bool includeRegFlowInfo)
{
// Report captured registers for entry block
if (block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any())
if (includeRegFlowInfo && block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any())
{
append(ctx.result, "; captured regs: ");
appendRegisterSet(ctx, ctx.cfg.captured, ", ");
@ -636,7 +637,7 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind
size_t start = ctx.result.size();
toString(ctx, block, index);
toString(ctx, block, blockIdx);
append(ctx.result, ":");
if (includeUseInfo)
@ -651,9 +652,9 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind
}
// Predecessor list
if (index < ctx.cfg.predecessorsOffsets.size())
if (includeCfgInfo && blockIdx < ctx.cfg.predecessorsOffsets.size())
{
BlockIteratorWrapper pred = predecessors(ctx.cfg, index);
BlockIteratorWrapper pred = predecessors(ctx.cfg, blockIdx);
if (!pred.empty())
{
@ -665,9 +666,9 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind
}
// Successor list
if (index < ctx.cfg.successorsOffsets.size())
if (includeCfgInfo && blockIdx < ctx.cfg.successorsOffsets.size())
{
BlockIteratorWrapper succ = successors(ctx.cfg, index);
BlockIteratorWrapper succ = successors(ctx.cfg, blockIdx);
if (!succ.empty())
{
@ -679,9 +680,9 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind
}
// Live-in VM regs
if (index < ctx.cfg.in.size())
if (includeRegFlowInfo && blockIdx < ctx.cfg.in.size())
{
const RegisterSet& in = ctx.cfg.in[index];
const RegisterSet& in = ctx.cfg.in[blockIdx];
if (in.regs.any() || in.varargSeq)
{
@ -692,9 +693,9 @@ void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t ind
}
// Live-out VM regs
if (index < ctx.cfg.out.size())
if (includeRegFlowInfo && blockIdx < ctx.cfg.out.size())
{
const RegisterSet& out = ctx.cfg.out[index];
const RegisterSet& out = ctx.cfg.out[blockIdx];
if (out.regs.any() || out.varargSeq)
{
@ -717,7 +718,7 @@ std::string toString(const IrFunction& function, bool includeUseInfo)
if (block.kind == IrBlockKind::Dead)
continue;
toStringDetailed(ctx, block, uint32_t(i), includeUseInfo);
toStringDetailed(ctx, block, uint32_t(i), includeUseInfo, /*includeCfgInfo*/ true, /*includeRegFlowInfo*/ true);
if (block.start == ~0u)
{

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Common.h"
#include "Luau/Common.h"
#include <algorithm>
@ -9,6 +8,7 @@
#include <memory>
#include <new>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace Luau

View file

@ -28,7 +28,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileRevK, false)
namespace Luau
{
@ -997,9 +996,7 @@ struct Compiler
for (const Capture& c : captures)
{
bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0);
}
}

View file

@ -479,6 +479,7 @@ if(TARGET Luau.Conformance)
tests/RegisterCallbacks.h
tests/RegisterCallbacks.cpp
tests/Conformance.test.cpp
tests/IrLowering.test.cpp
tests/main.cpp)
endif()

View file

@ -10,7 +10,6 @@
// option for multiple returns in `lua_pcall' and `lua_call'
#define LUA_MULTRET (-1)

View file

@ -243,10 +243,8 @@ typedef struct TString
int16_t atom;
// 2 byte padding
TString* next; // next string in the hash table bucket
unsigned int hash;
@ -256,7 +254,6 @@ typedef struct TString
} TString;
#define getstr(ts) (ts)->data
#define svalue(o) getstr(tsvalue(o))

View file

@ -18,7 +18,6 @@
#endif
#include <time.h>
static double clock_period()

View file

@ -10,7 +10,6 @@
#include "ldo.h"
#include "ldebug.h"
/*
** Main thread combines a thread state and the global state
*/
@ -181,7 +180,6 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->uvhead.u.open.next = &g->uvhead;
g->GCthreshold = 0; // mark it as unfinished state
g->registryfree = 0;
g->errorjmp = NULL;
g->rngstate = 0;
g->ptrenckey[0] = 1;

View file

@ -201,10 +201,8 @@ typedef struct global_State
TValue pseudotemp; // storage for temporary values used in pseudo2addr
TValue registry; // registry table, used by lua_ref and LUA_REGISTRYINDEX
int registryfree; // next free slot in registry
struct lua_jmpbuf* errorjmp; // jump buffer data for longjmp-style error handling
uint64_t rngstate; // PCG random number generator state

View file

@ -8,7 +8,6 @@
#include <string.h>
unsigned int luaS_hash(const char* str, size_t len)
{
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
@ -78,7 +77,6 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
TString* ts = luaM_newgco(L, TString, sizestring(l), L->activememcat);
luaC_init(L, ts, LUA_TSTRING);
ts->atom = ATOM_UNDEF;
ts->hash = h;
ts->len = unsigned(l);
@ -105,7 +103,6 @@ TString* luaS_bufstart(lua_State* L, size_t size)
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
luaC_init(L, ts, LUA_TSTRING);
ts->atom = ATOM_UNDEF;
ts->hash = 0; // computed in luaS_buffinish
ts->len = unsigned(size);
@ -193,6 +190,5 @@ void luaS_free(lua_State* L, TString* ts, lua_Page* page)
else
LUAU_ASSERT(ts->next == NULL); // orphaned string buffer
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
}

View file

@ -80,10 +80,8 @@
} \
}
#define VM_DISPATCH_OP(op) &&CASE_##op
#define VM_DISPATCH_TABLE() \
VM_DISPATCH_OP(LOP_NOP), VM_DISPATCH_OP(LOP_BREAK), VM_DISPATCH_OP(LOP_LOADNIL), VM_DISPATCH_OP(LOP_LOADB), VM_DISPATCH_OP(LOP_LOADN), \
VM_DISPATCH_OP(LOP_LOADK), VM_DISPATCH_OP(LOP_MOVE), VM_DISPATCH_OP(LOP_GETGLOBAL), VM_DISPATCH_OP(LOP_SETGLOBAL), \
@ -778,7 +776,6 @@ reentry:
break;
case LCT_REF:
setupvalue(L, &ncl->l.uprefs[ui], luaF_findupval(L, VM_REG(LUAU_INSN_B(uinsn))));
break;

View file

@ -160,7 +160,6 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
uint8_t version = read<uint8_t>(data, size, offset);
// 0 means the rest of the bytecode is the error message
if (version == 0)
{

View file

@ -36,6 +36,9 @@ static lua_CompileOptions defaultOptions()
copts.optimizationLevel = optimizationLevel;
copts.debugLevel = 1;
copts.vectorCtor = "vector";
copts.vectorType = "vector";
return copts;
}
@ -483,9 +486,6 @@ TEST_CASE("Pack")
TEST_CASE("Vector")
{
lua_CompileOptions copts = defaultOptions();
copts.vectorCtor = "vector";
runConformance(
"vector.lua",
[](lua_State* L) {
@ -511,7 +511,7 @@ TEST_CASE("Vector")
lua_setmetatable(L, -2);
lua_pop(L, 1);
},
nullptr, nullptr, &copts);
nullptr, nullptr, nullptr);
}
static void populateRTTI(lua_State* L, Luau::TypeId type)
@ -1975,10 +1975,6 @@ TEST_CASE("NativeTypeAnnotations")
if (!codegen || !luau_codegen_supported())
return;
lua_CompileOptions copts = defaultOptions();
copts.vectorCtor = "vector";
copts.vectorType = "vector";
runConformance(
"native_types.lua",
[](lua_State* L) {
@ -2009,7 +2005,7 @@ TEST_CASE("NativeTypeAnnotations")
lua_setmetatable(L, -2);
lua_pop(L, 1);
},
nullptr, nullptr, &copts);
nullptr, nullptr, nullptr);
}
TEST_CASE("HugeFunction")

View file

@ -18,11 +18,13 @@
#include <sstream>
#include <string_view>
#include <iostream>
#include <fstream>
static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
extern std::optional<unsigned> randomSeed; // tests/main.cpp
@ -150,6 +152,21 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete)
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
Luau::setPrintLine([](auto s) {});
if (FFlag::DebugLuauLogSolverToJsonFile)
{
frontend.writeJsonLog = [&](const Luau::ModuleName& moduleName, std::string log) {
std::string path = moduleName + ".log.json";
size_t pos = moduleName.find_last_of('/');
if (pos != std::string::npos)
path = moduleName.substr(pos + 1);
std::ofstream os(path);
os << log << std::endl;
MESSAGE("Wrote JSON log to ", path);
};
}
}
Fixture::~Fixture()
@ -177,7 +194,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
{
Mode mode = sourceModule->mode ? *sourceModule->mode : Mode::Strict;
ModulePtr module = Luau::check(*sourceModule, mode, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options, {});
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options, {}, false, {});
Luau::lint(sourceModule->root, *sourceModule->names, frontend.globals.globalScope, module.get(), sourceModule->hotcomments, {});
}

View file

@ -22,8 +22,6 @@
#include <unordered_map>
#include <optional>
LUAU_FASTFLAG(LuauBufferTypeck);
namespace Luau
{
@ -101,8 +99,6 @@ struct Fixture
ScopedFastFlag sff_DebugLuauFreezeArena;
ScopedFastFlag luauBufferTypeck{FFlag::LuauBufferTypeck, true};
TestFileResolver fileResolver;
TestConfigResolver configResolver;
NullModuleResolver moduleResolver;

90
tests/IrLowering.test.cpp Normal file
View file

@ -0,0 +1,90 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "lua.h"
#include "lualib.h"
#include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h"
#include "Luau/Compiler.h"
#include "Luau/Parser.h"
#include "doctest.h"
#include "ScopedFlags.h"
#include <memory>
LUAU_FASTFLAG(LuauFixDivrkInference)
LUAU_FASTFLAG(LuauCompileRevK)
static std::string getCodegenAssembly(const char* source)
{
Luau::CodeGen::AssemblyOptions options;
// For IR, we don't care about assembly, but we want a stable target
options.target = Luau::CodeGen::AssemblyOptions::Target::X64_SystemV;
options.outputBinary = false;
options.includeAssembly = false;
options.includeIr = true;
options.includeOutlinedCode = false;
options.includeIrPrefix = false;
options.includeUseInfo = false;
options.includeCfgInfo = false;
options.includeRegFlowInfo = false;
Luau::Allocator allocator;
Luau::AstNameTable names(allocator);
Luau::ParseResult result = Luau::Parser::parse(source, strlen(source), names, allocator);
if (!result.errors.empty())
throw Luau::ParseErrors(result.errors);
Luau::CompileOptions copts = {};
copts.optimizationLevel = 2;
copts.debugLevel = 1;
copts.vectorCtor = "vector";
copts.vectorType = "vector";
Luau::BytecodeBuilder bcb;
Luau::compileOrThrow(bcb, result, names, copts);
std::string bytecode = bcb.getBytecode();
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
if (luau_load(L, "name", bytecode.data(), bytecode.size(), 0) == 0)
return Luau::CodeGen::getAssembly(L, -1, options, nullptr);
FAIL("Failed to load bytecode");
return "";
}
TEST_SUITE_BEGIN("IrLowering");
TEST_CASE("VectorReciprocal")
{
ScopedFastFlag luauFixDivrkInference{FFlag::LuauFixDivrkInference, true};
ScopedFastFlag luauCompileRevK{FFlag::LuauCompileRevK, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vecrcp(a: vector)
return 1 / a
end
)"),
R"(
; function vecrcp($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
JUMP bb_fallback_3
bb_4:
INTERRUPT 1u
RETURN R1, 1i
)");
}
TEST_SUITE_END();

View file

@ -17,6 +17,7 @@ LUAU_FASTINT(LuauRecursionLimit);
LUAU_FASTINT(LuauTypeLengthLimit);
LUAU_FASTINT(LuauParseErrorLimit);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauReadWritePropertySyntax);
namespace
{
@ -3153,4 +3154,27 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
LUAU_ASSERT(pr.errors.size() > 0);
}
TEST_CASE_FIXTURE(Fixture, "read_write_table_properties")
{
ScopedFastFlag sff{FFlag::LuauReadWritePropertySyntax, true};
auto pr = tryParse(R"(
type A = {read x: number}
type B = {write x: number}
type C = {read x: number, write x: number}
type D = {read: () -> string}
type E = {write: (string) -> ()}
type F = {read read: () -> string}
type G = {read write: (string) -> ()}
type H = {read ["A"]: number}
type I = {write ["A"]: string}
type J = {read [number]: number}
type K = {write [number]: string}
)");
LUAU_ASSERT(pr.errors.size() == 0);
}
TEST_SUITE_END();

View file

@ -1160,6 +1160,25 @@ TEST_CASE_FIXTURE(SubtypeFixture, "dont_cache_tests_involving_cycles")
CHECK(!subtyping.peekCache().find({tableA, tableB}));
}
TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }) -> T, x: number }) -> number")
{
// <T>({ x: T }) -> T
TypeId tableToPropType = arena.addType(FunctionType{
{genericT},
{},
arena.addTypePack({tbl({{"x", genericT}})}),
arena.addTypePack({genericT})
});
// ({ method: <T>({ x: T }) -> T, x: number }) -> number
TypeId otherType = fn(
{tbl({{"method", tableToPropType}, {"x", builtinTypes->numberType}})},
{builtinTypes->numberType}
);
CHECK_IS_SUBTYPE(tableToPropType, otherType);
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("Subtyping.Subpaths");

View file

@ -339,7 +339,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_
CHECK(toString(result.errors[2]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_union_of_differing_tables")
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type MyObject = { x: number, y: number, z: number }
type MyOtherObject = { [string]: number }
type KeysOfMyOtherObject = keyof<MyOtherObject>
type KeysOfMyObjects = keyof<MyObject | MyOtherObject>
local function ok(idx: KeysOfMyOtherObject): "z" return idx end
local function err(idx: KeysOfMyObjects): "z" return idx end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
CHECK_EQ("string", toString(tpm->givenTp));
tpm = get<TypePackMismatch>(result.errors[1]);
REQUIRE(tpm);
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_common_subset_if_union_of_differing_tables")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
@ -349,14 +377,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_union_of_differi
type MyOtherObject = { w: number, y: number, z: number }
type KeysOfMyObject = keyof<MyObject | MyOtherObject>
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
local function err(idx: KeysOfMyObject): "z" return idx end
)");
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
CHECK(toString(result.errors[2]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_never_for_empty_table")
@ -437,7 +466,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab
CHECK(toString(result.errors[2]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_union_of_differing_tables")
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
@ -447,14 +476,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_union_of_diff
type MyOtherObject = { w: number, y: number, z: number }
type KeysOfMyObject = rawkeyof<MyObject | MyOtherObject>
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
local function err(idx: KeysOfMyObject): "z" return idx end
)");
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
CHECK(toString(result.errors[2]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_never_for_empty_table")
@ -510,7 +540,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par
CHECK(toString(result.errors[2]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
}
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_union_of_differing_classes")
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
@ -518,14 +548,10 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_union_of_differing_
CheckResult result = check(R"(
type KeysOfMyObject = keyof<BaseClass | Vector2>
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
local function ok(idx: KeysOfMyObject): never return idx end
)");
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
CHECK(toString(result.errors[2]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example")

View file

@ -17,10 +17,11 @@ using namespace Luau;
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(DebugLuauSharedSelf)
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauReadWritePropertySyntax);
TEST_SUITE_BEGIN("TableTests");
@ -3984,4 +3985,45 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
CHECK(toString(result.errors[0]) == expected);
}
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
{
ScopedFastFlag sff{FFlag::LuauReadWritePropertySyntax, true};
CheckResult result = check(R"(
type W = {read x: number}
type X = {write x: boolean}
type Y = {read ["prop"]: boolean}
type Z = {write ["prop"]: string}
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK("read keyword is illegal here" == toString(result.errors[0]));
CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
CHECK("write keyword is illegal here" == toString(result.errors[1]));
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
CHECK("read keyword is illegal here" == toString(result.errors[2]));
CHECK(Location{{4, 18}, {4, 22}} == result.errors[2].location);
CHECK("write keyword is illegal here" == toString(result.errors[3]));
CHECK(Location{{5, 18}, {5, 23}} == result.errors[3].location);
}
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
{
ScopedFastFlag sff{FFlag::LuauReadWritePropertySyntax, true};
CheckResult result = check(R"(
type T = {read [string]: number}
type U = {write [string]: boolean}
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK("read keyword is illegal here" == toString(result.errors[0]));
CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
CHECK("write keyword is illegal here" == toString(result.errors[1]));
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
}
TEST_SUITE_END();

View file

@ -2,6 +2,7 @@
#include "Luau/VecDeque.h"
#include "doctest.h"
#include <memory>
TEST_SUITE_BEGIN("VecDequeTests");
@ -593,4 +594,26 @@ TEST_CASE("shrink_to_fit_works_with_strings")
CHECK_EQ(queue[j], testStrings[j]);
}
struct TestStruct
{
};
// Verify that elements pushed to the front of the queue are properly destroyed when the queue is destroyed.
TEST_CASE("push_front_elements_are_destroyed_correctly")
{
std::shared_ptr<TestStruct> t = std::make_shared<TestStruct>();
{
Luau::VecDeque<std::shared_ptr<TestStruct>> queue{};
REQUIRE(queue.empty());
queue.reserve(10);
queue.push_front(t);
queue.push_front(t);
REQUIRE(t.use_count() == 3); // Num of references to the TestStruct instance is now 3
// <-- call destructor here
}
// At this point the destructor should be called and we should be back down to one instance of TestStruct
REQUIRE(t.use_count() == 1);
}
TEST_SUITE_END();

View file

@ -392,7 +392,6 @@ TypeAliases.mutually_recursive_types_swapsies_not_ok
TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases
TypeAliases.saturate_to_first_type_pack
TypeAliases.table_types_record_the_property_locations
TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
TypeAliases.type_alias_locations