From c759cd5581f570980cf05186b0db58e65371c9fb Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 10 Jan 2025 11:34:39 -0800 Subject: [PATCH] Sync to upstream/release/656 (#1612) # General All code has been re-formatted by `clang-format`; this is not mechanically enforced, so Luau may go out-of-sync over the course of the year. # New Solver * Track free types interior to a block of code on `Scope`, which should reduce the number of free types that remain un-generalized after type checking is complete (e.g.: less errors like `'a <: number is incompatible with number`). # Autocomplete * Fragment autocomplete now does *not* provide suggestions within comments (matching non-fragment autocomplete behavior). * Autocomplete now respects iteration and recursion limits (some hangs will now early exit with a "unification too complex error," some crashes will now become internal complier exceptions). # Runtime * Add a limit to how many Luau codegen slot nodes addresses can be in use at the same time (fixes #1605, fixes #1558). * Added constant folding for vector arithmetic (fixes #1553). * Added support for `buffer.readbits` and `buffer.writebits` (see: https://github.com/luau-lang/rfcs/pull/18). --- Co-authored-by: Aaron Weiss Co-authored-by: David Cope Co-authored-by: Hunter Goldstein Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/FragmentAutocomplete.h | 9 +- Analysis/include/Luau/Module.h | 3 + Analysis/include/Luau/Scope.h | 2 + Analysis/include/Luau/Type.h | 2 +- Analysis/include/Luau/TypeFunction.h | 2 +- Analysis/include/Luau/TypeUtils.h | 13 +- Analysis/src/AnyTypeSummary.cpp | 1 - Analysis/src/AutocompleteCore.cpp | 7 + Analysis/src/BuiltinDefinitions.cpp | 4 +- Analysis/src/Constraint.cpp | 1 - Analysis/src/ConstraintGenerator.cpp | 67 +++--- Analysis/src/ConstraintSolver.cpp | 34 ++- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 210 +----------------- Analysis/src/EqSatSimplification.cpp | 75 ++++--- Analysis/src/FragmentAutocomplete.cpp | 18 +- Analysis/src/Generalization.cpp | 3 +- Analysis/src/Module.cpp | 37 +++- Analysis/src/Normalize.cpp | 6 +- Analysis/src/OverloadResolution.cpp | 3 +- Analysis/src/Subtyping.cpp | 5 +- Analysis/src/TypeChecker2.cpp | 3 +- Analysis/src/TypeFunction.cpp | 9 +- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 4 +- Analysis/src/TypeInfer.cpp | 8 +- Analysis/src/TypeUtils.cpp | 21 +- Ast/include/Luau/Allocator.h | 2 +- Ast/include/Luau/Location.h | 47 +++- Ast/src/Allocator.cpp | 2 +- Ast/src/Ast.cpp | 5 +- Ast/src/Lexer.cpp | 4 +- Ast/src/Location.cpp | 46 ---- Ast/src/Parser.cpp | 13 +- CLI/src/Compile.cpp | 3 +- CLI/src/FileUtils.cpp | 2 +- CodeGen/include/Luau/IrDump.h | 3 + CodeGen/src/BytecodeAnalysis.cpp | 24 +- CodeGen/src/CodeGen.cpp | 2 +- CodeGen/src/CodeGenLower.h | 2 +- CodeGen/src/CodeGenUtils.cpp | 20 +- CodeGen/src/IrAnalysis.cpp | 2 +- CodeGen/src/IrDump.cpp | 77 ++++++- CodeGen/src/OptimizeConstProp.cpp | 139 +++++++++++- Common/include/Luau/ExperimentalFlags.h | 2 +- Compiler/src/Builtins.cpp | 3 +- Compiler/src/BytecodeBuilder.cpp | 3 +- Compiler/src/Compiler.cpp | 5 +- Compiler/src/CostModel.cpp | 3 +- Compiler/src/Types.cpp | 3 +- Config/src/Config.cpp | 3 +- EqSat/include/Luau/EGraph.h | 8 +- EqSat/include/Luau/Language.h | 10 +- VM/src/lbuflib.cpp | 95 +++++++- VM/src/ldo.cpp | 28 ++- VM/src/ldo.h | 14 +- VM/src/lgc.cpp | 2 +- VM/src/lmathlib.cpp | 7 +- VM/src/lmem.cpp | 2 +- VM/src/lstate.cpp | 2 +- VM/src/lvmexecute.cpp | 20 +- tests/AnyTypeSummary.test.cpp | 11 +- tests/Autocomplete.test.cpp | 9 +- tests/Compiler.test.cpp | 7 - tests/Conformance.test.cpp | 12 +- tests/EqSatSimplification.test.cpp | 221 +++++++------------ tests/Fixture.h | 3 +- tests/FragmentAutocomplete.test.cpp | 177 ++++++++++++++- tests/IrLowering.test.cpp | 66 +++--- tests/Lexer.test.cpp | 6 + tests/Parser.test.cpp | 89 +++++--- tests/Repl.test.cpp | 4 +- tests/ToString.test.cpp | 3 +- tests/TypeFunction.user.test.cpp | 2 - tests/TypeInfer.classes.test.cpp | 14 +- tests/TypeInfer.functions.test.cpp | 5 +- tests/TypeInfer.operators.test.cpp | 5 +- tests/TypeInfer.primitives.test.cpp | 2 - tests/TypeInfer.provisional.test.cpp | 4 +- tests/TypeInfer.refinements.test.cpp | 18 +- tests/TypeInfer.tables.test.cpp | 13 +- tests/TypeInfer.test.cpp | 15 +- tests/TypeInfer.typePacks.test.cpp | 9 +- tests/TypeInfer.unionTypes.test.cpp | 4 +- tests/conformance/buffers.lua | 85 +++++++ tests/conformance/calls.lua | 2 +- tests/conformance/native.lua | 64 ++++++ 85 files changed, 1299 insertions(+), 701 deletions(-) diff --git a/Analysis/include/Luau/FragmentAutocomplete.h b/Analysis/include/Luau/FragmentAutocomplete.h index 2bbba6e67..2125cc414 100644 --- a/Analysis/include/Luau/FragmentAutocomplete.h +++ b/Analysis/include/Luau/FragmentAutocomplete.h @@ -15,6 +15,12 @@ namespace Luau { struct FrontendOptions; +enum class FragmentTypeCheckStatus +{ + Success, + SkipAutocomplete, +}; + struct FragmentAutocompleteAncestryResult { DenseHashMap localMap{AstName()}; @@ -29,6 +35,7 @@ struct FragmentParseResult AstStatBlock* root = nullptr; std::vector ancestry; AstStat* nearestStatement = nullptr; + std::vector commentLocations; std::unique_ptr alloc = std::make_unique(); }; @@ -56,7 +63,7 @@ FragmentParseResult parseFragment( std::optional fragmentEndPosition ); -FragmentTypeCheckResult typecheckFragment( +std::pair typecheckFragment( Frontend& frontend, const ModuleName& moduleName, const Position& cursorPos, diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 49b4ae028..3f3e69f19 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -16,6 +16,8 @@ #include #include +LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection) + namespace Luau { @@ -55,6 +57,7 @@ struct SourceModule } }; +bool isWithinComment(const std::vector& commentLocations, Position pos); bool isWithinComment(const SourceModule& sourceModule, Position pos); bool isWithinComment(const ParseResult& result, Position pos); diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 302c273c3..4604a2e1a 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -95,6 +95,8 @@ struct Scope // we need that the generic type `T` in both cases is the same, so we use a cache. std::unordered_map typeAliasTypeParameters; std::unordered_map typeAliasTypePackParameters; + + std::optional> interiorFreeTypes; }; // Returns true iff the left scope encloses the right scope. A Scope* equal to diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 9e525ac68..701fe0515 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -626,7 +626,7 @@ struct TypeFunctionInstanceType std::vector typeArguments; std::vector packArguments; - std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs + std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs UserDefinedFunctionData userFuncData; TypeFunctionInstanceType( diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index ba8646215..dadad721d 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -71,7 +71,7 @@ struct TypeFunctionContext // The constraint being reduced in this run of the reduction const Constraint* constraint; - std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs + std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs TypeFunctionContext(NotNull cs, NotNull scope, NotNull constraint); diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index de9660ef6..03e1bb2f8 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -269,8 +269,8 @@ bool isLiteral(const AstExpr* expr); std::vector findBlockedTypesIn(AstExprTable* expr, NotNull> astTypes); /** - * Given a function call and a mapping from expression to type, determine - * whether the type of any argument in said call in depends on a blocked types. + * Given a function call and a mapping from expression to type, determine + * whether the type of any argument in said call in depends on a blocked types. * This is used as a precondition for bidirectional inference: be warned that * the behavior of this algorithm is tightly coupled to that of bidirectional * inference. @@ -280,4 +280,13 @@ std::vector findBlockedTypesIn(AstExprTable* expr, NotNull findBlockedArgTypesIn(AstExprCall* expr, NotNull> astTypes); +/** + * Given a scope and a free type, find the closest parent that has a present + * `interiorFreeTypes` and append the given type to said list. This list will + * be generalized when the requiste `GeneralizationConstraint` is resolved. + * @param scope Initial scope this free type was attached to + * @param ty Free type to track. + */ +void trackInteriorFreeType(Scope* scope, TypeId ty); + } // namespace Luau diff --git a/Analysis/src/AnyTypeSummary.cpp b/Analysis/src/AnyTypeSummary.cpp index e82592df7..db50e3e9a 100644 --- a/Analysis/src/AnyTypeSummary.cpp +++ b/Analysis/src/AnyTypeSummary.cpp @@ -177,7 +177,6 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* } } } - } void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull builtinTypes) diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index f9e7e10f2..f7f198260 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteUseLimits) static const std::unordered_set kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -177,6 +178,12 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull scope, T unifier.normalize = false; unifier.checkInhabited = false; + if (FFlag::LuauAutocompleteUseLimits) + { + unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; + unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit; + } + return unifier.canUnify(subTy, superTy).empty(); } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 6306b5b1f..2db6d5673 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -611,7 +611,9 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex if (!fmt) { if (FFlag::LuauStringFormatArityFix) - context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); + context.typechecker->reportError( + CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location + ); return; } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index a0b5fcf46..cde566d85 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -62,7 +62,6 @@ struct ReferenceCountInitializer : TypeOnceVisitor // of this type, hence: return !FFlag::LuauDontRefCountTypesInTypeFunctions; } - }; bool isReferenceCountedType(const TypeId typ) diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index f6fdc9aaf..57fdccab8 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -31,15 +31,15 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauTypestateBuiltins2) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues) -LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunExportedAndLocal) +LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses) LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint) +LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(InferGlobalTypes) @@ -233,8 +233,17 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) Checkpoint end = checkpoint(this); TypeId result = arena->addType(BlockedType{}); - NotNull genConstraint = - addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); + NotNull genConstraint = addConstraint( + scope, + block->location, + GeneralizationConstraint{ + result, moduleFnTy, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector{} : std::move(interiorTypes.back()) + } + ); + + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + scope->interiorFreeTypes = std::move(interiorTypes.back()); + getMutable(result)->setOwner(genConstraint); forEachConstraint( start, @@ -303,9 +312,19 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat } } + TypeId ConstraintGenerator::freshType(const ScopePtr& scope) { - return Luau::freshType(arena, builtinTypes, scope.get()); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + { + auto ft = Luau::freshType(arena, builtinTypes, scope.get()); + interiorTypes.back().push_back(ft); + return ft; + } + else + { + return Luau::freshType(arena, builtinTypes, scope.get()); + } } TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope) @@ -2408,8 +2427,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Checkpoint endCheckpoint = checkpoint(this); TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = - addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())}); + NotNull gc = addConstraint( + sig.signatureScope, + func->location, + GeneralizationConstraint{ + generalizedTy, sig.signature, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector{} : std::move(interiorTypes.back()) + } + ); + + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + sig.signatureScope->interiorFreeTypes = std::move(interiorTypes.back()); + getMutable(generalizedTy)->setOwner(gc); interiorTypes.pop_back(); @@ -2975,11 +3003,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ty, expr, toBlock - ); - // The visitor we ran prior should ensure that there are no - // blocked types that we would encounter while matching on - // this expression. - LUAU_ASSERT(toBlock.empty()); + ); + // The visitor we ran prior should ensure that there are no + // blocked types that we would encounter while matching on + // this expression. + LUAU_ASSERT(toBlock.empty()); } } @@ -3941,20 +3969,7 @@ TypeId ConstraintGenerator::createTypeFunctionInstance( TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right) { - if (FFlag::DebugLuauEqSatSimplification) - { - TypeId ty = arena->addType(UnionType{{left, right}}); - std::optional res = eqSatSimplify(simplifier, ty); - if (!res) - return ty; - - for (TypeId tyFun : res->newTypeFunctions) - addConstraint(scope, location, ReduceConstraint{tyFun}); - - return res->result; - } - else - return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result; + return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result; } std::vector> borrowConstraints(const std::vector& constraints) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 6cf717ecd..1be02a71e 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint) +LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) namespace Luau { @@ -724,8 +725,20 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullerrorRecoveryType()); } - for (TypeId ty : c.interiorTypes) - generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + { + // We check if this member is initialized and then access it, but + // clang-tidy doesn't understand this is safe. + if (constraint->scope->interiorFreeTypes) + for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) + generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); + } + else + { + for (TypeId ty : c.interiorTypes) + generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); + } + return true; } @@ -801,6 +814,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNullscope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + { + trackInteriorFreeType(constraint->scope, keyTy); + trackInteriorFreeType(constraint->scope, valueTy); + } TypeId tableTy = arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free}); @@ -2062,6 +2080,8 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullscope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + trackInteriorFreeType(constraint->scope, f); shiftReferences(resultTy, f); emplaceType(asMutable(resultTy), f); } @@ -2197,6 +2217,11 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl { TypeId keyTy = freshType(arena, builtinTypes, constraint->scope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + { + trackInteriorFreeType(constraint->scope, keyTy); + trackInteriorFreeType(constraint->scope, valueTy); + } TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope}); getMutable(tableTy)->indexer = TableIndexer{keyTy, valueTy}; @@ -2453,6 +2478,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( if (ttv->state == TableState::Free) { TypeId result = freshType(arena, builtinTypes, ttv->scope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + trackInteriorFreeType(ttv->scope, result); switch (context) { case ValueContext::RValue: @@ -2562,6 +2589,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( LUAU_ASSERT(tt); TypeId propType = freshType(arena, builtinTypes, scope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + trackInteriorFreeType(scope, propType); + switch (context) { case ValueContext::RValue: diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 828fc7ed3..caff137d5 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -1,16 +1,14 @@ // 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_FASTFLAG(LuauMathMap) - -LUAU_FASTFLAGVARIABLE(LuauVectorDefinitions) LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) +LUAU_FASTFLAG(LuauBufferBitMethods) namespace Luau { // TODO: there has to be a better way, like splitting up per library -static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( declare bit32: { band: @checked (...number) -> number, @@ -73,6 +71,7 @@ declare math: { clamp: @checked (n: number, min: number, max: number) -> number, noise: @checked (x: number, y: number?, z: number?) -> number, round: @checked (n: number) -> number, + map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number, } type DateTypeArg = { @@ -200,7 +199,9 @@ declare utf8: { -- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. declare function unpack(tab: {V}, i: number?, j: number?): ...V +)BUILTIN_SRC"; +static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC( --- Buffer API declare buffer: { create: @checked (size: number) -> buffer, @@ -231,198 +232,7 @@ declare buffer: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( - -declare bit32: { - band: @checked (...number) -> number, - bor: @checked (...number) -> number, - bxor: @checked (...number) -> number, - btest: @checked (number, ...number) -> boolean, - rrotate: @checked (x: number, disp: number) -> number, - lrotate: @checked (x: number, disp: number) -> number, - lshift: @checked (x: number, disp: number) -> number, - arshift: @checked (x: number, disp: number) -> number, - rshift: @checked (x: number, disp: number) -> number, - bnot: @checked (x: number) -> number, - extract: @checked (n: number, field: number, width: number?) -> number, - replace: @checked (n: number, v: number, field: number, width: number?) -> number, - countlz: @checked (n: number) -> number, - countrz: @checked (n: number) -> number, - byteswap: @checked (n: number) -> number, -} - -declare math: { - frexp: @checked (n: number) -> (number, number), - ldexp: @checked (s: number, e: number) -> number, - fmod: @checked (x: number, y: number) -> number, - modf: @checked (n: number) -> (number, number), - pow: @checked (x: number, y: number) -> number, - exp: @checked (n: number) -> number, - - ceil: @checked (n: number) -> number, - floor: @checked (n: number) -> number, - abs: @checked (n: number) -> number, - sqrt: @checked (n: number) -> number, - - log: @checked (n: number, base: number?) -> number, - log10: @checked (n: number) -> number, - - rad: @checked (n: number) -> number, - deg: @checked (n: number) -> number, - - sin: @checked (n: number) -> number, - cos: @checked (n: number) -> number, - tan: @checked (n: number) -> number, - sinh: @checked (n: number) -> number, - cosh: @checked (n: number) -> number, - tanh: @checked (n: number) -> number, - atan: @checked (n: number) -> number, - acos: @checked (n: number) -> number, - asin: @checked (n: number) -> number, - atan2: @checked (y: number, x: number) -> number, - - min: @checked (number, ...number) -> number, - max: @checked (number, ...number) -> number, - - pi: number, - huge: number, - - randomseed: @checked (seed: number) -> (), - random: @checked (number?, number?) -> number, - - sign: @checked (n: number) -> number, - clamp: @checked (n: number, min: number, max: number) -> number, - noise: @checked (x: number, y: number?, z: number?) -> number, - round: @checked (n: number) -> number, - map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number, -} - -type DateTypeArg = { - year: number, - month: number, - day: number, - hour: number?, - min: number?, - sec: number?, - isdst: boolean?, -} - -type DateTypeResult = { - year: number, - month: number, - wday: number, - yday: number, - day: number, - hour: number, - min: number, - sec: number, - isdst: boolean, -} - -declare os: { - time: (time: DateTypeArg?) -> number, - date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string), - difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number, - clock: () -> number, -} - -@checked declare function require(target: any): any - -@checked declare function getfenv(target: any): { [string]: any } - -declare _G: any -declare _VERSION: string - -declare function gcinfo(): number - -declare function print(...: T...) - -declare function type(value: T): string -declare function typeof(value: T): string - --- `assert` has a magic function attached that will give more detailed type information -declare function assert(value: T, errorMessage: string?): T -declare function error(message: T, level: number?): never - -declare function tostring(value: T): string -declare function tonumber(value: T, radix: number?): number? - -declare function rawequal(a: T1, b: T2): boolean -declare function rawget(tab: {[K]: V}, k: K): V -declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} -declare function rawlen(obj: {[K]: V} | string): number - -declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? - -declare function ipairs(tab: {V}): (({V}, number) -> (number?, V), {V}, number) - -declare function pcall(f: (A...) -> R..., ...: A...): (boolean, R...) - --- FIXME: The actual type of `xpcall` is: --- (f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...) --- Since we can't represent the return value, we use (boolean, R1...). -declare function xpcall(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...) - --- `select` has a magic function attached to provide more detailed type information -declare function select(i: string | number, ...: A...): ...any - --- FIXME: This type is not entirely correct - `loadstring` returns a function or --- (nil, string). -declare function loadstring(src: string, chunkname: string?): (((A...) -> any)?, string?) - -@checked declare function newproxy(mt: boolean?): any - -declare coroutine: { - create: (f: (A...) -> R...) -> thread, - resume: (co: thread, A...) -> (boolean, R...), - running: () -> thread, - status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended", - wrap: (f: (A...) -> R...) -> ((A...) -> R...), - yield: (A...) -> R..., - isyieldable: () -> boolean, - close: @checked (co: thread) -> (boolean, any) -} - -declare table: { - concat: (t: {V}, sep: string?, i: number?, j: number?) -> string, - insert: ((t: {V}, value: V) -> ()) & ((t: {V}, pos: number, value: V) -> ()), - maxn: (t: {V}) -> number, - remove: (t: {V}, number?) -> V?, - sort: (t: {V}, comp: ((V, V) -> boolean)?) -> (), - create: (count: number, value: V?) -> {V}, - find: (haystack: {V}, needle: V, init: number?) -> number?, - - unpack: (list: {V}, i: number?, j: number?) -> ...V, - pack: (...V) -> { n: number, [number]: V }, - - getn: (t: {V}) -> number, - foreach: (t: {[K]: V}, f: (K, V) -> ()) -> (), - foreachi: ({V}, (number, V) -> ()) -> (), - - move: (src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V}, - clear: (table: {[K]: V}) -> (), - - isfrozen: (t: {[K]: V}) -> boolean, -} - -declare debug: { - info: ((thread: thread, level: number, options: string) -> R...) & ((level: number, options: string) -> R...) & ((func: (A...) -> R1..., options: string) -> R2...), - traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), -} - -declare utf8: { - char: @checked (...number) -> string, - charpattern: string, - codes: @checked (str: string) -> ((string, number) -> (number, number), string, number), - codepoint: @checked (str: string, i: number?, j: number?) -> ...number, - len: @checked (s: string, i: number?, j: number?) -> (number?, number?), - offset: @checked (s: string, n: number?, i: number?) -> number, -} - --- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -declare function unpack(tab: {V}, i: number?, j: number?): ...V - - +static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC( --- Buffer API declare buffer: { create: @checked (size: number) -> buffer, @@ -449,6 +259,8 @@ declare buffer: { writef64: @checked (b: buffer, offset: number, value: number) -> (), readstring: @checked (b: buffer, offset: number, count: number) -> string, writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (), + readbits: @checked (b: buffer, bitOffset: number, bitCount: number) -> number, + writebits: @checked (b: buffer, bitOffset: number, bitCount: number, value: number) -> (), } )BUILTIN_SRC"; @@ -511,11 +323,13 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED; + std::string result = kBuiltinDefinitionLuaSrcChecked; + + result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; if (FFlag::LuauVectorDefinitionsExtra) result += kBuiltinDefinitionVectorSrc; - else if (FFlag::LuauVectorDefinitions) + else result += kBuiltinDefinitionVectorSrc_DEPRECATED; return result; diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index 736c622e1..71a5d2a75 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -193,9 +193,8 @@ static bool areTerminalAndDefinitelyDisjoint(const EType& lhs, const EType& rhs) // - Whether one of the enodes is a large semantic set such as TAny, // TUnknown, or TError. return !( - lhs.index() == rhs.index() || - lhs.get() || rhs.get() || lhs.get() || rhs.get() || lhs.get() || rhs.get() || - lhs.get() || rhs.get() || lhs.get() || rhs.get() + lhs.index() == rhs.index() || lhs.get() || rhs.get() || lhs.get() || rhs.get() || lhs.get() || + rhs.get() || lhs.get() || rhs.get() || lhs.get() || rhs.get() ); } @@ -694,7 +693,8 @@ TypeId flattenTableNode( StringId propName = t->propNames[i]; const Id propType = t->propTypes()[i]; - resultTable.props[strings.asString(propName)] = Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)}; + resultTable.props[strings.asString(propName)] = + Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)}; } } @@ -937,12 +937,20 @@ std::string mkDesc( const int RULE_PADDING = 35; const std::string rulePadding(std::max(0, RULE_PADDING - rule.size()), ' '); const std::string fromIdStr = ""; // "(" + std::to_string(uint32_t(from)) + ") "; - const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") "; + const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") "; return rule + ":" + rulePadding + fromIdStr + toString(fromTy, opts) + " <=> " + toIdStr + toString(toTy, opts); } -std::string mkDesc(EGraph& egraph, const StringCache& strings, NotNull arena, NotNull builtinTypes, Id from, Id to, const std::string& rule) +std::string mkDesc( + EGraph& egraph, + const StringCache& strings, + NotNull arena, + NotNull builtinTypes, + Id from, + Id to, + const std::string& rule +) { if (!FFlag::DebugLuauLogSimplification) return ""; @@ -1879,7 +1887,12 @@ void Simplifier::intersectWithNegatedClass(Id id) isTag(iNode) || isTag(iNode) || isTag(iNode) || isTag(iNode)) { // eg string & ~SomeClass - subst(id, iId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}); + subst( + id, + iId, + "intersectClassWithNegatedClass", + {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}} + ); return; } @@ -1887,27 +1900,37 @@ void Simplifier::intersectWithNegatedClass(Id id) { switch (relateClasses(class_, negatedClass)) { - case LeftSuper: - // eg Instance & ~Part - // This cannot be meaningfully reduced. - continue; - case RightSuper: - subst(id, egraph.add(TNever{}), "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}); - return; - case Unrelated: - // Part & ~Folder == Part + case LeftSuper: + // eg Instance & ~Part + // This cannot be meaningfully reduced. + continue; + case RightSuper: + subst( + id, + egraph.add(TNever{}), + "intersectClassWithNegatedClass", + {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}} + ); + return; + case Unrelated: + // Part & ~Folder == Part + { + std::vector newParts; + newParts.reserve(intersection->operands().size() - 1); + for (Id part : intersection->operands()) { - std::vector newParts; - newParts.reserve(intersection->operands().size() - 1); - for (Id part : intersection->operands()) - { - if (part != jId) - newParts.push_back(part); - } - - Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()}); - subst(id, substId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}); + if (part != jId) + newParts.push_back(part); } + + Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()}); + subst( + id, + substId, + "intersectClassWithNegatedClass", + {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}} + ); + } } } } diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 5819d3098..7687847b8 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -245,7 +245,6 @@ FragmentParseResult parseFragment( opts.captureComments = true; opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos}; ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts); - std::vector fabricatedAncestry = std::move(result.ancestry); // Get the ancestry for the fragment at the offset cursor position. @@ -258,6 +257,7 @@ FragmentParseResult parseFragment( fragmentResult.root = std::move(p.root); fragmentResult.ancestry = std::move(fabricatedAncestry); fragmentResult.nearestStatement = nearestStatement; + fragmentResult.commentLocations = std::move(p.commentLocations); return fragmentResult; } @@ -444,7 +444,7 @@ FragmentTypeCheckResult typecheckFragment_( } -FragmentTypeCheckResult typecheckFragment( +std::pair typecheckFragment( Frontend& frontend, const ModuleName& moduleName, const Position& cursorPos, @@ -469,12 +469,15 @@ FragmentTypeCheckResult typecheckFragment( } FragmentParseResult parseResult = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition); + if (isWithinComment(parseResult.commentLocations, fragmentEndPosition.value_or(cursorPos))) + return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; + FrontendOptions frontendOptions = opts.value_or(frontend.options); const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement); FragmentTypeCheckResult result = typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions); result.ancestry = std::move(parseResult.ancestry); - return result; + return {FragmentTypeCheckStatus::Success, result}; } @@ -498,7 +501,14 @@ FragmentAutocompleteResult fragmentAutocomplete( return {}; } - auto tcResult = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition); + // If the cursor is within a comment in the stale source module we should avoid providing a recommendation + if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition))) + return {}; + + auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition); + if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete) + return {}; + auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get(); TypeArena arenaForFragmentAutocomplete; diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 506087ba6..ceffc3074 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -977,7 +977,8 @@ struct TypeCacher : TypeOnceVisitor return false; } - bool visit(TypePackId tp, const BoundTypePack& btp) override { + bool visit(TypePackId tp, const BoundTypePack& btp) override + { traverse(btp.boundTo); if (isUncacheable(btp.boundTo)) markUncacheable(tp); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index cd133ba00..3209fd08b 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,11 +15,12 @@ #include LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection) namespace Luau { -static bool contains(Position pos, Comment comment) +static bool contains_DEPRECATED(Position pos, Comment comment) { if (comment.location.contains(pos)) return true; @@ -32,7 +33,22 @@ static bool contains(Position pos, Comment comment) return false; } -static bool isWithinComment(const std::vector& commentLocations, Position pos) +static bool contains(Position pos, Comment comment) +{ + if (comment.location.contains(pos)) + return true; + else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't + // have an end + return true; + // comments actually span the whole line - in incremental mode, we could pass a cursor outside of the current parsed comment range span, but it + // would still be 'within' the comment So, the cursor must be on the same line and the comment itself must come strictly after the `begin` + else if (comment.type == Lexeme::Comment && comment.location.end.line == pos.line && comment.location.begin <= pos) + return true; + else + return false; +} + +bool isWithinComment(const std::vector& commentLocations, Position pos) { auto iter = std::lower_bound( commentLocations.begin(), @@ -40,6 +56,11 @@ static bool isWithinComment(const std::vector& commentLocations, Positi Comment{Lexeme::Comment, Location{pos, pos}}, [](const Comment& a, const Comment& b) { + if (FFlag::LuauIncrementalAutocompleteCommentDetection) + { + if (a.type == Lexeme::Comment) + return a.location.end.line < b.location.end.line; + } return a.location.end < b.location.end; } ); @@ -47,7 +68,7 @@ static bool isWithinComment(const std::vector& commentLocations, Positi if (iter == commentLocations.end()) return false; - if (contains(pos, *iter)) + if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter)) return true; // Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends @@ -149,9 +170,9 @@ struct ClonePublicInterface : Substitution freety->scope->location, module->name, InternalError{"Free type is escaping its module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} - ); - result = builtinTypes->errorRecoveryType(); + "https://github.com/luau-lang/luau/issues"} + ); + result = builtinTypes->errorRecoveryType(); } else if (auto genericty = getMutable(result)) { @@ -173,8 +194,8 @@ struct ClonePublicInterface : Substitution ftp->scope->location, module->name, InternalError{"Free type pack is escaping its module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} - ); + "https://github.com/luau-lang/luau/issues"} + ); clonedTp = builtinTypes->errorRecoveryTypePack(); } else if (auto gtp = getMutable(clonedTp)) diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 2c3cb1622..bfa7c5325 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -1809,7 +1809,8 @@ NormalizationResult Normalizer::unionNormalWithTy( } else if (get(here.tops)) return NormalizationResult::True; - else if (get(there) || get(there) || get(there) || get(there) || get(there)) + else if (get(there) || get(there) || get(there) || get(there) || + get(there)) { if (tyvarIndex(there) <= ignoreSmallerTyvars) return NormalizationResult::True; @@ -3162,7 +3163,8 @@ NormalizationResult Normalizer::intersectNormalWithTy( } return NormalizationResult::True; } - else if (get(there) || get(there) || get(there) || get(there) || get(there)) + else if (get(there) || get(there) || get(there) || get(there) || + get(there)) { NormalizedType thereNorm{builtinTypes}; NormalizedType topNorm{builtinTypes}; diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index e84712645..32858cd1b 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -420,7 +420,8 @@ static std::optional selectOverload( TypePackId argsPack ) { - auto resolver = std::make_unique(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location); + auto resolver = + std::make_unique(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location); auto [status, overload] = resolver->selectOverload(fn, argsPack); if (status == OverloadResolver::Analysis::Ok) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 6f3a6f267..40132500d 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -1480,9 +1480,8 @@ SubtypingResult Subtyping::isCovariantWith( if (auto variadic = get(tail); variadic && variadic->hidden) { - result.orElse( - isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope).withBothComponent(TypePath::PackField::Arguments) - ); + result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope) + .withBothComponent(TypePath::PackField::Arguments)); } } } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 655abfa7e..3019bf01d 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1020,7 +1020,8 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) { reportError(OptionalValueAccess{iteratorTy}, forInStatement->values.data[0]->location); } - else if (std::optional iterMmTy = findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location)) + else if (std::optional iterMmTy = + findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location)) { Instantiation instantiation{TxnLog::empty(), &arena, builtinTypes, TypeLevel{}, scope}; diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 64680eca0..8860b2510 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -832,7 +832,12 @@ TypeFunctionReductionResult userDefinedTypeFunction( { if (FFlag::LuauUserTypeFunPrintToError) return { - std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value), ctx->typeFunctionRuntime->messages + std::nullopt, + Reduction::Erroneous, + {}, + {}, + format("'%s' type function: returned a non-type value", name.value), + ctx->typeFunctionRuntime->messages }; else return {std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value)}; @@ -2064,7 +2069,7 @@ TypeFunctionReductionResult refineTypeFunction( if (ctx->solver) { for (TypeId newTf : simplifyResult->newTypeFunctions) - ctx->solver->pushConstraint(ctx->scope, ctx->constraint->location, ReduceConstraint{newTf}); + ctx->pushConstraint(ReduceConstraint{newTf}); } return {simplifyResult->result, {}}; diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index c1ed9ff34..a89784b9c 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -464,7 +464,9 @@ class TypeFunctionDeserializer , typeFunctionRuntime(state->ctx->typeFunctionRuntime) , queue({}) , types({}) - , packs({}){}; + , packs({}) + { + } TypeId deserialize(TypeFunctionTypeId ty) { diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 1fd8f7ea2..addd34453 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -2799,10 +2799,10 @@ TypeId TypeChecker::checkRelationalOperation( reportError( expr.location, GenericError{ - format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) - } - ); - } + format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) + } + ); + } return booleanType; } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index ed7d5ebf2..6a562a3a9 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); +LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope); namespace Luau { @@ -318,6 +319,8 @@ TypePack extendTypePack( { FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType}; t = arena.addType(ft); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + trackInteriorFreeType(ftp->scope, t); } else t = arena.freshType(ftp->scope); @@ -533,7 +536,7 @@ std::vector findBlockedArgTypesIn(AstExprCall* expr, NotNull toBlock; BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}}; - for (auto arg: expr->args) + for (auto arg : expr->args) { if (isLiteral(arg) || arg->is()) { @@ -543,5 +546,21 @@ std::vector findBlockedArgTypesIn(AstExprCall* expr, NotNullparent.get()) + { + if (scope->interiorFreeTypes) + { + scope->interiorFreeTypes->push_back(ty); + return; + } + } + // There should at least be *one* generalization constraint per module + // where `interiorFreeTypes` is present, which would be the one made + // by ConstraintGenerator::visitModuleRoot. + LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member."); +} } // namespace Luau diff --git a/Ast/include/Luau/Allocator.h b/Ast/include/Luau/Allocator.h index 7fd951aeb..eaabcd8aa 100644 --- a/Ast/include/Luau/Allocator.h +++ b/Ast/include/Luau/Allocator.h @@ -45,4 +45,4 @@ class Allocator size_t offset; }; -} +} // namespace Luau diff --git a/Ast/include/Luau/Location.h b/Ast/include/Luau/Location.h index 3fc8921a5..95d4c78a3 100644 --- a/Ast/include/Luau/Location.h +++ b/Ast/include/Luau/Location.h @@ -14,12 +14,37 @@ struct Position { } - bool operator==(const Position& rhs) const; - bool operator!=(const Position& rhs) const; - bool operator<(const Position& rhs) const; - bool operator>(const Position& rhs) const; - bool operator<=(const Position& rhs) const; - bool operator>=(const Position& rhs) const; + bool operator==(const Position& rhs) const + { + return this->column == rhs.column && this->line == rhs.line; + } + + bool operator!=(const Position& rhs) const + { + return !(*this == rhs); + } + bool operator<(const Position& rhs) const + { + if (line == rhs.line) + return column < rhs.column; + else + return line < rhs.line; + } + bool operator>(const Position& rhs) const + { + if (line == rhs.line) + return column > rhs.column; + else + return line > rhs.line; + } + bool operator<=(const Position& rhs) const + { + return *this == rhs || *this < rhs; + } + bool operator>=(const Position& rhs) const + { + return *this == rhs || *this > rhs; + } void shift(const Position& start, const Position& oldEnd, const Position& newEnd); }; @@ -52,8 +77,14 @@ struct Location { } - bool operator==(const Location& rhs) const; - bool operator!=(const Location& rhs) const; + bool operator==(const Location& rhs) const + { + return this->begin == rhs.begin && this->end == rhs.end; + } + bool operator!=(const Location& rhs) const + { + return !(*this == rhs); + } bool encloses(const Location& l) const; bool overlaps(const Location& l) const; diff --git a/Ast/src/Allocator.cpp b/Ast/src/Allocator.cpp index f8a99db45..c7614d8ca 100644 --- a/Ast/src/Allocator.cpp +++ b/Ast/src/Allocator.cpp @@ -63,4 +63,4 @@ void* Allocator::allocate(size_t size) return page->data; } -} +} // namespace Luau diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 7e0efd435..8e5befadc 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -1151,10 +1151,7 @@ void AstTypePackGeneric::visit(AstVisitor* visitor) bool isLValue(const AstExpr* expr) { - return expr->is() - || expr->is() - || expr->is() - || expr->is(); + return expr->is() || expr->is() || expr->is() || expr->is(); } AstName getIdentifier(AstExpr* node) diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 86b440449..9aea49686 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -9,6 +9,8 @@ #include LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2) +LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart) + namespace Luau { @@ -759,7 +761,7 @@ Lexeme Lexer::readNext() return Lexeme(Location(start, 1), '}'); } - return readInterpolatedStringSection(position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd); + return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd); } case '=': diff --git a/Ast/src/Location.cpp b/Ast/src/Location.cpp index c2c66d9f2..e96fafb7e 100644 --- a/Ast/src/Location.cpp +++ b/Ast/src/Location.cpp @@ -4,42 +4,6 @@ namespace Luau { -bool Position::operator==(const Position& rhs) const -{ - return this->column == rhs.column && this->line == rhs.line; -} - -bool Position::operator!=(const Position& rhs) const -{ - return !(*this == rhs); -} - -bool Position::operator<(const Position& rhs) const -{ - if (line == rhs.line) - return column < rhs.column; - else - return line < rhs.line; -} - -bool Position::operator>(const Position& rhs) const -{ - if (line == rhs.line) - return column > rhs.column; - else - return line > rhs.line; -} - -bool Position::operator<=(const Position& rhs) const -{ - return *this == rhs || *this < rhs; -} - -bool Position::operator>=(const Position& rhs) const -{ - return *this == rhs || *this > rhs; -} - void Position::shift(const Position& start, const Position& oldEnd, const Position& newEnd) { if (*this >= start) @@ -54,16 +18,6 @@ void Position::shift(const Position& start, const Position& oldEnd, const Positi } } -bool Location::operator==(const Location& rhs) const -{ - return this->begin == rhs.begin && this->end == rhs.end; -} - -bool Location::operator!=(const Location& rhs) const -{ - return !(*this == rhs); -} - bool Location::encloses(const Location& l) const { return begin <= l.begin && end >= l.end; diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index e821902e3..fcb746940 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -18,7 +18,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // flag so that we don't break production games by reverting syntax changes. // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunParseExport) LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) @@ -936,12 +935,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported) Lexeme matchFn = lexer.current(); nextLexeme(); - if (!FFlag::LuauUserDefinedTypeFunParseExport) - { - if (exported) - report(start, "Type function cannot be exported"); - } - // parse the name of the type function std::optional fnName = parseNameOpt("type function name"); if (!fnName) @@ -2239,7 +2232,8 @@ std::optional Parser::checkBinaryConfusables(const BinaryOpPr report(Location(start, next.location), "Unexpected '||'; did you mean 'or'?"); return AstExprBinary::Or; } - else if (curr.type == '!' && next.type == '=' && curr.location.end == next.location.begin && binaryPriority[AstExprBinary::CompareNe].left > limit) + else if (curr.type == '!' && next.type == '=' && curr.location.end == next.location.begin && + binaryPriority[AstExprBinary::CompareNe].left > limit) { nextLexeme(); report(Location(start, next.location), "Unexpected '!='; did you mean '~='?"); @@ -2587,7 +2581,8 @@ AstExpr* Parser::parseSimpleExpr() { return parseNumber(); } - else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::InterpStringSimple) + else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || + lexer.current().type == Lexeme::InterpStringSimple) { return parseString(); } diff --git a/CLI/src/Compile.cpp b/CLI/src/Compile.cpp index e8498beb6..6f41b42d9 100644 --- a/CLI/src/Compile.cpp +++ b/CLI/src/Compile.cpp @@ -341,7 +341,8 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks); bcb.setDumpSource(*source); } - else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr || format == CompileFormat::CodegenVerbose) + else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr || + format == CompileFormat::CodegenVerbose) { bcb.setDumpFlags( Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | diff --git a/CLI/src/FileUtils.cpp b/CLI/src/FileUtils.cpp index fcdcad9e0..2207f6785 100644 --- a/CLI/src/FileUtils.cpp +++ b/CLI/src/FileUtils.cpp @@ -147,7 +147,7 @@ std::string resolvePath(std::string_view path, std::string_view baseFilePath) if (baseFilePathComponents.empty()) { if (isResolvedPathRelative) - numPrependedParents++; // "../" will later be added to the beginning of the resolved path + numPrependedParents++; // "../" will later be added to the beginning of the resolved path } else if (baseFilePathComponents.back() != "..") { diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index 9364f4619..c0737ae86 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -7,6 +7,8 @@ #include #include +struct Proto; + namespace Luau { namespace CodeGen @@ -23,6 +25,7 @@ struct IrToStringContext const std::vector& blocks; const std::vector& constants; const CfgInfo& cfg; + Proto* proto = nullptr; }; void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index); diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index f0f1ec8e9..134a49f21 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -848,7 +848,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = LBC_TYPE_NUMBER; else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); bcType.result = regTags[ra]; @@ -879,7 +880,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; } - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) { regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); } @@ -901,7 +903,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER) regTags[ra] = LBC_TYPE_NUMBER; - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); bcType.result = regTags[ra]; @@ -923,7 +926,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = LBC_TYPE_NUMBER; else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); bcType.result = regTags[ra]; @@ -954,7 +958,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; } - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) { regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); } @@ -976,7 +981,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER) regTags[ra] = LBC_TYPE_NUMBER; - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); bcType.result = regTags[ra]; @@ -997,7 +1003,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = LBC_TYPE_NUMBER; else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); bcType.result = regTags[ra]; @@ -1026,7 +1033,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; } - else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && + (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) { regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); } diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 2850dd152..f22b23796 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -166,7 +166,7 @@ bool isSupported() if (sizeof(LuaNode) != 32) return false; - // Windows CRT uses stack unwinding in longjmp so we have to use unwind data; on other platforms, it's only necessary for C++ EH. + // Windows CRT uses stack unwinding in longjmp so we have to use unwind data; on other platforms, it's only necessary for C++ EH. #if defined(_WIN32) if (!isUnwindSupported()) return false; diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 03eaabea6..f41211cd2 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -101,7 +101,7 @@ inline bool lowerImpl( bool outputEnabled = options.includeAssembly || options.includeIr; - IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg}; + IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg, function.proto}; // We use this to skip outlined fallback blocks from IR/asm text output size_t textSize = build.text.length(); diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index 9bda7c814..7244e6cce 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -18,6 +18,8 @@ #include +LUAU_DYNAMIC_FASTFLAG(LuauPopIncompleteCi) + // All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT // This makes sure that we save the pc (in case the Lua call needs to generate a backtrace) before the call, // and restores the stack pointer after in case stack gets reallocated @@ -191,7 +193,14 @@ Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults) // note: this reallocs stack, but we don't need to VM_PROTECT this // this is because we're going to modify base/savedpc manually anyhow // crucially, we can't use ra/argtop after this line - luaD_checkstack(L, ccl->stacksize); + if (DFFlag::LuauPopIncompleteCi) + { + luaD_checkstackfornewci(L, ccl->stacksize); + } + else + { + luaD_checkstack(L, ccl->stacksize); + } return ccl; } @@ -261,7 +270,14 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults) // note: this reallocs stack, but we don't need to VM_PROTECT this // this is because we're going to modify base/savedpc manually anyhow // crucially, we can't use ra/argtop after this line - luaD_checkstack(L, ccl->stacksize); + if (DFFlag::LuauPopIncompleteCi) + { + luaD_checkstackfornewci(L, ccl->stacksize); + } + else + { + luaD_checkstack(L, ccl->stacksize); + } LUAU_ASSERT(ci->top <= L->stack_last); diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index 0d2f9bd38..0d4b0a1f6 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -684,7 +684,7 @@ void computeCfgDominanceTreeChildren(IrFunction& function) info.domChildrenOffsets[domParent]++; } - // Convert counds to offsets using prefix sum + // Convert counts to offsets using prefix sum uint32_t total = 0; for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++) diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index a59db8e8a..fe6a2397c 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -4,6 +4,8 @@ #include "Luau/IrUtils.h" #include "lua.h" +#include "lobject.h" +#include "lstate.h" #include @@ -19,6 +21,7 @@ static const char* textForCondition[] = static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered"); const int kDetailsAlignColumn = 60; +const unsigned kMaxStringConstantPrintLength = 16; LUAU_PRINTF_ATTR(2, 3) static void append(std::string& result, const char* fmt, ...) @@ -39,6 +42,17 @@ static void padToDetailColumn(std::string& result, size_t lineStart) result.append(pad, ' '); } +static bool isPrintableStringConstant(const char* str, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + if (unsigned(str[i]) < ' ') + return false; + } + + return true; +} + static const char* getTagName(uint8_t tag) { switch (tag) @@ -431,6 +445,53 @@ void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index) append(ctx.result, "%s_%u", getBlockKindName(block.kind), index); } +static void appendVmConstant(std::string& result, Proto* proto, int index) +{ + TValue constant = proto->k[index]; + + if (constant.tt == LUA_TNIL) + { + append(result, "nil"); + } + else if (constant.tt == LUA_TBOOLEAN) + { + append(result, constant.value.b != 0 ? "true" : "false"); + } + else if (constant.tt == LUA_TNUMBER) + { + if (constant.value.n != constant.value.n) + append(result, "nan"); + else + append(result, "%.17g", constant.value.n); + } + else if (constant.tt == LUA_TSTRING) + { + TString* str = gco2ts(constant.value.gc); + const char* data = getstr(str); + + if (isPrintableStringConstant(data, str->len)) + { + if (str->len < kMaxStringConstantPrintLength) + append(result, "'%.*s'", int(str->len), data); + else + append(result, "'%.*s'...", int(kMaxStringConstantPrintLength), data); + } + } + else if (constant.tt == LUA_TVECTOR) + { + const float* v = constant.value.v; + +#if LUA_VECTOR_SIZE == 4 + if (v[3] != 0) + append(result, "%.9g, %.9g, %.9g, %.9g", v[0], v[1], v[2], v[3]); + else + append(result, "%.9g, %.9g, %.9g", v[0], v[1], v[2]); +#else + append(result, "%.9g, %.9g, %.9g", v[0], v[1], v[2]); +#endif + } +} + void toString(IrToStringContext& ctx, IrOp op) { switch (op.kind) @@ -458,6 +519,14 @@ void toString(IrToStringContext& ctx, IrOp op) break; case IrOpKind::VmConst: append(ctx.result, "K%d", vmConstOp(op)); + + if (ctx.proto) + { + append(ctx.result, " ("); + appendVmConstant(ctx.result, ctx.proto, vmConstOp(op)); + append(ctx.result, ")"); + } + break; case IrOpKind::VmUpvalue: append(ctx.result, "U%d", vmUpvalueOp(op)); @@ -770,7 +839,7 @@ void toStringDetailed( std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo) { std::string result; - IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto}; for (size_t i = 0; i < function.blocks.size(); i++) { @@ -877,7 +946,7 @@ static void appendBlocks(IrToStringContext& ctx, const IrFunction& function, boo std::string toDot(const IrFunction& function, bool includeInst) { std::string result; - IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto}; append(ctx.result, "digraph CFG {\n"); append(ctx.result, "node[shape=record]\n"); @@ -924,7 +993,7 @@ std::string toDot(const IrFunction& function, bool includeInst) std::string toDotCfg(const IrFunction& function) { std::string result; - IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto}; append(ctx.result, "digraph CFG {\n"); append(ctx.result, "node[shape=record]\n"); @@ -947,7 +1016,7 @@ std::string toDotCfg(const IrFunction& function) std::string toDotDjGraph(const IrFunction& function) { std::string result; - IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto}; append(ctx.result, "digraph CFG {\n"); diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 9c755563c..5920f7cc5 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -18,9 +19,11 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) +LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauVectorLibNativeDot); -LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt); +LUAU_FASTFLAG(LuauVectorLibNativeDot) +LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt) +LUAU_FASTFLAGVARIABLE(LuauCodeGenLimitLiveSlotReuse) namespace Luau { @@ -50,6 +53,14 @@ struct RegisterLink uint32_t version = 0; }; +// Reference to an instruction together with the position of that instruction in the current block chain and the last position of reuse +struct NumberedInstruction +{ + uint32_t instIdx = 0; + uint32_t startPos = 0; + uint32_t finishPos = 0; +}; + // Data we know about the current VM state struct ConstPropState { @@ -190,7 +201,11 @@ struct ConstPropState // Same goes for table array elements as well void invalidateHeapTableData() { - getSlotNodeCache.clear(); + if (FFlag::LuauCodeGenLimitLiveSlotReuse) + getSlotNodeCache.clear(); + else + getSlotNodeCache_DEPRECATED.clear(); + checkSlotMatchCache.clear(); getArrAddrCache.clear(); @@ -409,6 +424,64 @@ struct ConstPropState valueMap[versionedVmRegLoad(loadCmd, storeInst.a)] = storeInst.b.index; } + // Used to compute the pressure of the cached value 'set' on the spill registers + // We want to find out the maximum live range intersection count between the cached value at 'slot' and current instruction + // Note that this pressure is approximate, as some values that might have been live at one point could have been marked dead later + int getMaxInternalOverlap(std::vector& set, size_t slot) + { + CODEGEN_ASSERT(FFlag::LuauCodeGenLimitLiveSlotReuse); + + // Start with one live range for the slot we want to reuse + int curr = 1; + + // For any slots where lifetime began before the slot of interest, mark as live if lifetime end is still active + // This saves us from processing slots [0; slot] in the range sweep later, which requires sorting the lifetime end points + for (size_t i = 0; i < slot; i++) + { + if (set[i].finishPos >= set[slot].startPos) + curr++; + } + + int max = curr; + + // Collect lifetime end points and sort them + rangeEndTemp.clear(); + + for (size_t i = slot + 1; i < set.size(); i++) + rangeEndTemp.push_back(set[i].finishPos); + + std::sort(rangeEndTemp.begin(), rangeEndTemp.end()); + + // Go over the lifetime begin/end ranges that we store as separate array and walk based on the smallest of values + for (size_t i1 = slot + 1, i2 = 0; i1 < set.size() && i2 < rangeEndTemp.size();) + { + if (rangeEndTemp[i2] == set[i1].startPos) + { + i1++; + i2++; + } + else if (rangeEndTemp[i2] < set[i1].startPos) + { + CODEGEN_ASSERT(curr > 0); + + curr--; + i2++; + } + else + { + curr++; + i1++; + + if (curr > max) + max = curr; + } + } + + // We might have unprocessed lifetime end entries, but we will never have unprocessed lifetime start entries + // Not that lifetime end entries can only decrease the current value and do not affect the end result (maximum) + return max; + } + void clear() { for (int i = 0; i <= maxReg; ++i) @@ -416,6 +489,9 @@ struct ConstPropState maxReg = 0; + if (FFlag::LuauCodeGenLimitLiveSlotReuse) + instPos = 0u; + inSafeEnv = false; checkedGc = false; @@ -436,6 +512,9 @@ struct ConstPropState // For range/full invalidations, we only want to visit a limited number of data that we have recorded int maxReg = 0; + // Number of the instruction being processed + uint32_t instPos = 0; + bool inSafeEnv = false; bool checkedGc = false; @@ -447,7 +526,8 @@ struct ConstPropState std::vector tryNumToIndexCache; // Fallback block argument might be different // Heap changes might affect table state - std::vector getSlotNodeCache; // Additionally, pcpos argument might be different + std::vector getSlotNodeCache; // Additionally, pcpos argument might be different + std::vector getSlotNodeCache_DEPRECATED; // Additionally, pcpos argument might be different std::vector checkSlotMatchCache; // Additionally, fallback block argument might be different std::vector getArrAddrCache; @@ -457,6 +537,8 @@ struct ConstPropState // Userdata tag cache can point to both NEW_USERDATA and CHECK_USERDATA_TAG instructions std::vector useradataTagCache; // Additionally, fallback block argument might be different + + std::vector rangeEndTemp; }; static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults) @@ -570,6 +652,9 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index) { + if (FFlag::LuauCodeGenLimitLiveSlotReuse) + state.instPos++; + switch (inst.cmd) { case IrCmd::LOAD_TAG: @@ -1176,19 +1261,49 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.getArrAddrCache.push_back(index); break; case IrCmd::GET_SLOT_NODE_ADDR: - for (uint32_t prevIdx : state.getSlotNodeCache) + if (FFlag::LuauCodeGenLimitLiveSlotReuse) { - const IrInst& prev = function.instructions[prevIdx]; - - if (prev.a == inst.a && prev.c == inst.c) + for (size_t i = 0; i < state.getSlotNodeCache.size(); i++) { - substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); - return; // Break out from both the loop and the switch + auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i]; + + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.c == inst.c) + { + // Check if this reuse will increase the overall register pressure over the limit + int limit = FInt::LuauCodeGenLiveSlotReuseLimit; + + if (int(state.getSlotNodeCache.size()) > limit && state.getMaxInternalOverlap(state.getSlotNodeCache, i) > limit) + return; + + // Update live range of the value from the optimization standpoint + lastNum = state.instPos; + + substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); + return; // Break out from both the loop and the switch + } } + + if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.getSlotNodeCache.push_back({index, state.instPos, state.instPos}); } + else + { + for (uint32_t prevIdx : state.getSlotNodeCache_DEPRECATED) + { + const IrInst& prev = function.instructions[prevIdx]; + + if (prev.a == inst.a && prev.c == inst.c) + { + substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); + return; // Break out from both the loop and the switch + } + } - if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit) - state.getSlotNodeCache.push_back(index); + if (int(state.getSlotNodeCache_DEPRECATED.size()) < FInt::LuauCodeGenReuseSlotLimit) + state.getSlotNodeCache_DEPRECATED.push_back(index); + } break; case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_CLOSURE_UPVAL_ADDR: diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index c534bcb41..93fbd8d78 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -13,7 +13,7 @@ inline bool isFlagExperimental(const char* flag) static const char* const kList[] = { "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative - "StudioReportLuauAny2", // takes telemetry data for usage of any types + "StudioReportLuauAny2", // takes telemetry data for usage of any types "LuauSolverV2", // makes sure we always have at least one entry nullptr, diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index 902d74eab..af0a5f021 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -7,7 +7,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauVectorBuiltins) LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins) LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp) @@ -229,7 +228,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op return LBF_BUFFER_WRITEF64; } - if (FFlag::LuauVectorBuiltins && builtin.object == "vector") + if (builtin.object == "vector") { if (builtin.method == "create") return LBF_VECTOR; diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 685d94faa..469856285 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -1751,7 +1751,8 @@ void BytecodeBuilder::validateVariadic() const // variadic sequence since they are never executed if FASTCALL does anything, so it's okay to skip their validation until CALL // (we can't simply start a variadic sequence here because that would trigger assertions during linked CALL validation) } - else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL || op == LOP_GETTABLEKS || op == LOP_COVERAGE) + else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL || + op == LOP_GETTABLEKS || op == LOP_COVERAGE) { // instructions inside a variadic sequence must be neutral (can't change L->top) // while there are many neutral instructions like this, here we check that the instruction is one of the few diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index da945b354..8076d157d 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -1624,7 +1624,8 @@ struct Compiler return; } } - else if (FFlag::LuauCompileOptimizeRevArith && options.optimizationLevel >= 2 && (expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul)) + else if (FFlag::LuauCompileOptimizeRevArith && options.optimizationLevel >= 2 && + (expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul)) { // Optimization: replace k*r with r*k when r is known to be a number (otherwise metamethods may be called) if (LuauBytecodeType* ty = exprTypes.find(expr); ty && *ty == LBC_TYPE_NUMBER) @@ -4408,7 +4409,7 @@ void setCompileConstantString(CompileConstant* constant, const char* s, size_t l CompileError::raise({}, "Exceeded custom string constant length limit"); target->type = Compile::Constant::Type_String; - target->stringLength = l; + target->stringLength = unsigned(l); target->valueString = s; } diff --git a/Compiler/src/CostModel.cpp b/Compiler/src/CostModel.cpp index 4c8e13c6f..04adf3e31 100644 --- a/Compiler/src/CostModel.cpp +++ b/Compiler/src/CostModel.cpp @@ -130,7 +130,8 @@ struct CostVisitor : AstVisitor { return model(expr->expr); } - else if (node->is() || node->is() || node->is() || node->is()) + else if (node->is() || node->is() || node->is() || + node->is()) { return Cost(0, Cost::kLiteral); } diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index 41363ce1b..fd9074a11 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -3,7 +3,6 @@ #include "Luau/BytecodeBuilder.h" -LUAU_FASTFLAGVARIABLE(LuauCompileVectorTypeInfo) LUAU_FASTFLAG(LuauCompileLibraryConstants) namespace Luau @@ -32,7 +31,7 @@ static LuauBytecodeType getPrimitiveType(AstName name) return LBC_TYPE_THREAD; else if (name == "buffer") return LBC_TYPE_BUFFER; - else if (FFlag::LuauCompileVectorTypeInfo && name == "vector") + else if (name == "vector") return LBC_TYPE_VECTOR; else if (name == "any" || name == "unknown") return LBC_TYPE_ANY; diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 5dae6f032..44cbe2e52 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -305,7 +305,8 @@ static Error parseJson(const std::string& contents, Action action) arrayTop = (lexer.current().type == '['); next(lexer); } - else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue || lexer.current().type == Lexeme::ReservedFalse) + else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue || + lexer.current().type == Lexeme::ReservedFalse) { std::string value = lexer.current().type == Lexeme::QuotedString ? std::string(lexer.current().data, lexer.current().getLength()) diff --git a/EqSat/include/Luau/EGraph.h b/EqSat/include/Luau/EGraph.h index e8cc2e35f..6af79b774 100644 --- a/EqSat/include/Luau/EGraph.h +++ b/EqSat/include/Luau/EGraph.h @@ -198,7 +198,13 @@ struct EGraph final { // An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where // canonicalize(𝑓(𝑎1, 𝑎2, ...)) = 𝑓(find(𝑎1), find(𝑎2), ...). - Luau::EqSat::canonicalize(enode, [&](Id id) { return find(id); }); + Luau::EqSat::canonicalize( + enode, + [&](Id id) + { + return find(id); + } + ); } bool isCanonical(const L& enode) const diff --git a/EqSat/include/Luau/Language.h b/EqSat/include/Luau/Language.h index c4b60f972..f9d3aa4dc 100644 --- a/EqSat/include/Luau/Language.h +++ b/EqSat/include/Luau/Language.h @@ -244,7 +244,7 @@ struct NodeVector template struct NodeSet { - template + template friend void canonicalize(NodeSet& node, Find&& find); template @@ -302,7 +302,7 @@ struct Language final template using WithinDomain = std::disjunction, Ts>...>; - template + template friend void canonicalize(Language& enode, Find&& find); template @@ -388,7 +388,7 @@ struct Language final VariantTy v; }; -template +template void canonicalize(Node& node, Find&& find) { // An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where @@ -398,7 +398,7 @@ void canonicalize(Node& node, Find&& find) } // Canonicalizing the Ids in a NodeSet may result in the set decreasing in size. -template +template void canonicalize(NodeSet& node, Find&& find) { for (Id& id : node.vector) @@ -409,7 +409,7 @@ void canonicalize(NodeSet& node, Find&& find) node.vector.erase(endIt, end(node.vector)); } -template +template void canonicalize(Language& enode, Find&& find) { visit( diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 178261fb3..179e41711 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -10,6 +10,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods) + // while C API returns 'size_t' for binary compatibility in case of future extensions, // in the current implementation, length and offset are limited to 31 bits // because offset is limited to an integer, a single 64bit comparison can be used and will not overflow @@ -247,6 +249,95 @@ static int buffer_fill(lua_State* L) return 0; } +static int buffer_readbits(lua_State* L) +{ + size_t len = 0; + void* buf = luaL_checkbuffer(L, 1, &len); + int64_t bitoffset = (int64_t)luaL_checknumber(L, 2); + int bitcount = luaL_checkinteger(L, 3); + + if (bitoffset < 0) + luaL_error(L, "buffer access out of bounds"); + + if (unsigned(bitcount) > 32) + luaL_error(L, "bit count is out of range of [0; 32]"); + + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) + luaL_error(L, "buffer access out of bounds"); + + unsigned startbyte = unsigned(bitoffset / 8); + unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8); + + uint64_t data = 0; + memcpy(&data, (char*)buf + startbyte, endbyte - startbyte); + + uint64_t subbyteoffset = bitoffset & 0x7; + uint64_t mask = (1ull << bitcount) - 1; + + lua_pushunsigned(L, unsigned((data >> subbyteoffset) & mask)); + return 1; +} + +static int buffer_writebits(lua_State* L) +{ + size_t len = 0; + void* buf = luaL_checkbuffer(L, 1, &len); + int64_t bitoffset = (int64_t)luaL_checknumber(L, 2); + int bitcount = luaL_checkinteger(L, 3); + unsigned value = luaL_checkunsigned(L, 4); + + if (bitoffset < 0) + luaL_error(L, "buffer access out of bounds"); + + if (unsigned(bitcount) > 32) + luaL_error(L, "bit count is out of range of [0; 32]"); + + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) + luaL_error(L, "buffer access out of bounds"); + + unsigned startbyte = unsigned(bitoffset / 8); + unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8); + + uint64_t data = 0; + memcpy(&data, (char*)buf + startbyte, endbyte - startbyte); + + uint64_t subbyteoffset = bitoffset & 0x7; + uint64_t mask = ((1ull << bitcount) - 1) << subbyteoffset; + + data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask); + + memcpy((char*)buf + startbyte, &data, endbyte - startbyte); + return 0; +} + +static const luaL_Reg bufferlib_DEPRECATED[] = { + {"create", buffer_create}, + {"fromstring", buffer_fromstring}, + {"tostring", buffer_tostring}, + {"readi8", buffer_readinteger}, + {"readu8", buffer_readinteger}, + {"readi16", buffer_readinteger}, + {"readu16", buffer_readinteger}, + {"readi32", buffer_readinteger}, + {"readu32", buffer_readinteger}, + {"readf32", buffer_readfp}, + {"readf64", buffer_readfp}, + {"writei8", buffer_writeinteger}, + {"writeu8", buffer_writeinteger}, + {"writei16", buffer_writeinteger}, + {"writeu16", buffer_writeinteger}, + {"writei32", buffer_writeinteger}, + {"writeu32", buffer_writeinteger}, + {"writef32", buffer_writefp}, + {"writef64", buffer_writefp}, + {"readstring", buffer_readstring}, + {"writestring", buffer_writestring}, + {"len", buffer_len}, + {"copy", buffer_copy}, + {"fill", buffer_fill}, + {NULL, NULL}, +}; + static const luaL_Reg bufferlib[] = { {"create", buffer_create}, {"fromstring", buffer_fromstring}, @@ -272,12 +363,14 @@ static const luaL_Reg bufferlib[] = { {"len", buffer_len}, {"copy", buffer_copy}, {"fill", buffer_fill}, + {"readbits", buffer_readbits}, + {"writebits", buffer_writebits}, {NULL, NULL}, }; int luaopen_buffer(lua_State* L) { - luaL_register(L, LUA_BUFFERLIBNAME, bufferlib); + luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED); return 1; } diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 28ab00b63..f9fe30d62 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -18,6 +18,7 @@ #include LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStackLimit, false) +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauPopIncompleteCi, false) // keep max stack allocation request under 1GB #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) @@ -179,11 +180,23 @@ static void correctstack(lua_State* L, TValue* oldstack) L->base = (L->base - oldstack) + L->stack; } -void luaD_reallocstack(lua_State* L, int newsize) +void luaD_reallocstack(lua_State* L, int newsize, int fornewci) { // throw 'out of memory' error because space for a custom error message cannot be guaranteed here if (DFFlag::LuauStackLimit && newsize > MAX_STACK_SIZE) + { + // reallocation was performaed to setup a new CallInfo frame, which we have to remove + if (DFFlag::LuauPopIncompleteCi && fornewci) + { + CallInfo* cip = L->ci - 1; + + L->ci = cip; + L->base = cip->base; + L->top = cip->top; + } + luaD_throw(L, LUA_ERRMEM); + } TValue* oldstack = L->stack; int realsize = newsize + EXTRA_STACK; @@ -208,10 +221,17 @@ void luaD_reallocCI(lua_State* L, int newsize) void luaD_growstack(lua_State* L, int n) { - if (n <= L->stacksize) // double size is enough? - luaD_reallocstack(L, 2 * L->stacksize); + if (DFFlag::LuauPopIncompleteCi) + { + luaD_reallocstack(L, getgrownstacksize(L, n), 0); + } else - luaD_reallocstack(L, L->stacksize + n); + { + if (n <= L->stacksize) // double size is enough? + luaD_reallocstack(L, 2 * L->stacksize, 0); + else + luaD_reallocstack(L, L->stacksize + n, 0); + } } CallInfo* luaD_growCI(lua_State* L) diff --git a/VM/src/ldo.h b/VM/src/ldo.h index 0f7b42ad4..707af0ee9 100644 --- a/VM/src/ldo.h +++ b/VM/src/ldo.h @@ -7,11 +7,21 @@ #include "luaconf.h" #include "ldebug.h" +// returns target stack for 'n' extra elements to reallocate +// if possible, stack size growth factor is 2x +#define getgrownstacksize(L, n) ((n) <= L->stacksize ? 2 * L->stacksize : L->stacksize + (n)) + +#define luaD_checkstackfornewci(L, n) \ + if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ + luaD_reallocstack(L, getgrownstacksize(L, (n)), 1); \ + else \ + condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 1)); + #define luaD_checkstack(L, n) \ if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ luaD_growstack(L, n); \ else \ - condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); + condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); #define incr_top(L) \ { \ @@ -47,7 +57,7 @@ LUAI_FUNC CallInfo* luaD_growCI(lua_State* L); LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults); LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize); -LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize); +LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci); LUAI_FUNC void luaD_growstack(lua_State* L, int n); LUAI_FUNC void luaD_checkCstack(lua_State* L); diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 513a3a5a8..d9843ddc6 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -442,7 +442,7 @@ static void shrinkstack(lua_State* L) condhardstacktests(luaD_reallocCI(L, ci_used + 1)); if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize) - luaD_reallocstack(L, L->stacksize / 2); // still big enough... + luaD_reallocstack(L, L->stacksize / 2, 0); // still big enough... condhardstacktests(luaD_reallocstack(L, s_used)); } diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 737583d42..7bc99c358 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -463,6 +463,7 @@ static const luaL_Reg mathlib[] = { {"clamp", math_clamp}, {"sign", math_sign}, {"round", math_round}, + {"map", math_map}, {NULL, NULL}, }; @@ -483,10 +484,10 @@ int luaopen_math(lua_State* L) lua_pushnumber(L, HUGE_VAL); lua_setfield(L, -2, "huge"); - if (FFlag::LuauMathMap) + if (FFlag::LuauMathLerp) { - lua_pushcfunction(L, math_map, "map"); - lua_setfield(L, -2, "map"); + lua_pushcfunction(L, math_lerp, "lerp"); + lua_setfield(L, -2, "lerp"); } if (FFlag::LuauMathLerp) diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index f65d79dc7..6fe82b305 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -192,7 +192,7 @@ struct SizeClassConfig const SizeClassConfig kSizeClassConfig; // size class for a block of size sz; returns -1 for size=0 because empty allocations take no space -#define sizeclass(sz) (size_t((sz)-1) < kMaxSmallSizeUsed ? kSizeClassConfig.classForSize[sz] : -1) +#define sizeclass(sz) (size_t((sz) - 1) < kMaxSmallSizeUsed ? kSizeClassConfig.classForSize[sz] : -1) // metadata for a block is stored in the first pointer of the block #define metadata(block) (*(void**)(block)) diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index 6b7a9aa0d..ddb1e12e4 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -149,7 +149,7 @@ void lua_resetthread(lua_State* L) L->nCcalls = L->baseCcalls = 0; // clear thread stack if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK) - luaD_reallocstack(L, BASIC_STACK_SIZE); + luaD_reallocstack(L, BASIC_STACK_SIZE, 0); for (int i = 0; i < L->stacksize; i++) setnilvalue(L->stack + i); } diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index d73f64963..e3a310ca9 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,6 +16,8 @@ #include +LUAU_DYNAMIC_FASTFLAG(LuauPopIncompleteCi) + // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ #if __has_warning("-Wc99-designator") @@ -935,7 +937,14 @@ static void luau_execute(lua_State* L) // note: this reallocs stack, but we don't need to VM_PROTECT this // this is because we're going to modify base/savedpc manually anyhow // crucially, we can't use ra/argtop after this line - luaD_checkstack(L, ccl->stacksize); + if (DFFlag::LuauPopIncompleteCi) + { + luaD_checkstackfornewci(L, ccl->stacksize); + } + else + { + luaD_checkstack(L, ccl->stacksize); + } LUAU_ASSERT(ci->top <= L->stack_last); @@ -3071,7 +3080,14 @@ int luau_precall(lua_State* L, StkId func, int nresults) L->base = ci->base; // Note: L->top is assigned externally - luaD_checkstack(L, ccl->stacksize); + if (DFFlag::LuauPopIncompleteCi) + { + luaD_checkstackfornewci(L, ccl->stacksize); + } + else + { + luaD_checkstack(L, ccl->stacksize); + } LUAU_ASSERT(ci->top <= L->stack_last); if (!ccl->isC) diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 749960711..6c55a140b 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(StudioReportLuauAny2) +LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) struct ATSFixture : BuiltinsFixture @@ -97,7 +98,10 @@ end LUAU_ASSERT(module->ats.typeInfo.size() == 3); LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"); + LUAU_ASSERT( + module->ats.typeInfo[0].node == + "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend" + ); } TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret") @@ -581,6 +585,9 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + // Previously we'd report an error because number <: 'a is not a + // supertype. + {FFlag::LuauTrackInteriorFreeTypesOnScope, true} }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -632,7 +639,7 @@ initialize() )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(5, result1); + LUAU_REQUIRE_ERROR_COUNT(4, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 0424e3df9..5e65a9e19 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -2232,7 +2232,7 @@ local ec = e(f@5) TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_for_overloads") { if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly - // (overloads and singletons) + // (overloads and singletons) return; check(R"( local target: ((number) -> string) & ((string) -> number)) @@ -2582,7 +2582,7 @@ end TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys") { if (FFlag::LuauSolverV2) // CLI-116812 AutocompleteTest.suggest_table_keys needs to populate expected types for nested - // tables without an annotation + // tables without an annotation return; check(R"( @@ -3069,7 +3069,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") { if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly - // (overloads and singletons) + // (overloads and singletons) return; check(R"( @@ -4293,8 +4293,7 @@ end foo(@1) )"); - const std::optional EXPECTED_INSERT = - FFlag::LuauSolverV2 ? "function(...: number): number end" : "function(...): number end"; + const std::optional EXPECTED_INSERT = FFlag::LuauSolverV2 ? "function(...: number): number end" : "function(...): number end"; auto ac = autocomplete('1'); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 56201b324..e98926ac1 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -23,10 +23,8 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauRecursionLimit) -LUAU_FASTFLAG(LuauCompileVectorTypeInfo) LUAU_FASTFLAG(LuauCompileOptimizeRevArith) LUAU_FASTFLAG(LuauCompileLibraryConstants) -LUAU_FASTFLAG(LuauVectorBuiltins) LUAU_FASTFLAG(LuauVectorFolding) LUAU_FASTFLAG(LuauCompileDisabledBuiltins) @@ -1492,7 +1490,6 @@ RETURN R0 1 TEST_CASE("ConstantFoldVectorArith") { - ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true}; ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true}; CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a + b", 0, 2), R"( @@ -1560,7 +1557,6 @@ RETURN R0 1 TEST_CASE("ConstantFoldVectorArith4Wide") { - ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true}; ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true}; CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a + b", 0, 2), R"( @@ -5137,7 +5133,6 @@ RETURN R0 1 TEST_CASE("VectorConstantFields") { - ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true}; ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true}; CHECK_EQ("\n" + compileFunction("return vector.one, vector.zero", 0, 2), R"( @@ -8666,8 +8661,6 @@ end TEST_CASE("BuiltinTypeVector") { - ScopedFastFlag luauCompileVectorTypeInfo{FFlag::LuauCompileVectorTypeInfo, true}; - CHECK_EQ( "\n" + compileTypeTable(R"( function myfunc(test: Instance, pos: vector) diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 167a88ad3..e68ce2c79 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -31,17 +31,16 @@ extern int optimizationLevel; void luaC_fullgc(lua_State* L); void luaC_validate(lua_State* L); -LUAU_FASTFLAG(LuauMathMap) LUAU_FASTFLAG(LuauMathLerp) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauStackLimit) -LUAU_FASTFLAG(LuauVectorDefinitions) LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers) LUAU_FASTFLAG(LuauVectorLibNativeCodegen) LUAU_FASTFLAG(LuauVectorLibNativeDot) -LUAU_FASTFLAG(LuauVectorBuiltins) LUAU_FASTFLAG(LuauVectorMetatable) +LUAU_FASTFLAG(LuauBufferBitMethods) +LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse) static lua_CompileOptions defaultOptions() { @@ -655,12 +654,13 @@ TEST_CASE("Basic") TEST_CASE("Buffers") { + ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true}; + runConformance("buffers.lua"); } TEST_CASE("Math") { - ScopedFastFlag LuauMathMap{FFlag::LuauMathMap, true}; ScopedFastFlag LuauMathLerp{FFlag::LuauMathLerp, true}; runConformance("math.lua"); @@ -893,7 +893,6 @@ TEST_CASE("Vector") TEST_CASE("VectorLibrary") { - ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true}; ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true}; ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true}; ScopedFastFlag luauVectorMetatable{FFlag::LuauVectorMetatable, true}; @@ -987,7 +986,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { - ScopedFastFlag luauVectorDefinitions{FFlag::LuauVectorDefinitions, true}; ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, false}; // waiting for math.lerp to be added to embedded type definitions runConformance( @@ -2579,6 +2577,8 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { + ScopedFastFlag luauCodeGenLimitLiveSlotReuse{FFlag::LuauCodeGenLimitLiveSlotReuse, true}; + // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) return; diff --git a/tests/EqSatSimplification.test.cpp b/tests/EqSatSimplification.test.cpp index d4f571827..0331d0671 100644 --- a/tests/EqSatSimplification.test.cpp +++ b/tests/EqSatSimplification.test.cpp @@ -23,15 +23,11 @@ struct ESFixture : Fixture TypeId genericT = arena_.addType(GenericType{"T"}); TypeId genericU = arena_.addType(GenericType{"U"}); - TypeId numberToString = arena_.addType(FunctionType{ - arena_.addTypePack({builtinTypes->numberType}), - arena_.addTypePack({builtinTypes->stringType}) - }); + TypeId numberToString = + arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->numberType}), arena_.addTypePack({builtinTypes->stringType})}); - TypeId stringToNumber = arena_.addType(FunctionType{ - arena_.addTypePack({builtinTypes->stringType}), - arena_.addTypePack({builtinTypes->numberType}) - }); + TypeId stringToNumber = + arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->stringType}), arena_.addTypePack({builtinTypes->numberType})}); ESFixture() : simplifier(newSimplifier(arena, builtinTypes)) @@ -163,10 +159,11 @@ TEST_CASE_FIXTURE(ESFixture, "never & string") TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)") { - CHECK("string" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->stringType, - arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}}) - }}))); + CHECK( + "string" == simplifyStr(arena->addType( + IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}})}} + )) + ); } TEST_CASE_FIXTURE(ESFixture, "true | false") @@ -211,112 +208,97 @@ TEST_CASE_FIXTURE(ESFixture, "error | unknown") TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string") { - CHECK("string" == simplifyStr(arena->addType(UnionType{{ - arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType - }}))); + CHECK("string" == simplifyStr(arena->addType(UnionType{{arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType}}))); } TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"") { - CHECK("\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{ - arena->addType(SingletonType{StringSingleton{"hello"}}), - arena->addType(SingletonType{StringSingleton{"world"}}), - arena->addType(SingletonType{StringSingleton{"hello"}}), - }}))); + CHECK( + "\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{ + arena->addType(SingletonType{StringSingleton{"hello"}}), + arena->addType(SingletonType{StringSingleton{"world"}}), + arena->addType(SingletonType{StringSingleton{"hello"}}), + }})) + ); } TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer") { - CHECK("unknown" == simplifyStr(arena->addType(UnionType{{ - builtinTypes->nilType, - builtinTypes->booleanType, - builtinTypes->numberType, - builtinTypes->stringType, - builtinTypes->threadType, - builtinTypes->functionType, - builtinTypes->tableType, - builtinTypes->classType, - builtinTypes->bufferType, - }}))); + CHECK( + "unknown" == simplifyStr(arena->addType(UnionType{{ + builtinTypes->nilType, + builtinTypes->booleanType, + builtinTypes->numberType, + builtinTypes->stringType, + builtinTypes->threadType, + builtinTypes->functionType, + builtinTypes->tableType, + builtinTypes->classType, + builtinTypes->bufferType, + }})) + ); } TEST_CASE_FIXTURE(ESFixture, "Parent & number") { - CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ - parentClass, builtinTypes->numberType - }}))); + CHECK("never" == simplifyStr(arena->addType(IntersectionType{{parentClass, builtinTypes->numberType}}))); } TEST_CASE_FIXTURE(ESFixture, "Child & Parent") { - CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ - childClass, parentClass - }}))); + CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{childClass, parentClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Child & Unrelated") { - CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ - childClass, unrelatedClass - }}))); + CHECK("never" == simplifyStr(arena->addType(IntersectionType{{childClass, unrelatedClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Child | Parent") { - CHECK("Parent" == simplifyStr(arena->addType(UnionType{{ - childClass, parentClass - }}))); + CHECK("Parent" == simplifyStr(arena->addType(UnionType{{childClass, parentClass}}))); } TEST_CASE_FIXTURE(ESFixture, "class | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{ - builtinTypes->classType, childClass - }}))); + CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->classType, childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{ - parentClass, builtinTypes->classType, childClass - }}))); + CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->classType, childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated") { - CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ - parentClass, unrelatedClass - }}))); + CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{parentClass, unrelatedClass}}))); } TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated") { - CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ - builtinTypes->neverType, parentClass, unrelatedClass - }}))); + CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{builtinTypes->neverType, parentClass, unrelatedClass}}))); } TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated") { - CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ - builtinTypes->neverType, parentClass, - arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}), - unrelatedClass - }}))); + CHECK( + "Parent | Unrelated" == simplifyStr(arena->addType(UnionType{ + {builtinTypes->neverType, + parentClass, + arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}), + unrelatedClass} + })) + ); } TEST_CASE_FIXTURE(ESFixture, "T & U") { - CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{ - genericT, genericU - }}))); + CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{genericT, genericU}}))); } TEST_CASE_FIXTURE(ESFixture, "boolean & true") { - CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->booleanType, builtinTypes->trueType - }}))); + CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}}))); } TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | function | table | class | buffer)") @@ -332,23 +314,17 @@ TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | funct builtinTypes->bufferType, }}); - CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->booleanType, truthy - }}))); + CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, truthy}}))); } TEST_CASE_FIXTURE(ESFixture, "boolean & ~(false?)") { - CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->booleanType, builtinTypes->truthyType - }}))); + CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->truthyType}}))); } TEST_CASE_FIXTURE(ESFixture, "false & ~(false?)") { - CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->falseType, builtinTypes->truthyType - }}))); + CHECK("never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->falseType, builtinTypes->truthyType}}))); } TEST_CASE_FIXTURE(ESFixture, "(number) -> string & (number) -> string") @@ -399,28 +375,25 @@ TEST_CASE_FIXTURE(ESFixture, "(number) -> string | (string) -> number") TEST_CASE_FIXTURE(ESFixture, "add") { - CHECK("number" == simplifyStr(arena->addType( - TypeFunctionInstanceType{builtinTypeFunctions().addFunc, { - builtinTypes->numberType, builtinTypes->numberType - }} - ))); + CHECK( + "number" == + simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {builtinTypes->numberType, builtinTypes->numberType}})) + ); } TEST_CASE_FIXTURE(ESFixture, "union") { - CHECK("number" == simplifyStr(arena->addType( - TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, { - builtinTypes->numberType, builtinTypes->numberType - }} - ))); + CHECK( + "number" == + simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {builtinTypes->numberType, builtinTypes->numberType}})) + ); } TEST_CASE_FIXTURE(ESFixture, "never & ~string") { - CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->neverType, - arena->addType(NegationType{builtinTypes->stringType}) - }}))); + CHECK( + "never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->neverType, arena->addType(NegationType{builtinTypes->stringType})}})) + ); } TEST_CASE_FIXTURE(ESFixture, "blocked & never") @@ -444,7 +417,9 @@ TEST_CASE_FIXTURE(ESFixture, "blocked & ~number & function") TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)") { - const TypeId t1 = arena->addType(UnionType{{builtinTypes->numberType, builtinTypes->booleanType, builtinTypes->stringType, builtinTypes->nilType, builtinTypes->tableType}}); + const TypeId t1 = arena->addType( + UnionType{{builtinTypes->numberType, builtinTypes->booleanType, builtinTypes->stringType, builtinTypes->nilType, builtinTypes->tableType}} + ); CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, builtinTypes->falsyType}}))); } @@ -493,26 +468,17 @@ TEST_CASE_FIXTURE(ESFixture, "(blocked & number) | (blocked & number)") TEST_CASE_FIXTURE(ESFixture, "{} & unknown") { - CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ - tbl({}), - builtinTypes->unknownType - }}))); + CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->unknownType}}))); } TEST_CASE_FIXTURE(ESFixture, "{} & table") { - CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ - tbl({}), - builtinTypes->tableType - }}))); + CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->tableType}}))); } TEST_CASE_FIXTURE(ESFixture, "{} & ~(false?)") { - CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ - tbl({}), - builtinTypes->truthyType - }}))); + CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->truthyType}}))); } TEST_CASE_FIXTURE(ESFixture, "{x: number?} & {x: number}") @@ -606,10 +572,7 @@ TEST_CASE_FIXTURE(ESFixture, "{ x: number } & ~boolean") { const TypeId tblTy = tbl(TableType::Props{{"x", builtinTypes->numberType}}); - const TypeId ty = arena->addType(IntersectionType{{ - tblTy, - arena->addType(NegationType{builtinTypes->booleanType}) - }}); + const TypeId ty = arena->addType(IntersectionType{{tblTy, arena->addType(NegationType{builtinTypes->booleanType})}}); CHECK("{ x: number }" == simplifyStr(ty)); } @@ -634,10 +597,7 @@ TEST_CASE_FIXTURE(ESFixture, "string & (\"hi\" | \"bye\")") const TypeId hi = arena->addType(SingletonType{StringSingleton{"hi"}}); const TypeId bye = arena->addType(SingletonType{StringSingleton{"bye"}}); - CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->stringType, - arena->addType(UnionType{{hi, bye}}) - }}))); + CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{hi, bye}})}}))); } TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"") @@ -646,46 +606,32 @@ TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"") TypeId ok1 = arena->addType(SingletonType{StringSingleton{"ok"}}); TypeId ok2 = arena->addType(SingletonType{StringSingleton{"ok"}}); - TypeId ty = arena->addType(IntersectionType{{ - arena->addType(UnionType{{err, ok1}}), - arena->addType(NegationType{ok2}) - }}); + TypeId ty = arena->addType(IntersectionType{{arena->addType(UnionType{{err, ok1}}), arena->addType(NegationType{ok2})}}); CHECK("\"err\"" == simplifyStr(ty)); } TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & ~Child") { - const TypeId ty = arena->addType(IntersectionType{{ - arena->addType(UnionType{{childClass, unrelatedClass}}), - arena->addType(NegationType{childClass}) - }}); + const TypeId ty = + arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), arena->addType(NegationType{childClass})}}); CHECK("Unrelated" == simplifyStr(ty)); } TEST_CASE_FIXTURE(ESFixture, "string & ~Child") { - CHECK("string" == simplifyStr(arena->addType(IntersectionType{{ - builtinTypes->stringType, - arena->addType(NegationType{childClass}) - }}))); + CHECK("string" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(NegationType{childClass})}}))); } TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & Child") { - CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ - arena->addType(UnionType{{childClass, unrelatedClass}}), - childClass - }}))); + CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "(Child | AnotherChild) & ~Child") { - CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ - arena->addType(UnionType{{childClass, anotherChild}}), - childClass - }}))); + CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, anotherChild}}), childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: never }") @@ -706,11 +652,7 @@ TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: number? } & { x: string }") TEST_CASE_FIXTURE(ESFixture, "Child & add") { const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}}); - const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{ - builtinTypeFunctions().addFunc, - {u, parentClass}, - {} - }); + const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {u, parentClass}, {}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); @@ -720,11 +662,7 @@ TEST_CASE_FIXTURE(ESFixture, "Child & add TEST_CASE_FIXTURE(ESFixture, "Child & intersect") { const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}}); - const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{ - builtinTypeFunctions().intersectFunc, - {u, parentClass}, - {} - }); + const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().intersectFunc, {u, parentClass}, {}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); @@ -740,7 +678,8 @@ TEST_CASE_FIXTURE(ESFixture, "lt == boolean") {arena->addType(BlockedType{}), builtinTypes->stringType}, }; - for (const auto& [lhs, rhs] : cases) { + for (const auto& [lhs, rhs] : cases) + { const TypeId tfun = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().ltFunc, {lhs, rhs}}); CHECK("boolean" == simplifyStr(tfun)); } diff --git a/tests/Fixture.h b/tests/Fixture.h index ba0384032..23ec7e2bb 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -29,8 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(LuauVectorDefinitionsExtra) -#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) \ - ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; +#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; #define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 91c388bd9..8a30abf50 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -25,6 +25,7 @@ LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauSymbolEquality); LUAU_FASTFLAG(LuauStoreSolverTypeOnModule); LUAU_FASTFLAG(LexerResumesFromPosition2) +LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -91,7 +92,8 @@ struct FragmentAutocompleteFixtureImpl : BaseType std::optional fragmentEndPosition = std::nullopt ) { - return Luau::typecheckFragment(this->frontend, "MainModule", cursorPos, getOptions(), document, fragmentEndPosition); + auto [_, result] = Luau::typecheckFragment(this->frontend, "MainModule", cursorPos, getOptions(), document, fragmentEndPosition); + return result; } FragmentAutocompleteResult autocompleteFragment( @@ -1383,4 +1385,177 @@ t ); } +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple") +{ + const std::string source = R"( +-- sel +-- retur +-- fo +-- if +-- end +-- the +)"; + ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true}; + autocompleteFragmentInBothSolvers( + source, + source, + Position{4, 6}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_blocks") +{ + const std::string source = R"( +--[[ +comment 1 +]] local +-- [[ comment 2]] +-- +-- sdfsdfsdf +--[[comment 3]] +--[[ +foo +bar +baz +]] +)"; + ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true}; + autocompleteFragmentInBothSolvers( + source, + source, + Position{3, 0}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{3, 2}, + [](FragmentAutocompleteResult& result) + { + CHECK(!result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{8, 6}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{10, 0}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments") +{ + const std::string source = R"( +-- sel +-- retur +-- fo +--[[ sel ]] +local -- hello +)"; + ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true}; + autocompleteFragmentInBothSolvers( + source, + source, + Position{1, 7}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{2, 9}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{3, 6}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{4, 9}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{5, 6}, + [](FragmentAutocompleteResult& result) + { + CHECK(!result.acResults.entryMap.empty()); + } + ); + + autocompleteFragmentInBothSolvers( + source, + source, + Position{5, 14}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_in_incremental_fragment") +{ + const std::string source = R"( +local x = 5 +if x == 5 +)"; + const std::string updated = R"( +local x = 5 +if x == 5 then -- a comment +)"; + ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true}; + autocompleteFragmentInBothSolvers( + source, + updated, + Position{2, 28}, + [](FragmentAutocompleteResult& result) + { + CHECK(result.acResults.entryMap.empty()); + } + ); +} + TEST_SUITE_END(); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index cce167bf1..362c1016e 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -520,9 +520,9 @@ end JUMP bb_2 bb_2: CHECK_SAFE_ENV exit(3) - JUMP_EQ_TAG K1, tnil, bb_fallback_4, bb_3 + JUMP_EQ_TAG K1 (nil), tnil, bb_fallback_4, bb_3 bb_3: - %9 = LOAD_TVALUE K1 + %9 = LOAD_TVALUE K1 (nil) STORE_TVALUE R1, %9 JUMP bb_5 bb_5: @@ -575,7 +575,7 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - %4 = LOAD_TVALUE K0, 0i, tvector + %4 = LOAD_TVALUE K0 (1, 2, 3), 0i, tvector %11 = LOAD_TVALUE R0 %12 = ADD_VEC %4, %11 %13 = TAG_VECTOR %12 @@ -602,7 +602,7 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - FALLBACK_NAMECALL 0u, R1, R0, K0 + FALLBACK_NAMECALL 0u, R1, R0, K0 ('Abs') INTERRUPT 2u SET_SAVEDPC 3u CALL R1, 1i, -1i @@ -628,8 +628,8 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - FALLBACK_GETTABLEKS 0u, R3, R0, K0 - FALLBACK_GETTABLEKS 2u, R4, R0, K1 + FALLBACK_GETTABLEKS 0u, R3, R0, K0 ('XX') + FALLBACK_GETTABLEKS 2u, R4, R0, K1 ('YY') CHECK_TAG R3, tnumber, bb_fallback_3 CHECK_TAG R4, tnumber, bb_fallback_3 %14 = LOAD_DOUBLE R3 @@ -639,7 +639,7 @@ end JUMP bb_4 bb_4: CHECK_TAG R0, tvector, exit(5) - FALLBACK_GETTABLEKS 5u, R3, R0, K2 + FALLBACK_GETTABLEKS 5u, R3, R0, K2 ('ZZ') CHECK_TAG R2, tnumber, bb_fallback_5 CHECK_TAG R3, tnumber, bb_fallback_5 %30 = LOAD_DOUBLE R2 @@ -857,8 +857,8 @@ end JUMP bb_bytecode_1 bb_bytecode_1: %8 = LOAD_POINTER R0 - %9 = GET_SLOT_NODE_ADDR %8, 0u, K1 - CHECK_SLOT_MATCH %9, K1, bb_fallback_3 + %9 = GET_SLOT_NODE_ADDR %8, 0u, K1 ('n') + CHECK_SLOT_MATCH %9, K1 ('n'), bb_fallback_3 %11 = LOAD_TVALUE %9, 0i STORE_TVALUE R3, %11 JUMP bb_4 @@ -885,8 +885,8 @@ end STORE_VECTOR R3, %30, %33, %36 CHECK_TAG R0, ttable, exit(6) %41 = LOAD_POINTER R0 - %42 = GET_SLOT_NODE_ADDR %41, 6u, K3 - CHECK_SLOT_MATCH %42, K3, bb_fallback_5 + %42 = GET_SLOT_NODE_ADDR %41, 6u, K3 ('b') + CHECK_SLOT_MATCH %42, K3 ('b'), bb_fallback_5 %44 = LOAD_TVALUE %42, 0i STORE_TVALUE R5, %44 JUMP bb_6 @@ -929,8 +929,8 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - FALLBACK_GETTABLEKS 0u, R2, R0, K0 - FALLBACK_GETTABLEKS 2u, R3, R0, K1 + FALLBACK_GETTABLEKS 0u, R2, R0, K0 ('x') + FALLBACK_GETTABLEKS 2u, R3, R0, K1 ('y') CHECK_TAG R2, tnumber, bb_fallback_3 CHECK_TAG R3, tnumber, bb_fallback_3 %14 = LOAD_DOUBLE R2 @@ -964,9 +964,9 @@ end bb_bytecode_1: STORE_DOUBLE R1, 3 STORE_TAG R1, tnumber - FALLBACK_SETTABLEKS 1u, R1, R0, K0 + FALLBACK_SETTABLEKS 1u, R1, R0, K0 ('x') STORE_DOUBLE R1, 4 - FALLBACK_SETTABLEKS 4u, R1, R0, K1 + FALLBACK_SETTABLEKS 4u, R1, R0, K1 ('y') INTERRUPT 6u RETURN R0, 0i )" @@ -989,11 +989,11 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - FALLBACK_NAMECALL 0u, R2, R0, K0 + FALLBACK_NAMECALL 0u, R2, R0, K0 ('GetX') INTERRUPT 2u SET_SAVEDPC 3u CALL R2, 1i, 1i - FALLBACK_NAMECALL 3u, R3, R0, K1 + FALLBACK_NAMECALL 3u, R3, R0, K1 ('GetY') INTERRUPT 5u SET_SAVEDPC 6u CALL R3, 1i, 1i @@ -1367,8 +1367,8 @@ end bb_4: CHECK_TAG R2, ttable, exit(1) %23 = LOAD_POINTER R2 - %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 - CHECK_SLOT_MATCH %24, K0, bb_fallback_5 + %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 ('pos') + CHECK_SLOT_MATCH %24, K0 ('pos'), bb_fallback_5 %26 = LOAD_TVALUE %24, 0i STORE_TVALUE R4, %26 JUMP bb_6 @@ -1476,13 +1476,13 @@ end bb_4: CHECK_TAG R3, ttable, bb_fallback_5 %23 = LOAD_POINTER R3 - %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 - CHECK_SLOT_MATCH %24, K0, bb_fallback_5 + %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 ('normal') + CHECK_SLOT_MATCH %24, K0 ('normal'), bb_fallback_5 %26 = LOAD_TVALUE %24, 0i STORE_TVALUE R2, %26 JUMP bb_6 bb_6: - %31 = LOAD_TVALUE K1, 0i, tvector + %31 = LOAD_TVALUE K1 (0.707000017, 0, 0.707000017), 0i, tvector STORE_TVALUE R4, %31 CHECK_TAG R2, tvector, exit(4) %37 = LOAD_FLOAT R2, 0i @@ -1603,9 +1603,9 @@ end STORE_DOUBLE R1, 0 STORE_TAG R1, tnumber CHECK_SAFE_ENV exit(1) - JUMP_EQ_TAG K1, tnil, bb_fallback_6, bb_5 + JUMP_EQ_TAG K1 (nil), tnil, bb_fallback_6, bb_5 bb_5: - %9 = LOAD_TVALUE K1 + %9 = LOAD_TVALUE K1 (nil) STORE_TVALUE R2, %9 JUMP bb_7 bb_7: @@ -1627,8 +1627,8 @@ end bb_bytecode_2: CHECK_TAG R6, ttable, exit(6) %35 = LOAD_POINTER R6 - %36 = GET_SLOT_NODE_ADDR %35, 6u, K2 - CHECK_SLOT_MATCH %36, K2, bb_fallback_10 + %36 = GET_SLOT_NODE_ADDR %35, 6u, K2 ('pos') + CHECK_SLOT_MATCH %36, K2 ('pos'), bb_fallback_10 %38 = LOAD_TVALUE %36, 0i STORE_TVALUE R8, %38 JUMP bb_11 @@ -1829,8 +1829,8 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - FALLBACK_GETTABLEKS 0u, R2, R0, K0 - FALLBACK_GETTABLEKS 2u, R3, R0, K1 + FALLBACK_GETTABLEKS 0u, R2, R0, K0 ('Row1') + FALLBACK_GETTABLEKS 2u, R3, R0, K1 ('Row2') CHECK_TAG R2, tvector, exit(4) CHECK_TAG R3, tvector, exit(4) %14 = LOAD_TVALUE R2 @@ -2138,10 +2138,10 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - %4 = LOAD_TVALUE K0, 0i, tvector + %4 = LOAD_TVALUE K0 (1, 0, 0), 0i, tvector %11 = LOAD_TVALUE R0 %12 = MUL_VEC %4, %11 - %15 = LOAD_TVALUE K1, 0i, tvector + %15 = LOAD_TVALUE K1 (0, 1, 0), 0i, tvector %23 = ADD_VEC %12, %15 %24 = TAG_VECTOR %23 STORE_TVALUE R1, %24 @@ -2176,7 +2176,7 @@ end bb_2: JUMP bb_bytecode_1 bb_bytecode_1: - %4 = LOAD_TVALUE K0, 0i, tvector + %4 = LOAD_TVALUE K0 (0, 0, 0), 0i, tvector %11 = LOAD_TVALUE R0 %12 = ADD_VEC %4, %11 %13 = TAG_VECTOR %12 @@ -2208,9 +2208,9 @@ end STORE_TAG R1, tboolean STORE_DOUBLE R2, 4.75 STORE_TAG R2, tnumber - %5 = LOAD_TVALUE K1, 0i, tvector + %5 = LOAD_TVALUE K1 (1, 2, 4), 0i, tvector STORE_TVALUE R3, %5 - %7 = LOAD_TVALUE K2, 0i, tstring + %7 = LOAD_TVALUE K2 ('test'), 0i, tstring STORE_TVALUE R4, %7 INTERRUPT 5u RETURN R0, 5i diff --git a/tests/Lexer.test.cpp b/tests/Lexer.test.cpp index e0716e4c5..803a9e97b 100644 --- a/tests/Lexer.test.cpp +++ b/tests/Lexer.test.cpp @@ -8,6 +8,8 @@ using namespace Luau; +LUAU_FASTFLAG(LexerFixInterpStringStart) + TEST_SUITE_BEGIN("LexerTests"); TEST_CASE("broken_string_works") @@ -153,6 +155,8 @@ TEST_CASE("string_interpolation_basic") Lexeme interpEnd = lexer.next(); CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd); + // The InterpStringEnd should start with }, not `. + CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12); } TEST_CASE("string_interpolation_full") @@ -173,6 +177,7 @@ TEST_CASE("string_interpolation_full") Lexeme interpMid = lexer.next(); CHECK_EQ(interpMid.type, Lexeme::InterpStringMid); CHECK_EQ(interpMid.toString(), "} {"); + CHECK_EQ(interpMid.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12); Lexeme quote2 = lexer.next(); CHECK_EQ(quote2.type, Lexeme::QuotedString); @@ -181,6 +186,7 @@ TEST_CASE("string_interpolation_full") Lexeme interpEnd = lexer.next(); CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd); CHECK_EQ(interpEnd.toString(), "} end`"); + CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 19 : 20); } TEST_CASE("string_interpolation_double_brace") diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 387c0d103..c31cfb65e 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -16,7 +16,6 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForClassNames) @@ -447,38 +446,62 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_span_is_correct") TEST_CASE_FIXTURE(Fixture, "parse_error_messages") { - matchParseError(R"( + matchParseError( + R"( local a: (number, number) -> (string - )", "Expected ')' (to close '(' at line 2), got "); + )", + "Expected ')' (to close '(' at line 2), got " + ); - matchParseError(R"( + matchParseError( + R"( local a: (number, number) -> ( string - )", "Expected ')' (to close '(' at line 2), got "); + )", + "Expected ')' (to close '(' at line 2), got " + ); - matchParseError(R"( + matchParseError( + R"( local a: (number, number) - )", "Expected '->' when parsing function type, got "); + )", + "Expected '->' when parsing function type, got " + ); - matchParseError(R"( + matchParseError( + R"( local a: (number, number - )", "Expected ')' (to close '(' at line 2), got "); + )", + "Expected ')' (to close '(' at line 2), got " + ); - matchParseError(R"( + matchParseError( + R"( local a: {foo: string, - )", "Expected identifier when parsing table field, got "); + )", + "Expected identifier when parsing table field, got " + ); - matchParseError(R"( + matchParseError( + R"( local a: {foo: string - )", "Expected '}' (to close '{' at line 2), got "); + )", + "Expected '}' (to close '{' at line 2), got " + ); - matchParseError(R"( + matchParseError( + R"( local a: { [string]: number, [number]: string } - )", "Cannot have more than one table indexer"); + )", + "Cannot have more than one table indexer" + ); - matchParseError(R"( + matchParseError( + R"( type T = foo - )", "Expected '(' when parsing function parameters, got 'foo'"); + )", + "Expected '(' when parsing function parameters, got 'foo'" + ); } TEST_CASE_FIXTURE(Fixture, "mixed_intersection_and_union_not_allowed") @@ -613,9 +636,12 @@ TEST_CASE_FIXTURE(Fixture, "vertical_space") TEST_CASE_FIXTURE(Fixture, "parse_error_type_name") { - matchParseError(R"( + matchParseError( + R"( local a: Foo.= - )", "Expected identifier when parsing field name, got '='"); + )", + "Expected identifier when parsing field name, got '='" + ); } TEST_CASE_FIXTURE(Fixture, "parse_numbers_decimal") @@ -677,9 +703,12 @@ TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error") TEST_CASE_FIXTURE(Fixture, "error_on_unicode") { - matchParseError(R"( + matchParseError( + R"( local ☃ = 10 - )", "Expected identifier when parsing variable name, got Unicode character U+2603"); + )", + "Expected identifier when parsing variable name, got Unicode character U+2603" + ); } TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string") @@ -690,9 +719,12 @@ TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string") TEST_CASE_FIXTURE(Fixture, "error_on_confusable") { - matchParseError(R"( + matchParseError( + R"( local pi = 3․13 - )", "Expected identifier when parsing expression, got Unicode character U+2024 (did you mean '.'?)"); + )", + "Expected identifier when parsing expression, got Unicode character U+2024 (did you mean '.'?)" + ); } TEST_CASE_FIXTURE(Fixture, "error_on_non_utf8_sequence") @@ -2341,8 +2373,6 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms") TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") { - ScopedFastFlag sff2{FFlag::LuauUserDefinedTypeFunParseExport, true}; - AstStat* stat = parse(R"( type function foo() return types.number @@ -3656,7 +3686,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type") auto unionTy = paramTy.type->as(); LUAU_ASSERT(unionTy); CHECK_EQ(unionTy->types.size, 2); - CHECK(unionTy->types.data[0]->is()); // () -> () + CHECK(unionTy->types.data[0]->is()); // () -> () CHECK(unionTy->types.data[1]->is()); // nil } @@ -3702,11 +3732,14 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type") ScopedFastFlag _{FFlag::LuauErrorRecoveryForTableTypes, true}; ParseOptions opts; opts.allowDeclarationSyntax = true; - const auto result = tryParse(R"( + const auto result = tryParse( + R"( declare class Widget state: {string: function(string, Widget)} end - )", opts); + )", + opts + ); CHECK_EQ(result.errors.size(), 2); } diff --git a/tests/Repl.test.cpp b/tests/Repl.test.cpp index 5667badbc..85d533901 100644 --- a/tests/Repl.test.cpp +++ b/tests/Repl.test.cpp @@ -13,8 +13,6 @@ #include #include -LUAU_FASTFLAG(LuauMathMap) - struct Completion { std::string completion; @@ -175,7 +173,7 @@ TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables") CHECK(checkCompletion(completions, prefix, "myvariable1")); CHECK(checkCompletion(completions, prefix, "myvariable2")); } - if (FFlag::LuauMathMap) + { // Try completing some builtin functions CompletionSet completions = getCompletionSet("math.m"); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index db018eda7..536a4081d 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -211,8 +211,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table") CHECK( "t2 where " "t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; " - "t2 = { @metatable t1, { x: number, y: number, z: number } }" == - a + "t2 = { @metatable t1, { x: number, y: number, z: number } }" == a ); } else diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 281c73c72..aef63d069 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -12,7 +12,6 @@ LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAG(LuauUserTypeFunFixInner) LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) -LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) @@ -1332,7 +1331,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true}; - ScopedFastFlag luauUserDefinedTypeFunParseExport{FFlag::LuauUserDefinedTypeFunParseExport, true}; fileResolver.source["game/A"] = R"( export type function concat(a, b) diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index b81ac010a..53f1396d3 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -665,12 +665,11 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") )"); if (FFlag::LuauSolverV2) - CHECK( - "Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)) - ); + CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))); else CHECK_EQ( - toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" + toString(result.errors.at(0)), + "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" ); } { @@ -680,12 +679,11 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") )"); if (FFlag::LuauSolverV2) - CHECK( - "Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)) - ); + CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))); else CHECK_EQ( - toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" + toString(result.errors.at(0)), + "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" ); } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 83910e1bb..bd43410c0 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -2566,10 +2566,7 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDontRefCountTypesInTypeFunctions, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}}; // CLI-114134: This test: // a) Has a kind of weird result (suggesting `number | false` is not great); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 6bed04769..dea027c2e 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -800,7 +800,10 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown") "Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add", toString(result.errors[0]) ); - CHECK_EQ("Operator '-' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __sub", toString(result.errors[1])); + CHECK_EQ( + "Operator '-' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __sub", + toString(result.errors[1]) + ); } else { diff --git a/tests/TypeInfer.primitives.test.cpp b/tests/TypeInfer.primitives.test.cpp index 0c14a4482..2c76f1239 100644 --- a/tests/TypeInfer.primitives.test.cpp +++ b/tests/TypeInfer.primitives.test.cpp @@ -12,8 +12,6 @@ #include "doctest.h" -LUAU_FASTFLAG(LuauVectorDefinitions) - using namespace Luau; TEST_SUITE_BEGIN("TypeInferPrimitives"); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 005f2291c..a61b0fd10 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -69,9 +69,9 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") const std::string expectedWithEqSat = R"( function f(a:{fn:()->(unknown,...unknown)}): () if type(a) == 'boolean'then - local a1:never=a + local a1:{fn:()->(unknown,...unknown)}&boolean=a elseif a.fn()then - local a2:{fn:()->(unknown,...unknown)}=a + local a2:{fn:()->(unknown,...unknown)}&negate=a end end )"; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index cc7123cfa..0cd23c100 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -451,10 +451,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_ty LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0])); - CHECK(Location{{ 7, 18}, {7, 19}} == result.errors[0].location); + CHECK(Location{{7, 18}, {7, 19}} == result.errors[0].location); CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1])); - CHECK(Location{{ 13, 18}, {13, 19}} == result.errors[1].location); + CHECK(Location{{13, 18}, {13, 19}} == result.errors[1].location); } TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error") @@ -742,11 +742,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_ if (FFlag::LuauSolverV2) { // CLI-115281 Types produced by refinements do not consistently get simplified - CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" - CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" - - CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" - CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" + CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" + CHECK_EQ( + "(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24})) + ); // type(v) ~= "nil" + + CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" + CHECK_EQ( + "(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24})) + ); // equivalent to type(v) ~= "nil" } else { diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ee561e2f2..3ef618d1e 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack) LUAU_FASTFLAG(LuauTableKeysAreRValues) LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer) +LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) TEST_SUITE_BEGIN("TableTests"); @@ -3815,6 +3816,8 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { + ScopedFastFlag _{FFlag::LuauTrackInteriorFreeTypesOnScope, true}; + CheckResult result = check(R"( local function f(s): string local foo = s:absolutely_no_scalar_has_this_method() @@ -3824,17 +3827,14 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_ if (FFlag::LuauSolverV2) { - LUAU_REQUIRE_ERROR_COUNT(4, result); + LUAU_REQUIRE_ERROR_COUNT(3, result); CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value."); - // FIXME: These free types should have been generalized by now. CHECK( toString(result.errors[1]) == - "Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: ('a <: (never) -> ('b, c...)) -}' here." + "Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: (never) -> (unknown, ...unknown) -}' here." ); CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here."); - CHECK(get(result.errors[3])); - CHECK_EQ("(never) -> string", toString(requireType("f"))); } else @@ -5002,7 +5002,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( - "Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: (a) -> { @metatable t1, (a & ~(false?)) | { } } }'", + "Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: (a) -> { @metatable t1, (a & ~(false?)) | { " + "} } }'", toString(result.errors[0]) ); } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 2eb96152a..cea3fc6dc 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -1709,10 +1709,7 @@ TEST_CASE_FIXTURE(Fixture, "react_lua_follow_free_type_ub") TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauNewSolverVisitErrorExprLvalues, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewSolverVisitErrorExprLvalues, true}}; // This should always fail to parse, but shouldn't assert. Previously this // would assert as we end up _roughly_ parsing this (with a lot of error @@ -1734,10 +1731,7 @@ TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue") TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDontRefCountTypesInTypeFunctions, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}}; LUAU_CHECK_NO_ERRORS(check(R"( --!strict @@ -1750,10 +1744,7 @@ TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function") TEST_CASE_FIXTURE(Fixture, "avoid_double_reference_to_free_type") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDontRefCountTypesInTypeFunctions, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}}; LUAU_CHECK_NO_ERRORS(check(R"( --!strict diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index 9cf8f153e..858c3052a 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -953,11 +953,10 @@ a = b if (FFlag::LuauSolverV2) { - const std::string expected = - "Type\n" - " '() -> (number, ...boolean)'\n" - "could not be converted into\n" - " '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string"; + const std::string expected = "Type\n" + " '() -> (number, ...boolean)'\n" + "could not be converted into\n" + " '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string"; CHECK(expected == toString(result.errors[0])); } diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 49ec61e75..037c01035 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -881,7 +881,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))); + CHECK_EQ( + "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) + ); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2") diff --git a/tests/conformance/buffers.lua b/tests/conformance/buffers.lua index 5da2a688c..370fb8a8b 100644 --- a/tests/conformance/buffers.lua +++ b/tests/conformance/buffers.lua @@ -599,6 +599,90 @@ end misc(table.create(16, 0)) +local function bitops(size, base) + local b = buffer.create(size) + + buffer.writeu32(b, base / 8, 0x12345678) + + assert(buffer.readbits(b, base, 8) == buffer.readu8(b, base / 8)) + assert(buffer.readbits(b, base, 16) == buffer.readu16(b, base / 8)) + assert(buffer.readbits(b, base, 32) == buffer.readu32(b, base / 8)) + + buffer.writebits(b, base, 32, 0) + + buffer.writebits(b, base, 1, 1) + assert(buffer.readi8(b, base / 8) == 1) + + buffer.writebits(b, base + 1, 1, 1) + assert(buffer.readi8(b, base / 8) == 3) + + -- construct 00000010 00000000_01000000_00010000_00001000 00001000_00010000_01000010_00100101 + buffer.writebits(b, base + 0, 1, 0b1) + buffer.writebits(b, base + 1, 2, 0b10) + buffer.writebits(b, base + 3, 3, 0b100) + buffer.writebits(b, base + 6, 4, 0b1000) + buffer.writebits(b, base + 10, 5, 0b10000) + buffer.writebits(b, base + 15, 6, 0b100000) + buffer.writebits(b, base + 21, 7, 0b1000000) + buffer.writebits(b, base + 28, 8, 0b10000000) + buffer.writebits(b, base + 36, 9, 0b100000000) + buffer.writebits(b, base + 45, 10, 0b1000000000) + buffer.writebits(b, base + 55, 11, 0b10000000000) + + assert(buffer.readbits(b, base + 0, 32) == 0b00001000_00010000_01000010_00100101) + assert(buffer.readbits(b, base + 32, 32) == 0b00000000_01000000_00010000_00001000) + + assert(buffer.readu32(b, base / 8 + 0) == 0b00001000_00010000_01000010_00100101) + assert(buffer.readu32(b, base / 8 + 4) == 0b00000000_01000000_00010000_00001000) + + -- slide the window to touch 5 bytes + assert(buffer.readbits(b, base + 1, 32) == 0b00000100000010000010000100010010) + assert(buffer.readbits(b, base + 2, 32) == 0b00000010000001000001000010001001) + assert(buffer.readbits(b, base + 3, 32) == 0b00000001000000100000100001000100) + assert(buffer.readbits(b, base + 4, 32) == 0b10000000100000010000010000100010) + assert(buffer.readbits(b, base + 5, 32) == 0b01000000010000001000001000010001) + assert(buffer.readbits(b, base + 6, 32) == 0b00100000001000000100000100001000) + assert(buffer.readbits(b, base + 7, 32) == 0b00010000000100000010000010000100) + assert(buffer.readbits(b, base + 8, 32) == 0b00001000000010000001000001000010) + + assert(buffer.readbits(b, base + 1, 15) == 0b010000100010010) + assert(buffer.readbits(b, base + 2, 15) == 0b001000010001001) + assert(buffer.readbits(b, base + 3, 15) == 0b000100001000100) + assert(buffer.readbits(b, base + 4, 15) == 0b000010000100010) + assert(buffer.readbits(b, base + 5, 15) == 0b000001000010001) + assert(buffer.readbits(b, base + 6, 15) == 0b100000100001000) + assert(buffer.readbits(b, base + 7, 15) == 0b010000010000100) + assert(buffer.readbits(b, base + 8, 15) == 0b001000001000010) + + -- zero bit + buffer.writebits(b, base, 0, 0b1) + assert(buffer.readbits(b, base, 32) == 0b00001000_00010000_01000010_00100101) + assert(buffer.readbits(b, base, 0) == 0) + assert(buffer.readbits(b, size * 8, 0) == 0) + + -- bounds + assert(ecall(function() buffer.readbits(b, -1, 0) end) == "buffer access out of bounds") + assert(ecall(function() buffer.readbits(b, size * 8, 1) end) == "buffer access out of bounds") + assert(ecall(function() buffer.readbits(b, size * 8 - 1, 2) end) == "buffer access out of bounds") + assert(ecall(function() buffer.readbits(b, 0, 64) end) == "bit count is out of range of [0; 32]") + + assert(ecall(function() buffer.writebits(b, -1, 0, 1) end) == "buffer access out of bounds") + assert(ecall(function() buffer.writebits(b, size * 8, 1, 1) end) == "buffer access out of bounds") + assert(ecall(function() buffer.writebits(b, size * 8 - 1, 2, 1) end) == "buffer access out of bounds") + assert(ecall(function() buffer.writebits(b, 0, 64, 1) end) == "bit count is out of range of [0; 32]") + + + return b +end + +do + bitops(16, 0) + bitops(17, 8) + + -- a very large buffer and bit offsets can now be over 32 bits + bitops(1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024) +end + local function testslowcalls() getfenv() @@ -619,6 +703,7 @@ local function testslowcalls() fromtostring() fill() misc(table.create(16, 0)) + bitops(16, 0) end testslowcalls() diff --git a/tests/conformance/calls.lua b/tests/conformance/calls.lua index 6555f93e1..63ad81e18 100644 --- a/tests/conformance/calls.lua +++ b/tests/conformance/calls.lua @@ -237,7 +237,7 @@ if not limitedstack then end -- testing deep nested calls with a large thread stack -do +if not limitedstack then function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end local ok, msg = pcall(recurse, 19000) diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 038450137..16172babc 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -513,4 +513,68 @@ end assert(extramath3(2) == "number") assert(extramath3("2") == "number") +local function slotcachelimit1() + local tbl = { + f1 = function() return 1 end, + f2 = function() return 2 end, + f3 = function() return 3 end, + f4 = function() return 4 end, + f5 = function() return 5 end, + f6 = function() return 6 end, + f7 = function() return 7 end, + f8 = function() return 8 end, + f9 = function() return 9 end, + f10 = function() return 10 end, + f11 = function() return 11 end, + f12 = function() return 12 end, + f13 = function() return 13 end, + f14 = function() return 14 end, + f15 = function() return 15 end, + f16 = function() return 16 end, + } + + local lookup = { + [tbl.f1] = 1, + [tbl.f2] = 2, + [tbl.f3] = 3, + [tbl.f4] = 4, + [tbl.f5] = 5, + [tbl.f6] = 6, + [tbl.f7] = 7, + [tbl.f8] = 8, + [tbl.f9] = 9, + [tbl.f10] = 10, + [tbl.f11] = 11, + [tbl.f12] = 12, + [tbl.f13] = 13, + [tbl.f14] = 14, + [tbl.f15] = 15, + [tbl.f16] = 16, + } + + assert(is_native()) + + return lookup +end + +slotcachelimit1() + +local function slotcachelimit2(foo, size) + local c1 = foo(vector.create(size.X, size.Y, size.Z)) + local c2 = foo(vector.create(-size.X, size.Y, size.Z)) + local c3 = foo(vector.create(-size.X, -size.Y, size.Z)) + local c4 = foo(vector.create(-size.X, -size.Y, -size.Z)) + local c5 = foo(vector.create(size.X, -size.Y, -size.Z)) + local c6 = foo(vector.create(size.X, size.Y, -size.Z)) + local c7 = foo(vector.create(size.X, -size.Y, size.Z)) + local c8 = foo(vector.create(-size.X, size.Y, -size.Z)) + local max = vector.create(math.max(c1.X, c2.X, c3.X, c4.X, c5.X, c6.X, c7.X, c8.X), math.max(c1.Y, c2.Y, c3.Y, c4.Y, c5.Y, c6.Y, c7.Y, c8.Y), math.max(c1.Z, c2.Z, c3.Z, c4.Z, c5.Z, c6.Z, c7.Z, c8.Z)) + local min = vector.create(math.min(c1.X, c2.X, c3.X, c4.X, c5.X, c6.X, c7.X, c8.X), math.min(c1.Y, c2.Y, c3.Y, c4.Y, c5.Y, c6.Y, c7.Y, c8.Y), math.min(c1.Z, c2.Z, c3.Z, c4.Z, c5.Z, c6.Z, c7.Z, c8.Z)) + + assert(is_native()) + return max - min +end + +slotcachelimit2(function(a) return -a end, vector.create(1, 2, 3)) + return('OK')