From cdd1a380dbf768f168910317e7576210afcd9552 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:04:46 -0800 Subject: [PATCH] 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 Co-authored-by: Andy Friesen Co-authored-by: Lily Brown Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintGenerator.h | 4 +- Analysis/include/Luau/Frontend.h | 3 +- Analysis/src/ConstraintGenerator.cpp | 126 +++++++++++++++----- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 49 +------- Analysis/src/Frontend.cpp | 15 ++- Analysis/src/GlobalTypes.cpp | 5 +- Analysis/src/Linter.cpp | 4 +- Analysis/src/Normalize.cpp | 38 +++--- Analysis/src/ToString.cpp | 8 +- Analysis/src/Type.cpp | 3 - Analysis/src/TypeFamily.cpp | 38 +++--- Analysis/src/TypeInfer.cpp | 25 +++- Ast/include/Luau/Ast.h | 12 ++ Ast/include/Luau/Parser.h | 2 +- Ast/src/Parser.cpp | 41 +++++-- CLI/Analyze.cpp | 23 ++++ CodeGen/include/Luau/CodeGen.h | 5 + CodeGen/include/Luau/IrData.h | 2 +- CodeGen/include/Luau/IrDump.h | 3 +- CodeGen/src/BytecodeAnalysis.cpp | 22 +++- CodeGen/src/CodeGenLower.h | 14 ++- CodeGen/src/IrDump.cpp | 25 ++-- Common/include/Luau/VecDeque.h | 2 +- Compiler/src/Compiler.cpp | 3 - Sources.cmake | 1 + VM/include/lua.h | 1 - VM/src/lobject.h | 3 - VM/src/lperf.cpp | 1 - VM/src/lstate.cpp | 2 - VM/src/lstate.h | 2 - VM/src/lstring.cpp | 4 - VM/src/lvmexecute.cpp | 3 - VM/src/lvmload.cpp | 1 - tests/Conformance.test.cpp | 14 +-- tests/Fixture.cpp | 19 ++- tests/Fixture.h | 4 - tests/IrLowering.test.cpp | 90 ++++++++++++++ tests/Parser.test.cpp | 24 ++++ tests/Subtyping.test.cpp | 19 +++ tests/TypeFamily.test.cpp | 68 +++++++---- tests/TypeInfer.tables.test.cpp | 50 +++++++- tests/VecDeque.test.cpp | 23 ++++ tools/faillist.txt | 1 - 43 files changed, 575 insertions(+), 227 deletions(-) create mode 100644 tests/IrLowering.test.cpp diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 0808e84c8..ebd237f6e 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -241,8 +241,8 @@ struct ConstraintGenerator * 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 checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy); - std::optional checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy); + std::optional checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform); + std::optional checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform); std::optional checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy); std::optional checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy); std::optional checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy); diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 2b83c4436..fc9bc54f2 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -235,6 +235,7 @@ struct Frontend FrontendOptions options; InternalErrorReporter iceHandler; std::function prepareModuleScope; + std::function writeJsonLog = {}; std::unordered_map> sourceNodes; std::unordered_map> sourceModules; @@ -253,6 +254,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, const ScopePtr& globalScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits, bool recordJsonLog); + TypeCheckLimits limits, bool recordJsonLog, std::function writeJsonLog); } // namespace Luau diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 3ef73b33a..91f2ab7e2 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -1008,12 +1008,54 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass std::vector 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. 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()) + { + 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 ConstraintGenerator::checkBinary( } } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy) +std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform) { if (auto local = expr->as()) - return checkLValue(scope, local, assignedTy); + return checkLValue(scope, local, assignedTy, transform); else if (auto global = expr->as()) return checkLValue(scope, global, assignedTy); else if (auto indexName = expr->as()) @@ -2229,7 +2271,7 @@ std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, As ice->ice("checkLValue is inexhaustive"); } -std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy) +std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform) { std::optional annotatedTy = scope->lookup(local->local); LUAU_ASSERT(annotatedTy); @@ -2241,8 +2283,11 @@ std::optional ConstraintGenerator::checkLValue(const ScopePtr& scope, As if (ty) { - if (auto lt = getMutable(*ty)) - ++lt->blockCount; + if (transform) + { + if (auto lt = getMutable(*ty)) + ++lt->blockCount; + } } else { @@ -2251,13 +2296,17 @@ std::optional 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 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}; } } diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 4525368f9..4c01da2fc 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -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; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 005dcf3cc..6e991fe8c 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -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& 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& requireCycles, NotNull builtinTypes, NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, const ScopePtr& parentScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits) + TypeCheckLimits limits, std::function 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& requireCycles, NotNull builtinTypes, NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, const ScopePtr& parentScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits, bool recordJsonLog) + TypeCheckLimits limits, bool recordJsonLog, std::function writeJsonLog) { ModulePtr result = std::make_shared(); result->name = sourceModule.name; @@ -1281,7 +1283,10 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vectorcompileOutput(); - 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) { diff --git a/Analysis/src/GlobalTypes.cpp b/Analysis/src/GlobalTypes.cpp index ee59f52bb..9dd60caad 100644 --- a/Analysis/src/GlobalTypes.cpp +++ b/Analysis/src/GlobalTypes.cpp @@ -2,8 +2,6 @@ #include "Luau/GlobalTypes.h" -LUAU_FASTFLAG(LuauBufferTypeck) - namespace Luau { @@ -18,8 +16,7 @@ GlobalTypes::GlobalTypes(NotNull 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}); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index ea35a6a1f..79a7ef566 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -14,8 +14,6 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) -LUAU_FASTFLAG(LuauBufferTypeck) - namespace Luau { @@ -1107,7 +1105,7 @@ class LintUnknownType : AstVisitor 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") diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 30ab78959..985ad1e90 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -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(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(norm.tops) || !get(norm.booleans) || !norm.classes.isNever() || !get(norm.errors) || !get(norm.nils) || !get(norm.numbers) || !norm.strings.isNever() || !get(norm.threads) || - (FFlag::LuauBufferTypeck && !get(norm.buffers)) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty(); + !get(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty(); } bool Normalizer::isInhabited(const NormalizedType* norm, Set seen) @@ -411,8 +409,8 @@ bool Normalizer::isInhabited(const NormalizedType* norm, Set seen) return true; if (!get(norm->tops) || !get(norm->booleans) || !get(norm->errors) || !get(norm->nils) || - !get(norm->numbers) || !get(norm->threads) || (FFlag::LuauBufferTypeck && !get(norm->buffers)) || - !norm->classes.isNever() || !norm->strings.isNever() || !norm->functions.isNever()) + !get(norm->numbers) || !get(norm->threads) || !get(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(ty)) return true; else if (const PrimitiveType* ptv = get(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(there.numbers) ? here.numbers : there.numbers); unionStrings(here.strings, there.strings); here.threads = (get(there.threads) ? here.threads : there.threads); - if (FFlag::LuauBufferTypeck) - here.buffers = (get(there.buffers) ? here.buffers : there.buffers); + here.buffers = (get(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, Settype == 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 Normalizer::negateNormal(const NormalizedType& her result.strings.isCofinite = !result.strings.isCofinite; result.threads = get(here.threads) ? builtinTypes->threadType : builtinTypes->neverType; - if (FFlag::LuauBufferTypeck) - result.buffers = get(here.buffers) ? builtinTypes->bufferType : builtinTypes->neverType; + result.buffers = get(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(there.numbers) ? there.numbers : here.numbers); intersectStrings(here.strings, there.strings); here.threads = (get(there.threads) ? there.threads : here.threads); - if (FFlag::LuauBufferTypeck) - here.buffers = (get(there.buffers) ? there.buffers : here.buffers); + here.buffers = (get(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, Settype == 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(norm.threads)) result.push_back(builtinTypes->threadType); - if (FFlag::LuauBufferTypeck && !get(norm.buffers)) + if (!get(norm.buffers)) result.push_back(builtinTypes->bufferType); result.insert(result.end(), norm.tables.begin(), norm.tables.end()); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 9eda8a0ef..b14df311e 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -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("]"); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 843ac25ef..abe719e12 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -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); } diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index 11457b8c8..ed81b6630 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -1055,7 +1055,7 @@ TypeFamilyReductionResult refineFamilyFn(const std::vector& 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& result, DenseHashSet& seen, bool isRaw, NotNull ctx) +bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& seen, bool isRaw, NotNull ctx) { // if the type is the top table type, the answer is just "all strings" if (get(ty)) @@ -1069,6 +1069,13 @@ bool computeKeysOf(TypeId ty, DenseHashSet& result, DenseHashSet(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 keyofFamilyImpl(const std::vector& typ return {std::nullopt, true, {}, {}}; // we're going to collect the keys in here - DenseHashSet keys{{}}; + Set keys{{}}; // computing the keys for classes if (normTy->hasClasses()) @@ -1147,7 +1154,7 @@ TypeFamilyReductionResult keyofFamilyImpl(const std::vector& 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(*classesIter); @@ -1157,11 +1164,11 @@ TypeFamilyReductionResult keyofFamilyImpl(const std::vector& 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 keyofFamilyImpl(const std::vector& 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 localKeys{{}}; + Set 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); + } } } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 54018a266..a1247915f 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -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; 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") { diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 2abda7883..d4836bb5c 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -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 accessLocation; }; class AstStatDeclareClass : public AstStat @@ -915,6 +925,8 @@ struct AstTableProp AstName name; Location location; AstType* type; + AstTableAccess access = AstTableAccess::ReadWrite; + std::optional accessLocation; }; class AstTypeTable : public AstType diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 96e9639c2..e97df66b7 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -174,7 +174,7 @@ class Parser std::optional parseOptionalReturnType(); std::pair parseReturnType(); - AstTableIndexer* parseTableIndexer(); + AstTableIndexer* parseTableIndexer(AstTableAccess access, std::optional accessLocation); AstTypeOrPack parseFunctionType(bool allowPack, bool isCheckedFunction = false); AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray generics, AstArray genericPacks, diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index e4b840b2e..6d15e7096 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -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 Parser::parseReturnType() } // TableIndexer ::= `[' Type `]' `:' Type -AstTableIndexer* Parser::parseTableIndexer() +AstTableIndexer* Parser::parseTableIndexer(AstTableAccess access, std::optional accessLocation) { const Lexeme begin = lexer.current(); nextLexeme(); // [ @@ -1330,7 +1331,7 @@ AstTableIndexer* Parser::parseTableIndexer() AstType* result = parseType(); - return allocator.alloc(AstTableIndexer{index, result, Location(begin.location, result->location)}); + return allocator.alloc(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 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(type->location, std::nullopt, nameNumber, std::nullopt, type->location); - indexer = allocator.alloc(AstTableIndexer{index, type, type->location}); + indexer = allocator.alloc(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 == ';') diff --git a/CLI/Analyze.cpp b/CLI/Analyze.cpp index abe28b11a..911d2ad65 100644 --- a/CLI/Analyze.cpp +++ b/CLI/Analyze.cpp @@ -15,12 +15,14 @@ #include #include #include +#include #ifdef CALLGRIND #include #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); diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 6fa634e16..b42dc773b 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -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; diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index bd44a9e21..2330e680a 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -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, diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index 1f3ee5a95..bcd247671 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -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); diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 7e2310547..40c5d9ccb 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -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; diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 3acecc130..a62c19373 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -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) diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index ec9f978b4..454eaf138 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -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) { diff --git a/Common/include/Luau/VecDeque.h b/Common/include/Luau/VecDeque.h index 6eed34d61..24d3d165f 100644 --- a/Common/include/Luau/VecDeque.h +++ b/Common/include/Luau/VecDeque.h @@ -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 @@ -9,6 +8,7 @@ #include #include #include +#include #include namespace Luau diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 5174ac9d7..af17e3f2b 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -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); - } } diff --git a/Sources.cmake b/Sources.cmake index bbe9efee3..680fdd8fe 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -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() diff --git a/VM/include/lua.h b/VM/include/lua.h index 0390de7cf..4876b933f 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -10,7 +10,6 @@ - // option for multiple returns in `lua_pcall' and `lua_call' #define LUA_MULTRET (-1) diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 842be5ff3..44f2bccc0 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -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)) diff --git a/VM/src/lperf.cpp b/VM/src/lperf.cpp index 67befee41..b7293c148 100644 --- a/VM/src/lperf.cpp +++ b/VM/src/lperf.cpp @@ -18,7 +18,6 @@ #endif - #include static double clock_period() diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index 1c4d9c51f..858f61a3a 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -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; diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 0776d6dcd..2c6d35dc5 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -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 diff --git a/VM/src/lstring.cpp b/VM/src/lstring.cpp index 9f6bb4761..62c38dd7b 100644 --- a/VM/src/lstring.cpp +++ b/VM/src/lstring.cpp @@ -8,7 +8,6 @@ #include - 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); } diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index d8e316bd3..b7c21a391 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -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 @@ static void luau_execute(lua_State* L) break; case LCT_REF: - setupvalue(L, &ncl->l.uprefs[ui], luaF_findupval(L, VM_REG(LUAU_INSN_B(uinsn)))); break; diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 5f1ff40a0..98d74452b 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -160,7 +160,6 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size uint8_t version = read(data, size, offset); - // 0 means the rest of the bytecode is the error message if (version == 0) { diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index fe555ba0d..803eac6ce 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -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") diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 6e8919e60..a6d8a9c63 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -18,11 +18,13 @@ #include #include #include +#include static const char* mainModuleName = "MainModule"; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauFreezeArena); +LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile) extern std::optional 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, {}); } diff --git a/tests/Fixture.h b/tests/Fixture.h index 24c6d411a..481f79d3a 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -22,8 +22,6 @@ #include #include -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; diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp new file mode 100644 index 000000000..c89e9a697 --- /dev/null +++ b/tests/IrLowering.test.cpp @@ -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 + +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 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(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 9672165a6..103314088 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -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(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 1eca7ec11..1b55ac6aa 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -1160,6 +1160,25 @@ TEST_CASE_FIXTURE(SubtypeFixture, "dont_cache_tests_involving_cycles") CHECK(!subtyping.peekCache().find({tableA, tableB})); } +TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T }) -> T, x: number }) -> number") +{ + // ({ x: T }) -> T + TypeId tableToPropType = arena.addType(FunctionType{ + {genericT}, + {}, + arena.addTypePack({tbl({{"x", genericT}})}), + arena.addTypePack({genericT}) + }); + + // ({ method: ({ 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"); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index c8280dbfa..17e037d9a 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -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 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 + type KeysOfMyObjects = keyof + + 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(result.errors[0]); + REQUIRE(tpm); + CHECK_EQ("\"z\"", toString(tpm->wantedTp)); + CHECK_EQ("string", toString(tpm->givenTp)); + + tpm = get(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 - 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 is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance keyof is uninhabited"); - CHECK(toString(result.errors[2]) == "Type family instance keyof is uninhabited"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + + TypePackMismatch* tpm = get(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 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 - 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 is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance rawkeyof is uninhabited"); - CHECK(toString(result.errors[2]) == "Type family instance rawkeyof is uninhabited"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + + TypePackMismatch* tpm = get(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 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 - 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 is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance keyof is uninhabited"); - CHECK(toString(result.errors[2]) == "Type family instance keyof is uninhabited"); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index fb30c4024..24e0e44e4 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -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(); diff --git a/tests/VecDeque.test.cpp b/tests/VecDeque.test.cpp index 066b15e68..bc8ef2723 100644 --- a/tests/VecDeque.test.cpp +++ b/tests/VecDeque.test.cpp @@ -2,6 +2,7 @@ #include "Luau/VecDeque.h" #include "doctest.h" +#include 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 t = std::make_shared(); + { + Luau::VecDeque> 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(); diff --git a/tools/faillist.txt b/tools/faillist.txt index 6ab283593..d23d83621 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -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