mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
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:
parent
73360e5399
commit
cdd1a380db
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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});
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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());
|
||||
|
@ -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("]");
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 == ';')
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// option for multiple returns in `lua_pcall' and `lua_call'
|
||||
#define LUA_MULTRET (-1)
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static double clock_period()
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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")
|
||||
|
@ -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, {});
|
||||
}
|
||||
|
@ -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
90
tests/IrLowering.test.cpp
Normal 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();
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user