From 765591c22fccc1e476ef88534ce6a63a9cfa2487 Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 10 Jan 2025 09:13:13 -0800 Subject: [PATCH 1/4] Sync to origin/release/656 --- 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 | 112 +++++++--- Analysis/src/ConstraintSolver.cpp | 37 +++- 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/Compile.cpp | 3 +- CLI/FileUtils.cpp | 2 +- CodeGen/include/Luau/IrDump.h | 3 + CodeGen/src/BytecodeAnalysis.cpp | 40 ++-- 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 | 142 ++++++++++-- Common/include/Luau/Bytecode.h | 3 + Common/include/Luau/ExperimentalFlags.h | 2 +- Compiler/src/BuiltinFolding.cpp | 15 ++ Compiler/src/Builtins.cpp | 10 +- Compiler/src/BytecodeBuilder.cpp | 3 +- Compiler/src/Compiler.cpp | 5 +- Compiler/src/CostModel.cpp | 3 +- Compiler/src/Types.cpp | 4 +- Config/src/Config.cpp | 3 +- EqSat/include/Luau/EGraph.h | 8 +- EqSat/include/Luau/Language.h | 10 +- VM/src/lbuflib.cpp | 95 +++++++- VM/src/lbuiltins.cpp | 19 ++ VM/src/ldo.cpp | 28 ++- VM/src/ldo.h | 14 +- VM/src/lgc.cpp | 2 +- VM/src/lmathlib.cpp | 20 +- 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 | 19 +- 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.definitions.test.cpp | 27 ++- 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/math.lua | 17 ++ tests/conformance/native.lua | 64 ++++++ 90 files changed, 1452 insertions(+), 724 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 e6ecc8d03..57fdccab8 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -31,14 +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(LuauUserTypeFunExportedAndLocal) +LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses) LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint) +LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(InferGlobalTypes) @@ -232,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, @@ -302,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) @@ -667,6 +687,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block) { std::unordered_map aliasDefinitionLocations; + std::unordered_map classDefinitionLocations; // In order to enable mutually-recursive type aliases, we need to // populate the type bindings before we actually check any of the @@ -773,6 +794,32 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc aliasDefinitionLocations[function->name.value] = function->location; } + else if (auto classDeclaration = stat->as()) + { + if (!FFlag::LuauNewSolverPrePopulateClasses) + continue; + + if (scope->exportedTypeBindings.count(classDeclaration->name.value)) + { + auto it = classDefinitionLocations.find(classDeclaration->name.value); + LUAU_ASSERT(it != classDefinitionLocations.end()); + reportError(classDeclaration->location, DuplicateTypeDefinition{classDeclaration->name.value, it->second}); + continue; + } + + // A class might have no name if the code is syntactically + // illegal. We mustn't prepopulate anything in this case. + if (classDeclaration->name == kParseNameError) + continue; + + ScopePtr defnScope = childScope(classDeclaration, scope); + + TypeId initialType = arena->addType(BlockedType{}); + TypeFun initialFun{initialType}; + scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun); + + classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location; + } } if (FFlag::LuauUserTypeFunExportedAndLocal) @@ -1623,6 +1670,11 @@ static bool isMetamethod(const Name& name) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) { + // If a class with the same name was already defined, we skip over + auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value); + if (FFlag::LuauNewSolverPrePopulateClasses && bindingIt == scope->exportedTypeBindings.end()) + return ControlFlow::None; + std::optional superTy = std::make_optional(builtinTypes->classType); if (declaredClass->superName) { @@ -1637,7 +1689,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas // We don't have generic classes, so this assertion _should_ never be hit. LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); - superTy = lookupType->type; + if (FFlag::LuauNewSolverPrePopulateClasses) + superTy = follow(lookupType->type); + else + superTy = lookupType->type; if (!get(follow(*superTy))) { @@ -1660,7 +1715,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas ctv->metatable = metaTy; - scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; + + if (FFlag::LuauNewSolverPrePopulateClasses) + { + TypeId classBindTy = bindingIt->second.type; + emplaceType(asMutable(classBindTy), classTy); + } + else + scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; if (declaredClass->indexer) { @@ -2365,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(); @@ -2932,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()); } } @@ -3898,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 f0cd03f23..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}); @@ -1445,7 +1463,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() || expr->is() || expr->is() || expr->is()) + else if (expr->is() || expr->is() || expr->is() || + expr->is()) { Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; u2.unify(actualArgTy, expectedArgTy); @@ -2061,6 +2080,8 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullscope); + if (FFlag::LuauTrackInteriorFreeTypesOnScope) + trackInteriorFreeType(constraint->scope, f); shiftReferences(resultTy, f); emplaceType(asMutable(resultTy), f); } @@ -2196,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}; @@ -2452,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: @@ -2561,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/Compile.cpp b/CLI/Compile.cpp index 7d95387c3..ed9d419bb 100644 --- a/CLI/Compile.cpp +++ b/CLI/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/FileUtils.cpp b/CLI/FileUtils.cpp index 6925e99f1..414bec511 100644 --- a/CLI/FileUtils.cpp +++ b/CLI/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 85317b60c..134a49f21 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -235,7 +235,7 @@ static uint8_t getBytecodeConstantTag(Proto* proto, unsigned ki) return LBC_TYPE_ANY; } -static void applyBuiltinCall(int bfid, BytecodeTypes& types) +static void applyBuiltinCall(LuauBuiltinFunction bfid, BytecodeTypes& types) { switch (bfid) { @@ -549,6 +549,12 @@ static void applyBuiltinCall(int bfid, BytecodeTypes& types) types.b = LBC_TYPE_VECTOR; types.c = LBC_TYPE_VECTOR; // We can mark optional arguments break; + case LBF_MATH_LERP: + types.result = LBC_TYPE_NUMBER; + types.a = LBC_TYPE_NUMBER; + types.b = LBC_TYPE_NUMBER; + types.c = LBC_TYPE_NUMBER; + break; } } @@ -842,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]; @@ -873,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)); } @@ -895,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]; @@ -917,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]; @@ -948,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)); } @@ -970,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]; @@ -991,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]; @@ -1020,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)); } @@ -1086,7 +1100,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); int ra = LUAU_INSN_A(call); - applyBuiltinCall(bfid, bcType); + applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); regTags[ra + 1] = bcType.a; regTags[ra + 2] = bcType.b; regTags[ra + 3] = bcType.c; @@ -1105,7 +1119,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); int ra = LUAU_INSN_A(call); - applyBuiltinCall(bfid, bcType); + applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[ra] = bcType.result; @@ -1122,7 +1136,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); int ra = LUAU_INSN_A(call); - applyBuiltinCall(bfid, bcType); + applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[int(pc[1])] = bcType.b; @@ -1141,7 +1155,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); int ra = LUAU_INSN_A(call); - applyBuiltinCall(bfid, bcType); + applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[aux & 0xff] = bcType.b; 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 0c8eee2fe..ca96a8211 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -18,9 +18,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 +52,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 +200,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 +423,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 +488,9 @@ struct ConstPropState maxReg = 0; + if (FFlag::LuauCodeGenLimitLiveSlotReuse) + instPos = 0u; + inSafeEnv = false; checkedGc = false; @@ -436,6 +511,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 +525,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 +536,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) @@ -550,6 +631,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid case LBF_VECTOR_CLAMP: case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: + case LBF_MATH_LERP: break; case LBF_TABLE_INSERT: state.invalidateHeap(); @@ -569,6 +651,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: @@ -771,7 +856,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (tag == LUA_TBOOLEAN && (value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int))) canSplitTvalueStore = true; - else if (tag == LUA_TNUMBER && (value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double))) + else if (tag == LUA_TNUMBER && + (value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double))) canSplitTvalueStore = true; else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst) canSplitTvalueStore = true; @@ -1174,19 +1260,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/Bytecode.h b/Common/include/Luau/Bytecode.h index 8d2813939..a151056cd 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -613,6 +613,9 @@ enum LuauBuiltinFunction LBF_VECTOR_CLAMP, LBF_VECTOR_MIN, LBF_VECTOR_MAX, + + // math.lerp + LBF_MATH_LERP, }; // Capture type, used in LOP_CAPTURE 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/BuiltinFolding.cpp b/Compiler/src/BuiltinFolding.cpp index 0886e94aa..916021a66 100644 --- a/Compiler/src/BuiltinFolding.cpp +++ b/Compiler/src/BuiltinFolding.cpp @@ -5,6 +5,8 @@ #include +LUAU_FASTFLAG(LuauCompileMathLerp) + namespace Luau { namespace Compile @@ -479,6 +481,19 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count) return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, args[3].valueNumber); } break; + + case LBF_MATH_LERP: + if (FFlag::LuauCompileMathLerp && count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && + args[2].type == Constant::Type_Number) + { + double a = args[0].valueNumber; + double b = args[1].valueNumber; + double t = args[2].valueNumber; + + double v = (t == 1.0) ? b : a + (b - a) * t; + return cnum(v); + } + break; } return cvar(); diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index e8b0cd980..af0a5f021 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -7,8 +7,8 @@ #include -LUAU_FASTFLAGVARIABLE(LuauVectorBuiltins) LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins) +LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp) namespace Luau { @@ -140,6 +140,8 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op return LBF_MATH_SIGN; if (builtin.method == "round") return LBF_MATH_ROUND; + if (FFlag::LuauCompileMathLerp && builtin.method == "lerp") + return LBF_MATH_LERP; } if (builtin.object == "bit32") @@ -226,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; @@ -556,6 +558,10 @@ BuiltinInfo getBuiltinInfo(int bfid) case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: return {-1, 1}; // variadic + + case LBF_MATH_LERP: + LUAU_ASSERT(FFlag::LuauCompileMathLerp); + return {3, 1, BuiltinInfo::Flag_NoneSafe}; } LUAU_UNREACHABLE(); 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 02aec11aa..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; @@ -747,6 +746,7 @@ struct TypeMapVisitor : AstVisitor case LBF_BUFFER_READF64: case LBF_VECTOR_MAGNITUDE: case LBF_VECTOR_DOT: + case LBF_MATH_LERP: recordResolvedType(node, &builtinTypes.numberType); break; 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..10aa2534d 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) > 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) > 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/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 0bca4495b..6d71836e7 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -1694,6 +1694,23 @@ static int luauF_vectormax(lua_State* L, StkId res, TValue* arg0, int nresults, return -1; } +static int luauF_lerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) +{ + if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) + { + double a = nvalue(arg0); + double b = nvalue(args); + double t = nvalue(args + 1); + + double r = (t == 1.0) ? b : a + (b - a) * t; + + setnvalue(res, r); + return 1; + } + + return -1; +} + static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { return -1; @@ -1889,6 +1906,8 @@ const luau_FastFunction luauF_table[256] = { luauF_vectormin, luauF_vectormax, + luauF_lerp, + // When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. // This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing. // Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest. 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 3a93abcf9..9bd216073 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -7,7 +7,7 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauMathMap) +LUAU_FASTFLAGVARIABLE(LuauMathLerp) #undef PI #define PI (3.14159265358979323846) @@ -418,6 +418,17 @@ static int math_map(lua_State* L) return 1; } +static int math_lerp(lua_State* L) +{ + double a = luaL_checknumber(L, 1); + double b = luaL_checknumber(L, 2); + double t = luaL_checknumber(L, 3); + + double r = (t == 1.0) ? b : a + (b - a) * t; + lua_pushnumber(L, r); + return 1; +} + static const luaL_Reg mathlib[] = { {"abs", math_abs}, {"acos", math_acos}, @@ -451,6 +462,7 @@ static const luaL_Reg mathlib[] = { {"clamp", math_clamp}, {"sign", math_sign}, {"round", math_round}, + {"map", math_map}, {NULL, NULL}, }; @@ -471,10 +483,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"); } return 1; 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 c0e81371d..e68ce2c79 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -31,16 +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() { @@ -654,12 +654,14 @@ 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"); } @@ -891,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}; @@ -911,9 +912,7 @@ TEST_CASE("VectorLibrary") copts.optimizationLevel = 2; } - runConformance( - "vector_library.lua", [](lua_State* L) {}, nullptr, nullptr, &copts - ); + runConformance("vector_library.lua", [](lua_State* L) {}, nullptr, nullptr, &copts); } static void populateRTTI(lua_State* L, Luau::TypeId type) @@ -987,7 +986,7 @@ 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( "types.lua", @@ -2578,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 71a46878d..16574cff1 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 a5af44fba..f02983e51 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -11,7 +11,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) -LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) @@ -1309,7 +1308,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.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 5a530e833..2ab90ab51 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -9,6 +9,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses) + TEST_SUITE_BEGIN("DefinitionTests"); TEST_CASE_FIXTURE(Fixture, "definition_file_simple") @@ -492,11 +494,8 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_indexer") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") { - unfreeze(frontend.globals.globalTypes); - LoadDefinitionFileResult result = frontend.loadDefinitionFile( - frontend.globals, - frontend.globals.globalScope, - R"( + ScopedFastFlag _{FFlag::LuauNewSolverPrePopulateClasses, true}; + loadDefinition(R"( declare class Channel Messages: { Message } OnMessage: (message: Message) -> () @@ -506,13 +505,19 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") Text: string Channel: Channel end - )", - "@test", - /* captureComments */ false - ); - freeze(frontend.globals.globalTypes); + )"); - REQUIRE(result.success); + CheckResult result = check(R"( + local a: Channel + local b = a.Messages[1] + local c = b.Channel + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ(toString(requireType("a")), "Channel"); + CHECK_EQ(toString(requireType("b")), "Message"); + CHECK_EQ(toString(requireType("c")), "Channel"); } TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set") 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/math.lua b/tests/conformance/math.lua index 97c444624..fbd8f9ddb 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -402,6 +402,22 @@ assert(math.map(4, 4, 1, 2, 0) == 2) assert(math.map(-8, 0, 4, 0, 2) == -4) assert(math.map(16, 0, 4, 0, 2) == 8) +-- lerp basics +assert(math.lerp(1, 5, 0) == 1) +assert(math.lerp(1, 5, 1) == 5) +assert(math.lerp(1, 5, 0.5) == 3) +assert(math.lerp(1, 5, 1.5) == 7) +assert(math.lerp(1, 5, -0.5) == -1) + +-- lerp properties +local sq2, sq3 = math.sqrt(2), math.sqrt(3) +assert(math.lerp(sq2, sq3, 0) == sq2) -- exact at 0 +assert(math.lerp(sq2, sq3, 1) == sq3) -- exact at 1 +assert(math.lerp(-sq3, sq2, 1) == sq2) -- exact at 1 (fails for a + t*(b-a)) +assert(math.lerp(sq2, sq2, sq2 / 2) <= math.lerp(sq2, sq2, 1)) -- monotonic (fails for a*t + b*(1-t)) +assert(math.lerp(-sq3, sq2, 1) <= math.sqrt(2)) -- bounded (fails for a + t*(b-a)) +assert(math.lerp(sq2, sq2, sq2 / 2) == sq2) -- consistent (fails for a*t + b*(1-t)) + assert(tostring(math.pow(-2, 0.5)) == "nan") -- test that fastcalls return correct number of results @@ -464,5 +480,6 @@ assert(math.sign("2") == 1) assert(math.sign("-2") == -1) assert(math.sign("0") == 0) assert(math.round("1.8") == 2) +assert(math.lerp("1", "5", 0.5) == 3) return('OK') 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') From ba71ff135b5e431b7f6f6270eb50fb72d8071fcd Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 10 Jan 2025 11:20:39 -0800 Subject: [PATCH 2/4] Explicitly cast size_t to uint64_t in buffer_readbits / buffer_writebits --- VM/src/lbuflib.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 10aa2534d..179e41711 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -262,7 +262,7 @@ static int buffer_readbits(lua_State* L) if (unsigned(bitcount) > 32) luaL_error(L, "bit count is out of range of [0; 32]"); - if (uint64_t(bitoffset + bitcount) > len * 8) + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) luaL_error(L, "buffer access out of bounds"); unsigned startbyte = unsigned(bitoffset / 8); @@ -292,7 +292,7 @@ static int buffer_writebits(lua_State* L) if (unsigned(bitcount) > 32) luaL_error(L, "bit count is out of range of [0; 32]"); - if (uint64_t(bitoffset + bitcount) > len * 8) + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) luaL_error(L, "buffer access out of bounds"); unsigned startbyte = unsigned(bitoffset / 8); From aaaeae5db88cc53a3549557649705b4e58798a2f Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:33:11 -0800 Subject: [PATCH 3/4] Sync to upstream/release/657 --- Analysis/include/Luau/ConstraintSolver.h | 5 + Analysis/include/Luau/TypeFunctionRuntime.h | 23 +- Analysis/src/AstQuery.cpp | 36 +- Analysis/src/BuiltinDefinitions.cpp | 24 +- Analysis/src/ConstraintSolver.cpp | 100 ++- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 245 ++++++- Analysis/src/TypeFunctionRuntime.cpp | 669 ++++++++++++++++---- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 222 ++++++- Ast/include/Luau/Parser.h | 2 +- Ast/src/Parser.cpp | 11 +- CLI/{ => include/Luau}/Coverage.h | 0 CLI/{ => include/Luau}/FileUtils.h | 0 CLI/{ => include/Luau}/Flags.h | 0 CLI/{ => include/Luau}/Profiler.h | 0 CLI/{ => include/Luau}/Repl.h | 0 CLI/{ => include/Luau}/Require.h | 0 CLI/{ => src}/Analyze.cpp | 6 +- CLI/{ => src}/Ast.cpp | 2 +- CLI/{ => src}/Bytecode.cpp | 4 +- CLI/{ => src}/Compile.cpp | 4 +- CLI/{ => src}/Coverage.cpp | 2 +- CLI/{ => src}/FileUtils.cpp | 2 +- CLI/{ => src}/Flags.cpp | 0 CLI/{ => src}/Profiler.cpp | 0 CLI/{ => src}/Reduce.cpp | 2 +- CLI/{ => src}/Repl.cpp | 12 +- CLI/{ => src}/ReplEntry.cpp | 2 +- CLI/{ => src}/Require.cpp | 6 +- CLI/{ => src}/Web.cpp | 0 CMakeLists.txt | 5 +- CodeGen/include/Luau/AssemblyBuilderX64.h | 1 + CodeGen/include/Luau/CodeAllocator.h | 2 +- CodeGen/include/Luau/CodeGen.h | 268 +------- CodeGen/include/Luau/CodeGenCommon.h | 6 + CodeGen/include/Luau/CodeGenOptions.h | 188 ++++++ CodeGen/include/Luau/IrData.h | 31 +- CodeGen/include/Luau/IrDump.h | 2 +- CodeGen/include/Luau/IrUtils.h | 1 + CodeGen/include/Luau/LoweringStats.h | 103 +++ CodeGen/src/AssemblyBuilderX64.cpp | 5 + CodeGen/src/BytecodeAnalysis.cpp | 2 +- CodeGen/src/CodeBlockUnwind.cpp | 1 + CodeGen/src/CodeGen.cpp | 3 +- CodeGen/src/CodeGenAssembly.cpp | 1 - CodeGen/src/CodeGenContext.cpp | 1 + CodeGen/src/CodeGenLower.h | 5 + CodeGen/src/CodeGenUtils.cpp | 22 +- CodeGen/src/CodeGenUtils.h | 4 +- CodeGen/src/EmitCommonX64.cpp | 6 +- CodeGen/src/EmitInstructionX64.cpp | 8 +- CodeGen/src/IrDump.cpp | 2 + CodeGen/src/IrLoweringA64.cpp | 40 +- CodeGen/src/IrLoweringX64.cpp | 48 +- CodeGen/src/IrRegAllocA64.cpp | 2 +- CodeGen/src/IrRegAllocX64.cpp | 2 +- CodeGen/src/IrTranslateBuiltins.cpp | 39 ++ CodeGen/src/IrTranslation.cpp | 2 +- CodeGen/src/IrUtils.cpp | 13 + CodeGen/src/NativeState.h | 22 +- CodeGen/src/OptimizeConstProp.cpp | 2 + Makefile | 20 +- Sources.cmake | 53 +- VM/src/lapi.cpp | 24 +- VM/src/lbuflib.cpp | 8 +- VM/src/lbuiltins.cpp | 14 +- VM/src/lfunc.cpp | 4 +- VM/src/lfunc.h | 4 +- VM/src/lgc.cpp | 18 +- VM/src/lgc.h | 2 +- VM/src/lgcdebug.cpp | 12 +- VM/src/lmem.cpp | 2 +- VM/src/lobject.h | 10 +- VM/src/lstate.h | 8 +- VM/src/ltable.cpp | 70 +- VM/src/ltable.h | 30 +- VM/src/ltablib.cpp | 36 +- VM/src/ltm.cpp | 6 +- VM/src/ltm.h | 2 +- VM/src/lvm.h | 2 +- VM/src/lvmexecute.cpp | 30 +- VM/src/lvmload.cpp | 8 +- VM/src/lvmutils.cpp | 8 +- tests/AnyTypeSummary.test.cpp | 10 +- tests/AssemblyBuilderX64.test.cpp | 1 + tests/AstQuery.test.cpp | 4 - tests/CodeAllocator.test.cpp | 1 + tests/Conformance.test.cpp | 8 +- tests/Parser.test.cpp | 23 + tests/Repl.test.cpp | 2 +- tests/RequireByString.test.cpp | 12 +- tests/TypeFunction.user.test.cpp | 500 +++++++++++++++ tests/TypeInfer.builtins.test.cpp | 16 + tests/conformance/math.lua | 1 + tests/main.cpp | 2 + tools/natvis/VM.natvis | 2 +- 95 files changed, 2421 insertions(+), 748 deletions(-) rename CLI/{ => include/Luau}/Coverage.h (100%) rename CLI/{ => include/Luau}/FileUtils.h (100%) rename CLI/{ => include/Luau}/Flags.h (100%) rename CLI/{ => include/Luau}/Profiler.h (100%) rename CLI/{ => include/Luau}/Repl.h (100%) rename CLI/{ => include/Luau}/Require.h (100%) rename CLI/{ => src}/Analyze.cpp (99%) rename CLI/{ => src}/Ast.cpp (98%) rename CLI/{ => src}/Bytecode.cpp (99%) rename CLI/{ => src}/Compile.cpp (99%) rename CLI/{ => src}/Coverage.cpp (98%) rename CLI/{ => src}/FileUtils.cpp (99%) rename CLI/{ => src}/Flags.cpp (100%) rename CLI/{ => src}/Profiler.cpp (100%) rename CLI/{ => src}/Reduce.cpp (99%) rename CLI/{ => src}/Repl.cpp (99%) rename CLI/{ => src}/ReplEntry.cpp (89%) rename CLI/{ => src}/Require.cpp (99%) rename CLI/{ => src}/Web.cpp (100%) create mode 100644 CodeGen/include/Luau/CodeGenOptions.h create mode 100644 CodeGen/include/Luau/LoweringStats.h diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index ceb9cab41..bb358abbc 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -420,6 +420,11 @@ struct ConstraintSolver void throwUserCancelError() const; ToStringOptions opts; + + void fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes + ); }; void dump(NotNull rootScope, struct ToStringOptions& opts); diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 356d34a54..d715ccd38 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -119,7 +119,14 @@ struct TypeFunctionVariadicTypePack TypeFunctionTypeId type; }; -using TypeFunctionTypePackVariant = Variant; +struct TypeFunctionGenericTypePack +{ + bool isNamed = false; + + std::string name; +}; + +using TypeFunctionTypePackVariant = Variant; struct TypeFunctionTypePackVar { @@ -135,6 +142,9 @@ struct TypeFunctionTypePackVar struct TypeFunctionFunctionType { + std::vector generics; + std::vector genericPacks; + TypeFunctionTypePackId argTypes; TypeFunctionTypePackId retTypes; }; @@ -210,6 +220,14 @@ struct TypeFunctionClassType std::string name; }; +struct TypeFunctionGenericType +{ + bool isNamed = false; + bool isPack = false; + + std::string name; +}; + using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionPrimitiveType, TypeFunctionAnyType, @@ -221,7 +239,8 @@ using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionNegationType, TypeFunctionFunctionType, TypeFunctionTableType, - TypeFunctionClassType>; + TypeFunctionClassType, + TypeFunctionGenericType>; struct TypeFunctionType { diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 93dabeae8..96c4ea107 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -13,8 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition) - namespace Luau { @@ -518,7 +516,6 @@ static std::optional getMetatableDocumentation( const AstName& index ) { - LUAU_ASSERT(FFlag::LuauDocumentationAtPosition); auto indexIt = mtable->props.find("__index"); if (indexIt == mtable->props.end()) return std::nullopt; @@ -575,26 +572,7 @@ std::optional getDocumentationSymbolAtPosition(const Source } else if (const ClassType* ctv = get(parentTy)) { - if (FFlag::LuauDocumentationAtPosition) - { - while (ctv) - { - if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) - { - if (FFlag::LuauSolverV2) - { - if (auto ty = propIt->second.readTy) - return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); - } - else - return checkOverloadedDocumentationSymbol( - module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol - ); - } - ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; - } - } - else + while (ctv) { if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) { @@ -608,17 +586,15 @@ std::optional getDocumentationSymbolAtPosition(const Source module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol ); } + ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; } } - else if (FFlag::LuauDocumentationAtPosition) + else if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) { - if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) + if (auto mtable = get(*ptv->metatable)) { - if (auto mtable = get(*ptv->metatable)) - { - if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) - return docSymbol; - } + if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) + return docSymbol; } } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 2db6d5673..fba3c9643 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2) LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix) +LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2) LUAU_FASTFLAG(LuauVectorDefinitionsExtra) @@ -631,10 +632,29 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; // use subtyping instead here SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); + if (!result.isSubtype) { - Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); - context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + if (FFlag::LuauStringFormatErrorSuppression) + { + switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) + { + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + break; + case ErrorSuppression::DoNotSuppress: + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + + if (!reasonings.suppressed) + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } + } + else + { + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } } } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 1be02a71e..e6e549163 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes) namespace Luau { @@ -1153,6 +1154,42 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul return true; } +void ConstraintSolver::fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes +) +{ + for (std::optional ty : discriminantTypes) + { + if (!ty) + continue; + + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } + + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } + } +} + bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull constraint) { TypeId fn = follow(c.fn); @@ -1168,6 +1205,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(asMutable(c.result), builtinTypes->anyTypePack); unblock(c.result, constraint->location); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1175,12 +1214,16 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(fn)) { bind(constraint, c.result, builtinTypes->errorRecoveryTypePack()); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } if (get(fn)) { bind(constraint, c.result, builtinTypes->neverTypePack); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1261,36 +1304,45 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(constraint, c.result, constraint->scope); } - for (std::optional ty : c.discriminantTypes) + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) { - if (!ty) - continue; - - // If the discriminant type has been transmuted, we need to unblock them. - if (!isBlocked(*ty)) + fillInDiscriminantTypes(constraint, c.discriminantTypes); + } + else + { + // NOTE: This is the body of the `fillInDiscriminantTypes` helper. + for (std::optional ty : c.discriminantTypes) { - unblock(*ty, constraint->location); - continue; - } + if (!ty) + continue; - if (FFlag::LuauRemoveNotAnyHack) - { - // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. - emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); - } - else - { - // We use `any` here because the discriminant type may be pointed at by both branches, - // where the discriminant type is not negated, and the other where it is negated, i.e. - // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` - // v.s. - // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` - // - // In practice, users cannot negate `any`, so this is an implementation detail we can always change. - emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } + + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } } } + OverloadResolver resolver{ builtinTypes, NotNull{arena}, diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index caff137d5..e5d9e3b6d 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,13 +2,13 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) -LUAU_FASTFLAG(LuauBufferBitMethods) +LUAU_FASTFLAG(LuauBufferBitMethods2) +LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition) namespace Luau { -// TODO: there has to be a better way, like splitting up per library -static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC( declare bit32: { band: @checked (...number) -> number, @@ -201,6 +201,228 @@ declare function unpack(tab: {V}, i: number?, j: number?): ...V )BUILTIN_SRC"; +static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( + +@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 + +-- 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 kBuiltinDefinitionBit32Src = 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, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( + +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, + lerp: @checked (a: number, b: number, t: number) -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( + +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, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( + +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) +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( + +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, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( + +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), +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( + +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, +} + +)BUILTIN_SRC"; + static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC( --- Buffer API declare buffer: { @@ -323,9 +545,20 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = kBuiltinDefinitionLuaSrcChecked; - - result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; + std::string result = FFlag::LuauMathMapDefinition ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionLuaSrcChecked_DEPRECATED; + + if (FFlag::LuauMathMapDefinition) + { + result += kBuiltinDefinitionBit32Src; + result += kBuiltinDefinitionMathSrc; + result += kBuiltinDefinitionOsSrc; + result += kBuiltinDefinitionCoroutineSrc; + result += kBuiltinDefinitionTableSrc; + result += kBuiltinDefinitionDebugSrc; + result += kBuiltinDefinitionUtf8Src; + } + + result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; if (FFlag::LuauVectorDefinitionsExtra) result += kBuiltinDefinitionVectorSrc; diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 24c75a510..3766224b0 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -14,9 +14,12 @@ #include LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunPrintToError) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail) namespace Luau { @@ -160,6 +163,8 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty) return "function"; else if (get(ty)) return "class"; + else if (FFlag::LuauUserTypeFunGenerics && get(ty)) + return "generic"; LUAU_UNREACHABLE(); luaL_error(L, "VM encountered unexpected type variant when determining tag"); @@ -266,6 +271,20 @@ static int createSingleton(lua_State* L) luaL_error(L, "types.singleton: can't create singleton from `%s` type", lua_typename(L, 1)); } +// Luau: `types.generic(name: string, ispack: boolean?) -> type +// Create a generic type with the specified type. If an optinal boolean is set to true, result is a generic pack +static int createGeneric(lua_State* L) +{ + const char* name = luaL_checkstring(L, 1); + bool isPack = luaL_optboolean(L, 2, false); + + if (strlen(name) == 0) + luaL_error(L, "types.generic: generic name cannot be empty"); + + allocTypeUserData(L, TypeFunctionGenericType{/* isNamed */ true, isPack, name}); + return 1; +} + // Luau: `self:value() -> type` // Returns the value of a singleton static int getSingletonValue(lua_State* L) @@ -413,10 +432,21 @@ static int getNegatedValue(lua_State* L) luaL_error(L, "type.inner: expected 1 argument, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); - if (auto tfnt = get(self); !tfnt) - allocTypeUserData(L, tfnt->type->type); + + if (FFlag::LuauUserTypeFunFixInner) + { + if (auto tfnt = get(self); tfnt) + allocTypeUserData(L, tfnt->type->type); + else + luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str()); + } else - luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str()); + { + if (auto tfnt = get(self); !tfnt) + allocTypeUserData(L, tfnt->type->type); + else + luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str()); + } return 1; } @@ -768,9 +798,159 @@ static int setTableMetatable(lua_State* L) return 0; } -// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}) -> type` -// Returns the type instance representing a function -static int createFunction(lua_State* L) +static std::tuple, std::vector> getGenerics(lua_State* L, int idx, const char* fname) +{ + std::vector types; + std::vector packs; + + if (lua_istable(L, idx)) + { + lua_pushvalue(L, idx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + TypeFunctionTypeId ty = getTypeUserData(L, -1); + + if (auto gty = get(ty)) + { + if (gty->isPack) + { + packs.push_back(allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name})); + } + else + { + if (!packs.empty()) + luaL_error(L, "%s: generic type cannot follow a generic pack", fname); + + types.push_back(ty); + } + } + else + { + luaL_error(L, "%s: table member was not a generic type", fname); + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + else if (!lua_isnoneornil(L, idx)) + { + luaL_typeerrorL(L, idx, "table"); + } + + return {types, packs}; +} + +static TypeFunctionTypePackId getTypePack(lua_State* L, int headIdx, int tailIdx) +{ + TypeFunctionTypePackId result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + + std::vector head; + + if (lua_istable(L, headIdx)) + { + lua_pushvalue(L, headIdx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + std::optional tail; + + if (auto type = optionalTypeUserData(L, tailIdx)) + { + if (auto gty = get(*type); gty && gty->isPack) + tail = allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name}); + else + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + } + + if (head.size() == 0 && tail.has_value()) + result = *tail; + else + result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + + return result; +} + +static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) +{ + if (auto tftp = get(tp)) + { + lua_createtable(L, 0, 2); + + if (!tftp->head.empty()) + { + lua_createtable(L, int(tftp->head.size()), 0); + int pos = 1; + + for (auto el : tftp->head) + { + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + lua_setfield(L, -2, "head"); + } + + if (tftp->tail.has_value()) + { + if (auto tfvp = get(*tftp->tail)) + allocTypeUserData(L, tfvp->type->type); + else if (auto tfgp = get(*tftp->tail)) + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + else + luaL_error(L, "unsupported type pack type"); + + lua_setfield(L, -2, "tail"); + } + } + else if (auto tfvp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + else if (auto tfgp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + lua_setfield(L, -2, "tail"); + } + else + { + luaL_error(L, "unsupported type pack type"); + } +} + +static int createFunction_DEPRECATED(lua_State* L) { int argumentCount = lua_gettop(L); if (argumentCount > 2) @@ -858,7 +1038,62 @@ static int createFunction(lua_State* L) else if (!lua_isnoneornil(L, 2)) luaL_typeerrorL(L, 2, "table"); - allocTypeUserData(L, TypeFunctionFunctionType{argTypes, retTypes}); + allocTypeUserData(L, TypeFunctionFunctionType{{}, {}, argTypes, retTypes}); + + return 1; +} + +// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +// Returns the type instance representing a function +static int createFunction(lua_State* L) +{ + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "types.newfunction: expected 0-3 arguments, but got %d", argumentCount); + + TypeFunctionTypePackId argTypes = nullptr; + + if (lua_istable(L, 1)) + { + lua_getfield(L, 1, "head"); + lua_getfield(L, 1, "tail"); + + argTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 1)) + { + luaL_typeerrorL(L, 1, "table"); + } + else + { + argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + TypeFunctionTypePackId retTypes = nullptr; + + if (lua_istable(L, 2)) + { + lua_getfield(L, 2, "head"); + lua_getfield(L, 2, "tail"); + + retTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 2)) + { + luaL_typeerrorL(L, 2, "table"); + } + else + { + retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); + + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); return 1; } @@ -876,38 +1111,45 @@ static int setFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.setparameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) + { + tfft->argTypes = getTypePack(L, 2, 3); + } + else { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) + std::vector head{}; + if (lua_istable(L, 2)) { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) { - lua_pop(L, 1); - break; - } + lua_pushinteger(L, i); + lua_gettable(L, 2); - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } - lua_pop(L, 1); // Remove `ty` from stack + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); + + lua_pop(L, 1); // Remove `ty` from stack + } } - } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); - if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack - tfft->argTypes = *tail; - else // Make argTypes a type pack - tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack + tfft->argTypes = *tail; + else // Make argTypes a type pack + tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -925,52 +1167,60 @@ static int getFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.parameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->argTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; + pushTypePack(L, tfft->argTypes); + } + else + { + if (auto tftp = get(tfft->argTypes)) + { + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; - lua_createtable(L, 0, size); + lua_createtable(L, 0, size); - int argSize = (int)tftp->head.size(); - if (argSize > 0) - { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->argTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); - } - - return 1; - } - - if (auto tfvp = get(tfft->argTypes)) - { - lua_createtable(L, 0, 1); - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); + return 1; + } - return 1; + lua_createtable(L, 0, 0); } - lua_createtable(L, 0, 0); return 1; } @@ -987,38 +1237,45 @@ static int setFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.setreturns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) + tfft->retTypes = getTypePack(L, 2, 3); + } + else + { + std::vector head{}; + if (lua_istable(L, 2)) { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) { - lua_pop(L, 1); - break; - } + lua_pushinteger(L, i); + lua_gettable(L, 2); - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); - lua_pop(L, 1); // Remove `ty` from stack + lua_pop(L, 1); // Remove `ty` from stack + } } - } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); - if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack - tfft->retTypes = *tail; - else // Make retTypes a type pack - tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack + tfft->retTypes = *tail; + else // Make retTypes a type pack + tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -1036,52 +1293,109 @@ static int getFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.returns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->retTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; + pushTypePack(L, tfft->retTypes); + } + else + { + if (auto tftp = get(tfft->retTypes)) + { + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; - lua_createtable(L, 0, size); + lua_createtable(L, 0, size); - int argSize = (int)tftp->head.size(); - if (argSize > 0) - { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->retTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); + + return 1; } - return 1; + lua_createtable(L, 0, 0); } - if (auto tfvp = get(tfft->retTypes)) - { - lua_createtable(L, 0, 1); + return 1; +} - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); +// Luau: `self:setgenerics(generics: {type}?)` +static int setFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = getMutable(self); + if (!tfft) + luaL_error(L, "type.setgenerics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - return 1; + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "type.setgenerics: expected 3 arguments, but got %d", argumentCount); + + auto [genericTypes, genericPacks] = getGenerics(L, 2, "types.setgenerics"); + + tfft->generics = std::move(genericTypes); + tfft->genericPacks = std::move(genericPacks); + + return 0; +} + +// Luau: `self:generics() -> {type}` +static int getFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = get(self); + if (!tfft) + luaL_error(L, "type.generics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + + lua_createtable(L, int(tfft->generics.size()) + int(tfft->genericPacks.size()), 0); + + int pos = 1; + + for (const auto& el : tfft->generics) + { + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + for (const auto& el : tfft->genericPacks) + { + auto gty = get(el); + LUAU_ASSERT(gty); + allocTypeUserData(L, TypeFunctionGenericType{gty->isNamed, true, gty->name}); + lua_rawseti(L, -2, pos++); } - lua_createtable(L, 0, 0); return 1; } @@ -1107,6 +1421,36 @@ static int getClassParent(lua_State* L) return 1; } +// Luau: `self:name() -> string?` +// Returns the name of the generic or 'nil' if the generic is unnamed +static int getGenericName(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.name: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + if (tfgt->isNamed) + lua_pushstring(L, tfgt->name.c_str()); + else + lua_pushnil(L); + + return 1; +} + +// Luau: `self:ispack() -> boolean` +// Returns true if the generic is a pack +static int getGenericIsPack(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.ispack: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + lua_pushboolean(L, tfgt->isPack); + return 1; +} + // Luau: `self:properties() -> {[type]: { read: type?, write: type? }}` // Returns the properties of a table or class type static int getProps(lua_State* L) @@ -1376,7 +1720,7 @@ static int checkTag(lua_State* L) TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty); // Forward declaration -// Luau: `types.copy(arg: string) -> type` +// Luau: `types.copy(arg: type) -> type` // Returns a deep copy of the argument static int deepCopy(lua_State* L) { @@ -1426,8 +1770,9 @@ void registerTypesLibrary(lua_State* L) {"unionof", createUnion}, {"intersectionof", createIntersection}, {"newtable", createTable}, - {"newfunction", createFunction}, + {"newfunction", FFlag::LuauUserTypeFunGenerics ? createFunction : createFunction_DEPRECATED}, {"copy", deepCopy}, + {FFlag::LuauUserTypeFunGenerics ? "generic" : nullptr, FFlag::LuauUserTypeFunGenerics ? createGeneric : nullptr}, {nullptr, nullptr} }; @@ -1492,6 +1837,8 @@ void registerTypeUserData(lua_State* L) {"parameters", getFunctionParameters}, {"setreturns", setFunctionReturns}, {"returns", getFunctionReturns}, + {"setgenerics", setFunctionGenerics}, + {"generics", getFunctionGenerics}, // Union and Intersection type methods {"components", getComponents}, @@ -1499,6 +1846,14 @@ void registerTypeUserData(lua_State* L) // Class type methods {"parent", getClassParent}, + // Function type methods (cont.) + {FFlag::LuauUserTypeFunGenerics ? "setgenerics" : nullptr, FFlag::LuauUserTypeFunGenerics ? setFunctionGenerics : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "generics" : nullptr, FFlag::LuauUserTypeFunGenerics ? getFunctionGenerics : nullptr}, + + // Generic type methods + {FFlag::LuauUserTypeFunGenerics ? "name" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericName : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "ispack" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericIsPack : nullptr}, + {nullptr, nullptr} }; @@ -1764,6 +2119,27 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc if (seenSetContains(seen, &lhs, &rhs)) return true; + if (FFlag::LuauUserTypeFunGenerics) + { + if (lhs.generics.size() != rhs.generics.size()) + return false; + + for (auto l = lhs.generics.begin(), r = rhs.generics.begin(); l != lhs.generics.end() && r != rhs.generics.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + + if (lhs.genericPacks.size() != rhs.genericPacks.size()) + return false; + + for (auto l = lhs.genericPacks.begin(), r = rhs.genericPacks.begin(); l != lhs.genericPacks.end() && r != rhs.genericPacks.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + } + if (bool(lhs.argTypes) != bool(rhs.argTypes)) return false; @@ -1864,6 +2240,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType return areEqual(seen, *lf, *rf); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericType* lg = get(&lhs); + const TypeFunctionGenericType* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->isPack == rg->isPack && lg->name == rg->name; + } + } + return false; } @@ -1910,6 +2296,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionTypePackVar& lhs, const TypeFunct return areEqual(seen, *lv, *rv); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericTypePack* lg = get(&lhs); + const TypeFunctionGenericTypePack* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->name == rg->name; + } + } + return false; } @@ -2134,10 +2530,14 @@ class TypeFunctionCloner else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) target = ty; // Don't copy a class since they are immutable + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); + else + LUAU_ASSERT(!"Unknown type"); types[ty] = target; queue.emplace_back(ty, target); @@ -2155,6 +2555,10 @@ class TypeFunctionCloner target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); gPack && FFlag::LuauUserTypeFunGenerics) + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->isNamed, gPack->name}); + else + LUAU_ASSERT(!"Unknown type"); packs[tp] = target; queue.emplace_back(tp, target); @@ -2185,6 +2589,9 @@ class TypeFunctionCloner cloneChildren(f1, f2); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) cloneChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + cloneChildren(g1, g2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2196,6 +2603,9 @@ class TypeFunctionCloner else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) cloneChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + cloneChildren(gPack1, gPack2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2276,6 +2686,17 @@ class TypeFunctionCloner void cloneChildren(TypeFunctionFunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowClone(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowClone(tp)); + } + f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); } @@ -2285,16 +2706,32 @@ class TypeFunctionCloner // noop. } + void cloneChildren(TypeFunctionGenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void cloneChildren(TypeFunctionTypePack* t1, TypeFunctionTypePack* t2) { for (TypeFunctionTypeId& ty : t1->head) t2->head.push_back(shallowClone(ty)); + + if (FFlag::LuauUserTypeFunCloneTail) + { + if (t1->tail) + t2->tail = shallowClone(*t1->tail); + } } void cloneChildren(TypeFunctionVariadicTypePack* v1, TypeFunctionVariadicTypePack* v2) { v2->type = shallowClone(v1->type); } + + void cloneChildren(TypeFunctionGenericTypePack* g1, TypeFunctionGenericTypePack* g2) + { + // noop. + } }; TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index a89784b9c..4aa9344f2 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -21,6 +21,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) namespace Luau { @@ -221,13 +222,22 @@ class TypeFunctionSerializer else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) { state->classesSerialized[c->name] = ty; target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name}); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + Name name = g->name; + + if (!g->explicitName) + name = format("g%d", g->index); + + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->explicitName, false, name}); + } else { std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -252,6 +262,15 @@ class TypeFunctionSerializer target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + Name name = gPack->name; + + if (!gPack->explicitName) + name = format("g%d", gPack->index); + + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->explicitName, name}); + } else { std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -289,6 +308,9 @@ class TypeFunctionSerializer serializeChildren(f1, f2); else if (auto [c1, c2] = std::tuple{get(ty), getMutable(tfti)}; c1 && c2) serializeChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{get(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + serializeChildren(g1, g2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -302,6 +324,9 @@ class TypeFunctionSerializer serializeChildren(tPack1, tPack2); else if (auto [vPack1, vPack2] = std::tuple{get(tp), getMutable(tftp)}; vPack1 && vPack2) serializeChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{get(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + serializeChildren(gPack1, gPack2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -391,6 +416,17 @@ class TypeFunctionSerializer void serializeChildren(const FunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowSerialize(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowSerialize(tp)); + } + f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); } @@ -420,6 +456,11 @@ class TypeFunctionSerializer c2->parent = shallowSerialize(*c1->parent); } + void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void serializeChildren(const TypePack* t1, TypeFunctionTypePack* t2) { for (const TypeId& ty : t1->head) @@ -433,6 +474,25 @@ class TypeFunctionSerializer { v2->type = shallowSerialize(v1->ty); } + + void serializeChildren(const GenericTypePack* v1, TypeFunctionGenericTypePack* v2) + { + // noop. + } +}; + +template +struct SerializedGeneric +{ + bool isNamed = false; + std::string name; + T type = nullptr; +}; + +struct SerializedFunctionScope +{ + size_t oldQueueSize = 0; + TypeFunctionFunctionType* function = nullptr; }; // Complete inverse of TypeFunctionSerializer @@ -453,6 +513,15 @@ class TypeFunctionDeserializer // second must be PrimitiveType; else there should be an error std::vector> queue; + // Generic types and packs currently in scope + // Generics are resolved by name even if runtime generic type pointers are different + // Multiple names mapping to the same generic can be in scope for nested generic functions + std::vector> genericTypes; + std::vector> genericPacks; + + // To track when generics go out of scope, we have a list of queue positions at which a specific function has introduced generics + std::vector functionScopes; + SeenTypes types; // Mapping of TypeFunctionTypeIds that have been shallow deserialized to TypeIds SeenTypePacks packs; // Mapping of TypeFunctionTypePackIds that have been shallow deserialized to TypePackIds @@ -520,6 +589,16 @@ class TypeFunctionDeserializer queue.pop_back(); deserializeChildren(tfti, ty); + + if (FFlag::LuauUserTypeFunGenerics) + { + // If we have completed working on all children of a function, remove the generic parameters from scope + if (!functionScopes.empty() && queue.size() == functionScopes.back().oldQueueSize && state->errors.empty()) + { + closeFunctionScope(functionScopes.back().function); + functionScopes.pop_back(); + } + } } } @@ -552,6 +631,21 @@ class TypeFunctionDeserializer } } + void closeFunctionScope(TypeFunctionFunctionType* f) + { + if (!f->generics.empty()) + { + LUAU_ASSERT(genericTypes.size() >= f->generics.size()); + genericTypes.erase(genericTypes.begin() + int(genericTypes.size() - f->generics.size()), genericTypes.end()); + } + + if (!f->genericPacks.empty()) + { + LUAU_ASSERT(genericPacks.size() >= f->genericPacks.size()); + genericPacks.erase(genericPacks.begin() + int(genericPacks.size() - f->genericPacks.size()), genericPacks.end()); + } + } + TypeId shallowDeserialize(TypeFunctionTypeId ty) { if (auto it = find(ty)) @@ -631,6 +725,33 @@ class TypeFunctionDeserializer else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized"); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + if (g->isPack) + { + state->errors.push_back(format("Generic type pack '%s...' cannot be placed in a type position", g->name.c_str())); + return nullptr; + } + else + { + auto it = std::find_if( + genericTypes.rbegin(), + genericTypes.rend(), + [&](const SerializedGeneric& el) + { + return g->isNamed == el.isNamed && g->name == el.name; + } + ); + + if (it == genericTypes.rend()) + { + state->errors.push_back(format("Generic type '%s' is not in a scope of the active generic function", g->name.c_str())); + return nullptr; + } + + target = it->type; + } + } else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); @@ -647,11 +768,36 @@ class TypeFunctionDeserializer // Create a shallow deserialization TypePackId target = {}; if (auto tPack = get(tp)) + { target = state->ctx->arena->addTypePack(TypePack{}); + } else if (auto vPack = get(tp)) + { target = state->ctx->arena->addTypePack(VariadicTypePack{}); + } + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + auto it = std::find_if( + genericPacks.rbegin(), + genericPacks.rend(), + [&](const SerializedGeneric& el) + { + return gPack->isNamed == el.isNamed && gPack->name == el.name; + } + ); + + if (it == genericPacks.rend()) + { + state->errors.push_back(format("Generic type pack '%s...' is not in a scope of the active generic function", gPack->name.c_str())); + return nullptr; + } + + target = it->type; + } else + { state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); + } packs[tp] = target; queue.emplace_back(tp, target); @@ -686,6 +832,9 @@ class TypeFunctionDeserializer deserializeChildren(f2, f1); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) deserializeChildren(c2, c1); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + deserializeChildren(g2, g1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -697,6 +846,9 @@ class TypeFunctionDeserializer else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) deserializeChildren(vPack2, vPack1); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + deserializeChildren(gPack2, gPack1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -780,6 +932,64 @@ class TypeFunctionDeserializer void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1) { + if (FFlag::LuauUserTypeFunGenerics) + { + functionScopes.push_back({queue.size(), f2}); + + std::set> genericNames; + + // Introduce generic function parameters into scope + for (auto ty : f2->generics) + { + auto gty = get(ty); + LUAU_ASSERT(gty && !gty->isPack); + + std::pair nameKey = std::make_pair(gty->isNamed, gty->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gty->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypeId mapping = state->ctx->arena->addTV(Type(gty->isNamed ? GenericType{state->ctx->scope.get(), gty->name} : GenericType{})); + genericTypes.push_back({gty->isNamed, gty->name, mapping}); + } + + for (auto tp : f2->genericPacks) + { + auto gtp = get(tp); + LUAU_ASSERT(gtp); + + std::pair nameKey = std::make_pair(gtp->isNamed, gtp->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gtp->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypePackId mapping = + state->ctx->arena->addTypePack(TypePackVar(gtp->isNamed ? GenericTypePack{state->ctx->scope.get(), gtp->name} : GenericTypePack{}) + ); + genericPacks.push_back({gtp->isNamed, gtp->name, mapping}); + } + + f1->generics.reserve(f2->generics.size()); + for (auto ty : f2->generics) + f1->generics.push_back(shallowDeserialize(ty)); + + f1->genericPacks.reserve(f2->genericPacks.size()); + for (auto tp : f2->genericPacks) + f1->genericPacks.push_back(shallowDeserialize(tp)); + } + if (f2->argTypes) f1->argTypes = shallowDeserialize(f2->argTypes); @@ -792,6 +1002,11 @@ class TypeFunctionDeserializer // noop. } + void deserializeChildren(TypeFunctionGenericType* g2, GenericType* g1) + { + // noop. + } + void deserializeChildren(TypeFunctionTypePack* t2, TypePack* t1) { for (TypeFunctionTypeId& ty : t2->head) @@ -805,6 +1020,11 @@ class TypeFunctionDeserializer { v1->ty = shallowDeserialize(v2->type); } + + void deserializeChildren(TypeFunctionGenericTypePack* v2, GenericTypePack* v1) + { + // noop. + } }; TypeFunctionTypeId serialize(TypeId ty, TypeFunctionRuntimeBuilderState* state) diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 475d19da8..ce98f58e6 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -116,7 +116,7 @@ class Parser AstStat* parseFor(); // funcname ::= Name {`.' Name} [`:' Name] - AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname); + AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname); // function funcname funcbody LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray& attributes = {nullptr, 0}); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index fcb746940..f618bc067 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAGVARIABLE(LuauFixFunctionNameStartPosition) namespace Luau { @@ -638,7 +639,7 @@ AstStat* Parser::parseFor() } // funcname ::= Name {`.' Name} [`:' Name] -AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debugname) +AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname) { if (lexer.current().type == Lexeme::Name) debugname = AstName(lexer.current().name); @@ -658,7 +659,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, '.'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, '.' + ); // note: while the parser isn't recursive here, we're generating recursive structures of unbounded depth incrementRecursionCounter("function name"); @@ -677,7 +680,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, ':'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, ':' + ); hasself = true; } diff --git a/CLI/Coverage.h b/CLI/include/Luau/Coverage.h similarity index 100% rename from CLI/Coverage.h rename to CLI/include/Luau/Coverage.h diff --git a/CLI/FileUtils.h b/CLI/include/Luau/FileUtils.h similarity index 100% rename from CLI/FileUtils.h rename to CLI/include/Luau/FileUtils.h diff --git a/CLI/Flags.h b/CLI/include/Luau/Flags.h similarity index 100% rename from CLI/Flags.h rename to CLI/include/Luau/Flags.h diff --git a/CLI/Profiler.h b/CLI/include/Luau/Profiler.h similarity index 100% rename from CLI/Profiler.h rename to CLI/include/Luau/Profiler.h diff --git a/CLI/Repl.h b/CLI/include/Luau/Repl.h similarity index 100% rename from CLI/Repl.h rename to CLI/include/Luau/Repl.h diff --git a/CLI/Require.h b/CLI/include/Luau/Require.h similarity index 100% rename from CLI/Require.h rename to CLI/include/Luau/Require.h diff --git a/CLI/Analyze.cpp b/CLI/src/Analyze.cpp similarity index 99% rename from CLI/Analyze.cpp rename to CLI/src/Analyze.cpp index bc78f7cb3..e10a2c2ed 100644 --- a/CLI/Analyze.cpp +++ b/CLI/src/Analyze.cpp @@ -7,9 +7,9 @@ #include "Luau/TypeAttach.h" #include "Luau/Transpiler.h" -#include "FileUtils.h" -#include "Flags.h" -#include "Require.h" +#include "Luau/FileUtils.h" +#include "Luau/Flags.h" +#include "Luau/Require.h" #include #include diff --git a/CLI/Ast.cpp b/CLI/src/Ast.cpp similarity index 98% rename from CLI/Ast.cpp rename to CLI/src/Ast.cpp index b5a922aaa..5341d8892 100644 --- a/CLI/Ast.cpp +++ b/CLI/src/Ast.cpp @@ -8,7 +8,7 @@ #include "Luau/ParseOptions.h" #include "Luau/ToString.h" -#include "FileUtils.h" +#include "Luau/FileUtils.h" static void displayHelp(const char* argv0) { diff --git a/CLI/Bytecode.cpp b/CLI/src/Bytecode.cpp similarity index 99% rename from CLI/Bytecode.cpp rename to CLI/src/Bytecode.cpp index 2da9570b2..dc8e48338 100644 --- a/CLI/Bytecode.cpp +++ b/CLI/src/Bytecode.cpp @@ -7,8 +7,8 @@ #include "Luau/BytecodeBuilder.h" #include "Luau/Parser.h" #include "Luau/BytecodeSummary.h" -#include "FileUtils.h" -#include "Flags.h" +#include "Luau/FileUtils.h" +#include "Luau/Flags.h" #include diff --git a/CLI/Compile.cpp b/CLI/src/Compile.cpp similarity index 99% rename from CLI/Compile.cpp rename to CLI/src/Compile.cpp index ed9d419bb..6f41b42d9 100644 --- a/CLI/Compile.cpp +++ b/CLI/src/Compile.cpp @@ -8,8 +8,8 @@ #include "Luau/Parser.h" #include "Luau/TimeTrace.h" -#include "FileUtils.h" -#include "Flags.h" +#include "Luau/FileUtils.h" +#include "Luau/Flags.h" #include diff --git a/CLI/Coverage.cpp b/CLI/src/Coverage.cpp similarity index 98% rename from CLI/Coverage.cpp rename to CLI/src/Coverage.cpp index a509ab89a..7330d492d 100644 --- a/CLI/Coverage.cpp +++ b/CLI/src/Coverage.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Coverage.h" +#include "Luau/Coverage.h" #include "lua.h" diff --git a/CLI/FileUtils.cpp b/CLI/src/FileUtils.cpp similarity index 99% rename from CLI/FileUtils.cpp rename to CLI/src/FileUtils.cpp index 414bec511..2207f6785 100644 --- a/CLI/FileUtils.cpp +++ b/CLI/src/FileUtils.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "FileUtils.h" +#include "Luau/FileUtils.h" #include "Luau/Common.h" diff --git a/CLI/Flags.cpp b/CLI/src/Flags.cpp similarity index 100% rename from CLI/Flags.cpp rename to CLI/src/Flags.cpp diff --git a/CLI/Profiler.cpp b/CLI/src/Profiler.cpp similarity index 100% rename from CLI/Profiler.cpp rename to CLI/src/Profiler.cpp diff --git a/CLI/Reduce.cpp b/CLI/src/Reduce.cpp similarity index 99% rename from CLI/Reduce.cpp rename to CLI/src/Reduce.cpp index 7f8c459c7..e66d80dc9 100644 --- a/CLI/Reduce.cpp +++ b/CLI/src/Reduce.cpp @@ -5,7 +5,7 @@ #include "Luau/Parser.h" #include "Luau/Transpiler.h" -#include "FileUtils.h" +#include "Luau/FileUtils.h" #include #include diff --git a/CLI/Repl.cpp b/CLI/src/Repl.cpp similarity index 99% rename from CLI/Repl.cpp rename to CLI/src/Repl.cpp index 3bda38f1d..2dec1d8c6 100644 --- a/CLI/Repl.cpp +++ b/CLI/src/Repl.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Repl.h" +#include "Luau/Repl.h" #include "Luau/Common.h" #include "lua.h" @@ -10,11 +10,11 @@ #include "Luau/Parser.h" #include "Luau/TimeTrace.h" -#include "Coverage.h" -#include "FileUtils.h" -#include "Flags.h" -#include "Profiler.h" -#include "Require.h" +#include "Luau/Coverage.h" +#include "Luau/FileUtils.h" +#include "Luau/Flags.h" +#include "Luau/Profiler.h" +#include "Luau/Require.h" #include "isocline.h" diff --git a/CLI/ReplEntry.cpp b/CLI/src/ReplEntry.cpp similarity index 89% rename from CLI/ReplEntry.cpp rename to CLI/src/ReplEntry.cpp index 8543e3f78..7e5f9e064 100644 --- a/CLI/ReplEntry.cpp +++ b/CLI/src/ReplEntry.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Repl.h" +#include "Luau/Repl.h" int main(int argc, char** argv) { diff --git a/CLI/Require.cpp b/CLI/src/Require.cpp similarity index 99% rename from CLI/Require.cpp rename to CLI/src/Require.cpp index 4c1c3ac63..94bdd5443 100644 --- a/CLI/Require.cpp +++ b/CLI/src/Require.cpp @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Require.h" +#include "Luau/Require.h" -#include "FileUtils.h" +#include "Luau/FileUtils.h" #include "Luau/Common.h" #include "Luau/Config.h" @@ -301,4 +301,4 @@ bool RequireResolver::parseConfigInDirectory(const std::string& directory) } return true; -} \ No newline at end of file +} diff --git a/CLI/Web.cpp b/CLI/src/Web.cpp similarity index 100% rename from CLI/Web.cpp rename to CLI/src/Web.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 51fa919ef..5286fd9f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,11 +68,12 @@ include(Sources.cmake) target_include_directories(Luau.Common INTERFACE Common/include) target_compile_features(Luau.CLI.lib PUBLIC cxx_std_17) -target_link_libraries(Luau.CLI.lib PRIVATE Luau.Common) +target_include_directories(Luau.CLI.lib PUBLIC CLI/include) +target_link_libraries(Luau.CLI.lib PRIVATE Luau.Common Luau.Config) target_compile_features(Luau.Ast PUBLIC cxx_std_17) target_include_directories(Luau.Ast PUBLIC Ast/include) -target_link_libraries(Luau.Ast PUBLIC Luau.Common Luau.CLI.lib) +target_link_libraries(Luau.Ast PUBLIC Luau.Common) target_compile_features(Luau.Compiler PUBLIC cxx_std_17) target_include_directories(Luau.Compiler PUBLIC Compiler/include) diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 30790ee5c..ca5fa7a9f 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -160,6 +160,7 @@ class AssemblyBuilderX64 void vmaxsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vcmpeqsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index dcc1de855..db1774d81 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 0cf9d9a55..2e689fe2e 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -1,7 +1,10 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include +#include "Luau/CodeGenCommon.h" +#include "Luau/CodeGenOptions.h" +#include "Luau/LoweringStats.h" + #include #include #include @@ -12,25 +15,11 @@ struct lua_State; -#if defined(__x86_64__) || defined(_M_X64) -#define CODEGEN_TARGET_X64 -#elif defined(__aarch64__) || defined(_M_ARM64) -#define CODEGEN_TARGET_A64 -#endif - namespace Luau { namespace CodeGen { -enum CodeGenFlags -{ - // Only run native codegen for modules that have been marked with --!native - CodeGen_OnlyNativeModules = 1 << 0, - // Run native codegen for functions that the compiler considers not profitable - CodeGen_ColdFunctions = 1 << 1, -}; - // These enum values can be reported through telemetry. // To ensure consistency, changes should be additive. enum class CodeGenCompilationResult @@ -72,106 +61,6 @@ struct CompilationResult } }; -struct IrBuilder; -struct IrOp; - -using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); -using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostVectorNamecallHandler = - bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); - -enum class HostMetamethod -{ - Add, - Sub, - Mul, - Div, - Idiv, - Mod, - Pow, - Minus, - Equal, - LessThan, - LessEqual, - Length, - Concat, -}; - -using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); -using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); -using HostUserdataAccessHandler = - bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostUserdataMetamethodHandler = - bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); -using HostUserdataNamecallHandler = bool (*)( - IrBuilder& builder, - uint8_t type, - const char* member, - size_t memberLength, - int argResReg, - int sourceReg, - int params, - int results, - int pcpos -); - -struct HostIrHooks -{ - // Suggest result type of a vector field access - HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; - - // Suggest result type of a vector function namecall - HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; - - // Handle vector value field access - // 'sourceReg' is guaranteed to be a vector - // Guards should take a VM exit to 'pcpos' - HostVectorAccessHandler vectorAccess = nullptr; - - // Handle namecall performed on a vector value - // 'sourceReg' (self argument) is guaranteed to be a vector - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostVectorNamecallHandler vectorNamecall = nullptr; - - // Suggest result type of a userdata field access - HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; - - // Suggest result type of a metamethod call - HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; - - // Suggest result type of a userdata namecall - HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; - - // Handle userdata value field access - // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked - // Write to 'resultReg' might invalidate 'sourceReg' - // Guards should take a VM exit to 'pcpos' - HostUserdataAccessHandler userdataAccess = nullptr; - - // Handle metamethod operation on a userdata value - // 'lhs' and 'rhs' operands can be VM registers of constants - // Operand types have to be checked and userdata operand tags have to be checked - // Write to 'resultReg' might invalidate source operands - // Guards should take a VM exit to 'pcpos' - HostUserdataMetamethodHandler userdataMetamethod = nullptr; - - // Handle namecall performed on a userdata value - // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostUserdataNamecallHandler userdataNamecall = nullptr; -}; - -struct CompilationOptions -{ - unsigned int flags = 0; - HostIrHooks hooks; - - // null-terminated array of userdata types names that might have custom lowering - const char* const* userdataTypes = nullptr; -}; - struct CompilationStats { size_t bytecodeSizeBytes = 0; @@ -184,8 +73,6 @@ struct CompilationStats uint32_t functionsBound = 0; }; -using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); - bool isSupported(); class SharedCodeGenContext; @@ -249,153 +136,6 @@ CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsig CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); -using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); - -// Output "#" before IR blocks and instructions -enum class IncludeIrPrefix -{ - No, - Yes -}; - -// Output user count and last use information of blocks and instructions -enum class IncludeUseInfo -{ - No, - Yes -}; - -// Output CFG informations like block predecessors, successors and etc -enum class IncludeCfgInfo -{ - No, - Yes -}; - -// Output VM register live in/out information for blocks -enum class IncludeRegFlowInfo -{ - No, - Yes -}; - -struct AssemblyOptions -{ - enum Target - { - Host, - A64, - A64_NoFeatures, - X64_Windows, - X64_SystemV, - }; - - Target target = Host; - - CompilationOptions compilationOptions; - - bool outputBinary = false; - - bool includeAssembly = false; - bool includeIr = false; - bool includeOutlinedCode = false; - bool includeIrTypes = false; - - IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; - IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; - IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; - IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; - - // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id - AnnotatorFn annotator = nullptr; - void* annotatorContext = nullptr; -}; - -struct BlockLinearizationStats -{ - unsigned int constPropInstructionCount = 0; - double timeSeconds = 0.0; - - BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) - { - this->constPropInstructionCount += that.constPropInstructionCount; - this->timeSeconds += that.timeSeconds; - - return *this; - } - - BlockLinearizationStats operator+(const BlockLinearizationStats& other) const - { - BlockLinearizationStats result(*this); - result += other; - return result; - } -}; - -enum FunctionStatsFlags -{ - // Enable stats collection per function - FunctionStats_Enable = 1 << 0, - // Compute function bytecode summary - FunctionStats_BytecodeSummary = 1 << 1, -}; - -struct FunctionStats -{ - std::string name; - int line = -1; - unsigned bcodeCount = 0; - unsigned irCount = 0; - unsigned asmCount = 0; - unsigned asmSize = 0; - std::vector> bytecodeSummary; -}; - -struct LoweringStats -{ - unsigned totalFunctions = 0; - unsigned skippedFunctions = 0; - int spillsToSlot = 0; - int spillsToRestore = 0; - unsigned maxSpillSlotsUsed = 0; - unsigned blocksPreOpt = 0; - unsigned blocksPostOpt = 0; - unsigned maxBlockInstructions = 0; - - int regAllocErrors = 0; - int loweringErrors = 0; - - BlockLinearizationStats blockLinearizationStats; - - unsigned functionStatsFlags = 0; - std::vector functions; - - LoweringStats operator+(const LoweringStats& other) const - { - LoweringStats result(*this); - result += other; - return result; - } - - LoweringStats& operator+=(const LoweringStats& that) - { - this->totalFunctions += that.totalFunctions; - this->skippedFunctions += that.skippedFunctions; - this->spillsToSlot += that.spillsToSlot; - this->spillsToRestore += that.spillsToRestore; - this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); - this->blocksPreOpt += that.blocksPreOpt; - this->blocksPostOpt += that.blocksPostOpt; - this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); - this->regAllocErrors += that.regAllocErrors; - this->loweringErrors += that.loweringErrors; - this->blockLinearizationStats += that.blockLinearizationStats; - if (this->functionStatsFlags & FunctionStats_Enable) - this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); - return *this; - } -}; - // Generates assembly for target function and all inner functions std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr); diff --git a/CodeGen/include/Luau/CodeGenCommon.h b/CodeGen/include/Luau/CodeGenCommon.h index 84090423f..a9d1761cc 100644 --- a/CodeGen/include/Luau/CodeGenCommon.h +++ b/CodeGen/include/Luau/CodeGenCommon.h @@ -10,3 +10,9 @@ #else #define CODEGEN_ASSERT(expr) (void)sizeof(!!(expr)) #endif + +#if defined(__x86_64__) || defined(_M_X64) +#define CODEGEN_TARGET_X64 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define CODEGEN_TARGET_A64 +#endif diff --git a/CodeGen/include/Luau/CodeGenOptions.h b/CodeGen/include/Luau/CodeGenOptions.h new file mode 100644 index 000000000..de95efa62 --- /dev/null +++ b/CodeGen/include/Luau/CodeGenOptions.h @@ -0,0 +1,188 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +enum CodeGenFlags +{ + // Only run native codegen for modules that have been marked with --!native + CodeGen_OnlyNativeModules = 1 << 0, + // Run native codegen for functions that the compiler considers not profitable + CodeGen_ColdFunctions = 1 << 1, +}; + +using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); + +struct IrBuilder; +struct IrOp; + +using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); +using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostVectorNamecallHandler = + bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); + +enum class HostMetamethod +{ + Add, + Sub, + Mul, + Div, + Idiv, + Mod, + Pow, + Minus, + Equal, + LessThan, + LessEqual, + Length, + Concat, +}; + +using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); +using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); +using HostUserdataAccessHandler = + bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostUserdataMetamethodHandler = + bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); +using HostUserdataNamecallHandler = bool (*)( + IrBuilder& builder, + uint8_t type, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +); + +struct HostIrHooks +{ + // Suggest result type of a vector field access + HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; + + // Suggest result type of a vector function namecall + HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; + + // Handle vector value field access + // 'sourceReg' is guaranteed to be a vector + // Guards should take a VM exit to 'pcpos' + HostVectorAccessHandler vectorAccess = nullptr; + + // Handle namecall performed on a vector value + // 'sourceReg' (self argument) is guaranteed to be a vector + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostVectorNamecallHandler vectorNamecall = nullptr; + + // Suggest result type of a userdata field access + HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; + + // Suggest result type of a metamethod call + HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; + + // Suggest result type of a userdata namecall + HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; + + // Handle userdata value field access + // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked + // Write to 'resultReg' might invalidate 'sourceReg' + // Guards should take a VM exit to 'pcpos' + HostUserdataAccessHandler userdataAccess = nullptr; + + // Handle metamethod operation on a userdata value + // 'lhs' and 'rhs' operands can be VM registers of constants + // Operand types have to be checked and userdata operand tags have to be checked + // Write to 'resultReg' might invalidate source operands + // Guards should take a VM exit to 'pcpos' + HostUserdataMetamethodHandler userdataMetamethod = nullptr; + + // Handle namecall performed on a userdata value + // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostUserdataNamecallHandler userdataNamecall = nullptr; +}; + +struct CompilationOptions +{ + unsigned int flags = 0; + HostIrHooks hooks; + + // null-terminated array of userdata types names that might have custom lowering + const char* const* userdataTypes = nullptr; +}; + + +using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); + +// Output "#" before IR blocks and instructions +enum class IncludeIrPrefix +{ + No, + Yes +}; + +// Output user count and last use information of blocks and instructions +enum class IncludeUseInfo +{ + No, + Yes +}; + +// Output CFG informations like block predecessors, successors and etc +enum class IncludeCfgInfo +{ + No, + Yes +}; + +// Output VM register live in/out information for blocks +enum class IncludeRegFlowInfo +{ + No, + Yes +}; + +struct AssemblyOptions +{ + enum Target + { + Host, + A64, + A64_NoFeatures, + X64_Windows, + X64_SystemV, + }; + + Target target = Host; + + CompilationOptions compilationOptions; + + bool outputBinary = false; + + bool includeAssembly = false; + bool includeIr = false; + bool includeOutlinedCode = false; + bool includeIrTypes = false; + + IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; + IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; + IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; + IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; + + // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id + AnnotatorFn annotator = nullptr; + void* annotatorContext = nullptr; +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 779fe0120..38519f951 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -20,6 +20,8 @@ namespace Luau namespace CodeGen { +struct LoweringStats; + // IR extensions to LuauBuiltinFunction enum (these only exist inside IR, and start from 256 to avoid collisions) enum { @@ -67,18 +69,18 @@ enum class IrCmd : uint8_t LOAD_ENV, // Get pointer (TValue) to table array at index - // A: pointer (Table) + // A: pointer (LuaTable) // B: int GET_ARR_ADDR, // Get pointer (LuaNode) to table node element at the active cached slot index - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (pcpos) // C: Kn GET_SLOT_NODE_ADDR, // Get pointer (LuaNode) to table node element at the main position of the specified key hash - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (hash) GET_HASH_NODE_ADDR, @@ -185,6 +187,11 @@ enum class IrCmd : uint8_t // A: double SIGN_NUM, + // Select B if C == D, otherwise select A + // A, B: double (endpoints) + // C, D: double (condition arguments) + SELECT_NUM, + // Add/Sub/Mul/Div/Idiv two vectors // A, B: TValue ADD_VEC, @@ -268,7 +275,7 @@ enum class IrCmd : uint8_t JUMP_SLOT_MATCH, // Get table length - // A: pointer (Table) + // A: pointer (LuaTable) TABLE_LEN, // Get string length @@ -281,11 +288,11 @@ enum class IrCmd : uint8_t NEW_TABLE, // Duplicate a table - // A: pointer (Table) + // A: pointer (LuaTable) DUP_TABLE, // Insert an integer key into a table and return the pointer to inserted value (TValue) - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (key) TABLE_SETNUM, @@ -425,13 +432,13 @@ enum class IrCmd : uint8_t CHECK_TRUTHY, // Guard against readonly table - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_READONLY, // Guard against table having a metatable - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_NO_METATABLE, @@ -442,7 +449,7 @@ enum class IrCmd : uint8_t CHECK_SAFE_ENV, // Guard against index overflowing the table array size - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (index) // C: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure @@ -498,11 +505,11 @@ enum class IrCmd : uint8_t BARRIER_OBJ, // Handle GC write barrier (backwards) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) BARRIER_TABLE_BACK, // Handle GC write barrier (forward) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) // B: Rn (TValue that was written to the object) // C: tag/undef (tag of the value that was written) BARRIER_TABLE_FORWARD, @@ -1044,6 +1051,8 @@ struct IrFunction CfgInfo cfg; + LoweringStats* stats = nullptr; + IrBlock& blockOp(IrOp op) { CODEGEN_ASSERT(op.kind == IrOpKind::Block); diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index c0737ae86..27a9feb49 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -2,7 +2,7 @@ #pragma once #include "Luau/IrData.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include #include diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 773b23a66..1afa1a34c 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -174,6 +174,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::SQRT_NUM: case IrCmd::ABS_NUM: case IrCmd::SIGN_NUM: + case IrCmd::SELECT_NUM: case IrCmd::ADD_VEC: case IrCmd::SUB_VEC: case IrCmd::MUL_VEC: diff --git a/CodeGen/include/Luau/LoweringStats.h b/CodeGen/include/Luau/LoweringStats.h new file mode 100644 index 000000000..532a5270c --- /dev/null +++ b/CodeGen/include/Luau/LoweringStats.h @@ -0,0 +1,103 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +struct BlockLinearizationStats +{ + unsigned int constPropInstructionCount = 0; + double timeSeconds = 0.0; + + BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) + { + this->constPropInstructionCount += that.constPropInstructionCount; + this->timeSeconds += that.timeSeconds; + + return *this; + } + + BlockLinearizationStats operator+(const BlockLinearizationStats& other) const + { + BlockLinearizationStats result(*this); + result += other; + return result; + } +}; + +enum FunctionStatsFlags +{ + // Enable stats collection per function + FunctionStats_Enable = 1 << 0, + // Compute function bytecode summary + FunctionStats_BytecodeSummary = 1 << 1, +}; + +struct FunctionStats +{ + std::string name; + int line = -1; + unsigned bcodeCount = 0; + unsigned irCount = 0; + unsigned asmCount = 0; + unsigned asmSize = 0; + std::vector> bytecodeSummary; +}; + +struct LoweringStats +{ + unsigned totalFunctions = 0; + unsigned skippedFunctions = 0; + int spillsToSlot = 0; + int spillsToRestore = 0; + unsigned maxSpillSlotsUsed = 0; + unsigned blocksPreOpt = 0; + unsigned blocksPostOpt = 0; + unsigned maxBlockInstructions = 0; + + int regAllocErrors = 0; + int loweringErrors = 0; + + BlockLinearizationStats blockLinearizationStats; + + unsigned functionStatsFlags = 0; + std::vector functions; + + LoweringStats operator+(const LoweringStats& other) const + { + LoweringStats result(*this); + result += other; + return result; + } + + LoweringStats& operator+=(const LoweringStats& that) + { + this->totalFunctions += that.totalFunctions; + this->skippedFunctions += that.skippedFunctions; + this->spillsToSlot += that.spillsToSlot; + this->spillsToRestore += that.spillsToRestore; + this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); + this->blocksPreOpt += that.blocksPreOpt; + this->blocksPostOpt += that.blocksPostOpt; + this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); + + this->regAllocErrors += that.regAllocErrors; + this->loweringErrors += that.loweringErrors; + + this->blockLinearizationStats += that.blockLinearizationStats; + + if (this->functionStatsFlags & FunctionStats_Enable) + this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); + + return *this; + } +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 803732e2c..1fb1b671f 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -927,6 +927,11 @@ void AssemblyBuilderX64::vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vminsd", dst, src1, src2, 0x5d, false, AVX_0F, AVX_F2); } +void AssemblyBuilderX64::vcmpeqsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vcmpeqsd", dst, src1, src2, 0x00, 0xc2, false, AVX_0F, AVX_F2); +} + void AssemblyBuilderX64::vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vcmpltsd", dst, src1, src2, 0x01, 0xc2, false, AVX_0F, AVX_F2); diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 134a49f21..b859b111e 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -2,7 +2,7 @@ #include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index cb2d693ac..137c554c3 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -2,6 +2,7 @@ #include "Luau/CodeBlockUnwind.h" #include "Luau/CodeAllocator.h" +#include "Luau/CodeGenCommon.h" #include "Luau/UnwindBuilder.h" #include diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index f22b23796..a518165fd 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -3,7 +3,7 @@ #include "CodeGenLower.h" -#include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/IrBuilder.h" @@ -44,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering) +LUAU_FASTFLAGVARIABLE(CodegenWiderLoweringStats) // Per-module IR instruction count limit LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index bffce5175..6bbdc4738 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -1,5 +1,4 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/CodeGen.h" #include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" #include "Luau/BytecodeSummary.h" diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 262d4a429..82dfa17e4 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -5,6 +5,7 @@ #include "CodeGenLower.h" #include "CodeGenX64.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index f41211cd2..406fe5c95 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -7,6 +7,7 @@ #include "Luau/IrBuilder.h" #include "Luau/IrDump.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeDeadStore.h" #include "Luau/OptimizeFinalX64.h" @@ -24,6 +25,7 @@ LUAU_FASTFLAG(DebugCodegenNoOpt) LUAU_FASTFLAG(DebugCodegenOptSize) LUAU_FASTFLAG(DebugCodegenSkipNumbering) +LUAU_FASTFLAG(CodegenWiderLoweringStats) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsBlockLimit) LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit) @@ -298,6 +300,9 @@ inline bool lowerFunction( CodeGenCompilationResult& codeGenCompilationResult ) { + if (FFlag::CodegenWiderLoweringStats) + ir.function.stats = stats; + killUnusedBlocks(ir.function); unsigned preOptBlockCount = 0; diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index 7244e6cce..26451eea0 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -63,7 +63,7 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; @@ -106,7 +106,7 @@ bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) return false; } -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; int sizenode = 1 << h->lsizenode; @@ -233,7 +233,7 @@ Udata* newUserdata(lua_State* L, size_t s, int tag) { Udata* u = luaU_newudata(L, s, tag); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); @@ -345,7 +345,7 @@ const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __index metamethod @@ -368,7 +368,7 @@ const Instruction* executeSETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __newindex metamethod @@ -394,7 +394,7 @@ const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -506,7 +506,7 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -591,7 +591,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -605,7 +605,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -662,7 +662,7 @@ const Instruction* executeSETLIST(lua_State* L, const Instruction* pc, StkId bas L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -697,7 +697,7 @@ const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { diff --git a/CodeGen/src/CodeGenUtils.h b/CodeGen/src/CodeGenUtils.h index 15d4c95d2..1003a6f3a 100644 --- a/CodeGen/src/CodeGenUtils.h +++ b/CodeGen/src/CodeGenUtils.h @@ -8,8 +8,8 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra); -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra); +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra); +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra); bool forgLoopNonTableFallback(lua_State* L, int insnA, int aux); void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 79562b883..36b5130e0 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -120,12 +120,12 @@ void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, Regist CODEGEN_ASSERT(tmp != node); CODEGEN_ASSERT(table != node); - build.mov(node, qword[table + offsetof(Table, node)]); + build.mov(node, qword[table + offsetof(LuaTable, node)]); // compute cached slot build.mov(tmp, sCode); build.movzx(dwordReg(tmp), byte[tmp + pcpos * sizeof(Instruction) + kOffsetOfInstructionC]); - build.and_(byteReg(tmp), byte[table + offsetof(Table, nodemask8)]); + build.and_(byteReg(tmp), byte[table + offsetof(LuaTable, nodemask8)]); // LuaNode* n = &h->node[slot]; build.shl(dwordReg(tmp), kLuaNodeSizeLog2); @@ -282,7 +282,7 @@ void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, Regist IrCallWrapperX64 callWrap(regs, build); callWrap.addArgument(SizeX64::qword, rState); callWrap.addArgument(SizeX64::qword, table, tableOp); - callWrap.addArgument(SizeX64::qword, addr[table + offsetof(Table, gclist)]); + callWrap.addArgument(SizeX64::qword, addr[table + offsetof(LuaTable, gclist)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]); } diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index ae3d1308b..207f7f566 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -292,7 +292,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int Label skipResize; // Resize if h->sizearray < last - build.cmp(dword[table + offsetof(Table, sizearray)], last); + build.cmp(dword[table + offsetof(LuaTable, sizearray)], last); build.jcc(ConditionX64::NotBelow, skipResize); // Argument setup reordered to avoid conflicts @@ -309,7 +309,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int RegisterX64 arrayDst = rdx; RegisterX64 offset = rcx; - build.mov(arrayDst, qword[table + offsetof(Table, array)]); + build.mov(arrayDst, qword[table + offsetof(LuaTable, array)]); const int kUnrollSetListLimit = 4; @@ -380,7 +380,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // &array[index] build.mov(dwordReg(elemPtr), dwordReg(index)); build.shl(dwordReg(elemPtr), kTValueSizeLog2); - build.add(elemPtr, qword[table + offsetof(Table, array)]); + build.add(elemPtr, qword[table + offsetof(LuaTable, array)]); // Clear extra variables since we might have more than two for (int i = 2; i < aux; ++i) @@ -391,7 +391,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // First we advance index through the array portion // while (unsigned(index) < unsigned(sizearray)) Label arrayLoop = build.setLabel(); - build.cmp(dwordReg(index), dword[table + offsetof(Table, sizearray)]); + build.cmp(dwordReg(index), dword[table + offsetof(LuaTable, sizearray)]); build.jcc(ConditionX64::NotBelow, skipArray); // If element is nil, we increment the index; if it's not, we still need 'index + 1' inside diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index fe6a2397c..dcc9d879e 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -169,6 +169,8 @@ const char* getCmdName(IrCmd cmd) return "ABS_NUM"; case IrCmd::SIGN_NUM: return "SIGN_NUM"; + case IrCmd::SELECT_NUM: + return "SELECT_NUM"; case IrCmd::ADD_VEC: return "ADD_VEC"; case IrCmd::SUB_VEC: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index c7fcac271..2ec724457 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonA64.h" #include "NativeState.h" @@ -13,6 +14,7 @@ LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauCodeGenVectorDeadStoreElim) +LUAU_FASTFLAG(LuauCodeGenLerp) namespace Luau { @@ -329,7 +331,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::GET_ARR_ADDR: { inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a}); - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, array))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, array))); if (inst.b.kind == IrOpKind::Inst) { @@ -375,11 +377,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // C field can be shifted as long as it's at the most significant byte of the instruction word CODEGEN_ASSERT(kOffsetOfInstructionC == 3); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, nodemask8))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, nodemask8))); build.and_(temp2, temp2, temp1w, -24); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -392,13 +394,13 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // hash & ((1 << lsizenode) - 1) == hash & ~(-1 << lsizenode) build.mov(temp1, -1); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, lsizenode))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, lsizenode))); build.lsl(temp1, temp1, temp2); build.mov(temp2, uintOp(inst.b)); build.bic(temp2, temp2, temp1); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -703,6 +705,20 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fcsel(inst.regA64, temp1, inst.regA64, getConditionFP(IrCondition::Less)); break; } + case IrCmd::SELECT_NUM: + { + LUAU_ASSERT(FFlag::LuauCodeGenLerp); + inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b, inst.c, inst.d}); + + RegisterA64 temp1 = tempDouble(inst.a); + RegisterA64 temp2 = tempDouble(inst.b); + RegisterA64 temp3 = tempDouble(inst.c); + RegisterA64 temp4 = tempDouble(inst.d); + + build.fcmp(temp3, temp4); + build.fcsel(inst.regA64, temp2, temp1, getConditionFP(IrCondition::Equal)); + break; + } case IrCmd::ADD_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); @@ -1060,10 +1076,10 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp2 = regs.allocTemp(KindA64::w); - build.ldr(temp1, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp1, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbz(temp1, labelOp(inst.c)); // no metatable - build.ldrb(temp2, mem(temp1, offsetof(Table, tmcache))); + build.ldrb(temp2, mem(temp1, offsetof(LuaTable, tmcache))); build.tst(temp2, 1 << intOp(inst.b)); // can't use tbz/tbnz because their jump offsets are too short build.b(ConditionA64::NotEqual, labelOp(inst.c)); // Equal = Zero after tst; tmcache caches *absence* of metamethods @@ -1500,7 +1516,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldrb(temp, mem(regOp(inst.a), offsetof(Table, readonly))); + build.ldrb(temp, mem(regOp(inst.a), offsetof(LuaTable, readonly))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1509,7 +1525,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::x); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1520,7 +1536,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); RegisterA64 tempw = castReg(KindA64::w, temp); build.ldr(temp, mem(rClosure, offsetof(Closure, env))); - build.ldrb(tempw, mem(temp, offsetof(Table, safeenv))); + build.ldrb(tempw, mem(temp, offsetof(LuaTable, safeenv))); build.cbz(tempw, getTargetLabel(inst.a, fresh)); finalizeTargetLabel(inst.a, fresh); break; @@ -1531,7 +1547,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label& fail = getTargetLabel(inst.c, fresh); RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, sizearray))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, sizearray))); if (inst.b.kind == IrOpKind::Inst) { @@ -1758,7 +1774,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) size_t spills = regs.spill(build, index, {reg}); build.mov(x1, reg); build.mov(x0, rState); - build.add(x2, x1, uint16_t(offsetof(Table, gclist))); + build.add(x2, x1, uint16_t(offsetof(LuaTable, gclist))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierback))); build.blr(x3); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 814c6d8cf..e0ece9daf 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/IrCallWrapperX64.h" @@ -17,6 +18,7 @@ LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauCodeGenVectorDeadStoreElim) +LUAU_FASTFLAG(LuauCodeGenLerp) namespace Luau { @@ -158,13 +160,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(dwordReg(inst.regX64), regOp(inst.b)); build.shl(dwordReg(inst.regX64), kTValueSizeLog2); - build.add(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.add(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); } else if (inst.b.kind == IrOpKind::Constant) { inst.regX64 = regs.allocRegOrReuse(SizeX64::qword, index, {inst.a}); - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); if (intOp(inst.b) != 0) build.lea(inst.regX64, addr[inst.regX64 + intOp(inst.b) * sizeof(TValue)]); @@ -192,9 +194,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, node)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, node)]); build.mov(dwordReg(tmp.reg), 1); - build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(Table, lsizenode)]); + build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(LuaTable, lsizenode)]); build.shl(dwordReg(tmp.reg), byteReg(shiftTmp.reg)); build.dec(dwordReg(tmp.reg)); build.and_(dwordReg(tmp.reg), uintOp(inst.b)); @@ -622,6 +624,30 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vblendvpd(inst.regX64, tmp1.reg, build.f64x2(1, 1), inst.regX64); break; } + case IrCmd::SELECT_NUM: + { + LUAU_ASSERT(FFlag::LuauCodeGenLerp); + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.c, inst.d}); // can't reuse b if a is a memory operand + + ScopedRegX64 tmp{regs, SizeX64::xmmword}; + + if (inst.c.kind == IrOpKind::Inst) + build.vcmpeqsd(tmp.reg, regOp(inst.c), memRegDoubleOp(inst.d)); + else + { + build.vmovsd(tmp.reg, memRegDoubleOp(inst.c)); + build.vcmpeqsd(tmp.reg, tmp.reg, memRegDoubleOp(inst.d)); + } + + if (inst.a.kind == IrOpKind::Inst) + build.vblendvpd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b), tmp.reg); + else + { + build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); + build.vblendvpd(inst.regX64, inst.regX64, memRegDoubleOp(inst.b), tmp.reg); + } + break; + } case IrCmd::ADD_VEC: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); @@ -929,13 +955,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(Table, metatable)]); + build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(LuaTable, metatable)]); regs.freeLastUseReg(function.instOp(inst.a), index); // Release before the call if it's the last use build.test(tmp.reg, tmp.reg); build.jcc(ConditionX64::Zero, labelOp(inst.c)); // No metatable - build.test(byte[tmp.reg + offsetof(Table, tmcache)], 1 << intOp(inst.b)); + build.test(byte[tmp.reg + offsetof(LuaTable, tmcache)], 1 << intOp(inst.b)); build.jcc(ConditionX64::NotZero, labelOp(inst.c)); // No tag method ScopedRegX64 tmp2{regs, SizeX64::qword}; @@ -1295,11 +1321,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; } case IrCmd::CHECK_READONLY: - build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); + build.cmp(byte[regOp(inst.a) + offsetof(LuaTable, readonly)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_NO_METATABLE: - build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0); + build.cmp(qword[regOp(inst.a) + offsetof(LuaTable, metatable)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_SAFE_ENV: @@ -1308,16 +1334,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(tmp.reg, sClosure); build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); - build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); + build.cmp(byte[tmp.reg + offsetof(LuaTable, safeenv)], 0); jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next); break; } case IrCmd::CHECK_ARRAY_SIZE: if (inst.b.kind == IrOpKind::Inst) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], regOp(inst.b)); else if (inst.b.kind == IrOpKind::Constant) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], intOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], intOp(inst.b)); else CODEGEN_ASSERT(!"Unsupported instruction form"); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index bd2147a79..15a306c9c 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -2,8 +2,8 @@ #include "IrRegAllocA64.h" #include "Luau/AssemblyBuilderA64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "BitUtils.h" #include "EmitCommonA64.h" diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index d647484b2..646258685 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrRegAllocX64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonX64.h" diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index ebded5227..a5fa3ad0e 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -15,6 +15,7 @@ static const int kBit32BinaryOpUnrolledParams = 5; LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeCodegen); LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot); +LUAU_FASTFLAGVARIABLE(LuauCodeGenLerp); namespace Luau { @@ -284,6 +285,42 @@ static BuiltinImplResult translateBuiltinMathClamp( return {BuiltinImplType::UsesFallback, 1}; } +static BuiltinImplResult translateBuiltinMathLerp( + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + IrOp fallback, + int pcpos +) +{ + LUAU_ASSERT(FFlag::LuauCodeGenLerp); + + if (nparams < 3 || nresults > 1) + return {BuiltinImplType::None, -1}; + + builtinCheckDouble(build, build.vmReg(arg), pcpos); + builtinCheckDouble(build, args, pcpos); + builtinCheckDouble(build, arg3, pcpos); + + IrOp a = builtinLoadDouble(build, build.vmReg(arg)); + IrOp b = builtinLoadDouble(build, args); + IrOp t = builtinLoadDouble(build, arg3); + + IrOp l = build.inst(IrCmd::ADD_NUM, a, build.inst(IrCmd::MUL_NUM, build.inst(IrCmd::SUB_NUM, b, a), t)); + IrOp r = build.inst(IrCmd::SELECT_NUM, l, b, t, build.constDouble(1.0)); // select on t==1.0 + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), r); + + if (ra != arg) + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + + return {BuiltinImplType::Full, 1}; +} + static BuiltinImplResult translateBuiltinMathUnary(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, int nresults, int pcpos) { if (nparams < 1 || nresults > 1) @@ -1387,6 +1424,8 @@ BuiltinImplResult translateBuiltin( case LBF_VECTOR_MAX: return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap2(build, IrCmd::MAX_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) : noneResult; + case LBF_MATH_LERP: + return FFlag::LuauCodeGenLerp ? translateBuiltinMathLerp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos) : noneResult; default: return {BuiltinImplType::None, -1}; } diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 628297669..d15d57e2e 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -3,7 +3,7 @@ #include "Luau/Bytecode.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 5f3848071..00f274e7e 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrUtils.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "BitUtils.h" @@ -13,6 +14,7 @@ #include LUAU_FASTFLAG(LuauVectorLibNativeDot); +LUAU_FASTFLAG(LuauCodeGenLerp); namespace Luau { @@ -70,6 +72,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::SQRT_NUM: case IrCmd::ABS_NUM: case IrCmd::SIGN_NUM: + case IrCmd::SELECT_NUM: return IrValueKind::Double; case IrCmd::ADD_VEC: case IrCmd::SUB_VEC: @@ -656,6 +659,16 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 substitute(function, inst, build.constDouble(v > 0.0 ? 1.0 : v < 0.0 ? -1.0 : 0.0)); } break; + case IrCmd::SELECT_NUM: + LUAU_ASSERT(FFlag::LuauCodeGenLerp); + if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) + { + double c = function.doubleOp(inst.c); + double d = function.doubleOp(inst.d); + + substitute(function, inst, c == d ? inst.b : inst.a); + } + break; case IrCmd::NOT_ANY: if (inst.a.kind == IrOpKind::Constant) { diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 941db2525..b4f74132a 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,25 +44,25 @@ struct NativeContext void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; - void (*luaV_getimport)(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; + void (*luaV_getimport)(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; void (*luaV_concat)(lua_State* L, int total, int last) = nullptr; - int (*luaH_getn)(Table* t) = nullptr; - Table* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; - Table* (*luaH_clone)(lua_State* L, Table* tt) = nullptr; - void (*luaH_resizearray)(lua_State* L, Table* t, int nasize) = nullptr; - TValue* (*luaH_setnum)(lua_State* L, Table* t, int key); + int (*luaH_getn)(LuaTable* t) = nullptr; + LuaTable* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; + LuaTable* (*luaH_clone)(lua_State* L, LuaTable* tt) = nullptr; + void (*luaH_resizearray)(lua_State* L, LuaTable* t, int nasize) = nullptr; + TValue* (*luaH_setnum)(lua_State* L, LuaTable* t, int key); - void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr; + void (*luaC_barriertable)(lua_State* L, LuaTable* t, GCObject* v) = nullptr; void (*luaC_barrierf)(lua_State* L, GCObject* o, GCObject* v) = nullptr; void (*luaC_barrierback)(lua_State* L, GCObject* o, GCObject** gclist) = nullptr; size_t (*luaC_step)(lua_State* L, bool assist) = nullptr; void (*luaF_close)(lua_State* L, StkId level) = nullptr; UpVal* (*luaF_findupval)(lua_State* L, StkId level) = nullptr; - Closure* (*luaF_newLclosure)(lua_State* L, int nelems, Table* e, Proto* p) = nullptr; + Closure* (*luaF_newLclosure)(lua_State* L, int nelems, LuaTable* e, Proto* p) = nullptr; - const TValue* (*luaT_gettm)(Table* events, TMS event, TString* ename) = nullptr; + const TValue* (*luaT_gettm)(LuaTable* events, TMS event, TString* ename) = nullptr; const TString* (*luaT_objtypenamestr)(lua_State* L, const TValue* o) = nullptr; double (*libm_exp)(double) = nullptr; @@ -87,8 +87,8 @@ struct NativeContext double (*libm_modf)(double, double*) = nullptr; // Helper functions - bool (*forgLoopTableIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; - bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; + bool (*forgLoopTableIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; + bool (*forgLoopNodeIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; bool (*forgLoopNonTableFallback)(lua_State* L, int insnA, int aux) = nullptr; void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr; Closure* (*callProlog)(lua_State* L, TValue* ra, StkId argtop, int nresults) = nullptr; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index ca96a8211..ce44f5d10 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -1381,6 +1382,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SQRT_NUM: case IrCmd::ABS_NUM: case IrCmd::SIGN_NUM: + case IrCmd::SELECT_NUM: case IrCmd::NOT_ANY: state.substituteOrRecord(inst, index); break; diff --git a/Makefile b/Makefile index 6fb0b8f62..2ad0fc00b 100644 --- a/Makefile +++ b/Makefile @@ -42,23 +42,23 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o) ISOCLINE_TARGET=$(BUILD)/libisocline.a -TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/Require.cpp +TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/Require.cpp TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o) TESTS_TARGET=$(BUILD)/luau-tests -REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp CLI/Require.cpp +REPL_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/Require.cpp REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o) REPL_CLI_TARGET=$(BUILD)/luau -ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Require.cpp CLI/Analyze.cpp +ANALYZE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Require.cpp CLI/src/Analyze.cpp ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o) ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze -COMPILE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Compile.cpp +COMPILE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Compile.cpp COMPILE_CLI_OBJECTS=$(COMPILE_CLI_SOURCES:%=$(BUILD)/%.o) COMPILE_CLI_TARGET=$(BUILD)/luau-compile -BYTECODE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Bytecode.cpp +BYTECODE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Bytecode.cpp BYTECODE_CLI_OBJECTS=$(BYTECODE_CLI_SOURCES:%=$(BUILD)/%.o) BYTECODE_CLI_TARGET=$(BUILD)/luau-bytecode @@ -149,11 +149,11 @@ $(EQSAT_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IEqSat/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals $(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include -$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -IEqSat/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY -$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include -$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -Iextern -$(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -$(BYTECODE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include +$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -IEqSat/include -ICodeGen/include -IVM/include -ICLI/include -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY +$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include -ICLI/include +$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -Iextern -ICLI/include +$(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -ICLI/include +$(BYTECODE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -ICLI/include $(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IEqSat/include -IVM/include -ICodeGen/include -IConfig/include $(TESTS_TARGET): LDFLAGS+=-lpthread diff --git a/Sources.cmake b/Sources.cmake index 1adbe862e..306d15300 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -78,6 +78,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/CodeBlockUnwind.h CodeGen/include/Luau/CodeGen.h CodeGen/include/Luau/CodeGenCommon.h + CodeGen/include/Luau/CodeGenOptions.h CodeGen/include/Luau/ConditionA64.h CodeGen/include/Luau/ConditionX64.h CodeGen/include/Luau/IrAnalysis.h @@ -89,6 +90,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/IrUtils.h CodeGen/include/Luau/IrVisitUseDef.h CodeGen/include/Luau/Label.h + CodeGen/include/Luau/LoweringStats.h CodeGen/include/Luau/NativeProtoExecData.h CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OptimizeConstProp.h @@ -389,36 +391,39 @@ target_sources(isocline PRIVATE # Common sources shared between all CLI apps target_sources(Luau.CLI.lib PRIVATE - CLI/FileUtils.cpp - CLI/Flags.cpp - CLI/Flags.h - CLI/FileUtils.h + CLI/include/Luau/FileUtils.h + CLI/include/Luau/Flags.h + CLI/include/Luau/Require.h + + CLI/src/FileUtils.cpp + CLI/src/Flags.cpp + CLI/src/Require.cpp ) if(TARGET Luau.Repl.CLI) # Luau.Repl.CLI Sources target_sources(Luau.Repl.CLI PRIVATE - CLI/Coverage.h - CLI/Coverage.cpp - CLI/Profiler.h - CLI/Profiler.cpp - CLI/Repl.cpp - CLI/ReplEntry.cpp - CLI/Require.cpp) + CLI/include/Luau/Coverage.h + CLI/include/Luau/Profiler.h + + CLI/src/Coverage.cpp + CLI/src/Profiler.cpp + CLI/src/Repl.cpp + CLI/src/ReplEntry.cpp + ) endif() if(TARGET Luau.Analyze.CLI) # Luau.Analyze.CLI Sources target_sources(Luau.Analyze.CLI PRIVATE - CLI/Analyze.cpp - CLI/Require.cpp + CLI/src/Analyze.cpp ) endif() if(TARGET Luau.Ast.CLI) # Luau.Ast.CLI Sources target_sources(Luau.Ast.CLI PRIVATE - CLI/Ast.cpp + CLI/src/Ast.cpp ) endif() @@ -543,12 +548,12 @@ endif() if(TARGET Luau.CLI.Test) # Luau.CLI.Test Sources target_sources(Luau.CLI.Test PRIVATE - CLI/Coverage.h - CLI/Coverage.cpp - CLI/Profiler.h - CLI/Profiler.cpp - CLI/Repl.cpp - CLI/Require.cpp + CLI/include/Luau/Coverage.h + CLI/include/Luau/Profiler.h + + CLI/src/Coverage.cpp + CLI/src/Profiler.cpp + CLI/src/Repl.cpp tests/RegisterCallbacks.h tests/RegisterCallbacks.cpp @@ -560,24 +565,24 @@ endif() if(TARGET Luau.Web) # Luau.Web Sources target_sources(Luau.Web PRIVATE - CLI/Web.cpp) + CLI/src/Web.cpp) endif() if(TARGET Luau.Reduce.CLI) # Luau.Reduce.CLI Sources target_sources(Luau.Reduce.CLI PRIVATE - CLI/Reduce.cpp + CLI/src/Reduce.cpp ) endif() if(TARGET Luau.Compile.CLI) # Luau.Compile.CLI Sources target_sources(Luau.Compile.CLI PRIVATE - CLI/Compile.cpp) + CLI/src/Compile.cpp) endif() if(TARGET Luau.Bytecode.CLI) # Luau.Bytecode.CLI Sources target_sources(Luau.Bytecode.CLI PRIVATE - CLI/Bytecode.cpp) + CLI/src/Bytecode.cpp) endif() diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 052d8c82d..1a8af74d8 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -64,7 +64,7 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2024 Roblox Corporation $\n" ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \ } -static Table* getcurrenv(lua_State* L) +static LuaTable* getcurrenv(lua_State* L) { if (L->ci == L->base_ci) // no enclosing function? return L->gt; // use global table as environment @@ -762,7 +762,7 @@ void lua_setreadonly(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); api_check(L, t != hvalue(registry(L))); t->readonly = bool(enabled); } @@ -771,7 +771,7 @@ int lua_getreadonly(lua_State* L, int objindex) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); int res = t->readonly; return res; } @@ -780,14 +780,14 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); t->safeenv = bool(enabled); } int lua_getmetatable(lua_State* L, int objindex) { luaC_threadbarrier(L); - Table* mt = NULL; + LuaTable* mt = NULL; const TValue* obj = index2addr(L, objindex); switch (ttype(obj)) { @@ -894,7 +894,7 @@ int lua_setmetatable(lua_State* L, int objindex) api_checknelems(L, 1); TValue* obj = index2addr(L, objindex); api_checkvalidindex(L, obj); - Table* mt = NULL; + LuaTable* mt = NULL; if (!ttisnil(L->top - 1)) { api_check(L, ttistable(L->top - 1)); @@ -1214,7 +1214,7 @@ int lua_rawiter(lua_State* L, int idx, int iter) api_check(L, ttistable(t)); api_check(L, iter >= 0); - Table* h = hvalue(t); + LuaTable* h = hvalue(t); int sizearray = h->sizearray; // first we advance iter through the array portion @@ -1293,7 +1293,7 @@ void* lua_newuserdatataggedwithmetatable(lua_State* L, size_t sz, int tag) // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); - Table* h = L->global->udatamt[tag]; + LuaTable* h = L->global->udatamt[tag]; api_check(L, h != nullptr); u->metatable = h; @@ -1394,7 +1394,7 @@ int lua_ref(lua_State* L, int idx) StkId p = index2addr(L, idx); if (!ttisnil(p)) { - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); if (g->registryfree != 0) { // reuse existing slot @@ -1421,7 +1421,7 @@ void lua_unref(lua_State* L, int ref) return; global_State* g = L->global; - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); TValue* slot = luaH_setnum(L, reg, ref); setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable g->registryfree = ref; @@ -1462,7 +1462,7 @@ void lua_getuserdatametatable(lua_State* L, int tag) api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); luaC_threadbarrier(L); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { sethvalue(L, L->top, h); } @@ -1510,7 +1510,7 @@ void lua_cleartable(lua_State* L, int idx) { StkId t = index2addr(L, idx); api_check(L, ttistable(t)); - Table* tt = hvalue(t); + LuaTable* tt = hvalue(t); if (tt->readonly) luaG_readonlyerror(L); luaH_clear(tt); diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 10aa2534d..643d3a9c0 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -10,7 +10,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods) +LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods2) // 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 @@ -262,7 +262,7 @@ static int buffer_readbits(lua_State* L) if (unsigned(bitcount) > 32) luaL_error(L, "bit count is out of range of [0; 32]"); - if (uint64_t(bitoffset + bitcount) > len * 8) + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) luaL_error(L, "buffer access out of bounds"); unsigned startbyte = unsigned(bitoffset / 8); @@ -292,7 +292,7 @@ static int buffer_writebits(lua_State* L) if (unsigned(bitcount) > 32) luaL_error(L, "bit count is out of range of [0; 32]"); - if (uint64_t(bitoffset + bitcount) > len * 8) + if (uint64_t(bitoffset + bitcount) > uint64_t(len) * 8) luaL_error(L, "buffer access out of bounds"); unsigned startbyte = unsigned(bitoffset / 8); @@ -370,7 +370,7 @@ static const luaL_Reg bufferlib[] = { int luaopen_buffer(lua_State* L) { - luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED); + luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods2 ? bufferlib : bufferlib_DEPRECATED); return 1; } diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 6d71836e7..0c2582fe1 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -998,7 +998,7 @@ static int luauF_rawset(lua_State* L, StkId res, TValue* arg0, int nresults, Stk else if (ttisvector(key) && luai_vecisnan(vvalue(key))) return -1; - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1015,7 +1015,7 @@ static int luauF_tinsert(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams == 2 && nresults <= 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1032,7 +1032,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams >= 1 && nresults < 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); int n = -1; if (nparams == 1) @@ -1160,7 +1160,7 @@ static int luauF_rawlen(lua_State* L, StkId res, TValue* arg0, int nresults, Stk { if (ttistable(arg0)) { - Table* h = hvalue(arg0); + LuaTable* h = hvalue(arg0); setnvalue(res, double(luaH_getn(h))); return 1; } @@ -1204,7 +1204,7 @@ static int luauF_getmetatable(lua_State* L, StkId res, TValue* arg0, int nresult { if (nparams >= 1 && nresults <= 1) { - Table* mt = NULL; + LuaTable* mt = NULL; if (ttistable(arg0)) mt = hvalue(arg0)->metatable; else if (ttisuserdata(arg0)) @@ -1239,11 +1239,11 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult // note: setmetatable(_, nil) is rare so we use fallback for it to optimize the fast path if (nparams >= 2 && nresults <= 1 && ttistable(arg0) && ttistable(args)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly || t->metatable != NULL) return -1; // note: overwriting non-null metatable is very rare but it requires __metatable check - Table* mt = hvalue(args); + LuaTable* mt = hvalue(args); t->metatable = mt; luaC_objbarrier(L, t, mt); diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 2a1e45c49..b172d0ad6 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -55,7 +55,7 @@ Proto* luaF_newproto(lua_State* L) return f; } -Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) +Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p) { Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); @@ -70,7 +70,7 @@ Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) return c; } -Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e) +Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e) { Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); diff --git a/VM/src/lfunc.h b/VM/src/lfunc.h index 679836e7e..453cf581b 100644 --- a/VM/src/lfunc.h +++ b/VM/src/lfunc.h @@ -8,8 +8,8 @@ #define sizeLclosure(n) (offsetof(Closure, l.uprefs) + sizeof(TValue) * (n)) LUAI_FUNC Proto* luaF_newproto(lua_State* L); -LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p); -LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e); +LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p); +LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e); LUAI_FUNC UpVal* luaF_findupval(lua_State* L, StkId level); LUAI_FUNC void luaF_close(lua_State* L, StkId level); LUAI_FUNC void luaF_closeupval(lua_State* L, UpVal* uv, bool dead); diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index d9843ddc6..c5e16e43e 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -244,7 +244,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } case LUA_TUSERDATA: { - Table* mt = gco2u(o)->metatable; + LuaTable* mt = gco2u(o)->metatable; gray2black(o); // udata are never gray if (mt) markobject(g, mt); @@ -292,7 +292,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } } -static const char* gettablemode(global_State* g, Table* h) +static const char* gettablemode(global_State* g, LuaTable* h) { const TValue* mode = gfasttm(g, h->metatable, TM_MODE); @@ -302,13 +302,13 @@ static const char* gettablemode(global_State* g, Table* h) return NULL; } -static int traversetable(global_State* g, Table* h) +static int traversetable(global_State* g, LuaTable* h) { int i; int weakkey = 0; int weakvalue = 0; if (h->metatable) - markobject(g, cast_to(Table*, h->metatable)); + markobject(g, cast_to(LuaTable*, h->metatable)); // is there a weak mode? if (const char* modev = gettablemode(g, h)) @@ -459,11 +459,11 @@ static size_t propagatemark(global_State* g) { case LUA_TTABLE: { - Table* h = gco2h(o); + LuaTable* h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) // table is weak? black2gray(o); // keep it gray - return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + return sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); } case LUA_TFUNCTION: { @@ -553,8 +553,8 @@ static size_t cleartable(lua_State* L, GCObject* l) size_t work = 0; while (l) { - Table* h = gco2h(l); - work += sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + LuaTable* h = gco2h(l); + work += sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); int i = h->sizearray; while (i--) @@ -1155,7 +1155,7 @@ void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v) makewhite(g, o); // mark as white just to avoid other barriers } -void luaC_barriertable(lua_State* L, Table* t, GCObject* v) +void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v) { global_State* g = L->global; GCObject* o = obj2gco(t); diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 722de9d19..683542b66 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -131,7 +131,7 @@ LUAI_FUNC void luaC_fullgc(lua_State* L); LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt); LUAI_FUNC void luaC_upvalclosed(lua_State* L, UpVal* uv); LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v); -LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v); +LUAI_FUNC void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v); LUAI_FUNC void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist); LUAI_FUNC void luaC_validate(lua_State* L); LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 768561cbf..7a47ab86c 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -34,7 +34,7 @@ static void validateref(global_State* g, GCObject* f, TValue* v) } } -static void validatetable(global_State* g, Table* h) +static void validatetable(global_State* g, LuaTable* h) { int sizenode = 1 << h->lsizenode; @@ -290,9 +290,9 @@ static void dumpstring(FILE* f, TString* ts) fprintf(f, "\"}"); } -static void dumptable(FILE* f, Table* h) +static void dumptable(FILE* f, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size)); @@ -654,9 +654,9 @@ static void enumstring(EnumContext* ctx, TString* ts) enumnode(ctx, obj2gco(ts), ts->len, NULL); } -static void enumtable(EnumContext* ctx, Table* h) +static void enumtable(EnumContext* ctx, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); // Provide a name for a special registry table enumnode(ctx, obj2gco(h), size, h == hvalue(registry(ctx->L)) ? "registry" : NULL); @@ -754,7 +754,7 @@ static void enumudata(EnumContext* ctx, Udata* u) { const char* name = NULL; - if (Table* h = u->metatable) + if (LuaTable* h = u->metatable) { if (h->node != &luaH_dummynode) { diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 6fe82b305..0738840b8 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -121,7 +121,7 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header"); -static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header"); +static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header"); static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header"); const size_t kSizeClasses = LUA_SIZECLASSES; diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 18c696415..6719faaf5 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -263,7 +263,7 @@ typedef struct Udata int len; - struct Table* metatable; + struct LuaTable* metatable; union { @@ -390,7 +390,7 @@ typedef struct Closure uint8_t preload; GCObject* gclist; - struct Table* env; + struct LuaTable* env; union { @@ -454,7 +454,7 @@ typedef struct LuaNode } // clang-format off -typedef struct Table +typedef struct LuaTable { CommonHeader; @@ -473,11 +473,11 @@ typedef struct Table }; - struct Table* metatable; + struct LuaTable* metatable; TValue* array; // array part LuaNode* node; GCObject* gclist; -} Table; +} LuaTable; // clang-format on /* diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 3f4f94257..ad1623919 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -198,7 +198,7 @@ typedef struct global_State struct lua_State* mainthread; UpVal uvhead; // head of double-linked list of all open upvalues - struct Table* mt[LUA_T_COUNT]; // metatables for basic types + struct LuaTable* mt[LUA_T_COUNT]; // metatables for basic types TString* ttname[LUA_T_COUNT]; // names for basic types TString* tmname[TM_N]; // array with tag-method names @@ -217,7 +217,7 @@ typedef struct global_State lua_ExecutionCallbacks ecb; void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory - Table* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata + LuaTable* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata TString* lightuserdataname[LUA_LUTAG_LIMIT]; // names for tagged lightuserdata @@ -266,7 +266,7 @@ struct lua_State int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? - Table* gt; // table of globals + LuaTable* gt; // table of globals UpVal* openupval; // list of open upvalues in this stack GCObject* gclist; @@ -285,7 +285,7 @@ union GCObject struct TString ts; struct Udata u; struct Closure cl; - struct Table h; + struct LuaTable h; struct Proto p; struct UpVal uv; struct lua_State th; // thread diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index dafb2b3f8..ee5ae7ec6 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -58,7 +58,7 @@ const LuaNode luaH_dummynode = { #define hashstr(t, str) hashpow2(t, (str)->hash) #define hashboolean(t, p) hashpow2(t, p) -static LuaNode* hashpointer(const Table* t, const void* p) +static LuaNode* hashpointer(const LuaTable* t, const void* p) { // we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway unsigned int h = unsigned(uintptr_t(p)); @@ -73,7 +73,7 @@ static LuaNode* hashpointer(const Table* t, const void* p) return hashpow2(t, h); } -static LuaNode* hashnum(const Table* t, double n) +static LuaNode* hashnum(const LuaTable* t, double n) { static_assert(sizeof(double) == sizeof(unsigned int) * 2, "expected a 8-byte double"); unsigned int i[2]; @@ -99,7 +99,7 @@ static LuaNode* hashnum(const Table* t, double n) return hashpow2(t, h2); } -static LuaNode* hashvec(const Table* t, const float* v) +static LuaNode* hashvec(const LuaTable* t, const float* v) { unsigned int i[LUA_VECTOR_SIZE]; memcpy(i, v, sizeof(i)); @@ -130,7 +130,7 @@ static LuaNode* hashvec(const Table* t, const float* v) ** returns the `main' position of an element in a table (that is, the index ** of its hash value) */ -static LuaNode* mainposition(const Table* t, const TValue* key) +static LuaNode* mainposition(const LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -166,7 +166,7 @@ static int arrayindex(double key) ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signalled by -1. */ -static int findindex(lua_State* L, Table* t, StkId key) +static int findindex(lua_State* L, LuaTable* t, StkId key) { int i; if (ttisnil(key)) @@ -194,7 +194,7 @@ static int findindex(lua_State* L, Table* t, StkId key) } } -int luaH_next(lua_State* L, Table* t, StkId key) +int luaH_next(lua_State* L, LuaTable* t, StkId key) { int i = findindex(L, t, key); // find original element for (i++; i < t->sizearray; i++) @@ -270,7 +270,7 @@ static int countint(double key, int* nums) return 0; } -static int numusearray(const Table* t, int* nums) +static int numusearray(const LuaTable* t, int* nums) { int lg; int ttlg; // 2^lg @@ -298,7 +298,7 @@ static int numusearray(const Table* t, int* nums) return ause; } -static int numusehash(const Table* t, int* nums, int* pnasize) +static int numusehash(const LuaTable* t, int* nums, int* pnasize) { int totaluse = 0; // total number of elements int ause = 0; // summation of `nums' @@ -317,7 +317,7 @@ static int numusehash(const Table* t, int* nums, int* pnasize) return totaluse; } -static void setarrayvector(lua_State* L, Table* t, int size) +static void setarrayvector(lua_State* L, LuaTable* t, int size) { if (size > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -328,7 +328,7 @@ static void setarrayvector(lua_State* L, Table* t, int size) t->sizearray = size; } -static void setnodevector(lua_State* L, Table* t, int size) +static void setnodevector(lua_State* L, LuaTable* t, int size) { int lsize; if (size == 0) @@ -357,9 +357,9 @@ static void setnodevector(lua_State* L, Table* t, int size) t->lastfree = size; // all positions are free } -static TValue* newkey(lua_State* L, Table* t, const TValue* key); +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key); -static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) +static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnumber(key)) { @@ -373,7 +373,7 @@ static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -static void resize(lua_State* L, Table* t, int nasize, int nhsize) +static void resize(lua_State* L, LuaTable* t, int nasize, int nhsize) { if (nasize > MAXSIZE || nhsize > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -424,7 +424,7 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); // free old array } -static int adjustasize(Table* t, int size, const TValue* ek) +static int adjustasize(LuaTable* t, int size, const TValue* ek) { bool tbound = t->node != dummynode || size < t->sizearray; int ekindex = ek && ttisnumber(ek) ? arrayindex(nvalue(ek)) : -1; @@ -434,19 +434,19 @@ static int adjustasize(Table* t, int size, const TValue* ek) return size; } -void luaH_resizearray(lua_State* L, Table* t, int nasize) +void luaH_resizearray(lua_State* L, LuaTable* t, int nasize) { int nsize = (t->node == dummynode) ? 0 : sizenode(t); int asize = adjustasize(t, nasize, NULL); resize(L, t, asize, nsize); } -void luaH_resizehash(lua_State* L, Table* t, int nhsize) +void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize) { resize(L, t, t->sizearray, nhsize); } -static void rehash(lua_State* L, Table* t, const TValue* ek) +static void rehash(lua_State* L, LuaTable* t, const TValue* ek) { int nums[MAXBITS + 1]; // nums[i] = number of keys between 2^(i-1) and 2^i for (int i = 0; i <= MAXBITS; i++) @@ -491,9 +491,9 @@ static void rehash(lua_State* L, Table* t, const TValue* ek) ** }============================================================= */ -Table* luaH_new(lua_State* L, int narray, int nhash) +LuaTable* luaH_new(lua_State* L, int narray, int nhash) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = NULL; t->tmcache = cast_byte(~0); @@ -512,16 +512,16 @@ Table* luaH_new(lua_State* L, int narray, int nhash) return t; } -void luaH_free(lua_State* L, Table* t, lua_Page* page) +void luaH_free(lua_State* L, LuaTable* t, lua_Page* page) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat); if (t->array) luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat); - luaM_freegco(L, t, sizeof(Table), t->memcat, page); + luaM_freegco(L, t, sizeof(LuaTable), t->memcat, page); } -static LuaNode* getfreepos(Table* t) +static LuaNode* getfreepos(LuaTable* t) { while (t->lastfree > 0) { @@ -541,7 +541,7 @@ static LuaNode* getfreepos(Table* t) ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ -static TValue* newkey(lua_State* L, Table* t, const TValue* key) +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key) { // enforce boundary invariant if (ttisnumber(key) && nvalue(key) == t->sizearray + 1) @@ -601,7 +601,7 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key) /* ** search function for integers */ -const TValue* luaH_getnum(Table* t, int key) +const TValue* luaH_getnum(LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -627,7 +627,7 @@ const TValue* luaH_getnum(Table* t, int key) /* ** search function for strings */ -const TValue* luaH_getstr(Table* t, TString* key) +const TValue* luaH_getstr(LuaTable* t, TString* key) { LuaNode* n = hashstr(t, key); for (;;) @@ -644,7 +644,7 @@ const TValue* luaH_getstr(Table* t, TString* key) /* ** main search function */ -const TValue* luaH_get(Table* t, const TValue* key) +const TValue* luaH_get(LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -677,7 +677,7 @@ const TValue* luaH_get(Table* t, const TValue* key) } } -TValue* luaH_set(lua_State* L, Table* t, const TValue* key) +TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key) { const TValue* p = luaH_get(t, key); invalidateTMcache(t); @@ -687,7 +687,7 @@ TValue* luaH_set(lua_State* L, Table* t, const TValue* key) return luaH_newkey(L, t, key); } -TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) +TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); @@ -698,7 +698,7 @@ TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -TValue* luaH_setnum(lua_State* L, Table* t, int key) +TValue* luaH_setnum(lua_State* L, LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -715,7 +715,7 @@ TValue* luaH_setnum(lua_State* L, Table* t, int key) } } -TValue* luaH_setstr(lua_State* L, Table* t, TString* key) +TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key) { const TValue* p = luaH_getstr(t, key); invalidateTMcache(t); @@ -729,7 +729,7 @@ TValue* luaH_setstr(lua_State* L, Table* t, TString* key) } } -static int updateaboundary(Table* t, int boundary) +static int updateaboundary(LuaTable* t, int boundary) { if (boundary < t->sizearray && ttisnil(&t->array[boundary - 1])) { @@ -752,7 +752,7 @@ static int updateaboundary(Table* t, int boundary) ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ -int luaH_getn(Table* t) +int luaH_getn(LuaTable* t) { int boundary = getaboundary(t); @@ -793,9 +793,9 @@ int luaH_getn(Table* t) } } -Table* luaH_clone(lua_State* L, Table* tt) +LuaTable* luaH_clone(lua_State* L, LuaTable* tt) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = tt->metatable; t->tmcache = tt->tmcache; @@ -830,7 +830,7 @@ Table* luaH_clone(lua_State* L, Table* tt) return t; } -void luaH_clear(Table* tt) +void luaH_clear(LuaTable* tt) { // clear array part for (int i = 0; i < tt->sizearray; ++i) diff --git a/VM/src/ltable.h b/VM/src/ltable.h index 021f21bf7..50d1e643a 100644 --- a/VM/src/ltable.h +++ b/VM/src/ltable.h @@ -14,21 +14,21 @@ // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->tmcache = 0 -LUAI_FUNC const TValue* luaH_getnum(Table* t, int key); -LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key); -LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key); -LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key); -LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash); -LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize); -LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize); -LUAI_FUNC void luaH_free(lua_State* L, Table* t, struct lua_Page* page); -LUAI_FUNC int luaH_next(lua_State* L, Table* t, StkId key); -LUAI_FUNC int luaH_getn(Table* t); -LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt); -LUAI_FUNC void luaH_clear(Table* tt); +LUAI_FUNC const TValue* luaH_getnum(LuaTable* t, int key); +LUAI_FUNC TValue* luaH_setnum(lua_State* L, LuaTable* t, int key); +LUAI_FUNC const TValue* luaH_getstr(LuaTable* t, TString* key); +LUAI_FUNC TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key); +LUAI_FUNC const TValue* luaH_get(LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC LuaTable* luaH_new(lua_State* L, int narray, int lnhash); +LUAI_FUNC void luaH_resizearray(lua_State* L, LuaTable* t, int nasize); +LUAI_FUNC void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize); +LUAI_FUNC void luaH_free(lua_State* L, LuaTable* t, struct lua_Page* page); +LUAI_FUNC int luaH_next(lua_State* L, LuaTable* t, StkId key); +LUAI_FUNC int luaH_getn(LuaTable* t); +LUAI_FUNC LuaTable* luaH_clone(lua_State* L, LuaTable* tt); +LUAI_FUNC void luaH_clear(LuaTable* tt); #define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot))) diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 75d9f4003..dbe60e4e7 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -53,7 +53,7 @@ static int maxn(lua_State* L) double max = 0; luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); for (int i = 0; i < t->sizearray; i++) { @@ -87,8 +87,8 @@ static int getn(lua_State* L) static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) { - Table* src = hvalue(L->base + (srct - 1)); - Table* dst = hvalue(L->base + (dstt - 1)); + LuaTable* src = hvalue(L->base + (srct - 1)); + LuaTable* dst = hvalue(L->base + (dstt - 1)); if (dst->readonly) luaG_readonlyerror(L); @@ -213,7 +213,7 @@ static int tmove(lua_State* L) int n = e - f + 1; // number of elements to move luaL_argcheck(L, t <= INT_MAX - n + 1, 4, "destination wrap around"); - Table* dst = hvalue(L->base + (tt - 1)); + LuaTable* dst = hvalue(L->base + (tt - 1)); if (dst->readonly) // also checked in moveelements, but this blocks resizes of r/o tables luaG_readonlyerror(L); @@ -229,7 +229,7 @@ static int tmove(lua_State* L) return 1; } -static void addfield(lua_State* L, luaL_Strbuf* b, int i, Table* t) +static void addfield(lua_State* L, luaL_Strbuf* b, int i, LuaTable* t) { if (t && unsigned(i - 1) < unsigned(t->sizearray) && ttisstring(&t->array[i - 1])) { @@ -253,7 +253,7 @@ static int tconcat(lua_State* L) int i = luaL_optinteger(L, 3, 1); int last = luaL_opt(L, luaL_checkinteger, 4, lua_objlen(L, 1)); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); luaL_Strbuf b; luaL_buffinit(L, &b); @@ -274,7 +274,7 @@ static int tpack(lua_State* L) int n = lua_gettop(L); // number of elements to pack lua_createtable(L, n, 1); // create result table - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); for (int i = 0; i < n; ++i) { @@ -292,7 +292,7 @@ static int tpack(lua_State* L) static int tunpack(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int i = luaL_optinteger(L, 2, 1); int e = luaL_opt(L, luaL_checkinteger, 3, lua_objlen(L, 1)); @@ -335,7 +335,7 @@ static int sort_func(lua_State* L, const TValue* l, const TValue* r) return !l_isfalse(L->top); } -inline void sort_swap(lua_State* L, Table* t, int i, int j) +inline void sort_swap(lua_State* L, LuaTable* t, int i, int j) { TValue* arr = t->array; int n = t->sizearray; @@ -348,7 +348,7 @@ inline void sort_swap(lua_State* L, Table* t, int i, int j) setobj2t(L, &arr[j], &temp); } -inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) +inline int sort_less(lua_State* L, LuaTable* t, int i, int j, SortPredicate pred) { TValue* arr = t->array; int n = t->sizearray; @@ -363,7 +363,7 @@ inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) return res; } -static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pred, int root) +static void sort_siftheap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred, int root) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -389,7 +389,7 @@ static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pr sort_swap(L, t, l + root, l + lastleft); } -static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) +static void sort_heap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -404,7 +404,7 @@ static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) } } -static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredicate pred) +static void sort_rec(lua_State* L, LuaTable* t, int l, int u, int limit, SortPredicate pred) { // sort range [l..u] (inclusive, 0-based) while (l < u) @@ -477,7 +477,7 @@ static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredic static int tsort(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int n = luaH_getn(t); if (t->readonly) luaG_readonlyerror(L); @@ -504,7 +504,7 @@ static int tcreate(lua_State* L) if (!lua_isnoneornil(L, 2)) { lua_createtable(L, size, 0); - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); StkId v = L->base + 1; @@ -530,7 +530,7 @@ static int tfind(lua_State* L) if (init < 1) luaL_argerror(L, 3, "index out of range"); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); StkId v = L->base + 1; for (int i = init;; ++i) @@ -554,7 +554,7 @@ static int tclear(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* tt = hvalue(L->base); + LuaTable* tt = hvalue(L->base); if (tt->readonly) luaG_readonlyerror(L); @@ -587,7 +587,7 @@ static int tclone(lua_State* L) luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable"); - Table* tt = luaH_clone(L, hvalue(L->base)); + LuaTable* tt = luaH_clone(L, hvalue(L->base)); TValue v; sethvalue(L, &v, tt); diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index f38ab80bf..f6b0079ad 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -86,7 +86,7 @@ void luaT_init(lua_State* L) ** function to be used with macro "fasttm": optimized for absence of ** tag methods. */ -const TValue* luaT_gettm(Table* events, TMS event, TString* ename) +const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename) { const TValue* tm = luaH_getstr(events, ename); LUAU_ASSERT(event <= TM_EQ); @@ -105,7 +105,7 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event) NB: Tag-methods were replaced by meta-methods in Lua 5.0, but the old names are still around (this function, for example). */ - Table* mt; + LuaTable* mt; switch (ttype(o)) { case LUA_TTABLE: @@ -147,7 +147,7 @@ const TString* luaT_objtypenamestr(lua_State* L, const TValue* o) } // For all types except userdata and table, a global metatable can be set with a global name override - if (Table* mt = L->global->mt[ttype(o)]) + if (LuaTable* mt = L->global->mt[ttype(o)]) { const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]); diff --git a/VM/src/ltm.h b/VM/src/ltm.h index 7dafd4eda..f3294b64c 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -51,7 +51,7 @@ typedef enum LUAI_DATA const char* const luaT_typenames[]; LUAI_DATA const char* const luaT_eventname[]; -LUAI_FUNC const TValue* luaT_gettm(Table* events, TMS event, TString* ename); +LUAI_FUNC const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename); LUAI_FUNC const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event); LUAI_FUNC const TString* luaT_objtypenamestr(lua_State* L, const TValue* o); diff --git a/VM/src/lvm.h b/VM/src/lvm.h index 0b8690be7..6989bcee5 100644 --- a/VM/src/lvm.h +++ b/VM/src/lvm.h @@ -26,7 +26,7 @@ LUAI_FUNC int luaV_tostring(lua_State* L, StkId obj); LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val); LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val); LUAI_FUNC void luaV_concat(lua_State* L, int total, int last); -LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil); +LUAI_FUNC void luaV_getimport(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil); LUAI_FUNC void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit); LUAI_FUNC void luaV_callTM(lua_State* L, int nparams, int res); LUAI_FUNC void luaV_tryfuncTM(lua_State* L, StkId func); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index e3a310ca9..ce07d878f 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -330,7 +330,7 @@ static void luau_execute(lua_State* L) LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -361,7 +361,7 @@ static void luau_execute(lua_State* L) LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -451,7 +451,7 @@ static void luau_execute(lua_State* L) // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -568,7 +568,7 @@ static void luau_execute(lua_State* L) // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -642,7 +642,7 @@ static void luau_execute(lua_State* L) // fast-path: array lookup if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -672,7 +672,7 @@ static void luau_execute(lua_State* L) // fast-path: array assign if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -703,7 +703,7 @@ static void luau_execute(lua_State* L) // fast-path: array lookup if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable)) { @@ -731,7 +731,7 @@ static void luau_execute(lua_State* L) // fast-path: array assign if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable && !h->readonly)) { @@ -804,7 +804,7 @@ static void luau_execute(lua_State* L) if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // note: we can't use nodemask8 here because we need to query the main position of the table, and 8-bit nodemask8 only works // for predictive lookups LuaNode* n = &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)]; @@ -844,7 +844,7 @@ static void luau_execute(lua_State* L) } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -858,7 +858,7 @@ static void luau_execute(lua_State* L) } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -2126,7 +2126,7 @@ static void luau_execute(lua_State* L) // fast-path #1: tables if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (fastnotm(h->metatable, TM_LEN)) { @@ -2196,7 +2196,7 @@ static void luau_execute(lua_State* L) L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -2281,7 +2281,7 @@ static void luau_execute(lua_State* L) } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { @@ -2340,7 +2340,7 @@ static void luau_execute(lua_State* L) // TODO: remove the table check per guarantee above if (ttisnil(ra) && ttistable(ra + 1)) { - Table* h = hvalue(ra + 1); + LuaTable* h = hvalue(ra + 1); int index = int(reinterpret_cast(pvalue(ra + 2))); int sizearray = h->sizearray; diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index aa248fc1f..2a3443eb1 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -72,7 +72,7 @@ struct ScopedSetGCThreshold size_t originalThreshold = 0; }; -void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) +void luaV_getimport(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) { int count = id >> 30; LUAU_ASSERT(count > 0); @@ -141,7 +141,7 @@ static TString* readString(TempBuffer& strings, const char* data, size return id == 0 ? NULL : strings[id - 1]; } -static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id) +static void resolveImportSafe(lua_State* L, LuaTable* env, TValue* k, uint32_t id) { struct ResolveImport { @@ -273,7 +273,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX}; // env is 0 for current environment and a stack index otherwise - Table* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); + LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); TString* source = luaS_new(L, chunkname); @@ -481,7 +481,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size case LBC_CONSTANT_TABLE: { int keys = readVarInt(data, size, offset); - Table* h = luaH_new(L, 0, keys); + LuaTable* h = luaH_new(L, 0, keys); for (int i = 0; i < keys; ++i) { int key = readVarInt(data, size, offset); diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 0cf9d2066..5c49139f6 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -101,7 +101,7 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* res = luaH_get(h, key); // do a primitive get @@ -137,7 +137,7 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* oldval = luaH_get(h, key); @@ -185,7 +185,7 @@ static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId re return 1; } -static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event) +static const TValue* get_compTM(lua_State* L, LuaTable* mt1, LuaTable* mt2, TMS event) { const TValue* tm1 = fasttm(L, mt1, event); const TValue* tm2; @@ -533,7 +533,7 @@ void luaV_dolen(lua_State* L, StkId ra, const TValue* rb) { case LUA_TTABLE: { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if ((tm = fasttm(L, h->metatable, TM_LEN)) == NULL) { setnvalue(ra, cast_num(luaH_getn(h))); diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 6c55a140b..7184ef764 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -19,6 +19,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes) +LUAU_FASTFLAG(LuauRemoveNotAnyHack) struct ATSFixture : BuiltinsFixture @@ -410,6 +412,8 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -425,7 +429,7 @@ end )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(3, result1); + LUAU_REQUIRE_ERROR_COUNT(1, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); @@ -502,6 +506,8 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -565,7 +571,7 @@ initialize() )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(5, result1); + LUAU_REQUIRE_ERROR_COUNT(3, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 504e40e4c..fd1deccf4 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -506,6 +506,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms") SINGLE_COMPARE(vmaxsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5f, 0xc6); SINGLE_COMPARE(vminsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5d, 0xc6); + SINGLE_COMPARE(vcmpeqsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0xc2, 0xc6, 0x00); SINGLE_COMPARE(vcmpltsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0xc2, 0xc6, 0x01); } diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index e730171f2..702be46b0 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -8,8 +8,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauDocumentationAtPosition) - struct DocumentationSymbolFixture : BuiltinsFixture { std::optional getDocSymbol(const std::string& source, Position position) @@ -167,7 +165,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; std::optional symbol = getDocSymbol( R"( local x: string = "Foo" @@ -181,7 +178,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "parent_class_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; loadDefinition(R"( declare class Foo function bar(self, x: string): number diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 6998cb2ba..953f7110f 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -3,6 +3,7 @@ #include "Luau/AssemblyBuilderA64.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" +#include "Luau/CodeGen.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" #include "Luau/UnwindBuilderWin.h" diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index e68ce2c79..4dbf83424 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -39,8 +39,9 @@ LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers) LUAU_FASTFLAG(LuauVectorLibNativeCodegen) LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorMetatable) -LUAU_FASTFLAG(LuauBufferBitMethods) +LUAU_FASTFLAG(LuauBufferBitMethods2) LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse) +LUAU_FASTFLAG(LuauMathMapDefinition) static lua_CompileOptions defaultOptions() { @@ -654,7 +655,7 @@ TEST_CASE("Basic") TEST_CASE("Buffers") { - ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true}; + ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods2, true}; runConformance("buffers.lua"); } @@ -986,7 +987,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { - ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, false}; // waiting for math.lerp to be added to embedded type definitions + ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, true}; + ScopedFastFlag luauMathMapDefinition{FFlag::LuauMathMapDefinition, true}; runConformance( "types.lua", diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c31cfb65e..434701b29 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAG(LuauFixFunctionNameStartPosition) namespace { @@ -3743,5 +3744,27 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type") CHECK_EQ(result.errors.size(), 2); } +TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location") +{ + ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true}; + AstStatBlock* block = parse(R"( + function simple() + end + + function T:complex() + end + )"); + + REQUIRE_EQ(2, block->body.size); + + const auto function1 = block->body.data[0]->as(); + LUAU_ASSERT(function1); + CHECK_EQ(Position{1, 17}, function1->name->location.begin); + + const auto function2 = block->body.data[1]->as(); + LUAU_ASSERT(function2); + CHECK_EQ(Position{4, 17}, function2->name->location.begin); +} + TEST_SUITE_END(); diff --git a/tests/Repl.test.cpp b/tests/Repl.test.cpp index 16574cff1..85d533901 100644 --- a/tests/Repl.test.cpp +++ b/tests/Repl.test.cpp @@ -2,7 +2,7 @@ #include "lua.h" #include "lualib.h" -#include "Repl.h" +#include "Luau/Repl.h" #include "ScopedFlags.h" #include "doctest.h" diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 574a45611..ff00a681b 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -6,8 +6,8 @@ #include "lua.h" #include "lualib.h" -#include "Repl.h" -#include "FileUtils.h" +#include "Luau/Repl.h" +#include "Luau/FileUtils.h" #include "doctest.h" @@ -116,7 +116,7 @@ class ReplWithPathFixture for (int i = 0; i < 20; ++i) { bool engineTestDir = isDirectory(luauDirAbs + "/Client/Luau/tests"); - bool luauTestDir = isDirectory(luauDirAbs + "/luau/tests/require"); + bool luauTestDir = isDirectory(luauDirAbs + "/tests/require"); if (engineTestDir || luauTestDir) { @@ -125,12 +125,6 @@ class ReplWithPathFixture luauDirRel += "/Client/Luau"; luauDirAbs += "/Client/Luau"; } - else - { - luauDirRel += "/luau"; - luauDirAbs += "/luau"; - } - if (type == PathType::Relative) return luauDirRel; diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index f02983e51..66c397a1a 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -9,10 +9,13 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite) +LUAU_FASTFLAG(LuauUserTypeFunFixInner) LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) +LUAU_FASTFLAG(LuauUserTypeFunCloneTail) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -474,6 +477,28 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") CHECK(toString(tpm->givenTp) == "~string"); } +TEST_CASE_FIXTURE(ClassFixture, "udtf_negation_inner") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunFixInner{FFlag::LuauUserTypeFunFixInner, true}; + + CheckResult result = check(R"( +type function pass(t) + return types.negationof(t):inner() +end + +type function fail(t) + return t:inner() +end + +local function ok(idx: pass): number return idx end +local function notok(idx: fail): never return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"('fail' type function errored at runtime: [string "fail"]:7: type.inner: cannot call inner method on non-negation type: `number` type)"); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1391,4 +1416,479 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result") CHECK(toString(result.errors[3]) == R"(Type function instance t0 is uninhabited)"); } +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + ScopedFastFlag luauUserTypeFunCloneTail{FFlag::LuauUserTypeFunCloneTail, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.singleton(types.copy(arg) == arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): true return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + return types.newfunction({ head = {T} }, { head = {T} }, {T}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + local f = types.newfunction() + f:setparameters({T, T}); + f:setreturns({T}); + f:setgenerics({T}); + return f +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + assert(T.tag == "generic") + assert(T:name() == "T") + assert(T:ispack() == false) + + local Us, Vs = types.generic("U", true), types.generic("V", true) + assert(Us.tag == "generic") + assert(Us:name() == "U") + assert(Us:ispack() == true) + + local f = types.newfunction() + f:setparameters({T}, Us); + f:setreturns({T}, Vs); + f:setgenerics({T, Us, Vs}); + return f +end + +local function ok(idx: pass<>): (T, U...) -> (T, V...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T, U = types.generic("T"), types.generic("U") + + -- (T) -> () + local func = types.newfunction({ head = {T} }, {}, {T}); + + -- { x: (T) -> (), y: U } + local tbl = types.newtable({ [types.singleton("x")] = func, [types.singleton("y")] = U }) + + -- (T, { x: (T) -> (), y: U }, U) -> () + return types.newfunction({ head = {T, tbl, U } }, {}, {T, U}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass<>): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + return types.newfunction({ head = {T} }, {}, {types.copy(T)}) +end + +local function ok(idx: pass<>): (T) -> () return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T, U = generics[1], generics[2] + local f = types.newfunction() + f:setparameters({T}); + f:setreturns({U}); + f:setgenerics({T, U}); + return f +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): (T) -> (U) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (T, U...) -> (T, U...) + +local function ok(idx: pass): (T, U...) -> (T, U...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_8") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (U...) -> (U...) + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + + local tbl1 = types.newtable({ [types.singleton("x")] = T }) + local tbl2 = types.newtable({ [types.singleton("x")] = Us }) -- it is possible to have invalid types in-flight + + return types.singleton(tbl1 == tbl2) +end + +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({}, {}, {Us, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK( + toString(result.errors[0]) == + R"('get' type function errored at runtime: [string "get"]:4: types.newfunction: generic type cannot follow a generic pack)" + ); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, {}, {}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'T' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, U = types.generic("T"), types.generic("U") + + -- (U) -> () + local func = types.newfunction({ head = {U} }, {}, {U}); + + -- broken: (T, (U) -> (), U) -> () + return types.newfunction({ head = {T, func, U } }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'U' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, { tail = Us }, {T, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Ts = types.generic("T"), types.generic("T", true) + return types.newfunction({ head = {T} }, { tail = Ts }, {T, Ts}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {Us} }, {}, {T, Us}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' cannot be placed in a type position)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ tail = Us }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_variadic_api") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters({p.tail}, p.head[1]); + f:setreturns({r.tail}, r.head[1]); + return f +end + +type test = (string, ...number) -> (number, ...string) + +local function ok(idx: pass): (number, ...string) -> (string, ...number) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index d9c4c13e4..34e430ea5 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTypestateBuiltins2) LUAU_FASTFLAG(LuauStringFormatArityFix) +LUAU_FASTFLAG(LuauStringFormatErrorSuppression) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1586,4 +1587,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_find_should_not_crash") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + local x: any = "world" + print(string.format("Hello, %s!", x)) + )"); + + if (FFlag::LuauStringFormatErrorSuppression) + LUAU_REQUIRE_NO_ERRORS(result); + else + LUAU_REQUIRE_ERROR_COUNT(1, result); +} + TEST_SUITE_END(); diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index fbd8f9ddb..586023edb 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -408,6 +408,7 @@ assert(math.lerp(1, 5, 1) == 5) assert(math.lerp(1, 5, 0.5) == 3) assert(math.lerp(1, 5, 1.5) == 7) assert(math.lerp(1, 5, -0.5) == -1) +assert(math.lerp(1, 5, noinline(0.5)) == 3) -- lerp properties local sq2, sq3 = math.sqrt(2), math.sqrt(3) diff --git a/tests/main.cpp b/tests/main.cpp index bd5a05176..005a3e61e 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" + #define DOCTEST_CONFIG_IMPLEMENT // Our calls to parseOption/parseFlag don't provide a prefix so set the prefix to the empty string. #define DOCTEST_CONFIG_OPTIONS_PREFIX "" diff --git a/tools/natvis/VM.natvis b/tools/natvis/VM.natvis index 59bc43c46..adf603eb6 100644 --- a/tools/natvis/VM.natvis +++ b/tools/natvis/VM.natvis @@ -77,7 +77,7 @@ --- - + table metatable From 76e0958146fead45fae71db4ce4cfee59dbae68e Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:43:07 -0800 Subject: [PATCH 4/4] Include and in IrUtils.cpp --- CodeGen/src/IrUtils.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 00f274e7e..02d19e49b 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -10,6 +10,9 @@ #include "lua.h" #include "lnumutils.h" +#include +#include + #include #include