diff --git a/.clang-format b/.clang-format index 5f533acf..b715e3c9 100644 --- a/.clang-format +++ b/.clang-format @@ -16,10 +16,14 @@ SortIncludes: false IndentWidth: 4 TabWidth: 4 ObjCBlockIndentWidth: 4 -AlignAfterOpenBracket: DontAlign UseTab: Never PointerAlignment: Left SpaceAfterTemplateKeyword: false AlignEscapedNewlines: DontAlign AlwaysBreakTemplateDeclarations: Yes MaxEmptyLinesToKeep: 10 +AllowAllParametersOfDeclarationOnNextLine: false +AlignAfterOpenBracket: BlockIndent +BinPackArguments: false +BinPackParameters: false +PenaltyReturnTypeOnItsOwnLine: 10000 diff --git a/Analysis/include/Luau/AnyTypeSummary.h b/Analysis/include/Luau/AnyTypeSummary.h index c1444db2..73d6f851 100644 --- a/Analysis/include/Luau/AnyTypeSummary.h +++ b/Analysis/include/Luau/AnyTypeSummary.h @@ -1,6 +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/AstQuery.h" #include "Luau/Config.h" #include "Luau/ModuleResolver.h" #include "Luau/Scope.h" @@ -36,6 +37,7 @@ struct AnyTypeSummary { TypeArena arena; + AstStatBlock* rootSrc = nullptr; DenseHashSet seenTypeFamilyInstances{nullptr}; int recursionCount = 0; @@ -47,33 +49,30 @@ struct AnyTypeSummary AnyTypeSummary(); - void traverse(Module* module, AstStat* src, NotNull builtinTypes); + void traverse(const Module* module, AstStat* src, NotNull builtinTypes); - std::pair checkForAnyCast(Scope* scope, AstExprTypeAssertion* expr); - - // Todo: errors resolved by anys - void reportError(Location location, TypeErrorData err); + std::pair checkForAnyCast(const Scope* scope, AstExprTypeAssertion* expr); bool containsAny(TypePackId typ); bool containsAny(TypeId typ); - bool isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull builtinTypes); - bool isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull builtinTypes); + bool isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull builtinTypes); + bool isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull builtinTypes); - bool hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); - bool hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); - bool hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); + bool hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes); + bool hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes); + bool hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes); - TypeId checkForFamilyInhabitance(TypeId instance, Location location); - TypeId lookupType(AstExpr* expr, Module* module, NotNull builtinTypes); - TypePackId reconstructTypePack(AstArray exprs, Module* module, NotNull builtinTypes); + TypeId checkForFamilyInhabitance(const TypeId instance, Location location); + TypeId lookupType(const AstExpr* expr, const Module* module, NotNull builtinTypes); + TypePackId reconstructTypePack(const AstArray exprs, const Module* module, NotNull builtinTypes); DenseHashSet seenTypeFunctionInstances{nullptr}; - TypeId lookupAnnotation(AstType* annotation, Module* module, NotNull builtintypes); - std::optional lookupPackAnnotation(AstTypePack* annotation, Module* module); - TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location); + TypeId lookupAnnotation(AstType* annotation, const Module* module, NotNull builtintypes); + std::optional lookupPackAnnotation(AstTypePack* annotation, const Module* module); + TypeId checkForTypeFunctionInhabitance(const TypeId instance, const Location location); - enum Pattern : uint64_t + enum Pattern: uint64_t { Casts, FuncArg, @@ -91,11 +90,25 @@ struct AnyTypeSummary Pattern code; std::string node; TelemetryTypePair type; - std::string debug; explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type); }; + struct FindReturnAncestry final : public AstVisitor + { + AstNode* currNode{nullptr}; + AstNode* stat{nullptr}; + Position rootEnd; + bool found = false; + + explicit FindReturnAncestry(AstNode* stat, Position rootEnd); + + bool visit(AstType* node) override; + bool visit(AstNode* node) override; + bool visit(AstStatFunction* node) override; + bool visit(AstStatLocalFunction* node) override; + }; + std::vector typeInfo; /** @@ -103,29 +116,32 @@ struct AnyTypeSummary * @param node the lexical node that the scope belongs to. * @param parent the parent scope of the new scope. Must not be null. */ - Scope* childScope(AstNode* node, const Scope* parent); + const Scope* childScope(const AstNode* node, const Scope* parent); - Scope* findInnerMostScope(Location location, Module* module); + std::optional matchRequire(const AstExprCall& call); + AstNode* getNode(AstStatBlock* root, AstNode* node); + const Scope* findInnerMostScope(const Location location, const Module* module); + const AstNode* findAstAncestryAtLocation(const AstStatBlock* root, AstNode* node); - void visit(Scope* scope, AstStat* stat, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatBlock* block, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatLocal* local, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatFor* for_, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatFunction* function, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull builtinTypes); - void visit(Scope* scope, AstStatError* error, Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStat* stat, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull builtinTypes); + void visit(const Scope* scope, AstStatError* error, const Module* module, NotNull builtinTypes); }; } // namespace Luau \ No newline at end of file diff --git a/Analysis/include/Luau/Anyification.h b/Analysis/include/Luau/Anyification.h index c81dc3fb..4b9c8ee9 100644 --- a/Analysis/include/Luau/Anyification.h +++ b/Analysis/include/Luau/Anyification.h @@ -19,10 +19,22 @@ using ScopePtr = std::shared_ptr; // A substitution which replaces free types by any struct Anyification : Substitution { - Anyification(TypeArena* arena, NotNull scope, NotNull builtinTypes, InternalErrorReporter* iceHandler, TypeId anyType, - TypePackId anyTypePack); - Anyification(TypeArena* arena, const ScopePtr& scope, NotNull builtinTypes, InternalErrorReporter* iceHandler, TypeId anyType, - TypePackId anyTypePack); + Anyification( + TypeArena* arena, + NotNull scope, + NotNull builtinTypes, + InternalErrorReporter* iceHandler, + TypeId anyType, + TypePackId anyTypePack + ); + Anyification( + TypeArena* arena, + const ScopePtr& scope, + NotNull builtinTypes, + InternalErrorReporter* iceHandler, + TypeId anyType, + TypePackId anyTypePack + ); NotNull scope; NotNull builtinTypes; InternalErrorReporter* iceHandler; diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index 5622c143..94b0d87f 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -25,21 +25,42 @@ TypeId makeOption(NotNull builtinTypes, TypeArena& arena, TypeId t /** Small utility function for building up type definitions from C++. */ TypeId makeFunction( // Monomorphic - TypeArena& arena, std::optional selfType, std::initializer_list paramTypes, std::initializer_list retTypes, - bool checked = false); + TypeArena& arena, + std::optional selfType, + std::initializer_list paramTypes, + std::initializer_list retTypes, + bool checked = false +); TypeId makeFunction( // Polymorphic - TypeArena& arena, std::optional selfType, std::initializer_list generics, std::initializer_list genericPacks, - std::initializer_list paramTypes, std::initializer_list retTypes, bool checked = false); + TypeArena& arena, + std::optional selfType, + std::initializer_list generics, + std::initializer_list genericPacks, + std::initializer_list paramTypes, + std::initializer_list retTypes, + bool checked = false +); TypeId makeFunction( // Monomorphic - TypeArena& arena, std::optional selfType, std::initializer_list paramTypes, std::initializer_list paramNames, - std::initializer_list retTypes, bool checked = false); + TypeArena& arena, + std::optional selfType, + std::initializer_list paramTypes, + std::initializer_list paramNames, + std::initializer_list retTypes, + bool checked = false +); TypeId makeFunction( // Polymorphic - TypeArena& arena, std::optional selfType, std::initializer_list generics, std::initializer_list genericPacks, - std::initializer_list paramTypes, std::initializer_list paramNames, std::initializer_list retTypes, - bool checked = false); + TypeArena& arena, + std::optional selfType, + std::initializer_list generics, + std::initializer_list genericPacks, + std::initializer_list paramTypes, + std::initializer_list paramNames, + std::initializer_list retTypes, + bool checked = false +); void attachMagicFunction(TypeId ty, MagicFunction fn); void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn); diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 79129f72..61253732 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -256,9 +256,24 @@ struct ReducePackConstraint TypePackId tp; }; -using ConstraintV = Variant; +using ConstraintV = Variant< + SubtypeConstraint, + PackSubtypeConstraint, + GeneralizationConstraint, + IterableConstraint, + NameConstraint, + TypeAliasExpansionConstraint, + FunctionCallConstraint, + FunctionCheckConstraint, + PrimitiveTypeConstraint, + HasPropConstraint, + HasIndexerConstraint, + AssignPropConstraint, + AssignIndexConstraint, + UnpackConstraint, + ReduceConstraint, + ReducePackConstraint, + EqualityConstraint>; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 510d9828..eb6e18eb 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -122,9 +122,18 @@ struct ConstraintGenerator DcrLogger* logger; - ConstraintGenerator(ModulePtr module, NotNull normalizer, NotNull moduleResolver, NotNull builtinTypes, - NotNull ice, const ScopePtr& globalScope, std::function prepareModuleScope, - DcrLogger* logger, NotNull dfg, std::vector requireCycles); + ConstraintGenerator( + ModulePtr module, + NotNull normalizer, + NotNull moduleResolver, + NotNull builtinTypes, + NotNull ice, + const ScopePtr& globalScope, + std::function prepareModuleScope, + DcrLogger* logger, + NotNull dfg, + std::vector requireCycles + ); /** * The entry point to the ConstraintGenerator. This will construct a set @@ -195,10 +204,23 @@ private: }; using RefinementContext = InsertionOrderedMap; - void unionRefinements(const ScopePtr& scope, Location location, const RefinementContext& lhs, const RefinementContext& rhs, - RefinementContext& dest, std::vector* constraints); - void computeRefinement(const ScopePtr& scope, Location location, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, - std::vector* constraints); + void unionRefinements( + const ScopePtr& scope, + Location location, + const RefinementContext& lhs, + const RefinementContext& rhs, + RefinementContext& dest, + std::vector* constraints + ); + void computeRefinement( + const ScopePtr& scope, + Location location, + RefinementId refinement, + RefinementContext* refis, + bool sense, + bool eq, + std::vector* constraints + ); void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); @@ -217,6 +239,7 @@ private: ControlFlow visit(const ScopePtr& scope, AstStatCompoundAssign* assign); ControlFlow visit(const ScopePtr& scope, AstStatIf* ifStatement); ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias); + ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function); ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal); ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass); ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); @@ -224,7 +247,11 @@ private: InferencePack checkPack(const ScopePtr& scope, AstArray exprs, const std::vector>& expectedTypes = {}); InferencePack checkPack( - const ScopePtr& scope, AstExpr* expr, const std::vector>& expectedTypes = {}, bool generalize = true); + const ScopePtr& scope, + AstExpr* expr, + const std::vector>& expectedTypes = {}, + bool generalize = true + ); InferencePack checkPack(const ScopePtr& scope, AstExprCall* call); @@ -238,7 +265,12 @@ private: * @return the type of the expression. */ Inference check( - const ScopePtr& scope, AstExpr* expr, std::optional expectedType = {}, bool forceSingleton = false, bool generalize = true); + const ScopePtr& scope, + AstExpr* expr, + std::optional expectedType = {}, + bool forceSingleton = false, + bool generalize = true + ); Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional expectedType, bool forceSingleton); @@ -276,7 +308,11 @@ private: }; FunctionSignature checkFunctionSignature( - const ScopePtr& parent, AstExprFunction* fn, std::optional expectedType = {}, std::optional originalName = {}); + const ScopePtr& parent, + AstExprFunction* fn, + std::optional expectedType = {}, + std::optional originalName = {} + ); /** * Checks the body of a function expression. @@ -323,7 +359,11 @@ private: * privateTypeBindings map. **/ std::vector> createGenerics( - const ScopePtr& scope, AstArray generics, bool useCache = false, bool addTypes = true); + const ScopePtr& scope, + AstArray generics, + bool useCache = false, + bool addTypes = true + ); /** * Creates generic type packs given a list of AST definitions, resolving @@ -336,7 +376,11 @@ private: * privateTypePackBindings map. **/ std::vector> createGenericPacks( - const ScopePtr& scope, AstArray packs, bool useCache = false, bool addTypes = true); + const ScopePtr& scope, + AstArray packs, + bool useCache = false, + bool addTypes = true + ); Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack); @@ -371,7 +415,12 @@ private: std::vector> getExpectedCallTypesForFunctionOverloads(const TypeId fnType); TypeId createTypeFunctionInstance( - const TypeFunction& function, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location); + const TypeFunction& function, + std::vector typeArguments, + std::vector packArguments, + const ScopePtr& scope, + Location location + ); }; /** Borrow a vector of pointers from a vector of owning pointers to constraints. diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index eb6ed29f..c6b4a828 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -109,9 +109,16 @@ struct ConstraintSolver DenseHashMap typeFunctionsToFinalize{nullptr}; - explicit ConstraintSolver(NotNull normalizer, NotNull rootScope, std::vector> constraints, - ModuleName moduleName, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, - TypeCheckLimits limits); + explicit ConstraintSolver( + NotNull normalizer, + NotNull rootScope, + std::vector> constraints, + ModuleName moduleName, + NotNull moduleResolver, + std::vector requireCycles, + DcrLogger* logger, + TypeCheckLimits limits + ); // Randomize the order in which to dispatch constraints void randomize(unsigned seed); @@ -170,7 +177,13 @@ public: bool tryDispatchHasIndexer( - int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen); + int& recursionDepth, + NotNull constraint, + TypeId subjectType, + TypeId indexType, + TypeId resultType, + Set& seen + ); bool tryDispatch(const HasIndexerConstraint& c, NotNull constraint); bool tryDispatch(const AssignPropConstraint& c, NotNull constraint); @@ -187,10 +200,23 @@ public: // for a, ... in next_function, t, ... do bool tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull constraint, bool force); - std::pair, std::optional> lookupTableProp(NotNull constraint, TypeId subjectType, - const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false); - std::pair, std::optional> lookupTableProp(NotNull constraint, TypeId subjectType, - const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen); + std::pair, std::optional> lookupTableProp( + NotNull constraint, + TypeId subjectType, + const std::string& propName, + ValueContext context, + bool inConditional = false, + bool suppressSimplification = false + ); + std::pair, std::optional> lookupTableProp( + NotNull constraint, + TypeId subjectType, + const std::string& propName, + ValueContext context, + bool inConditional, + bool suppressSimplification, + DenseHashSet& seen + ); /** * Generate constraints to unpack the types of srcTypes and assign each diff --git a/Analysis/include/Luau/DataFlowGraph.h b/Analysis/include/Luau/DataFlowGraph.h index 3f1a4378..2a894bc9 100644 --- a/Analysis/include/Luau/DataFlowGraph.h +++ b/Analysis/include/Luau/DataFlowGraph.h @@ -162,6 +162,7 @@ private: ControlFlow visit(DfgScope* scope, AstStatFunction* f); ControlFlow visit(DfgScope* scope, AstStatLocalFunction* l); ControlFlow visit(DfgScope* scope, AstStatTypeAlias* t); + ControlFlow visit(DfgScope* scope, AstStatTypeFunction* f); ControlFlow visit(DfgScope* scope, AstStatDeclareGlobal* d); ControlFlow visit(DfgScope* scope, AstStatDeclareFunction* d); ControlFlow visit(DfgScope* scope, AstStatDeclareClass* d); diff --git a/Analysis/include/Luau/DcrLogger.h b/Analysis/include/Luau/DcrLogger.h index 1e170d5b..d650d9e0 100644 --- a/Analysis/include/Luau/DcrLogger.h +++ b/Analysis/include/Luau/DcrLogger.h @@ -126,7 +126,11 @@ struct DcrLogger void captureInitialSolverState(const Scope* rootScope, const std::vector>& unsolvedConstraints); StepSnapshot prepareStepSnapshot( - const Scope* rootScope, NotNull current, bool force, const std::vector>& unsolvedConstraints); + const Scope* rootScope, + NotNull current, + bool force, + const std::vector>& unsolvedConstraints + ); void commitStepSnapshot(StepSnapshot snapshot); void captureFinalSolverState(const Scope* rootScope, const std::vector>& unsolvedConstraints); diff --git a/Analysis/include/Luau/Differ.h b/Analysis/include/Luau/Differ.h index 587ed5ae..d9b78939 100644 --- a/Analysis/include/Luau/Differ.h +++ b/Analysis/include/Luau/Differ.h @@ -62,7 +62,12 @@ struct DiffPathNodeLeaf // TODO: Rename to anonymousIndex, for both union and Intersection std::optional unionIndex; DiffPathNodeLeaf( - std::optional ty, std::optional tableProperty, std::optional minLength, bool isVariadic, std::optional unionIndex) + std::optional ty, + std::optional tableProperty, + std::optional minLength, + bool isVariadic, + std::optional unionIndex + ) : ty(ty) , tableProperty(tableProperty) , minLength(minLength) @@ -159,7 +164,11 @@ struct DifferEnvironment DenseHashMap genericTpMatchedPairs; DifferEnvironment( - TypeId rootLeft, TypeId rootRight, std::optional externalSymbolLeft, std::optional externalSymbolRight) + TypeId rootLeft, + TypeId rootRight, + std::optional externalSymbolLeft, + std::optional externalSymbolRight + ) : rootLeft(rootLeft) , rootRight(rootRight) , externalSymbolLeft(externalSymbolLeft) diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 06d08652..baf3318c 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -194,6 +194,11 @@ struct InternalError bool operator==(const InternalError& rhs) const; }; +struct ConstraintSolvingIncompleteError +{ + bool operator==(const ConstraintSolvingIncompleteError& rhs) const; +}; + struct CannotCallNonFunction { TypeId ty; @@ -443,15 +448,55 @@ struct UnexpectedTypePackInSubtyping bool operator==(const UnexpectedTypePackInSubtyping& rhs) const; }; -using TypeErrorData = - Variant; +using TypeErrorData = Variant< + TypeMismatch, + UnknownSymbol, + UnknownProperty, + NotATable, + CannotExtendTable, + OnlyTablesCanHaveMethods, + DuplicateTypeDefinition, + CountMismatch, + FunctionDoesNotTakeSelf, + FunctionRequiresSelf, + OccursCheckFailed, + UnknownRequire, + IncorrectGenericParameterCount, + SyntaxError, + CodeTooComplex, + UnificationTooComplex, + UnknownPropButFoundLikeProp, + GenericError, + InternalError, + ConstraintSolvingIncompleteError, + CannotCallNonFunction, + ExtraInformation, + DeprecatedApiUsed, + ModuleHasCyclicDependency, + IllegalRequire, + FunctionExitsWithoutReturning, + DuplicateGenericParameter, + CannotAssignToNever, + CannotInferBinaryOperation, + MissingProperties, + SwappedGenericTypeParameter, + OptionalValueAccess, + MissingUnionProperty, + TypesAreUnrelated, + NormalizationTooComplex, + TypePackMismatch, + DynamicPropertyLookupOnClassesUnsafe, + UninhabitedTypeFunction, + UninhabitedTypePackFunction, + WhereClauseNeeded, + PackWhereClauseNeeded, + CheckedFunctionCallError, + NonStrictFunctionDefinitionError, + PropertyAccessViolation, + CheckedFunctionIncorrectArgs, + UnexpectedTypeInSubtyping, + UnexpectedTypePackInSubtyping, + ExplicitFunctionAnnotationRecommended>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 2a70b072..f476b582 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -185,30 +185,55 @@ struct Frontend void registerBuiltinDefinition(const std::string& name, std::function); void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); - LoadDefinitionFileResult loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source, const std::string& packageName, - bool captureComments, bool typeCheckForAutocomplete = false); + LoadDefinitionFileResult loadDefinitionFile( + GlobalTypes& globals, + ScopePtr targetScope, + std::string_view source, + const std::string& packageName, + bool captureComments, + bool typeCheckForAutocomplete = false + ); // Batch module checking. Queue modules and check them together, retrieve results with 'getCheckResult' // If provided, 'executeTask' function is allowed to call the 'task' function on any thread and return without waiting for 'task' to complete void queueModuleCheck(const std::vector& names); void queueModuleCheck(const ModuleName& name); - std::vector checkQueuedModules(std::optional optionOverride = {}, - std::function task)> executeTask = {}, std::function progress = {}); + std::vector checkQueuedModules( + std::optional optionOverride = {}, + std::function task)> executeTask = {}, + std::function progress = {} + ); std::optional getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false); private: - ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector requireCycles, std::optional environmentScope, - bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits); + ModulePtr check( + const SourceModule& sourceModule, + Mode mode, + std::vector requireCycles, + std::optional environmentScope, + bool forAutocomplete, + bool recordJsonLog, + TypeCheckLimits typeCheckLimits + ); std::pair getSourceNode(const ModuleName& name); SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions); bool parseGraph( - std::vector& buildQueue, const ModuleName& root, bool forAutocomplete, std::function canSkip = {}); + std::vector& buildQueue, + const ModuleName& root, + bool forAutocomplete, + std::function canSkip = {} + ); - void addBuildQueueItems(std::vector& items, std::vector& buildQueue, bool cycleDetected, - DenseHashSet& seen, const FrontendOptions& frontendOptions); + void addBuildQueueItems( + std::vector& items, + std::vector& buildQueue, + bool cycleDetected, + DenseHashSet& seen, + const FrontendOptions& frontendOptions + ); void checkBuildQueueItem(BuildQueueItem& item); void checkBuildQueueItems(std::vector& items); void recordItemResult(const BuildQueueItem& item); @@ -248,14 +273,34 @@ public: std::vector moduleQueue; }; -ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, - NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, - const ScopePtr& globalScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits); +ModulePtr check( + const SourceModule& sourceModule, + Mode mode, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& globalScope, + std::function prepareModuleScope, + FrontendOptions options, + TypeCheckLimits limits +); -ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, - NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, - const ScopePtr& globalScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits, bool recordJsonLog, std::function writeJsonLog); +ModulePtr check( + const SourceModule& sourceModule, + Mode mode, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& globalScope, + std::function prepareModuleScope, + FrontendOptions options, + TypeCheckLimits limits, + bool recordJsonLog, + std::function writeJsonLog +); } // namespace Luau diff --git a/Analysis/include/Luau/Generalization.h b/Analysis/include/Luau/Generalization.h index 04ac2df1..18d5b678 100644 --- a/Analysis/include/Luau/Generalization.h +++ b/Analysis/include/Luau/Generalization.h @@ -8,6 +8,12 @@ namespace Luau { -std::optional generalize(NotNull arena, NotNull builtinTypes, NotNull scope, - NotNull> bakedTypes, TypeId ty, /* avoid sealing tables*/ bool avoidSealingTables = false); +std::optional generalize( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull> bakedTypes, + TypeId ty, + /* avoid sealing tables*/ bool avoidSealingTables = false +); } diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index 8b65dfe3..0fd2817a 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -17,8 +17,15 @@ struct TypeCheckLimits; // A substitution which replaces generic types in a given set by free types. struct ReplaceGenerics : Substitution { - ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull builtinTypes, TypeLevel level, Scope* scope, - const std::vector& generics, const std::vector& genericPacks) + ReplaceGenerics( + const TxnLog* log, + TypeArena* arena, + NotNull builtinTypes, + TypeLevel level, + Scope* scope, + const std::vector& generics, + const std::vector& genericPacks + ) : Substitution(log, arena) , builtinTypes(builtinTypes) , level(level) @@ -28,8 +35,15 @@ struct ReplaceGenerics : Substitution { } - void resetState(const TxnLog* log, TypeArena* arena, NotNull builtinTypes, TypeLevel level, Scope* scope, - const std::vector& generics, const std::vector& genericPacks); + void resetState( + const TxnLog* log, + TypeArena* arena, + NotNull builtinTypes, + TypeLevel level, + Scope* scope, + const std::vector& generics, + const std::vector& genericPacks + ); NotNull builtinTypes; @@ -141,6 +155,11 @@ struct GenericTypeFinder : TypeOnceVisitor * limits to be exceeded. */ std::optional instantiate( - NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty); + NotNull builtinTypes, + NotNull arena, + NotNull limits, + NotNull scope, + TypeId ty +); } // namespace Luau diff --git a/Analysis/include/Luau/Instantiation2.h b/Analysis/include/Luau/Instantiation2.h index 39e874a3..c9215fad 100644 --- a/Analysis/include/Luau/Instantiation2.h +++ b/Analysis/include/Luau/Instantiation2.h @@ -75,8 +75,16 @@ struct Instantiation2 : Substitution }; std::optional instantiate2( - TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypeId ty); -std::optional instantiate2(TypeArena* arena, DenseHashMap genericSubstitutions, - DenseHashMap genericPackSubstitutions, TypePackId tp); + TypeArena* arena, + DenseHashMap genericSubstitutions, + DenseHashMap genericPackSubstitutions, + TypeId ty +); +std::optional instantiate2( + TypeArena* arena, + DenseHashMap genericSubstitutions, + DenseHashMap genericPackSubstitutions, + TypePackId tp +); } // namespace Luau diff --git a/Analysis/include/Luau/Linter.h b/Analysis/include/Luau/Linter.h index 303cd9e9..f911a652 100644 --- a/Analysis/include/Luau/Linter.h +++ b/Analysis/include/Luau/Linter.h @@ -25,8 +25,14 @@ struct LintResult std::vector warnings; }; -std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, - const std::vector& hotcomments, const LintOptions& options); +std::vector lint( + AstStat* root, + const AstNameTable& names, + const ScopePtr& env, + const Module* module, + const std::vector& hotcomments, + const LintOptions& options +); std::vector getDeprecatedGlobals(const AstNameTable& names); diff --git a/Analysis/include/Luau/NonStrictTypeChecker.h b/Analysis/include/Luau/NonStrictTypeChecker.h index ad9a8d7f..8e80c762 100644 --- a/Analysis/include/Luau/NonStrictTypeChecker.h +++ b/Analysis/include/Luau/NonStrictTypeChecker.h @@ -12,8 +12,15 @@ struct BuiltinTypes; struct UnifierSharedState; struct TypeCheckLimits; -void checkNonStrict(NotNull builtinTypes, NotNull ice, NotNull unifierState, - NotNull dfg, NotNull limits, const SourceModule& sourceModule, Module* module); +void checkNonStrict( + NotNull builtinTypes, + NotNull ice, + NotNull unifierState, + NotNull dfg, + NotNull limits, + const SourceModule& sourceModule, + Module* module +); } // namespace Luau diff --git a/Analysis/include/Luau/OverloadResolution.h b/Analysis/include/Luau/OverloadResolution.h index 73cef264..9a2974a5 100644 --- a/Analysis/include/Luau/OverloadResolution.h +++ b/Analysis/include/Luau/OverloadResolution.h @@ -31,8 +31,15 @@ struct OverloadResolver OverloadIsNonviable, // Arguments were incompatible with the overloads parameters but were otherwise compatible by arity }; - OverloadResolver(NotNull builtinTypes, NotNull arena, NotNull normalizer, NotNull scope, - NotNull reporter, NotNull limits, Location callLocation); + OverloadResolver( + NotNull builtinTypes, + NotNull arena, + NotNull normalizer, + NotNull scope, + NotNull reporter, + NotNull limits, + Location callLocation + ); NotNull builtinTypes; NotNull arena; @@ -58,11 +65,21 @@ private: std::optional testIsSubtype(const Location& location, TypeId subTy, TypeId superTy); std::optional testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy); std::pair checkOverload( - TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector* argExprs, bool callMetamethodOk = true); + TypeId fnTy, + const TypePack* args, + AstExpr* fnLoc, + const std::vector* argExprs, + bool callMetamethodOk = true + ); static bool isLiteral(AstExpr* expr); LUAU_NOINLINE std::pair checkOverload_( - TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector* argExprs); + TypeId fnTy, + const FunctionType* fn, + const TypePack* args, + AstExpr* fnExpr, + const std::vector* argExprs + ); size_t indexof(Analysis analysis); void add(Analysis analysis, TypeId ty, ErrorVec&& errors); }; @@ -88,8 +105,16 @@ struct SolveResult // Helper utility, presently used for binary operator type functions. // // Given a function and a set of arguments, select a suitable overload. -SolveResult solveFunctionCall(NotNull arena, NotNull builtinTypes, NotNull normalizer, - NotNull iceReporter, NotNull limits, NotNull scope, const Location& location, TypeId fn, - TypePackId argsPack); +SolveResult solveFunctionCall( + NotNull arena, + NotNull builtinTypes, + NotNull normalizer, + NotNull iceReporter, + NotNull limits, + NotNull scope, + const Location& location, + TypeId fn, + TypePackId argsPack +); } // namespace Luau diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index d72963cb..a3f199f3 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -140,8 +140,13 @@ struct Subtyping SeenSet seenTypes{{}}; - Subtyping(NotNull builtinTypes, NotNull typeArena, NotNull normalizer, - NotNull iceReporter, NotNull scope); + Subtyping( + NotNull builtinTypes, + NotNull typeArena, + NotNull normalizer, + NotNull iceReporter, + NotNull scope + ); Subtyping(const Subtyping&) = delete; Subtyping& operator=(const Subtyping&) = delete; @@ -209,13 +214,19 @@ private: SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name); SubtypingResult isCovariantWith( - SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm); + SubtypingEnvironment& env, + const std::shared_ptr& subNorm, + const std::shared_ptr& superNorm + ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables); SubtypingResult isCovariantWith( - SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction); + SubtypingEnvironment& env, + const NormalizedFunctionType& subFunction, + const NormalizedFunctionType& superFunction + ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); diff --git a/Analysis/include/Luau/TableLiteralInference.h b/Analysis/include/Luau/TableLiteralInference.h index 40151940..dd9ecf97 100644 --- a/Analysis/include/Luau/TableLiteralInference.h +++ b/Analysis/include/Luau/TableLiteralInference.h @@ -14,7 +14,15 @@ struct BuiltinTypes; struct Unifier2; class AstExpr; -TypeId matchLiteralType(NotNull> astTypes, NotNull> astExpectedTypes, - NotNull builtinTypes, NotNull arena, NotNull unifier, TypeId expectedType, TypeId exprType, - const AstExpr* expr, std::vector& toBlock); +TypeId matchLiteralType( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull builtinTypes, + NotNull arena, + NotNull unifier, + TypeId expectedType, + TypeId exprType, + const AstExpr* expr, + std::vector& toBlock +); } // namespace Luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 28ca076e..585c2493 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -276,8 +276,8 @@ struct WithPredicate } }; -using MagicFunction = std::function>( - struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate)>; +using MagicFunction = std::function>(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate)>; struct MagicFunctionCallContext { @@ -305,19 +305,46 @@ struct FunctionType FunctionType(TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); // Global polymorphic function - FunctionType(std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, - std::optional defn = {}, bool hasSelf = false); + FunctionType( + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn = {}, + bool hasSelf = false + ); // Local monomorphic function FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); FunctionType( - TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); + TypeLevel level, + Scope* scope, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn = {}, + bool hasSelf = false + ); // Local polymorphic function - FunctionType(TypeLevel level, std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, - std::optional defn = {}, bool hasSelf = false); - FunctionType(TypeLevel level, Scope* scope, std::vector generics, std::vector genericPacks, TypePackId argTypes, - TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); + FunctionType( + TypeLevel level, + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn = {}, + bool hasSelf = false + ); + FunctionType( + TypeLevel level, + Scope* scope, + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn = {}, + bool hasSelf = false + ); std::optional definition; /// These should all be generic @@ -398,9 +425,15 @@ struct Property // DEPRECATED // TODO: Kill all constructors in favor of `Property::rw(TypeId read, TypeId write)` and friends. Property(); - Property(TypeId readTy, bool deprecated = false, const std::string& deprecatedSuggestion = "", std::optional location = std::nullopt, - const Tags& tags = {}, const std::optional& documentationSymbol = std::nullopt, - std::optional typeLocation = std::nullopt); + Property( + TypeId readTy, + bool deprecated = false, + const std::string& deprecatedSuggestion = "", + std::optional location = std::nullopt, + const Tags& tags = {}, + const std::optional& documentationSymbol = std::nullopt, + std::optional typeLocation = std::nullopt + ); // DEPRECATED: Should only be called in non-RWP! We assert that the `readTy` is not nullopt. // TODO: Kill once we don't have non-RWP. @@ -502,8 +535,16 @@ struct ClassType std::optional definitionLocation; std::optional indexer; - ClassType(Name name, Props props, std::optional parent, std::optional metatable, Tags tags, - std::shared_ptr userData, ModuleName definitionModuleName, std::optional definitionLocation) + ClassType( + Name name, + Props props, + std::optional parent, + std::optional metatable, + Tags tags, + std::shared_ptr userData, + ModuleName definitionModuleName, + std::optional definitionLocation + ) : name(name) , props(props) , parent(parent) @@ -515,9 +556,17 @@ struct ClassType { } - ClassType(Name name, Props props, std::optional parent, std::optional metatable, Tags tags, - std::shared_ptr userData, ModuleName definitionModuleName, std::optional definitionLocation, - std::optional indexer) + ClassType( + Name name, + Props props, + std::optional parent, + std::optional metatable, + Tags tags, + std::shared_ptr userData, + ModuleName definitionModuleName, + std::optional definitionLocation, + std::optional indexer + ) : name(name) , props(props) , parent(parent) @@ -661,9 +710,26 @@ struct NegationType using ErrorType = Unifiable::Error; -using TypeVariant = - Unifiable::Variant; +using TypeVariant = Unifiable::Variant< + TypeId, + FreeType, + GenericType, + PrimitiveType, + SingletonType, + BlockedType, + PendingExpansionType, + FunctionType, + TableType, + MetatableType, + ClassType, + AnyType, + UnionType, + IntersectionType, + LazyType, + UnknownType, + NeverType, + NegationType, + TypeFunctionInstanceType>; struct Type final { diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index b30cfe01..981fdfe6 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -14,7 +14,13 @@ struct UnifierSharedState; struct SourceModule; struct Module; -void check(NotNull builtinTypes, NotNull sharedState, NotNull limits, DcrLogger* logger, - const SourceModule& sourceModule, Module* module); +void check( + NotNull builtinTypes, + NotNull sharedState, + NotNull limits, + DcrLogger* logger, + const SourceModule& sourceModule, + Module* module +); } // namespace Luau diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index f5ac22ad..ad7b92ef 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -44,8 +44,14 @@ struct TypeFunctionContext { } - TypeFunctionContext(NotNull arena, NotNull builtins, NotNull scope, NotNull normalizer, - NotNull ice, NotNull limits) + TypeFunctionContext( + NotNull arena, + NotNull builtins, + NotNull scope, + NotNull normalizer, + NotNull ice, + NotNull limits + ) : arena(arena) , builtins(builtins) , scope(scope) diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 340c1e72..7f2e29b5 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -62,7 +62,11 @@ struct HashBoolNamePair struct TypeChecker { explicit TypeChecker( - const ScopePtr& globalScope, ModuleResolver* resolver, NotNull builtinTypes, InternalErrorReporter* iceHandler); + const ScopePtr& globalScope, + ModuleResolver* resolver, + NotNull builtinTypes, + InternalErrorReporter* iceHandler + ); TypeChecker(const TypeChecker&) = delete; TypeChecker& operator=(const TypeChecker&) = delete; @@ -85,6 +89,7 @@ struct TypeChecker ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function); ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function); ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias); + ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction); ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass); ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction); @@ -96,7 +101,11 @@ struct TypeChecker void checkBlockTypeAliases(const ScopePtr& scope, std::vector& sorted); WithPredicate checkExpr( - const ScopePtr& scope, const AstExpr& expr, std::optional expectedType = std::nullopt, bool forceSingleton = false); + const ScopePtr& scope, + const AstExpr& expr, + std::optional expectedType = std::nullopt, + bool forceSingleton = false + ); WithPredicate checkExpr(const ScopePtr& scope, const AstExprLocal& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprGlobal& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprVarargs& expr); @@ -107,17 +116,31 @@ struct TypeChecker WithPredicate checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional expectedType = std::nullopt); WithPredicate checkExpr(const ScopePtr& scope, const AstExprUnary& expr); TypeId checkRelationalOperation( - const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {}); + const ScopePtr& scope, + const AstExprBinary& expr, + TypeId lhsType, + TypeId rhsType, + const PredicateVec& predicates = {} + ); TypeId checkBinaryOperation( - const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {}); + const ScopePtr& scope, + const AstExprBinary& expr, + TypeId lhsType, + TypeId rhsType, + const PredicateVec& predicates = {} + ); WithPredicate checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional expectedType = std::nullopt); WithPredicate checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprError& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional expectedType = std::nullopt); WithPredicate checkExpr(const ScopePtr& scope, const AstExprInterpString& expr); - TypeId checkExprTable(const ScopePtr& scope, const AstExprTable& expr, const std::vector>& fieldTypes, - std::optional expectedType); + TypeId checkExprTable( + const ScopePtr& scope, + const AstExprTable& expr, + const std::vector>& fieldTypes, + std::optional expectedType + ); // Returns the type of the lvalue. TypeId checkLValue(const ScopePtr& scope, const AstExpr& expr, ValueContext ctx); @@ -130,34 +153,79 @@ struct TypeChecker TypeId checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr, ValueContext ctx); TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level); - std::pair checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr, - std::optional originalNameLoc, std::optional selfType, std::optional expectedType); + std::pair checkFunctionSignature( + const ScopePtr& scope, + int subLevel, + const AstExprFunction& expr, + std::optional originalNameLoc, + std::optional selfType, + std::optional expectedType + ); void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function); - void checkArgumentList(const ScopePtr& scope, const AstExpr& funName, Unifier& state, TypePackId paramPack, TypePackId argPack, - const std::vector& argLocations); + void checkArgumentList( + const ScopePtr& scope, + const AstExpr& funName, + Unifier& state, + TypePackId paramPack, + TypePackId argPack, + const std::vector& argLocations + ); WithPredicate checkExprPack(const ScopePtr& scope, const AstExpr& expr); WithPredicate checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr); WithPredicate checkExprPackHelper(const ScopePtr& scope, const AstExprCall& expr); WithPredicate checkExprPackHelper2( - const ScopePtr& scope, const AstExprCall& expr, TypeId selfType, TypeId actualFunctionType, TypeId functionType, TypePackId retPack); + const ScopePtr& scope, + const AstExprCall& expr, + TypeId selfType, + TypeId actualFunctionType, + TypeId functionType, + TypePackId retPack + ); std::vector> getExpectedTypesForCall(const std::vector& overloads, size_t argumentCount, bool selfCall); - std::unique_ptr> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack, - TypePackId argPack, TypePack* args, const std::vector* argLocations, const WithPredicate& argListResult, - std::vector& overloadsThatMatchArgCount, std::vector& overloadsThatDont, std::vector& errors); - bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector& argLocations, - const std::vector& errors); - void reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack, - const std::vector& argLocations, const std::vector& overloads, const std::vector& overloadsThatMatchArgCount, - std::vector& errors); + std::unique_ptr> checkCallOverload( + const ScopePtr& scope, + const AstExprCall& expr, + TypeId fn, + TypePackId retPack, + TypePackId argPack, + TypePack* args, + const std::vector* argLocations, + const WithPredicate& argListResult, + std::vector& overloadsThatMatchArgCount, + std::vector& overloadsThatDont, + std::vector& errors + ); + bool handleSelfCallMismatch( + const ScopePtr& scope, + const AstExprCall& expr, + TypePack* args, + const std::vector& argLocations, + const std::vector& errors + ); + void reportOverloadResolutionError( + const ScopePtr& scope, + const AstExprCall& expr, + TypePackId retPack, + TypePackId argPack, + const std::vector& argLocations, + const std::vector& overloads, + const std::vector& overloadsThatMatchArgCount, + std::vector& errors + ); - WithPredicate checkExprList(const ScopePtr& scope, const Location& location, const AstArray& exprs, - bool substituteFreeForNil = false, const std::vector& lhsAnnotations = {}, - const std::vector>& expectedTypes = {}); + WithPredicate checkExprList( + const ScopePtr& scope, + const Location& location, + const AstArray& exprs, + bool substituteFreeForNil = false, + const std::vector& lhsAnnotations = {}, + const std::vector>& expectedTypes = {} + ); static std::optional matchRequire(const AstExprCall& call); TypeId checkRequire(const ScopePtr& scope, const ModuleInfo& moduleInfo, const Location& location); @@ -175,8 +243,13 @@ struct TypeChecker */ bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location); bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options); - bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, - CountMismatch::Context ctx = CountMismatch::Context::Arg); + bool unify( + TypePackId subTy, + TypePackId superTy, + const ScopePtr& scope, + const Location& location, + CountMismatch::Context ctx = CountMismatch::Context::Arg + ); /** Attempt to unify the types. * If this fails, and the subTy type can be instantiated, do so and try unification again. @@ -313,12 +386,23 @@ private: TypeId resolveTypeWorker(const ScopePtr& scope, const AstType& annotation); TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& types); TypePackId resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation); - TypeId instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector& typeParams, - const std::vector& typePackParams, const Location& location); + TypeId instantiateTypeFun( + const ScopePtr& scope, + const TypeFun& tf, + const std::vector& typeParams, + const std::vector& typePackParams, + const Location& location + ); // Note: `scope` must be a fresh scope. - GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional levelOpt, const AstNode& node, - const AstArray& genericNames, const AstArray& genericPackNames, bool useCache = false); + GenericTypeDefinitions createGenericTypes( + const ScopePtr& scope, + std::optional levelOpt, + const AstNode& node, + const AstArray& genericNames, + const AstArray& genericPackNames, + bool useCache = false + ); public: void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index c8ee99e9..92be19d1 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -56,14 +56,35 @@ struct InConditionalContext using ScopePtr = std::shared_ptr; std::optional findTableProperty( - NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location); + NotNull builtinTypes, + ErrorVec& errors, + TypeId ty, + const std::string& name, + Location location +); std::optional findMetatableEntry( - NotNull builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location); + NotNull builtinTypes, + ErrorVec& errors, + TypeId type, + const std::string& entry, + Location location +); std::optional findTablePropertyRespectingMeta( - NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location); + NotNull builtinTypes, + ErrorVec& errors, + TypeId ty, + const std::string& name, + Location location +); std::optional findTablePropertyRespectingMeta( - NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location); + NotNull builtinTypes, + ErrorVec& errors, + TypeId ty, + const std::string& name, + ValueContext context, + Location location +); bool occursCheck(TypeId needle, TypeId haystack); @@ -73,7 +94,12 @@ std::pair> getParameterExtents(const TxnLog* log, // Extend the provided pack to at least `length` types. // Returns a temporary TypePack that contains those types plus a tail. TypePack extendTypePack( - TypeArena& arena, NotNull builtinTypes, TypePackId pack, size_t length, std::vector> overrides = {}); + TypeArena& arena, + NotNull builtinTypes, + TypePackId pack, + size_t length, + std::vector> overrides = {} +); /** * Reduces a union by decomposing to the any/error type if it appears in the diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index d1a3b358..b0a855d3 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -106,11 +106,21 @@ struct Unifier * Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt. */ void tryUnify( - TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); + TypeId subTy, + TypeId superTy, + bool isFunctionCall = false, + bool isIntersection = false, + const LiteralProperties* aliasableMap = nullptr + ); private: void tryUnify_( - TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); + TypeId subTy, + TypeId superTy, + bool isFunctionCall = false, + bool isIntersection = false, + const LiteralProperties* aliasableMap = nullptr + ); void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy); // Traverse the two types provided and block on any BlockedTypes we find. @@ -120,8 +130,14 @@ private: void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionType* uv, bool cacheEnabled, bool isFunctionCall); void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionType* uv); void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall); - void tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, - std::optional error = std::nullopt); + void tryUnifyNormalizedTypes( + TypeId subTy, + TypeId superTy, + const NormalizedType& subNorm, + const NormalizedType& superNorm, + std::string reason, + std::optional error = std::nullopt + ); void tryUnifyPrimitives(TypeId subTy, TypeId superTy); void tryUnifySingletons(TypeId subTy, TypeId superTy); void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false); diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 13c2d7f5..8734aeec 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -52,8 +52,13 @@ struct Unifier2 DenseHashSet* uninhabitedTypeFunctions; Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice); - Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice, - DenseHashSet* uninhabitedTypeFunctions); + Unifier2( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull ice, + DenseHashSet* uninhabitedTypeFunctions + ); /** Attempt to commit the subtype relation subTy <: superTy to the type * graph. diff --git a/Analysis/src/AnyTypeSummary.cpp b/Analysis/src/AnyTypeSummary.cpp index 3bcb5144..c5e8e8c6 100644 --- a/Analysis/src/AnyTypeSummary.cpp +++ b/Analysis/src/AnyTypeSummary.cpp @@ -46,33 +46,12 @@ LUAU_FASTFLAG(DebugLuauMagicTypes); namespace Luau { -// TODO: instead of pair just type for solver? generated type -// TODO: see lookupAnnotation in typechecker2. is cleaner than resolvetype -// or delay containsAny() check and do not return pair. -// quick flag in typeid saying was annotation or inferred, would be solid -std::optional getInferredType(AstExpr* expr, Module* module) +void AnyTypeSummary::traverse(const Module* module, AstStat* src, NotNull builtinTypes) { - std::optional inferredType; - - if (module->astTypePacks.contains(expr)) - { - inferredType = *module->astTypePacks.find(expr); - } - else if (module->astTypes.contains(expr)) - { - inferredType = *module->astTypes.find(expr); - } - - return inferredType; + visit(findInnerMostScope(src->location, module), src, module, builtinTypes); } -void AnyTypeSummary::traverse(Module* module, AstStat* src, NotNull builtinTypes) -{ - Scope* scope = findInnerMostScope(src->location, module); - visit(scope, src, module, builtinTypes); -} - -void AnyTypeSummary::visit(Scope* scope, AstStat* stat, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStat* stat, const Module* module, NotNull builtinTypes) { RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit}; @@ -114,7 +93,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStat* stat, Module* module, NotNull< return visit(scope, s, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatBlock* block, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull builtinTypes) { RecursionCounter counter{&recursionCount}; @@ -125,37 +104,38 @@ void AnyTypeSummary::visit(Scope* scope, AstStatBlock* block, Module* module, No visit(scope, stat, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull builtinTypes) { if (ifStatement->thenbody) { - Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module); + const Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module); visit(thenScope, ifStatement->thenbody, module, builtinTypes); } if (ifStatement->elsebody) { - Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module); + const Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module); visit(elseScope, ifStatement->elsebody, module, builtinTypes); } } -void AnyTypeSummary::visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull builtinTypes) { - Scope* whileScope = findInnerMostScope(while_->location, module); + const Scope* whileScope = findInnerMostScope(while_->location, module); visit(whileScope, while_->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull builtinTypes) { - Scope* repeatScope = findInnerMostScope(repeat->location, module); + const Scope* repeatScope = findInnerMostScope(repeat->location, module); visit(repeatScope, repeat->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull builtinTypes) -{ - // Scope* outScope = findOuterScope(ret->location, module); - Scope* retScope = findInnerMostScope(ret->location, module); +void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull builtinTypes) +{ + const Scope* retScope = findInnerMostScope(ret->location, module); + + auto ctxNode = getNode(rootSrc, ret); for (auto val : ret->list) { @@ -163,7 +143,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, Not { TelemetryTypePair types; types.inferredType = toString(lookupType(val, module, builtinTypes)); - TypeInfo ti{Pattern::FuncApp, toString(ret), types}; + TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types}; typeInfo.push_back(ti); } @@ -174,19 +154,19 @@ void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, Not TelemetryTypePair types; types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes)); - auto inf = getInferredType(cast->expr, module); - if (inf) - types.inferredType = toString(*inf); + types.inferredType = toString(lookupType(cast->expr, module, builtinTypes)); - TypeInfo ti{Pattern::Casts, toString(ret), types}; + TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; typeInfo.push_back(ti); } } } } -void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull builtinTypes) { + auto ctxNode = getNode(rootSrc, local); + TypePackId values = reconstructTypePack(local->values, module, builtinTypes); auto [head, tail] = flatten(values); @@ -203,18 +183,30 @@ void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, No TelemetryTypePair types; types.annotatedType = toString(annot); + types.inferredType = toString(lookupType(local->values.data[posn], module, builtinTypes)); - auto inf = getInferredType(local->values.data[posn], module); - if (inf) - types.inferredType = toString(*inf); - - TypeInfo ti{Pattern::VarAnnot, toString(local), types}; + TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types}; typeInfo.push_back(ti); } } + + const AstExprTypeAssertion* maybeRequire = local->values.data[posn]->as(); + if (!maybeRequire) + continue; + + if (isAnyCast(scope, local->values.data[posn], module, builtinTypes)) + { + TelemetryTypePair types; + + types.inferredType = toString(head[std::min(local->values.size - 1, posn)]); + + TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; + typeInfo.push_back(ti); + } } else { + if (std::min(local->values.size - 1, posn) < head.size()) { if (loc->annotation) @@ -227,7 +219,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, No types.annotatedType = toString(annot); types.inferredType = toString(head[std::min(local->values.size - 1, posn)]); - TypeInfo ti{Pattern::VarAnnot, toString(local), types}; + TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types}; typeInfo.push_back(ti); } } @@ -242,7 +234,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, No types.inferredType = toString(*tail); - TypeInfo ti{Pattern::VarAny, toString(local), types}; + TypeInfo ti{Pattern::VarAny, toString(ctxNode), types}; typeInfo.push_back(ti); } } @@ -253,20 +245,22 @@ void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, No } } -void AnyTypeSummary::visit(Scope* scope, AstStatFor* for_, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull builtinTypes) { - Scope* forScope = findInnerMostScope(for_->location, module); + const Scope* forScope = findInnerMostScope(for_->location, module); visit(forScope, for_->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull builtinTypes) { - Scope* loopScope = findInnerMostScope(forIn->location, module); + const Scope* loopScope = findInnerMostScope(forIn->location, module); visit(loopScope, forIn->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull builtinTypes) { + auto ctxNode = getNode(rootSrc, assign); + TypePackId values = reconstructTypePack(assign->values, module, builtinTypes); auto [head, tail] = flatten(values); @@ -290,7 +284,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, else types.inferredType = toString(builtinTypes->nilType); - TypeInfo ti{Pattern::Assign, toString(assign), types}; + TypeInfo ti{Pattern::Assign, toString(ctxNode), types}; typeInfo.push_back(ti); } ++posn; @@ -302,11 +296,9 @@ void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, { TelemetryTypePair types; - auto inf = getInferredType(val, module); - if (inf) - types.inferredType = toString(*inf); + types.inferredType = toString(lookupType(val, module, builtinTypes)); - TypeInfo ti{Pattern::FuncApp, toString(assign), types}; + TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types}; typeInfo.push_back(ti); } @@ -317,17 +309,15 @@ void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, TelemetryTypePair types; types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes)); - auto inf = getInferredType(val, module); - if (inf) - types.inferredType = toString(*inf); + types.inferredType = toString(lookupType(val, module, builtinTypes)); - TypeInfo ti{Pattern::Casts, toString(assign), types}; + TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; typeInfo.push_back(ti); } } } - if (tail) + if (tail) { if (containsAny(*tail)) { @@ -335,14 +325,16 @@ void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, types.inferredType = toString(*tail); - TypeInfo ti{Pattern::Assign, toString(assign), types}; + TypeInfo ti{Pattern::Assign, toString(ctxNode), types}; typeInfo.push_back(ti); } } } -void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull builtinTypes) { + auto ctxNode = getNode(rootSrc, assign); + TelemetryTypePair types; types.inferredType = toString(lookupType(assign->value, module, builtinTypes)); @@ -352,7 +344,7 @@ void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* { if (containsAny(*module->astTypes.find(assign->var))) { - TypeInfo ti{Pattern::Assign, toString(assign), types}; + TypeInfo ti{Pattern::Assign, toString(ctxNode), types}; typeInfo.push_back(ti); } } @@ -360,14 +352,14 @@ void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* { if (containsAny(*module->astTypePacks.find(assign->var))) { - TypeInfo ti{Pattern::Assign, toString(assign), types}; + TypeInfo ti{Pattern::Assign, toString(ctxNode), types}; typeInfo.push_back(ti); } } if (isAnyCall(scope, assign->value, module, builtinTypes)) { - TypeInfo ti{Pattern::FuncApp, toString(assign), types}; + TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types}; typeInfo.push_back(ti); } @@ -376,17 +368,15 @@ void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* if (auto cast = assign->value->as()) { types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes)); - auto inf = getInferredType(cast->expr, module); - if (inf) - types.inferredType = toString(*inf); + types.inferredType = toString(lookupType(cast->expr, module, builtinTypes)); - TypeInfo ti{Pattern::Casts, toString(assign), types}; + TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; typeInfo.push_back(ti); } } } -void AnyTypeSummary::visit(Scope* scope, AstStatFunction* function, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull builtinTypes) { TelemetryTypePair types; types.inferredType = toString(lookupType(function->func, module, builtinTypes)); @@ -413,25 +403,27 @@ void AnyTypeSummary::visit(Scope* scope, AstStatFunction* function, Module* modu visit(scope, function->func->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull builtinTypes) { TelemetryTypePair types; - types.inferredType = toString(lookupType(function->func, module, builtinTypes)); if (hasVariadicAnys(scope, function->func, module, builtinTypes)) { + types.inferredType = toString(lookupType(function->func, module, builtinTypes)); TypeInfo ti{Pattern::VarAny, toString(function), types}; typeInfo.push_back(ti); } if (hasArgAnys(scope, function->func, module, builtinTypes)) { + types.inferredType = toString(lookupType(function->func, module, builtinTypes)); TypeInfo ti{Pattern::FuncArg, toString(function), types}; typeInfo.push_back(ti); } if (hasAnyReturns(scope, function->func, module, builtinTypes)) { + types.inferredType = toString(lookupType(function->func, module, builtinTypes)); TypeInfo ti{Pattern::FuncRet, toString(function), types}; typeInfo.push_back(ti); } @@ -440,8 +432,9 @@ void AnyTypeSummary::visit(Scope* scope, AstStatLocalFunction* function, Module* visit(scope, function->func->body, module, builtinTypes); } -void AnyTypeSummary::visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull builtinTypes) { + auto ctxNode = getNode(rootSrc, alias); auto annot = lookupAnnotation(alias->type, module, builtinTypes); if (containsAny(annot)) @@ -450,33 +443,34 @@ void AnyTypeSummary::visit(Scope* scope, AstStatTypeAlias* alias, Module* module TelemetryTypePair types; types.annotatedType = toString(annot); - TypeInfo ti{Pattern::Alias, toString(alias), types}; + TypeInfo ti{Pattern::Alias, toString(ctxNode), types}; typeInfo.push_back(ti); } } -void AnyTypeSummary::visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull builtinTypes) +void AnyTypeSummary::visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull builtinTypes) { + auto ctxNode = getNode(rootSrc, expr); + if (isAnyCall(scope, expr->expr, module, builtinTypes)) { TelemetryTypePair types; - types.inferredType = toString(lookupType(expr->expr, module, builtinTypes)); - TypeInfo ti{Pattern::FuncApp, toString(expr), types}; + TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types}; typeInfo.push_back(ti); } } -void AnyTypeSummary::visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull builtinTypes) {} +void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull builtinTypes) {} -void AnyTypeSummary::visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull builtinTypes) {} +void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull builtinTypes) {} -void AnyTypeSummary::visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull builtinTypes) {} +void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull builtinTypes) {} -void AnyTypeSummary::visit(Scope* scope, AstStatError* error, Module* module, NotNull builtinTypes) {} +void AnyTypeSummary::visit(const Scope* scope, AstStatError* error, const Module* module, NotNull builtinTypes) {} -TypeId AnyTypeSummary::checkForFamilyInhabitance(TypeId instance, Location location) +TypeId AnyTypeSummary::checkForFamilyInhabitance(const TypeId instance, const Location location) { if (seenTypeFamilyInstances.find(instance)) return instance; @@ -485,13 +479,13 @@ TypeId AnyTypeSummary::checkForFamilyInhabitance(TypeId instance, Location locat return instance; } -TypeId AnyTypeSummary::lookupType(AstExpr* expr, Module* module, NotNull builtinTypes) +TypeId AnyTypeSummary::lookupType(const AstExpr* expr, const Module* module, NotNull builtinTypes) { - TypeId* ty = module->astTypes.find(expr); + const TypeId* ty = module->astTypes.find(expr); if (ty) return checkForFamilyInhabitance(follow(*ty), expr->location); - TypePackId* tp = module->astTypePacks.find(expr); + const TypePackId* tp = module->astTypePacks.find(expr); if (tp) { if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false)) @@ -503,7 +497,7 @@ TypeId AnyTypeSummary::lookupType(AstExpr* expr, Module* module, NotNullerrorRecoveryType(); } -TypePackId AnyTypeSummary::reconstructTypePack(AstArray exprs, Module* module, NotNull builtinTypes) +TypePackId AnyTypeSummary::reconstructTypePack(AstArray exprs, const Module* module, NotNull builtinTypes) { if (exprs.size == 0) return arena.addTypePack(TypePack{{}, std::nullopt}); @@ -515,14 +509,14 @@ TypePackId AnyTypeSummary::reconstructTypePack(AstArray exprs, Module* head.push_back(lookupType(exprs.data[i], module, builtinTypes)); } - TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]); + const TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]); if (tail) return arena.addTypePack(TypePack{std::move(head), follow(*tail)}); else return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()}); } -bool AnyTypeSummary::isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull builtinTypes) +bool AnyTypeSummary::isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull builtinTypes) { if (auto call = expr->as()) { @@ -537,7 +531,7 @@ bool AnyTypeSummary::isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotN return false; } -bool AnyTypeSummary::hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes) +bool AnyTypeSummary::hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes) { if (expr->vararg && expr->varargAnnotation) { @@ -550,7 +544,7 @@ bool AnyTypeSummary::hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module return false; } -bool AnyTypeSummary::hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes) +bool AnyTypeSummary::hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes) { if (expr->args.size > 0) { @@ -569,7 +563,7 @@ bool AnyTypeSummary::hasArgAnys(Scope* scope, AstExprFunction* expr, Module* mod return false; } -bool AnyTypeSummary::hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes) +bool AnyTypeSummary::hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull builtinTypes) { if (!expr->returnAnnotation) { @@ -596,7 +590,7 @@ bool AnyTypeSummary::hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* return false; } -bool AnyTypeSummary::isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull builtinTypes) +bool AnyTypeSummary::isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull builtinTypes) { if (auto cast = expr->as()) { @@ -609,7 +603,7 @@ bool AnyTypeSummary::isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotN return false; } -TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, Module* module, NotNull builtintypes) +TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, const Module* module, NotNull builtintypes) { if (FFlag::DebugLuauMagicTypes) { @@ -623,14 +617,14 @@ TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, Module* module, Not } } - TypeId* ty = module->astResolvedTypes.find(annotation); + const TypeId* ty = module->astResolvedTypes.find(annotation); if (ty) return checkForTypeFunctionInhabitance(follow(*ty), annotation->location); else return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location); } -TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(TypeId instance, Location location) +TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(const TypeId instance, const Location location) { if (seenTypeFunctionInstances.find(instance)) return instance; @@ -639,9 +633,9 @@ TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(TypeId instance, Location return instance; } -std::optional AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, Module* module) +std::optional AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, const Module* module) { - TypePackId* tp = module->astResolvedTypePacks.find(annotation); + const TypePackId* tp = module->astResolvedTypePacks.find(annotation); if (tp != nullptr) return {follow(*tp)}; return {}; @@ -786,9 +780,9 @@ bool AnyTypeSummary::containsAny(TypePackId typ) return found; } -Scope* AnyTypeSummary::findInnerMostScope(Location location, Module* module) +const Scope* AnyTypeSummary::findInnerMostScope(const Location location, const Module* module) { - Scope* bestScope = module->getModuleScope().get(); + const Scope* bestScope = module->getModuleScope().get(); bool didNarrow = false; do @@ -808,6 +802,69 @@ Scope* AnyTypeSummary::findInnerMostScope(Location location, Module* module) return bestScope; } +std::optional AnyTypeSummary::matchRequire(const AstExprCall& call) +{ + const char* require = "require"; + + if (call.args.size != 1) + return std::nullopt; + + const AstExprGlobal* funcAsGlobal = call.func->as(); + if (!funcAsGlobal || funcAsGlobal->name != require) + return std::nullopt; + + if (call.args.size != 1) + return std::nullopt; + + return call.args.data[0]; +} + +AstNode* AnyTypeSummary::getNode(AstStatBlock* root, AstNode* node) +{ + FindReturnAncestry finder(node, root->location.end); + root->visit(&finder); + + if (!finder.currNode) + finder.currNode = node; + + LUAU_ASSERT(finder.found && finder.currNode); + return finder.currNode; +} + +bool AnyTypeSummary::FindReturnAncestry::visit(AstStatLocalFunction* node) +{ + currNode = node; + return !found; +} + +bool AnyTypeSummary::FindReturnAncestry::visit(AstStatFunction* node) +{ + currNode = node; + return !found; +} + +bool AnyTypeSummary::FindReturnAncestry::visit(AstType* node) +{ + return !found; +} + +bool AnyTypeSummary::FindReturnAncestry::visit(AstNode* node) +{ + if (node == stat) + { + found = true; + } + + if (node->location.end == rootEnd && stat->location.end >= rootEnd) + { + currNode = node; + found = true; + } + + return !found; +} + + AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type) : code(code) , node(node) @@ -815,6 +872,12 @@ AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryType { } +AnyTypeSummary::FindReturnAncestry::FindReturnAncestry(AstNode* stat, Position rootEnd) + : stat(stat) + , rootEnd(rootEnd) +{ +} + AnyTypeSummary::AnyTypeSummary() {} } // namespace Luau \ No newline at end of file diff --git a/Analysis/src/Anyification.cpp b/Analysis/src/Anyification.cpp index 741d2141..4bacec03 100644 --- a/Analysis/src/Anyification.cpp +++ b/Analysis/src/Anyification.cpp @@ -9,8 +9,14 @@ namespace Luau { -Anyification::Anyification(TypeArena* arena, NotNull scope, NotNull builtinTypes, InternalErrorReporter* iceHandler, - TypeId anyType, TypePackId anyTypePack) +Anyification::Anyification( + TypeArena* arena, + NotNull scope, + NotNull builtinTypes, + InternalErrorReporter* iceHandler, + TypeId anyType, + TypePackId anyTypePack +) : Substitution(TxnLog::empty(), arena) , scope(scope) , builtinTypes(builtinTypes) @@ -20,8 +26,14 @@ Anyification::Anyification(TypeArena* arena, NotNull scope, NotNull builtinTypes, InternalErrorReporter* iceHandler, - TypeId anyType, TypePackId anyTypePack) +Anyification::Anyification( + TypeArena* arena, + const ScopePtr& scope, + NotNull builtinTypes, + InternalErrorReporter* iceHandler, + TypeId anyType, + TypePackId anyTypePack +) : Anyification(arena, NotNull{scope.get()}, builtinTypes, iceHandler, anyType, anyTypePack) { } diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index 3507a68f..ceeee73c 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -273,9 +273,14 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprGroup* node) { - writeNode(node, "AstExprGroup", [&]() { - write("expr", node->expr); - }); + writeNode( + node, + "AstExprGroup", + [&]() + { + write("expr", node->expr); + } + ); } void write(class AstExprConstantNil* node) @@ -285,37 +290,62 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprConstantBool* node) { - writeNode(node, "AstExprConstantBool", [&]() { - write("value", node->value); - }); + writeNode( + node, + "AstExprConstantBool", + [&]() + { + write("value", node->value); + } + ); } void write(class AstExprConstantNumber* node) { - writeNode(node, "AstExprConstantNumber", [&]() { - write("value", node->value); - }); + writeNode( + node, + "AstExprConstantNumber", + [&]() + { + write("value", node->value); + } + ); } void write(class AstExprConstantString* node) { - writeNode(node, "AstExprConstantString", [&]() { - write("value", node->value); - }); + writeNode( + node, + "AstExprConstantString", + [&]() + { + write("value", node->value); + } + ); } void write(class AstExprLocal* node) { - writeNode(node, "AstExprLocal", [&]() { - write("local", node->local); - }); + writeNode( + node, + "AstExprLocal", + [&]() + { + write("local", node->local); + } + ); } void write(class AstExprGlobal* node) { - writeNode(node, "AstExprGlobal", [&]() { - write("global", node->name); - }); + writeNode( + node, + "AstExprGlobal", + [&]() + { + write("global", node->name); + } + ); } void write(class AstExprVarargs* node) @@ -349,51 +379,71 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprCall* node) { - writeNode(node, "AstExprCall", [&]() { - PROP(func); - PROP(args); - PROP(self); - PROP(argLocation); - }); + writeNode( + node, + "AstExprCall", + [&]() + { + PROP(func); + PROP(args); + PROP(self); + PROP(argLocation); + } + ); } void write(class AstExprIndexName* node) { - writeNode(node, "AstExprIndexName", [&]() { - PROP(expr); - PROP(index); - PROP(indexLocation); - PROP(op); - }); + writeNode( + node, + "AstExprIndexName", + [&]() + { + PROP(expr); + PROP(index); + PROP(indexLocation); + PROP(op); + } + ); } void write(class AstExprIndexExpr* node) { - writeNode(node, "AstExprIndexExpr", [&]() { - PROP(expr); - PROP(index); - }); + writeNode( + node, + "AstExprIndexExpr", + [&]() + { + PROP(expr); + PROP(index); + } + ); } void write(class AstExprFunction* node) { - writeNode(node, "AstExprFunction", [&]() { - PROP(generics); - PROP(genericPacks); - if (node->self) - PROP(self); - PROP(args); - if (node->returnAnnotation) - PROP(returnAnnotation); - PROP(vararg); - PROP(varargLocation); - if (node->varargAnnotation) - PROP(varargAnnotation); + writeNode( + node, + "AstExprFunction", + [&]() + { + PROP(generics); + PROP(genericPacks); + if (node->self) + PROP(self); + PROP(args); + if (node->returnAnnotation) + PROP(returnAnnotation); + PROP(vararg); + PROP(varargLocation); + if (node->varargAnnotation) + PROP(varargAnnotation); - PROP(body); - PROP(functionDepth); - PROP(debugname); - }); + PROP(body); + PROP(functionDepth); + PROP(debugname); + } + ); } void write(const std::optional& typeList) @@ -475,28 +525,43 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprIfElse* node) { - writeNode(node, "AstExprIfElse", [&]() { - PROP(condition); - PROP(hasThen); - PROP(trueExpr); - PROP(hasElse); - PROP(falseExpr); - }); + writeNode( + node, + "AstExprIfElse", + [&]() + { + PROP(condition); + PROP(hasThen); + PROP(trueExpr); + PROP(hasElse); + PROP(falseExpr); + } + ); } void write(class AstExprInterpString* node) { - writeNode(node, "AstExprInterpString", [&]() { - PROP(strings); - PROP(expressions); - }); + writeNode( + node, + "AstExprInterpString", + [&]() + { + PROP(strings); + PROP(expressions); + } + ); } void write(class AstExprTable* node) { - writeNode(node, "AstExprTable", [&]() { - PROP(items); - }); + writeNode( + node, + "AstExprTable", + [&]() + { + PROP(items); + } + ); } void write(AstExprUnary::Op op) @@ -514,10 +579,15 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprUnary* node) { - writeNode(node, "AstExprUnary", [&]() { - PROP(op); - PROP(expr); - }); + writeNode( + node, + "AstExprUnary", + [&]() + { + PROP(op); + PROP(expr); + } + ); } void write(AstExprBinary::Op op) @@ -563,75 +633,110 @@ struct AstJsonEncoder : public AstVisitor void write(class AstExprBinary* node) { - writeNode(node, "AstExprBinary", [&]() { - PROP(op); - PROP(left); - PROP(right); - }); + writeNode( + node, + "AstExprBinary", + [&]() + { + PROP(op); + PROP(left); + PROP(right); + } + ); } void write(class AstExprTypeAssertion* node) { - writeNode(node, "AstExprTypeAssertion", [&]() { - PROP(expr); - PROP(annotation); - }); + writeNode( + node, + "AstExprTypeAssertion", + [&]() + { + PROP(expr); + PROP(annotation); + } + ); } void write(class AstExprError* node) { - writeNode(node, "AstExprError", [&]() { - PROP(expressions); - PROP(messageIndex); - }); + writeNode( + node, + "AstExprError", + [&]() + { + PROP(expressions); + PROP(messageIndex); + } + ); } void write(class AstStatBlock* node) { - writeNode(node, "AstStatBlock", [&]() { - writeRaw(",\"hasEnd\":"); - write(node->hasEnd); - writeRaw(",\"body\":["); - bool comma = false; - for (AstStat* stat : node->body) + writeNode( + node, + "AstStatBlock", + [&]() { - if (comma) - writeRaw(","); - else - comma = true; + writeRaw(",\"hasEnd\":"); + write(node->hasEnd); + writeRaw(",\"body\":["); + bool comma = false; + for (AstStat* stat : node->body) + { + if (comma) + writeRaw(","); + else + comma = true; - write(stat); + write(stat); + } + writeRaw("]"); } - writeRaw("]"); - }); + ); } void write(class AstStatIf* node) { - writeNode(node, "AstStatIf", [&]() { - PROP(condition); - PROP(thenbody); - if (node->elsebody) - PROP(elsebody); - write("hasThen", node->thenLocation.has_value()); - }); + writeNode( + node, + "AstStatIf", + [&]() + { + PROP(condition); + PROP(thenbody); + if (node->elsebody) + PROP(elsebody); + write("hasThen", node->thenLocation.has_value()); + } + ); } void write(class AstStatWhile* node) { - writeNode(node, "AstStatWhile", [&]() { - PROP(condition); - PROP(body); - PROP(hasDo); - }); + writeNode( + node, + "AstStatWhile", + [&]() + { + PROP(condition); + PROP(body); + PROP(hasDo); + } + ); } void write(class AstStatRepeat* node) { - writeNode(node, "AstStatRepeat", [&]() { - PROP(condition); - PROP(body); - }); + writeNode( + node, + "AstStatRepeat", + [&]() + { + PROP(condition); + PROP(body); + } + ); } void write(class AstStatBreak* node) @@ -646,128 +751,188 @@ struct AstJsonEncoder : public AstVisitor void write(class AstStatReturn* node) { - writeNode(node, "AstStatReturn", [&]() { - PROP(list); - }); + writeNode( + node, + "AstStatReturn", + [&]() + { + PROP(list); + } + ); } void write(class AstStatExpr* node) { - writeNode(node, "AstStatExpr", [&]() { - PROP(expr); - }); + writeNode( + node, + "AstStatExpr", + [&]() + { + PROP(expr); + } + ); } void write(class AstStatLocal* node) { - writeNode(node, "AstStatLocal", [&]() { - PROP(vars); - PROP(values); - }); + writeNode( + node, + "AstStatLocal", + [&]() + { + PROP(vars); + PROP(values); + } + ); } void write(class AstStatFor* node) { - writeNode(node, "AstStatFor", [&]() { - PROP(var); - PROP(from); - PROP(to); - if (node->step) - PROP(step); - PROP(body); - PROP(hasDo); - }); + writeNode( + node, + "AstStatFor", + [&]() + { + PROP(var); + PROP(from); + PROP(to); + if (node->step) + PROP(step); + PROP(body); + PROP(hasDo); + } + ); } void write(class AstStatForIn* node) { - writeNode(node, "AstStatForIn", [&]() { - PROP(vars); - PROP(values); - PROP(body); - PROP(hasIn); - PROP(hasDo); - }); + writeNode( + node, + "AstStatForIn", + [&]() + { + PROP(vars); + PROP(values); + PROP(body); + PROP(hasIn); + PROP(hasDo); + } + ); } void write(class AstStatAssign* node) { - writeNode(node, "AstStatAssign", [&]() { - PROP(vars); - PROP(values); - }); + writeNode( + node, + "AstStatAssign", + [&]() + { + PROP(vars); + PROP(values); + } + ); } void write(class AstStatCompoundAssign* node) { - writeNode(node, "AstStatCompoundAssign", [&]() { - PROP(op); - PROP(var); - PROP(value); - }); + writeNode( + node, + "AstStatCompoundAssign", + [&]() + { + PROP(op); + PROP(var); + PROP(value); + } + ); } void write(class AstStatFunction* node) { - writeNode(node, "AstStatFunction", [&]() { - PROP(name); - PROP(func); - }); + writeNode( + node, + "AstStatFunction", + [&]() + { + PROP(name); + PROP(func); + } + ); } void write(class AstStatLocalFunction* node) { - writeNode(node, "AstStatLocalFunction", [&]() { - PROP(name); - PROP(func); - }); + writeNode( + node, + "AstStatLocalFunction", + [&]() + { + PROP(name); + PROP(func); + } + ); } void write(class AstStatTypeAlias* node) { - writeNode(node, "AstStatTypeAlias", [&]() { - PROP(name); - PROP(generics); - PROP(genericPacks); - PROP(type); - PROP(exported); - }); + writeNode( + node, + "AstStatTypeAlias", + [&]() + { + PROP(name); + PROP(generics); + PROP(genericPacks); + PROP(type); + PROP(exported); + } + ); } void write(class AstStatDeclareFunction* node) { - writeNode(node, "AstStatDeclareFunction", [&]() { - // TODO: attributes - PROP(name); - - if (FFlag::LuauDeclarationExtraPropData) - PROP(nameLocation); - - PROP(params); - - if (FFlag::LuauDeclarationExtraPropData) + writeNode( + node, + "AstStatDeclareFunction", + [&]() { - PROP(paramNames); - PROP(vararg); - PROP(varargLocation); - } + // TODO: attributes + PROP(name); - PROP(retTypes); - PROP(generics); - PROP(genericPacks); - }); + if (FFlag::LuauDeclarationExtraPropData) + PROP(nameLocation); + + PROP(params); + + if (FFlag::LuauDeclarationExtraPropData) + { + PROP(paramNames); + PROP(vararg); + PROP(varargLocation); + } + + PROP(retTypes); + PROP(generics); + PROP(genericPacks); + } + ); } void write(class AstStatDeclareGlobal* node) { - writeNode(node, "AstStatDeclareGlobal", [&]() { - PROP(name); + writeNode( + node, + "AstStatDeclareGlobal", + [&]() + { + PROP(name); - if (FFlag::LuauDeclarationExtraPropData) - PROP(nameLocation); + if (FFlag::LuauDeclarationExtraPropData) + PROP(nameLocation); - PROP(type); - }); + PROP(type); + } + ); } void write(const AstDeclaredClassProp& prop) @@ -791,21 +956,31 @@ struct AstJsonEncoder : public AstVisitor void write(class AstStatDeclareClass* node) { - writeNode(node, "AstStatDeclareClass", [&]() { - PROP(name); - if (node->superName) - write("superName", *node->superName); - PROP(props); - PROP(indexer); - }); + writeNode( + node, + "AstStatDeclareClass", + [&]() + { + PROP(name); + if (node->superName) + write("superName", *node->superName); + PROP(props); + PROP(indexer); + } + ); } void write(class AstStatError* node) { - writeNode(node, "AstStatError", [&]() { - PROP(expressions); - PROP(statements); - }); + writeNode( + node, + "AstStatError", + [&]() + { + PROP(expressions); + PROP(statements); + } + ); } void write(struct AstTypeOrPack node) @@ -818,15 +993,20 @@ struct AstJsonEncoder : public AstVisitor void write(class AstTypeReference* node) { - writeNode(node, "AstTypeReference", [&]() { - if (node->prefix) - PROP(prefix); - if (node->prefixLocation) - write("prefixLocation", *node->prefixLocation); - PROP(name); - PROP(nameLocation); - PROP(parameters); - }); + writeNode( + node, + "AstTypeReference", + [&]() + { + if (node->prefix) + PROP(prefix); + if (node->prefixLocation) + write("prefixLocation", *node->prefixLocation); + PROP(name); + PROP(nameLocation); + PROP(parameters); + } + ); } void write(const AstTableProp& prop) @@ -845,10 +1025,15 @@ struct AstJsonEncoder : public AstVisitor void write(class AstTypeTable* node) { - writeNode(node, "AstTypeTable", [&]() { - PROP(props); - PROP(indexer); - }); + writeNode( + node, + "AstTypeTable", + [&]() + { + PROP(props); + PROP(indexer); + } + ); } void write(struct AstTableIndexer* indexer) @@ -871,78 +1056,128 @@ struct AstJsonEncoder : public AstVisitor void write(class AstTypeFunction* node) { - writeNode(node, "AstTypeFunction", [&]() { - PROP(generics); - PROP(genericPacks); - PROP(argTypes); - PROP(argNames); - PROP(returnTypes); - }); + writeNode( + node, + "AstTypeFunction", + [&]() + { + PROP(generics); + PROP(genericPacks); + PROP(argTypes); + PROP(argNames); + PROP(returnTypes); + } + ); } void write(class AstTypeTypeof* node) { - writeNode(node, "AstTypeTypeof", [&]() { - PROP(expr); - }); + writeNode( + node, + "AstTypeTypeof", + [&]() + { + PROP(expr); + } + ); } void write(class AstTypeUnion* node) { - writeNode(node, "AstTypeUnion", [&]() { - PROP(types); - }); + writeNode( + node, + "AstTypeUnion", + [&]() + { + PROP(types); + } + ); } void write(class AstTypeIntersection* node) { - writeNode(node, "AstTypeIntersection", [&]() { - PROP(types); - }); + writeNode( + node, + "AstTypeIntersection", + [&]() + { + PROP(types); + } + ); } void write(class AstTypeError* node) { - writeNode(node, "AstTypeError", [&]() { - PROP(types); - PROP(messageIndex); - }); + writeNode( + node, + "AstTypeError", + [&]() + { + PROP(types); + PROP(messageIndex); + } + ); } void write(class AstTypePackExplicit* node) { - writeNode(node, "AstTypePackExplicit", [&]() { - PROP(typeList); - }); + writeNode( + node, + "AstTypePackExplicit", + [&]() + { + PROP(typeList); + } + ); } void write(class AstTypePackVariadic* node) { - writeNode(node, "AstTypePackVariadic", [&]() { - PROP(variadicType); - }); + writeNode( + node, + "AstTypePackVariadic", + [&]() + { + PROP(variadicType); + } + ); } void write(class AstTypePackGeneric* node) { - writeNode(node, "AstTypePackGeneric", [&]() { - PROP(genericName); - }); + writeNode( + node, + "AstTypePackGeneric", + [&]() + { + PROP(genericName); + } + ); } bool visit(class AstTypeSingletonBool* node) override { - writeNode(node, "AstTypeSingletonBool", [&]() { - write("value", node->value); - }); + writeNode( + node, + "AstTypeSingletonBool", + [&]() + { + write("value", node->value); + } + ); return false; } bool visit(class AstTypeSingletonString* node) override { - writeNode(node, "AstTypeSingletonString", [&]() { - write("value", node->value); - }); + writeNode( + node, + "AstTypeSingletonString", + [&]() + { + write("value", node->value); + } + ); return false; } diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index be296f72..243834f8 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -331,9 +331,14 @@ static std::optional findBindingLocalStatement(const SourceModule return std::nullopt; std::vector nodes = findAstAncestryOfPosition(source, binding.location.begin); - auto iter = std::find_if(nodes.rbegin(), nodes.rend(), [](AstNode* node) { - return node->is(); - }); + auto iter = std::find_if( + nodes.rbegin(), + nodes.rend(), + [](AstNode* node) + { + return node->is(); + } + ); return iter != nodes.rend() ? std::make_optional((*iter)->as()) : std::nullopt; } @@ -472,7 +477,11 @@ ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos) } static std::optional checkOverloadedDocumentationSymbol( - const Module& module, const TypeId ty, const AstExpr* parentExpr, const std::optional documentationSymbol) + const Module& module, + const TypeId ty, + const AstExpr* parentExpr, + const std::optional documentationSymbol +) { if (!documentationSymbol) return std::nullopt; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 0dab640f..a4acfb85 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -15,8 +15,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -static const std::unordered_set kStatementStartingKeywords = { - "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; +static const std::unordered_set kStatementStartingKeywords = + {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; namespace Luau { @@ -161,7 +161,13 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull scope, T } static TypeCorrectKind checkTypeCorrectKind( - const Module& module, TypeArena* typeArena, NotNull builtinTypes, AstNode* node, Position position, TypeId ty) + const Module& module, + TypeArena* typeArena, + NotNull builtinTypes, + AstNode* node, + Position position, + TypeId ty +) { ty = follow(ty); @@ -176,7 +182,8 @@ static TypeCorrectKind checkTypeCorrectKind( TypeId expectedType = follow(*typeAtPosition); - auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv) { + auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv) + { if (std::optional firstRetTy = first(ftv->retTypes)) return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, builtinTypes); @@ -209,9 +216,18 @@ enum class PropIndexType Key, }; -static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull builtinTypes, TypeId rootTy, TypeId ty, - PropIndexType indexType, const std::vector& nodes, AutocompleteEntryMap& result, std::unordered_set& seen, - std::optional containingClass = std::nullopt) +static void autocompleteProps( + const Module& module, + TypeArena* typeArena, + NotNull builtinTypes, + TypeId rootTy, + TypeId ty, + PropIndexType indexType, + const std::vector& nodes, + AutocompleteEntryMap& result, + std::unordered_set& seen, + std::optional containingClass = std::nullopt +) { rootTy = follow(rootTy); ty = follow(ty); @@ -220,13 +236,15 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul return; seen.insert(ty); - auto isWrongIndexer = [typeArena, builtinTypes, &module, rootTy, indexType](Luau::TypeId type) { + auto isWrongIndexer = [typeArena, builtinTypes, &module, rootTy, indexType](Luau::TypeId type) + { if (indexType == PropIndexType::Key) return false; bool calledWithSelf = indexType == PropIndexType::Colon; - auto isCompatibleCall = [typeArena, builtinTypes, &module, rootTy, calledWithSelf](const FunctionType* ftv) { + auto isCompatibleCall = [typeArena, builtinTypes, &module, rootTy, calledWithSelf](const FunctionType* ftv) + { // Strong match with definition is a success if (calledWithSelf == ftv->hasSelf) return true; @@ -265,7 +283,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul return calledWithSelf; }; - auto fillProps = [&](const ClassType::Props& props) { + auto fillProps = [&](const ClassType::Props& props) + { for (const auto& [name, prop] : props) { // We are walking up the class hierarchy, so if we encounter a property that we have @@ -291,13 +310,26 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul ParenthesesRecommendation parens = indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); - result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect, - containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon}; + result[name] = AutocompleteEntry{ + AutocompleteEntryKind::Property, + type, + prop.deprecated, + isWrongIndexer(type), + typeCorrect, + containingClass, + &prop, + prop.documentationSymbol, + {}, + parens, + {}, + indexType == PropIndexType::Colon + }; } } }; - auto fillMetatableProps = [&](const TableType* mtable) { + auto fillMetatableProps = [&](const TableType* mtable) + { auto indexIt = mtable->props.find("__index"); if (indexIt != mtable->props.end()) { @@ -409,7 +441,11 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul } static void autocompleteKeywords( - const SourceModule& sourceModule, const std::vector& ancestry, Position position, AutocompleteEntryMap& result) + const SourceModule& sourceModule, + const std::vector& ancestry, + Position position, + AutocompleteEntryMap& result +) { LUAU_ASSERT(!ancestry.empty()); @@ -429,15 +465,28 @@ static void autocompleteKeywords( } } -static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull builtinTypes, TypeId ty, PropIndexType indexType, - const std::vector& nodes, AutocompleteEntryMap& result) +static void autocompleteProps( + const Module& module, + TypeArena* typeArena, + NotNull builtinTypes, + TypeId ty, + PropIndexType indexType, + const std::vector& nodes, + AutocompleteEntryMap& result +) { std::unordered_set seen; autocompleteProps(module, typeArena, builtinTypes, ty, ty, indexType, nodes, result, seen); } -AutocompleteEntryMap autocompleteProps(const Module& module, TypeArena* typeArena, NotNull builtinTypes, TypeId ty, - PropIndexType indexType, const std::vector& nodes) +AutocompleteEntryMap autocompleteProps( + const Module& module, + TypeArena* typeArena, + NotNull builtinTypes, + TypeId ty, + PropIndexType indexType, + const std::vector& nodes +) { AutocompleteEntryMap result; autocompleteProps(module, typeArena, builtinTypes, ty, indexType, nodes, result); @@ -472,7 +521,8 @@ static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AstNode* node return; } - auto formatKey = [addQuotes](const std::string& key) { + auto formatKey = [addQuotes](const std::string& key) + { if (addQuotes) return "\"" + escape(key) + "\""; @@ -705,9 +755,14 @@ static std::optional functionIsExpectedAt(const Module& module, AstNode* n if (const IntersectionType* itv = get(expectedType)) { - return std::all_of(begin(itv->parts), end(itv->parts), [](auto&& ty) { - return get(Luau::follow(ty)) != nullptr; - }); + return std::all_of( + begin(itv->parts), + end(itv->parts), + [](auto&& ty) + { + return get(Luau::follow(ty)) != nullptr; + } + ); } if (const UnionType* utv = get(expectedType)) @@ -727,15 +782,31 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi for (const auto& [name, ty] : scope->exportedTypeBindings) { if (!result.count(name)) - result[name] = AutocompleteEntry{AutocompleteEntryKind::Type, ty.type, false, false, TypeCorrectKind::None, std::nullopt, - std::nullopt, ty.type->documentationSymbol}; + result[name] = AutocompleteEntry{ + AutocompleteEntryKind::Type, + ty.type, + false, + false, + TypeCorrectKind::None, + std::nullopt, + std::nullopt, + ty.type->documentationSymbol + }; } for (const auto& [name, ty] : scope->privateTypeBindings) { if (!result.count(name)) - result[name] = AutocompleteEntry{AutocompleteEntryKind::Type, ty.type, false, false, TypeCorrectKind::None, std::nullopt, - std::nullopt, ty.type->documentationSymbol}; + result[name] = AutocompleteEntry{ + AutocompleteEntryKind::Type, + ty.type, + false, + false, + TypeCorrectKind::None, + std::nullopt, + std::nullopt, + ty.type->documentationSymbol + }; } for (const auto& [name, _] : scope->importedTypeBindings) @@ -825,7 +896,8 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi else if (AstExprFunction* node = parent->as()) { // For lookup inside expected function type if that's available - auto tryGetExpectedFunctionType = [](const Module& module, AstExpr* expr) -> const FunctionType* { + auto tryGetExpectedFunctionType = [](const Module& module, AstExpr* expr) -> const FunctionType* + { auto it = module.astExpectedTypes.find(expr); if (!it) @@ -1029,7 +1101,11 @@ static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& } static AutocompleteEntryMap autocompleteStatement( - const SourceModule& sourceModule, const Module& module, const std::vector& ancestry, Position position) + const SourceModule& sourceModule, + const Module& module, + const std::vector& ancestry, + Position position +) { // This is inefficient. :( ScopePtr scope = findScopeAtPosition(module, position); @@ -1051,8 +1127,18 @@ static AutocompleteEntryMap autocompleteStatement( std::string n = toString(name); if (!result.count(n)) - result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, TypeCorrectKind::None, std::nullopt, - std::nullopt, binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, TypeCorrectKind::None)}; + result[n] = { + AutocompleteEntryKind::Binding, + binding.typeId, + binding.deprecated, + false, + TypeCorrectKind::None, + std::nullopt, + std::nullopt, + binding.documentationSymbol, + {}, + getParenRecommendation(binding.typeId, ancestry, TypeCorrectKind::None) + }; } scope = scope->parent; @@ -1122,7 +1208,11 @@ static AutocompleteEntryMap autocompleteStatement( // Returns true iff `node` was handled by this function (completions, if any, are returned in `outResult`) static bool autocompleteIfElseExpression( - const AstNode* node, const std::vector& ancestry, const Position& position, AutocompleteEntryMap& outResult) + const AstNode* node, + const std::vector& ancestry, + const Position& position, + AutocompleteEntryMap& outResult +) { AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : nullptr; if (!parent) @@ -1161,8 +1251,15 @@ static bool autocompleteIfElseExpression( } } -static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull builtinTypes, - TypeArena* typeArena, const std::vector& ancestry, Position position, AutocompleteEntryMap& result) +static AutocompleteContext autocompleteExpression( + const SourceModule& sourceModule, + const Module& module, + NotNull builtinTypes, + TypeArena* typeArena, + const std::vector& ancestry, + Position position, + AutocompleteEntryMap& result +) { LUAU_ASSERT(!ancestry.empty()); @@ -1197,8 +1294,18 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu { TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, binding.typeId); - result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt, - binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)}; + result[n] = { + AutocompleteEntryKind::Binding, + binding.typeId, + binding.deprecated, + false, + typeCorrect, + std::nullopt, + std::nullopt, + binding.documentationSymbol, + {}, + getParenRecommendation(binding.typeId, ancestry, typeCorrect) + }; } } @@ -1225,8 +1332,14 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu return AutocompleteContext::Expression; } -static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull builtinTypes, - TypeArena* typeArena, const std::vector& ancestry, Position position) +static AutocompleteResult autocompleteExpression( + const SourceModule& sourceModule, + const Module& module, + NotNull builtinTypes, + TypeArena* typeArena, + const std::vector& ancestry, + Position position +) { AutocompleteEntryMap result; AutocompleteContext context = autocompleteExpression(sourceModule, module, builtinTypes, typeArena, ancestry, position, result); @@ -1312,8 +1425,13 @@ static std::optional getStringContents(const AstNode* node) } } -static std::optional autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module, - const std::vector& nodes, Position position, StringCompletionCallback callback) +static std::optional autocompleteStringParams( + const SourceModule& sourceModule, + const ModulePtr& module, + const std::vector& nodes, + Position position, + StringCompletionCallback callback +) { if (nodes.size() < 2) { @@ -1354,7 +1472,8 @@ static std::optional autocompleteStringParams(const Source std::optional candidateString = getStringContents(nodes.back()); - auto performCallback = [&](const FunctionType* funcType) -> std::optional { + auto performCallback = [&](const FunctionType* funcType) -> std::optional + { for (const std::string& tag : funcType->tags) { if (std::optional ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString)) @@ -1463,7 +1582,11 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func } static std::optional makeAnonymousAutofilled( - const ModulePtr& module, Position position, const AstNode* node, const std::vector& ancestry) + const ModulePtr& module, + Position position, + const AstNode* node, + const std::vector& ancestry +) { const AstExprCall* call = node->as(); if (!call && ancestry.size() > 1) @@ -1530,8 +1653,15 @@ static std::optional makeAnonymousAutofilled( return std::make_optional(std::move(entry)); } -static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull builtinTypes, - TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback) +static AutocompleteResult autocomplete( + const SourceModule& sourceModule, + const ModulePtr& module, + NotNull builtinTypes, + TypeArena* typeArena, + Scope* globalScope, + Position position, + StringCompletionCallback callback +) { if (isWithinComment(sourceModule, position)) return {}; @@ -1656,14 +1786,17 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M else if (AstStatWhile* statWhile = extractStat(ancestry); (statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) && statWhile->condition && - !statWhile->condition->location.containsClosed(position))) + !statWhile->condition->location.containsClosed(position))) { return autocompleteWhileLoopKeywords(ancestry); } else if (AstStatIf* statIf = node->as(); statIf && !statIf->elseLocation.has_value()) { - return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, - ancestry, AutocompleteContext::Keyword}; + return { + {{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, + ancestry, + AutocompleteContext::Keyword + }; } else if (AstStatIf* statIf = parent->as(); statIf && node->is()) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index e7782150..082d79e8 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -29,15 +29,35 @@ namespace Luau { static std::optional> magicFunctionSelect( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +); static std::optional> magicFunctionSetMetaTable( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +); static std::optional> magicFunctionAssert( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +); static std::optional> magicFunctionPack( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +); static std::optional> magicFunctionRequire( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +); static bool dcrMagicFunctionSelect(MagicFunctionCallContext context); @@ -61,26 +81,51 @@ TypeId makeOption(NotNull builtinTypes, TypeArena& arena, TypeId t } TypeId makeFunction( - TypeArena& arena, std::optional selfType, std::initializer_list paramTypes, std::initializer_list retTypes, bool checked) + TypeArena& arena, + std::optional selfType, + std::initializer_list paramTypes, + std::initializer_list retTypes, + bool checked +) { return makeFunction(arena, selfType, {}, {}, paramTypes, {}, retTypes, checked); } -TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initializer_list generics, - std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list retTypes, bool checked) +TypeId makeFunction( + TypeArena& arena, + std::optional selfType, + std::initializer_list generics, + std::initializer_list genericPacks, + std::initializer_list paramTypes, + std::initializer_list retTypes, + bool checked +) { return makeFunction(arena, selfType, generics, genericPacks, paramTypes, {}, retTypes, checked); } -TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initializer_list paramTypes, - std::initializer_list paramNames, std::initializer_list retTypes, bool checked) +TypeId makeFunction( + TypeArena& arena, + std::optional selfType, + std::initializer_list paramTypes, + std::initializer_list paramNames, + std::initializer_list retTypes, + bool checked +) { return makeFunction(arena, selfType, {}, {}, paramTypes, paramNames, retTypes, checked); } -TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initializer_list generics, - std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list paramNames, - std::initializer_list retTypes, bool checked) +TypeId makeFunction( + TypeArena& arena, + std::optional selfType, + std::initializer_list generics, + std::initializer_list genericPacks, + std::initializer_list paramTypes, + std::initializer_list paramNames, + std::initializer_list retTypes, + bool checked +) { std::vector params; if (selfType) @@ -219,7 +264,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile( - globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete); + globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete + ); LUAU_ASSERT(loadResult.success); TypeId genericK = arena.addType(GenericType{"K"}); @@ -313,10 +359,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC // declare function assert(value: T, errorMessage: string?): intersect TypeId genericT = arena.addType(GenericType{"T"}); TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ - NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}}); + NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} + }); TypeId assertTy = arena.addType(FunctionType{ - {genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}})}); + {genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}}) + }); addGlobalBinding(globals, "assert", assertTy, "@luau"); } @@ -380,7 +428,11 @@ static std::vector parseFormatString(NotNull builtinTypes, } std::optional> magicFunctionFormat( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; @@ -529,7 +581,11 @@ static std::vector parsePatternString(NotNull builtinTypes } static std::optional> magicFunctionGmatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); @@ -594,7 +650,11 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context) } static std::optional> magicFunctionMatch( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); @@ -666,7 +726,11 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context) } static std::optional> magicFunctionFind( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); @@ -804,9 +868,11 @@ TypeId makeStringMetatable(NotNull builtinTypes) const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true); - const TypeId replArgType = - arena->addType(UnionType{{stringType, arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), - makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}}); + const TypeId replArgType = arena->addType(UnionType{ + {stringType, + arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), + makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)} + }); const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false); const TypeId gmatchFunc = @@ -815,14 +881,17 @@ TypeId makeStringMetatable(NotNull builtinTypes) attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch); FunctionType matchFuncTy{ - arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}; + arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}) + }; matchFuncTy.isCheckedFunction = true; const TypeId matchFunc = arena->addType(matchFuncTy); attachMagicFunction(matchFunc, magicFunctionMatch); attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch); - FunctionType findFuncTy{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), - arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})}; + FunctionType findFuncTy{ + arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), + arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList}) + }; findFuncTy.isCheckedFunction = true; const TypeId findFunc = arena->addType(findFuncTy); attachMagicFunction(findFunc, magicFunctionFind); @@ -857,13 +926,22 @@ TypeId makeStringMetatable(NotNull builtinTypes) {"reverse", {stringToStringType}}, {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}}, {"upper", {stringToStringType}}, - {"split", {makeFunction(*arena, stringType, {}, {}, {optionalString}, {}, - {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})}, - /* checked */ true)}}, - {"pack", {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType}, variadicTailPack}), - oneStringPack, - })}}, + {"split", + {makeFunction( + *arena, + stringType, + {}, + {}, + {optionalString}, + {}, + {arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})}, + /* checked */ true + )}}, + {"pack", + {arena->addType(FunctionType{ + arena->addTypePack(TypePack{{stringType}, variadicTailPack}), + oneStringPack, + })}}, {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, {"unpack", {arena->addType(stringDotUnpack)}}, }; @@ -879,7 +957,11 @@ TypeId makeStringMetatable(NotNull builtinTypes) } static std::optional> magicFunctionSelect( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; @@ -965,7 +1047,11 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context) } static std::optional> magicFunctionSetMetaTable( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; @@ -1043,7 +1129,11 @@ static std::optional> magicFunctionSetMetaTable( } static std::optional> magicFunctionAssert( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, predicates] = withPredicate; @@ -1073,7 +1163,11 @@ static std::optional> magicFunctionAssert( } static std::optional> magicFunctionPack( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { auto [paramPack, _predicates] = withPredicate; @@ -1174,7 +1268,11 @@ static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr) } static std::optional> magicFunctionRequire( - TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typechecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { TypeArena& arena = typechecker.currentModule->internalTypes; diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 76bf9233..7446846a 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -227,19 +227,23 @@ private: void cloneChildren(TypeId ty) { return visit( - [&](auto&& t) { + [&](auto&& t) + { return cloneChildren(&t); }, - asMutable(ty)->ty); + asMutable(ty)->ty + ); } void cloneChildren(TypePackId tp) { return visit( - [&](auto&& t) { + [&](auto&& t) + { return cloneChildren(&t); }, - asMutable(tp)->ty); + asMutable(tp)->ty + ); } void cloneChildren(Kind kind) diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index d3e3f596..036f4313 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -189,10 +189,18 @@ bool hasFreeType(TypeId ty) } // namespace -ConstraintGenerator::ConstraintGenerator(ModulePtr module, NotNull normalizer, NotNull moduleResolver, - NotNull builtinTypes, NotNull ice, const ScopePtr& globalScope, - std::function prepareModuleScope, DcrLogger* logger, NotNull dfg, - std::vector requireCycles) +ConstraintGenerator::ConstraintGenerator( + ModulePtr module, + NotNull normalizer, + NotNull moduleResolver, + NotNull builtinTypes, + NotNull ice, + const ScopePtr& globalScope, + std::function prepareModuleScope, + DcrLogger* logger, + NotNull dfg, + std::vector requireCycles +) : module(module) , builtinTypes(builtinTypes) , arena(normalizer->arena) @@ -240,9 +248,15 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) NotNull genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); getMutable(result)->setOwner(genConstraint); - forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) { - genConstraint->dependencies.push_back(NotNull{c.get()}); - }); + forEachConstraint( + start, + end, + this, + [genConstraint](const ConstraintPtr& c) + { + genConstraint->dependencies.push_back(NotNull{c.get()}); + } + ); interiorTypes.pop_back(); @@ -354,10 +368,17 @@ NotNull ConstraintGenerator::addConstraint(const ScopePtr& scope, st return NotNull{constraints.emplace_back(std::move(c)).get()}; } -void ConstraintGenerator::unionRefinements(const ScopePtr& scope, Location location, const RefinementContext& lhs, const RefinementContext& rhs, - RefinementContext& dest, std::vector* constraints) +void ConstraintGenerator::unionRefinements( + const ScopePtr& scope, + Location location, + const RefinementContext& lhs, + const RefinementContext& rhs, + RefinementContext& dest, + std::vector* constraints +) { - const auto intersect = [&](const std::vector& types) { + const auto intersect = [&](const std::vector& types) + { if (1 == types.size()) return types[0]; else if (2 == types.size()) @@ -386,8 +407,15 @@ void ConstraintGenerator::unionRefinements(const ScopePtr& scope, Location locat } } -void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location location, RefinementId refinement, RefinementContext* refis, bool sense, - bool eq, std::vector* constraints) +void ConstraintGenerator::computeRefinement( + const ScopePtr& scope, + Location location, + RefinementId refinement, + RefinementContext* refis, + bool sense, + bool eq, + std::vector* constraints +) { if (!refinement) return; @@ -555,8 +583,11 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat switch (shouldSuppressErrors(normalizer, ty)) { case ErrorSuppression::DoNotSuppress: - ty = makeIntersect(scope, location, ty, dt); + { + if (!get(follow(ty))) + ty = makeIntersect(scope, location, ty, dt); break; + } case ErrorSuppression::Suppress: ty = makeIntersect(scope, location, ty, dt); ty = makeUnion(scope, location, ty, builtinTypes->errorType); @@ -688,6 +719,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) return visit(scope, f); else if (auto a = stat->as()) return visit(scope, a); + else if (auto f = stat->as()) + return visit(scope, f); else if (auto s = stat->as()) return visit(scope, s); else if (auto s = stat->as()) @@ -792,11 +825,15 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack}); - forEachConstraint(start, end, this, + forEachConstraint( + start, + end, + this, [&uc](const ConstraintPtr& runBefore) { uc->dependencies.push_back(NotNull{runBefore.get()}); - }); + } + ); for (TypeId t : valueTypes) getMutable(t)->setOwner(uc); @@ -875,7 +912,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_) if (for_->var->annotation) annotationTy = resolveType(scope, for_->var->annotation, /* inTypeArguments */ false); - auto inferNumber = [&](AstExpr* expr) { + auto inferNumber = [&](AstExpr* expr) + { if (!expr) return; @@ -929,7 +967,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI } auto iterable = addConstraint( - loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes}); + loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes} + ); for (TypeId var : variableTypes) { @@ -943,9 +982,15 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI Checkpoint end = checkpoint(this); // This iter constraint must dispatch first. - forEachConstraint(start, end, this, [&iterable](const ConstraintPtr& runLater) { - runLater->dependencies.push_back(iterable); - }); + forEachConstraint( + start, + end, + this, + [&iterable](const ConstraintPtr& runLater) + { + runLater->dependencies.push_back(iterable); + } + ); return ControlFlow::None; } @@ -1011,17 +1056,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); Constraint* previous = nullptr; - forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) { - c->dependencies.push_back(NotNull{constraint.get()}); - - if (auto psc = get(*constraint); psc && psc->returns) + forEachConstraint( + start, + end, + this, + [&c, &previous](const ConstraintPtr& constraint) { - if (previous) - constraint->dependencies.push_back(NotNull{previous}); + c->dependencies.push_back(NotNull{constraint.get()}); - previous = constraint.get(); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } } - }); + ); getMutable(functionType)->setOwner(addConstraint(scope, std::move(c))); module->astTypes[function->func] = functionType; @@ -1055,17 +1106,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f getMutable(generalizedType)->setOwner(c); Constraint* previous = nullptr; - forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) { - c->dependencies.push_back(NotNull{constraint.get()}); - - if (auto psc = get(*constraint); psc && psc->returns) + forEachConstraint( + start, + end, + this, + [&c, &previous](const ConstraintPtr& constraint) { - if (previous) - constraint->dependencies.push_back(NotNull{previous}); + c->dependencies.push_back(NotNull{constraint.get()}); - previous = constraint.get(); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } } - }); + ); } DefId def = dfg->getDef(function->name); @@ -1211,7 +1268,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement) { - RefinementId refinement = [&]() { + RefinementId refinement = [&]() + { InConditionalContext flipper{&typeContext}; return check(scope, ifStatement->condition, std::nullopt).refinement; }(); @@ -1293,18 +1351,26 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* for (auto tpParam : createGenericPacks(*defnScope, alias->genericPacks, /* useCache */ true, /* addTypes */ false)) typePackParams.push_back(tpParam.second.tp); - addConstraint(scope, alias->type->location, + addConstraint( + scope, + alias->type->location, NameConstraint{ ty, alias->name.value, /*synthetic=*/false, std::move(typeParams), std::move(typePackParams), - }); + } + ); return ControlFlow::None; } +ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function) +{ + return ControlFlow::None; +} + ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) { LUAU_ASSERT(global->type); @@ -1350,8 +1416,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas if (!get(follow(*superTy))) { - reportError(declaredClass->location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)}); + reportError( + declaredClass->location, + GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)} + ); return ControlFlow::None; } @@ -1579,7 +1647,11 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstArray>& expectedTypes, bool generalize) + const ScopePtr& scope, + AstExpr* expr, + const std::vector>& expectedTypes, + bool generalize +) { RecursionCounter counter{&recursionCount}; @@ -1661,7 +1733,6 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* std::vector> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType); - module->astOriginalCallTypes[call->func] = fnType; module->astOriginalCallTypes[call] = fnType; Checkpoint argBeginCheckpoint = checkpoint(this); @@ -1796,14 +1867,25 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* * 4. Solve the call */ - NotNull checkConstraint = addConstraint(scope, call->func->location, - FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}}); + NotNull checkConstraint = addConstraint( + scope, + call->func->location, + FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}} + ); - forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) { - checkConstraint->dependencies.emplace_back(constraint.get()); - }); + forEachConstraint( + funcBeginCheckpoint, + funcEndCheckpoint, + this, + [checkConstraint](const ConstraintPtr& constraint) + { + checkConstraint->dependencies.emplace_back(constraint.get()); + } + ); - NotNull callConstraint = addConstraint(scope, call->func->location, + NotNull callConstraint = addConstraint( + scope, + call->func->location, FunctionCallConstraint{ fnType, argPack, @@ -1811,17 +1893,24 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* call, std::move(discriminantTypes), &module->astOverloadResolvedTypes, - }); + } + ); getMutable(rets)->owner = callConstraint.get(); callConstraint->dependencies.push_back(checkConstraint); - forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [checkConstraint, callConstraint](const ConstraintPtr& constraint) { - constraint->dependencies.emplace_back(checkConstraint); + forEachConstraint( + argBeginCheckpoint, + argEndCheckpoint, + this, + [checkConstraint, callConstraint](const ConstraintPtr& constraint) + { + constraint->dependencies.emplace_back(checkConstraint); - callConstraint->dependencies.emplace_back(constraint.get()); - }); + callConstraint->dependencies.emplace_back(constraint.get()); + } + ); return InferencePack{rets, {refinementArena.variadic(returnRefinements)}}; } @@ -1974,7 +2063,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa } Inference ConstraintGenerator::checkIndexName( - const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation) + const ScopePtr& scope, + const RefinementKey* key, + AstExpr* indexee, + const std::string& index, + Location indexLocation +) { TypeId obj = check(scope, indexee).ty; TypeId result = nullptr; @@ -2005,7 +2099,8 @@ Inference ConstraintGenerator::checkIndexName( result = arena->addType(BlockedType{}); auto c = addConstraint( - scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)}); + scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)} + ); getMutable(result)->setOwner(c); } @@ -2076,17 +2171,23 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun interiorTypes.pop_back(); Constraint* previous = nullptr; - forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) { - gc->dependencies.emplace_back(constraint.get()); - - if (auto psc = get(*constraint); psc && psc->returns) + forEachConstraint( + startCheckpoint, + endCheckpoint, + this, + [gc, &previous](const ConstraintPtr& constraint) { - if (previous) - constraint->dependencies.push_back(NotNull{previous}); + gc->dependencies.emplace_back(constraint.get()); - previous = constraint.get(); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } } - }); + ); if (generalize && hasFreeType(sig.signature)) { @@ -2187,9 +2288,13 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar } case AstExprBinary::Op::CompareGe: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, + TypeId resultType = createTypeFunctionInstance( + builtinTypeFunctions().ltFunc, {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` - {}, scope, binary->location); + {}, + scope, + binary->location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLe: @@ -2200,9 +2305,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar case AstExprBinary::Op::CompareGt: { TypeId resultType = createTypeFunctionInstance( -builtinTypeFunctions().leFunc, + builtinTypeFunctions().leFunc, {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` - {}, scope, binary->location); + {}, + scope, + binary->location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareEq: @@ -2234,7 +2342,8 @@ builtinTypeFunctions().leFunc, Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional expectedType) { - RefinementId refinement = [&]() { + RefinementId refinement = [&]() + { InConditionalContext flipper{&typeContext}; ScopePtr condScope = childScope(ifElse->condition, scope); return check(condScope, ifElse->condition).refinement; @@ -2266,7 +2375,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprInterpString* } std::tuple ConstraintGenerator::checkBinary( - const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType) + const ScopePtr& scope, + AstExprBinary* binary, + std::optional expectedType +) { if (binary->op == AstExprBinary::And) { @@ -2460,7 +2572,8 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexName* e bool incremented = recordPropertyAssignment(lhsTy); - auto apc = addConstraint(scope, expr->location, AssignPropConstraint{lhsTy, expr->index.value, rhsType, expr->indexLocation, propTy, incremented}); + auto apc = + addConstraint(scope, expr->location, AssignPropConstraint{lhsTy, expr->index.value, rhsType, expr->indexLocation, propTy, incremented}); getMutable(propTy)->setOwner(apc); } @@ -2476,7 +2589,9 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexExpr* e bool incremented = recordPropertyAssignment(lhsTy); - auto apc = addConstraint(scope, expr->location, AssignPropConstraint{lhsTy, std::move(propName), rhsType, expr->index->location, propTy, incremented}); + auto apc = addConstraint( + scope, expr->location, AssignPropConstraint{lhsTy, std::move(propName), rhsType, expr->index->location, propTy, incremented} + ); getMutable(propTy)->setOwner(apc); return; @@ -2505,7 +2620,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, TypeIds indexKeyLowerBound; TypeIds indexValueLowerBound; - auto createIndexer = [&indexKeyLowerBound, &indexValueLowerBound](const Location& location, TypeId currentIndexType, TypeId currentResultType) { + auto createIndexer = [&indexKeyLowerBound, &indexValueLowerBound](const Location& location, TypeId currentIndexType, TypeId currentResultType) + { indexKeyLowerBound.insert(follow(currentIndexType)); indexValueLowerBound.insert(follow(currentResultType)); }; @@ -2565,14 +2681,19 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice}; std::vector toBlock; matchLiteralType( - NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}, builtinTypes, arena, NotNull{&unifier}, *expectedType, ty, expr, toBlock); + NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}, builtinTypes, arena, NotNull{&unifier}, *expectedType, ty, expr, toBlock + ); } return Inference{ty}; } ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignature( - const ScopePtr& parent, AstExprFunction* fn, std::optional expectedType, std::optional originalName) + const ScopePtr& parent, + AstExprFunction* fn, + std::optional expectedType, + std::optional originalName +) { ScopePtr signatureScope = nullptr; ScopePtr bodyScope = nullptr; @@ -3076,7 +3197,11 @@ TypePackId ConstraintGenerator::resolveTypePack(const ScopePtr& scope, const Ast } std::vector> ConstraintGenerator::createGenerics( - const ScopePtr& scope, AstArray generics, bool useCache, bool addTypes) + const ScopePtr& scope, + AstArray generics, + bool useCache, + bool addTypes +) { std::vector> result; for (const auto& generic : generics) @@ -3106,7 +3231,11 @@ std::vector> ConstraintGenerator::createG } std::vector> ConstraintGenerator::createGenericPacks( - const ScopePtr& scope, AstArray generics, bool useCache, bool addTypes) + const ScopePtr& scope, + AstArray generics, + bool useCache, + bool addTypes +) { std::vector> result; for (const auto& generic : generics) @@ -3323,7 +3452,8 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF // For a list of functions f_0 : e_0 -> r_0, ... f_n : e_n -> r_n, // emit a list of arguments that the function could take at each position // by unioning the arguments at each place - auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) { + auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) + { if (index == expectedTypes.size()) expectedTypes.push_back(ty); else if (ty) @@ -3372,7 +3502,12 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF } TypeId ConstraintGenerator::createTypeFunctionInstance( - const TypeFunction& function, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location) + const TypeFunction& function, + std::vector typeArguments, + std::vector packArguments, + const ScopePtr& scope, + Location location +) { TypeId result = arena->addTypeFunction(function, typeArguments, packArguments); addConstraint(scope, location, ReduceConstraint{result}); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index cba198e6..9cc8ef38 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -89,8 +89,13 @@ size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const return true; } -static std::pair, std::vector> saturateArguments(TypeArena* arena, NotNull builtinTypes, - const TypeFun& fn, const std::vector& rawTypeArguments, const std::vector& rawPackArguments) +static std::pair, std::vector> saturateArguments( + TypeArena* arena, + NotNull builtinTypes, + const TypeFun& fn, + const std::vector& rawTypeArguments, + const std::vector& rawPackArguments +) { std::vector saturatedTypeArguments; std::vector extraTypes; @@ -310,8 +315,16 @@ struct InstantiationQueuer : TypeOnceVisitor } }; -ConstraintSolver::ConstraintSolver(NotNull normalizer, NotNull rootScope, std::vector> constraints, - ModuleName moduleName, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, TypeCheckLimits limits) +ConstraintSolver::ConstraintSolver( + NotNull normalizer, + NotNull rootScope, + std::vector> constraints, + ModuleName moduleName, + NotNull moduleResolver, + std::vector requireCycles, + DcrLogger* logger, + TypeCheckLimits limits +) : arena(normalizer->arena) , builtinTypes(normalizer->builtinTypes) , normalizer(normalizer) @@ -374,7 +387,8 @@ void ConstraintSolver::run() if (FFlag::DebugLuauLogSolver) { printf( - "Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str()); + "Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str() + ); dump(this, opts); printf("Bindings:\n"); dumpBindings(rootScope, opts); @@ -385,7 +399,8 @@ void ConstraintSolver::run() logger->captureInitialSolverState(rootScope, unsolvedConstraints); } - auto runSolverPass = [&](bool force) { + auto runSolverPass = [&](bool force) + { bool progress = false; size_t i = 0; @@ -489,7 +504,7 @@ void ConstraintSolver::run() } while (progress); if (!unsolvedConstraints.empty()) - reportError(InternalError{"Type inference failed to complete, you may see some confusing types and type errors."}, Location{}); + reportError(ConstraintSolvingIncompleteError{}, Location{}); // After we have run all the constraints, type functions should be generalized // At this point, we can try to perform one final simplification to suss out @@ -730,7 +745,8 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull(c.target)); shiftReferences(c.target, result); bind(constraint, c.target, result); @@ -929,14 +946,27 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul auto [typeArguments, packArguments] = saturateArguments(arena, builtinTypes, *tf, petv->typeArguments, petv->packArguments); - bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) { - return itp == p.ty; - }); + bool sameTypes = std::equal( + typeArguments.begin(), + typeArguments.end(), + tf->typeParams.begin(), + tf->typeParams.end(), + [](auto&& itp, auto&& p) + { + return itp == p.ty; + } + ); - bool samePacks = - std::equal(packArguments.begin(), packArguments.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itp, auto&& p) { + bool samePacks = std::equal( + packArguments.begin(), + packArguments.end(), + tf->typePackParams.begin(), + tf->typePackParams.end(), + [](auto&& itp, auto&& p) + { return itp == p.tp; - }); + } + ); // If we're instantiating the type with its generic saturatedTypeArguments we are // performing the identity substitution. We can just short-circuit and bind @@ -1023,9 +1053,14 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul //clang-format off bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target)) || - std::any_of(typeArguments.begin(), typeArguments.end(), [&](const auto& other) { - return other == target; - }); + std::any_of( + typeArguments.begin(), + typeArguments.end(), + [&](const auto& other) + { + return other == target; + } + ); //clang-format on // Only tables have the properties we're trying to set. @@ -1120,7 +1155,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull std::optional { + auto collapse = [](const auto* t) -> std::optional + { auto it = begin(t); auto endIt = end(t); @@ -1145,6 +1181,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull callMm = findMetatableEntry(builtinTypes, errors, fn, "__call", constraint->location)) { + if (isBlocked(*callMm)) + return block(*callMm, constraint); + argsHead.insert(argsHead.begin(), fn); if (argsTail && isBlocked(*argsTail)) @@ -1195,7 +1234,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, NotNull{&iceReporter}, NotNull{&limits}, constraint->location}; + builtinTypes, NotNull{arena}, normalizer, constraint->scope, NotNull{&iceReporter}, NotNull{&limits}, constraint->location + }; auto [status, overload] = resolver.selectOverload(fn, argsPack); TypeId overloadToUse = fn; if (status == OverloadResolver::Analysis::Ok) @@ -1334,8 +1374,7 @@ 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); @@ -1421,7 +1460,13 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen) + int& recursionDepth, + NotNull constraint, + TypeId subjectType, + TypeId indexType, + TypeId resultType, + Set& seen +) { RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit}; @@ -1455,7 +1500,8 @@ bool ConstraintSolver::tryDispatchHasIndexer( FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType}; emplace(constraint, resultType, freeResult); - TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, TableState::Unsealed}); + TypeId upperBound = + arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); unify(constraint, subjectType, upperBound); @@ -1777,7 +1823,8 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull std::optional { + auto tableStuff = [&](TableType* lhsTable) -> std::optional + { if (lhsTable->indexer) { unify(constraint, indexType, lhsTable->indexer->indexType); @@ -2074,7 +2121,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl return true; } - auto unpack = [&](TypeId ty) { + auto unpack = [&](TypeId ty) + { for (TypeId varTy : c.variables) { LUAU_ASSERT(get(varTy)); @@ -2200,7 +2248,12 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl } bool ConstraintSolver::tryDispatchIterableFunction( - TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull constraint, bool force) + TypeId nextTy, + TypeId tableTy, + const IterableConstraint& c, + NotNull constraint, + bool force +) { const FunctionType* nextFn = get(nextTy); // If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place. @@ -2237,7 +2290,10 @@ bool ConstraintSolver::tryDispatchIterableFunction( } NotNull ConstraintSolver::unpackAndAssign( - const std::vector destTypes, TypePackId srcTypes, NotNull constraint) + const std::vector destTypes, + TypePackId srcTypes, + NotNull constraint +) { auto c = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{destTypes, srcTypes}); @@ -2251,15 +2307,28 @@ NotNull ConstraintSolver::unpackAndAssign( return c; } -std::pair, std::optional> ConstraintSolver::lookupTableProp(NotNull constraint, TypeId subjectType, - const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification) +std::pair, std::optional> ConstraintSolver::lookupTableProp( + NotNull constraint, + TypeId subjectType, + const std::string& propName, + ValueContext context, + bool inConditional, + bool suppressSimplification +) { DenseHashSet seen{nullptr}; return lookupTableProp(constraint, subjectType, propName, context, inConditional, suppressSimplification, seen); } -std::pair, std::optional> ConstraintSolver::lookupTableProp(NotNull constraint, TypeId subjectType, - const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet& seen) +std::pair, std::optional> ConstraintSolver::lookupTableProp( + NotNull constraint, + TypeId subjectType, + const std::string& propName, + ValueContext context, + bool inConditional, + bool suppressSimplification, + DenseHashSet& seen +) { if (seen.contains(subjectType)) return {}; diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 708001cd..57e45c3e 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -204,7 +204,8 @@ void DataFlowGraphBuilder::joinBindings(DfgScope* p, const DfgScope& a, const Df void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const DfgScope& b) { - auto phinodify = [this](DfgScope* scope, const auto& a, const auto& b, DefId parent) mutable { + auto phinodify = [this](DfgScope* scope, const auto& a, const auto& b, DefId parent) mutable + { auto& p = scope->props[parent]; for (const auto& [k, defA] : a) { @@ -373,6 +374,8 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStat* s) return visit(scope, l); else if (auto t = s->as()) return visit(scope, t); + else if (auto f = s->as()) + return visit(scope, f); else if (auto d = s->as()) return visit(scope, d); else if (auto d = s->as()) @@ -631,6 +634,14 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatTypeAlias* t) return ControlFlow::None; } +ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatTypeFunction* f) +{ + DfgScope* unreachable = childScope(scope); + visitExpr(unreachable, f->body); + + return ControlFlow::None; +} + ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareGlobal* d) { DefId def = defArena->freshCell(); @@ -691,7 +702,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExpr* e) return {NotNull{*def}, key ? *key : nullptr}; } - auto go = [&]() -> DataFlowResult { + auto go = [&]() -> DataFlowResult + { if (auto g = e->as()) return visitExpr(scope, g); else if (auto c = e->as()) @@ -910,7 +922,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* er void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment) { - auto go = [&]() { + auto go = [&]() + { if (auto l = e->as()) return visitLValue(scope, l, incomingDef, isCompoundAssignment); else if (auto g = e->as()) diff --git a/Analysis/src/DcrLogger.cpp b/Analysis/src/DcrLogger.cpp index 9f66b022..f013b985 100644 --- a/Analysis/src/DcrLogger.cpp +++ b/Analysis/src/DcrLogger.cpp @@ -124,7 +124,8 @@ void write(JsonEmitter& emitter, const ConstraintBlock& block) ObjectEmitter o = emitter.writeObject(); o.writePair("stringification", block.stringification); - auto go = [&o](auto&& t) { + auto go = [&o](auto&& t) + { using T = std::decay_t; o.writePair("id", toPointerId(t)); @@ -350,8 +351,12 @@ void DcrLogger::popBlock(NotNull block) } } -static void snapshotTypeStrings(const std::vector& interestedExprs, - const std::vector& interestedAnnots, DenseHashMap& map, ToStringOptions& opts) +static void snapshotTypeStrings( + const std::vector& interestedExprs, + const std::vector& interestedAnnots, + DenseHashMap& map, + ToStringOptions& opts +) { for (const ExprTypesAtLocation& tys : interestedExprs) { @@ -368,7 +373,10 @@ static void snapshotTypeStrings(const std::vector& interest } void DcrLogger::captureBoundaryState( - BoundarySnapshot& target, const Scope* rootScope, const std::vector>& unsolvedConstraints) + BoundarySnapshot& target, + const Scope* rootScope, + const std::vector>& unsolvedConstraints +) { target.rootScope = snapshotScope(rootScope, opts); target.unsolvedConstraints.clear(); @@ -391,7 +399,11 @@ void DcrLogger::captureInitialSolverState(const Scope* rootScope, const std::vec } StepSnapshot DcrLogger::prepareStepSnapshot( - const Scope* rootScope, NotNull current, bool force, const std::vector>& unsolvedConstraints) + const Scope* rootScope, + NotNull current, + bool force, + const std::vector>& unsolvedConstraints +) { ScopeSnapshot scopeSnapshot = snapshotScope(rootScope, opts); DenseHashMap constraints{nullptr}; diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index 5b614cef..25687e11 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -286,15 +286,22 @@ struct FindSeteqCounterexampleResult bool inLeft; }; static FindSeteqCounterexampleResult findSeteqCounterexample( - DifferEnvironment& env, const std::vector& left, const std::vector& right); + DifferEnvironment& env, + const std::vector& left, + const std::vector& right +); static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId right); /** * The last argument gives context info on which complex type contained the TypePack. */ static DifferResult diffTpi(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right); -static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, - const std::pair, std::optional>& left, const std::pair, std::optional>& right); +static DifferResult diffCanonicalTpShape( + DifferEnvironment& env, + DiffError::Kind possibleNonNormalErrorKind, + const std::pair, std::optional>& left, + const std::pair, std::optional>& right +); static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, TypePackId left, TypePackId right); static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypePackId right); @@ -324,8 +331,13 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) if (leftTable->props.find(field) == leftTable->props.end()) { // right has a field the left doesn't - return DifferResult{DiffError{DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::nullopts(), - DiffPathNodeLeaf::detailsTableProperty(value.type(), field), env.getDevFixFriendlyNameLeft(), env.getDevFixFriendlyNameRight()}}; + return DifferResult{DiffError{ + DiffError::Kind::MissingTableProperty, + DiffPathNodeLeaf::nullopts(), + DiffPathNodeLeaf::detailsTableProperty(value.type(), field), + env.getDevFixFriendlyNameLeft(), + env.getDevFixFriendlyNameRight() + }}; } } // left and right have the same set of keys @@ -491,7 +503,10 @@ static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right) } static FindSeteqCounterexampleResult findSeteqCounterexample( - DifferEnvironment& env, const std::vector& left, const std::vector& right) + DifferEnvironment& env, + const std::vector& left, + const std::vector& right +) { std::unordered_set unmatchedRightIdxes; for (size_t i = 0; i < right.size(); i++) @@ -760,8 +775,12 @@ static DifferResult diffTpi(DifferEnvironment& env, DiffError::Kind possibleNonN return diffHandleFlattenedTail(env, possibleNonNormalErrorKind, *leftFlatTpi.second, *rightFlatTpi.second); } -static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind possibleNonNormalErrorKind, - const std::pair, std::optional>& left, const std::pair, std::optional>& right) +static DifferResult diffCanonicalTpShape( + DifferEnvironment& env, + DiffError::Kind possibleNonNormalErrorKind, + const std::pair, std::optional>& left, + const std::pair, std::optional>& right +) { if (left.first.size() == right.first.size() && left.second.has_value() == right.second.has_value()) return DifferResult{}; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index f69efe80..60058d99 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -21,7 +21,12 @@ LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false) static std::string wrongNumberOfArgsString( - size_t expectedCount, std::optional maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) + size_t expectedCount, + std::optional maximumCount, + size_t actualCount, + const char* argPrefix = nullptr, + bool isVariadic = false +) { std::string s = "expects "; @@ -65,8 +70,21 @@ namespace Luau { // this list of binary operator type functions is used for better stringification of type functions errors -static const std::unordered_map kBinaryOps{{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, - {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"}, {"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}}; +static const std::unordered_map kBinaryOps{ + {"add", "+"}, + {"sub", "-"}, + {"mul", "*"}, + {"div", "/"}, + {"idiv", "//"}, + {"pow", "^"}, + {"mod", "%"}, + {"concat", ".."}, + {"and", "and"}, + {"or", "or"}, + {"lt", "< or >="}, + {"le", "<= or >"}, + {"eq", "== or ~="} +}; // this list of unary operator type functions is used for better stringification of type functions errors static const std::unordered_map kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}}; @@ -86,12 +104,15 @@ struct ErrorConverter std::string result; - auto quote = [&](std::string s) { + auto quote = [&](std::string s) + { return "'" + s + "'"; }; - auto constructErrorMessage = [&](std::string givenType, std::string wantedType, std::optional givenModule, - std::optional wantedModule) -> std::string { + auto constructErrorMessage = + [&](std::string givenType, std::string wantedType, std::optional givenModule, std::optional wantedModule + ) -> std::string + { std::string given = givenModule ? quote(givenType) + " from " + quote(*givenModule) : quote(givenType); std::string wanted = wantedModule ? quote(wantedType) + " from " + quote(*wantedModule) : quote(wantedType); size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); @@ -351,6 +372,11 @@ struct ErrorConverter return e.message; } + std::string operator()(const Luau::ConstraintSolvingIncompleteError& e) const + { + return "Type inference failed to complete, you may see some confusing types and type errors."; + } + std::optional findCallMetamethod(TypeId type) const { type = follow(type); @@ -987,6 +1013,11 @@ bool InternalError::operator==(const InternalError& rhs) const return message == rhs.message; } +bool ConstraintSolvingIncompleteError::operator==(const ConstraintSolvingIncompleteError& rhs) const +{ + return true; +} + bool CannotCallNonFunction::operator==(const CannotCallNonFunction& rhs) const { return ty == rhs.ty; @@ -1177,11 +1208,13 @@ bool containsParseErrorName(const TypeError& error) template void copyError(T& e, TypeArena& destArena, CloneState& cloneState) { - auto clone = [&](auto&& ty) { + auto clone = [&](auto&& ty) + { return ::Luau::clone(ty, destArena, cloneState); }; - auto visitErrorData = [&](auto&& e) { + auto visitErrorData = [&](auto&& e) + { copyError(e, destArena, cloneState); }; @@ -1256,6 +1289,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) else if constexpr (std::is_same_v) { } + else if constexpr (std::is_same_v) + { + } else if constexpr (std::is_same_v) { e.ty = clone(e.ty); @@ -1363,7 +1399,8 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena, NotNull bu { CloneState cloneState{builtinTypes}; - auto visitErrorData = [&](auto&& e) { + auto visitErrorData = [&](auto&& e) + { copyError(e, destArena, cloneState); }; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 810a1786..a8ae99d5 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -176,8 +176,14 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S } } -LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source, - const std::string& packageName, bool captureComments, bool typeCheckForAutocomplete) +LoadDefinitionFileResult Frontend::loadDefinitionFile( + GlobalTypes& globals, + ScopePtr targetScope, + std::string_view source, + const std::string& packageName, + bool captureComments, + bool typeCheckForAutocomplete +) { LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend"); @@ -269,7 +275,10 @@ namespace { static ErrorVec accumulateErrors( - const std::unordered_map>& sourceNodes, ModuleResolver& moduleResolver, const ModuleName& name) + const std::unordered_map>& sourceNodes, + ModuleResolver& moduleResolver, + const ModuleName& name +) { DenseHashSet seen{{}}; std::vector queue{name}; @@ -301,9 +310,14 @@ static ErrorVec accumulateErrors( Module& module = *modulePtr; - std::sort(module.errors.begin(), module.errors.end(), [](const TypeError& e1, const TypeError& e2) -> bool { - return e1.location.begin > e2.location.begin; - }); + std::sort( + module.errors.begin(), + module.errors.end(), + [](const TypeError& e1, const TypeError& e2) -> bool + { + return e1.location.begin > e2.location.begin; + } + ); result.insert(result.end(), module.errors.begin(), module.errors.end()); } @@ -334,8 +348,12 @@ static void filterLintOptions(LintOptions& lintOptions, const std::vector getRequireCycles(const FileResolver* resolver, - const std::unordered_map>& sourceNodes, const SourceNode* start, bool stopAtFirst = false) +std::vector getRequireCycles( + const FileResolver* resolver, + const std::unordered_map>& sourceNodes, + const SourceNode* start, + bool stopAtFirst = false +) { std::vector result; @@ -503,7 +521,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalats.root = toString(sourceModule.root); } - + item.module->ats.rootSrc = sourceModule.root; item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_}); } } @@ -522,8 +540,11 @@ void Frontend::queueModuleCheck(const ModuleName& name) moduleQueue.push_back(name); } -std::vector Frontend::checkQueuedModules(std::optional optionOverride, - std::function task)> executeTask, std::function progress) +std::vector Frontend::checkQueuedModules( + std::optional optionOverride, + std::function task)> executeTask, + std::function progress +) { FrontendOptions frontendOptions = optionOverride.value_or(options); if (FFlag::DebugLuauDeferredConstraintResolution) @@ -548,9 +569,15 @@ std::vector Frontend::checkQueuedModules(std::optional queue; - bool cycleDetected = parseGraph(queue, name, frontendOptions.forAutocomplete, [&seen](const ModuleName& name) { - return seen.contains(name); - }); + bool cycleDetected = parseGraph( + queue, + name, + frontendOptions.forAutocomplete, + [&seen](const ModuleName& name) + { + return seen.contains(name); + } + ); addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions); } @@ -570,7 +597,8 @@ std::vector Frontend::checkQueuedModules(std::optional task) { + executeTask = [](std::function task) + { task(); }; } @@ -582,7 +610,8 @@ std::vector Frontend::checkQueuedModules(std::optional Frontend::checkQueuedModules(std::optional Frontend::checkQueuedModules(std::optional Frontend::getCheckResult(const ModuleName& name, bool } bool Frontend::parseGraph( - std::vector& buildQueue, const ModuleName& root, bool forAutocomplete, std::function canSkip) + std::vector& buildQueue, + const ModuleName& root, + bool forAutocomplete, + std::function canSkip +) { LUAU_TIMETRACE_SCOPE("Frontend::parseGraph", "Frontend"); LUAU_TIMETRACE_ARGUMENT("root", root.c_str()); @@ -884,8 +926,13 @@ bool Frontend::parseGraph( return cyclic; } -void Frontend::addBuildQueueItems(std::vector& items, std::vector& buildQueue, bool cycleDetected, - DenseHashSet& seen, const FrontendOptions& frontendOptions) +void Frontend::addBuildQueueItems( + std::vector& items, + std::vector& buildQueue, + bool cycleDetected, + DenseHashSet& seen, + const FrontendOptions& frontendOptions +) { for (const ModuleName& moduleName : buildQueue) { @@ -981,8 +1028,15 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) if (item.options.forAutocomplete) { // The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features - ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true, - /*recordJsonLog*/ false, typeCheckLimits); + ModulePtr moduleForAutocomplete = check( + sourceModule, + Mode::Strict, + requireCycles, + environmentScope, + /*forAutocomplete*/ true, + /*recordJsonLog*/ false, + typeCheckLimits + ); double duration = getTimestamp() - timestamp; @@ -1209,14 +1263,37 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons return const_cast(this)->getSourceModule(moduleName); } -ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, - NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, - const ScopePtr& parentScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits, std::function writeJsonLog) +ModulePtr check( + const SourceModule& sourceModule, + Mode mode, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& parentScope, + std::function prepareModuleScope, + FrontendOptions options, + TypeCheckLimits limits, + std::function writeJsonLog +) { const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson; - return check(sourceModule, mode, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope, - std::move(prepareModuleScope), options, limits, recordJsonLog, writeJsonLog); + return check( + sourceModule, + mode, + requireCycles, + builtinTypes, + iceHandler, + moduleResolver, + fileResolver, + parentScope, + std::move(prepareModuleScope), + options, + limits, + recordJsonLog, + writeJsonLog + ); } struct InternalTypeFinder : TypeOnceVisitor @@ -1263,10 +1340,21 @@ struct InternalTypeFinder : TypeOnceVisitor } }; -ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector& requireCycles, NotNull builtinTypes, - NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, - const ScopePtr& parentScope, std::function prepareModuleScope, FrontendOptions options, - TypeCheckLimits limits, bool recordJsonLog, std::function writeJsonLog) +ModulePtr check( + const SourceModule& sourceModule, + Mode mode, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& parentScope, + std::function prepareModuleScope, + FrontendOptions options, + TypeCheckLimits limits, + bool recordJsonLog, + std::function writeJsonLog +) { LUAU_TIMETRACE_SCOPE("Frontend::check", "Typechecking"); LUAU_TIMETRACE_ARGUMENT("module", sourceModule.name.c_str()); @@ -1300,14 +1388,32 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vectorinternalTypes, builtinTypes, NotNull{&unifierState}}; - ConstraintGenerator cg{result, NotNull{&normalizer}, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope), - logger.get(), NotNull{&dfg}, requireCycles}; + ConstraintGenerator cg{ + result, + NotNull{&normalizer}, + moduleResolver, + builtinTypes, + iceHandler, + parentScope, + std::move(prepareModuleScope), + logger.get(), + NotNull{&dfg}, + requireCycles + }; cg.visitModuleRoot(sourceModule.root); result->errors = std::move(cg.errors); - ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver, requireCycles, - logger.get(), limits}; + ConstraintSolver cs{ + NotNull{&normalizer}, + NotNull(cg.rootScope), + borrowConstraints(cg.constraints), + result->name, + moduleResolver, + requireCycles, + logger.get(), + limits + }; if (options.randomizeConstraintResolutionSeed) cs.randomize(*options.randomizeConstraintResolutionSeed); @@ -1419,22 +1525,41 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector requireCycles, - std::optional environmentScope, bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits) +ModulePtr Frontend::check( + const SourceModule& sourceModule, + Mode mode, + std::vector requireCycles, + std::optional environmentScope, + bool forAutocomplete, + bool recordJsonLog, + TypeCheckLimits typeCheckLimits +) { if (FFlag::DebugLuauDeferredConstraintResolution) { - auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) { + auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) + { if (prepareModuleScope) prepareModuleScope(name, scope, forAutocomplete); }; try { - return Luau::check(sourceModule, mode, requireCycles, builtinTypes, NotNull{&iceHandler}, - NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver}, - environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog, - writeJsonLog); + return Luau::check( + sourceModule, + mode, + requireCycles, + builtinTypes, + NotNull{&iceHandler}, + NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, + NotNull{fileResolver}, + environmentScope ? *environmentScope : globals.globalScope, + prepareModuleScopeWrap, + options, + typeCheckLimits, + recordJsonLog, + writeJsonLog + ); } catch (const InternalCompilerError& err) { @@ -1445,12 +1570,17 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect } else { - TypeChecker typeChecker(forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope, - forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler); + TypeChecker typeChecker( + forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope, + forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, + builtinTypes, + &iceHandler + ); if (prepareModuleScope) { - typeChecker.prepareModuleScope = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) { + typeChecker.prepareModuleScope = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) + { prepareModuleScope(name, scope, forAutocomplete); }; } diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index e6a8bda3..ea736642 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -26,8 +26,14 @@ struct MutatingGeneralizer : TypeOnceVisitor bool isWithinFunction = false; bool avoidSealingTables = false; - MutatingGeneralizer(NotNull builtinTypes, NotNull scope, NotNull> cachedTypes, - DenseHashMap positiveTypes, DenseHashMap negativeTypes, bool avoidSealingTables) + MutatingGeneralizer( + NotNull builtinTypes, + NotNull scope, + NotNull> cachedTypes, + DenseHashMap positiveTypes, + DenseHashMap negativeTypes, + bool avoidSealingTables + ) : TypeOnceVisitor(/* skipBoundTypes */ true) , builtinTypes(builtinTypes) , scope(scope) @@ -867,8 +873,14 @@ struct TypeCacher : TypeOnceVisitor } }; -std::optional generalize(NotNull arena, NotNull builtinTypes, NotNull scope, - NotNull> cachedTypes, TypeId ty, bool avoidSealingTables) +std::optional generalize( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull> cachedTypes, + TypeId ty, + bool avoidSealingTables +) { ty = follow(ty); diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 811aa048..8422d8c4 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -102,8 +102,15 @@ TypePackId Instantiation::clean(TypePackId tp) return tp; } -void ReplaceGenerics::resetState(const TxnLog* log, TypeArena* arena, NotNull builtinTypes, TypeLevel level, Scope* scope, - const std::vector& generics, const std::vector& genericPacks) +void ReplaceGenerics::resetState( + const TxnLog* log, + TypeArena* arena, + NotNull builtinTypes, + TypeLevel level, + Scope* scope, + const std::vector& generics, + const std::vector& genericPacks +) { LUAU_ASSERT(FFlag::LuauReusableSubstitutions); @@ -187,7 +194,12 @@ TypePackId ReplaceGenerics::clean(TypePackId tp) } std::optional instantiate( - NotNull builtinTypes, NotNull arena, NotNull limits, NotNull scope, TypeId ty) + NotNull builtinTypes, + NotNull arena, + NotNull limits, + NotNull scope, + TypeId ty +) { ty = follow(ty); diff --git a/Analysis/src/Instantiation2.cpp b/Analysis/src/Instantiation2.cpp index 36e5bde6..106ad870 100644 --- a/Analysis/src/Instantiation2.cpp +++ b/Analysis/src/Instantiation2.cpp @@ -8,6 +8,23 @@ bool Instantiation2::ignoreChildren(TypeId ty) { if (get(ty)) return true; + + if (auto ftv = get(ty)) + { + if (ftv->hasNoFreeOrGenericTypes) + return false; + + // If this function type quantifies over these generics, we don't want substitution to + // go any further into them because it's being shadowed in this case. + for (auto generic : ftv->generics) + if (genericSubstitutions.contains(generic)) + return true; + + for (auto generic : ftv->genericPacks) + if (genericPackSubstitutions.contains(generic)) + return true; + } + return false; } @@ -47,14 +64,22 @@ TypePackId Instantiation2::clean(TypePackId tp) } std::optional instantiate2( - TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypeId ty) + TypeArena* arena, + DenseHashMap genericSubstitutions, + DenseHashMap genericPackSubstitutions, + TypeId ty +) { Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions)}; return instantiation.substitute(ty); } std::optional instantiate2( - TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypePackId tp) + TypeArena* arena, + DenseHashMap genericSubstitutions, + DenseHashMap genericPackSubstitutions, + TypePackId tp +) { Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions)}; return instantiation.substitute(tp); diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index b6d50173..a3d8b4e3 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -114,6 +114,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "GenericError { " << err.message << " }"; else if constexpr (std::is_same_v) stream << "InternalError { " << err.message << " }"; + else if constexpr (std::is_same_v) + stream << "ConstraintSolvingIncompleteError {}"; else if constexpr (std::is_same_v) stream << "CannotCallNonFunction { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) @@ -259,7 +261,8 @@ std::ostream& operator<<(std::ostream& stream, const CannotAssignToNever::Reason std::ostream& operator<<(std::ostream& stream, const TypeErrorData& data) { - auto cb = [&](const auto& e) { + auto cb = [&](const auto& e) + { return errorToString(stream, e); }; visit(cb, data); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 586ff7b0..23457f4c 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -275,8 +275,14 @@ private: else if (g->deprecated) { if (const char* replacement = *g->deprecated; replacement && strlen(replacement)) - emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated, use '%s' instead", - gv->name.value, replacement); + emitWarning( + *context, + LintWarning::Code_DeprecatedGlobal, + gv->location, + "Global '%s' is deprecated, use '%s' instead", + gv->name.value, + replacement + ); else emitWarning(*context, LintWarning::Code_DeprecatedGlobal, gv->location, "Global '%s' is deprecated", gv->name.value); } @@ -291,18 +297,33 @@ private: AstExprFunction* top = g.functionRef.back(); if (top->debugname.value) - emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, - "Global '%s' is only used in the enclosing function '%s'; consider changing it to local", g.firstRef->name.value, - top->debugname.value); + emitWarning( + *context, + LintWarning::Code_GlobalUsedAsLocal, + g.firstRef->location, + "Global '%s' is only used in the enclosing function '%s'; consider changing it to local", + g.firstRef->name.value, + top->debugname.value + ); else - emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, + emitWarning( + *context, + LintWarning::Code_GlobalUsedAsLocal, + g.firstRef->location, "Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local", - g.firstRef->name.value, top->location.begin.line + 1); + g.firstRef->name.value, + top->location.begin.line + 1 + ); } else if (g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && g.firstRef->name != context->placeholder) { - emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, - "Global '%s' is never read before being written. Consider changing it to local", g.firstRef->name.value); + emitWarning( + *context, + LintWarning::Code_GlobalUsedAsLocal, + g.firstRef->location, + "Global '%s' is never read before being written. Consider changing it to local", + g.firstRef->name.value + ); } } } @@ -329,7 +350,8 @@ private: if (node->name == context->placeholder) emitWarning( - *context, LintWarning::Code_PlaceholderRead, node->location, "Placeholder value '_' is read here; consider using a named variable"); + *context, LintWarning::Code_PlaceholderRead, node->location, "Placeholder value '_' is read here; consider using a named variable" + ); return true; } @@ -338,7 +360,8 @@ private: { if (node->local->name == context->placeholder) emitWarning( - *context, LintWarning::Code_PlaceholderRead, node->location, "Placeholder value '_' is read here; consider using a named variable"); + *context, LintWarning::Code_PlaceholderRead, node->location, "Placeholder value '_' is read here; consider using a named variable" + ); return true; } @@ -366,8 +389,13 @@ private: } if (g.builtin) - emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location, - "Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value); + emitWarning( + *context, + LintWarning::Code_BuiltinGlobalWrite, + gv->location, + "Built-in global '%s' is overwritten here; consider using a local or changing the name", + gv->name.value + ); else g.assigned = true; @@ -396,8 +424,13 @@ private: Global& g = globals[gv->name]; if (g.builtin) - emitWarning(*context, LintWarning::Code_BuiltinGlobalWrite, gv->location, - "Built-in global '%s' is overwritten here; consider using a local or changing the name", gv->name.value); + emitWarning( + *context, + LintWarning::Code_BuiltinGlobalWrite, + gv->location, + "Built-in global '%s' is overwritten here; consider using a local or changing the name", + gv->name.value + ); else { g.assigned = true; @@ -565,8 +598,12 @@ private: if (node->body.data[i - 1]->hasSemicolon) continue; - emitWarning(*context, LintWarning::Code_SameLineStatement, location, - "A new statement is on the same line; add semi-colon on previous statement to silence"); + emitWarning( + *context, + LintWarning::Code_SameLineStatement, + location, + "A new statement is on the same line; add semi-colon on previous statement to silence" + ); lastLine = location.begin.line; } @@ -613,7 +650,8 @@ private: if (location.begin.column <= top.start.begin.column) { emitWarning( - *context, LintWarning::Code_MultiLineStatement, location, "Statement spans multiple lines; use indentation to silence"); + *context, LintWarning::Code_MultiLineStatement, location, "Statement spans multiple lines; use indentation to silence" + ); top.flagged = true; } @@ -727,8 +765,14 @@ private: // don't warn on inter-function shadowing since it is much more fragile wrt refactoring if (shadow->functionDepth == local->functionDepth) - emitWarning(*context, LintWarning::Code_LocalShadow, local->location, "Variable '%s' shadows previous declaration at line %d", - local->name.value, shadow->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_LocalShadow, + local->location, + "Variable '%s' shadows previous declaration at line %d", + local->name.value, + shadow->location.begin.line + 1 + ); } else if (Global* global = globals.find(local->name)) { @@ -736,8 +780,14 @@ private: ; // there are many builtins with common names like 'table'; some of them are deprecated as well else if (global->firstRef) { - emitWarning(*context, LintWarning::Code_LocalShadow, local->location, "Variable '%s' shadows a global variable used at line %d", - local->name.value, global->firstRef->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_LocalShadow, + local->location, + "Variable '%s' shadows a global variable used at line %d", + local->name.value, + global->firstRef->location.begin.line + 1 + ); } else { @@ -752,14 +802,21 @@ private: return; if (info.function) - emitWarning(*context, LintWarning::Code_FunctionUnused, local->location, "Function '%s' is never used; prefix with '_' to silence", - local->name.value); + emitWarning( + *context, + LintWarning::Code_FunctionUnused, + local->location, + "Function '%s' is never used; prefix with '_' to silence", + local->name.value + ); else if (info.import) - emitWarning(*context, LintWarning::Code_ImportUnused, local->location, "Import '%s' is never used; prefix with '_' to silence", - local->name.value); + emitWarning( + *context, LintWarning::Code_ImportUnused, local->location, "Import '%s' is never used; prefix with '_' to silence", local->name.value + ); else - emitWarning(*context, LintWarning::Code_LocalUnused, local->location, "Variable '%s' is never used; prefix with '_' to silence", - local->name.value); + emitWarning( + *context, LintWarning::Code_LocalUnused, local->location, "Variable '%s' is never used; prefix with '_' to silence", local->name.value + ); } bool isRequireCall(AstExpr* expr) @@ -913,8 +970,13 @@ private: for (auto& g : globals) { if (g.second.function && !g.second.used && g.first.value[0] != '_') - emitWarning(*context, LintWarning::Code_FunctionUnused, g.second.location, "Function '%s' is never used; prefix with '_' to silence", - g.first.value); + emitWarning( + *context, + LintWarning::Code_FunctionUnused, + g.second.location, + "Function '%s' is never used; prefix with '_' to silence", + g.first.value + ); } } @@ -1013,8 +1075,13 @@ private: if (step == Error && si->is() && next->is() && i + 2 == stat->body.size) return Error; - emitWarning(*context, LintWarning::Code_UnreachableCode, next->location, "Unreachable code (previous statement always %ss)", - getReason(step)); + emitWarning( + *context, + LintWarning::Code_UnreachableCode, + next->location, + "Unreachable code (previous statement always %ss)", + getReason(step) + ); return step; } } @@ -1209,22 +1276,34 @@ private: // for i=#t,1 do if (fu && fu->op == AstExprUnary::Len && tc && tc->value == 1.0) emitWarning( - *context, LintWarning::Code_ForRange, rangeLocation, "For loop should iterate backwards; did you forget to specify -1 as step?"); + *context, LintWarning::Code_ForRange, rangeLocation, "For loop should iterate backwards; did you forget to specify -1 as step?" + ); // for i=8,1 do else if (fc && tc && fc->value > tc->value) emitWarning( - *context, LintWarning::Code_ForRange, rangeLocation, "For loop should iterate backwards; did you forget to specify -1 as step?"); + *context, LintWarning::Code_ForRange, rangeLocation, "For loop should iterate backwards; did you forget to specify -1 as step?" + ); // for i=1,8.75 do else if (fc && tc && getLoopEnd(fc->value, tc->value) != tc->value) - emitWarning(*context, LintWarning::Code_ForRange, rangeLocation, "For loop ends at %g instead of %g; did you forget to specify step?", - getLoopEnd(fc->value, tc->value), tc->value); + emitWarning( + *context, + LintWarning::Code_ForRange, + rangeLocation, + "For loop ends at %g instead of %g; did you forget to specify step?", + getLoopEnd(fc->value, tc->value), + tc->value + ); // for i=0,#t do else if (fc && tu && fc->value == 0.0 && tu->op == AstExprUnary::Len) emitWarning(*context, LintWarning::Code_ForRange, rangeLocation, "For loop starts at 0, but arrays start at 1"); // for i=#t,0 do else if (fu && fu->op == AstExprUnary::Len && tc && tc->value == 0.0) - emitWarning(*context, LintWarning::Code_ForRange, rangeLocation, - "For loop should iterate backwards; did you forget to specify -1 as step? Also consider changing 0 to 1 since arrays start at 1"); + emitWarning( + *context, + LintWarning::Code_ForRange, + rangeLocation, + "For loop should iterate backwards; did you forget to specify -1 as step? Also consider changing 0 to 1 since arrays start at 1" + ); } return true; @@ -1252,16 +1331,27 @@ private: AstExpr* last = values.data[values.size - 1]; if (vars < values.size) - emitWarning(*context, LintWarning::Code_UnbalancedAssignment, location, - "Assigning %d values to %d variables leaves some values unused", int(values.size), int(vars)); + emitWarning( + *context, + LintWarning::Code_UnbalancedAssignment, + location, + "Assigning %d values to %d variables leaves some values unused", + int(values.size), + int(vars) + ); else if (last->is() || last->is()) ; // we don't know how many values the last expression returns else if (last->is()) ; // last expression is nil which explicitly silences the nil-init warning else - emitWarning(*context, LintWarning::Code_UnbalancedAssignment, location, - "Assigning %d values to %d variables initializes extra variables with nil; add 'nil' to value list to silence", int(values.size), - int(vars)); + emitWarning( + *context, + LintWarning::Code_UnbalancedAssignment, + location, + "Assigning %d values to %d variables initializes extra variables with nil; add 'nil' to value list to silence", + int(values.size), + int(vars) + ); } } @@ -1344,13 +1434,22 @@ private: Location location = getEndLocation(bodyf); if (node->debugname.value) - emitWarning(*context, LintWarning::Code_ImplicitReturn, location, + emitWarning( + *context, + LintWarning::Code_ImplicitReturn, + location, "Function '%s' can implicitly return no values even though there's an explicit return at line %d; add explicit return to silence", - node->debugname.value, vret->location.begin.line + 1); + node->debugname.value, + vret->location.begin.line + 1 + ); else - emitWarning(*context, LintWarning::Code_ImplicitReturn, location, + emitWarning( + *context, + LintWarning::Code_ImplicitReturn, + location, "Function can implicitly return no values even though there's an explicit return at line %d; add explicit return to silence", - vret->location.begin.line + 1); + vret->location.begin.line + 1 + ); } return true; @@ -1821,23 +1920,41 @@ private: int& line = names[&expr->value]; if (line) - emitWarning(*context, LintWarning::Code_TableLiteral, expr->location, - "Table field '%.*s' is a duplicate; previously defined at line %d", int(expr->value.size), expr->value.data, line); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + expr->location, + "Table field '%.*s' is a duplicate; previously defined at line %d", + int(expr->value.size), + expr->value.data, + line + ); else line = expr->location.begin.line + 1; } else if (AstExprConstantNumber* expr = item.key->as()) { if (expr->value >= 1 && expr->value <= double(count) && double(int(expr->value)) == expr->value) - emitWarning(*context, LintWarning::Code_TableLiteral, expr->location, - "Table index %d is a duplicate; previously defined as a list entry", int(expr->value)); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + expr->location, + "Table index %d is a duplicate; previously defined as a list entry", + int(expr->value) + ); else if (expr->value >= 0 && expr->value <= double(INT_MAX) && double(int(expr->value)) == expr->value) { int& line = indices[int(expr->value)]; if (line) - emitWarning(*context, LintWarning::Code_TableLiteral, expr->location, - "Table index %d is a duplicate; previously defined at line %d", int(expr->value), line); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + expr->location, + "Table index %d is a duplicate; previously defined at line %d", + int(expr->value), + line + ); else line = expr->location.begin.line + 1; } @@ -1875,18 +1992,41 @@ private: if (int(rec->access) & int(item.access)) { if (rec->access == item.access) - emitWarning(*context, LintWarning::Code_TableLiteral, item.location, - "Table type field '%s' is a duplicate; previously defined at line %d", item.name.value, rec->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + item.location, + "Table type field '%s' is a duplicate; previously defined at line %d", + item.name.value, + rec->location.begin.line + 1 + ); else if (rec->access == AstTableAccess::ReadWrite) - emitWarning(*context, LintWarning::Code_TableLiteral, item.location, - "Table type field '%s' is already read-write; previously defined at line %d", item.name.value, - rec->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + item.location, + "Table type field '%s' is already read-write; previously defined at line %d", + item.name.value, + rec->location.begin.line + 1 + ); else if (rec->access == AstTableAccess::Read) - emitWarning(*context, LintWarning::Code_TableLiteral, rec->location, - "Table type field '%s' already has a read type defined at line %d", item.name.value, rec->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + rec->location, + "Table type field '%s' already has a read type defined at line %d", + item.name.value, + rec->location.begin.line + 1 + ); else if (rec->access == AstTableAccess::Write) - emitWarning(*context, LintWarning::Code_TableLiteral, rec->location, - "Table type field '%s' already has a write type defined at line %d", item.name.value, rec->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + rec->location, + "Table type field '%s' already has a write type defined at line %d", + item.name.value, + rec->location.begin.line + 1 + ); else LUAU_ASSERT(!"Unreachable"); } @@ -1904,8 +2044,14 @@ private: int& line = names[item.name]; if (line) - emitWarning(*context, LintWarning::Code_TableLiteral, item.location, - "Table type field '%s' is a duplicate; previously defined at line %d", item.name.value, line); + emitWarning( + *context, + LintWarning::Code_TableLiteral, + item.location, + "Table type field '%s' is a duplicate; previously defined at line %d", + item.name.value, + line + ); else line = item.location.begin.line + 1; } @@ -1966,9 +2112,14 @@ private: if (l.defined && !l.initialized && !l.assigned && l.firstUse) { - emitWarning(*context, LintWarning::Code_UninitializedLocal, l.firstUse->location, - "Variable '%s' defined at line %d is never initialized or assigned; initialize with 'nil' to silence", local->name.value, - local->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_UninitializedLocal, + l.firstUse->location, + "Variable '%s' defined at line %d is never initialized or assigned; initialize with 'nil' to silence", + local->name.value, + local->location.begin.line + 1 + ); } } } @@ -2102,8 +2253,14 @@ private: void report(const std::string& name, Location location, Location otherLocation) { - emitWarning(*context, LintWarning::Code_DuplicateFunction, location, "Duplicate function definition: '%s' also defined on line %d", - name.c_str(), otherLocation.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateFunction, + location, + "Duplicate function definition: '%s' also defined on line %d", + name.c_str(), + otherLocation.begin.line + 1 + ); } }; @@ -2152,7 +2309,8 @@ private: const char* suggestion = (fenv->name == "getfenv") ? "; consider using 'debug.info' instead" : ""; emitWarning( - *context, LintWarning::Code_DeprecatedApi, node->location, "Function '%s' is deprecated%s", fenv->name.value, suggestion); + *context, LintWarning::Code_DeprecatedApi, node->location, "Function '%s' is deprecated%s", fenv->name.value, suggestion + ); } } } @@ -2265,7 +2423,8 @@ private: if (!tty->indexer && !tty->props.empty() && tty->state != TableState::Generic) emitWarning( - *context, LintWarning::Code_TableOperations, node->location, "Using '%s' on a table without an array part is likely a bug", op); + *context, LintWarning::Code_TableOperations, node->location, "Using '%s' on a table without an array part is likely a bug", op + ); else if (tty->indexer && isString(tty->indexer->indexType)) // note: to avoid complexity of subtype tests we just check if the key is a string emitWarning(*context, LintWarning::Code_TableOperations, node->location, "Using '%s' on a table with string keys is likely a bug", op); } @@ -2283,9 +2442,13 @@ private: size_t ret = getReturnCount(follow(*funty)); if (ret > 1) - emitWarning(*context, LintWarning::Code_TableOperations, tail->location, + emitWarning( + *context, + LintWarning::Code_TableOperations, + tail->location, "table.insert may change behavior if the call returns more than one result; consider adding parentheses around second " - "argument"); + "argument" + ); } } } @@ -2294,28 +2457,44 @@ private: { // table.insert(t, 0, ?) if (isConstant(args[1], 0.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, - "table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, + "table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?" + ); // table.insert(t, #t, ?) if (isLength(args[1], args[0])) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, "table.insert will insert the value before the last element, which is likely a bug; consider removing the second argument or " - "wrap it in parentheses to silence"); + "wrap it in parentheses to silence" + ); // table.insert(t, #t+1, ?) if (AstExprBinary* add = args[1]->as(); add && add->op == AstExprBinary::Add && isLength(add->left, args[0]) && isConstant(add->right, 1.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, - "table.insert will append the value to the table; consider removing the second argument for efficiency"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, + "table.insert will append the value to the table; consider removing the second argument for efficiency" + ); } if (func->index == "remove" && node->args.size >= 2) { // table.remove(t, 0) if (isConstant(args[1], 0.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, - "table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, + "table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?" + ); // note: it's tempting to check for table.remove(t, #t), which is equivalent to table.remove(t), but it's correct, occurs frequently, // and also reads better. @@ -2323,35 +2502,55 @@ private: // table.remove(t, #t-1) if (AstExprBinary* sub = args[1]->as(); sub && sub->op == AstExprBinary::Sub && isLength(sub->left, args[0]) && isConstant(sub->right, 1.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, "table.remove will remove the value before the last element, which is likely a bug; consider removing the second argument or " - "wrap it in parentheses to silence"); + "wrap it in parentheses to silence" + ); } if (func->index == "move" && node->args.size >= 4) { // table.move(t, 0, _, _) if (isConstant(args[1], 0.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, - "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, + "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?" + ); // table.move(t, _, _, 0) else if (isConstant(args[3], 0.0)) - emitWarning(*context, LintWarning::Code_TableOperations, args[3]->location, - "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[3]->location, + "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?" + ); } if (func->index == "create" && node->args.size == 2) { // table.create(n, {...}) if (args[1]->is()) - emitWarning(*context, LintWarning::Code_TableOperations, args[1]->location, - "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + args[1]->location, + "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead" + ); // table.create(n, {...} :: ?) if (AstExprTypeAssertion* as = args[1]->as(); as && as->expr->is()) - emitWarning(*context, LintWarning::Code_TableOperations, as->expr->location, - "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"); + emitWarning( + *context, + LintWarning::Code_TableOperations, + as->expr->location, + "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead" + ); } } @@ -2543,11 +2742,21 @@ private: if (similar(conditions[j], conditions[i])) { if (conditions[i]->location.begin.line == conditions[j]->location.begin.line) - emitWarning(*context, LintWarning::Code_DuplicateCondition, conditions[i]->location, - "Condition has already been checked on column %d", conditions[j]->location.begin.column + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateCondition, + conditions[i]->location, + "Condition has already been checked on column %d", + conditions[j]->location.begin.column + 1 + ); else - emitWarning(*context, LintWarning::Code_DuplicateCondition, conditions[i]->location, - "Condition has already been checked on line %d", conditions[j]->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateCondition, + conditions[i]->location, + "Condition has already been checked on line %d", + conditions[j]->location.begin.line + 1 + ); break; } } @@ -2592,11 +2801,23 @@ private: if (local->shadow && locals[local->shadow] == node && !ignoreDuplicate(local)) { if (local->shadow->location.begin.line == local->location.begin.line) - emitWarning(*context, LintWarning::Code_DuplicateLocal, local->location, "Variable '%s' already defined on column %d", - local->name.value, local->shadow->location.begin.column + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateLocal, + local->location, + "Variable '%s' already defined on column %d", + local->name.value, + local->shadow->location.begin.column + 1 + ); else - emitWarning(*context, LintWarning::Code_DuplicateLocal, local->location, "Variable '%s' already defined on line %d", - local->name.value, local->shadow->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateLocal, + local->location, + "Variable '%s' already defined on line %d", + local->name.value, + local->shadow->location.begin.line + 1 + ); } } @@ -2620,11 +2841,23 @@ private: if (local->shadow == node->self) emitWarning(*context, LintWarning::Code_DuplicateLocal, local->location, "Function parameter 'self' already defined implicitly"); else if (local->shadow->location.begin.line == local->location.begin.line) - emitWarning(*context, LintWarning::Code_DuplicateLocal, local->location, "Function parameter '%s' already defined on column %d", - local->name.value, local->shadow->location.begin.column + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateLocal, + local->location, + "Function parameter '%s' already defined on column %d", + local->name.value, + local->shadow->location.begin.column + 1 + ); else - emitWarning(*context, LintWarning::Code_DuplicateLocal, local->location, "Function parameter '%s' already defined on line %d", - local->name.value, local->shadow->location.begin.line + 1); + emitWarning( + *context, + LintWarning::Code_DuplicateLocal, + local->location, + "Function parameter '%s' already defined on line %d", + local->name.value, + local->shadow->location.begin.line + 1 + ); } } @@ -2668,10 +2901,14 @@ private: alt = "false"; if (alt) - emitWarning(*context, LintWarning::Code_MisleadingAndOr, node->location, + emitWarning( + *context, + LintWarning::Code_MisleadingAndOr, + node->location, "The and-or expression always evaluates to the second alternative because the first alternative is %s; consider using if-then-else " "expression instead", - alt); + alt + ); return true; } @@ -2699,16 +2936,28 @@ private: case ConstantNumberParseResult::Malformed: break; case ConstantNumberParseResult::Imprecise: - emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, - "Number literal exceeded available precision and was truncated to closest representable number"); + emitWarning( + *context, + LintWarning::Code_IntegerParsing, + node->location, + "Number literal exceeded available precision and was truncated to closest representable number" + ); break; case ConstantNumberParseResult::BinOverflow: - emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, - "Binary number literal exceeded available precision and was truncated to 2^64"); + emitWarning( + *context, + LintWarning::Code_IntegerParsing, + node->location, + "Binary number literal exceeded available precision and was truncated to 2^64" + ); break; case ConstantNumberParseResult::HexOverflow: - emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, - "Hexadecimal number literal exceeded available precision and was truncated to 2^64"); + emitWarning( + *context, + LintWarning::Code_IntegerParsing, + node->location, + "Hexadecimal number literal exceeded available precision and was truncated to 2^64" + ); break; } @@ -2759,12 +3008,24 @@ private: std::string op = toString(node->op); if (isEquality(node->op)) - emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or add parentheses to silence", op.c_str(), op.c_str(), - node->op == AstExprBinary::CompareEq ? "~=" : "=="); + emitWarning( + *context, + LintWarning::Code_ComparisonPrecedence, + node->location, + "not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or add parentheses to silence", + op.c_str(), + op.c_str(), + node->op == AstExprBinary::CompareEq ? "~=" : "==" + ); else - emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "not X %s Y is equivalent to (not X) %s Y; add parentheses to silence", op.c_str(), op.c_str()); + emitWarning( + *context, + LintWarning::Code_ComparisonPrecedence, + node->location, + "not X %s Y is equivalent to (not X) %s Y; add parentheses to silence", + op.c_str(), + op.c_str() + ); } else if (AstExprBinary* left = node->left->as(); left && isComparison(left->op)) { @@ -2772,12 +3033,29 @@ private: std::string rop = toString(node->op); if (isEquality(left->op) || isEquality(node->op)) - emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "X %s Y %s Z is equivalent to (X %s Y) %s Z; add parentheses to silence", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str()); + emitWarning( + *context, + LintWarning::Code_ComparisonPrecedence, + node->location, + "X %s Y %s Z is equivalent to (X %s Y) %s Z; add parentheses to silence", + lop.c_str(), + rop.c_str(), + lop.c_str(), + rop.c_str() + ); else - emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "X %s Y %s Z is equivalent to (X %s Y) %s Z; did you mean X %s Y and Y %s Z?", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str(), - lop.c_str(), rop.c_str()); + emitWarning( + *context, + LintWarning::Code_ComparisonPrecedence, + node->location, + "X %s Y %s Z is equivalent to (X %s Y) %s Z; did you mean X %s Y and Y %s Z?", + lop.c_str(), + rop.c_str(), + lop.c_str(), + rop.c_str(), + lop.c_str(), + rop.c_str() + ); } return true; @@ -2843,8 +3121,12 @@ static void lintComments(LintContext& context, const std::vector& ho if (!hc.header) { - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "Comment directive is ignored because it is placed after the first non-comment token"); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "Comment directive is ignored because it is placed after the first non-comment token" + ); } else { @@ -2865,21 +3147,36 @@ static void lintComments(LintContext& context, const std::vector& ho // skip Unknown if (const char* suggestion = fuzzyMatch(rule, kWarningNames + 1, LintWarning::Code__Count - 1)) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "nolint directive refers to unknown lint rule '%s'; did you mean '%s'?", rule, suggestion); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "nolint directive refers to unknown lint rule '%s'; did you mean '%s'?", + rule, + suggestion + ); else emitWarning( - context, LintWarning::Code_CommentDirective, hc.location, "nolint directive refers to unknown lint rule '%s'", rule); + context, LintWarning::Code_CommentDirective, hc.location, "nolint directive refers to unknown lint rule '%s'", rule + ); } } else if (first == "nocheck" || first == "nonstrict" || first == "strict") { if (space != std::string::npos) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "Comment directive with the type checking mode has extra symbols at the end of the line"); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "Comment directive with the type checking mode has extra symbols at the end of the line" + ); else if (seenMode) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "Comment directive with the type checking mode has already been used"); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "Comment directive with the type checking mode has already been used" + ); else seenMode = true; } @@ -2894,15 +3191,21 @@ static void lintComments(LintContext& context, const std::vector& ho const char* level = hc.content.c_str() + notspace; if (strcmp(level, "0") && strcmp(level, "1") && strcmp(level, "2")) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, - "optimize directive uses unknown optimization level '%s', 0..2 expected", level); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "optimize directive uses unknown optimization level '%s', 0..2 expected", + level + ); } } else if (first == "native") { if (space != std::string::npos) emitWarning( - context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line"); + context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line" + ); } else { @@ -2916,11 +3219,19 @@ static void lintComments(LintContext& context, const std::vector& ho }; if (const char* suggestion = fuzzyMatch(first, kHotComments, std::size(kHotComments))) - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'; did you mean '%s'?", - int(first.size()), first.data(), suggestion); + emitWarning( + context, + LintWarning::Code_CommentDirective, + hc.location, + "Unknown comment directive '%.*s'; did you mean '%s'?", + int(first.size()), + first.data(), + suggestion + ); else - emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'", int(first.size()), - first.data()); + emitWarning( + context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'", int(first.size()), first.data() + ); } } } @@ -2973,8 +3284,12 @@ private: { if (attribute->type == AstAttr::Type::Native) { - emitWarning(*context, LintWarning::Code_RedundantNativeAttribute, attribute->location, - "native attribute on a function is redundant in a native module; consider removing it"); + emitWarning( + *context, + LintWarning::Code_RedundantNativeAttribute, + attribute->location, + "native attribute on a function is redundant in a native module; consider removing it" + ); } } @@ -2982,8 +3297,14 @@ private: } }; -std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, - const std::vector& hotcomments, const LintOptions& options) +std::vector lint( + AstStat* root, + const AstNameTable& names, + const ScopePtr& env, + const Module* module, + const std::vector& hotcomments, + const LintOptions& options +) { LintContext context; @@ -3068,8 +3389,7 @@ std::vector lint(AstStat* root, const AstNameTable& names, const Sc if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence)) LintComparisonPrecedence::process(context); - if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute && - context.warningEnabled(LintWarning::Code_RedundantNativeAttribute)) + if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute)) { if (hasNativeCommentDirective(hotcomments)) LintRedundantNativeAttribute::process(context); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 9dab5123..f9a3f67a 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -24,8 +24,8 @@ 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 + else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't + // have an end return true; else if (comment.type == Lexeme::Comment && comment.location.end == pos) return true; @@ -36,9 +36,14 @@ static bool contains(Position pos, Comment comment) static bool isWithinComment(const std::vector& commentLocations, Position pos) { auto iter = std::lower_bound( - commentLocations.begin(), commentLocations.end(), Comment{Lexeme::Comment, Location{pos, pos}}, [](const Comment& a, const Comment& b) { + commentLocations.begin(), + commentLocations.end(), + Comment{Lexeme::Comment, Location{pos, pos}}, + [](const Comment& a, const Comment& b) + { return a.location.end < b.location.end; - }); + } + ); if (iter == commentLocations.end()) return false; diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index f567bf05..16225e96 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -69,7 +69,11 @@ struct NonStrictContext NonStrictContext& operator=(NonStrictContext&&) = default; static NonStrictContext disjunction( - NotNull builtinTypes, NotNull arena, const NonStrictContext& left, const NonStrictContext& right) + NotNull builtinTypes, + NotNull arena, + const NonStrictContext& left, + const NonStrictContext& right + ) { // disjunction implements union over the domain of keys // if the default value for a defId not in the map is `never` @@ -94,7 +98,11 @@ struct NonStrictContext } static NonStrictContext conjunction( - NotNull builtins, NotNull arena, const NonStrictContext& left, const NonStrictContext& right) + NotNull builtins, + NotNull arena, + const NonStrictContext& left, + const NonStrictContext& right + ) { NonStrictContext conj{}; @@ -160,8 +168,15 @@ struct NonStrictTypeChecker const NotNull limits; - NonStrictTypeChecker(NotNull arena, NotNull builtinTypes, const NotNull ice, - NotNull unifierState, NotNull dfg, NotNull limits, Module* module) + NonStrictTypeChecker( + NotNull arena, + NotNull builtinTypes, + const NotNull ice, + NotNull unifierState, + NotNull dfg, + NotNull limits, + Module* module + ) : builtinTypes(builtinTypes) , ice(ice) , arena(arena) @@ -213,7 +228,8 @@ struct NonStrictTypeChecker return instance; ErrorVec errors = - reduceTypeFunctions(instance, location, TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true).errors; + reduceTypeFunctions(instance, location, TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) + .errors; if (errors.empty()) noTypeFunctionErrors.insert(instance); @@ -271,6 +287,8 @@ struct NonStrictTypeChecker return visit(s); else if (auto s = stat->as()) return visit(s); + else if (auto f = stat->as()) + return visit(f); else if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) @@ -395,6 +413,12 @@ struct NonStrictTypeChecker return {}; } + NonStrictContext visit(AstStatTypeFunction* typeFunc) + { + reportError(GenericError{"This syntax is not supported"}, typeFunc->location); + return {}; + } + NonStrictContext visit(AstStatDeclareFunction* declFn) { return {}; @@ -726,8 +750,15 @@ private: }; }; -void checkNonStrict(NotNull builtinTypes, NotNull ice, NotNull unifierState, - NotNull dfg, NotNull limits, const SourceModule& sourceModule, Module* module) +void checkNonStrict( + NotNull builtinTypes, + NotNull ice, + NotNull unifierState, + NotNull dfg, + NotNull limits, + const SourceModule& sourceModule, + Module* module +) { LUAU_TIMETRACE_SCOPE("checkNonStrict", "Typechecking"); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index fda70953..088b74b1 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -159,10 +159,15 @@ size_t TypeIds::getHash() const bool TypeIds::isNever() const { - return std::all_of(begin(), end(), [&](TypeId i) { - // If each typeid is never, then I guess typeid's is also never? - return get(i) != nullptr; - }); + return std::all_of( + begin(), + end(), + [&](TypeId i) + { + // If each typeid is never, then I guess typeid's is also never? + return get(i) != nullptr; + } + ); } bool TypeIds::operator==(const TypeIds& there) const @@ -371,10 +376,15 @@ bool NormalizedType::shouldSuppressErrors() const bool NormalizedType::hasTopTable() const { - return hasTables() && std::any_of(tables.begin(), tables.end(), [&](TypeId ty) { - auto primTy = get(ty); - return primTy && primTy->type == PrimitiveType::Type::Table; - }); + return hasTables() && std::any_of( + tables.begin(), + tables.end(), + [&](TypeId ty) + { + auto primTy = get(ty); + return primTy && primTy->type == PrimitiveType::Type::Table; + } + ); } bool NormalizedType::hasTops() const @@ -449,7 +459,7 @@ bool NormalizedType::isFalsy() const } return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() && - !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars()); + !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars()); } bool NormalizedType::isTruthy() const @@ -806,7 +816,8 @@ static bool areNormalizedClasses(const NormalizedClassType& tys) if (isSubclass(ctv, octv)) { - auto iss = [ctv](TypeId t) { + auto iss = [ctv](TypeId t) + { const ClassType* c = get(t); if (!c) return false; @@ -970,7 +981,6 @@ NormalizationResult Normalizer::normalizeIntersections(const std::vector NormalizedType norm{builtinTypes}; norm.tops = builtinTypes->anyType; // Now we need to intersect the two types - Set seenSetTypes{nullptr}; for (auto ty : intersections) { NormalizationResult res = intersectNormalWithTy(norm, ty, seenSet); @@ -1417,8 +1427,9 @@ std::optional Normalizer::unionOfTypePacks(TypePackId here, TypePack itt++; } - auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, - bool& thereSubHere) { + auto dealWithDifferentArities = + [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere) + { if (ith != end(here)) { TypeId tty = builtinTypes->nilType; @@ -1803,8 +1814,7 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t } 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; @@ -2379,8 +2389,9 @@ std::optional Normalizer::intersectionOfTypePacks(TypePackId here, T itt++; } - auto dealWithDifferentArities = [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, - bool& thereSubHere) { + auto dealWithDifferentArities = + [&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere) + { if (ith != end(here)) { TypeId tty = builtinTypes->nilType; @@ -2570,7 +2581,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there } } - NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenSet); + NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy); // Cleanup if (fixCyclicTablesBlowingStack()) @@ -3088,8 +3099,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type } 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}; @@ -3441,7 +3451,12 @@ bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull scope, Not } bool isConsistentSubtype( - TypePackId subPack, TypePackId superPack, NotNull scope, NotNull builtinTypes, InternalErrorReporter& ice) + TypePackId subPack, + TypePackId superPack, + NotNull scope, + NotNull builtinTypes, + InternalErrorReporter& ice +) { LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution); diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 3cc0bc64..9aad142d 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -13,8 +13,15 @@ namespace Luau { -OverloadResolver::OverloadResolver(NotNull builtinTypes, NotNull arena, NotNull normalizer, NotNull scope, - NotNull reporter, NotNull limits, Location callLocation) +OverloadResolver::OverloadResolver( + NotNull builtinTypes, + NotNull arena, + NotNull normalizer, + NotNull scope, + NotNull reporter, + NotNull limits, + Location callLocation +) : builtinTypes(builtinTypes) , arena(arena) , normalizer(normalizer) @@ -28,10 +35,15 @@ OverloadResolver::OverloadResolver(NotNull builtinTypes, NotNull OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack) { - auto tryOne = [&](TypeId f) { + auto tryOne = [&](TypeId f) + { if (auto ftv = get(f)) { + Subtyping::Variance variance = subtyping.variance; + subtyping.variance = Subtyping::Variance::Contravariant; SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes); + subtyping.variance = variance; + if (r.isSubtype) return true; } @@ -137,7 +149,12 @@ std::optional OverloadResolver::testIsSubtype(const Location& location } std::pair OverloadResolver::checkOverload( - TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector* argExprs, bool callMetamethodOk) + TypeId fnTy, + const TypePack* args, + AstExpr* fnLoc, + const std::vector* argExprs, + bool callMetamethodOk +) { fnTy = follow(fnTy); @@ -173,7 +190,12 @@ bool OverloadResolver::isLiteral(AstExpr* expr) } std::pair OverloadResolver::checkOverload_( - TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector* argExprs) + TypeId fnTy, + const FunctionType* fn, + const TypePack* args, + AstExpr* fnExpr, + const std::vector* argExprs +) { FunctionGraphReductionResult result = reduceTypeFunctions(fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true); @@ -373,9 +395,17 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors) // we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`. // this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed. -std::optional selectOverload(NotNull builtinTypes, NotNull arena, NotNull normalizer, - NotNull scope, NotNull iceReporter, NotNull limits, const Location& location, TypeId fn, - TypePackId argsPack) +std::optional selectOverload( + NotNull builtinTypes, + NotNull arena, + NotNull normalizer, + NotNull scope, + NotNull iceReporter, + NotNull limits, + const Location& location, + TypeId fn, + TypePackId argsPack +) { OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location}; auto [status, overload] = resolver.selectOverload(fn, argsPack); @@ -389,9 +419,17 @@ std::optional selectOverload(NotNull builtinTypes, NotNull return {}; } -SolveResult solveFunctionCall(NotNull arena, NotNull builtinTypes, NotNull normalizer, - NotNull iceReporter, NotNull limits, NotNull scope, const Location& location, TypeId fn, - TypePackId argsPack) +SolveResult solveFunctionCall( + NotNull arena, + NotNull builtinTypes, + NotNull normalizer, + NotNull iceReporter, + NotNull limits, + NotNull scope, + const Location& location, + TypeId fn, + TypePackId argsPack +) { std::optional overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack); if (!overloadToUse) diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 0cc53d65..daa61fd5 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -8,8 +8,6 @@ #include "Luau/Type.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(DebugLuauSharedSelf) - namespace Luau { @@ -100,53 +98,13 @@ struct Quantifier final : TypeOnceVisitor void quantify(TypeId ty, TypeLevel level) { - if (FFlag::DebugLuauSharedSelf) - { - ty = follow(ty); + Quantifier q{level}; + q.traverse(ty); - if (auto ttv = getTableType(ty); ttv && ttv->selfTy) - { - Quantifier selfQ{level}; - selfQ.traverse(*ttv->selfTy); - - Quantifier q{level}; - q.traverse(ty); - - for (const auto& [_, prop] : ttv->props) - { - auto ftv = getMutable(follow(prop.type())); - if (!ftv || !ftv->hasSelf) - continue; - - if (Luau::first(ftv->argTypes) == ttv->selfTy) - { - ftv->generics.insert(ftv->generics.end(), selfQ.generics.begin(), selfQ.generics.end()); - ftv->genericPacks.insert(ftv->genericPacks.end(), selfQ.genericPacks.begin(), selfQ.genericPacks.end()); - } - } - } - else if (auto ftv = getMutable(ty)) - { - Quantifier q{level}; - q.traverse(ty); - - ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); - ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); - - if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType) - ftv->hasNoFreeOrGenericTypes = true; - } - } - else - { - Quantifier q{level}; - q.traverse(ty); - - FunctionType* ftv = getMutable(ty); - LUAU_ASSERT(ftv); - ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); - ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); - } + FunctionType* ftv = getMutable(ty); + LUAU_ASSERT(ftv); + ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); + ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); } struct PureQuantifier : Substitution diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index dae7b2d2..ed953f63 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -238,12 +238,22 @@ Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen) LUAU_ASSERT(1 == rightTable->props.size()); // Disjoint props have nothing in common // t1 with props p1's cannot appear in t2 and t2 with props p2's cannot appear in t1 - bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props), [&](auto prop) { - return rightTable->props.count(prop.first) > 0; - }); - bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) { - return leftTable->props.count(prop.first) > 0; - }); + bool foundPropFromLeftInRight = std::any_of( + begin(leftTable->props), + end(leftTable->props), + [&](auto prop) + { + return rightTable->props.count(prop.first) > 0; + } + ); + bool foundPropFromRightInLeft = std::any_of( + begin(rightTable->props), + end(rightTable->props), + [&](auto prop) + { + return leftTable->props.count(prop.first) > 0; + } + ); if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1) return Relation::Disjoint; @@ -1112,8 +1122,13 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) { case Relation::Disjoint: return builtinTypes->neverType; + case Relation::Superset: case Relation::Coincident: return right; + case Relation::Subset: + if (1 == rt->props.size()) + return left; + break; default: break; } @@ -1121,6 +1136,40 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) } else if (1 == rt->props.size()) return basicIntersect(right, left); + + // If two tables have disjoint properties and indexers, we can combine them. + if (!lt->indexer && !rt->indexer && lt->state == TableState::Sealed && rt->state == TableState::Sealed) + { + if (rt->props.empty()) + return left; + + bool areDisjoint = true; + for (const auto& [name, leftProp]: lt->props) + { + if (rt->props.count(name)) + { + areDisjoint = false; + break; + } + } + + if (areDisjoint) + { + TableType::Props mergedProps = lt->props; + for (const auto& [name, rightProp]: rt->props) + mergedProps[name] = rightProp; + + return arena->addType(TableType{ + mergedProps, + std::nullopt, + TypeLevel{}, + lt->scope, + TableState::Sealed + }); + } + } + + return std::nullopt; } Relation relation = relate(left, right); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 9b8561a1..8b8f22e8 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -18,7 +18,8 @@ namespace Luau static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) { - auto go = [ty, &dest, alwaysClone](auto&& a) { + auto go = [ty, &dest, alwaysClone](auto&& a) + { using T = std::decay_t; // The pointer identities of free and local types is very important. @@ -672,7 +673,8 @@ TypePackId Substitution::clone(TypePackId tp) else if (const TypeFunctionInstanceTypePack* tfitp = get(tp)) { TypeFunctionInstanceTypePack clone{ - tfitp->function, std::vector(tfitp->typeArguments.size()), std::vector(tfitp->packArguments.size())}; + tfitp->function, std::vector(tfitp->typeArguments.size()), std::vector(tfitp->packArguments.size()) + }; clone.typeArguments.assign(tfitp->typeArguments.begin(), tfitp->typeArguments.end()); clone.packArguments.assign(tfitp->packArguments.begin(), tfitp->packArguments.end()); return addTypePack(std::move(clone)); diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 3182fb7c..dc63851f 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -91,7 +91,8 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant) { SubtypingReasoning inverseReasoning = SubtypingReasoning{ - r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant}; + r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant + }; if (b.contains(inverseReasoning)) result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant}); else @@ -106,7 +107,8 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant) { SubtypingReasoning inverseReasoning = SubtypingReasoning{ - r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant}; + r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant + }; if (a.contains(inverseReasoning)) result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant}); else @@ -267,7 +269,11 @@ struct ApplyMappedGenerics : Substitution ApplyMappedGenerics( - NotNull builtinTypes, NotNull arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks) + NotNull builtinTypes, + NotNull arena, + MappedGenerics& mappedGenerics, + MappedGenericPacks& mappedGenericPacks + ) : Substitution(TxnLog::empty(), arena) , builtinTypes(builtinTypes) , arena(arena) @@ -323,8 +329,13 @@ std::optional SubtypingEnvironment::applyMappedGenerics(NotNull builtinTypes, NotNull typeArena, NotNull normalizer, - NotNull iceReporter, NotNull scope) +Subtyping::Subtyping( + NotNull builtinTypes, + NotNull typeArena, + NotNull normalizer, + NotNull iceReporter, + NotNull scope +) : builtinTypes(builtinTypes) , arena(typeArena) , normalizer(normalizer) @@ -1243,8 +1254,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl std::vector results; if (auto subIter = subTable->props.find(name); subIter != subTable->props.end()) results.push_back(isCovariantWith(env, subIter->second, superProp, name)); - - if (subTable->indexer) + else if (subTable->indexer) { if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype) { @@ -1317,7 +1327,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas } SubtypingResult Subtyping::isCovariantWith( - SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable) + SubtypingEnvironment& env, + TypeId subTy, + const ClassType* subClass, + TypeId superTy, + const TableType* superTable +) { SubtypingResult result{true}; @@ -1366,7 +1381,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim { if (auto stringTable = get(it->second.type())) result.orElse( - isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); + isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) + ); } } } @@ -1388,7 +1404,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing { if (auto stringTable = get(it->second.type())) result.orElse( - isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); + isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) + ); } } } @@ -1429,7 +1446,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop } SubtypingResult Subtyping::isCovariantWith( - SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm) + SubtypingEnvironment& env, + const std::shared_ptr& subNorm, + const std::shared_ptr& superNorm +) { if (!subNorm || !superNorm) return {false, true}; @@ -1540,7 +1560,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm } SubtypingResult Subtyping::isCovariantWith( - SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction) + SubtypingEnvironment& env, + const NormalizedFunctionType& subFunction, + const NormalizedFunctionType& superFunction +) { if (subFunction.isNever()) return {true}; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 3514ff65..630cb441 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -13,8 +13,10 @@ namespace Luau static bool isLiteral(const AstExpr* expr) { - return (expr->is() || expr->is() || expr->is() || expr->is() || - expr->is() || expr->is()); + return ( + expr->is() || expr->is() || expr->is() || expr->is() || + expr->is() || expr->is() + ); } // A fast approximation of subTy <: superTy @@ -108,9 +110,17 @@ static std::optional extractMatchingTableType(std::vector& table return std::nullopt; } -TypeId matchLiteralType(NotNull> astTypes, NotNull> astExpectedTypes, - NotNull builtinTypes, NotNull arena, NotNull unifier, TypeId expectedType, TypeId exprType, - const AstExpr* expr, std::vector& toBlock) +TypeId matchLiteralType( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull builtinTypes, + NotNull arena, + NotNull unifier, + TypeId expectedType, + TypeId exprType, + const AstExpr* expr, + std::vector& toBlock +) { /* * Table types that arise from literal table expressions have some @@ -208,7 +218,7 @@ TypeId matchLiteralType(NotNull> astTypes, if (auto exprTable = expr->as()) { - TableType* tableTy = getMutable(exprType); + TableType* const tableTy = getMutable(exprType); LUAU_ASSERT(tableTy); const TableType* expectedTableTy = get(expectedType); @@ -260,8 +270,17 @@ TypeId matchLiteralType(NotNull> astTypes, (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; (*astExpectedTypes)[item.value] = expectedTableTy->indexer->indexResultType; - TypeId matchedType = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, - expectedTableTy->indexer->indexResultType, propTy, item.value, toBlock); + TypeId matchedType = matchLiteralType( + astTypes, + astExpectedTypes, + builtinTypes, + arena, + unifier, + expectedTableTy->indexer->indexResultType, + propTy, + item.value, + toBlock + ); if (tableTy->indexer) unifier->unify(matchedType, tableTy->indexer->indexResultType); @@ -334,8 +353,17 @@ TypeId matchLiteralType(NotNull> astTypes, LUAU_ASSERT(propTy); unifier->unify(expectedTableTy->indexer->indexType, builtinTypes->numberType); - TypeId matchedType = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, - expectedTableTy->indexer->indexResultType, *propTy, item.value, toBlock); + TypeId matchedType = matchLiteralType( + astTypes, + astExpectedTypes, + builtinTypes, + arena, + unifier, + expectedTableTy->indexer->indexResultType, + *propTy, + item.value, + toBlock + ); // if the index result type is the prop type, we can replace it with the matched type here. if (tableTy->indexer->indexResultType == *propTy) @@ -410,6 +438,15 @@ TypeId matchLiteralType(NotNull> astTypes, if (exprProp.readTy || exprProp.writeTy) tableTy->props[*key] = std::move(exprProp); } + + // If the expected table has an indexer, then the provided table can + // have one too. + // TODO: If the expected table also has an indexer, we might want to + // push the expected indexer's types into it. + if (expectedTableTy->indexer && !tableTy->indexer) + { + tableTy->indexer = expectedTableTy->indexer; + } } return exprType; diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index 66c91e39..aa2dc1e3 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -146,7 +146,8 @@ void StateDot::visitChildren(TypeId ty, int index) startNode(index); startNodeLabel(); - auto go = [&](auto&& t) { + auto go = [&](auto&& t) + { using T = std::decay_t; if constexpr (std::is_same_v) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 55a48baa..b5d8a98b 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -168,7 +168,8 @@ struct StringifierState DenseHashMap cycleNames{{}}; DenseHashMap cycleTpNames{{}}; Set seen{{}}; - // `$$$` was chosen as the tombstone for `usedNames` since it is not a valid name syntactically and is relatively short for string comparison reasons. + // `$$$` was chosen as the tombstone for `usedNames` since it is not a valid name syntactically and is relatively short for string comparison + // reasons. DenseHashSet usedNames{"$$$"}; size_t indentation = 0; @@ -356,10 +357,12 @@ struct TypeStringifier } Luau::visit( - [this, tv](auto&& t) { + [this, tv](auto&& t) + { return (*this)(tv, t); }, - tv->ty); + tv->ty + ); } void emitKey(const std::string& name) @@ -1104,10 +1107,12 @@ struct TypePackStringifier } Luau::visit( - [this, tp](auto&& t) { + [this, tp](auto&& t) + { return (*this)(tp, t); }, - tp->ty); + tp->ty + ); } void operator()(TypePackId, const TypePack& tp) @@ -1272,8 +1277,13 @@ void TypeStringifier::stringify(TypePackId tpid, const std::vector& cycles, const std::set& cycleTPs, DenseHashMap& cycleNames, - DenseHashMap& cycleTpNames, bool exhaustive) +static void assignCycleNames( + const std::set& cycles, + const std::set& cycleTPs, + DenseHashMap& cycleNames, + DenseHashMap& cycleTpNames, + bool exhaustive +) { int nextIndex = 1; @@ -1285,9 +1295,14 @@ static void assignCycleNames(const std::set& cycles, const std::set(follow(cycleTy)); !exhaustive && ttv && (ttv->syntheticName || ttv->name)) { // If we have a cycle type in type parameters, assign a cycle name for this named table - if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), [&](auto&& el) { - return cycles.count(follow(el)); - }) != ttv->instantiatedTypeParams.end()) + if (std::find_if( + ttv->instantiatedTypeParams.begin(), + ttv->instantiatedTypeParams.end(), + [&](auto&& el) + { + return cycles.count(follow(el)); + } + ) != ttv->instantiatedTypeParams.end()) cycleNames[cycleTy] = ttv->name ? *ttv->name : *ttv->syntheticName; continue; @@ -1381,9 +1396,14 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) state.exhaustive = true; std::vector> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()}; - std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) { - return a.second < b.second; - }); + std::sort( + sortedCycleNames.begin(), + sortedCycleNames.end(), + [](const auto& a, const auto& b) + { + return a.second < b.second; + } + ); bool semi = false; for (const auto& [cycleTy, name] : sortedCycleNames) @@ -1394,18 +1414,25 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tvs, cycleTy = cycleTy](auto&& t) { + [&tvs, cycleTy = cycleTy](auto&& t) + { return tvs(cycleTy, t); }, - cycleTy->ty); + cycleTy->ty + ); semi = true; } std::vector> sortedCycleTpNames(state.cycleTpNames.begin(), state.cycleTpNames.end()); - std::sort(sortedCycleTpNames.begin(), sortedCycleTpNames.end(), [](const auto& a, const auto& b) { - return a.second < b.second; - }); + std::sort( + sortedCycleTpNames.begin(), + sortedCycleTpNames.end(), + [](const auto& a, const auto& b) + { + return a.second < b.second; + } + ); TypePackStringifier tps{state}; @@ -1417,10 +1444,12 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tps, cycleTy = cycleTp](auto&& t) { + [&tps, cycleTy = cycleTp](auto&& t) + { return tps(cycleTy, t); }, - cycleTp->ty); + cycleTp->ty + ); semi = true; } @@ -1474,9 +1503,14 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) state.exhaustive = true; std::vector> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()}; - std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) { - return a.second < b.second; - }); + std::sort( + sortedCycleNames.begin(), + sortedCycleNames.end(), + [](const auto& a, const auto& b) + { + return a.second < b.second; + } + ); bool semi = false; for (const auto& [cycleTy, name] : sortedCycleNames) @@ -1487,18 +1521,25 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tvs, cycleTy = cycleTy](auto t) { + [&tvs, cycleTy = cycleTy](auto t) + { return tvs(cycleTy, t); }, - cycleTy->ty); + cycleTy->ty + ); semi = true; } std::vector> sortedCycleTpNames{state.cycleTpNames.begin(), state.cycleTpNames.end()}; - std::sort(sortedCycleTpNames.begin(), sortedCycleTpNames.end(), [](const auto& a, const auto& b) { - return a.second < b.second; - }); + std::sort( + sortedCycleTpNames.begin(), + sortedCycleTpNames.end(), + [](const auto& a, const auto& b) + { + return a.second < b.second; + } + ); TypePackStringifier tps{tvs.state}; @@ -1510,10 +1551,12 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tps, cycleTp = cycleTp](auto t) { + [&tps, cycleTp = cycleTp](auto t) + { return tps(cycleTp, t); }, - cycleTp->ty); + cycleTp->ty + ); semi = true; } @@ -1713,10 +1756,12 @@ std::string toStringVector(const std::vector& types, ToStringOptions& op std::string toString(const Constraint& constraint, ToStringOptions& opts) { - auto go = [&opts](auto&& c) -> std::string { + auto go = [&opts](auto&& c) -> std::string + { using T = std::decay_t; - auto tos = [&opts](auto&& a) { + auto tos = [&opts](auto&& a) + { return toString(a, opts); }; diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index d78bf157..a42882ed 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -28,8 +28,8 @@ bool isIdentifierChar(char c) return isIdentifierStartChar(c) || isDigit(c); } -const std::vector keywords = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", - "not", "or", "repeat", "return", "then", "true", "until", "while"}; +const std::vector keywords = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", + "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}; } // namespace @@ -844,6 +844,15 @@ struct Printer visualizeTypeAnnotation(*a->type); } } + else if (const auto& t = program.as()) + { + if (writeTypes) + { + writer.keyword("type function"); + writer.identifier(t->name.value); + visualizeFunctionBody(*t->body); + } + } else if (const auto& a = program.as()) { writer.symbol("(error-stat"); diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 6446570c..bde7751a 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -469,34 +469,44 @@ std::optional TxnLog::getLevel(TypeId ty) const TypeId TxnLog::follow(TypeId ty) const { - return Luau::follow(ty, this, [](const void* ctx, TypeId ty) -> TypeId { - const TxnLog* self = static_cast(ctx); - PendingType* state = self->pending(ty); + return Luau::follow( + ty, + this, + [](const void* ctx, TypeId ty) -> TypeId + { + const TxnLog* self = static_cast(ctx); + PendingType* state = self->pending(ty); - if (state == nullptr) - return ty; + if (state == nullptr) + return ty; - // Ugly: Fabricate a TypeId that doesn't adhere to most of the invariants - // that normally apply. This is safe because follow will only call get<> - // on the returned pointer. - return const_cast(&state->pending); - }); + // Ugly: Fabricate a TypeId that doesn't adhere to most of the invariants + // that normally apply. This is safe because follow will only call get<> + // on the returned pointer. + return const_cast(&state->pending); + } + ); } TypePackId TxnLog::follow(TypePackId tp) const { - return Luau::follow(tp, this, [](const void* ctx, TypePackId tp) -> TypePackId { - const TxnLog* self = static_cast(ctx); - PendingTypePack* state = self->pending(tp); + return Luau::follow( + tp, + this, + [](const void* ctx, TypePackId tp) -> TypePackId + { + const TxnLog* self = static_cast(ctx); + PendingTypePack* state = self->pending(tp); - if (state == nullptr) - return tp; + if (state == nullptr) + return tp; - // Ugly: Fabricate a TypePackId that doesn't adhere to most of the - // invariants that normally apply. This is safe because follow will - // only call get<> on the returned pointer. - return const_cast(&state->pending); - }); + // Ugly: Fabricate a TypePackId that doesn't adhere to most of the + // invariants that normally apply. This is safe because follow will + // only call get<> on the returned pointer. + return const_cast(&state->pending); + } + ); } std::pair, std::vector> TxnLog::getChanges() const diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index ce09e510..ffc4a97e 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -58,9 +58,15 @@ TypeId follow(TypeId t) TypeId follow(TypeId t, FollowOption followOption) { - return follow(t, followOption, nullptr, [](const void*, TypeId t) -> TypeId { - return t; - }); + return follow( + t, + followOption, + nullptr, + [](const void*, TypeId t) -> TypeId + { + return t; + } + ); } TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeId)) @@ -70,7 +76,8 @@ TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeI TypeId follow(TypeId t, FollowOption followOption, const void* context, TypeId (*mapper)(const void*, TypeId)) { - auto advance = [followOption, context, mapper](TypeId ty) -> std::optional { + auto advance = [followOption, context, mapper](TypeId ty) -> std::optional + { TypeId mapped = mapper(context, ty); if (auto btv = get>(mapped)) @@ -259,7 +266,8 @@ bool isOverloadedFunction(TypeId ty) if (!get(follow(ty))) return false; - auto isFunction = [](TypeId part) -> bool { + auto isFunction = [](TypeId part) -> bool + { return get(part); }; @@ -567,7 +575,11 @@ void BlockedType::replaceOwner(Constraint* newOwner) } PendingExpansionType::PendingExpansionType( - std::optional prefix, AstName name, std::vector typeArguments, std::vector packArguments) + std::optional prefix, + AstName name, + std::vector typeArguments, + std::vector packArguments +) : prefix(prefix) , name(name) , typeArguments(typeArguments) @@ -596,7 +608,13 @@ FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retT } FunctionType::FunctionType( - TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) + TypeLevel level, + Scope* scope, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn, + bool hasSelf +) : definition(std::move(defn)) , level(level) , scope(scope) @@ -606,8 +624,14 @@ FunctionType::FunctionType( { } -FunctionType::FunctionType(std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, - std::optional defn, bool hasSelf) +FunctionType::FunctionType( + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn, + bool hasSelf +) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) @@ -617,8 +641,15 @@ FunctionType::FunctionType(std::vector generics, std::vector { } -FunctionType::FunctionType(TypeLevel level, std::vector generics, std::vector genericPacks, TypePackId argTypes, - TypePackId retTypes, std::optional defn, bool hasSelf) +FunctionType::FunctionType( + TypeLevel level, + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn, + bool hasSelf +) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) @@ -629,8 +660,16 @@ FunctionType::FunctionType(TypeLevel level, std::vector generics, std::v { } -FunctionType::FunctionType(TypeLevel level, Scope* scope, std::vector generics, std::vector genericPacks, TypePackId argTypes, - TypePackId retTypes, std::optional defn, bool hasSelf) +FunctionType::FunctionType( + TypeLevel level, + Scope* scope, + std::vector generics, + std::vector genericPacks, + TypePackId argTypes, + TypePackId retTypes, + std::optional defn, + bool hasSelf +) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) @@ -644,8 +683,15 @@ FunctionType::FunctionType(TypeLevel level, Scope* scope, std::vector ge Property::Property() {} -Property::Property(TypeId readTy, bool deprecated, const std::string& deprecatedSuggestion, std::optional location, const Tags& tags, - const std::optional& documentationSymbol, std::optional typeLocation) +Property::Property( + TypeId readTy, + bool deprecated, + const std::string& deprecatedSuggestion, + std::optional location, + const Tags& tags, + const std::optional& documentationSymbol, + std::optional typeLocation +) : deprecated(deprecated) , deprecatedSuggestion(deprecatedSuggestion) , location(location) @@ -953,9 +999,15 @@ Type& Type::operator=(const Type& rhs) return *this; } -TypeId makeFunction(TypeArena& arena, std::optional selfType, std::initializer_list generics, - std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list paramNames, - std::initializer_list retTypes); +TypeId makeFunction( + TypeArena& arena, + std::optional selfType, + std::initializer_list generics, + std::initializer_list genericPacks, + std::initializer_list paramTypes, + std::initializer_list paramNames, + std::initializer_list retTypes +); TypeId makeStringMetatable(NotNull builtinTypes); // BuiltinDefinitions.cpp diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index 0ef128d1..a288cfbe 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -166,7 +166,8 @@ public: } return allocator->alloc( - Location(), std::nullopt, AstName(ttv.name->c_str()), std::nullopt, Location(), parameters.size != 0, parameters); + Location(), std::nullopt, AstName(ttv.name->c_str()), std::nullopt, Location(), parameters.size != 0, parameters + ); } if (hasSeen(&ttv)) @@ -319,7 +320,8 @@ public: retTailAnnotation = rehydrate(*retTail); return allocator->alloc( - Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}); + Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation} + ); } AstType* operator()(const Unifiable::Error&) { @@ -328,7 +330,8 @@ public: AstType* operator()(const GenericType& gtv) { return allocator->alloc( - Location(), std::nullopt, AstName(getName(allocator, syntheticNames, gtv)), std::nullopt, Location()); + Location(), std::nullopt, AstName(getName(allocator, syntheticNames, gtv)), std::nullopt, Location() + ); } AstType* operator()(const Unifiable::Bound& bound) { diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 167e87b1..f53c994e 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -442,8 +442,12 @@ struct TypeChecker2 return instance; seenTypeFunctionInstances.insert(instance); - ErrorVec errors = reduceTypeFunctions(instance, location, - TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) + ErrorVec errors = reduceTypeFunctions( + instance, + location, + TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, + true + ) .errors; if (!isErrorSuppressing(location, instance)) reportErrors(std::move(errors)); @@ -488,7 +492,8 @@ struct TypeChecker2 { TypeId argTy = lookupAnnotation(ref->parameters.data[0].type); luauPrintLine(format( - "_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str())); + "_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str() + )); return follow(argTy); } } @@ -597,6 +602,8 @@ struct TypeChecker2 return visit(s); else if (auto s = stat->as()) return visit(s); + else if (auto f = stat->as()) + return visit(f); else if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) @@ -728,7 +735,8 @@ struct TypeChecker2 local->values.data[local->values.size - 1]->is() ? CountMismatch::FunctionResult : CountMismatch::ExprListResult, }, - errorLocation); + errorLocation + ); } } } @@ -744,7 +752,8 @@ struct TypeChecker2 testIsSubtype(builtinTypes->numberType, annotatedType, forStatement->var->location); } - auto checkNumber = [this](AstExpr* expr) { + auto checkNumber = [this](AstExpr* expr) + { if (!expr) return; @@ -839,7 +848,8 @@ struct TypeChecker2 } TypeId iteratorTy = follow(iteratorTypes.head[0]); - auto checkFunction = [this, &arena, &forInStatement, &variableTypes](const FunctionType* iterFtv, std::vector iterTys, bool isMm) { + auto checkFunction = [this, &arena, &forInStatement, &variableTypes](const FunctionType* iterFtv, std::vector iterTys, bool isMm) + { if (iterTys.size() < 1 || iterTys.size() > 3) { if (isMm) @@ -856,7 +866,8 @@ struct TypeChecker2 { if (isMm) reportError( - GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values)); + GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values) + ); else reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location); } @@ -1143,6 +1154,13 @@ struct TypeChecker2 visit(stat->type); } + void visit(AstStatTypeFunction* stat) + { + // TODO: add type checking for user-defined type functions + + reportError(TypeError{stat->location, GenericError{"This syntax is not supported"}}); + } + void visit(AstTypeList types) { for (AstType* type : types.types) @@ -1349,11 +1367,6 @@ struct TypeChecker2 args.head.push_back(lookupType(indexExpr->expr)); argExprs.push_back(indexExpr->expr); } - else if (findMetatableEntry(builtinTypes, module->errors, *originalCallTy, "__call", call->func->location)) - { - args.head.insert(args.head.begin(), lookupType(call->func)); - argExprs.push_back(call->func); - } for (size_t i = 0; i < call->args.size; ++i) { @@ -1698,12 +1711,17 @@ struct TypeChecker2 // together. For now, this will work. reportError( GenericError{format( - "Parameter '%s' has been reduced to never. This function is not callable with any possible value.", arg->name.value)}, - arg->location); + "Parameter '%s' has been reduced to never. This function is not callable with any possible value.", arg->name.value + )}, + arg->location + ); for (const auto& [site, component] : *contributors) - reportError(ExtraInformation{format("Parameter '%s' is required to be a subtype of '%s' here.", arg->name.value, - toString(component).c_str())}, - site); + reportError( + ExtraInformation{ + format("Parameter '%s' is required to be a subtype of '%s' here.", arg->name.value, toString(component).c_str()) + }, + site + ); } } @@ -1739,8 +1757,10 @@ struct TypeChecker2 { TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); if (result.shouldRecommendAnnotation) - reportError(ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, - fn->location); + reportError( + ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, + fn->location + ); } } } @@ -1881,9 +1901,12 @@ struct TypeChecker2 if ((get(leftType) || get(leftType) || get(leftType)) && !isEquality && !isLogical) { auto name = getIdentifierOfBaseVar(expr->left); - reportError(CannotInferBinaryOperation{expr->op, name, - isComparison ? CannotInferBinaryOperation::OpKind::Comparison : CannotInferBinaryOperation::OpKind::Operation}, - expr->location); + reportError( + CannotInferBinaryOperation{ + expr->op, name, isComparison ? CannotInferBinaryOperation::OpKind::Comparison : CannotInferBinaryOperation::OpKind::Operation + }, + expr->location + ); return leftType; } @@ -1897,7 +1920,8 @@ struct TypeChecker2 if (isEquality && !matches) { - auto testUnion = [&matches, builtinTypes = this->builtinTypes](const UnionType* utv, std::optional otherMt) { + auto testUnion = [&matches, builtinTypes = this->builtinTypes](const UnionType* utv, std::optional otherMt) + { for (TypeId option : utv) { if (getMetatable(follow(option), builtinTypes) == otherMt) @@ -1929,9 +1953,15 @@ struct TypeChecker2 if (!matches && isComparison) { - reportError(GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", - toString(leftType).c_str(), toString(rightType).c_str(), toString(expr->op).c_str())}, - expr->location); + reportError( + GenericError{format( + "Types %s and %s cannot be compared with %s because they do not have the same metatable", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str() + )}, + expr->location + ); return builtinTypes->errorRecoveryType(); } @@ -2034,17 +2064,29 @@ struct TypeChecker2 { if (isComparison) { - reportError(GenericError{format( - "Types '%s' and '%s' cannot be compared with %s because neither type's metatable has a '%s' metamethod", - toString(leftType).c_str(), toString(rightType).c_str(), toString(expr->op).c_str(), it->second)}, - expr->location); + reportError( + GenericError{format( + "Types '%s' and '%s' cannot be compared with %s because neither type's metatable has a '%s' metamethod", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str(), + it->second + )}, + expr->location + ); } else { - reportError(GenericError{format( - "Operator %s is not applicable for '%s' and '%s' because neither type's metatable has a '%s' metamethod", - toString(expr->op).c_str(), toString(leftType).c_str(), toString(rightType).c_str(), it->second)}, - expr->location); + reportError( + GenericError{format( + "Operator %s is not applicable for '%s' and '%s' because neither type's metatable has a '%s' metamethod", + toString(expr->op).c_str(), + toString(leftType).c_str(), + toString(rightType).c_str(), + it->second + )}, + expr->location + ); } return builtinTypes->errorRecoveryType(); @@ -2053,15 +2095,27 @@ struct TypeChecker2 { if (isComparison) { - reportError(GenericError{format("Types '%s' and '%s' cannot be compared with %s because neither type has a metatable", - toString(leftType).c_str(), toString(rightType).c_str(), toString(expr->op).c_str())}, - expr->location); + reportError( + GenericError{format( + "Types '%s' and '%s' cannot be compared with %s because neither type has a metatable", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str() + )}, + expr->location + ); } else { - reportError(GenericError{format("Operator %s is not applicable for '%s' and '%s' because neither type has a metatable", - toString(expr->op).c_str(), toString(leftType).c_str(), toString(rightType).c_str())}, - expr->location); + reportError( + GenericError{format( + "Operator %s is not applicable for '%s' and '%s' because neither type has a metatable", + toString(expr->op).c_str(), + toString(leftType).c_str(), + toString(rightType).c_str() + )}, + expr->location + ); } return builtinTypes->errorRecoveryType(); @@ -2111,9 +2165,15 @@ struct TypeChecker2 return builtinTypes->booleanType; } - reportError(GenericError{format("Types '%s' and '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), - toString(rightType).c_str(), toString(expr->op).c_str())}, - expr->location); + reportError( + GenericError{format( + "Types '%s' and '%s' cannot be compared with relational operator %s", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str() + )}, + expr->location + ); return builtinTypes->errorRecoveryType(); } @@ -2297,13 +2357,23 @@ struct TypeChecker2 size_t typesRequired = alias->typeParams.size(); size_t packsRequired = alias->typePackParams.size(); - bool hasDefaultTypes = std::any_of(alias->typeParams.begin(), alias->typeParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); + bool hasDefaultTypes = std::any_of( + alias->typeParams.begin(), + alias->typeParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); - bool hasDefaultPacks = std::any_of(alias->typePackParams.begin(), alias->typePackParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); + bool hasDefaultPacks = std::any_of( + alias->typePackParams.begin(), + alias->typePackParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); if (!ty->hasParameterList) { @@ -2385,13 +2455,15 @@ struct TypeChecker2 if (typesProvided != typesRequired || packsProvided != packsRequired) { - reportError(IncorrectGenericParameterCount{ - /* name */ ty->name.value, - /* typeFun */ *alias, - /* actualParameters */ typesProvided, - /* actualPackParameters */ packsProvided, - }, - ty->location); + reportError( + IncorrectGenericParameterCount{ + /* name */ ty->name.value, + /* typeFun */ *alias, + /* actualParameters */ typesProvided, + /* actualPackParameters */ packsProvided, + }, + ty->location + ); } } else @@ -2403,7 +2475,8 @@ struct TypeChecker2 ty->name.value, SwappedGenericTypeParameter::Kind::Type, }, - ty->location); + ty->location + ); } else { @@ -2501,7 +2574,8 @@ struct TypeChecker2 tp->genericName.value, SwappedGenericTypeParameter::Kind::Pack, }, - tp->location); + tp->location + ); } else { @@ -2715,8 +2789,14 @@ struct TypeChecker2 * contains the prop, and * * A vector of types that do not contain the prop. */ - PropertyTypes lookupProp(const NormalizedType* norm, const std::string& prop, ValueContext context, const Location& location, - TypeId astIndexExprType, std::vector& errors) + PropertyTypes lookupProp( + const NormalizedType* norm, + const std::string& prop, + ValueContext context, + const Location& location, + TypeId astIndexExprType, + std::vector& errors + ) { std::vector typesOfProp; std::vector typesMissingTheProp; @@ -2724,7 +2804,8 @@ struct TypeChecker2 // this is `false` if we ever hit the resource limits during any of our uses of `fetch`. bool normValid = true; - auto fetch = [&](TypeId ty) { + auto fetch = [&](TypeId ty) + { NormalizationResult result = normalizer.isInhabited(ty); if (result == NormalizationResult::HitLimits) normValid = false; @@ -2875,8 +2956,15 @@ struct TypeChecker2 std::optional result; }; - PropertyType hasIndexTypeFromType(TypeId ty, const std::string& prop, ValueContext context, const Location& location, DenseHashSet& seen, - TypeId astIndexExprType, std::vector& errors) + PropertyType hasIndexTypeFromType( + TypeId ty, + const std::string& prop, + ValueContext context, + const Location& location, + DenseHashSet& seen, + TypeId astIndexExprType, + std::vector& errors + ) { // If we have already encountered this type, we must assume that some // other codepath will do the right thing and signal false if the @@ -2982,7 +3070,8 @@ struct TypeChecker2 std::string_view sv(utk->key); std::set candidates; - auto accumulate = [&](const TableType::Props& props) { + auto accumulate = [&](const TableType::Props& props) + { for (const auto& [name, ty] : props) { if (sv != name && equalsLower(sv, name)) @@ -3055,8 +3144,14 @@ struct TypeChecker2 } }; -void check(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, - const SourceModule& sourceModule, Module* module) +void check( + NotNull builtinTypes, + NotNull unifierState, + NotNull limits, + DcrLogger* logger, + const SourceModule& sourceModule, + Module* module +) { LUAU_TIMETRACE_SCOPE("check", "Typechecking"); @@ -3064,6 +3159,12 @@ void check(NotNull builtinTypes, NotNull unifi typeChecker.visit(sourceModule.root); + // if the only error we're producing is one about constraint solving being incomplete, we can silence it. + // this means we won't give this warning if types seem totally nonsensical, but there are no other errors. + // this is probably, on the whole, a good decision to not annoy users though. + if (module->errors.size() == 1 && get(module->errors[0])) + module->errors.clear(); + unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 28b18289..76fa18f6 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -112,8 +112,15 @@ struct TypeFunctionReducer // Local to the constraint being reduced. Location location; - TypeFunctionReducer(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclicTypes, - Location location, TypeFunctionContext ctx, bool force = false) + TypeFunctionReducer( + VecDeque queuedTys, + VecDeque queuedTps, + TypeOrTypePackIdSet shouldGuess, + std::vector cyclicTypes, + Location location, + TypeFunctionContext ctx, + bool force = false + ) : ctx(ctx) , queuedTys(std::move(queuedTys)) , queuedTps(std::move(queuedTps)) @@ -218,8 +225,12 @@ struct TypeFunctionReducer else if (!reduction.uninhabited && !force) { if (FFlag::DebugLuauLogTypeFamilies) - printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), - reduction.blockedPacks.size()); + printf( + "%s is irreducible; blocked on %zu types, %zu packs\n", + toString(subject, {true}).c_str(), + reduction.blockedTypes.size(), + reduction.blockedPacks.size() + ); for (TypeId b : reduction.blockedTypes) result.blockedTypes.insert(b); @@ -371,7 +382,8 @@ struct TypeFunctionReducer if (tryGuessing(subject)) return; - TypeFunctionReductionResult result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); + TypeFunctionReductionResult result = + tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleTypeFunctionReduction(subject, result); } } @@ -385,8 +397,15 @@ struct TypeFunctionReducer } }; -static FunctionGraphReductionResult reduceFunctionsInternal(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, - std::vector cyclics, Location location, TypeFunctionContext ctx, bool force) +static FunctionGraphReductionResult reduceFunctionsInternal( + VecDeque queuedTys, + VecDeque queuedTps, + TypeOrTypePackIdSet shouldGuess, + std::vector cyclics, + Location location, + TypeFunctionContext ctx, + bool force +) { TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; int iterationCount = 0; @@ -422,8 +441,15 @@ FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location loc if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFunctionsInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), location, ctx, force); + return reduceFunctionsInternal( + std::move(collector.tys), + std::move(collector.tps), + std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), + location, + ctx, + force + ); } FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force) @@ -442,8 +468,15 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFunctionsInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), location, ctx, force); + return reduceFunctionsInternal( + std::move(collector.tys), + std::move(collector.tps), + std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), + location, + ctx, + force + ); } bool isPending(TypeId ty, ConstraintSolver* solver) @@ -452,8 +485,14 @@ bool isPending(TypeId ty, ConstraintSolver* solver) } template -static std::optional> tryDistributeTypeFunctionApp(F f, TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, Args&&... args) +static std::optional> tryDistributeTypeFunctionApp( + F f, + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx, + Args&&... args +) { // op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d) bool uninhabited = false; @@ -529,7 +568,11 @@ static std::optional> tryDistributeTypeFunct } TypeFunctionReductionResult notTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -553,7 +596,11 @@ TypeFunctionReductionResult notTypeFunction( } TypeFunctionReductionResult lenTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -645,7 +692,11 @@ TypeFunctionReductionResult lenTypeFunction( } TypeFunctionReductionResult unmTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -744,8 +795,13 @@ NotNull TypeFunctionContext::pushConstraint(ConstraintV&& c) return newConstraint; } -TypeFunctionReductionResult numericBinopTypeFunction(TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, const std::string metamethod) +TypeFunctionReductionResult numericBinopTypeFunction( + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx, + const std::string metamethod +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -848,7 +904,11 @@ TypeFunctionReductionResult numericBinopTypeFunction(TypeId instance, co } TypeFunctionReductionResult addTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -860,7 +920,11 @@ TypeFunctionReductionResult addTypeFunction( } TypeFunctionReductionResult subTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -872,7 +936,11 @@ TypeFunctionReductionResult subTypeFunction( } TypeFunctionReductionResult mulTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -884,7 +952,11 @@ TypeFunctionReductionResult mulTypeFunction( } TypeFunctionReductionResult divTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -896,7 +968,11 @@ TypeFunctionReductionResult divTypeFunction( } TypeFunctionReductionResult idivTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -908,7 +984,11 @@ TypeFunctionReductionResult idivTypeFunction( } TypeFunctionReductionResult powTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -920,7 +1000,11 @@ TypeFunctionReductionResult powTypeFunction( } TypeFunctionReductionResult modTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -932,7 +1016,11 @@ TypeFunctionReductionResult modTypeFunction( } TypeFunctionReductionResult concatTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1040,7 +1128,11 @@ TypeFunctionReductionResult concatTypeFunction( } TypeFunctionReductionResult andTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1091,7 +1183,11 @@ TypeFunctionReductionResult andTypeFunction( } TypeFunctionReductionResult orTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1141,8 +1237,13 @@ TypeFunctionReductionResult orTypeFunction( return {overallResult.result, false, std::move(blockedTypes), {}}; } -static TypeFunctionReductionResult comparisonTypeFunction(TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, const std::string metamethod) +static TypeFunctionReductionResult comparisonTypeFunction( + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx, + const std::string metamethod +) { if (typeParams.size() != 2 || !packParams.empty()) @@ -1281,7 +1382,11 @@ static TypeFunctionReductionResult comparisonTypeFunction(TypeId instanc } TypeFunctionReductionResult ltTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1293,7 +1398,11 @@ TypeFunctionReductionResult ltTypeFunction( } TypeFunctionReductionResult leTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1305,7 +1414,11 @@ TypeFunctionReductionResult leTypeFunction( } TypeFunctionReductionResult eqTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1436,7 +1549,11 @@ struct FindRefinementBlockers : TypeOnceVisitor TypeFunctionReductionResult refineTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1521,7 +1638,11 @@ TypeFunctionReductionResult refineTypeFunction( } TypeFunctionReductionResult singletonTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1558,7 +1679,11 @@ TypeFunctionReductionResult singletonTypeFunction( } TypeFunctionReductionResult unionTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (!packParams.empty()) { @@ -1619,7 +1744,11 @@ TypeFunctionReductionResult unionTypeFunction( TypeFunctionReductionResult intersectTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (!packParams.empty()) { @@ -1726,7 +1855,11 @@ bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& se } TypeFunctionReductionResult keyofFunctionImpl( - const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx, + bool isRaw +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1843,7 +1976,11 @@ TypeFunctionReductionResult keyofFunctionImpl( } TypeFunctionReductionResult keyofTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1855,7 +1992,11 @@ TypeFunctionReductionResult keyofTypeFunction( } TypeFunctionReductionResult rawkeyofTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1870,7 +2011,12 @@ TypeFunctionReductionResult rawkeyofTypeFunction( If found, appends that property to `result` and returns true Else, returns false */ bool searchPropsAndIndexer( - TypeId ty, TableType::Props tblProps, std::optional tblIndexer, DenseHashSet& result, NotNull ctx) + TypeId ty, + TableType::Props tblProps, + std::optional tblIndexer, + DenseHashSet& result, + NotNull ctx +) { ty = follow(ty); @@ -1961,7 +2107,11 @@ bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet& result, indexer refers to the type that is used to access indexee Example: index => `Person` is the indexee and `"name"` is the indexer */ TypeFunctionReductionResult indexFunctionImpl( - const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx, + bool isRaw +) { TypeId indexeeTy = follow(typeParams.at(0)); std::shared_ptr indexeeNormTy = ctx->normalizer->normalize(indexeeTy); @@ -2053,9 +2203,15 @@ TypeFunctionReductionResult indexFunctionImpl( } // Call `follow()` on each element to resolve all Bound types before returning - std::transform(properties.begin(), properties.end(), properties.begin(), [](TypeId ty) { - return follow(ty); - }); + std::transform( + properties.begin(), + properties.end(), + properties.begin(), + [](TypeId ty) + { + return follow(ty); + } + ); // If the type being reduced to is a single type, no need to union if (properties.size() == 1) @@ -2065,7 +2221,11 @@ TypeFunctionReductionResult indexFunctionImpl( } TypeFunctionReductionResult indexTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -2077,7 +2237,11 @@ TypeFunctionReductionResult indexTypeFunction( } TypeFunctionReductionResult rawgetTypeFunction( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) + TypeId instance, + const std::vector& typeParams, + const std::vector& packParams, + NotNull ctx +) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -2119,7 +2283,8 @@ BuiltinTypeFunctions::BuiltinTypeFunctions() void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull scope) const { // make a type function for a one-argument type function - auto mkUnaryTypeFunction = [&](const TypeFunction* tf) { + auto mkUnaryTypeFunction = [&](const TypeFunction* tf) + { TypeId t = arena->addType(GenericType{"T"}); GenericTypeDefinition genericT{t}; @@ -2127,7 +2292,8 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s }; // make a type function for a two-argument type function - auto mkBinaryTypeFunction = [&](const TypeFunction* tf) { + auto mkBinaryTypeFunction = [&](const TypeFunction* tf) + { TypeId t = arena->addType(GenericType{"T"}); TypeId u = arena->addType(GenericType{"U"}); GenericTypeDefinition genericT{t}; diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index fac8ef16..d4a7c7c0 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -128,7 +128,10 @@ std::optional TypeFunctionReductionGuesser::guess(TypePackId tp) } TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunctionReductionForFunctionExpr( - const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy) + const AstExprFunction& expr, + const FunctionType* ftv, + TypeId retTy +) { InstanceCollector2 collector; collector.traverse(retTy); @@ -204,8 +207,9 @@ std::optional TypeFunctionReductionGuesser::guessType(TypeId arg) bool TypeFunctionReductionGuesser::isNumericBinopFunction(const TypeFunctionInstanceType& instance) { - return instance.function->name == "add" || instance.function->name == "sub" || instance.function->name == "mul" || instance.function->name == "div" || - instance.function->name == "idiv" || instance.function->name == "pow" || instance.function->name == "mod"; + return instance.function->name == "add" || instance.function->name == "sub" || instance.function->name == "mul" || + instance.function->name == "div" || instance.function->name == "idiv" || instance.function->name == "pow" || + instance.function->name == "mod"; } bool TypeFunctionReductionGuesser::isComparisonFunction(const TypeFunctionInstanceType& instance) @@ -350,7 +354,8 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFunctio TypeId lhsTy = follow(instance->typeArguments[0]); TypeId rhsTy = follow(instance->typeArguments[1]); - auto comparisonInference = [&](TypeId op) -> TypeFunctionInferenceResult { + auto comparisonInference = [&](TypeId op) -> TypeFunctionInferenceResult + { return TypeFunctionInferenceResult{{op, op}, builtins->booleanType}; }; diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 7df90628..7b7d6fae 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -31,9 +31,7 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) -LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false) LUAU_FASTFLAGVARIABLE(LuauReusableSubstitutions, false) @@ -294,13 +292,6 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo currentModule->cancelled = true; } - if (FFlag::DebugLuauSharedSelf) - { - for (auto& [ty, scope] : deferredQuantification) - Luau::quantify(ty, scope->level); - deferredQuantification.clear(); - } - if (get(follow(moduleScope->returnType))) moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt}); else @@ -379,6 +370,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program) ice("Should not be calling two-argument check() on a function statement", program.location); else if (auto typealias = program.as()) return check(scope, *typealias); + else if (auto typefunction = program.as()) + return check(scope, *typefunction); else if (auto global = program.as()) { TypeId globalType = resolveType(scope, *global->type); @@ -517,7 +510,8 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, std::unordered_map> functionDecls; - auto checkBody = [&](AstStat* stat) { + auto checkBody = [&](AstStat* stat) + { if (auto fun = stat->as()) { LUAU_ASSERT(functionDecls.count(stat)); @@ -581,39 +575,15 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, } else if (auto fun = (*protoIter)->as()) { - std::optional selfType; + std::optional selfType; // TODO clip std::optional expectedType; - if (FFlag::DebugLuauSharedSelf) + if (!fun->func->self) { if (auto name = fun->name->as()) { - TypeId baseTy = checkExpr(scope, *name->expr).type; - tablify(baseTy); - - if (!fun->func->self) - expectedType = getIndexTypeFromType(scope, baseTy, name->index.value, name->indexLocation, /* addErrors= */ false); - else if (auto ttv = getMutableTableType(baseTy)) - { - if (!baseTy->persistent && ttv->state != TableState::Sealed && !ttv->selfTy) - { - ttv->selfTy = anyIfNonstrict(freshType(ttv->level)); - deferredQuantification.push_back({baseTy, scope}); - } - - selfType = ttv->selfTy; - } - } - } - else - { - if (!fun->func->self) - { - if (auto name = fun->name->as()) - { - TypeId exprTy = checkExpr(scope, *name->expr).type; - expectedType = getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false); - } + TypeId exprTy = checkExpr(scope, *name->expr).type; + expectedType = getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false); } } @@ -1563,14 +1533,26 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& ty // Additionally, we can't modify types that come from other modules if (ttv->name || follow(ty)->owningArena != ¤tModule->internalTypes) { - bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(), - binding->typeParams.end(), [](auto&& itp, auto&& tp) { + bool sameTys = std::equal( + ttv->instantiatedTypeParams.begin(), + ttv->instantiatedTypeParams.end(), + binding->typeParams.begin(), + binding->typeParams.end(), + [](auto&& itp, auto&& tp) + { return itp == tp.ty; - }); - bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(), binding->typePackParams.begin(), - binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) { + } + ); + bool sameTps = std::equal( + ttv->instantiatedTypePackParams.begin(), + ttv->instantiatedTypePackParams.end(), + binding->typePackParams.begin(), + binding->typePackParams.end(), + [](auto&& itpp, auto&& tpp) + { return itpp == tpp.tp; - }); + } + ); // Copy can be skipped if this is an identical alias if (!ttv->name || ttv->name != name || !sameTys || !sameTps) @@ -1630,6 +1612,13 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& ty return ControlFlow::None; } +ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeFunction& typefunction) +{ + reportError(TypeError{typefunction.location, GenericError{"This syntax is not supported"}}); + + return ControlFlow::None; +} + void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel) { Name name = typealias.name.value; @@ -1704,8 +1693,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de if (!get(follow(*superTy))) { - reportError(declaredClass.location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)}); + reportError( + declaredClass.location, + GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)} + ); incorrectClassDefinitions.insert(&declaredClass); return; } @@ -1852,15 +1843,27 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti std::vector genericTys; genericTys.reserve(generics.size()); - std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) { - return el.ty; - }); + std::transform( + generics.begin(), + generics.end(), + std::back_inserter(genericTys), + [](auto&& el) + { + return el.ty; + } + ); std::vector genericTps; genericTps.reserve(genericPacks.size()); - std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) { - return el.tp; - }); + std::transform( + genericPacks.begin(), + genericPacks.end(), + std::back_inserter(genericTps), + [](auto&& el) + { + return el.tp; + } + ); TypePackId argPack = resolveTypePack(funScope, global.params); TypePackId retPack = resolveTypePack(funScope, global.retTypes); @@ -2085,7 +2088,12 @@ std::optional TypeChecker::findMetatableEntry(TypeId type, std::string e } std::optional TypeChecker::getIndexTypeFromType( - const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors) + const ScopePtr& scope, + TypeId type, + const Name& name, + const Location& location, + bool addErrors +) { size_t errorCount = currentModule->errors.size(); @@ -2098,7 +2106,12 @@ std::optional TypeChecker::getIndexTypeFromType( } std::optional TypeChecker::getIndexTypeFromTypeImpl( - const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors) + const ScopePtr& scope, + TypeId type, + const Name& name, + const Location& location, + bool addErrors +) { type = follow(type); @@ -2297,7 +2310,11 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp } TypeId TypeChecker::checkExprTable( - const ScopePtr& scope, const AstExprTable& expr, const std::vector>& fieldTypes, std::optional expectedType) + const ScopePtr& scope, + const AstExprTable& expr, + const std::vector>& fieldTypes, + std::optional expectedType +) { TableType::Props props; std::optional indexer; @@ -2526,8 +2543,10 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp return WithPredicate{retType}; } - reportError(expr.location, - GenericError{format("Unary operator '%s' not supported by type '%s'", toString(expr.op).c_str(), toString(operandType).c_str())}); + reportError( + expr.location, + GenericError{format("Unary operator '%s' not supported by type '%s'", toString(expr.op).c_str(), toString(operandType).c_str())} + ); return WithPredicate{errorRecoveryType(scope)}; } @@ -2674,7 +2693,8 @@ static std::optional areEqComparable(NotNull arena, NotNull(t); }; @@ -2705,9 +2725,15 @@ static std::optional areEqComparable(NotNull arena, NotNull(leftType) || isBoolean(leftType))) { - reportError(expr.location, GenericError{format("Type '%s' cannot be compared with relational operator %s", - toString(leftType).c_str(), toString(expr.op).c_str())}); + reportError( + expr.location, + GenericError{ + format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) + } + ); } } @@ -2879,8 +2914,14 @@ TypeId TypeChecker::checkRelationalOperation( if (!matches) { reportError( - expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable", - toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())}); + expr.location, + GenericError{format( + "Types %s and %s cannot be compared with %s because they do not have the same metatable", + toString(lhsType).c_str(), + toString(rhsType).c_str(), + toString(expr.op).c_str() + )} + ); return errorRecoveryType(booleanType); } } @@ -2911,7 +2952,8 @@ TypeId TypeChecker::checkRelationalOperation( TypeId actualFunctionType = addType(FunctionType(scope->level, addTypePack({lhsType, rhsType}), addTypePack({booleanType}))); state.tryUnify( - instantiate(scope, actualFunctionType, expr.location), instantiate(scope, *metamethod, expr.location), /*isFunctionCall*/ true); + instantiate(scope, actualFunctionType, expr.location), instantiate(scope, *metamethod, expr.location), /*isFunctionCall*/ true + ); state.log.commit(); @@ -2921,7 +2963,8 @@ TypeId TypeChecker::checkRelationalOperation( else if (needsMetamethod) { reportError( - expr.location, GenericError{format("Table %s does not offer metamethod %s", toString(lhsType).c_str(), metamethodName.c_str())}); + expr.location, GenericError{format("Table %s does not offer metamethod %s", toString(lhsType).c_str(), metamethodName.c_str())} + ); return errorRecoveryType(booleanType); } } @@ -2935,8 +2978,12 @@ TypeId TypeChecker::checkRelationalOperation( if (needsMetamethod) { - reportError(expr.location, GenericError{format("Type %s cannot be compared with %s because it has no metatable", - toString(lhsType).c_str(), toString(expr.op).c_str())}); + reportError( + expr.location, + GenericError{ + format("Type %s cannot be compared with %s because it has no metatable", toString(lhsType).c_str(), toString(expr.op).c_str()) + } + ); return errorRecoveryType(booleanType); } @@ -3006,7 +3053,12 @@ TypeId TypeChecker::checkRelationalOperation( } TypeId TypeChecker::checkBinaryOperation( - const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates) + const ScopePtr& scope, + const AstExprBinary& expr, + TypeId lhsType, + TypeId rhsType, + const PredicateVec& predicates +) { switch (expr.op) { @@ -3057,7 +3109,8 @@ TypeId TypeChecker::checkBinaryOperation( if (typeCouldHaveMetatable(lhsType) || typeCouldHaveMetatable(rhsType)) { - auto checkMetatableCall = [this, &scope, &expr](TypeId fnt, TypeId lhst, TypeId rhst) -> TypeId { + auto checkMetatableCall = [this, &scope, &expr](TypeId fnt, TypeId lhst, TypeId rhst) -> TypeId + { TypeId actualFunctionType = instantiate(scope, fnt, expr.location); TypePackId arguments = addTypePack({lhst, rhst}); TypePackId retTypePack = freshTypePack(scope); @@ -3104,8 +3157,15 @@ TypeId TypeChecker::checkBinaryOperation( return checkMetatableCall(*fnt, rhsType, lhsType); } - reportError(expr.location, GenericError{format("Binary operator '%s' not supported by types '%s' and '%s'", toString(expr.op).c_str(), - toString(lhsType).c_str(), toString(rhsType).c_str())}); + reportError( + expr.location, + GenericError{format( + "Binary operator '%s' not supported by types '%s' and '%s'", + toString(expr.op).c_str(), + toString(lhsType).c_str(), + toString(rhsType).c_str() + )} + ); return errorRecoveryType(scope); } @@ -3537,7 +3597,8 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex // Primarily about detecting duplicates. TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level) { - auto freshTy = [&]() { + auto freshTy = [&]() + { return freshType(level); }; @@ -3610,8 +3671,14 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T // `(X) -> Y...`, but after typechecking the body, we cam unify `Y...` with `X` // to get type `(X) -> X`, then we quantify the free types to get the final // generic type `(a) -> a`. -std::pair TypeChecker::checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr, - std::optional originalName, std::optional selfType, std::optional expectedType) +std::pair TypeChecker::checkFunctionSignature( + const ScopePtr& scope, + int subLevel, + const AstExprFunction& expr, + std::optional originalName, + std::optional selfType, + std::optional expectedType +) { ScopePtr funScope = childFunctionScope(scope, expr.location, subLevel); @@ -3704,25 +3771,11 @@ std::pair TypeChecker::checkFunctionSignature(const ScopePtr& funScope->returnType = retPack; - if (FFlag::DebugLuauSharedSelf) + if (expr.self) { - if (expr.self) - { - // TODO: generic self types: CLI-39906 - TypeId selfTy = anyIfNonstrict(selfType ? *selfType : freshType(funScope)); - funScope->bindings[expr.self] = {selfTy, expr.self->location}; - argTypes.push_back(selfTy); - } - } - else - { - if (expr.self) - { - // TODO: generic self types: CLI-39906 - TypeId selfType = anyIfNonstrict(freshType(funScope)); - funScope->bindings[expr.self] = {selfType, expr.self->location}; - argTypes.push_back(selfType); - } + TypeId selfType = anyIfNonstrict(freshType(funScope)); + funScope->bindings[expr.self] = {selfType, expr.self->location}; + argTypes.push_back(selfType); } // Prepare expected argument type iterators if we have an expected function type @@ -3911,8 +3964,14 @@ WithPredicate TypeChecker::checkExprPackHelper(const ScopePtr& scope } } -void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funName, Unifier& state, TypePackId argPack, TypePackId paramPack, - const std::vector& argLocations) +void TypeChecker::checkArgumentList( + const ScopePtr& scope, + const AstExpr& funName, + Unifier& state, + TypePackId argPack, + TypePackId paramPack, + const std::vector& argLocations +) { /* Important terminology refresher: * A function requires parameters. @@ -3924,7 +3983,8 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam size_t paramIndex = 0; - auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() { + auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() + { // For this case, we want the error span to cover every errant extra parameter Location location = state.location; if (!argLocations.empty()) @@ -3936,8 +3996,10 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam namePath = *path; auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack); - state.reportError(TypeError{location, - CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath}}); + state.reportError(TypeError{ + location, + CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath} + }); }; while (true) @@ -4044,7 +4106,8 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam namePath = *path; state.reportError(TypeError{ - funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}}); + funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath} + }); return; } ++paramIter; @@ -4188,7 +4251,8 @@ WithPredicate TypeChecker::checkExprPackHelper(const ScopePtr& scope // We break this function up into a lambda here to limit our stack footprint. // The vectors used by this function aren't allocated until the lambda is actually called. - auto the_rest = [&]() -> WithPredicate { + auto the_rest = [&]() -> WithPredicate + { // checkExpr will log the pre-instantiated type of the function. // That's not nearly as interesting as the instantiated type, which will include details about how // generic functions are being instantiated for this particular callsite. @@ -4231,7 +4295,8 @@ WithPredicate TypeChecker::checkExprPackHelper(const ScopePtr& scope fn = follow(fn); if (auto ret = checkCallOverload( - scope, expr, fn, retPack, argPack, args, &argLocations, argListResult, overloadsThatMatchArgCount, overloadsThatDont, errors)) + scope, expr, fn, retPack, argPack, args, &argLocations, argListResult, overloadsThatMatchArgCount, overloadsThatDont, errors + )) return *ret; } @@ -4258,7 +4323,8 @@ std::vector> TypeChecker::getExpectedTypesForCall(const st { std::vector> expectedTypes; - auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) { + auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) + { if (index == expectedTypes.size()) { expectedTypes.push_back(ty); @@ -4317,9 +4383,19 @@ std::vector> TypeChecker::getExpectedTypesForCall(const st * If this was an optional, callers would have to pay the stack cost for the result. This is problematic * for functions that need to support recursion up to 600 levels deep. */ -std::unique_ptr> TypeChecker::checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, - TypePackId retPack, TypePackId argPack, TypePack* args, const std::vector* argLocations, const WithPredicate& argListResult, - std::vector& overloadsThatMatchArgCount, std::vector& overloadsThatDont, std::vector& errors) +std::unique_ptr> TypeChecker::checkCallOverload( + const ScopePtr& scope, + const AstExprCall& expr, + TypeId fn, + TypePackId retPack, + TypePackId argPack, + TypePack* args, + const std::vector* argLocations, + const WithPredicate& argListResult, + std::vector& overloadsThatMatchArgCount, + std::vector& overloadsThatDont, + std::vector& errors +) { LUAU_ASSERT(argLocations); @@ -4453,8 +4529,13 @@ std::unique_ptr> TypeChecker::checkCallOverload(const return nullptr; } -bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector& argLocations, - const std::vector& errors) +bool TypeChecker::handleSelfCallMismatch( + const ScopePtr& scope, + const AstExprCall& expr, + TypePack* args, + const std::vector& argLocations, + const std::vector& errors +) { // No overloads succeeded: Scan for one that would have worked had the user // used a.b() rather than a:b() or vice versa. @@ -4521,14 +4602,20 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal return false; } -void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack, - const std::vector& argLocations, const std::vector& overloads, const std::vector& overloadsThatMatchArgCount, - std::vector& errors) +void TypeChecker::reportOverloadResolutionError( + const ScopePtr& scope, + const AstExprCall& expr, + TypePackId retPack, + TypePackId argPack, + const std::vector& argLocations, + const std::vector& overloads, + const std::vector& overloadsThatMatchArgCount, + std::vector& errors +) { if (overloads.size() == 1) { - if (FFlag::LuauAlwaysCommitInferencesOfFunctionCalls) - errors.front().log.commit(); + errors.front().log.commit(); reportErrors(errors.front().errors); return; @@ -4551,14 +4638,18 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast const FunctionType* ftv = get(overload); - auto error = std::find_if(errors.begin(), errors.end(), [ftv](const OverloadErrorEntry& e) { - return ftv == e.fnTy; - }); + auto error = std::find_if( + errors.begin(), + errors.end(), + [ftv](const OverloadErrorEntry& e) + { + return ftv == e.fnTy; + } + ); LUAU_ASSERT(error != errors.end()); - if (FFlag::LuauAlwaysCommitInferencesOfFunctionCalls) - error->log.commit(); + error->log.commit(); reportErrors(error->errors); @@ -4601,14 +4692,21 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast return; } -WithPredicate TypeChecker::checkExprList(const ScopePtr& scope, const Location& location, const AstArray& exprs, - bool substituteFreeForNil, const std::vector& instantiateGenerics, const std::vector>& expectedTypes) +WithPredicate TypeChecker::checkExprList( + const ScopePtr& scope, + const Location& location, + const AstArray& exprs, + bool substituteFreeForNil, + const std::vector& instantiateGenerics, + const std::vector>& expectedTypes +) { bool uninhabitable = false; TypePackId pack = addTypePack(TypePack{}); PredicateVec predicates; // At the moment we will be pushing all predicate sets into this. Do we need some way to split them up? - auto insert = [&predicates](PredicateVec& vec) { + auto insert = [&predicates](PredicateVec& vec) + { for (Predicate& c : vec) predicates.push_back(std::move(c)); }; @@ -4875,20 +4973,10 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location { ty = follow(ty); - if (FFlag::DebugLuauSharedSelf) - { - if (auto ftv = get(ty)) - Luau::quantify(ty, scope->level); - else if (auto ttv = getTableType(ty); ttv && ttv->selfTy) - Luau::quantify(ty, scope->level); - } - else - { - const FunctionType* ftv = get(ty); + const FunctionType* ftv = get(ty); - if (ftv) - Luau::quantify(ty, scope->level); - } + if (ftv) + Luau::quantify(ty, scope->level); return ty; } @@ -5031,11 +5119,17 @@ LUAU_NOINLINE void TypeChecker::throwUserCancelError() void TypeChecker::prepareErrorsForDisplay(ErrorVec& errVec) { // Remove errors with names that were generated by recovery from a parse error - errVec.erase(std::remove_if(errVec.begin(), errVec.end(), - [](auto& err) { - return containsParseErrorName(err); - }), - errVec.end()); + errVec.erase( + std::remove_if( + errVec.begin(), + errVec.end(), + [](auto& err) + { + return containsParseErrorName(err); + } + ), + errVec.end() + ); for (auto& err : errVec) { @@ -5049,7 +5143,8 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d std::string_view sv(utk->key); std::set candidates; - auto accumulate = [&](const TableType::Props& props) { + auto accumulate = [&](const TableType::Props& props) + { for (const auto& [name, ty] : props) { if (sv != name && equalsLower(sv, name)) @@ -5103,25 +5198,30 @@ ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& locatio void TypeChecker::merge(RefinementMap& l, const RefinementMap& r) { - Luau::merge(l, r, [this](TypeId a, TypeId b) { - // TODO: normalize(UnionType{{a, b}}) - std::unordered_set set; + Luau::merge( + l, + r, + [this](TypeId a, TypeId b) + { + // TODO: normalize(UnionType{{a, b}}) + std::unordered_set set; - if (auto utv = get(follow(a))) - set.insert(begin(utv), end(utv)); - else - set.insert(a); + if (auto utv = get(follow(a))) + set.insert(begin(utv), end(utv)); + else + set.insert(a); - if (auto utv = get(follow(b))) - set.insert(begin(utv), end(utv)); - else - set.insert(b); + if (auto utv = get(follow(b))) + set.insert(begin(utv), end(utv)); + else + set.insert(b); - std::vector options(set.begin(), set.end()); - if (set.size() == 1) - return options[0]; - return addType(UnionType{std::move(options)}); - }); + std::vector options(set.begin(), set.end()); + if (set.size() == 1) + return options[0]; + return addType(UnionType{std::move(options)}); + } + ); } Unifier TypeChecker::mkUnifier(const ScopePtr& scope, const Location& location) @@ -5172,7 +5272,8 @@ TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess) TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense, TypeId emptySetTy) { - return [this, sense, emptySetTy](TypeId ty) -> std::optional { + return [this, sense, emptySetTy](TypeId ty) -> std::optional + { // any/error/free gets a special pass unconditionally because they can't be decided. if (get(ty) || get(ty) || get(ty)) return ty; @@ -5314,12 +5415,22 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno return tf->type; bool parameterCountErrorReported = false; - bool hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); - bool hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) { - return el.defaultValue.has_value(); - }); + bool hasDefaultTypes = std::any_of( + tf->typeParams.begin(), + tf->typeParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); + bool hasDefaultPacks = std::any_of( + tf->typePackParams.begin(), + tf->typePackParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); if (!lit->hasParameterList) { @@ -5442,7 +5553,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno { if (!parameterCountErrorReported) reportError( - TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}}); + TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}} + ); // Pad the types out with error recovery types while (typeParams.size() < tf->typeParams.size()) @@ -5451,13 +5563,26 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno typePackParams.push_back(errorRecoveryTypePack(scope)); } - bool sameTys = std::equal(typeParams.begin(), typeParams.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& tp) { - return itp == tp.ty; - }); + bool sameTys = std::equal( + typeParams.begin(), + typeParams.end(), + tf->typeParams.begin(), + tf->typeParams.end(), + [](auto&& itp, auto&& tp) + { + return itp == tp.ty; + } + ); bool sameTps = std::equal( - typePackParams.begin(), typePackParams.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itpp, auto&& tpp) { + typePackParams.begin(), + typePackParams.end(), + tf->typePackParams.begin(), + tf->typePackParams.end(), + [](auto&& itpp, auto&& tpp) + { return itpp == tpp.tp; - }); + } + ); // If the generic parameters and the type arguments are the same, we are about to // perform an identity substitution, which we can just short-circuit. @@ -5512,15 +5637,27 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno std::vector genericTys; genericTys.reserve(generics.size()); - std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) { - return el.ty; - }); + std::transform( + generics.begin(), + generics.end(), + std::back_inserter(genericTys), + [](auto&& el) + { + return el.ty; + } + ); std::vector genericTps; genericTps.reserve(genericPacks.size()); - std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) { - return el.tp; - }); + std::transform( + genericPacks.begin(), + genericPacks.end(), + std::back_inserter(genericTps), + [](auto&& el) + { + return el.tp; + } + ); TypeId fnType = addType(FunctionType{funcScope->level, std::move(genericTys), std::move(genericTps), argTypes, retTypes}); @@ -5641,8 +5778,13 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack return result; } -TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector& typeParams, - const std::vector& typePackParams, const Location& location) +TypeId TypeChecker::instantiateTypeFun( + const ScopePtr& scope, + const TypeFun& tf, + const std::vector& typeParams, + const std::vector& typePackParams, + const Location& location +) { if (tf.typeParams.empty() && tf.typePackParams.empty()) return tf.type; @@ -5706,8 +5848,14 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, return instantiated; } -GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, std::optional levelOpt, const AstNode& node, - const AstArray& genericNames, const AstArray& genericPackNames, bool useCache) +GenericTypeDefinitions TypeChecker::createGenericTypes( + const ScopePtr& scope, + std::optional levelOpt, + const AstNode& node, + const AstArray& genericNames, + const AstArray& genericPackNames, + bool useCache +) { LUAU_ASSERT(scope->parent); @@ -5835,7 +5983,8 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const } } - auto intoType = [this](const std::unordered_set& s) -> std::optional { + auto intoType = [this](const std::unordered_set& s) -> std::optional + { if (s.empty()) return std::nullopt; @@ -6022,7 +6171,8 @@ void TypeChecker::resolve(const OrPredicate& orP, RefinementMap& refis, const Sc void TypeChecker::resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense) { - auto predicate = [&](TypeId option) -> std::optional { + auto predicate = [&](TypeId option) -> std::optional + { // This by itself is not truly enough to determine that A is stronger than B or vice versa. bool optionIsSubtype = canUnify(option, isaP.ty, scope, isaP.location).empty(); bool targetIsSubtype = canUnify(isaP.ty, option, scope, isaP.location).empty(); @@ -6085,8 +6235,10 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r return; } - auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional mapsTo = std::nullopt) { - TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional { + auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional mapsTo = std::nullopt) + { + TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional + { if (sense && get(ty)) return mapsTo.value_or(ty); @@ -6117,22 +6269,31 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r return refine(isBuffer, bufferType); else if (typeguardP.kind == "table") { - return refine([](TypeId ty) -> bool { - return isTableIntersection(ty) || get(ty) || get(ty); - }); + return refine( + [](TypeId ty) -> bool + { + return isTableIntersection(ty) || get(ty) || get(ty); + } + ); } else if (typeguardP.kind == "function") { - return refine([](TypeId ty) -> bool { - return isOverloadedFunction(ty) || get(ty); - }); + return refine( + [](TypeId ty) -> bool + { + return isOverloadedFunction(ty) || get(ty); + } + ); } else if (typeguardP.kind == "userdata") { // For now, we don't really care about being accurate with userdata if the typeguard was using typeof. - return refine([](TypeId ty) -> bool { - return get(ty); - }); + return refine( + [](TypeId ty) -> bool + { + return get(ty); + } + ); } if (!typeguardP.isTypeof) @@ -6162,7 +6323,8 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense) { // This refinement will require success typing to do everything correctly. For now, we can get most of the way there. - auto options = [](TypeId ty) -> std::vector { + auto options = [](TypeId ty) -> std::vector + { if (auto utv = get(follow(ty))) return std::vector(begin(utv), end(utv)); return {ty}; @@ -6173,7 +6335,8 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable)) return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here. - auto predicate = [&](TypeId option) -> std::optional { + auto predicate = [&](TypeId option) -> std::optional + { if (!sense && isNil(eqP.type)) return (isUndecidable(option) || !isNil(option)) ? std::optional(option) : std::nullopt; diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index d94031ba..9f3924f0 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -257,14 +257,20 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs) TypePackId follow(TypePackId tp) { - return follow(tp, nullptr, [](const void*, TypePackId t) { - return t; - }); + return follow( + tp, + nullptr, + [](const void*, TypePackId t) + { + return t; + } + ); } TypePackId follow(TypePackId tp, const void* context, TypePackId (*mapper)(const void*, TypePackId)) { - auto advance = [context, mapper](TypePackId ty) -> std::optional { + auto advance = [context, mapper](TypePackId ty) -> std::optional + { TypePackId mapped = mapper(context, ty); if (const Unifiable::Bound* btv = get>(mapped)) diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 76a47341..29f5cfb5 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -534,7 +534,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot) std::stringstream result; bool first = true; - auto strComponent = [&](auto&& c) { + auto strComponent = [&](auto&& c) + { using T = std::decay_t; if constexpr (std::is_same_v) { @@ -626,7 +627,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot) static bool traverse(TraversalState& state, const Path& path) { - auto step = [&state](auto&& c) { + auto step = [&state](auto&& c) + { return state.traverse(c); }; diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index c2512ddc..b40805e9 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -24,7 +24,8 @@ bool occursCheck(TypeId needle, TypeId haystack) LUAU_ASSERT(get(needle) || get(needle)); haystack = follow(haystack); - auto checkHaystack = [needle](TypeId haystack) { + auto checkHaystack = [needle](TypeId haystack) + { return occursCheck(needle, haystack); }; @@ -92,7 +93,12 @@ std::optional findTableProperty(NotNull builtinTypes, Er } std::optional findMetatableEntry( - NotNull builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location) + NotNull builtinTypes, + ErrorVec& errors, + TypeId type, + const std::string& entry, + Location location +) { type = follow(type); @@ -120,13 +126,24 @@ std::optional findMetatableEntry( } std::optional findTablePropertyRespectingMeta( - NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location) + NotNull builtinTypes, + ErrorVec& errors, + TypeId ty, + const std::string& name, + Location location +) { return findTablePropertyRespectingMeta(builtinTypes, errors, ty, name, ValueContext::RValue, location); } std::optional findTablePropertyRespectingMeta( - NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location) + NotNull builtinTypes, + ErrorVec& errors, + TypeId ty, + const std::string& name, + ValueContext context, + Location location +) { if (get(ty)) return ty; @@ -217,7 +234,12 @@ std::pair> getParameterExtents(const TxnLog* log, } TypePack extendTypePack( - TypeArena& arena, NotNull builtinTypes, TypePackId pack, size_t length, std::vector> overrides) + TypeArena& arena, + NotNull builtinTypes, + TypePackId pack, + size_t length, + std::vector> overrides +) { TypePack result; diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 90d9e92e..3dc66d1d 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -19,7 +19,6 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false) -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false) LUAU_FASTFLAGVARIABLE(LuauUnifierShouldNotCopyError, false) @@ -329,7 +328,8 @@ TypePackId Widen::operator()(TypePackId tp) std::optional hasUnificationTooComplex(const ErrorVec& errors) { - auto isUnificationTooComplex = [](const TypeError& te) { + auto isUnificationTooComplex = [](const TypeError& te) + { return nullptr != get(te); }; @@ -342,7 +342,8 @@ std::optional hasUnificationTooComplex(const ErrorVec& errors) std::optional hasCountMismatch(const ErrorVec& errors) { - auto isCountMismatch = [](const TypeError& te) { + auto isCountMismatch = [](const TypeError& te) + { return nullptr != get(te); }; @@ -771,47 +772,7 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, Typ } } - if (FFlag::LuauAlwaysCommitInferencesOfFunctionCalls) - log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types}); - else - { - // even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option. - auto tryBind = [this, subTy](TypeId superOption) { - superOption = log.follow(superOption); - - // just skip if the superOption is not free-ish. - auto ttv = log.getMutable(superOption); - if (!log.is(superOption) && (!ttv || ttv->state != TableState::Free)) - return; - - // If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype - // test is successful. - if (auto subUnion = get(subTy)) - { - if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption)) - return; - } - - // Since we have already checked if S <: T, checking it again will not queue up the type for replacement. - // So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set. - if (log.haveSeen(subTy, superOption)) - { - // TODO: would it be nice for TxnLog::replace to do this? - if (log.is(superOption)) - log.bindTable(superOption, subTy); - else - log.replace(superOption, *subTy); - } - }; - - if (auto superUnion = log.getMutable(superTy)) - { - for (TypeId ty : superUnion) - tryBind(ty); - } - else - tryBind(superTy); - } + log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types}); if (unificationTooComplex) reportError(*unificationTooComplex); @@ -954,7 +915,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp return reportError(location, NormalizationTooComplex{}); else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) innerState.tryUnifyNormalizedTypes( - subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); + subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption + ); else innerState.tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); @@ -985,7 +947,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp failure = true; else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) reportError( - location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption, mismatchContext()}); + location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption, mismatchContext()} + ); else reportError(location, TypeMismatch{superTy, subTy, "none of the union options are compatible", mismatchContext()}); } @@ -1151,7 +1114,13 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* } void Unifier::tryUnifyNormalizedTypes( - TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional error) + TypeId subTy, + TypeId superTy, + const NormalizedType& subNorm, + const NormalizedType& superNorm, + std::string reason, + std::optional error +) { if (get(superNorm.tops)) return; @@ -1394,7 +1363,8 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy) if (subTyInfo && *subTyInfo) return false; - auto skipCacheFor = [this](TypeId ty) { + auto skipCacheFor = [this](TypeId ty) + { SkipCacheForType visitor{sharedState.skipCacheForType, types}; visitor.traverse(ty); @@ -1674,7 +1644,8 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal superIter.scope = scope.get(); subIter.scope = scope.get(); - auto mkFreshType = [this](Scope* scope, TypeLevel level) { + auto mkFreshType = [this](Scope* scope, TypeLevel level) + { if (FFlag::DebugLuauDeferredConstraintResolution) return freshType(NotNull{types}, builtinTypes, scope); else @@ -1970,8 +1941,16 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal if (auto e = hasUnificationTooComplex(innerState.errors)) reportError(*e); else if (!innerState.errors.empty() && innerState.firstPackErrorPos) - reportError(location, TypeMismatch{superTy, subTy, format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos), - innerState.errors.front(), mismatchContext()}); + reportError( + location, + TypeMismatch{ + superTy, + subTy, + format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos), + innerState.errors.front(), + mismatchContext() + } + ); else if (!innerState.errors.empty()) reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front(), mismatchContext()}); @@ -1985,8 +1964,16 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal else if (!innerState.errors.empty() && size(superFunction->retTypes) == 1 && finite(superFunction->retTypes)) reportError(location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front(), mismatchContext()}); else if (!innerState.errors.empty() && innerState.firstPackErrorPos) - reportError(location, TypeMismatch{superTy, subTy, format("Return #%d type is not compatible.", *innerState.firstPackErrorPos), - innerState.errors.front(), mismatchContext()}); + reportError( + location, + TypeMismatch{ + superTy, + subTy, + format("Return #%d type is not compatible.", *innerState.firstPackErrorPos), + innerState.errors.front(), + mismatchContext() + } + ); else if (!innerState.errors.empty()) reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front(), mismatchContext()}); } @@ -2402,7 +2389,8 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed) if (!superTable || superTable->state != TableState::Free) return reportError(location, TypeMismatch{osuperTy, osubTy, mismatchContext()}); - auto fail = [&](std::optional e) { + auto fail = [&](std::optional e) + { std::string reason = "The former's metatable does not satisfy the requirements."; if (e) reportError(location, TypeMismatch{osuperTy, osubTy, reason, *e, mismatchContext()}); @@ -2497,7 +2485,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) reportError(*e); else if (!innerState.errors.empty()) reportError( - location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()}); + location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()} + ); log.concat(std::move(innerState.log)); failure |= innerState.failure; @@ -2535,8 +2524,10 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) if (auto e = hasUnificationTooComplex(innerState.errors)) reportError(*e); else if (!innerState.errors.empty()) - reportError(TypeError{location, - TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()}}); + reportError(TypeError{ + location, + TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()} + }); else if (!missingProperty) { log.concat(std::move(innerState.log)); @@ -2574,7 +2565,8 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) if (reversed) std::swap(superTy, subTy); - auto fail = [&]() { + auto fail = [&]() + { if (!reversed) reportError(location, TypeMismatch{superTy, subTy, mismatchContext()}); else @@ -2770,8 +2762,15 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever } } -static void tryUnifyWithAny(std::vector& queue, Unifier& state, DenseHashSet& seen, DenseHashSet& seenTypePacks, - const TypeArena* typeArena, TypeId anyType, TypePackId anyTypePack) +static void tryUnifyWithAny( + std::vector& queue, + Unifier& state, + DenseHashSet& seen, + DenseHashSet& seenTypePacks, + const TypeArena* typeArena, + TypeId anyType, + TypePackId anyTypePack +) { while (!queue.empty()) { @@ -2927,7 +2926,8 @@ bool Unifier::occursCheck(DenseHashSet& seen, TypeId needle, TypeId hays bool occurrence = false; - auto check = [&](TypeId tv) { + auto check = [&](TypeId tv) + { if (occursCheck(seen, needle, tv)) occurrence = true; }; @@ -3064,8 +3064,10 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s if (auto e = hasUnificationTooComplex(innerErrors)) reportError(*e); else if (!innerErrors.empty()) - reportError(TypeError{location, - TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front(), mismatchContext()}}); + reportError(TypeError{ + location, + TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front(), mismatchContext()} + }); } void Unifier::ice(const std::string& message, const Location& location) diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index df448113..5ea11ad0 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -33,7 +33,8 @@ static bool areCompatible(TypeId left, TypeId right) const TableType* rightTable = p.second; LUAU_ASSERT(rightTable); - const auto missingPropIsCompatible = [](const Property& leftProp, const TableType* rightTable) { + const auto missingPropIsCompatible = [](const Property& leftProp, const TableType* rightTable) + { // Two tables may be compatible even if their shapes aren't exactly the // same if the extra property is optional, free (and therefore // potentially optional), or if the right table has an indexer. Or if @@ -96,8 +97,13 @@ Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, { } -Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice, - DenseHashSet* uninhabitedTypeFunctions) +Unifier2::Unifier2( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull ice, + DenseHashSet* uninhabitedTypeFunctions +) : arena(arena) , builtinTypes(builtinTypes) , scope(scope) @@ -251,7 +257,8 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) FreeType* subFree = getMutable(subTy); LUAU_ASSERT(subFree); - auto doDefault = [&]() { + auto doDefault = [&]() + { subFree->upperBound = mkIntersection(subFree->upperBound, superTy); expandedFreeTypes[subTy].push_back(superTy); return true; @@ -841,7 +848,8 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet& seen, TypeId needl OccursCheckResult occurrence = OccursCheckResult::Pass; - auto check = [&](TypeId ty) { + auto check = [&](TypeId ty) + { if (occursCheck(seen, needle, ty) == OccursCheckResult::Fail) occurrence = OccursCheckResult::Fail; }; diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index e2ac8b7d..099ece2b 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -384,7 +384,13 @@ public: LUAU_RTTI(AstExprIndexName) AstExprIndexName( - const Location& location, AstExpr* expr, const AstName& index, const Location& indexLocation, const Position& opPosition, char op); + const Location& location, + AstExpr* expr, + const AstName& index, + const Location& indexLocation, + const Position& opPosition, + char op + ); void visit(AstVisitor* visitor) override; @@ -413,11 +419,22 @@ class AstExprFunction : public AstExpr public: LUAU_RTTI(AstExprFunction) - AstExprFunction(const Location& location, const AstArray& attributes, const AstArray& generics, - const AstArray& genericPacks, AstLocal* self, const AstArray& args, bool vararg, - const Location& varargLocation, AstStatBlock* body, size_t functionDepth, const AstName& debugname, - const std::optional& returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr, - const std::optional& argLocation = std::nullopt); + AstExprFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + AstLocal* self, + const AstArray& args, + bool vararg, + const Location& varargLocation, + AstStatBlock* body, + size_t functionDepth, + const AstName& debugname, + const std::optional& returnAnnotation = {}, + AstTypePack* varargAnnotation = nullptr, + const std::optional& argLocation = std::nullopt + ); void visit(AstVisitor* visitor) override; @@ -603,8 +620,14 @@ class AstStatIf : public AstStat public: LUAU_RTTI(AstStatIf) - AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, const std::optional& thenLocation, - const std::optional& elseLocation); + AstStatIf( + const Location& location, + AstExpr* condition, + AstStatBlock* thenbody, + AstStat* elsebody, + const std::optional& thenLocation, + const std::optional& elseLocation + ); void visit(AstVisitor* visitor) override; @@ -698,8 +721,12 @@ class AstStatLocal : public AstStat public: LUAU_RTTI(AstStatLocal) - AstStatLocal(const Location& location, const AstArray& vars, const AstArray& values, - const std::optional& equalsSignLocation); + AstStatLocal( + const Location& location, + const AstArray& vars, + const AstArray& values, + const std::optional& equalsSignLocation + ); void visit(AstVisitor* visitor) override; @@ -714,8 +741,16 @@ class AstStatFor : public AstStat public: LUAU_RTTI(AstStatFor) - AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo, - const Location& doLocation); + AstStatFor( + const Location& location, + AstLocal* var, + AstExpr* from, + AstExpr* to, + AstExpr* step, + AstStatBlock* body, + bool hasDo, + const Location& doLocation + ); void visit(AstVisitor* visitor) override; @@ -734,8 +769,16 @@ class AstStatForIn : public AstStat public: LUAU_RTTI(AstStatForIn) - AstStatForIn(const Location& location, const AstArray& vars, const AstArray& values, AstStatBlock* body, bool hasIn, - const Location& inLocation, bool hasDo, const Location& doLocation); + AstStatForIn( + const Location& location, + const AstArray& vars, + const AstArray& values, + AstStatBlock* body, + bool hasIn, + const Location& inLocation, + bool hasDo, + const Location& doLocation + ); void visit(AstVisitor* visitor) override; @@ -808,8 +851,15 @@ class AstStatTypeAlias : public AstStat public: LUAU_RTTI(AstStatTypeAlias) - AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation, const AstArray& generics, - const AstArray& genericPacks, AstType* type, bool exported); + AstStatTypeAlias( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + AstType* type, + bool exported + ); void visit(AstVisitor* visitor) override; @@ -821,6 +871,20 @@ public: bool exported; }; +class AstStatTypeFunction : public AstStat +{ +public: + LUAU_RTTI(AstStatTypeFunction); + + AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body); + + void visit(AstVisitor* visitor) override; + + AstName name; + Location nameLocation; + AstExprFunction* body; +}; + class AstStatDeclareGlobal : public AstStat { public: @@ -840,13 +904,32 @@ class AstStatDeclareFunction : public AstStat public: LUAU_RTTI(AstStatDeclareFunction) - AstStatDeclareFunction(const Location& location, const AstName& name, const Location& nameLocation, const AstArray& generics, - const AstArray& genericPacks, const AstTypeList& params, const AstArray& paramNames, bool vararg, - const Location& varargLocation, const AstTypeList& retTypes); + AstStatDeclareFunction( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + const AstTypeList& retTypes + ); - AstStatDeclareFunction(const Location& location, const AstArray& attributes, const AstName& name, const Location& nameLocation, - const AstArray& generics, const AstArray& genericPacks, const AstTypeList& params, - const AstArray& paramNames, bool vararg, const Location& varargLocation, const AstTypeList& retTypes); + AstStatDeclareFunction( + const Location& location, + const AstArray& attributes, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + const AstTypeList& retTypes + ); void visit(AstVisitor* visitor) override; @@ -896,8 +979,13 @@ class AstStatDeclareClass : public AstStat public: LUAU_RTTI(AstStatDeclareClass) - AstStatDeclareClass(const Location& location, const AstName& name, std::optional superName, const AstArray& props, - AstTableIndexer* indexer = nullptr); + AstStatDeclareClass( + const Location& location, + const AstName& name, + std::optional superName, + const AstArray& props, + AstTableIndexer* indexer = nullptr + ); void visit(AstVisitor* visitor) override; @@ -934,8 +1022,15 @@ class AstTypeReference : public AstType public: LUAU_RTTI(AstTypeReference) - AstTypeReference(const Location& location, std::optional prefix, AstName name, std::optional prefixLocation, - const Location& nameLocation, bool hasParameterList = false, const AstArray& parameters = {}); + AstTypeReference( + const Location& location, + std::optional prefix, + AstName name, + std::optional prefixLocation, + const Location& nameLocation, + bool hasParameterList = false, + const AstArray& parameters = {} + ); void visit(AstVisitor* visitor) override; @@ -974,12 +1069,24 @@ class AstTypeFunction : public AstType public: LUAU_RTTI(AstTypeFunction) - AstTypeFunction(const Location& location, const AstArray& generics, const AstArray& genericPacks, - const AstTypeList& argTypes, const AstArray>& argNames, const AstTypeList& returnTypes); + AstTypeFunction( + const Location& location, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + const AstTypeList& returnTypes + ); - AstTypeFunction(const Location& location, const AstArray& attributes, const AstArray& generics, - const AstArray& genericPacks, const AstTypeList& argTypes, const AstArray>& argNames, - const AstTypeList& returnTypes); + AstTypeFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + const AstTypeList& returnTypes + ); void visit(AstVisitor* visitor) override; @@ -1413,4 +1520,4 @@ struct hash } }; -} // namespace std +} // namespace std \ No newline at end of file diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 5a945e26..4e49028a 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -55,7 +55,12 @@ class Parser { public: static ParseResult parse( - const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions()); + const char* buffer, + std::size_t bufferSize, + AstNameTable& names, + Allocator& allocator, + ParseOptions options = ParseOptions() + ); private: struct Name; @@ -140,6 +145,9 @@ private: // type Name `=' Type AstStat* parseTypeAlias(const Location& start, bool exported); + // type function Name ... end + AstStat* parseTypeFunction(const Location& start); + AstDeclaredClassProp parseDeclaredClassMethod(); // `declare global' Name: Type | @@ -157,7 +165,12 @@ private: // funcbodyhead ::= `(' [namelist [`,' `...'] | `...'] `)' [`:` Type] // funcbody ::= funcbodyhead block end std::pair parseFunctionBody( - bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName, const AstArray& attributes); + bool hasself, + const Lexeme& matchFunction, + const AstName& debugname, + const Name* localName, + const AstArray& attributes + ); // explist ::= {exp `,'} exp void parseExprList(TempVector& result); @@ -191,9 +204,15 @@ private: AstTableIndexer* parseTableIndexer(AstTableAccess access, std::optional accessLocation); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray& attributes); - AstType* parseFunctionTypeTail(const Lexeme& begin, const AstArray& attributes, AstArray generics, - AstArray genericPacks, AstArray params, AstArray> paramNames, - AstTypePack* varargAnnotation); + AstType* parseFunctionTypeTail( + const Lexeme& begin, + const AstArray& attributes, + AstArray generics, + AstArray genericPacks, + AstArray params, + AstArray> paramNames, + AstTypePack* varargAnnotation + ); AstType* parseTableType(bool inDeclarationContext = false); AstTypeOrPack parseSimpleType(bool allowPack, bool inDeclarationContext = false); @@ -315,8 +334,13 @@ private: void reportNameError(const char* context); - AstStatError* reportStatError(const Location& location, const AstArray& expressions, const AstArray& statements, - const char* format, ...) LUAU_PRINTF_ATTR(5, 6); + AstStatError* reportStatError( + const Location& location, + const AstArray& expressions, + const AstArray& statements, + const char* format, + ... + ) LUAU_PRINTF_ATTR(5, 6); AstExprError* reportExprError(const Location& location, const AstArray& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5); AstTypeError* reportTypeError(const Location& location, const AstArray& types, const char* format, ...) LUAU_PRINTF_ATTR(4, 5); // `parseErrorLocation` is associated with the parser error @@ -428,4 +452,4 @@ private: std::string scratchData; }; -} // namespace Luau +} // namespace Luau \ No newline at end of file diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index e48ae2a1..ff7c7cc6 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -141,7 +141,13 @@ void AstExprCall::visit(AstVisitor* visitor) } AstExprIndexName::AstExprIndexName( - const Location& location, AstExpr* expr, const AstName& index, const Location& indexLocation, const Position& opPosition, char op) + const Location& location, + AstExpr* expr, + const AstName& index, + const Location& indexLocation, + const Position& opPosition, + char op +) : AstExpr(ClassIndex(), location) , expr(expr) , index(index) @@ -173,10 +179,22 @@ void AstExprIndexExpr::visit(AstVisitor* visitor) } } -AstExprFunction::AstExprFunction(const Location& location, const AstArray& attributes, const AstArray& generics, - const AstArray& genericPacks, AstLocal* self, const AstArray& args, bool vararg, const Location& varargLocation, - AstStatBlock* body, size_t functionDepth, const AstName& debugname, const std::optional& returnAnnotation, - AstTypePack* varargAnnotation, const std::optional& argLocation) +AstExprFunction::AstExprFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + AstLocal* self, + const AstArray& args, + bool vararg, + const Location& varargLocation, + AstStatBlock* body, + size_t functionDepth, + const AstName& debugname, + const std::optional& returnAnnotation, + AstTypePack* varargAnnotation, + const std::optional& argLocation +) : AstExpr(ClassIndex(), location) , attributes(attributes) , generics(generics) @@ -418,8 +436,14 @@ void AstStatBlock::visit(AstVisitor* visitor) } } -AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, - const std::optional& thenLocation, const std::optional& elseLocation) +AstStatIf::AstStatIf( + const Location& location, + AstExpr* condition, + AstStatBlock* thenbody, + AstStat* elsebody, + const std::optional& thenLocation, + const std::optional& elseLocation +) : AstStat(ClassIndex(), location) , condition(condition) , thenbody(thenbody) @@ -524,7 +548,11 @@ void AstStatExpr::visit(AstVisitor* visitor) } AstStatLocal::AstStatLocal( - const Location& location, const AstArray& vars, const AstArray& values, const std::optional& equalsSignLocation) + const Location& location, + const AstArray& vars, + const AstArray& values, + const std::optional& equalsSignLocation +) : AstStat(ClassIndex(), location) , vars(vars) , values(values) @@ -548,7 +576,15 @@ void AstStatLocal::visit(AstVisitor* visitor) } AstStatFor::AstStatFor( - const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo, const Location& doLocation) + const Location& location, + AstLocal* var, + AstExpr* from, + AstExpr* to, + AstExpr* step, + AstStatBlock* body, + bool hasDo, + const Location& doLocation +) : AstStat(ClassIndex(), location) , var(var) , from(from) @@ -577,8 +613,16 @@ void AstStatFor::visit(AstVisitor* visitor) } } -AstStatForIn::AstStatForIn(const Location& location, const AstArray& vars, const AstArray& values, AstStatBlock* body, - bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation) +AstStatForIn::AstStatForIn( + const Location& location, + const AstArray& vars, + const AstArray& values, + AstStatBlock* body, + bool hasIn, + const Location& inLocation, + bool hasDo, + const Location& doLocation +) : AstStat(ClassIndex(), location) , vars(vars) , values(values) @@ -672,8 +716,15 @@ void AstStatLocalFunction::visit(AstVisitor* visitor) func->visit(visitor); } -AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation, - const AstArray& generics, const AstArray& genericPacks, AstType* type, bool exported) +AstStatTypeAlias::AstStatTypeAlias( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + AstType* type, + bool exported +) : AstStat(ClassIndex(), location) , name(name) , nameLocation(nameLocation) @@ -704,6 +755,20 @@ void AstStatTypeAlias::visit(AstVisitor* visitor) } } +AstStatTypeFunction::AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body) + : AstStat(ClassIndex(), location) + , name(name) + , nameLocation(nameLocation) + , body(body) +{ +} + +void AstStatTypeFunction::visit(AstVisitor* visitor) +{ + if (visitor->visit(this)) + body->visit(visitor); +} + AstStatDeclareGlobal::AstStatDeclareGlobal(const Location& location, const AstName& name, const Location& nameLocation, AstType* type) : AstStat(ClassIndex(), location) , name(name) @@ -718,9 +783,18 @@ void AstStatDeclareGlobal::visit(AstVisitor* visitor) type->visit(visitor); } -AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstName& name, const Location& nameLocation, - const AstArray& generics, const AstArray& genericPacks, const AstTypeList& params, - const AstArray& paramNames, bool vararg, const Location& varargLocation, const AstTypeList& retTypes) +AstStatDeclareFunction::AstStatDeclareFunction( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + const AstTypeList& retTypes +) : AstStat(ClassIndex(), location) , attributes() , name(name) @@ -735,9 +809,19 @@ AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const A { } -AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstArray& attributes, const AstName& name, - const Location& nameLocation, const AstArray& generics, const AstArray& genericPacks, - const AstTypeList& params, const AstArray& paramNames, bool vararg, const Location& varargLocation, const AstTypeList& retTypes) +AstStatDeclareFunction::AstStatDeclareFunction( + const Location& location, + const AstArray& attributes, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + const AstTypeList& retTypes +) : AstStat(ClassIndex(), location) , attributes(attributes) , name(name) @@ -772,8 +856,13 @@ bool AstStatDeclareFunction::isCheckedFunction() const return false; } -AstStatDeclareClass::AstStatDeclareClass(const Location& location, const AstName& name, std::optional superName, - const AstArray& props, AstTableIndexer* indexer) +AstStatDeclareClass::AstStatDeclareClass( + const Location& location, + const AstName& name, + std::optional superName, + const AstArray& props, + AstTableIndexer* indexer +) : AstStat(ClassIndex(), location) , name(name) , superName(superName) @@ -792,7 +881,11 @@ void AstStatDeclareClass::visit(AstVisitor* visitor) } AstStatError::AstStatError( - const Location& location, const AstArray& expressions, const AstArray& statements, unsigned messageIndex) + const Location& location, + const AstArray& expressions, + const AstArray& statements, + unsigned messageIndex +) : AstStat(ClassIndex(), location) , expressions(expressions) , statements(statements) @@ -812,8 +905,15 @@ void AstStatError::visit(AstVisitor* visitor) } } -AstTypeReference::AstTypeReference(const Location& location, std::optional prefix, AstName name, std::optional prefixLocation, - const Location& nameLocation, bool hasParameterList, const AstArray& parameters) +AstTypeReference::AstTypeReference( + const Location& location, + std::optional prefix, + AstName name, + std::optional prefixLocation, + const Location& nameLocation, + bool hasParameterList, + const AstArray& parameters +) : AstType(ClassIndex(), location) , hasParameterList(hasParameterList) , prefix(prefix) @@ -860,8 +960,14 @@ void AstTypeTable::visit(AstVisitor* visitor) } } -AstTypeFunction::AstTypeFunction(const Location& location, const AstArray& generics, const AstArray& genericPacks, - const AstTypeList& argTypes, const AstArray>& argNames, const AstTypeList& returnTypes) +AstTypeFunction::AstTypeFunction( + const Location& location, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + const AstTypeList& returnTypes +) : AstType(ClassIndex(), location) , attributes() , generics(generics) @@ -873,9 +979,15 @@ AstTypeFunction::AstTypeFunction(const Location& location, const AstArray& attributes, const AstArray& generics, - const AstArray& genericPacks, const AstTypeList& argTypes, const AstArray>& argNames, - const AstTypeList& returnTypes) +AstTypeFunction::AstTypeFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + const AstTypeList& returnTypes +) : AstType(ClassIndex(), location) , attributes(attributes) , generics(generics) @@ -1053,4 +1165,4 @@ Location getLocation(const AstTypeList& typeList) return result; } -} // namespace Luau +} // namespace Luau \ No newline at end of file diff --git a/Ast/src/Confusables.cpp b/Ast/src/Confusables.cpp index 1c792156..8f7fb56c 100644 --- a/Ast/src/Confusables.cpp +++ b/Ast/src/Confusables.cpp @@ -1808,9 +1808,15 @@ static const Confusable kConfusables[] = const char* findConfusable(uint32_t codepoint) { - auto it = std::lower_bound(std::begin(kConfusables), std::end(kConfusables), codepoint, [](const Confusable& lhs, uint32_t rhs) { - return lhs.codepoint < rhs; - }); + auto it = std::lower_bound( + std::begin(kConfusables), + std::end(kConfusables), + codepoint, + [](const Confusable& lhs, uint32_t rhs) + { + return lhs.codepoint < rhs; + } + ); return (it != std::end(kConfusables) && it->codepoint == codepoint) ? it->text : nullptr; } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 0ea05296..a5e1d40e 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -92,8 +92,10 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz , length(unsigned(size)) , data(data) { - LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd || - type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment); + LUAU_ASSERT( + type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd || + type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment + ); } Lexeme::Lexeme(const Location& location, Type type, const char* name) @@ -107,14 +109,16 @@ Lexeme::Lexeme(const Location& location, Type type, const char* name) unsigned int Lexeme::getLength() const { - LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd || - type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment); + LUAU_ASSERT( + type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd || + type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment + ); return length; } -static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", - "repeat", "return", "then", "true", "until", "while"}; +static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", + "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}; std::string Lexeme::toString() const { diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 9701cbc5..d58fe1e8 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false) LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false) LUAU_FASTFLAGVARIABLE(LuauDeclarationExtraPropData, false) +LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false) namespace Luau { @@ -785,9 +786,13 @@ AstStat* Parser::parseAttributeStat() return parseDeclaration(expr->location, attributes); } default: - return reportStatError(lexer.current().location, {}, {}, + return reportStatError( + lexer.current().location, + {}, + {}, "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got %s instead", - lexer.current().toString().c_str()); + lexer.current().toString().c_str() + ); } } @@ -825,8 +830,13 @@ AstStat* Parser::parseLocal(const AstArray& attributes) { if (attributes.size != 0) { - return reportStatError(lexer.current().location, {}, {}, "Expected 'function' after local declaration with attribute, but got %s instead", - lexer.current().toString().c_str()); + return reportStatError( + lexer.current().location, + {}, + {}, + "Expected 'function' after local declaration with attribute, but got %s instead", + lexer.current().toString().c_str() + ); } matchRecoveryStopOnToken['=']++; @@ -880,6 +890,15 @@ AstStat* Parser::parseReturn() // type Name [`<' varlist `>'] `=' Type AstStat* Parser::parseTypeAlias(const Location& start, bool exported) { + // parsing a type function + if (FFlag::LuauUserDefinedTypeFunctions) + { + if (lexer.current().type == Lexeme::ReservedFunction) + return parseTypeFunction(start); + } + + // parsing a type alias + // note: `type` token is already parsed for us, so we just need to parse the rest std::optional name = parseNameOpt("type name"); @@ -897,6 +916,26 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) return allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); } +// type function Name `(' arglist `)' `=' funcbody `end' +AstStat* Parser::parseTypeFunction(const Location& start) +{ + Lexeme matchFn = lexer.current(); + nextLexeme(); + + // parse the name of the type function + std::optional fnName = parseNameOpt("type function name"); + if (!fnName) + fnName = Name(nameError, lexer.current().location); + + matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; + + AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray({nullptr, 0})).first; + + matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; + + return allocator.alloc(Location(start, body->location), fnName->name, fnName->location, body); +} + AstDeclaredClassProp Parser::parseDeclaredClassMethod() { Location start; @@ -940,8 +979,12 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod() if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) { - return AstDeclaredClassProp{fnName.name, FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{}, - reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true}; + return AstDeclaredClassProp{ + fnName.name, + FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{}, + reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), + true + }; } // Skip the first index. @@ -959,10 +1002,16 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod() report(start, "All declaration parameters aside from 'self' must be annotated"); AstType* fnType = allocator.alloc( - Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes); + Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes + ); - return AstDeclaredClassProp{fnName.name, FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{}, fnType, true, - FFlag::LuauDeclarationExtraPropData ? Location(start, end) : Location{}}; + return AstDeclaredClassProp{ + fnName.name, + FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{}, + fnType, + true, + FFlag::LuauDeclarationExtraPropData ? Location(start, end) : Location{} + }; } AstStat* Parser::parseDeclaration(const Location& start, const AstArray& attributes) @@ -970,8 +1019,13 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray(Location(start, end), attributes, globalName.name, globalName.location, generics, - genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), vararg, varargLocation, retTypes); + return allocator.alloc( + Location(start, end), + attributes, + globalName.name, + globalName.location, + generics, + genericPacks, + AstTypeList{copy(vars), varargAnnotation}, + copy(varNames), + vararg, + varargLocation, + retTypes + ); else - return allocator.alloc(Location(start, end), attributes, globalName.name, Location{}, generics, genericPacks, - AstTypeList{copy(vars), varargAnnotation}, copy(varNames), false, Location{}, retTypes); + return allocator.alloc( + Location(start, end), + attributes, + globalName.name, + Location{}, + generics, + genericPacks, + AstTypeList{copy(vars), varargAnnotation}, + copy(varNames), + false, + Location{}, + retTypes + ); } else if (AstName(lexer.current().name) == "class") { @@ -1064,7 +1140,8 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraydata), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())}); + AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) + }); else report(begin.location, "String literal contains malformed escape sequence or \\0"); } @@ -1107,8 +1184,8 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray( - Location(start, type->location), globalName->name, FFlag::LuauDeclarationExtraPropData ? globalName->location : Location{}, type); + Location(start, type->location), globalName->name, FFlag::LuauDeclarationExtraPropData ? globalName->location : Location{}, type + ); } else { @@ -1205,7 +1283,12 @@ std::pair> Parser::prepareFunctionArguments(const // funcbody ::= `(' [parlist] `)' [`:' ReturnType] block end // parlist ::= bindinglist [`,' `...'] | `...' std::pair Parser::parseFunctionBody( - bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName, const AstArray& attributes) + bool hasself, + const Lexeme& matchFunction, + const AstName& debugname, + const Name* localName, + const AstArray& attributes +) { Location start = matchFunction.location; @@ -1257,9 +1340,25 @@ std::pair Parser::parseFunctionBody( bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction); body->hasEnd = hasEnd; - return {allocator.alloc(Location(start, end), attributes, generics, genericPacks, self, vars, vararg, varargLocation, body, - functionStack.size(), debugname, typelist, varargAnnotation, argLocation), - funLocal}; + return { + allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + self, + vars, + vararg, + varargLocation, + body, + functionStack.size(), + debugname, + typelist, + varargAnnotation, + argLocation + ), + funLocal + }; } // explist ::= {exp `,'} exp @@ -1656,9 +1755,15 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray return {parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}}; } -AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, const AstArray& attributes, AstArray generics, - AstArray genericPacks, AstArray params, AstArray> paramNames, - AstTypePack* varargAnnotation) +AstType* Parser::parseFunctionTypeTail( + const Lexeme& begin, + const AstArray& attributes, + AstArray generics, + AstArray genericPacks, + AstArray params, + AstArray> paramNames, + AstTypePack* varargAnnotation +) { incrementRecursionCounter("type annotation"); @@ -1683,7 +1788,8 @@ AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, const AstArray( - Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList); + Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList + ); } // Type ::= @@ -1760,8 +1866,11 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) if (isUnion && isIntersection) { - return reportTypeError(Location(begin, parts.back()->location), copy(parts), - "Mixing union and intersection types is not allowed; consider wrapping in parentheses."); + return reportTypeError( + Location(begin, parts.back()->location), + copy(parts), + "Mixing union and intersection types is not allowed; consider wrapping in parentheses." + ); } location.end = parts.back()->location.end; @@ -1922,7 +2031,8 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) Location end = lexer.previousLocation(); return { - allocator.alloc(Location(start, end), prefix, name.name, prefixLocation, name.location, hasParameters, parameters), {}}; + allocator.alloc(Location(start, end), prefix, name.name, prefixLocation, name.location, hasParameters, parameters), {} + }; } else if (lexer.current().type == '{') { @@ -1936,10 +2046,15 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) { nextLexeme(); - return {reportTypeError(start, {}, - "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> " - "...any'"), - {}}; + return { + reportTypeError( + start, + {}, + "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> " + "...any'" + ), + {} + }; } else { @@ -2114,8 +2229,7 @@ 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 '~='?"); @@ -2129,6 +2243,7 @@ std::optional Parser::checkBinaryConfusables(const BinaryOpPr // where `binop' is any binary operator with a priority higher than `limit' AstExpr* Parser::parseExpr(unsigned int limit) { + // clang-format off static const BinaryOpPriority binaryPriority[] = { {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, // `+' `-' `*' `/' `//' `%' {10, 9}, {5, 4}, // power and concat (right associative) @@ -2136,6 +2251,8 @@ AstExpr* Parser::parseExpr(unsigned int limit) {3, 3}, {3, 3}, {3, 3}, {3, 3}, // order {2, 2}, {1, 1} // logical (and/or) }; + // clang-format on + static_assert(sizeof(binaryPriority) / sizeof(binaryPriority[0]) == size_t(AstExprBinary::Op__Count), "binaryPriority needs an entry per op"); unsigned int oldRecursionCount = recursionCounter; @@ -2414,7 +2531,8 @@ AstExpr* Parser::parseSimpleExpr() if (lexer.current().type != Lexeme::ReservedFunction) { return reportExprError( - start, {}, "Expected 'function' declaration after attribute, but got %s instead", lexer.current().toString().c_str()); + start, {}, "Expected 'function' declaration after attribute, but got %s instead", lexer.current().toString().c_str() + ); } } @@ -2447,8 +2565,7 @@ 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(); } @@ -2548,15 +2665,22 @@ LUAU_NOINLINE AstExpr* Parser::reportFunctionArgsError(AstExpr* func, bool self) } else { - return reportExprError(Location(func->location.begin, lexer.current().location.begin), copy({func}), - "Expected '(', '{' or when parsing function call, got %s", lexer.current().toString().c_str()); + return reportExprError( + Location(func->location.begin, lexer.current().location.begin), + copy({func}), + "Expected '(', '{' or when parsing function call, got %s", + lexer.current().toString().c_str() + ); } } LUAU_NOINLINE void Parser::reportAmbiguousCallError() { - report(lexer.current().location, "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of " - "new statement; use ';' to separate statements"); + report( + lexer.current().location, + "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of " + "new statement; use ';' to separate statements" + ); } // tableconstructor ::= `{' [fieldlist] `}' @@ -2868,8 +2992,10 @@ AstArray Parser::parseTypeParams() std::optional> Parser::parseCharArray() { - LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || - lexer.current().type == Lexeme::InterpStringSimple); + LUAU_ASSERT( + lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || + lexer.current().type == Lexeme::InterpStringSimple + ); scratchData.assign(lexer.current().data, lexer.current().getLength()); @@ -2911,8 +3037,10 @@ AstExpr* Parser::parseInterpString() do { Lexeme currentLexeme = lexer.current(); - LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid || - currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple); + LUAU_ASSERT( + currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid || + currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple + ); endLocation = currentLexeme.location; @@ -3013,7 +3141,8 @@ AstLocal* Parser::pushLocal(const Binding& binding) AstLocal*& local = localMap[name.name]; local = allocator.alloc( - name.name, name.location, /* shadow= */ local, functionStack.size() - 1, functionStack.back().loopDepth, binding.annotation); + name.name, name.location, /* shadow= */ local, functionStack.size() - 1, functionStack.back().loopDepth, binding.annotation + ); localStack.push_back(local); @@ -3146,11 +3275,25 @@ LUAU_NOINLINE void Parser::expectMatchAndConsumeFail(Lexeme::Type type, const Ma std::string matchString = Lexeme(Location(Position(0, 0), 0), begin.type).toString(); if (lexer.current().location.begin.line == begin.position.line) - report(lexer.current().location, "Expected %s (to close %s at column %d), got %s%s", typeString.c_str(), matchString.c_str(), - begin.position.column + 1, lexer.current().toString().c_str(), extra ? extra : ""); + report( + lexer.current().location, + "Expected %s (to close %s at column %d), got %s%s", + typeString.c_str(), + matchString.c_str(), + begin.position.column + 1, + lexer.current().toString().c_str(), + extra ? extra : "" + ); else - report(lexer.current().location, "Expected %s (to close %s at line %d), got %s%s", typeString.c_str(), matchString.c_str(), - begin.position.line + 1, lexer.current().toString().c_str(), extra ? extra : ""); + report( + lexer.current().location, + "Expected %s (to close %s at line %d), got %s%s", + typeString.c_str(), + matchString.c_str(), + begin.position.line + 1, + lexer.current().toString().c_str(), + extra ? extra : "" + ); } bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begin) @@ -3287,7 +3430,12 @@ LUAU_NOINLINE void Parser::reportNameError(const char* context) } AstStatError* Parser::reportStatError( - const Location& location, const AstArray& expressions, const AstArray& statements, const char* format, ...) + const Location& location, + const AstArray& expressions, + const AstArray& statements, + const char* format, + ... +) { va_list args; va_start(args, format); @@ -3359,4 +3507,4 @@ void Parser::nextLexeme() } } -} // namespace Luau +} // namespace Luau \ No newline at end of file diff --git a/Ast/src/StringUtils.cpp b/Ast/src/StringUtils.cpp index d7099a31..d3a4cea2 100644 --- a/Ast/src/StringUtils.cpp +++ b/Ast/src/StringUtils.cpp @@ -141,7 +141,8 @@ size_t editDistance(std::string_view a, std::string_view b) size_t maxDistance = a.size() + b.size(); std::vector distances((a.size() + 2) * (b.size() + 2), 0); - auto getPos = [b](size_t x, size_t y) -> size_t { + auto getPos = [b](size_t x, size_t y) -> size_t + { return (x * (b.size() + 2)) + y; }; diff --git a/Ast/src/TimeTrace.cpp b/Ast/src/TimeTrace.cpp index cfcf9ce2..4782b25c 100644 --- a/Ast/src/TimeTrace.cpp +++ b/Ast/src/TimeTrace.cpp @@ -184,8 +184,14 @@ void flushEvents(GlobalContext& context, uint32_t threadId, const std::vector(&error.data)) report(format, humanReadableName.c_str(), error.location, "SyntaxError", syntaxError->message.c_str()); else - report(format, humanReadableName.c_str(), error.location, "TypeError", - Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str()); + report( + format, + humanReadableName.c_str(), + error.location, + "TypeError", + Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str() + ); } static void reportWarning(ReportFormat format, const char* name, const Luau::LintWarning& warning) @@ -235,9 +240,12 @@ struct TaskScheduler { for (unsigned i = 0; i < threadCount; i++) { - workers.emplace_back([this] { - workerFunction(); - }); + workers.emplace_back( + [this] + { + workerFunction(); + } + ); } } @@ -254,9 +262,13 @@ struct TaskScheduler { std::unique_lock guard(mtx); - cv.wait(guard, [this] { - return !tasks.empty(); - }); + cv.wait( + guard, + [this] + { + return !tasks.empty(); + } + ); std::function task = tasks.front(); tasks.pop(); @@ -351,7 +363,8 @@ int main(int argc, char** argv) if (FFlag::DebugLuauLogSolverToJsonFile) { - frontend.writeJsonLog = [&basePath](const Luau::ModuleName& moduleName, std::string log) { + frontend.writeJsonLog = [&basePath](const Luau::ModuleName& moduleName, std::string log) + { std::string path = moduleName + ".log.json"; size_t pos = moduleName.find_last_of('/'); if (pos != std::string::npos) @@ -390,9 +403,13 @@ int main(int argc, char** argv) { TaskScheduler scheduler(threadCount); - checkedModules = frontend.checkQueuedModules(std::nullopt, [&](std::function f) { - scheduler.push(std::move(f)); - }); + checkedModules = frontend.checkQueuedModules( + std::nullopt, + [&](std::function f) + { + scheduler.push(std::move(f)); + } + ); } catch (const Luau::InternalCompilerError& ice) { @@ -403,8 +420,13 @@ int main(int argc, char** argv) Luau::TypeError error(location, moduleName, Luau::InternalError{ice.message}); - report(format, humanReadableName.c_str(), location, "InternalCompilerError", - Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str()); + report( + format, + humanReadableName.c_str(), + location, + "InternalCompilerError", + Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str() + ); return 1; } diff --git a/CLI/Bytecode.cpp b/CLI/Bytecode.cpp index 02ef3237..2da9570b 100644 --- a/CLI/Bytecode.cpp +++ b/CLI/Bytecode.cpp @@ -231,7 +231,10 @@ static void serializeScriptSummary(const std::string& file, const std::vector& files, const std::vector>& scriptSummaries, const std::string& summaryFile) + const std::vector& files, + const std::vector>& scriptSummaries, + const std::string& summaryFile +) { FILE* fp = fopen(summaryFile.c_str(), "w"); diff --git a/CLI/Compile.cpp b/CLI/Compile.cpp index dd6b14ab..7d95387c 100644 --- a/CLI/Compile.cpp +++ b/CLI/Compile.cpp @@ -108,7 +108,11 @@ static void reportError(const char* name, const Luau::CompileError& error) } static std::string getCodegenAssembly( - const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options, Luau::CodeGen::LoweringStats* stats) + const char* name, + const std::string& bytecode, + Luau::CodeGen::AssemblyOptions options, + Luau::CodeGen::LoweringStats* stats +) { std::unique_ptr globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); @@ -326,8 +330,10 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A if (format == CompileFormat::Text) { - bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | - Luau::BytecodeBuilder::Dump_Remarks | Luau::BytecodeBuilder::Dump_Types); + bcb.setDumpFlags( + Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | + Luau::BytecodeBuilder::Dump_Remarks | Luau::BytecodeBuilder::Dump_Types + ); bcb.setDumpSource(*source); } else if (format == CompileFormat::Remarks) @@ -335,11 +341,12 @@ 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 | - Luau::BytecodeBuilder::Dump_Remarks); + bcb.setDumpFlags( + Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | + Luau::BytecodeBuilder::Dump_Remarks + ); bcb.setDumpSource(*source); } @@ -623,19 +630,37 @@ int main(int argc, char** argv) if (compileFormat == CompileFormat::Null) { - printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024), - stats.readTime, stats.parseTime, stats.compileTime); + printf( + "Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", + int(stats.lines / 1000), + int(stats.bytecode / 1024), + stats.readTime, + stats.parseTime, + stats.compileTime + ); } else if (compileFormat == CompileFormat::CodegenNull) { - printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n", - int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024), - stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime, - stats.codegenTime); + printf( + "Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n", + int(stats.lines / 1000), + int(stats.bytecode / 1024), + int(stats.codegen / 1024), + stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), + stats.readTime, + stats.parseTime, + stats.compileTime, + stats.codegenTime + ); - printf("Lowering: regalloc failed: %d, lowering failed %d; spills to stack: %d, spills to restore: %d, max spill slot %u\n", - stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors, stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore, - stats.lowerStats.maxSpillSlotsUsed); + printf( + "Lowering: regalloc failed: %d, lowering failed %d; spills to stack: %d, spills to restore: %d, max spill slot %u\n", + stats.lowerStats.regAllocErrors, + stats.lowerStats.loweringErrors, + stats.lowerStats.spillsToSlot, + stats.lowerStats.spillsToRestore, + stats.lowerStats.maxSpillSlotsUsed + ); } if (recordStats != RecordStats::None) diff --git a/CLI/FileUtils.cpp b/CLI/FileUtils.cpp index 2e17bcb5..daa7c295 100644 --- a/CLI/FileUtils.cpp +++ b/CLI/FileUtils.cpp @@ -442,12 +442,16 @@ std::vector getSourceFiles(int argc, char** argv) if (isDirectory(argv[i])) { - traverseDirectory(argv[i], [&](const std::string& name) { - std::string ext = getExtension(name); + traverseDirectory( + argv[i], + [&](const std::string& name) + { + std::string ext = getExtension(name); - if (ext == ".lua" || ext == ".luau") - files.push_back(name); - }); + if (ext == ".lua" || ext == ".luau") + files.push_back(name); + } + ); } else { diff --git a/CLI/Flags.cpp b/CLI/Flags.cpp index 4e261171..ee5918c9 100644 --- a/CLI/Flags.cpp +++ b/CLI/Flags.cpp @@ -54,8 +54,9 @@ void setLuauFlags(const char* list) else if (value == "false" || value == "False") setLuauFlag(key, false); else - fprintf(stderr, "Warning: unrecognized value '%.*s' for flag '%.*s'.\n", int(value.length()), value.data(), int(key.length()), - key.data()); + fprintf( + stderr, "Warning: unrecognized value '%.*s' for flag '%.*s'.\n", int(value.length()), value.data(), int(key.length()), key.data() + ); } else { diff --git a/CLI/Profiler.cpp b/CLI/Profiler.cpp index d3ad4e99..3cf0aea2 100644 --- a/CLI/Profiler.cpp +++ b/CLI/Profiler.cpp @@ -131,8 +131,13 @@ void profilerDump(const char* path) fclose(f); - printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", path, double(total) / 1e6, - static_cast(gProfiler.samples.load()), static_cast(gProfiler.data.size())); + printf( + "Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", + path, + double(total) / 1e6, + static_cast(gProfiler.samples.load()), + static_cast(gProfiler.data.size()) + ); uint64_t totalgc = 0; for (uint64_t p : gProfiler.gc) diff --git a/CLI/Reduce.cpp b/CLI/Reduce.cpp index 4b41fc76..7f8c459c 100644 --- a/CLI/Reduce.cpp +++ b/CLI/Reduce.cpp @@ -184,7 +184,8 @@ struct Reducer { std::vector result; - auto append = [&](AstStatBlock* block) { + auto append = [&](AstStatBlock* block) + { if (block) result.insert(result.end(), block->body.data, block->body.data + block->body.size); }; @@ -250,7 +251,8 @@ struct Reducer std::vector> result; - auto append = [&result](Span a, Span b) { + auto append = [&result](Span a, Span b) + { if (a.first == a.second && b.first == b.second) return; else diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 814d7c8c..b8e9d814 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -388,8 +388,13 @@ static void safeGetTable(lua_State* L, int tableIndex) // completePartialMatches finds keys that match the specified 'prefix' // Note: the table/object to be searched must be on the top of the Lua stack -static void completePartialMatches(lua_State* L, bool completeOnlyFunctions, const std::string& editBuffer, std::string_view prefix, - const AddCompletionCallback& addCompletionCallback) +static void completePartialMatches( + lua_State* L, + bool completeOnlyFunctions, + const std::string& editBuffer, + std::string_view prefix, + const AddCompletionCallback& addCompletionCallback +) { for (int i = 0; i < MaxTraversalLimit && lua_istable(L, -1); i++) { @@ -483,9 +488,14 @@ static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer) { auto* L = reinterpret_cast(ic_completion_arg(cenv)); - getCompletions(L, std::string(editBuffer), [cenv](const std::string& completion, const std::string& display) { - ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr); - }); + getCompletions( + L, + std::string(editBuffer), + [cenv](const std::string& completion, const std::string& display) + { + ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr); + } + ); } static bool isMethodOrFunctionChar(const char* s, long len) @@ -788,9 +798,13 @@ int replMain(int argc, char** argv) // note, there's no need to close the log explicitly as it will be closed when the process exits FILE* codegenPerfLog = fopen(path, "w"); - Luau::CodeGen::setPerfLog(codegenPerfLog, [](void* context, uintptr_t addr, unsigned size, const char* symbol) { - fprintf(static_cast(context), "%016lx %08x %s\n", long(addr), size, symbol); - }); + Luau::CodeGen::setPerfLog( + codegenPerfLog, + [](void* context, uintptr_t addr, unsigned size, const char* symbol) + { + fprintf(static_cast(context), "%016lx %08x %s\n", long(addr), size, symbol); + } + ); #else fprintf(stderr, "--codegen-perf option is only supported on Linux\n"); return 1; diff --git a/CLI/Require.cpp b/CLI/Require.cpp index dd95d634..5de78a4a 100644 --- a/CLI/Require.cpp +++ b/CLI/Require.cpp @@ -223,9 +223,15 @@ void RequireResolver::substituteAliasIfPresent(std::string& path) std::optional RequireResolver::getAlias(std::string alias) { - std::transform(alias.begin(), alias.end(), alias.begin(), [](unsigned char c) { - return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c; - }); + std::transform( + alias.begin(), + alias.end(), + alias.begin(), + [](unsigned char c) + { + return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c; + } + ); while (!config.aliases.count(alias) && !isConfigFullyResolved) { parseNextConfig(); diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index f3f85ed5..c52d95c5 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -212,8 +212,19 @@ public: private: // Instruction archetypes - void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev, - uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg); + void placeBinary( + const char* name, + OperandX64 lhs, + OperandX64 rhs, + uint8_t codeimm8, + uint8_t codeimm, + uint8_t codeimmImm8, + uint8_t code8rev, + uint8_t coderev, + uint8_t code8, + uint8_t code, + uint8_t opreg + ); void placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code, uint8_t codeImm8, uint8_t opreg); void placeBinaryRegAndRegMem(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code); void placeBinaryRegMemAndReg(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code); @@ -228,7 +239,16 @@ private: void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix); void placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix); void placeAvx( - const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t imm8, uint8_t code, bool setW, uint8_t mode, uint8_t prefix); + const char* name, + OperandX64 dst, + OperandX64 src1, + OperandX64 src2, + uint8_t imm8, + uint8_t code, + bool setW, + uint8_t mode, + uint8_t prefix + ); // Instruction components void placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs, int32_t extraCodeBytes = 0); diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index d7e43272..dcc1de85 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -25,7 +25,14 @@ struct CodeAllocator // To allow allocation while previously allocated code is already running, allocation has page granularity // It's important to group functions together so that page alignment won't result in a lot of wasted space bool allocate( - const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart); + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize, + uint8_t*& result, + size_t& resultSize, + uint8_t*& resultCodeStart + ); // Provided to unwind info callbacks void* context = nullptr; diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 7dd05660..0cf9d9a5 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -77,8 +77,8 @@ 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); +using HostVectorNamecallHandler = + bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); enum class HostMetamethod { @@ -99,12 +99,21 @@ enum class HostMetamethod 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 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); + IrBuilder& builder, + uint8_t type, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +); struct HostIrHooks { @@ -196,7 +205,11 @@ using UniqueSharedCodeGenContext = std::unique_ptr& defBlocks, const std::vector& liveInBlocks); + IdfContext& ctx, + const IrFunction& function, + const std::vector& defBlocks, + const std::vector& liveInBlocks +); // Function used to update all CFG data void computeCfgInfo(IrFunction& function); diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index 8fcb85dd..9364f461 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -36,9 +36,21 @@ const char* getBytecodeTypeName(uint8_t type, const char* const* userdataTypes); void toString(std::string& result, const BytecodeTypes& bcTypes, const char* const* userdataTypes); void toStringDetailed( - IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, IncludeUseInfo includeUseInfo); -void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, IncludeUseInfo includeUseInfo, IncludeCfgInfo includeCfgInfo, - IncludeRegFlowInfo includeRegFlowInfo); + IrToStringContext& ctx, + const IrBlock& block, + uint32_t blockIdx, + const IrInst& inst, + uint32_t instIdx, + IncludeUseInfo includeUseInfo +); +void toStringDetailed( + IrToStringContext& ctx, + const IrBlock& block, + uint32_t blockIdx, + IncludeUseInfo includeUseInfo, + IncludeCfgInfo includeCfgInfo, + IncludeRegFlowInfo includeRegFlowInfo +); std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo); diff --git a/CodeGen/include/Luau/SharedCodeAllocator.h b/CodeGen/include/Luau/SharedCodeAllocator.h index 7796096a..62175e7f 100644 --- a/CodeGen/include/Luau/SharedCodeAllocator.h +++ b/CodeGen/include/Luau/SharedCodeAllocator.h @@ -42,8 +42,12 @@ class SharedCodeAllocator; class NativeModule { public: - NativeModule(SharedCodeAllocator* allocator, const std::optional& moduleId, const uint8_t* moduleBaseAddress, - std::vector nativeProtos) noexcept; + NativeModule( + SharedCodeAllocator* allocator, + const std::optional& moduleId, + const uint8_t* moduleBaseAddress, + std::vector nativeProtos + ) noexcept; NativeModule(const NativeModule&) = delete; NativeModule(NativeModule&&) = delete; @@ -132,11 +136,22 @@ public: // data and code such that it can be executed). Like std::map::insert, the // bool result is true if a new module was created; false if an existing // module is being returned. - std::pair getOrInsertNativeModule(const ModuleId& moduleId, std::vector nativeProtos, - const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize); + std::pair getOrInsertNativeModule( + const ModuleId& moduleId, + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize + ); NativeModuleRef insertAnonymousNativeModule( - std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize); + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize + ); // If a NativeModule exists for the given ModuleId and that NativeModule // is no longer referenced, the NativeModule is destroyed. This should diff --git a/CodeGen/include/Luau/UnwindBuilder.h b/CodeGen/include/Luau/UnwindBuilder.h index 03c9b56a..ff3c2aae 100644 --- a/CodeGen/include/Luau/UnwindBuilder.h +++ b/CodeGen/include/Luau/UnwindBuilder.h @@ -49,8 +49,13 @@ public: // mov rbp, rsp // push reg in the order specified in regs // sub rsp, stackSize - virtual void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, - const std::vector& simd) = 0; + virtual void prologueX64( + uint32_t prologueSize, + uint32_t stackSize, + bool setupFrame, + std::initializer_list gpr, + const std::vector& simd + ) = 0; virtual size_t getUnwindInfoSize(size_t blockSize) const = 0; diff --git a/CodeGen/include/Luau/UnwindBuilderDwarf2.h b/CodeGen/include/Luau/UnwindBuilderDwarf2.h index 1b634dec..64d4cdc3 100644 --- a/CodeGen/include/Luau/UnwindBuilderDwarf2.h +++ b/CodeGen/include/Luau/UnwindBuilderDwarf2.h @@ -30,8 +30,13 @@ public: void finishInfo() override; void prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list regs) override; - void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, - const std::vector& simd) override; + void prologueX64( + uint32_t prologueSize, + uint32_t stackSize, + bool setupFrame, + std::initializer_list gpr, + const std::vector& simd + ) override; size_t getUnwindInfoSize(size_t blockSize = 0) const override; diff --git a/CodeGen/include/Luau/UnwindBuilderWin.h b/CodeGen/include/Luau/UnwindBuilderWin.h index bc43b94a..f3c94f4d 100644 --- a/CodeGen/include/Luau/UnwindBuilderWin.h +++ b/CodeGen/include/Luau/UnwindBuilderWin.h @@ -50,8 +50,13 @@ public: void finishInfo() override; void prologueA64(uint32_t prologueSize, uint32_t stackSize, std::initializer_list regs) override; - void prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, - const std::vector& simd) override; + void prologueX64( + uint32_t prologueSize, + uint32_t stackSize, + bool setupFrame, + std::initializer_list gpr, + const std::vector& simd + ) override; size_t getUnwindInfoSize(size_t blockSize = 0) const override; diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 9d0522c0..b98a21f2 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -17,8 +17,8 @@ namespace A64 static const uint8_t codeForCondition[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; static_assert(sizeof(codeForCondition) / sizeof(codeForCondition[0]) == size_t(ConditionA64::Count), "all conditions have to be covered"); -static const char* textForCondition[] = { - "b.eq", "b.ne", "b.cs", "b.cc", "b.mi", "b.pl", "b.vs", "b.vc", "b.hi", "b.ls", "b.ge", "b.lt", "b.gt", "b.le", "b.al"}; +static const char* textForCondition[] = + {"b.eq", "b.ne", "b.cs", "b.cc", "b.mi", "b.pl", "b.vs", "b.vc", "b.hi", "b.ls", "b.ge", "b.lt", "b.gt", "b.le", "b.al"}; static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(ConditionA64::Count), "all conditions have to be covered"); const unsigned kMaxAlign = 32; @@ -968,8 +968,10 @@ void AssemblyBuilderA64::placeSR3(const char* name, RegisterA64 dst, RegisterA64 uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; - place(dst.index | (src1.index << 5) | ((shift < 0 ? -shift : shift) << 10) | (src2.index << 16) | (N << 21) | (int(shift < 0) << 22) | - (op << 24) | sf); + place( + dst.index | (src1.index << 5) | ((shift < 0 ? -shift : shift) << 10) | (src2.index << 16) | (N << 21) | (int(shift < 0) << 22) | (op << 24) | + sf + ); commit(); } @@ -1173,7 +1175,15 @@ void AssemblyBuilderA64::placeP(const char* name, RegisterA64 src1, RegisterA64 } void AssemblyBuilderA64::placeCS( - const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond, uint8_t op, uint8_t opc, int invert) + const char* name, + RegisterA64 dst, + RegisterA64 src1, + RegisterA64 src2, + ConditionA64 cond, + uint8_t op, + uint8_t opc, + int invert +) { if (logText) log(name, dst, src1, src2, cond); diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index f999d753..73c40679 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -15,21 +15,22 @@ namespace X64 // TODO: more assertions on operand sizes -static const uint8_t codeForCondition[] = { - 0x0, 0x1, 0x2, 0x3, 0x2, 0x6, 0x7, 0x3, 0x4, 0xc, 0xe, 0xf, 0xd, 0x3, 0x7, 0x6, 0x2, 0x5, 0xd, 0xf, 0xe, 0xc, 0x4, 0x5, 0xa, 0xb}; +static const uint8_t codeForCondition[] = {0x0, 0x1, 0x2, 0x3, 0x2, 0x6, 0x7, 0x3, 0x4, 0xc, 0xe, 0xf, 0xd, + 0x3, 0x7, 0x6, 0x2, 0x5, 0xd, 0xf, 0xe, 0xc, 0x4, 0x5, 0xa, 0xb}; static_assert(sizeof(codeForCondition) / sizeof(codeForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); -static const char* jccTextForCondition[] = {"jo", "jno", "jc", "jnc", "jb", "jbe", "ja", "jae", "je", "jl", "jle", "jg", "jge", "jnb", "jnbe", "jna", - "jnae", "jne", "jnl", "jnle", "jng", "jnge", "jz", "jnz", "jp", "jnp"}; +static const char* jccTextForCondition[] = {"jo", "jno", "jc", "jnc", "jb", "jbe", "ja", "jae", "je", "jl", "jle", "jg", "jge", + "jnb", "jnbe", "jna", "jnae", "jne", "jnl", "jnle", "jng", "jnge", "jz", "jnz", "jp", "jnp"}; static_assert(sizeof(jccTextForCondition) / sizeof(jccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); -static const char* setccTextForCondition[] = {"seto", "setno", "setc", "setnc", "setb", "setbe", "seta", "setae", "sete", "setl", "setle", "setg", - "setge", "setnb", "setnbe", "setna", "setnae", "setne", "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"}; +static const char* setccTextForCondition[] = {"seto", "setno", "setc", "setnc", "setb", "setbe", "seta", "setae", "sete", + "setl", "setle", "setg", "setge", "setnb", "setnbe", "setna", "setnae", "setne", + "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"}; static_assert(sizeof(setccTextForCondition) / sizeof(setccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); -static const char* cmovTextForCondition[] = {"cmovo", "cmovno", "cmovc", "cmovnc", "cmovb", "cmovbe", "cmova", "cmovae", "cmove", "cmovl", "cmovle", - "cmovg", "cmovge", "cmovnb", "cmovnbe", "cmovna", "cmovnae", "cmovne", "cmovnl", "cmovnle", "cmovng", "cmovnge", "cmovz", "cmovnz", "cmovp", - "cmovnp"}; +static const char* cmovTextForCondition[] = {"cmovo", "cmovno", "cmovc", "cmovnc", "cmovb", "cmovbe", "cmova", "cmovae", "cmove", + "cmovl", "cmovle", "cmovg", "cmovge", "cmovnb", "cmovnbe", "cmovna", "cmovnae", "cmovne", + "cmovnl", "cmovnle", "cmovng", "cmovnge", "cmovz", "cmovnz", "cmovp", "cmovnp"}; static_assert(sizeof(cmovTextForCondition) / sizeof(cmovTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); #define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7)) @@ -50,8 +51,8 @@ static_assert(sizeof(cmovTextForCondition) / sizeof(cmovTextForCondition[0]) == #define AVX_3_2(r, x, b, m) (AVX_R(r) | AVX_X(x) | AVX_B(b) | (m)) #define AVX_3_3(w, v, l, p) (AVX_W(w) | ((~(v.index) & 0xf) << 3) | ((l) << 2) | (p)) -#define MOD_RM(mod, reg, rm) (((mod) << 6) | (((reg)&0x7) << 3) | ((rm)&0x7)) -#define SIB(scale, index, base) ((getScaleEncoding(scale) << 6) | (((index)&0x7) << 3) | ((base)&0x7)) +#define MOD_RM(mod, reg, rm) (((mod) << 6) | (((reg) & 0x7) << 3) | ((rm) & 0x7)) +#define SIB(scale, index, base) ((getScaleEncoding(scale) << 6) | (((index) & 0x7) << 3) | ((base) & 0x7)) const unsigned AVX_0F = 0b0001; [[maybe_unused]] const unsigned AVX_0F38 = 0b0010; @@ -1136,8 +1137,19 @@ unsigned AssemblyBuilderX64::getInstructionCount() const return instructionCount; } -void AssemblyBuilderX64::placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, - uint8_t code8rev, uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg) +void AssemblyBuilderX64::placeBinary( + const char* name, + OperandX64 lhs, + OperandX64 rhs, + uint8_t codeimm8, + uint8_t codeimm, + uint8_t codeimmImm8, + uint8_t code8rev, + uint8_t coderev, + uint8_t code8, + uint8_t code, + uint8_t opreg +) { if (logText) log(name, lhs, rhs); @@ -1292,7 +1304,15 @@ void AssemblyBuilderX64::placeAvx(const char* name, OperandX64 dst, OperandX64 s } void AssemblyBuilderX64::placeAvx( - const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix) + const char* name, + OperandX64 dst, + OperandX64 src, + uint8_t code, + uint8_t coderev, + bool setW, + uint8_t mode, + uint8_t prefix +) { CODEGEN_ASSERT((dst.cat == CategoryX64::mem && src.cat == CategoryX64::reg) || (dst.cat == CategoryX64::reg && src.cat == CategoryX64::mem)); @@ -1316,7 +1336,15 @@ void AssemblyBuilderX64::placeAvx( } void AssemblyBuilderX64::placeAvx( - const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix) + const char* name, + OperandX64 dst, + OperandX64 src1, + OperandX64 src2, + uint8_t code, + bool setW, + uint8_t mode, + uint8_t prefix +) { CODEGEN_ASSERT(dst.cat == CategoryX64::reg); CODEGEN_ASSERT(src1.cat == CategoryX64::reg); @@ -1332,8 +1360,8 @@ void AssemblyBuilderX64::placeAvx( commit(); } -void AssemblyBuilderX64::placeAvx( - const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t imm8, uint8_t code, bool setW, uint8_t mode, uint8_t prefix) +void AssemblyBuilderX64:: + placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t imm8, uint8_t code, bool setW, uint8_t mode, uint8_t prefix) { CODEGEN_ASSERT(dst.cat == CategoryX64::reg); CODEGEN_ASSERT(src1.cat == CategoryX64::reg); @@ -1735,13 +1763,15 @@ const char* AssemblyBuilderX64::getSizeName(SizeX64 size) const const char* AssemblyBuilderX64::getRegisterName(RegisterX64 reg) const { - static const char* names[][16] = {{"rip", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, + static const char* names[][16] = { + {"rip", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, {"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b"}, {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w"}, {"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d"}, {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}, {"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"}, - {"ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15"}}; + {"ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15"} + }; CODEGEN_ASSERT(reg.index < 16); CODEGEN_ASSERT(reg.size <= SizeX64::ymmword); diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 023960b8..01adea34 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -116,12 +116,17 @@ void loadBytecodeTypeInfo(IrFunction& function) static void prepareRegTypeInfoLookups(BytecodeTypeInfo& typeInfo) { // Sort by register first, then by end PC - std::sort(typeInfo.regTypes.begin(), typeInfo.regTypes.end(), [](const BytecodeRegTypeInfo& a, const BytecodeRegTypeInfo& b) { - if (a.reg != b.reg) - return a.reg < b.reg; + std::sort( + typeInfo.regTypes.begin(), + typeInfo.regTypes.end(), + [](const BytecodeRegTypeInfo& a, const BytecodeRegTypeInfo& b) + { + if (a.reg != b.reg) + return a.reg < b.reg; - return a.endpc < b.endpc; - }); + return a.endpc < b.endpc; + } + ); // Prepare data for all registers as 'regTypes' might be missing temporaries typeInfo.regTypeOffsets.resize(256 + 1); @@ -805,8 +810,7 @@ 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]; @@ -837,8 +841,7 @@ 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)); } @@ -860,8 +863,7 @@ 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]; @@ -883,8 +885,7 @@ 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]; @@ -915,8 +916,7 @@ 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)); } @@ -938,8 +938,7 @@ 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]; @@ -960,8 +959,7 @@ 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]; @@ -990,8 +988,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) regTags[ra] = LBC_TYPE_VECTOR; } - else if (hostHooks.userdataMetamethodBytecodeType && - (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) + else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) { regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); } diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index ca667ae7..cf41c81c 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -143,7 +143,14 @@ CodeAllocator::~CodeAllocator() } bool CodeAllocator::allocate( - const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart) + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize, + uint8_t*& result, + size_t& resultSize, + uint8_t*& resultCodeStart +) { // 'Round up' to preserve code alignment size_t alignedDataSize = (dataSize + (kCodeAlignment - 1)) & ~(kCodeAlignment - 1); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 694a9f7e..667f5726 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -109,29 +109,34 @@ void onDisable(lua_State* L, Proto* proto) // walk all thread call stacks and clear the LUA_CALLINFO_NATIVE flag from any // entries pointing to the current proto that has native code enabled. - luaM_visitgco(L, proto, [](void* context, lua_Page* page, GCObject* gco) { - Proto* proto = (Proto*)context; - - if (gco->gch.tt != LUA_TTHREAD) - return false; - - lua_State* th = gco2th(gco); - - for (CallInfo* ci = th->ci; ci > th->base_ci; ci--) + luaM_visitgco( + L, + proto, + [](void* context, lua_Page* page, GCObject* gco) { - if (isLua(ci)) - { - Proto* p = clvalue(ci->func)->l.p; + Proto* proto = (Proto*)context; - if (p == proto) + if (gco->gch.tt != LUA_TTHREAD) + return false; + + lua_State* th = gco2th(gco); + + for (CallInfo* ci = th->ci; ci > th->base_ci; ci--) + { + if (isLua(ci)) { - ci->flags &= ~LUA_CALLINFO_NATIVE; + Proto* p = clvalue(ci->func)->l.p; + + if (p == proto) + { + ci->flags &= ~LUA_CALLINFO_NATIVE; + } } } - } - return false; - }); + return false; + } + ); } #if defined(CODEGEN_TARGET_A64) @@ -161,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/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 06f64955..1d6e17a5 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -274,8 +274,15 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) CODEGEN_ASSERT(build.data.empty()); uint8_t* codeStart = nullptr; - if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), reinterpret_cast(build.code.data()), - int(build.code.size() * sizeof(build.code[0])), codeGenContext.gateData, codeGenContext.gateDataSize, codeStart)) + if (!codeGenContext.codeAllocator.allocate( + build.data.data(), + int(build.data.size()), + reinterpret_cast(build.code.data()), + int(build.code.size() * sizeof(build.code[0])), + codeGenContext.gateData, + codeGenContext.gateDataSize, + codeStart + )) { CODEGEN_ASSERT(!"Failed to create entry function"); return false; diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index f78586e5..295e1cb8 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -157,11 +157,17 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A else gatherFunctions_DEPRECATED(protos, root, options.compilationOptions.flags); - protos.erase(std::remove_if(protos.begin(), protos.end(), - [](Proto* p) { - return p == nullptr; - }), - protos.end()); + protos.erase( + std::remove_if( + protos.begin(), + protos.end(), + [](Proto* p) + { + return p == nullptr; + } + ), + protos.end() + ); if (stats) stats->totalFunctions += unsigned(protos.size()); diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index a31a08ba..a463091d 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -50,14 +50,21 @@ static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size) } static void logPerfFunctions( - const std::vector& moduleProtos, const uint8_t* nativeModuleBaseAddress, const std::vector& nativeProtos) + const std::vector& moduleProtos, + const uint8_t* nativeModuleBaseAddress, + const std::vector& nativeProtos +) { if (gPerfLogFn == nullptr) return; if (nativeProtos.size() > 0) - gPerfLogFn(gPerfLogContext, uintptr_t(nativeModuleBaseAddress), - unsigned(getNativeProtoExecDataHeader(nativeProtos[0].get()).entryOffsetOrAddress - nativeModuleBaseAddress), ""); + gPerfLogFn( + gPerfLogContext, + uintptr_t(nativeModuleBaseAddress), + unsigned(getNativeProtoExecDataHeader(nativeProtos[0].get()).entryOffsetOrAddress - nativeModuleBaseAddress), + "" + ); auto protoIt = moduleProtos.begin(); @@ -156,7 +163,11 @@ BaseCodeGenContext::BaseCodeGenContext(size_t blockSize, size_t maxTotalSize, Al StandaloneCodeGenContext::StandaloneCodeGenContext( - size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + size_t blockSize, + size_t maxTotalSize, + AllocationCallback* allocationCallback, + void* allocationCallbackContext +) : BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext} { } @@ -167,8 +178,15 @@ StandaloneCodeGenContext::StandaloneCodeGenContext( return {}; } -[[nodiscard]] ModuleBindResult StandaloneCodeGenContext::bindModule(const std::optional&, const std::vector& moduleProtos, - std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) +[[nodiscard]] ModuleBindResult StandaloneCodeGenContext::bindModule( + const std::optional&, + const std::vector& moduleProtos, + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize +) { uint8_t* nativeData = nullptr; size_t sizeNativeData = 0; @@ -207,14 +225,20 @@ void StandaloneCodeGenContext::onDestroyFunction(void* execdata) noexcept SharedCodeGenContext::SharedCodeGenContext( - size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + size_t blockSize, + size_t maxTotalSize, + AllocationCallback* allocationCallback, + void* allocationCallbackContext +) : BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext} , sharedAllocator{&codeAllocator} { } [[nodiscard]] std::optional SharedCodeGenContext::tryBindExistingModule( - const ModuleId& moduleId, const std::vector& moduleProtos) + const ModuleId& moduleId, + const std::vector& moduleProtos +) { NativeModuleRef nativeModule = sharedAllocator.tryGetNativeModule(moduleId); if (nativeModule.empty()) @@ -229,10 +253,18 @@ SharedCodeGenContext::SharedCodeGenContext( return {{CodeGenCompilationResult::Success, protosBound}}; } -[[nodiscard]] ModuleBindResult SharedCodeGenContext::bindModule(const std::optional& moduleId, const std::vector& moduleProtos, - std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) +[[nodiscard]] ModuleBindResult SharedCodeGenContext::bindModule( + const std::optional& moduleId, + const std::vector& moduleProtos, + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize +) { - const std::pair insertionResult = [&]() -> std::pair { + const std::pair insertionResult = [&]() -> std::pair + { if (moduleId.has_value()) { return sharedAllocator.getOrInsertNativeModule(*moduleId, std::move(nativeProtos), data, dataSize, code, codeSize); @@ -279,11 +311,16 @@ void SharedCodeGenContext::onDestroyFunction(void* execdata) noexcept [[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(AllocationCallback* allocationCallback, void* allocationCallbackContext) { return createSharedCodeGenContext( - size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext); + size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext + ); } [[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext( - size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext) + size_t blockSize, + size_t maxTotalSize, + AllocationCallback* allocationCallback, + void* allocationCallbackContext +) { UniqueSharedCodeGenContext codeGenContext{new SharedCodeGenContext{blockSize, maxTotalSize, nullptr, nullptr}}; @@ -422,8 +459,14 @@ void create(lua_State* L, SharedCodeGenContext* codeGenContext) } template -[[nodiscard]] static NativeProtoExecDataPtr createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, - uint32_t& totalIrInstCount, const HostIrHooks& hooks, CodeGenCompilationResult& result) +[[nodiscard]] static NativeProtoExecDataPtr createNativeFunction( + AssemblyBuilder& build, + ModuleHelpers& helpers, + Proto* proto, + uint32_t& totalIrInstCount, + const HostIrHooks& hooks, + CodeGenCompilationResult& result +) { IrBuilder ir(hooks); ir.buildFunctionIr(proto); @@ -447,7 +490,12 @@ template } [[nodiscard]] static CompilationResult compileInternal( - const std::optional& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats) + const std::optional& moduleId, + lua_State* L, + int idx, + const CompilationOptions& options, + CompilationStats* stats +) { CODEGEN_ASSERT(lua_isLfunction(L, idx)); const TValue* func = luaA_toobject(L, idx); @@ -468,11 +516,17 @@ template gatherFunctions_DEPRECATED(protos, root, options.flags); // Skip protos that have been compiled during previous invocations of CodeGen::compile - protos.erase(std::remove_if(protos.begin(), protos.end(), - [](Proto* p) { - return p == nullptr || p->execdata != nullptr; - }), - protos.end()); + protos.erase( + std::remove_if( + protos.begin(), + protos.end(), + [](Proto* p) + { + return p == nullptr || p->execdata != nullptr; + } + ), + protos.end() + ); if (protos.empty()) return CompilationResult{CodeGenCompilationResult::NothingToCompile}; @@ -523,8 +577,8 @@ template } else { - compilationResult.protoFailures.push_back( - {protoResult, protos[i]->debugname ? getstr(protos[i]->debugname) : "", protos[i]->linedefined}); + compilationResult.protoFailures.push_back({protoResult, protos[i]->debugname ? getstr(protos[i]->debugname) : "", protos[i]->linedefined} + ); } } @@ -570,9 +624,15 @@ template header.nativeCodeSize = end - begin; } - const ModuleBindResult bindResult = - codeGenContext->bindModule(moduleId, protos, std::move(nativeProtos), reinterpret_cast(build.data.data()), build.data.size(), - reinterpret_cast(build.code.data()), build.code.size() * sizeof(build.code[0])); + const ModuleBindResult bindResult = codeGenContext->bindModule( + moduleId, + protos, + std::move(nativeProtos), + reinterpret_cast(build.data.data()), + build.data.size(), + reinterpret_cast(build.code.data()), + build.code.size() * sizeof(build.code[0]) + ); if (stats != nullptr) stats->functionsBound = bindResult.functionsBound; diff --git a/CodeGen/src/CodeGenContext.h b/CodeGen/src/CodeGenContext.h index 43099a9b..19ef295e 100644 --- a/CodeGen/src/CodeGenContext.h +++ b/CodeGen/src/CodeGenContext.h @@ -36,10 +36,19 @@ public: [[nodiscard]] bool initHeaderFunctions(); [[nodiscard]] virtual std::optional tryBindExistingModule( - const ModuleId& moduleId, const std::vector& moduleProtos) = 0; + const ModuleId& moduleId, + const std::vector& moduleProtos + ) = 0; - [[nodiscard]] virtual ModuleBindResult bindModule(const std::optional& moduleId, const std::vector& moduleProtos, - std::vector nativeExecDatas, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) = 0; + [[nodiscard]] virtual ModuleBindResult bindModule( + const std::optional& moduleId, + const std::vector& moduleProtos, + std::vector nativeExecDatas, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize + ) = 0; virtual void onCloseState() noexcept = 0; virtual void onDestroyFunction(void* execdata) noexcept = 0; @@ -61,11 +70,18 @@ class StandaloneCodeGenContext final : public BaseCodeGenContext public: StandaloneCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); - [[nodiscard]] virtual std::optional tryBindExistingModule( - const ModuleId& moduleId, const std::vector& moduleProtos) override; + [[nodiscard]] virtual std::optional tryBindExistingModule(const ModuleId& moduleId, const std::vector& moduleProtos) + override; - [[nodiscard]] virtual ModuleBindResult bindModule(const std::optional& moduleId, const std::vector& moduleProtos, - std::vector nativeExecDatas, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) override; + [[nodiscard]] virtual ModuleBindResult bindModule( + const std::optional& moduleId, + const std::vector& moduleProtos, + std::vector nativeExecDatas, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize + ) override; virtual void onCloseState() noexcept override; virtual void onDestroyFunction(void* execdata) noexcept override; @@ -78,11 +94,18 @@ class SharedCodeGenContext final : public BaseCodeGenContext public: SharedCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); - [[nodiscard]] virtual std::optional tryBindExistingModule( - const ModuleId& moduleId, const std::vector& moduleProtos) override; + [[nodiscard]] virtual std::optional tryBindExistingModule(const ModuleId& moduleId, const std::vector& moduleProtos) + override; - [[nodiscard]] virtual ModuleBindResult bindModule(const std::optional& moduleId, const std::vector& moduleProtos, - std::vector nativeExecDatas, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) override; + [[nodiscard]] virtual ModuleBindResult bindModule( + const std::optional& moduleId, + const std::vector& moduleProtos, + std::vector nativeExecDatas, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize + ) override; virtual void onCloseState() noexcept override; virtual void onDestroyFunction(void* execdata) noexcept override; diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index daeaa251..9e77844b 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -53,7 +53,12 @@ inline void gatherFunctions_DEPRECATED(std::vector& results, Proto* prot } inline void gatherFunctionsHelper( - std::vector& results, Proto* proto, const unsigned int flags, const bool hasNativeFunctions, const bool root) + std::vector& results, + Proto* proto, + const unsigned int flags, + const bool hasNativeFunctions, + const bool root +) { if (results.size() <= size_t(proto->bytecodeid)) results.resize(proto->bytecodeid + 1); @@ -83,14 +88,25 @@ inline void gatherFunctions(std::vector& results, Proto* root, const uns inline unsigned getInstructionCount(const std::vector& instructions, IrCmd cmd) { - return unsigned(std::count_if(instructions.begin(), instructions.end(), [&cmd](const IrInst& inst) { - return inst.cmd == cmd; - })); + return unsigned(std::count_if( + instructions.begin(), + instructions.end(), + [&cmd](const IrInst& inst) + { + return inst.cmd == cmd; + } + )); } template -inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, const std::vector& sortedBlocks, int bytecodeid, - AssemblyOptions options) +inline bool lowerImpl( + AssemblyBuilder& build, + IrLowering& lowering, + IrFunction& function, + const std::vector& sortedBlocks, + int bytecodeid, + AssemblyOptions options +) { // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? std::vector bcLocations(function.instructions.size() + 1, ~0u); @@ -259,8 +275,15 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& return true; } -inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, const std::vector& sortedBlocks, ModuleHelpers& helpers, Proto* proto, - AssemblyOptions options, LoweringStats* stats) +inline bool lowerIr( + X64::AssemblyBuilderX64& build, + IrBuilder& ir, + const std::vector& sortedBlocks, + ModuleHelpers& helpers, + Proto* proto, + AssemblyOptions options, + LoweringStats* stats +) { optimizeMemoryOperandsX64(ir.function); @@ -269,8 +292,15 @@ inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, const std::ve return lowerImpl(build, lowering, ir.function, sortedBlocks, proto->bytecodeid, options); } -inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::vector& sortedBlocks, ModuleHelpers& helpers, Proto* proto, - AssemblyOptions options, LoweringStats* stats) +inline bool lowerIr( + A64::AssemblyBuilderA64& build, + IrBuilder& ir, + const std::vector& sortedBlocks, + ModuleHelpers& helpers, + Proto* proto, + AssemblyOptions options, + LoweringStats* stats +) { A64::IrLoweringA64 lowering(build, helpers, ir.function, stats); @@ -278,8 +308,15 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::ve } template -inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats, - CodeGenCompilationResult& codeGenCompilationResult) +inline bool lowerFunction( + IrBuilder& ir, + AssemblyBuilder& build, + ModuleHelpers& helpers, + Proto* proto, + AssemblyOptions options, + LoweringStats* stats, + CodeGenCompilationResult& codeGenCompilationResult +) { killUnusedBlocks(ir.function); diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index b8df3774..3a7aa2b5 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -202,8 +202,15 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) CODEGEN_ASSERT(build.data.empty()); uint8_t* codeStart = nullptr; - if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), build.code.data(), int(build.code.size()), - codeGenContext.gateData, codeGenContext.gateDataSize, codeStart)) + if (!codeGenContext.codeAllocator.allocate( + build.data.data(), + int(build.data.size()), + build.code.data(), + int(build.code.size()), + codeGenContext.gateData, + codeGenContext.gateDataSize, + codeStart + )) { CODEGEN_ASSERT(!"Failed to create entry function"); return false; diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index 73775bf9..0d2f9bd3 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -29,7 +29,8 @@ void updateUseCounts(IrFunction& function) for (IrInst& inst : instructions) inst.useCount = 0; - auto checkOp = [&](IrOp op) { + auto checkOp = [&](IrOp op) + { if (op.kind == IrOpKind::Inst) { IrInst& target = instructions[op.index]; @@ -82,7 +83,8 @@ void updateLastUseLocations(IrFunction& function, const std::vector& s CODEGEN_ASSERT(instIdx < function.instructions.size()); IrInst& inst = instructions[instIdx]; - auto checkOp = [&](IrOp op) { + auto checkOp = [&](IrOp op) + { if (op.kind == IrOpKind::Inst) instructions[op.index].lastUse = uint32_t(instIdx); }; @@ -145,7 +147,8 @@ std::pair getLiveInOutValueCount(IrFunction& function, IrBlo uint32_t liveIns = 0; uint32_t liveOuts = 0; - auto checkOp = [&](IrOp op) { + auto checkOp = [&](IrOp op) + { if (op.kind == IrOpKind::Inst) { if (op.index >= block.start && op.index <= block.finish) @@ -477,7 +480,8 @@ static void computeCfgBlockEdges(IrFunction& function) { const IrInst& inst = function.instructions[instIdx]; - auto checkOp = [&](IrOp op) { + auto checkOp = [&](IrOp op) + { if (op.kind == IrOpKind::Block) { // We use a trick here, where we use the starting offset of the predecessor list as the position where to write next predecessor @@ -512,7 +516,11 @@ static void computeCfgBlockEdges(IrFunction& function) // Optionally, collect required node order into a vector template void computeBlockOrdering( - IrFunction& function, std::vector& ordering, std::vector* preOrder, std::vector* postOrder) + IrFunction& function, + std::vector& ordering, + std::vector* preOrder, + std::vector* postOrder +) { CfgInfo& info = function.cfg; @@ -712,7 +720,11 @@ void computeCfgDominanceTreeChildren(IrFunction& function) // This algorithm is based on 'A Linear Time Algorithm for Placing Phi-Nodes' [Vugranam C.Sreedhar] // It uses the optimized form from LLVM that relies an implicit DJ-graph (join edges are edges of the CFG that are not part of the dominance tree) void computeIteratedDominanceFrontierForDefs( - IdfContext& ctx, const IrFunction& function, const std::vector& defBlocks, const std::vector& liveInBlocks) + IdfContext& ctx, + const IrFunction& function, + const std::vector& defBlocks, + const std::vector& liveInBlocks +) { CODEGEN_ASSERT(!function.cfg.domOrdering.empty()); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 73a1bdc2..53def728 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -432,8 +432,9 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstDupTable(*this, pc, i); break; case LOP_SETLIST: - inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), - undef()); + inst( + IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), undef() + ); break; case LOP_GETUPVAL: translateInstGetUpval(*this, pc, i); @@ -603,7 +604,8 @@ void IrBuilder::clone(const IrBlock& source, bool removeCurrentTerminator) { DenseHashMap instRedir{~0u}; - auto redirect = [&instRedir](IrOp& op) { + auto redirect = [&instRedir](IrOp& op) + { if (op.kind == IrOpKind::Inst) { if (const uint32_t* newIndex = instRedir.find(op.index)) diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index ab20c28d..2846db54 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -12,8 +12,8 @@ namespace Luau namespace CodeGen { -static const char* textForCondition[] = { - "eq", "not_eq", "lt", "not_lt", "le", "not_le", "gt", "not_gt", "ge", "not_ge", "u_lt", "u_le", "u_gt", "u_ge"}; +static const char* textForCondition[] = + {"eq", "not_eq", "lt", "not_lt", "le", "not_le", "gt", "not_gt", "ge", "not_ge", "u_lt", "u_le", "u_gt", "u_ge"}; static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered"); const int kDetailsAlignColumn = 60; @@ -403,7 +403,8 @@ void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index) ctx.result.append(getCmdName(inst.cmd)); - auto checkOp = [&ctx](IrOp op, const char* sep) { + auto checkOp = [&ctx](IrOp op, const char* sep) + { if (op.kind != IrOpKind::None) { ctx.result.append(sep); @@ -624,7 +625,13 @@ static RegisterSet getJumpTargetExtraLiveIn(IrToStringContext& ctx, const IrBloc } void toStringDetailed( - IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, IncludeUseInfo includeUseInfo) + IrToStringContext& ctx, + const IrBlock& block, + uint32_t blockIdx, + const IrInst& inst, + uint32_t instIdx, + IncludeUseInfo includeUseInfo +) { size_t start = ctx.result.size(); @@ -667,8 +674,14 @@ void toStringDetailed( } } -void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, IncludeUseInfo includeUseInfo, IncludeCfgInfo includeCfgInfo, - IncludeRegFlowInfo includeRegFlowInfo) +void toStringDetailed( + IrToStringContext& ctx, + const IrBlock& block, + uint32_t blockIdx, + IncludeUseInfo includeUseInfo, + IncludeCfgInfo includeCfgInfo, + IncludeRegFlowInfo includeRegFlowInfo +) { // Report captured registers for entry block if (includeRegFlowInfo == IncludeRegFlowInfo::Yes && block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any()) @@ -877,7 +890,8 @@ std::string toDot(const IrFunction& function, bool includeInst) { const IrInst& inst = function.instructions[instIdx]; - auto checkOp = [&](IrOp op) { + auto checkOp = [&](IrOp op) + { if (op.kind == IrOpKind::Block) { if (function.blocks[op.index].kind != IrBlockKind::Fallback) diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index b718be32..f40faa2d 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -278,10 +278,14 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, , valueTracker(function) , exitHandlerMap(~0u) { - valueTracker.setRestoreCallack(this, [](void* context, IrInst& inst) { - IrLoweringA64* self = static_cast(context); - self->regs.restoreReg(self->build, inst); - }); + valueTracker.setRestoreCallack( + this, + [](void* context, IrInst& inst) + { + IrLoweringA64* self = static_cast(context); + self->regs.restoreReg(self->build, inst); + } + ); } void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index f38370ce..76d88955 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -34,9 +34,13 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, , valueTracker(function) , exitHandlerMap(~0u) { - valueTracker.setRestoreCallack(®s, [](void* context, IrInst& inst) { - ((IrRegAllocX64*)context)->restore(inst, false); - }); + valueTracker.setRestoreCallack( + ®s, + [](void* context, IrInst& inst) + { + ((IrRegAllocX64*)context)->restore(inst, false); + } + ); build.align(kFunctionAlignment, X64::AlignmentDataX64::Ud2); } @@ -118,7 +122,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vcvtss2sd(inst.regX64, inst.regX64, dword[rBase + vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, value) + intOp(inst.b)]); else if (inst.a.kind == IrOpKind::VmConst) build.vcvtss2sd( - inst.regX64, inst.regX64, dword[rConstants + vmConstOp(inst.a) * sizeof(TValue) + offsetof(TValue, value) + intOp(inst.b)]); + inst.regX64, inst.regX64, dword[rConstants + vmConstOp(inst.a) * sizeof(TValue) + offsetof(TValue, value) + intOp(inst.b)] + ); else CODEGEN_ASSERT(!"Unsupported instruction form"); break; @@ -1522,7 +1527,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::SETLIST: regs.assertAllFree(); emitInstSetList( - regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e), inst.f.kind == IrOpKind::Undef ? -1 : int(uintOp(inst.f))); + regs, build, vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d), uintOp(inst.e), inst.f.kind == IrOpKind::Undef ? -1 : int(uintOp(inst.f)) + ); break; case IrCmd::CALL: regs.assertAllFree(); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index 522b0bc1..4471aaa5 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -245,7 +245,8 @@ void IrRegAllocA64::freeLastUseReg(IrInst& target, uint32_t index) void IrRegAllocA64::freeLastUseRegs(const IrInst& inst, uint32_t index) { - auto checkOp = [this, index](IrOp op) { + auto checkOp = [this, index](IrOp op) + { if (op.kind == IrOpKind::Inst) freeLastUseReg(function.instructions[op.index], index); }; diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 12b9e379..d647484b 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -170,7 +170,8 @@ void IrRegAllocX64::freeLastUseReg(IrInst& target, uint32_t instIdx) void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t instIdx) { - auto checkOp = [this, instIdx](IrOp op) { + auto checkOp = [this, instIdx](IrOp op) + { if (op.kind == IrOpKind::Inst) freeLastUseReg(function.instructions[op.index], instIdx); }; diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index f6a77f21..7abab9f0 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -41,7 +41,15 @@ static IrOp builtinLoadDouble(IrBuilder& build, IrOp arg) // (number, ...) -> number static BuiltinImplResult translateBuiltinNumberToNumber( - IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) + IrBuilder& build, + LuauBuiltinFunction bfid, + int nparams, + int ra, + int arg, + IrOp args, + int nresults, + int pcpos +) { CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); @@ -59,7 +67,14 @@ static BuiltinImplResult translateBuiltinNumberToNumber( } static BuiltinImplResult translateBuiltinNumberToNumberLibm( - IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, int nresults, int pcpos) + IrBuilder& build, + LuauBuiltinFunction bfid, + int nparams, + int ra, + int arg, + int nresults, + int pcpos +) { if (nparams < 1 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -78,7 +93,15 @@ static BuiltinImplResult translateBuiltinNumberToNumberLibm( } static BuiltinImplResult translateBuiltin2NumberToNumberLibm( - IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) + IrBuilder& build, + LuauBuiltinFunction bfid, + int nparams, + int ra, + int arg, + IrOp args, + int nresults, + int pcpos +) { if (nparams < 2 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -104,7 +127,15 @@ static BuiltinImplResult translateBuiltin2NumberToNumberLibm( // (number, ...) -> (number, number) static BuiltinImplResult translateBuiltinNumberTo2Number( - IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) + IrBuilder& build, + LuauBuiltinFunction bfid, + int nparams, + int ra, + int arg, + IrOp args, + int nresults, + int pcpos +) { if (nparams < 1 || nresults > 2) return {BuiltinImplType::None, -1}; @@ -114,8 +145,15 @@ static BuiltinImplResult translateBuiltinNumberTo2Number( if (FFlag::LuauCodegenFastcall3) build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2)); else - build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.undef(), build.constInt(1), - build.constInt(nresults == 1 ? 1 : 2)); + build.inst( + IrCmd::FASTCALL, + build.constUint(bfid), + build.vmReg(ra), + build.vmReg(arg), + build.undef(), + build.constInt(1), + build.constInt(nresults == 1 ? 1 : 2) + ); return {BuiltinImplType::Full, 2}; } @@ -195,7 +233,16 @@ static BuiltinImplResult translateBuiltinMathLog(IrBuilder& build, int nparams, } static BuiltinImplResult translateBuiltinMathMinMax( - IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos) + IrBuilder& build, + IrCmd cmd, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + int pcpos +) { if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1) return {BuiltinImplType::None, -1}; @@ -235,7 +282,16 @@ static BuiltinImplResult translateBuiltinMathMinMax( } static BuiltinImplResult translateBuiltinMathClamp( - IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos) + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + IrOp fallback, + int pcpos +) { if (nparams < 3 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -312,7 +368,17 @@ static BuiltinImplResult translateBuiltinTypeof(IrBuilder& build, int nparams, i } static BuiltinImplResult translateBuiltinBit32BinaryOp( - IrBuilder& build, IrCmd cmd, bool btest, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos) + IrBuilder& build, + IrCmd cmd, + bool btest, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + int pcpos +) { if (nparams < 2 || nparams > kBit32BinaryOpUnrolledParams || nresults > 1) return {BuiltinImplType::None, -1}; @@ -401,7 +467,16 @@ static BuiltinImplResult translateBuiltinBit32Bnot(IrBuilder& build, int nparams } static BuiltinImplResult translateBuiltinBit32Shift( - IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback, int pcpos) + IrBuilder& build, + IrCmd cmd, + int nparams, + int ra, + int arg, + IrOp args, + int nresults, + IrOp fallback, + int pcpos +) { if (nparams < 2 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -467,7 +542,16 @@ static BuiltinImplResult translateBuiltinBit32Rotate(IrBuilder& build, IrCmd cmd } static BuiltinImplResult translateBuiltinBit32Extract( - IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos) + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + IrOp fallback, + int pcpos +) { if (nparams < 2 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -605,7 +689,16 @@ static BuiltinImplResult translateBuiltinBit32Unary(IrBuilder& build, IrCmd cmd, } static BuiltinImplResult translateBuiltinBit32Replace( - IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, IrOp fallback, int pcpos) + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + IrOp fallback, + int pcpos +) { if (nparams < 3 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -755,7 +848,16 @@ static BuiltinImplResult translateBuiltinStringLen(IrBuilder& build, int nparams } static void translateBufferArgsAndCheckBounds( - IrBuilder& build, int nparams, int arg, IrOp args, IrOp arg3, int size, int pcpos, IrOp& buf, IrOp& intIndex) + IrBuilder& build, + int nparams, + int arg, + IrOp args, + IrOp arg3, + int size, + int pcpos, + IrOp& buf, + IrOp& intIndex +) { build.loadAndCheckTag(build.vmReg(arg), LUA_TBUFFER, build.vmExit(pcpos)); builtinCheckDouble(build, args, pcpos); @@ -772,7 +874,18 @@ static void translateBufferArgsAndCheckBounds( } static BuiltinImplResult translateBuiltinBufferRead( - IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos, IrCmd readCmd, int size, IrCmd convCmd) + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + int pcpos, + IrCmd readCmd, + int size, + IrCmd convCmd +) { if (nparams < 2 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -788,7 +901,18 @@ static BuiltinImplResult translateBuiltinBufferRead( } static BuiltinImplResult translateBuiltinBufferWrite( - IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos, IrCmd writeCmd, int size, IrCmd convCmd) + IrBuilder& build, + int nparams, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nresults, + int pcpos, + IrCmd writeCmd, + int size, + IrCmd convCmd +) { if (nparams < 3 || nresults > 0) return {BuiltinImplType::None, -1}; @@ -803,7 +927,17 @@ static BuiltinImplResult translateBuiltinBufferWrite( } BuiltinImplResult translateBuiltin( - IrBuilder& build, int bfid, int ra, int arg, IrOp args, IrOp arg3, int nparams, int nresults, IrOp fallback, int pcpos) + IrBuilder& build, + int bfid, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nparams, + int nresults, + IrOp fallback, + int pcpos +) { // Builtins are not allowed to handle variadic arguments if (nparams == LUA_MULTRET) diff --git a/CodeGen/src/IrTranslateBuiltins.h b/CodeGen/src/IrTranslateBuiltins.h index 54a05aba..d95a251d 100644 --- a/CodeGen/src/IrTranslateBuiltins.h +++ b/CodeGen/src/IrTranslateBuiltins.h @@ -23,7 +23,17 @@ struct BuiltinImplResult }; BuiltinImplResult translateBuiltin( - IrBuilder& build, int bfid, int ra, int arg, IrOp args, IrOp arg3, int nparams, int nresults, IrOp fallback, int pcpos); + IrBuilder& build, + int bfid, + int ra, + int arg, + IrOp args, + IrOp arg3, + int nparams, + int nresults, + IrOp fallback, + int pcpos +); } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 59c616f9..5d6aa95a 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -460,15 +460,23 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, if (rb != -1) { IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); - build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), - bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback)); + build.inst( + IrCmd::CHECK_TAG, + tb, + build.constTag(LUA_TNUMBER), + bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback) + ); } if (rc != -1 && rc != rb) { IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)); - build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), - bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback)); + build.inst( + IrCmd::CHECK_TAG, + tc, + build.constTag(LUA_TNUMBER), + bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback) + ); } IrOp vb = loadDoubleOrConstant(build, opb); @@ -548,19 +556,22 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm) { translateInstBinaryNumeric( - build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm); + build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm + ); } void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm) { translateInstBinaryNumeric( - build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmReg(LUAU_INSN_B(*pc)), build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm); + build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmReg(LUAU_INSN_B(*pc)), build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm + ); } void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm) { translateInstBinaryNumeric( - build, LUAU_INSN_A(*pc), -1, LUAU_INSN_C(*pc), build.vmConst(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm); + build, LUAU_INSN_A(*pc), -1, LUAU_INSN_C(*pc), build.vmConst(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm + ); } void translateInstNot(IrBuilder& build, const Instruction* pc) @@ -609,8 +620,12 @@ void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fallback; IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); - build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), - bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback)); + build.inst( + IrCmd::CHECK_TAG, + tb, + build.constTag(LUA_TNUMBER), + bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback) + ); // fast-path: number IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb)); @@ -760,7 +775,8 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool build.inst(IrCmd::CHECK_SAFE_ENV, build.vmExit(pcpos + getOpLength(opcode))); BuiltinImplResult br = translateBuiltin( - build, LuauBuiltinFunction(bfid), ra, arg, builtinArgs, builtinArg3, nparams, nresults, fallback, pcpos + getOpLength(opcode)); + build, LuauBuiltinFunction(bfid), ra, arg, builtinArgs, builtinArg3, nparams, nresults, fallback, pcpos + getOpLength(opcode) + ); if (br.type != BuiltinImplType::None) { @@ -784,8 +800,16 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool // TODO: we can skip saving pc for some well-behaved builtins which we didn't inline build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode))); - IrOp res = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, arg3, build.constInt(nparams), - build.constInt(nresults)); + IrOp res = build.inst( + IrCmd::INVOKE_FASTCALL, + build.constUint(bfid), + build.vmReg(ra), + build.vmReg(arg), + args, + arg3, + build.constInt(nparams), + build.constInt(nresults) + ); build.inst(IrCmd::CHECK_FASTCALL_RES, res, fallback); if (nresults == LUA_MULTRET) @@ -798,8 +822,9 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool // TODO: we can skip saving pc for some well-behaved builtins which we didn't inline build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode))); - IrOp res = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), - build.constInt(nresults)); + IrOp res = build.inst( + IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults) + ); build.inst(IrCmd::CHECK_FASTCALL_RES, res, fallback); if (nresults == LUA_MULTRET) diff --git a/CodeGen/src/IrTranslation.h b/CodeGen/src/IrTranslation.h index 8b514cc1..662799b3 100644 --- a/CodeGen/src/IrTranslation.h +++ b/CodeGen/src/IrTranslation.h @@ -45,7 +45,14 @@ void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc); IrOp translateFastCallN( - IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs, IrOp customArg3); + IrBuilder& build, + const Instruction* pc, + int pcpos, + bool customParams, + int customParamCount, + IrOp customArgs, + IrOp customArg3 +); void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstForGPrepNext(IrBuilder& build, const Instruction* pc, int pcpos); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index b5795ca1..ebf4c34b 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -963,21 +963,26 @@ std::vector getSortedBlockOrder(IrFunction& function) for (uint32_t i = 0; i < function.blocks.size(); i++) sortedBlocks.push_back(i); - std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) { - const IrBlock& a = function.blocks[idxA]; - const IrBlock& b = function.blocks[idxB]; + std::sort( + sortedBlocks.begin(), + sortedBlocks.end(), + [&](uint32_t idxA, uint32_t idxB) + { + const IrBlock& a = function.blocks[idxA]; + const IrBlock& b = function.blocks[idxB]; - // Place fallback blocks at the end - if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) - return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); + // Place fallback blocks at the end + if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback)) + return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); - // Try to order by instruction order - if (a.sortkey != b.sortkey) - return a.sortkey < b.sortkey; + // Try to order by instruction order + if (a.sortkey != b.sortkey) + return a.sortkey < b.sortkey; - // Chains of blocks are merged together by having the same sort key and consecutive chain key - return a.chainkey < b.chainkey; - }); + // Chains of blocks are merged together by having the same sort key and consecutive chain key + return a.chainkey < b.chainkey; + } + ); return sortedBlocks; } diff --git a/CodeGen/src/NativeProtoExecData.cpp b/CodeGen/src/NativeProtoExecData.cpp index 33a1db76..3563bfbd 100644 --- a/CodeGen/src/NativeProtoExecData.cpp +++ b/CodeGen/src/NativeProtoExecData.cpp @@ -42,7 +42,8 @@ void destroyNativeProtoExecData(const uint32_t* instructionOffsets) noexcept [[nodiscard]] const NativeProtoExecDataHeader& getNativeProtoExecDataHeader(const uint32_t* instructionOffsets) noexcept { return *reinterpret_cast( - reinterpret_cast(instructionOffsets) - sizeof(NativeProtoExecDataHeader)); + reinterpret_cast(instructionOffsets) - sizeof(NativeProtoExecDataHeader) + ); } } // namespace CodeGen diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 34a2a3d6..e7207731 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -759,8 +759,7 @@ 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; @@ -1145,7 +1144,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& } case IrCmd::INVOKE_FASTCALL: handleBuiltinEffects( - state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f)); + state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f) + ); break; // These instructions don't have an effect on register/memory state we are tracking diff --git a/CodeGen/src/OptimizeDeadStore.cpp b/CodeGen/src/OptimizeDeadStore.cpp index 34f647a7..b4b4c7b5 100644 --- a/CodeGen/src/OptimizeDeadStore.cpp +++ b/CodeGen/src/OptimizeDeadStore.cpp @@ -303,8 +303,16 @@ struct RemoveDeadStoreState bool hasGcoToClear = false; }; -static bool tryReplaceTagWithFullStore(RemoveDeadStoreState& state, IrBuilder& build, IrFunction& function, IrBlock& block, uint32_t instIndex, - IrOp targetOp, IrOp tagOp, StoreRegInfo& regInfo) +static bool tryReplaceTagWithFullStore( + RemoveDeadStoreState& state, + IrBuilder& build, + IrFunction& function, + IrBlock& block, + uint32_t instIndex, + IrOp targetOp, + IrOp tagOp, + StoreRegInfo& regInfo +) { uint8_t tag = function.tagOp(tagOp); @@ -359,8 +367,16 @@ static bool tryReplaceTagWithFullStore(RemoveDeadStoreState& state, IrBuilder& b return false; } -static bool tryReplaceValueWithFullStore(RemoveDeadStoreState& state, IrBuilder& build, IrFunction& function, IrBlock& block, uint32_t instIndex, - IrOp targetOp, IrOp valueOp, StoreRegInfo& regInfo) +static bool tryReplaceValueWithFullStore( + RemoveDeadStoreState& state, + IrBuilder& build, + IrFunction& function, + IrBlock& block, + uint32_t instIndex, + IrOp targetOp, + IrOp valueOp, + StoreRegInfo& regInfo +) { // If the tag+value pair is established, we can mark both as dead and use a single split TValue store if (regInfo.tagInstIdx != ~0u && regInfo.valueInstIdx != ~0u) diff --git a/CodeGen/src/SharedCodeAllocator.cpp b/CodeGen/src/SharedCodeAllocator.cpp index c659a9b2..9907c8ac 100644 --- a/CodeGen/src/SharedCodeAllocator.cpp +++ b/CodeGen/src/SharedCodeAllocator.cpp @@ -40,8 +40,12 @@ struct NativeProtoBytecodeIdLess } }; -NativeModule::NativeModule(SharedCodeAllocator* allocator, const std::optional& moduleId, const uint8_t* moduleBaseAddress, - std::vector nativeProtos) noexcept +NativeModule::NativeModule( + SharedCodeAllocator* allocator, + const std::optional& moduleId, + const uint8_t* moduleBaseAddress, + std::vector nativeProtos +) noexcept : allocator{allocator} , moduleId{moduleId} , moduleBaseAddress{moduleBaseAddress} @@ -62,7 +66,8 @@ NativeModule::NativeModule(SharedCodeAllocator* allocator, const std::optionalnativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdEqual{}) == this->nativeProtos.end()); + std::adjacent_find(this->nativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdEqual{}) == this->nativeProtos.end() + ); } NativeModule::~NativeModule() noexcept @@ -216,8 +221,14 @@ SharedCodeAllocator::~SharedCodeAllocator() noexcept return tryGetNativeModuleWithLockHeld(moduleId); } -std::pair SharedCodeAllocator::getOrInsertNativeModule(const ModuleId& moduleId, - std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) +std::pair SharedCodeAllocator::getOrInsertNativeModule( + const ModuleId& moduleId, + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize +) { std::unique_lock lock{mutex}; @@ -239,7 +250,12 @@ std::pair SharedCodeAllocator::getOrInsertNativeModule(co } NativeModuleRef SharedCodeAllocator::insertAnonymousNativeModule( - std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) + std::vector nativeProtos, + const uint8_t* data, + size_t dataSize, + const uint8_t* code, + size_t codeSize +) { std::unique_lock lock{mutex}; diff --git a/CodeGen/src/UnwindBuilderDwarf2.cpp b/CodeGen/src/UnwindBuilderDwarf2.cpp index 2f090b52..2e1e3114 100644 --- a/CodeGen/src/UnwindBuilderDwarf2.cpp +++ b/CodeGen/src/UnwindBuilderDwarf2.cpp @@ -53,8 +53,24 @@ #define DW_REG_A64_SP 31 // X64 register mapping from real register index to DWARF2 (r8..r15 are mapped 1-1, but named registers aren't) -const int regIndexToDwRegX64[16] = {DW_REG_X64_RAX, DW_REG_X64_RCX, DW_REG_X64_RDX, DW_REG_X64_RBX, DW_REG_X64_RSP, DW_REG_X64_RBP, DW_REG_X64_RSI, - DW_REG_X64_RDI, 8, 9, 10, 11, 12, 13, 14, 15}; +const int regIndexToDwRegX64[16] = { + DW_REG_X64_RAX, + DW_REG_X64_RCX, + DW_REG_X64_RDX, + DW_REG_X64_RBX, + DW_REG_X64_RSP, + DW_REG_X64_RBP, + DW_REG_X64_RSI, + DW_REG_X64_RDI, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 +}; const int kCodeAlignFactor = 1; const int kDataAlignFactor = 8; @@ -225,8 +241,13 @@ void UnwindBuilderDwarf2::prologueA64(uint32_t prologueSize, uint32_t stackSize, } } -void UnwindBuilderDwarf2::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, - const std::vector& simd) +void UnwindBuilderDwarf2::prologueX64( + uint32_t prologueSize, + uint32_t stackSize, + bool setupFrame, + std::initializer_list gpr, + const std::vector& simd +) { CODEGEN_ASSERT(stackSize > 0 && stackSize < 4096 && stackSize % 8 == 0); diff --git a/CodeGen/src/UnwindBuilderWin.cpp b/CodeGen/src/UnwindBuilderWin.cpp index 2bcc0321..418b9087 100644 --- a/CodeGen/src/UnwindBuilderWin.cpp +++ b/CodeGen/src/UnwindBuilderWin.cpp @@ -109,8 +109,13 @@ void UnwindBuilderWin::prologueA64(uint32_t prologueSize, uint32_t stackSize, st CODEGEN_ASSERT(!"Not implemented"); } -void UnwindBuilderWin::prologueX64(uint32_t prologueSize, uint32_t stackSize, bool setupFrame, std::initializer_list gpr, - const std::vector& simd) +void UnwindBuilderWin::prologueX64( + uint32_t prologueSize, + uint32_t stackSize, + bool setupFrame, + std::initializer_list gpr, + const std::vector& simd +) { CODEGEN_ASSERT(stackSize > 0 && stackSize < 4096 && stackSize % 8 == 0); CODEGEN_ASSERT(prologueSize < 256); diff --git a/Common/include/Luau/Variant.h b/Common/include/Luau/Variant.h index eeaa2c01..88722257 100644 --- a/Common/include/Luau/Variant.h +++ b/Common/include/Luau/Variant.h @@ -239,8 +239,9 @@ auto visit(Visitor&& vis, const Variant& var) static_assert(std::conjunction_v...>, "visitor must accept every alternative as an argument"); using Result = std::invoke_result_t::first_alternative>; - static_assert(std::conjunction_v>...>, - "visitor result type must be consistent between alternatives"); + static_assert( + std::conjunction_v>...>, "visitor result type must be consistent between alternatives" + ); if constexpr (std::is_same_v) { @@ -266,8 +267,9 @@ auto visit(Visitor&& vis, Variant& var) static_assert(std::conjunction_v...>, "visitor must accept every alternative as an argument"); using Result = std::invoke_result_t::first_alternative&>; - static_assert(std::conjunction_v>...>, - "visitor result type must be consistent between alternatives"); + static_assert( + std::conjunction_v>...>, "visitor result type must be consistent between alternatives" + ); if constexpr (std::is_same_v) { diff --git a/Common/include/Luau/VecDeque.h b/Common/include/Luau/VecDeque.h index a1b555b0..0b3d88e5 100644 --- a/Common/include/Luau/VecDeque.h +++ b/Common/include/Luau/VecDeque.h @@ -130,8 +130,10 @@ public: , queue_size(other.queue_size) { // copy the initialized contents of the other buffer to this one - size_t head_size = std::min(other.queue_size, - other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer) + size_t head_size = std::min( + other.queue_size, + other.buffer_capacity - other.head + ); // how many elements are in the head portion (i.e. from the head to the end of the buffer) size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front) if (head_size != 0) @@ -149,8 +151,10 @@ public: , queue_size(other.queue_size) { // copy the initialized contents of the other buffer to this one - size_t head_size = std::min(other.queue_size, - other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer) + size_t head_size = std::min( + other.queue_size, + other.buffer_capacity - other.head + ); // how many elements are in the head portion (i.e. from the head to the end of the buffer) size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front) if (head_size != 0) @@ -212,8 +216,10 @@ public: buffer_capacity = other.buffer_capacity; } - size_t head_size = std::min(other.queue_size, - other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer) + size_t head_size = std::min( + other.queue_size, + other.buffer_capacity - other.head + ); // how many elements are in the head portion (i.e. from the head to the end of the buffer) size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front) // Assignment doesn't try to match the capacity of 'other' and thus makes the buffer contiguous diff --git a/Compiler/include/Luau/Compiler.h b/Compiler/include/Luau/Compiler.h index 119e0aa2..403fa6dd 100644 --- a/Compiler/include/Luau/Compiler.h +++ b/Compiler/include/Luau/Compiler.h @@ -75,6 +75,10 @@ void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const // compiles bytecode into a bytecode blob, that either contains the valid bytecode or an encoded error that luau_load can decode std::string compile( - const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {}, BytecodeEncoder* encoder = nullptr); + const std::string& source, + const CompileOptions& options = {}, + const ParseOptions& parseOptions = {}, + BytecodeEncoder* encoder = nullptr +); } // namespace Luau diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index c576e3a4..90bf72c4 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -246,8 +246,12 @@ struct BuiltinVisitor : AstVisitor const CompileOptions& options; - BuiltinVisitor(DenseHashMap& result, const DenseHashMap& globals, - const DenseHashMap& variables, const CompileOptions& options) + BuiltinVisitor( + DenseHashMap& result, + const DenseHashMap& globals, + const DenseHashMap& variables, + const CompileOptions& options + ) : result(result) , globals(globals) , variables(variables) @@ -274,8 +278,13 @@ struct BuiltinVisitor : AstVisitor } }; -void analyzeBuiltins(DenseHashMap& result, const DenseHashMap& globals, - const DenseHashMap& variables, const CompileOptions& options, AstNode* root) +void analyzeBuiltins( + DenseHashMap& result, + const DenseHashMap& globals, + const DenseHashMap& variables, + const CompileOptions& options, + AstNode* root +) { BuiltinVisitor visitor{result, globals, variables, options}; root->visit(&visitor); diff --git a/Compiler/src/Builtins.h b/Compiler/src/Builtins.h index 2d783248..e6427c2a 100644 --- a/Compiler/src/Builtins.h +++ b/Compiler/src/Builtins.h @@ -36,8 +36,13 @@ struct Builtin Builtin getBuiltin(AstExpr* node, const DenseHashMap& globals, const DenseHashMap& variables); -void analyzeBuiltins(DenseHashMap& result, const DenseHashMap& globals, - const DenseHashMap& variables, const CompileOptions& options, AstNode* root); +void analyzeBuiltins( + DenseHashMap& result, + const DenseHashMap& globals, + const DenseHashMap& variables, + const CompileOptions& options, + AstNode* root +); struct BuiltinInfo { diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index e5e65095..2e9bd88c 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -383,7 +383,8 @@ int32_t BytecodeBuilder::addConstantVector(float x, float y, float z, float w) ConstantKey k = {Constant::Type_Vector}; static_assert( - sizeof(k.value) == sizeof(x) + sizeof(y) && sizeof(k.extra) == sizeof(z) + sizeof(w), "Expecting vector to have four 32-bit components"); + sizeof(k.value) == sizeof(x) + sizeof(y) && sizeof(k.extra) == sizeof(z) + sizeof(w), "Expecting vector to have four 32-bit components" + ); memcpy(&k.value, &x, sizeof(x)); memcpy((char*)&k.value + sizeof(x), &y, sizeof(y)); memcpy(&k.extra, &z, sizeof(z)); @@ -1107,9 +1108,14 @@ void BytecodeBuilder::expandJumps() const int kMaxJumpDistanceConservative = 32767 / 3; // we will need to process jumps in order - std::sort(jumps.begin(), jumps.end(), [](const Jump& lhs, const Jump& rhs) { - return lhs.source < rhs.source; - }); + std::sort( + jumps.begin(), + jumps.end(), + [](const Jump& lhs, const Jump& rhs) + { + return lhs.source < rhs.source; + } + ); // first, let's add jump thunks for every jump with a distance that's too big // we will create new instruction buffers, with remap table keeping track of the moves: remap[oldpc] = newpc @@ -1767,8 +1773,7 @@ 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 @@ -2246,12 +2251,16 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, break; case LOP_CAPTURE: - formatAppend(result, "CAPTURE %s %c%d\n", + formatAppend( + result, + "CAPTURE %s %c%d\n", LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL" : LUAU_INSN_A(insn) == LCT_REF ? "REF" : LUAU_INSN_A(insn) == LCT_VAL ? "VAL" : "", - LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn)); + LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', + LUAU_INSN_B(insn) + ); break; case LOP_JUMPXEQKNIL: @@ -2346,8 +2355,16 @@ std::string BytecodeBuilder::dumpCurrentFunction(std::vector& dumpinstoffs) LUAU_ASSERT(l.endpc <= lines.size()); // endpc is exclusive in the debug info, but it's more intuitive to print inclusive data // it would be nice to emit name as well but it requires reverse lookup through stringtable - formatAppend(result, "local %d: reg %d, start pc %d line %d, end pc %d line %d\n", int(i), l.reg, l.startpc, lines[l.startpc], - l.endpc - 1, lines[l.endpc - 1]); + formatAppend( + result, + "local %d: reg %d, start pc %d line %d, end pc %d line %d\n", + int(i), + l.reg, + l.startpc, + lines[l.startpc], + l.endpc - 1, + lines[l.endpc - 1] + ); } } } diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 98520a7f..93c31252 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -133,7 +133,8 @@ struct Compiler if (upvals.size() >= kMaxUpvalueCount) CompileError::raise( - local->location, "Out of upvalue registers when trying to allocate %s: exceeded limit %d", local->name.value, kMaxUpvalueCount); + local->location, "Out of upvalue registers when trying to allocate %s: exceeded limit %d", local->name.value, kMaxUpvalueCount + ); // mark local as captured so that closeLocals emits LOP_CLOSEUPVALS accordingly Variable* v = variables.find(local); @@ -456,7 +457,15 @@ struct Compiler } void compileExprFastcallN( - AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1) + AstExprCall* expr, + uint8_t target, + uint8_t targetCount, + bool targetTop, + bool multRet, + uint8_t regs, + int bfid, + int bfK = -1 + ) { LUAU_ASSERT(!expr->self); LUAU_ASSERT(expr->args.size >= 1); @@ -483,8 +492,9 @@ struct Compiler } else { - opc = expr->args.size == 1 ? LOP_FASTCALL1 - : (bfK >= 0 || (expr->args.size == 2 && isConstant(expr->args.data[1]))) ? LOP_FASTCALL2K : LOP_FASTCALL2; + opc = expr->args.size == 1 ? LOP_FASTCALL1 + : (bfK >= 0 || (expr->args.size == 2 && isConstant(expr->args.data[1]))) ? LOP_FASTCALL2K + : LOP_FASTCALL2; } uint32_t args[3] = {}; @@ -556,8 +566,16 @@ struct Compiler } } - bool tryCompileInlinedCall(AstExprCall* expr, AstExprFunction* func, uint8_t target, uint8_t targetCount, bool multRet, int thresholdBase, - int thresholdMaxBoost, int depthLimit) + bool tryCompileInlinedCall( + AstExprCall* expr, + AstExprFunction* func, + uint8_t target, + uint8_t targetCount, + bool multRet, + int thresholdBase, + int thresholdMaxBoost, + int depthLimit + ) { Function* fi = functions.find(func); LUAU_ASSERT(fi); @@ -618,7 +636,8 @@ struct Compiler } bytecode.addDebugRemark( - "inlining succeeded (cost %d, profit %.2fx, depth %d)", inlinedCost, double(inlineProfit) / 100, int(inlineFrames.size())); + "inlining succeeded (cost %d, profit %.2fx, depth %d)", inlinedCost, double(inlineProfit) / 100, int(inlineFrames.size()) + ); compileInlinedCall(expr, func, target, targetCount); return true; @@ -784,8 +803,16 @@ struct Compiler Function* fi = func ? functions.find(func) : nullptr; if (fi && fi->canInline && - tryCompileInlinedCall(expr, func, target, targetCount, multRet, FInt::LuauCompileInlineThreshold, - FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth)) + tryCompileInlinedCall( + expr, + func, + target, + targetCount, + multRet, + FInt::LuauCompileInlineThreshold, + FInt::LuauCompileInlineThresholdMaxBoost, + FInt::LuauCompileInlineDepth + )) return; // add a debug remark for cases when we didn't even call tryCompileInlinedCall @@ -2998,7 +3025,8 @@ struct Compiler if (unrolledCost > threshold) { bytecode.addDebugRemark( - "loop unroll failed: too expensive (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100); + "loop unroll failed: too expensive (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100 + ); return false; } @@ -3649,9 +3677,12 @@ struct Compiler condition->visit(&visitor); if (visitor.undef) - CompileError::raise(condition->location, + CompileError::raise( + condition->location, "Local %s used in the repeat..until condition is undefined because continue statement on line %d jumps over it", - visitor.undef->name.value, cont->location.begin.line + 1); + visitor.undef->name.value, + cont->location.begin.line + 1 + ); } void gatherConstUpvals(AstExprFunction* func) @@ -3667,7 +3698,8 @@ struct Compiler { if (localStack.size() >= kMaxLocalCount) CompileError::raise( - local->location, "Out of local registers when trying to allocate %s: exceeded limit %d", local->name.value, kMaxLocalCount); + local->location, "Out of local registers when trying to allocate %s: exceeded limit %d", local->name.value, kMaxLocalCount + ); localStack.push_back(local); @@ -4101,7 +4133,7 @@ struct Compiler DenseHashMap localTypes; DenseHashMap exprTypes; - BuiltinTypes builtinTypes; + BuiltinAstTypes builtinTypes; const DenseHashMap* builtinsFold = nullptr; bool builtinsFoldMathK = false; @@ -4217,8 +4249,18 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c // computes type information for all functions based on type annotations if (options.typeInfoLevel >= 1) - buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.userdataTypes, - compiler.builtinTypes, compiler.builtins, compiler.globals, bytecode); + buildTypeMap( + compiler.functionTypes, + compiler.localTypes, + compiler.exprTypes, + root, + options.vectorType, + compiler.userdataTypes, + compiler.builtinTypes, + compiler.builtins, + compiler.globals, + bytecode + ); for (AstExprFunction* expr : functions) { @@ -4231,10 +4273,19 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c mainFlags |= LPF_NATIVE_FUNCTION; } - AstExprFunction main(root->location, /*attributes=*/AstArray({nullptr, 0}), /*generics= */ AstArray(), + AstExprFunction main( + root->location, + /*attributes=*/AstArray({nullptr, 0}), + /*generics= */ AstArray(), /*genericPacks= */ AstArray(), - /* self= */ nullptr, AstArray(), /* vararg= */ true, /* varargLocation= */ Luau::Location(), root, /* functionDepth= */ 0, - /* debugname= */ AstName()); + /* self= */ nullptr, + AstArray(), + /* vararg= */ true, + /* varargLocation= */ Luau::Location(), + root, + /* functionDepth= */ 0, + /* debugname= */ AstName() + ); uint32_t mainid = compiler.compileFunction(&main, mainFlags); const Compiler::Function* mainf = compiler.functions.find(&main); diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index 3b3f9dd2..2895bf08 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -215,8 +215,13 @@ struct ConstantVisitor : AstVisitor std::vector builtinArgs; - ConstantVisitor(DenseHashMap& constants, DenseHashMap& variables, - DenseHashMap& locals, const DenseHashMap* builtins, bool foldMathK) + ConstantVisitor( + DenseHashMap& constants, + DenseHashMap& variables, + DenseHashMap& locals, + const DenseHashMap* builtins, + bool foldMathK + ) : constants(constants) , variables(variables) , locals(locals) @@ -458,8 +463,14 @@ struct ConstantVisitor : AstVisitor } }; -void foldConstants(DenseHashMap& constants, DenseHashMap& variables, - DenseHashMap& locals, const DenseHashMap* builtins, bool foldMathK, AstNode* root) +void foldConstants( + DenseHashMap& constants, + DenseHashMap& variables, + DenseHashMap& locals, + const DenseHashMap* builtins, + bool foldMathK, + AstNode* root +) { ConstantVisitor visitor{constants, variables, locals, builtins, foldMathK}; root->visit(&visitor); diff --git a/Compiler/src/ConstantFolding.h b/Compiler/src/ConstantFolding.h index b22b0bf0..e4eb6428 100644 --- a/Compiler/src/ConstantFolding.h +++ b/Compiler/src/ConstantFolding.h @@ -44,8 +44,14 @@ struct Constant } }; -void foldConstants(DenseHashMap& constants, DenseHashMap& variables, - DenseHashMap& locals, const DenseHashMap* builtins, bool foldMathK, AstNode* root); +void foldConstants( + DenseHashMap& constants, + DenseHashMap& variables, + DenseHashMap& locals, + const DenseHashMap* builtins, + bool foldMathK, + AstNode* root +); } // namespace Compile } // namespace Luau diff --git a/Compiler/src/CostModel.cpp b/Compiler/src/CostModel.cpp index 04adf3e3..4c8e13c6 100644 --- a/Compiler/src/CostModel.cpp +++ b/Compiler/src/CostModel.cpp @@ -130,8 +130,7 @@ 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 447b51d3..0e7a3242 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -37,9 +37,15 @@ static LuauBytecodeType getPrimitiveType(AstName name) return LBC_TYPE_INVALID; } -static LuauBytecodeType getType(const AstType* ty, const AstArray& generics, - const DenseHashMap& typeAliases, bool resolveAliases, const char* vectorType, - const DenseHashMap& userdataTypes, BytecodeBuilder& bytecode) +static LuauBytecodeType getType( + const AstType* ty, + const AstArray& generics, + const DenseHashMap& typeAliases, + bool resolveAliases, + const char* vectorType, + const DenseHashMap& userdataTypes, + BytecodeBuilder& bytecode +) { if (const AstTypeReference* ref = ty->as()) { @@ -122,8 +128,13 @@ static LuauBytecodeType getType(const AstType* ty, const AstArray& typeAliases, const char* vectorType, - const DenseHashMap& userdataTypes, BytecodeBuilder& bytecode) +static std::string getFunctionType( + const AstExprFunction* func, + const DenseHashMap& typeAliases, + const char* vectorType, + const DenseHashMap& userdataTypes, + BytecodeBuilder& bytecode +) { bool self = func->self != 0; @@ -171,7 +182,7 @@ struct TypeMapVisitor : AstVisitor DenseHashMap& exprTypes; const char* vectorType; const DenseHashMap& userdataTypes; - const BuiltinTypes& builtinTypes; + const BuiltinAstTypes& builtinTypes; const DenseHashMap& builtinCalls; const DenseHashMap& globals; BytecodeBuilder& bytecode; @@ -181,10 +192,17 @@ struct TypeMapVisitor : AstVisitor DenseHashMap resolvedLocals; DenseHashMap resolvedExprs; - TypeMapVisitor(DenseHashMap& functionTypes, DenseHashMap& localTypes, - DenseHashMap& exprTypes, const char* vectorType, const DenseHashMap& userdataTypes, - const BuiltinTypes& builtinTypes, const DenseHashMap& builtinCalls, const DenseHashMap& globals, - BytecodeBuilder& bytecode) + TypeMapVisitor( + DenseHashMap& functionTypes, + DenseHashMap& localTypes, + DenseHashMap& exprTypes, + const char* vectorType, + const DenseHashMap& userdataTypes, + const BuiltinAstTypes& builtinTypes, + const DenseHashMap& builtinCalls, + const DenseHashMap& globals, + BytecodeBuilder& bytecode + ) : functionTypes(functionTypes) , localTypes(localTypes) , exprTypes(exprTypes) @@ -694,10 +712,18 @@ struct TypeMapVisitor : AstVisitor // * AstExprCall is very complex (especially if builtins and registered globals are included), will be extended in the future }; -void buildTypeMap(DenseHashMap& functionTypes, DenseHashMap& localTypes, - DenseHashMap& exprTypes, AstNode* root, const char* vectorType, const DenseHashMap& userdataTypes, - const BuiltinTypes& builtinTypes, const DenseHashMap& builtinCalls, const DenseHashMap& globals, - BytecodeBuilder& bytecode) +void buildTypeMap( + DenseHashMap& functionTypes, + DenseHashMap& localTypes, + DenseHashMap& exprTypes, + AstNode* root, + const char* vectorType, + const DenseHashMap& userdataTypes, + const BuiltinAstTypes& builtinTypes, + const DenseHashMap& builtinCalls, + const DenseHashMap& globals, + BytecodeBuilder& bytecode +) { TypeMapVisitor visitor(functionTypes, localTypes, exprTypes, vectorType, userdataTypes, builtinTypes, builtinCalls, globals, bytecode); root->visit(&visitor); diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h index bd12ea77..a310bfcc 100644 --- a/Compiler/src/Types.h +++ b/Compiler/src/Types.h @@ -12,9 +12,9 @@ namespace Luau { class BytecodeBuilder; -struct BuiltinTypes +struct BuiltinAstTypes { - BuiltinTypes(const char* vectorType) + BuiltinAstTypes(const char* vectorType) : vectorType{{}, std::nullopt, AstName{vectorType}, std::nullopt, {}} { } @@ -26,9 +26,17 @@ struct BuiltinTypes AstTypeReference vectorType; }; -void buildTypeMap(DenseHashMap& functionTypes, DenseHashMap& localTypes, - DenseHashMap& exprTypes, AstNode* root, const char* vectorType, const DenseHashMap& userdataTypes, - const BuiltinTypes& builtinTypes, const DenseHashMap& builtinCalls, const DenseHashMap& globals, - BytecodeBuilder& bytecode); +void buildTypeMap( + DenseHashMap& functionTypes, + DenseHashMap& localTypes, + DenseHashMap& exprTypes, + AstNode* root, + const char* vectorType, + const DenseHashMap& userdataTypes, + const BuiltinAstTypes& builtinTypes, + const DenseHashMap& builtinCalls, + const DenseHashMap& globals, + BytecodeBuilder& bytecode +); } // namespace Luau diff --git a/Config/include/Luau/Config.h b/Config/include/Luau/Config.h index 2df02163..6333c55a 100644 --- a/Config/include/Luau/Config.h +++ b/Config/include/Luau/Config.h @@ -52,7 +52,12 @@ struct NullConfigResolver : ConfigResolver std::optional parseModeString(Mode& mode, const std::string& modeString, bool compat = false); std::optional parseLintRuleString( - LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat = false); + LintOptions& enabledLints, + LintOptions& fatalLints, + const std::string& warningName, + const std::string& value, + bool compat = false +); bool isValidAlias(const std::string& alias); diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 5fba9fa3..7d010265 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -45,7 +45,12 @@ Error parseModeString(Mode& mode, const std::string& modeString, bool compat) } static Error parseLintRuleStringForCode( - LintOptions& enabledLints, LintOptions& fatalLints, LintWarning::Code code, const std::string& value, bool compat) + LintOptions& enabledLints, + LintOptions& fatalLints, + LintWarning::Code code, + const std::string& value, + bool compat +) { if (value == "true") { @@ -136,9 +141,15 @@ Error parseAlias(std::unordered_map& aliases, std::str if (!isValidAlias(aliasKey)) return Error{"Invalid alias " + aliasKey}; - std::transform(aliasKey.begin(), aliasKey.end(), aliasKey.begin(), [](unsigned char c) { - return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c; - }); + std::transform( + aliasKey.begin(), + aliasKey.end(), + aliasKey.begin(), + [](unsigned char c) + { + return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c; + } + ); if (!aliases.count(aliasKey)) aliases[std::move(aliasKey)] = aliasValue; @@ -246,8 +257,7 @@ 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()) @@ -277,35 +287,39 @@ static Error parseJson(const std::string& contents, Action action) Error parseConfig(const std::string& contents, Config& config, bool compat) { - return parseJson(contents, [&](const std::vector& keys, const std::string& value) -> Error { - if (keys.size() == 1 && keys[0] == "languageMode") - return parseModeString(config.mode, value, compat); - else if (keys.size() == 2 && keys[0] == "lint") - return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat); - else if (keys.size() == 1 && keys[0] == "lintErrors") - return parseBoolean(config.lintErrors, value); - else if (keys.size() == 1 && keys[0] == "typeErrors") - return parseBoolean(config.typeErrors, value); - else if (keys.size() == 1 && keys[0] == "globals") + return parseJson( + contents, + [&](const std::vector& keys, const std::string& value) -> Error { - config.globals.push_back(value); - return std::nullopt; + if (keys.size() == 1 && keys[0] == "languageMode") + return parseModeString(config.mode, value, compat); + else if (keys.size() == 2 && keys[0] == "lint") + return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat); + else if (keys.size() == 1 && keys[0] == "lintErrors") + return parseBoolean(config.lintErrors, value); + else if (keys.size() == 1 && keys[0] == "typeErrors") + return parseBoolean(config.typeErrors, value); + else if (keys.size() == 1 && keys[0] == "globals") + { + config.globals.push_back(value); + return std::nullopt; + } + else if (keys.size() == 1 && keys[0] == "paths") + { + config.paths.push_back(value); + return std::nullopt; + } + else if (keys.size() == 2 && keys[0] == "aliases") + return parseAlias(config.aliases, keys[1], value); + else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode") + return parseModeString(config.mode, value, compat); + else + { + std::vector keysv(keys.begin(), keys.end()); + return "Unknown key " + join(keysv, "/"); + } } - else if (keys.size() == 1 && keys[0] == "paths") - { - config.paths.push_back(value); - return std::nullopt; - } - else if (keys.size() == 2 && keys[0] == "aliases") - return parseAlias(config.aliases, keys[1], value); - else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode") - return parseModeString(config.mode, value, compat); - else - { - std::vector keysv(keys.begin(), keys.end()); - return "Unknown key " + join(keysv, "/"); - } - }); + ); } const Config& NullConfigResolver::getConfig(const ModuleName& name) const diff --git a/EqSat/include/Luau/EGraph.h b/EqSat/include/Luau/EGraph.h index abccd70c..8344b0e5 100644 --- a/EqSat/include/Luau/EGraph.h +++ b/EqSat/include/Luau/EGraph.h @@ -170,12 +170,15 @@ private: Id id = unionfind.makeSet(); - classes.insert_or_assign(id, EClass{ + classes.insert_or_assign( id, - {enode}, - analysis.make(*this, enode), - {}, - }); + EClass{ + id, + {enode}, + analysis.make(*this, enode), + {}, + } + ); for (Id operand : enode.operands()) get(operand).parents.push_back({enode, id}); diff --git a/EqSat/include/Luau/Language.h b/EqSat/include/Luau/Language.h index c17ac577..4aeb6c32 100644 --- a/EqSat/include/Luau/Language.h +++ b/EqSat/include/Luau/Language.h @@ -248,16 +248,24 @@ struct Language final /// Reading is ok, but you should also never assume that these `Id`s are stable. Slice operands() noexcept { - return visit([](auto&& v) -> Slice { - return v.operands(); - }, v); + return visit( + [](auto&& v) -> Slice + { + return v.operands(); + }, + v + ); } Slice operands() const noexcept { - return visit([](auto&& v) -> Slice { - return v.operands(); - }, v); + return visit( + [](auto&& v) -> Slice + { + return v.operands(); + }, + v + ); } template @@ -290,9 +298,16 @@ public: size_t operator()(const Language& language) const { size_t seed = std::hash{}(language.index()); - hashCombine(seed, visit([](auto&& v) { - return typename std::decay_t::Hash{}(v); - }, language.v)); + hashCombine( + seed, + visit( + [](auto&& v) + { + return typename std::decay_t::Hash{}(v); + }, + language.v + ) + ); return seed; } }; diff --git a/EqSat/include/Luau/LanguageHash.h b/EqSat/include/Luau/LanguageHash.h index 8c5f837c..7226132c 100644 --- a/EqSat/include/Luau/LanguageHash.h +++ b/EqSat/include/Luau/LanguageHash.h @@ -16,7 +16,7 @@ struct LanguageHash } }; -template +template size_t languageHash(const T& lang) { return LanguageHash{}(lang); diff --git a/VM/src/lbitlib.cpp b/VM/src/lbitlib.cpp index fbbc95ba..1d07c7a4 100644 --- a/VM/src/lbitlib.cpp +++ b/VM/src/lbitlib.cpp @@ -9,7 +9,7 @@ #define NBITS int(8 * sizeof(unsigned)) // macro to trim extra bits -#define trim(x) ((x)&ALLONES) +#define trim(x) ((x) & ALLONES) // builds a number with 'n' ones (1 <= n <= NBITS) #define mask(n) (~((ALLONES << 1) << ((n)-1))) diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 010d7e86..722de9d1 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -135,8 +135,11 @@ LUAI_FUNC void luaC_barriertable(lua_State* L, Table* 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)); -LUAI_FUNC void luaC_enumheap(lua_State* L, void* context, +LUAI_FUNC void luaC_enumheap( + lua_State* L, + void* context, void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name), - void (*edge)(void* context, void* from, void* to, const char* name)); + void (*edge)(void* context, void* from, void* to, const char* name) +); LUAI_FUNC int64_t luaC_allocationrate(lua_State* L); LUAI_FUNC const char* luaC_statename(int state); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 2610ddf8..768561cb 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -344,8 +344,9 @@ static void dumptable(FILE* f, Table* h) static void dumpclosure(FILE* f, Closure* cl) { - fprintf(f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat, - cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues))); + fprintf( + f, "{\"type\":\"function\",\"cat\":%d,\"size\":%d", cl->memcat, cl->isC ? int(sizeCclosure(cl->nupvalues)) : int(sizeLclosure(cl->nupvalues)) + ); fprintf(f, ",\"env\":"); dumpref(f, obj2gco(cl->env)); @@ -888,8 +889,12 @@ static bool enumgco(void* context, lua_Page* page, GCObject* gco) return false; } -void luaC_enumheap(lua_State* L, void* context, void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name), - void (*edge)(void* context, void* from, void* to, const char* name)) +void luaC_enumheap( + lua_State* L, + void* context, + void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name), + void (*edge)(void* context, void* from, void* to, const char* name) +) { global_State* g = L->global; diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 5a817f25..7adaf0b4 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -275,18 +275,36 @@ static int math_randomseed(lua_State* L) return 0; } -static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, - 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, - 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, - 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, - 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, - 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, - 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, - 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, - 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151}; +static const unsigned char kPerlinHash[257] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, + 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, + 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, + 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, + 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, + 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, + 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, + 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151 +}; -const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, - {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; +const float kPerlinGrad[16][3] = { + {1, 1, 0}, + {-1, 1, 0}, + {1, -1, 0}, + {-1, -1, 0}, + {1, 0, 1}, + {-1, 0, 1}, + {1, 0, -1}, + {-1, 0, -1}, + {0, 1, 1}, + {0, -1, 1}, + {0, 1, -1}, + {0, -1, -1}, + {1, 1, 0}, + {0, -1, 1}, + {-1, 1, 0}, + {0, -1, -1} +}; inline float perlin_fade(float t) { diff --git a/VM/src/lobject.cpp b/VM/src/lobject.cpp index 640bd96e..f685b235 100644 --- a/VM/src/lobject.cpp +++ b/VM/src/lobject.cpp @@ -18,11 +18,12 @@ const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL}; int luaO_log2(unsigned int x) { static const uint8_t log_2[256] = {0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; int l = -1; while (x >= 256) { diff --git a/VM/src/lutf8lib.cpp b/VM/src/lutf8lib.cpp index e18c0b96..cbb0b5b0 100644 --- a/VM/src/lutf8lib.cpp +++ b/VM/src/lutf8lib.cpp @@ -6,7 +6,7 @@ #define MAXUNICODE 0x10FFFF -#define iscont(p) ((*(p)&0xC0) == 0x80) +#define iscont(p) ((*(p) & 0xC0) == 0x80) // from strlib // translate a relative string position: negative means back from end diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index fb253c6a..0b26f079 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -1678,8 +1678,13 @@ reentry: { const float* vb = vvalue(rb); float vc = cast_to(float, nvalue(rc)); - setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), - float(luai_numidiv(vb[3], vc))); + setvvalue( + ra, + float(luai_numidiv(vb[0], vc)), + float(luai_numidiv(vb[1], vc)), + float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc)) + ); VM_NEXT(); } else @@ -1904,8 +1909,13 @@ reentry: { const float* vb = vvalue(rb); float vc = cast_to(float, nvalue(kv)); - setvvalue(ra, float(luai_numidiv(vb[0], vc)), float(luai_numidiv(vb[1], vc)), float(luai_numidiv(vb[2], vc)), - float(luai_numidiv(vb[3], vc))); + setvvalue( + ra, + float(luai_numidiv(vb[0], vc)), + float(luai_numidiv(vb[1], vc)), + float(luai_numidiv(vb[2], vc)), + float(luai_numidiv(vb[3], vc)) + ); VM_NEXT(); } else diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 480c6f4e..aa248fc1 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -287,8 +287,9 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size { char chunkbuf[LUA_IDSIZE]; const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname)); - lua_pushfstring(L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN, LBC_TYPE_VERSION_MAX, - typesversion); + lua_pushfstring( + L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN, LBC_TYPE_VERSION_MAX, typesversion + ); return 1; } } diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 41990742..0cf9d206 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -404,8 +404,13 @@ void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); return; case TM_IDIV: - setvvalue(ra, float(luai_numidiv(vb[0], vc[0])), float(luai_numidiv(vb[1], vc[1])), float(luai_numidiv(vb[2], vc[2])), - float(luai_numidiv(vb[3], vc[3]))); + setvvalue( + ra, + float(luai_numidiv(vb[0], vc[0])), + float(luai_numidiv(vb[1], vc[1])), + float(luai_numidiv(vb[2], vc[2])), + float(luai_numidiv(vb[3], vc[3])) + ); return; case TM_UNM: setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); @@ -431,8 +436,9 @@ void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc); return; case TM_IDIV: - setvvalue(ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)), - float(luai_numidiv(vb[3], nc))); + setvvalue( + ra, float(luai_numidiv(vb[0], nc)), float(luai_numidiv(vb[1], nc)), float(luai_numidiv(vb[2], nc)), float(luai_numidiv(vb[3], nc)) + ); return; default: break; @@ -456,8 +462,9 @@ void luaV_doarithimpl(lua_State* L, StkId ra, const TValue* rb, const TValue* rc setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]); return; case TM_IDIV: - setvvalue(ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])), - float(luai_numidiv(nb, vc[3]))); + setvvalue( + ra, float(luai_numidiv(nb, vc[0])), float(luai_numidiv(nb, vc[1])), float(luai_numidiv(nb, vc[2])), float(luai_numidiv(nb, vc[3])) + ); return; default: break; diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index b9af5247..9114f824 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -167,7 +167,8 @@ static void setupFrontend(Luau::Frontend& frontend) registerTypes(frontend, frontend.globalsForAutocomplete, true); Luau::freeze(frontend.globalsForAutocomplete.globalTypes); - frontend.iceHandler.onInternalError = [](const char* error) { + frontend.iceHandler.onInternalError = [](const char* error) + { printf("ICE: %s\n", error); LUAU_ASSERT(!"ICE"); }; @@ -394,7 +395,8 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) { static lua_State* globalState = createGlobalState(); - auto runCode = [](const std::string& bytecode, bool useCodegen) { + auto runCode = [](const std::string& bytecode, bool useCodegen) + { lua_State* L = lua_newthread(globalState); luaL_sandboxthread(L); diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index 75bcf398..0adc0968 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -2,188 +2,27 @@ #include "luau.pb.h" static const std::string kNames[] = { - "_G", - "_VERSION", - "__add", - "__call", - "__concat", - "__div", - "__eq", - "__idiv", - "__index", - "__iter", - "__le", - "__len", - "__lt", - "__mod", - "__mode", - "__mul", - "__namecall", - "__newindex", - "__pow", - "__sub", - "__type", - "__unm", - "abs", - "acos", - "arshift", - "asin", - "assert", - "atan", - "atan2", - "band", - "bit32", - "bnot", - "boolean", - "bor", - "btest", - "buffer", - "bxor", - "byte", - "ceil", - "char", - "charpattern", - "clamp", - "clear", - "clock", - "clone", - "close", - "codepoint", - "codes", - "collectgarbage", - "concat", - "copy", - "coroutine", - "cos", - "cosh", - "countlz", - "countrz", - "create", - "date", - "debug", - "deg", - "difftime", - "error", - "exp", - "extract", - "fill", - "find", - "floor", - "fmod", - "foreach", - "foreachi", - "format", - "freeze", - "frexp", - "fromstring", - "function", - "gcinfo", - "getfenv", - "getmetatable", - "getn", - "gmatch", - "gsub", - "huge", - "info", - "insert", - "ipairs", - "isfrozen", - "isyieldable", - "ldexp", - "len", - "loadstring", - "log", - "log10", - "lower", - "lrotate", - "lshift", - "match", - "math", - "max", - "maxn", - "min", - "modf", - "move", - "newproxy", - "next", - "nil", - "noise", - "number", - "offset", - "os", - "pack", - "packsize", - "pairs", - "pcall", - "pi", - "pow", - "print", - "rad", - "random", - "randomseed", - "rawequal", - "rawget", - "rawlen", - "rawset", - "readf32", - "readf64", - "readi16", - "readi32", - "readi8", - "readstring", - "readu16", - "readu32", - "readu8", - "remove", - "rep", - "replace", - "require", - "resume", - "reverse", - "round", - "rrotate", - "rshift", - "running", - "select", - "setfenv", - "setmetatable", - "sign", - "sin", - "sinh", - "sort", - "split", - "sqrt", - "status", - "string", - "sub", - "table", - "tan", - "tanh", - "thread", - "time", - "tonumber", - "tostring", - "tostring", - "traceback", - "type", - "typeof", - "unpack", - "upper", - "userdata", - "utf8", - "vector", - "wrap", - "writef32", - "writef64", - "writei16", - "writei32", - "writei8", - "writestring", - "writeu16", - "writeu32", - "writeu8", - "xpcall", - "yield", + "_G", "_VERSION", "__add", "__call", "__concat", "__div", "__eq", "__idiv", "__index", + "__iter", "__le", "__len", "__lt", "__mod", "__mode", "__mul", "__namecall", "__newindex", + "__pow", "__sub", "__type", "__unm", "abs", "acos", "arshift", "asin", "assert", + "atan", "atan2", "band", "bit32", "bnot", "boolean", "bor", "btest", "buffer", + "bxor", "byte", "ceil", "char", "charpattern", "clamp", "clear", "clock", "clone", + "close", "codepoint", "codes", "collectgarbage", "concat", "copy", "coroutine", "cos", "cosh", + "countlz", "countrz", "create", "date", "debug", "deg", "difftime", "error", "exp", + "extract", "fill", "find", "floor", "fmod", "foreach", "foreachi", "format", "freeze", + "frexp", "fromstring", "function", "gcinfo", "getfenv", "getmetatable", "getn", "gmatch", "gsub", + "huge", "info", "insert", "ipairs", "isfrozen", "isyieldable", "ldexp", "len", "loadstring", + "log", "log10", "lower", "lrotate", "lshift", "match", "math", "max", "maxn", + "min", "modf", "move", "newproxy", "next", "nil", "noise", "number", "offset", + "os", "pack", "packsize", "pairs", "pcall", "pi", "pow", "print", "rad", + "random", "randomseed", "rawequal", "rawget", "rawlen", "rawset", "readf32", "readf64", "readi16", + "readi32", "readi8", "readstring", "readu16", "readu32", "readu8", "remove", "rep", "replace", + "require", "resume", "reverse", "round", "rrotate", "rshift", "running", "select", "setfenv", + "setmetatable", "sign", "sin", "sinh", "sort", "split", "sqrt", "status", "string", + "sub", "table", "tan", "tanh", "thread", "time", "tonumber", "tostring", "tostring", + "traceback", "type", "typeof", "unpack", "upper", "userdata", "utf8", "vector", "wrap", + "writef32", "writef64", "writei16", "writei32", "writei8", "writestring", "writeu16", "writeu32", "writeu8", + "xpcall", "yield", }; static const std::string kTypes[] = { diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 705e7e46..b737aa71 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -11,13 +11,15 @@ using namespace Luau; +using Pattern = AnyTypeSummary::Pattern; + LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauFreezeArena); LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(StudioReportLuauAny); -struct ATSFixture: BuiltinsFixture +struct ATSFixture : BuiltinsFixture { ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; @@ -44,7 +46,8 @@ type A = (number, string) -> ...any if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)"); } } @@ -61,6 +64,9 @@ export type t8 = t0 &((true | any)->('')) if (FFlag::StudioReportLuauAny) { + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8 = t0 &((true | any)->(''))"); } } @@ -78,7 +84,8 @@ type Pair = {first: T, second: any} if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = {first: T, second: any}"); } } @@ -119,7 +126,30 @@ type Pair = (boolean, T) -> ...any if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = (boolean, T)->( ...any)"); + } +} + +TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func") +{ + fileResolver.source["game/Gui/Modules/A"] = R"( + local function f() + local a: any = 1 + local b: typeof(a) = 1 + end +)"; + + CheckResult result1 = frontend.check("game/Gui/Modules/A"); + LUAU_REQUIRE_NO_ERRORS(result1); + + ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); + + if (FFlag::StudioReportLuauAny) + { + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end"); } } @@ -142,11 +172,12 @@ foo(addNumbers) LUAU_REQUIRE_NO_ERRORS(result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - + if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT((int)module->ats.typeInfo[1].code == 3); // FuncApp + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo(a: (...A)->( any),...: A)\n return a(...)\nend"); } } @@ -191,14 +222,15 @@ end if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = nil\n}\nelse\n local expected = x * 5\nend\nend"); } } TEST_CASE_FIXTURE(ATSFixture, "variadic_any") { fileResolver.source["game/Gui/Modules/A"] = R"( - local function f(): (number, ...any) -- double count "any" ret if varags + local function f(): (number, ...any) return 1, 5 end @@ -212,8 +244,9 @@ TEST_CASE_FIXTURE(ATSFixture, "variadic_any") if (FFlag::StudioReportLuauAny) { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); // do we need this to be the var arg pattern? - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Func Ret + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end"); } } @@ -234,7 +267,8 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT((int)module->ats.typeInfo[2].code == 4); // Variable Annotation + LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}"); } } @@ -259,7 +293,8 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_arg") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 4); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); } } @@ -280,7 +315,8 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_apps") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); } } @@ -323,10 +359,12 @@ end LUAU_REQUIRE_ERROR_COUNT(2, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - + if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); } } @@ -363,7 +401,9 @@ initialize() if (FFlag::StudioReportLuauAny) { - LUAU_ASSERT(module->ats.typeInfo.size() == 5); //unknown Model -- why are we inferring so many anys here --prbably something with the data model. worth looking into + LUAU_ASSERT(module->ats.typeInfo.size() == 5); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); } } @@ -437,7 +477,8 @@ initialize() if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 11); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end\nend"); } } @@ -499,7 +540,8 @@ initialize() if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 7); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, `{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, `{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend"); } } @@ -517,6 +559,13 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic") CheckResult result1 = frontend.check("game/Gui/Modules/A"); LUAU_REQUIRE_ERROR_COUNT(2, result1); + + ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); + + if (FFlag::StudioReportLuauAny) + { + LUAU_ASSERT(module->ats.typeInfo.size() == 0); + } } TEST_CASE_FIXTURE(ATSFixture, "explicit_pack") @@ -534,7 +583,8 @@ type Bar = Foo<(number, any)> if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>"); } } @@ -555,7 +605,8 @@ local x: number, y: any, z, h: nil = 1, nil if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3"); } } @@ -575,7 +626,8 @@ TEST_CASE_FIXTURE(ATSFixture, "table_uses_any") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0"); } } @@ -595,7 +647,8 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT((int)module->ats.typeInfo[1].code == 1); // Function Arguments + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end"); } } @@ -615,8 +668,9 @@ TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned") if (FFlag::StudioReportLuauAny) { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); // double counting variable annot again - LUAU_ASSERT((int)module->ats.typeInfo[1].code == 8); // Assign + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}"); } } @@ -635,13 +689,13 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end"); } } TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret") { - // TODO: should only return 1 fileResolver.source["game/Gui/Modules/A"] = R"( function other(y: number): any return "gotcha!" @@ -656,7 +710,8 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Function Return + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end"); } } @@ -676,8 +731,9 @@ TEST_CASE_FIXTURE(ATSFixture, "nested_local") if (FFlag::StudioReportLuauAny) { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); // should be one, double counitng annot - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end"); } } @@ -697,7 +753,8 @@ TEST_CASE_FIXTURE(ATSFixture, "generic_func") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse(a: {T}, b: any): {T}\n return a\n end"); } } @@ -716,7 +773,8 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_any") if (FFlag::StudioReportLuauAny) { LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); } } @@ -740,6 +798,42 @@ TEST_CASE_FIXTURE(ATSFixture, "multi_module_any") CheckResult result = frontend.check("game/B"); LUAU_REQUIRE_ERROR_COUNT(1, result); + + ModulePtr module = frontend.moduleResolver.getModule("game/B"); + + if (FFlag::StudioReportLuauAny) + { + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); + } } +TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req") +{ + fileResolver.source["game/A"] = R"( + local a = require(script.Parent.B) -- not resolving this module + export type MyFunction = (number, string) -> (any) +)"; + + fileResolver.source["game/B"] = R"( + local MyFunc = require(script.Parent.A) :: any + type Clear = any + local z: Clear = "zip" +)"; + + CheckResult result = frontend.check("game/B"); + LUAU_REQUIRE_ERROR_COUNT(0, result); + + ModulePtr module = frontend.moduleResolver.getModule("game/B"); + + if (FFlag::StudioReportLuauAny) + { + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any"); + } +} + + TEST_SUITE_END(); diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index 68fa48f9..2cd821b5 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -62,10 +62,12 @@ TEST_SUITE_BEGIN("A64Assembly"); #define SINGLE_COMPARE(inst, ...) \ CHECK(check( \ - [](AssemblyBuilderA64& build) { \ + [](AssemblyBuilderA64& build) \ + { \ build.inst; \ }, \ - {__VA_ARGS__})) + {__VA_ARGS__} \ + )) TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Unary") { @@ -228,66 +230,83 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Moves") SINGLE_COMPARE(movk(x0, 42, 16), 0xF2A00540); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, 42); }, - {0xD2800540})); + {0xD2800540} + )); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, 424242); }, - {0xD28F2640, 0xF2A000C0})); + {0xD28F2640, 0xF2A000C0} + )); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, -42); }, - {0x92800520})); + {0x92800520} + )); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, -424242); }, - {0x928F2620, 0xF2BFFF20})); + {0x928F2620, 0xF2BFFF20} + )); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, -65536); }, - {0x929FFFE0})); + {0x929FFFE0} + )); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.mov(x0, -65537); }, - {0x92800000, 0xF2BFFFC0})); + {0x92800000, 0xF2BFFFC0} + )); } TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "ControlFlow") { // Jump back CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { Label start = build.setLabel(); build.mov(x0, x1); build.b(ConditionA64::Equal, start); }, - {0xAA0103E0, 0x54FFFFE0})); + {0xAA0103E0, 0x54FFFFE0} + )); // Jump forward CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { Label skip; build.b(ConditionA64::Equal, skip); build.mov(x0, x1); build.setLabel(skip); }, - {0x54000040, 0xAA0103E0})); + {0x54000040, 0xAA0103E0} + )); // Jumps CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { Label skip; build.b(ConditionA64::Equal, skip); build.cbz(x0, skip); @@ -298,7 +317,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "ControlFlow") build.b(skip); build.bl(skip); }, - {0x540000A0, 0xB4000080, 0xB5000060, 0x36280040, 0x37280020, 0x14000000, 0x97ffffff})); + {0x540000A0, 0xB4000080, 0xB5000060, 0x36280040, 0x37280020, 0x14000000, 0x97ffffff} + )); // Basic control flow SINGLE_COMPARE(br(x0), 0xD61F0000); @@ -398,10 +418,14 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath") SINGLE_COMPARE(ucvtf(d1, x2), 0x9E630041); CHECK(check( - [](AssemblyBuilderA64& build) { + [](AssemblyBuilderA64& build) + { build.fjcvtzs(w1, d2); }, - {0x1E7E0041}, {}, A64::Feature_JSCVT)); + {0x1E7E0041}, + {}, + A64::Feature_JSCVT + )); } TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPLoadStore") diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index eaea3545..655fa8f1 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -51,10 +51,12 @@ TEST_SUITE_BEGIN("x64Assembly"); #define SINGLE_COMPARE(inst, ...) \ CHECK(check( \ - [](AssemblyBuilderX64& build) { \ + [](AssemblyBuilderX64& build) \ + { \ build.inst; \ }, \ - {__VA_ARGS__})) + {__VA_ARGS__} \ + )) TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseBinaryInstructionForms") { @@ -318,33 +320,41 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "NopForms") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AlignmentForms") { CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { build.ret(); build.align(8, AlignmentDataX64::Nop); }, - {0xc3, 0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00})); + {0xc3, 0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00} + )); CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { build.ret(); build.align(32, AlignmentDataX64::Nop); }, - {0xc3, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x84, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0x40, 0x00})); + {0xc3, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0x40, 0x00} + )); CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { build.ret(); build.align(8, AlignmentDataX64::Int3); }, - {0xc3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc})); + {0xc3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc} + )); CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { build.ret(); build.align(8, AlignmentDataX64::Ud2); }, - {0xc3, 0x0f, 0x0b, 0x0f, 0x0b, 0x0f, 0x0b, 0xcc})); + {0xc3, 0x0f, 0x0b, 0x0f, 0x0b, 0x0f, 0x0b, 0xcc} + )); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AlignmentOverflow") @@ -387,28 +397,33 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow") { // Jump back CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label start = build.setLabel(); build.add(rsi, 1); build.cmp(rsi, rdi); build.jcc(ConditionX64::Equal, start); }, - {0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf3, 0xff, 0xff, 0xff})); + {0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf3, 0xff, 0xff, 0xff} + )); // Jump back, but the label is set before use CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label start; build.add(rsi, 1); build.setLabel(start); build.cmp(rsi, rdi); build.jcc(ConditionX64::Equal, start); }, - {0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf7, 0xff, 0xff, 0xff})); + {0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf7, 0xff, 0xff, 0xff} + )); // Jump forward CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label skip; build.cmp(rsi, rdi); @@ -416,24 +431,28 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow") build.or_(rdi, 0x3e); build.setLabel(skip); }, - {0x48, 0x3b, 0xf7, 0x0f, 0x8f, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xcf, 0x3e})); + {0x48, 0x3b, 0xf7, 0x0f, 0x8f, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xcf, 0x3e} + )); // Regular jump CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label skip; build.jmp(skip); build.and_(rdi, 0x3e); build.setLabel(skip); }, - {0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e})); + {0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e} + )); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelCall") { CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label fnB; build.and_(rcx, 0x3e); @@ -444,7 +463,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelCall") build.lea(rax, addr[rcx + 0x1f]); build.ret(); }, - {0x48, 0x83, 0xe1, 0x3e, 0xe8, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x48, 0x8d, 0x41, 0x1f, 0xc3})); + {0x48, 0x83, 0xe1, 0x3e, 0xe8, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x48, 0x8d, 0x41, 0x1f, 0xc3} + )); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms") @@ -550,7 +570,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") { SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, RoundingModeX64::RoundToNegativeInfinity), 0xc4, 0xe3, 0x19, 0x0b, 0xfb, 0x09); SINGLE_COMPARE( - vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a); + vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a + ); SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b); SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); @@ -573,7 +594,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelLea") { CHECK(check( - [](AssemblyBuilderX64& build) { + [](AssemblyBuilderX64& build) + { Label fn; build.lea(rax, fn); build.ret(); @@ -581,7 +603,8 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelLea") build.setLabel(fn); build.ret(); }, - {0x48, 0x8d, 0x05, 0x01, 0x00, 0x00, 0x00, 0xc3, 0xc3})); + {0x48, 0x8d, 0x05, 0x01, 0x00, 0x00, 0x00, 0xc3, 0xc3} + )); } TEST_CASE("LogTest") diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 1c8b2127..464eacce 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -107,7 +107,9 @@ TEST_CASE("encode_AstStatBlock") CHECK( toJson(&block) == - (R"({"type":"AstStatBlock","location":"0,0 - 0,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})")); + (R"({"type":"AstStatBlock","location":"0,0 - 0,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})" + ) + ); } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables") @@ -125,7 +127,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables") CHECK( json == - R"({"type":"AstStatBlock","location":"0,0 - 6,4","hasEnd":true,"body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"luauType":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","type":"AstTableProp","location":"2,12 - 2,15","propType":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","nameLocation":"2,17 - 2,23","parameters":[]}}],"indexer":null},"name":"x","type":"AstLocal","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})"); + R"({"type":"AstStatBlock","location":"0,0 - 6,4","hasEnd":true,"body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"luauType":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","type":"AstTableProp","location":"2,12 - 2,15","propType":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","nameLocation":"2,17 - 2,23","parameters":[]}}],"indexer":null},"name":"x","type":"AstLocal","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})" + ); } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array") @@ -137,7 +140,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array") CHECK( json == - R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"); + R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})" + ); } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer") @@ -149,7 +153,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer") CHECK( json == - R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})"); + R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})" + ); } TEST_CASE("encode_AstExprGroup") @@ -200,8 +205,10 @@ TEST_CASE("encode_AstExprLocal") AstLocal local{AstName{"foo"}, Location{}, nullptr, 0, 0, nullptr}; AstExprLocal exprLocal{Location{}, &local, false}; - CHECK(toJson(&exprLocal) == - R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"luauType":null,"name":"foo","type":"AstLocal","location":"0,0 - 0,0"}})"); + CHECK( + toJson(&exprLocal) == + R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"luauType":null,"name":"foo","type":"AstLocal","location":"0,0 - 0,0"}})" + ); } TEST_CASE("encode_AstExprVarargs") diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index 3c302c37..55b2a132 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -27,20 +27,24 @@ TEST_SUITE_BEGIN("AstQuery::getDocumentationSymbolAtPosition"); TEST_CASE_FIXTURE(DocumentationSymbolFixture, "binding") { - std::optional global = getDocSymbol(R"( + std::optional global = getDocSymbol( + R"( local a = string.sub() )", - Position(1, 21)); + Position(1, 21) + ); CHECK_EQ(global, "@luau/global/string"); } TEST_CASE_FIXTURE(DocumentationSymbolFixture, "prop") { - std::optional substring = getDocSymbol(R"( + std::optional substring = getDocSymbol( + R"( local a = string.sub() )", - Position(1, 27)); + Position(1, 27) + ); CHECK_EQ(substring, "@luau/global/string.sub"); } @@ -51,11 +55,13 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "event_callback_arg") declare function Connect(fn: (string) -> ()) )"); - std::optional substring = getDocSymbol(R"( + std::optional substring = getDocSymbol( + R"( Connect(function(abc) end) )", - Position(1, 27)); + Position(1, 27) + ); CHECK_EQ(substring, "@test/global/Connect/param/0/param/0"); } @@ -66,10 +72,12 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_fn") declare foo: ((string) -> number) & ((number) -> string) )"); - std::optional symbol = getDocSymbol(R"( + std::optional symbol = getDocSymbol( + R"( foo("asdf") )", - Position(1, 10)); + Position(1, 10) + ); CHECK_EQ(symbol, "@test/global/foo/overload/(string) -> number"); } @@ -86,11 +94,13 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method") } )"); - std::optional symbol = getDocSymbol(R"( + std::optional symbol = getDocSymbol( + R"( local x: Foo = Foo.new() x:bar("asdf") )", - Position(2, 11)); + Position(2, 11) + ); CHECK_EQ(symbol, "@test/globaltype/Foo.bar"); } @@ -108,11 +118,13 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method") } )"); - std::optional symbol = getDocSymbol(R"( + std::optional symbol = getDocSymbol( + R"( local x: Foo = Foo.new() x:bar("asdf") )", - Position(2, 11)); + Position(2, 11) + ); CHECK_EQ(symbol, "@test/globaltype/Foo.bar/overload/(Foo, string) -> number"); } @@ -125,10 +137,12 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_function_prop") } )"); - std::optional symbol = getDocSymbol(R"( + std::optional symbol = getDocSymbol( + R"( Foo.new("asdf") )", - Position(1, 13)); + Position(1, 13) + ); CHECK_EQ(symbol, "@test/global/Foo.new"); } @@ -141,10 +155,12 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop") } )"); - std::optional symbol = getDocSymbol(R"( + std::optional symbol = getDocSymbol( + R"( Foo.new("asdf") )", - Position(1, 13)); + Position(1, 13) + ); CHECK_EQ(symbol, "@test/global/Foo.new/overload/(string) -> number"); } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 4e8a0442..c1b62d7a 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -106,7 +106,8 @@ struct ACFixtureImpl : BaseType GlobalTypes& globals = this->frontend.globalsForAutocomplete; unfreeze(globals.globalTypes); LoadDefinitionFileResult result = this->frontend.loadDefinitionFile( - globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true); + globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true + ); freeze(globals.globalTypes); if (FFlag::DebugLuauDeferredConstraintResolution) @@ -114,7 +115,8 @@ struct ACFixtureImpl : BaseType GlobalTypes& globals = this->frontend.globals; unfreeze(globals.globalTypes); LoadDefinitionFileResult result = this->frontend.loadDefinitionFile( - globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true); + globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typeCheckForAutocomplete */ true + ); freeze(globals.globalTypes); } @@ -868,8 +870,10 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords") auto ac1 = autocomplete('1'); CHECK_EQ(ac1.entryMap.count("then"), 0); - CHECK_EQ(ac1.entryMap.count("function"), - 1); // FIXME: This is kind of dumb. It is technically syntactically valid but you can never do anything interesting with this. + CHECK_EQ( + ac1.entryMap.count("function"), + 1 + ); // FIXME: This is kind of dumb. It is technically syntactically valid but you can never do anything interesting with this. CHECK_EQ(ac1.entryMap.count("table"), 1); CHECK_EQ(ac1.entryMap.count("else"), 0); CHECK_EQ(ac1.entryMap.count("elseif"), 0); @@ -3559,7 +3563,11 @@ t.@1 auto ac = autocomplete('1'); REQUIRE(ac.entryMap.count("m")); - CHECK(!ac.entryMap["m"].wrongIndexType); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK(ac.entryMap["m"].wrongIndexType); + else + CHECK(!ac.entryMap["m"].wrongIndexType); CHECK(!ac.entryMap["m"].indexedWithSelf); } @@ -3752,10 +3760,13 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") bool isCorrect = false; auto ac1 = autocomplete( - '1', [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional { + '1', + [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional + { isCorrect = contents && *contents == "testing/"; return std::nullopt; - }); + } + ); CHECK(isCorrect); } @@ -3856,8 +3867,9 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") local x = require(@1"@2"@3) )"); - StringCompletionCallback callback = [](std::string, std::optional, - std::optional contents) -> std::optional { + StringCompletionCallback callback = [](std::string, std::optional, std::optional contents + ) -> std::optional + { Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; return results; }; diff --git a/tests/BuiltinDefinitions.test.cpp b/tests/BuiltinDefinitions.test.cpp index 75d054bd..08505c8d 100644 --- a/tests/BuiltinDefinitions.test.cpp +++ b/tests/BuiltinDefinitions.test.cpp @@ -20,7 +20,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols") std::string expectedRootSymbol = "@luau/global/" + nameString; std::optional actualRootSymbol = binding.documentationSymbol; CHECK_MESSAGE( - actualRootSymbol == expectedRootSymbol, "expected symbol ", expectedRootSymbol, " for global ", nameString, ", got ", actualRootSymbol); + actualRootSymbol == expectedRootSymbol, "expected symbol ", expectedRootSymbol, " for global ", nameString, ", got ", actualRootSymbol + ); const TableType::Props* props = nullptr; if (const TableType* ttv = get(binding.typeId)) @@ -39,8 +40,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols") std::string fullPropName = nameString + "." + propName; std::string expectedPropSymbol = expectedRootSymbol + "." + propName; std::optional actualPropSymbol = prop.documentationSymbol; - CHECK_MESSAGE(actualPropSymbol == expectedPropSymbol, "expected symbol ", expectedPropSymbol, " for ", fullPropName, ", got ", - actualPropSymbol); + CHECK_MESSAGE( + actualPropSymbol == expectedPropSymbol, "expected symbol ", expectedPropSymbol, " for ", fullPropName, ", got ", actualPropSymbol + ); } } } diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index c369cb30..a9bf9596 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -29,7 +29,8 @@ ClassFixture::ClassFixture() }; getMutable(connectionType)->props = { - {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}}; + {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}} + }; TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(baseClassType)->props = { @@ -102,10 +103,12 @@ ClassFixture::ClassFixture() }; getMutable(vector2MetaType)->props = { {"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}}, - {"__mul", {arena.addType(IntersectionType{{ - makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), - makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}), - }})}}}; + {"__mul", + {arena.addType(IntersectionType{{ + makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), + makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}), + }})}} + }; globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; addGlobalBinding(globals, "Vector2", vector2Type, "@test"); @@ -116,7 +119,8 @@ ClassFixture::ClassFixture() }; globals.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType}; - auto addIndexableClass = [&arena, &globals](const char* className, TypeId keyType, TypeId returnType) { + auto addIndexableClass = [&arena, &globals](const char* className, TypeId keyType, TypeId returnType) + { TypeId indexableClassMetaType = arena.addType(TableType{}); TypeId indexableClassType = arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}}); diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 21228d6b..058a1100 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -57,7 +57,8 @@ TEST_CASE("CodeAllocationCallbacks") AllocationData allocationData{}; - const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) { + const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) + { AllocationData& allocationData = *static_cast(context); if (oldPointer != nullptr) { @@ -146,7 +147,8 @@ TEST_CASE("CodeAllocationWithUnwindCallbacks") data.resize(8); allocator.context = &info; - allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& beginOffset) -> void* { + allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& beginOffset) -> void* + { Info& info = *(Info*)context; CHECK(info.unwind.size() == 8); @@ -157,7 +159,8 @@ TEST_CASE("CodeAllocationWithUnwindCallbacks") return new int(7); }; - allocator.destroyBlockUnwindInfo = [](void* context, void* unwindData) { + allocator.destroyBlockUnwindInfo = [](void* context, void* unwindData) + { Info& info = *(Info*)context; info.destroyCalled = true; @@ -194,8 +197,8 @@ TEST_CASE("WindowsUnwindCodesX64") data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); - std::vector expected{0x44, 0x33, 0x22, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x17, 0x0a, 0x05, 0x17, 0x82, 0x13, - 0xf0, 0x11, 0xe0, 0x0f, 0xd0, 0x0d, 0xc0, 0x0b, 0x30, 0x09, 0x60, 0x07, 0x70, 0x05, 0x03, 0x02, 0x50}; + std::vector expected{0x44, 0x33, 0x22, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x17, 0x0a, 0x05, 0x17, 0x82, + 0x13, 0xf0, 0x11, 0xe0, 0x0f, 0xd0, 0x0d, 0xc0, 0x0b, 0x30, 0x09, 0x60, 0x07, 0x70, 0x05, 0x03, 0x02, 0x50}; REQUIRE(data.size() == expected.size()); CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0); @@ -218,11 +221,12 @@ TEST_CASE("Dwarf2UnwindCodesX64") data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); - std::vector expected{0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x10, 0x0c, 0x07, 0x08, 0x90, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0e, 0x10, 0x86, 0x02, 0x02, 0x03, 0x02, 0x02, 0x0e, 0x18, 0x85, 0x03, 0x02, 0x02, 0x0e, - 0x20, 0x84, 0x04, 0x02, 0x02, 0x0e, 0x28, 0x83, 0x05, 0x02, 0x02, 0x0e, 0x30, 0x8c, 0x06, 0x02, 0x02, 0x0e, 0x38, 0x8d, 0x07, 0x02, 0x02, - 0x0e, 0x40, 0x8e, 0x08, 0x02, 0x02, 0x0e, 0x48, 0x8f, 0x09, 0x02, 0x04, 0x0e, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + std::vector expected{0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x10, 0x0c, 0x07, 0x08, 0x90, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0e, 0x10, 0x86, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x0e, 0x18, 0x85, 0x03, 0x02, 0x02, 0x0e, 0x20, 0x84, 0x04, 0x02, 0x02, 0x0e, 0x28, + 0x83, 0x05, 0x02, 0x02, 0x0e, 0x30, 0x8c, 0x06, 0x02, 0x02, 0x0e, 0x38, 0x8d, 0x07, 0x02, 0x02, 0x0e, 0x40, + 0x8e, 0x08, 0x02, 0x02, 0x0e, 0x48, 0x8f, 0x09, 0x02, 0x04, 0x0e, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; REQUIRE(data.size() == expected.size()); CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0); @@ -244,10 +248,10 @@ TEST_CASE("Dwarf2UnwindCodesA64") data.resize(unwind.getUnwindInfoSize()); unwind.finalize(data.data(), 0, nullptr, 0); - std::vector expected{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x1e, 0x0c, 0x1f, 0x00, 0x2c, 0x00, 0x00, - 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, - 0x0e, 0x40, 0x02, 0x18, 0x9d, 0x08, 0x9e, 0x07, 0x93, 0x06, 0x94, 0x05, 0x95, 0x04, 0x96, 0x03, 0x97, 0x02, 0x98, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00}; + std::vector expected{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x1e, 0x0c, 0x1f, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x0e, 0x40, 0x02, 0x18, 0x9d, 0x08, 0x9e, 0x07, 0x93, + 0x06, 0x94, 0x05, 0x95, 0x04, 0x96, 0x03, 0x97, 0x02, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; REQUIRE(data.size() == expected.size()); CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0); @@ -731,8 +735,8 @@ TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64") uint8_t* nativeData1; size_t sizeNativeData1; uint8_t* nativeEntry1; - REQUIRE( - allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData1, sizeNativeData1, nativeEntry1)); + REQUIRE(allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData1, sizeNativeData1, nativeEntry1) + ); REQUIRE(nativeEntry1); // Now we set the offset at the begining so that functions in new blocks will not overlay the locations @@ -755,8 +759,9 @@ TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64") uint8_t* nativeData2; size_t sizeNativeData2; uint8_t* nativeEntry2; - REQUIRE(allocator.allocate( - build2.data.data(), build2.data.size(), build2.code.data(), build2.code.size(), nativeData2, sizeNativeData2, nativeEntry2)); + REQUIRE( + allocator.allocate(build2.data.data(), build2.data.size(), build2.code.data(), build2.code.size(), nativeData2, sizeNativeData2, nativeEntry2) + ); REQUIRE(nativeEntry2); // To simplify debugging, CHECK_THROWS_WITH_AS is not used here @@ -808,8 +813,15 @@ TEST_CASE("GeneratedCodeExecutionA64") uint8_t* nativeData; size_t sizeNativeData; uint8_t* nativeEntry; - REQUIRE(allocator.allocate(build.data.data(), build.data.size(), reinterpret_cast(build.code.data()), build.code.size() * 4, nativeData, - sizeNativeData, nativeEntry)); + REQUIRE(allocator.allocate( + build.data.data(), + build.data.size(), + reinterpret_cast(build.code.data()), + build.code.size() * 4, + nativeData, + sizeNativeData, + nativeEntry + )); REQUIRE(nativeEntry); using FunctionType = int64_t(int64_t, int*); @@ -878,8 +890,15 @@ TEST_CASE("GeneratedCodeExecutionWithThrowA64") uint8_t* nativeData; size_t sizeNativeData; uint8_t* nativeEntry; - REQUIRE(allocator.allocate(build.data.data(), build.data.size(), reinterpret_cast(build.code.data()), build.code.size() * 4, nativeData, - sizeNativeData, nativeEntry)); + REQUIRE(allocator.allocate( + build.data.data(), + build.data.size(), + reinterpret_cast(build.code.data()), + build.code.size() * 4, + nativeData, + sizeNativeData, + nativeEntry + )); REQUIRE(nativeEntry); using FunctionType = int64_t(int64_t, void (*)(int64_t)); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 250de6e4..58f52a08 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -213,7 +213,8 @@ RETURN R0 0 TEST_CASE("ReflectionBytecode") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local part = Instance.new('Part', workspace) part.Size = Vector3.new(1, 2, 3) return part.Size.Z * part:GetMass() @@ -235,7 +236,8 @@ NAMECALL R3 R0 K10 ['GetMass'] CALL R3 1 1 MUL R1 R2 R3 RETURN R1 1 -)"); +)" + ); } TEST_CASE("ImportCall") @@ -693,7 +695,8 @@ RETURN R0 1 TEST_CASE("TableLiteralsIndexConstant") { // validate that we use SETTTABLEKS for constant variable keys - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = "key", "value" return {[a] = 42, [b] = 0} )"), @@ -704,10 +707,12 @@ SETTABLEKS R1 R0 K0 ['key'] LOADN R1 0 SETTABLEKS R1 R0 K1 ['value'] RETURN R0 1 -)"); +)" + ); // validate that we use SETTABLEN for constant variable keys *and* that we predict array size - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = 1, 2 return {[a] = 42, [b] = 0} )"), @@ -718,12 +723,14 @@ SETTABLEN R1 R0 1 LOADN R1 0 SETTABLEN R1 R0 2 RETURN R0 1 -)"); +)" + ); } TEST_CASE("TableSizePredictionBasic") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = {} t.a = 1 t.b = 1 @@ -756,9 +763,11 @@ SETTABLEKS R1 R0 K7 ['h'] LOADN R1 1 SETTABLEKS R1 R0 K8 ['i'] RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = {} t.x = 1 t.x = 2 @@ -791,9 +800,11 @@ SETTABLEKS R1 R0 K0 ['x'] LOADN R1 9 SETTABLEKS R1 R0 K0 ['x'] RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = {} t[1] = 1 t[2] = 1 @@ -829,12 +840,15 @@ SETTABLEN R1 R0 9 LOADN R1 1 SETTABLEN R1 R0 10 RETURN R0 0 -)"); +)" + ); } TEST_CASE("TableSizePredictionObject") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} t.field = 1 function t:getfield() @@ -842,7 +856,8 @@ function t:getfield() end return t )", - 1), + 1 + ), R"( NEWTABLE R0 2 0 LOADN R1 1 @@ -850,12 +865,14 @@ SETTABLEKS R1 R0 K0 ['field'] DUPCLOSURE R1 K1 ['getfield'] SETTABLEKS R1 R0 K2 ['getfield'] RETURN R0 1 -)"); +)" + ); } TEST_CASE("TableSizePredictionSetMetatable") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = setmetatable({}, nil) t.field1 = 1 t.field2 = 2 @@ -872,12 +889,14 @@ SETTABLEKS R1 R0 K3 ['field1'] LOADN R1 2 SETTABLEKS R1 R0 K4 ['field2'] RETURN R0 1 -)"); +)" + ); } TEST_CASE("TableSizePredictionLoop") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = {} for i=1,4 do t[i] = 0 @@ -894,7 +913,8 @@ L0: LOADN R4 0 SETTABLE R4 R0 R3 FORNLOOP R1 L0 L1: RETURN R0 1 -)"); +)" + ); } TEST_CASE("ReflectionEnums") @@ -1330,7 +1350,8 @@ TEST_CASE("InterpStringWithNoExpressions") TEST_CASE("InterpStringZeroCost") { - CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"), + CHECK_EQ( + "\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"), R"( LOADK R1 K0 ['hello, %*!'] LOADK R3 K1 ['world'] @@ -1338,12 +1359,14 @@ NAMECALL R1 R1 K2 ['format'] CALL R1 2 1 MOVE R0 R1 RETURN R0 0 -)"); +)" + ); } TEST_CASE("InterpStringRegisterCleanup") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c = nil, "um", "uh oh" a = `foo{"bar"}` print(a) @@ -1362,7 +1385,8 @@ GETIMPORT R3 6 [print] MOVE R4 R0 CALL R3 1 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("InterpStringRegisterLimit") @@ -1814,13 +1838,15 @@ until rr < 0.5 { CHECK_EQ(e.getLocation().begin.line + 1, 8); CHECK_EQ( - std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it"); + std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it" + ); } // but it's okay if continue is inside a non-repeat..until loop, or inside a loop that doesn't use the local (here `continue` just terminates // inner loop) - CHECK_EQ("\n" + compileFunction0( - "repeat local r = math.random() repeat if r > 0.5 then continue end r = r - 0.1 until true r = r + 0.3 until r < 0.5"), + CHECK_EQ( + "\n" + + compileFunction0("repeat local r = math.random() repeat if r > 0.5 then continue end r = r - 0.1 until true r = r + 0.3 until r < 0.5"), R"( L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -1832,12 +1858,14 @@ LOADK R1 K3 [0.5] JUMPIFLT R0 R1 L2 JUMPBACK L0 L2: RETURN R0 0 -)"); +)" + ); // and it's also okay to use a local defined in the until expression as long as it's inside a function! CHECK_EQ( "\n" + compileFunction( - "repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until (function() local a = r return a < 0.5 end)()", 1), + "repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until (function() local a = r return a < 0.5 end)()", 1 + ), R"( L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -1852,7 +1880,8 @@ CLOSEUPVALS R0 JUMPBACK L0 L2: CLOSEUPVALS R0 RETURN R0 0 -)"); +)" + ); // but not if the function just refers to an upvalue try @@ -1874,12 +1903,14 @@ until (function() return rr end)() < 0.5 { CHECK_EQ(e.getLocation().begin.line + 1, 8); CHECK_EQ( - std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it"); + std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it" + ); } // unless that upvalue is from an outer scope - CHECK_EQ("\n" + compileFunction0("local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then " - "continue end r = r + 0.3 until stop or r < 0.5 end"), + CHECK_EQ( + "\n" + compileFunction0("local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then " + "continue end r = r + 0.3 until stop or r < 0.5 end"), R"( L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -1892,12 +1923,16 @@ LOADK R1 K3 [0.5] JUMPIFLT R0 R1 L2 JUMPBACK L0 L2: RETURN R0 0 -)"); +)" + ); // including upvalue references from a function expression - CHECK_EQ("\n" + compileFunction("local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then continue " - "end r = r + 0.3 until (function() return stop or r < 0.5 end)() end", - 1), + CHECK_EQ( + "\n" + compileFunction( + "local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then continue " + "end r = r + 0.3 until (function() return stop or r < 0.5 end)() end", + 1 + ), R"( L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -1913,13 +1948,15 @@ CLOSEUPVALS R0 JUMPBACK L0 L2: CLOSEUPVALS R0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopContinueIgnoresImplicitConstant") { // this used to crash the compiler :( - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local _ repeat continue @@ -1928,13 +1965,15 @@ until not _ R"( RETURN R0 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopContinueIgnoresExplicitConstant") { // Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local c = true repeat continue @@ -1943,7 +1982,8 @@ until c R"( RETURN R0 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopContinueRespectsExplicitConstant") @@ -1966,14 +2006,17 @@ until c { CHECK_EQ(e.getLocation().begin.line + 1, 6); CHECK_EQ( - std::string(e.what()), "Local c used in the repeat..until condition is undefined because continue statement on line 3 jumps over it"); + std::string(e.what()), "Local c used in the repeat..until condition is undefined because continue statement on line 3 jumps over it" + ); } } TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline") { // Inlining might also replace some locals with constants instead of allocating them - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function inline(f) repeat continue @@ -1986,11 +2029,14 @@ end test() )", - 1, 2), + 1, + 2 + ), R"( RETURN R0 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll") @@ -2000,7 +2046,8 @@ TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll") // access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it try { - compileFunction(R"( + compileFunction( + R"( for i = 1, 2 do s() repeat @@ -2011,7 +2058,9 @@ for i = 1, 2 do until f(x) end )", - 0, 2); + 0, + 2 + ); CHECK(!"Expected CompileError"); } @@ -2019,7 +2068,8 @@ end { CHECK_EQ(e.getLocation().begin.line + 1, 9); CHECK_EQ( - std::string(e.what()), "Local x used in the repeat..until condition is undefined because continue statement on line 6 jumps over it"); + std::string(e.what()), "Local x used in the repeat..until condition is undefined because continue statement on line 6 jumps over it" + ); } } @@ -2028,7 +2078,9 @@ TEST_CASE("LoopContinueUntilCapture") // validate continue upvalue closing behavior: continue must close locals defined in the nested scopes // but can't close locals defined in the loop scope - these are visible to the condition and will be closed // when evaluating the condition instead. - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local a a = 0 repeat local b b = 0 @@ -2045,7 +2097,8 @@ until function() a = 0 b = 0 end -- must close b on loop exit -- must close a )", - 2), + 2 + ), R"( LOADNIL R0 LOADN R0 0 @@ -2070,10 +2123,13 @@ JUMPBACK L0 L3: CLOSEUPVALS R1 CLOSEUPVALS R0 RETURN R0 0 -)"); +)" + ); // a simpler version of the above test doesn't need to close anything when evaluating continue - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local a a = 0 repeat local b b = 0 @@ -2085,7 +2141,8 @@ until function() a = 0 b = 0 end -- must close b on loop exit -- must close a )", - 1), + 1 + ), R"( LOADNIL R0 LOADN R0 0 @@ -2101,13 +2158,16 @@ JUMPBACK L0 L2: CLOSEUPVALS R1 CLOSEUPVALS R0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopContinueEarlyCleanup") { // locals after a potential 'continue' are not accessible inside the condition and can be closed at the end of a block - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local y repeat local a, b @@ -2123,7 +2183,8 @@ repeat y = x until a )", - 1), + 1 + ), R"( LOADNIL R0 L0: LOADNIL R1 @@ -2143,20 +2204,24 @@ CLOSEUPVALS R1 JUMPBACK L0 L2: CLOSEUPVALS R1 RETURN R0 0 -)"); +)" + ); } TEST_CASE("AndOrOptimizations") { // the OR/ORK optimization triggers for cutoff since lhs is simple - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function advancedRidgedFilter(value, cutoff) local cutoff = cutoff or .5 value = value - cutoff return 1 - (value < 0 and -value or value) * 1 / (1 - cutoff) end )", - 0), + 0 + ), R"( ORK R2 R1 K0 [0.5] SUB R0 R0 R2 @@ -2170,15 +2235,19 @@ SUBRK R6 K1 [1] R2 DIV R4 R5 R6 SUBRK R3 K1 [1] R4 RETURN R3 1 -)"); +)" + ); // sometimes we need to compute a boolean; this uses LOADB with an offset - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function thinSurface(surfaceGradient, surfaceThickness) return surfaceGradient > .5 - surfaceThickness*.4 and surfaceGradient < .5 + surfaceThickness*.4 end )", - 0), + 0 + ), R"( LOADB R2 0 MULK R4 R1 K1 [0.40000000000000002] @@ -2191,15 +2260,19 @@ JUMPIFLT R0 R3 L0 LOADB R2 0 +1 L0: LOADB R2 1 L1: RETURN R2 1 -)"); +)" + ); // sometimes we need to compute a boolean; this uses LOADB with an offset for the last op, note that first op is compiled better - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function thickSurface(surfaceGradient, surfaceThickness) return surfaceGradient < .5 - surfaceThickness*.4 or surfaceGradient > .5 + surfaceThickness*.4 end )", - 0), + 0 + ), R"( LOADB R2 1 MULK R4 R1 K1 [0.40000000000000002] @@ -2212,30 +2285,38 @@ JUMPIFLT R3 R0 L0 LOADB R2 0 +1 L0: LOADB R2 1 L1: RETURN R2 1 -)"); +)" + ); // trivial ternary if with constants - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function testSurface(surface) return surface and 1 or 0 end )", - 0), + 0 + ), R"( JUMPIFNOT R0 L0 LOADN R1 1 RETURN R1 1 L0: LOADN R1 0 RETURN R1 1 -)"); +)" + ); // canonical saturate - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function saturate(x) return x < 0 and 0 or x > 1 and 1 or x end )", - 0), + 0 + ), R"( LOADN R2 0 JUMPIFNOTLT R0 R2 L0 @@ -2247,7 +2328,8 @@ LOADN R1 1 RETURN R1 1 L1: MOVE R1 R0 RETURN R1 1 -)"); +)" + ); } TEST_CASE("JumpFold") @@ -2292,7 +2374,9 @@ L0: RETURN R0 0 // in this example, we do *not* have a JUMP after RETURN in the if branch // this is important since, even though this jump is never reached, jump folding needs to be able to analyze it - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function getPerlin(x, y, z, seed, scale, raw) local seed = seed or 0 local scale = scale or 1 @@ -2303,7 +2387,8 @@ return math.noise(x / scale + (seed * 17) + masterSeed, y / scale - masterSeed, end end )", - 0), + 0 + ), R"( ORK R6 R3 K0 [0] ORK R7 R4 K1 [1] @@ -2338,7 +2423,8 @@ MUL R13 R6 R6 SUB R11 R12 R13 CALL R8 3 -1 RETURN R8 -1 -)"); +)" + ); } TEST_CASE("RecursionParse") @@ -2548,7 +2634,9 @@ L1: RETURN R3 -1 TEST_CASE("UpvaluesLoopsBytecode") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() for i=1,10 do i = i @@ -2560,7 +2648,8 @@ function test() return 0 end )", - 1), + 1 + ), R"( LOADN R2 1 LOADN R0 10 @@ -2579,9 +2668,12 @@ L1: CLOSEUPVALS R3 FORNLOOP R0 L0 L2: LOADN R0 0 RETURN R0 1 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() for i in ipairs(data) do i = i @@ -2593,7 +2685,8 @@ function test() return 0 end )", - 1), + 1 + ), R"( GETIMPORT R0 1 [ipairs] GETIMPORT R1 3 [data] @@ -2611,9 +2704,12 @@ L1: CLOSEUPVALS R3 L2: FORGLOOP R0 L0 1 [inext] L3: LOADN R0 0 RETURN R0 1 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() local i = 0 while i < 5 do @@ -2628,7 +2724,8 @@ function test() return 0 end )", - 1), + 1 + ), R"( LOADN R0 0 L0: LOADN R1 5 @@ -2648,9 +2745,12 @@ L1: CLOSEUPVALS R1 JUMPBACK L0 L2: LOADN R1 0 RETURN R1 1 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() local i = 0 repeat @@ -2665,7 +2765,8 @@ function test() return 0 end )", - 1), + 1 + ), R"( LOADN R0 0 L0: LOADNIL R1 @@ -2686,7 +2787,8 @@ JUMPBACK L0 L2: CLOSEUPVALS R1 L3: LOADN R1 0 RETURN R1 1 -)"); +)" + ); } TEST_CASE("TypeAliasing") @@ -2815,7 +2917,9 @@ end TEST_CASE("DebugLineInfoRepeatUntil") { - CHECK_EQ("\n" + compileFunction0Coverage(R"( + CHECK_EQ( + "\n" + compileFunction0Coverage( + R"( local f = 0 repeat f += 1 @@ -2826,7 +2930,8 @@ repeat end until f == 0 )", - 0), + 0 + ), R"( 2: LOADN R0 0 4: L0: ADDK R0 R0 K0 [1] @@ -2839,7 +2944,8 @@ until f == 0 10: L2: JUMPXEQKN R0 K3 L3 [0] 10: JUMPBACK L0 11: L3: RETURN R0 0 -)"); +)" + ); } TEST_CASE("DebugLineInfoSubTable") @@ -3487,7 +3593,8 @@ TEST_CASE("Fastcall3") { ScopedFastFlag luauCompileFastcall3{FFlag::LuauCompileFastcall3, true}; - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c = ... return math.min(a, b, c) + math.clamp(a, b, c) )"), @@ -3507,7 +3614,8 @@ GETIMPORT R5 4 [math.clamp] CALL R5 3 1 L1: ADD R3 R4 R5 RETURN R3 1 -)"); +)" + ); } TEST_CASE("FastcallSelect") @@ -3523,7 +3631,8 @@ L0: RETURN R0 1 )"); // more complex example: select inside a for loop bound + select from a iterator - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local sum = 0 for i=1, select('#', ...) do sum += select(i, ...) @@ -3549,7 +3658,8 @@ CALL R4 -1 1 L2: ADD R0 R0 R4 FORNLOOP R1 L1 L3: RETURN R0 1 -)"); +)" + ); // currently we assume a single value return to avoid dealing with stack resizing CHECK_EQ("\n" + compileFunction0("return select('#', ...)"), R"( @@ -3717,19 +3827,23 @@ RETURN R0 1 // multi-level recursive capture where function isn't top-level // note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() local function bar() return function() return bar() end end end )", - 1), + 1 + ), R"( NEWCLOSURE R0 P0 CAPTURE UPVAL U0 RETURN R0 1 -)"); +)" + ); } TEST_CASE("OutOfLocals") @@ -4077,7 +4191,8 @@ TEST_CASE("CompileBytecode") TEST_CASE("NestedNamecall") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... return obj:Method(1):Method(2):Method(3) )"), @@ -4093,23 +4208,27 @@ LOADN R3 3 NAMECALL R1 R1 K0 ['Method'] CALL R1 2 -1 RETURN R1 -1 -)"); +)" + ); } TEST_CASE("ElideLocals") { // simple local elision: all locals are constant - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = 1, 2 return a + b )"), R"( LOADN R0 3 RETURN R0 1 -)"); +)" + ); // side effecting expressions block local elision - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a = g() return a )"), @@ -4117,10 +4236,12 @@ return a GETIMPORT R0 1 [g] CALL R0 0 1 RETURN R0 1 -)"); +)" + ); // ... even if they are not used - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a = 1, g() return a )"), @@ -4129,12 +4250,14 @@ LOADN R0 1 GETIMPORT R1 1 [g] CALL R1 0 1 RETURN R0 1 -)"); +)" + ); } TEST_CASE("ConstantJumpCompare") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = obj == 1 )"), @@ -4144,9 +4267,11 @@ JUMPXEQKN R0 K0 L0 [1] LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = 1 == obj )"), @@ -4156,9 +4281,11 @@ JUMPXEQKN R0 K0 L0 [1] LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = "Hello, Sailor!" == obj )"), @@ -4168,9 +4295,11 @@ JUMPXEQKS R0 K0 L0 ['Hello, Sailor!'] LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = nil == obj )"), @@ -4180,9 +4309,11 @@ JUMPXEQKNIL R0 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = true == obj )"), @@ -4192,9 +4323,11 @@ JUMPXEQKB R0 1 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = nil ~= obj )"), @@ -4204,10 +4337,12 @@ JUMPXEQKNIL R0 L0 NOT LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); // table literals should not generate IFEQK variants - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local obj = ... local b = obj == {} )"), @@ -4218,12 +4353,14 @@ JUMPIFEQ R0 R2 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 -)"); +)" + ); } TEST_CASE("TableConstantStringIndex") { - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = { a = 2 } return t['a'] )"), @@ -4233,9 +4370,11 @@ LOADN R1 2 SETTABLEKS R1 R0 K0 ['a'] GETTABLEKS R1 R0 K0 ['a'] RETURN R1 1 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = {} t['a'] = 2 )"), @@ -4244,17 +4383,21 @@ NEWTABLE R0 0 0 LOADN R1 2 SETTABLEKS R1 R0 K0 ['a'] RETURN R0 0 -)"); +)" + ); } TEST_CASE("Coverage") { // basic statement coverage - CHECK_EQ("\n" + compileFunction0Coverage(R"( + CHECK_EQ( + "\n" + compileFunction0Coverage( + R"( print(1) print(2) )", - 1), + 1 + ), R"( 2: COVERAGE 2: GETIMPORT R0 1 [print] @@ -4265,17 +4408,21 @@ print(2) 3: LOADN R1 2 3: CALL R0 1 0 4: RETURN R0 0 -)"); +)" + ); // branching - CHECK_EQ("\n" + compileFunction0Coverage(R"( + CHECK_EQ( + "\n" + compileFunction0Coverage( + R"( if x then print(1) else print(2) end )", - 1), + 1 + ), R"( 2: COVERAGE 2: GETIMPORT R0 1 [x] @@ -4290,11 +4437,14 @@ end 5: LOADN R1 2 5: CALL R0 1 0 7: RETURN R0 0 -)"); +)" + ); // branching with comments // note that commented lines don't have COVERAGE insns! - CHECK_EQ("\n" + compileFunction0Coverage(R"( + CHECK_EQ( + "\n" + compileFunction0Coverage( + R"( if x then -- first print(1) @@ -4303,7 +4453,8 @@ else print(2) end )", - 1), + 1 + ), R"( 2: COVERAGE 2: GETIMPORT R0 1 [x] @@ -4318,11 +4469,14 @@ end 7: LOADN R1 2 7: CALL R0 1 0 9: RETURN R0 0 -)"); +)" + ); // expression coverage for table literals // note: duplicate COVERAGE instructions are there since we don't deduplicate expr/stat - CHECK_EQ("\n" + compileFunction0Coverage(R"( + CHECK_EQ( + "\n" + compileFunction0Coverage( + R"( local c = ... local t = { a = 1, @@ -4330,7 +4484,8 @@ local t = { c = c } )", - 2), + 2 + ), R"( 2: COVERAGE 2: COVERAGE @@ -4349,52 +4504,68 @@ local t = { 6: COVERAGE 6: SETTABLEKS R0 R1 K2 ['c'] 8: RETURN R0 0 -)"); +)" + ); } TEST_CASE("ConstantClosure") { // closures without upvalues are created when bytecode is loaded - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return function() end )", - 1), + 1 + ), R"( DUPCLOSURE R0 K0 [] RETURN R0 1 -)"); +)" + ); // they can access globals just fine - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return function() print("hi") end )", - 1), + 1 + ), R"( DUPCLOSURE R0 K0 [] RETURN R0 1 -)"); +)" + ); // if they need upvalues, we can't create them before running the code (but see SharedClosure test) - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() local print = print return function() print("hi") end end )", - 1), + 1 + ), R"( GETIMPORT R0 1 [print] NEWCLOSURE R1 P0 CAPTURE VAL R0 RETURN R1 1 -)"); +)" + ); // if they don't need upvalues but we sense that environment may be modified, we disable this to avoid fenv-related identity confusion - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( setfenv(1, {}) return function() print("hi") end )", - 1), + 1 + ), R"( GETIMPORT R0 1 [setfenv] LOADN R1 1 @@ -4402,39 +4573,50 @@ NEWTABLE R2 0 0 CALL R0 2 0 NEWCLOSURE R0 P0 RETURN R0 1 -)"); +)" + ); // note that fenv analysis isn't flow-sensitive right now, which is sort of a feature - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( if false then setfenv(1, {}) end return function() print("hi") end )", - 1), + 1 + ), R"( NEWCLOSURE R0 P0 RETURN R0 1 -)"); +)" + ); } TEST_CASE("SharedClosure") { // closures can be shared even if functions refer to upvalues, as long as upvalues are top-level - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local val = ... local function foo() return function() return val end end )", - 1), + 1 + ), R"( DUPCLOSURE R0 K0 [] CAPTURE UPVAL U0 RETURN R0 1 -)"); +)" + ); // ... as long as the values aren't mutated. - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local val = ... local function foo() @@ -4443,28 +4625,36 @@ end val = 5 )", - 1), + 1 + ), R"( NEWCLOSURE R0 P0 CAPTURE UPVAL U0 RETURN R0 1 -)"); +)" + ); // making the upvalue non-toplevel disables the optimization since it's likely that it will change - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(val) return function() return val end end )", - 1), + 1 + ), R"( NEWCLOSURE R1 P0 CAPTURE VAL R0 RETURN R1 1 -)"); +)" + ); // the upvalue analysis is transitive through local functions, which allows for code reuse to not defeat the optimization - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local val = ... local function foo() @@ -4475,17 +4665,21 @@ local function foo() return function() return bar() end end )", - 2), + 2 + ), R"( DUPCLOSURE R0 K0 ['bar'] CAPTURE UPVAL U0 DUPCLOSURE R1 K1 [] CAPTURE VAL R0 RETURN R1 1 -)"); +)" + ); // as such, if the upvalue that we reach transitively isn't top-level we fall back to newclosure - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(val) local function bar() return val @@ -4494,14 +4688,16 @@ local function foo(val) return function() return bar() end end )", - 2), + 2 + ), R"( NEWCLOSURE R1 P0 CAPTURE VAL R0 NEWCLOSURE R2 P1 CAPTURE VAL R1 RETURN R2 1 -)"); +)" + ); // we also allow recursive function captures to share the object, even when it's not top-level CHECK_EQ("\n" + compileFunction("function test() local function foo() return foo() end end", 1), R"( @@ -4512,22 +4708,28 @@ RETURN R0 0 // multi-level recursive capture where function isn't top-level fails however. // note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() local function bar() return function() return bar() end end end )", - 1), + 1 + ), R"( NEWCLOSURE R0 P0 CAPTURE UPVAL U0 RETURN R0 1 -)"); +)" + ); // top level upvalues inside loops should not be shared -- note that the bytecode below only uses NEWCLOSURE - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,10 do print(function() return i end) end @@ -4541,7 +4743,8 @@ for i=1,10 do print(function() return j end) end )", - 3), + 3 + ), R"( LOADN R2 1 LOADN R0 10 @@ -4571,7 +4774,8 @@ CAPTURE VAL R2 CALL R3 1 0 FORNLOOP R0 L4 L5: RETURN R0 0 -)"); +)" + ); } TEST_CASE("MutableGlobals") @@ -4756,7 +4960,8 @@ RETURN R0 1 TEST_CASE("TypeAssertion") { // validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( print(foo() :: typeof(error("compile time"))) )"), R"( @@ -4765,10 +4970,12 @@ GETIMPORT R1 3 [foo] CALL R1 0 1 CALL R0 1 0 RETURN R0 0 -)"); +)" + ); // note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( print(foo()) )"), R"( @@ -4777,13 +4984,15 @@ GETIMPORT R1 3 [foo] CALL R1 0 -1 CALL R0 -1 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("Arithmetics") { // basic arithmetics codegen with non-constants - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... return a + b, a - b, a / b, a * b, a % b, a ^ b )"), @@ -4796,11 +5005,13 @@ MUL R5 R0 R1 MOD R6 R0 R1 POW R7 R0 R1 RETURN R2 6 -)"); +)" + ); // basic arithmetics codegen with constants on the right side // note that we don't simplify these expressions as we don't know the type of a - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a = ... return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1 )"), @@ -4813,20 +5024,25 @@ MULK R4 R0 K0 [1] MODK R5 R0 K0 [1] POWK R6 R0 K0 [1] RETURN R1 6 -)"); +)" + ); } TEST_CASE("LoopUnrollBasic") { // forward loops - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,2 do t[i] = i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 2 LOADN R1 1 @@ -4834,17 +5050,22 @@ SETTABLEN R1 R0 1 LOADN R1 2 SETTABLEN R1 R0 2 RETURN R0 1 -)"); +)" + ); // backward loops - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=2,1,-1 do t[i] = i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R1 2 @@ -4852,17 +5073,22 @@ SETTABLEN R1 R0 2 LOADN R1 1 SETTABLEN R1 R0 1 RETURN R0 1 -)"); +)" + ); // loops with step that doesn't divide to-from - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,4,2 do t[i] = i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R1 1 @@ -4870,23 +5096,31 @@ SETTABLEN R1 R0 1 LOADN R1 3 SETTABLEN R1 R0 3 RETURN R0 1 -)"); +)" + ); // empty loops - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=2,1 do end )", - 0, 2), + 0, + 2 + ), R"( RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollNested") { // we can unroll nested loops just fine - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=0,1 do for j=0,1 do @@ -4894,7 +5128,9 @@ for i=0,1 do end end )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R1 0 @@ -4906,10 +5142,13 @@ SETTABLEN R1 R0 3 LOADN R1 0 SETTABLEN R1 R0 4 RETURN R0 0 -)"); +)" + ); // if the inner loop is too expensive, we won't unroll the outer loop though, but we'll still unroll the inner loop! - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=0,3 do for j=0,3 do @@ -4917,7 +5156,9 @@ for i=0,3 do end end )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R3 0 @@ -4942,10 +5183,13 @@ LOADN R5 0 SETTABLE R5 R0 R4 FORNLOOP R1 L0 L1: RETURN R0 0 -)"); +)" + ); // note, we sometimes can even unroll a loop with varying internal iterations - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=0,1 do for j=0,i do @@ -4953,7 +5197,9 @@ for i=0,1 do end end )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R1 0 @@ -4963,17 +5209,22 @@ SETTABLEN R1 R0 3 LOADN R1 0 SETTABLEN R1 R0 4 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollUnsupported") { // can't unroll loops with non-constant bounds - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=x,y,z do end )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R2 1 [x] GETIMPORT R0 3 [y] @@ -4981,14 +5232,19 @@ GETIMPORT R1 5 [z] FORNPREP R0 L1 L0: FORNLOOP R0 L0 L1: RETURN R0 0 -)"); +)" + ); // can't unroll loops with bounds where we can't compute trip count - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,1,0 do end )", - 0, 2), + 0, + 2 + ), R"( LOADN R2 1 LOADN R0 1 @@ -4996,14 +5252,19 @@ LOADN R1 0 FORNPREP R0 L1 L0: FORNLOOP R0 L0 L1: RETURN R0 0 -)"); +)" + ); // can't unroll loops with bounds that might be imprecise (non-integer) - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,2,0.1 do end )", - 0, 2), + 0, + 2 + ), R"( LOADN R2 1 LOADN R0 2 @@ -5011,14 +5272,19 @@ LOADK R1 K0 [0.10000000000000001] FORNPREP R0 L1 L0: FORNLOOP R0 L0 L1: RETURN R0 0 -)"); +)" + ); // can't unroll loops if the bounds are too large, as it might overflow trip count math - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=4294967295,4294967296 do end )", - 0, 2), + 0, + 2 + ), R"( LOADK R2 K0 [4294967295] LOADK R0 K1 [4294967296] @@ -5026,7 +5292,8 @@ LOADN R1 1 FORNPREP R0 L1 L0: FORNLOOP R0 L0 L1: RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollControlFlow") @@ -5037,14 +5304,18 @@ TEST_CASE("LoopUnrollControlFlow") }; // break jumps to the end - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,3 do if math.random() < 0.5 then break end end )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -5059,10 +5330,13 @@ CALL R0 0 1 LOADK R1 K3 [0.5] JUMPIFLT R0 R1 L0 L0: RETURN R0 0 -)"); +)" + ); // continue jumps to the next iteration - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,3 do if math.random() < 0.5 then continue @@ -5070,7 +5344,9 @@ for i=1,3 do print(i) end )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R0 2 [math.random] CALL R0 0 1 @@ -5094,10 +5370,13 @@ GETIMPORT R0 5 [print] LOADN R1 3 CALL R0 1 0 L2: RETURN R0 0 -)"); +)" + ); // continue needs to properly close upvalues - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,1 do local j = global(i) print(function() return j end) @@ -5107,7 +5386,9 @@ for i=1,1 do j += 1 end )", - 1, 2), + 1, + 2 + ), R"( GETIMPORT R0 1 [global] LOADN R1 1 @@ -5125,10 +5406,13 @@ RETURN R0 0 L0: ADDK R0 R0 K8 [1] CLOSEUPVALS R0 RETURN R0 0 -)"); +)" + ); // this weird contraption just disappears - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,1 do for j=1,1 do if i == 1 then @@ -5139,22 +5423,29 @@ for i=1,1 do end end )", - 0, 2), + 0, + 2 + ), R"( RETURN R0 0 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollNestedClosure") { // if the body has functions that refer to loop variables, we unroll the loop and use MOVE+CAPTURE for upvalues - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,2 do local x = function() return i end end )", - 1, 2), + 1, + 2 + ), R"( LOADN R1 1 NEWCLOSURE R0 P0 @@ -5163,7 +5454,8 @@ LOADN R1 2 NEWCLOSURE R0 P0 CAPTURE VAL R1 RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollCost") @@ -5174,14 +5466,18 @@ TEST_CASE("LoopUnrollCost") }; // loops with short body - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,10 do t[i] = i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 10 LOADN R1 1 @@ -5205,17 +5501,22 @@ SETTABLEN R1 R0 9 LOADN R1 10 SETTABLEN R1 R0 10 RETURN R0 1 -)"); +)" + ); // loops with body that's too long - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,100 do t[i] = i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R3 1 @@ -5225,17 +5526,22 @@ FORNPREP R1 L1 L0: SETTABLE R3 R0 R3 FORNLOOP R1 L0 L1: RETURN R0 1 -)"); +)" + ); // loops with body that's long but has a high boost factor due to constant folding - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,25 do t[i] = i * i * i end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 0 LOADN R1 1 @@ -5289,17 +5595,22 @@ SETTABLEN R1 R0 24 LOADN R1 15625 SETTABLEN R1 R0 25 RETURN R0 1 -)"); +)" + ); // loops with body that's long and doesn't have a high boost factor - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local t = {} for i=1,10 do t[i] = math.abs(math.sin(i)) end return t )", - 0, 2), + 0, + 2 + ), R"( NEWTABLE R0 0 10 LOADN R3 1 @@ -5316,19 +5627,24 @@ CALL R4 1 1 L2: SETTABLE R4 R0 R3 FORNLOOP R1 L0 L3: RETURN R0 1 -)"); +)" + ); } TEST_CASE("LoopUnrollMutable") { // can't unroll loops that mutate iteration variable - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( for i=1,3 do i = 3 print(i) -- should print 3 three times in a row end )", - 0, 2), + 0, + 2 + ), R"( LOADN R2 1 LOADN R0 3 @@ -5341,7 +5657,8 @@ MOVE R5 R3 CALL R4 1 0 FORNLOOP R0 L0 L1: RETURN R0 0 -)"); +)" + ); } TEST_CASE("LoopUnrollCostBuiltins") @@ -5352,14 +5669,18 @@ TEST_CASE("LoopUnrollCostBuiltins") }; // this loop uses builtins and is close to the cost budget so it's important that we model builtins as cheaper than regular calls - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function cipher(block, nonce) for i = 0,3 do block[i + 1] = bit32.band(bit32.rshift(nonce, i * 8), 0xff) end end )", - 0, 2), + 0, + 2 + ), R"( FASTCALL2K 39 R1 K0 L0 [0] MOVE R4 R1 @@ -5402,10 +5723,13 @@ GETIMPORT R2 6 [bit32.band] CALL R2 2 1 L7: SETTABLEN R2 R0 4 RETURN R0 0 -)"); +)" + ); // note that if we break compiler's ability to reason about bit32 builtin the loop is no longer unrolled as it's too expensive - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( bit32 = {} function cipher(block, nonce) @@ -5414,7 +5738,9 @@ function cipher(block, nonce) end end )", - 0, 2), + 0, + 2 + ), R"( LOADN R4 0 LOADN R2 3 @@ -5433,17 +5759,22 @@ CALL R6 2 1 SETTABLE R6 R0 R5 FORNLOOP R2 L0 L1: RETURN R0 0 -)"); +)" + ); // additionally, if we pass too many constants the builtin stops being cheap because of argument setup - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function cipher(block, nonce) for i = 0,3 do block[i + 1] = bit32.band(bit32.rshift(nonce, i * 8), 0xff, 0xff, 0xff, 0xff, 0xff) end end )", - 0, 2), + 0, + 2 + ), R"( LOADN R4 0 LOADN R2 3 @@ -5466,13 +5797,16 @@ CALL R6 6 1 L2: SETTABLE R6 R0 R5 FORNLOOP R2 L0 L3: RETURN R0 0 -)"); +)" + ); } TEST_CASE("InlineBasic") { // inline function that returns a constant - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() return 42 end @@ -5480,15 +5814,20 @@ end local x = foo() return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); // inline function that returns the argument - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -5496,15 +5835,20 @@ end local x = foo(42) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); // inline function that returns one of the two arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b, c) if a then return b @@ -5516,7 +5860,9 @@ end local x = foo(true, math.random(), 5) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETIMPORT R2 3 [math.random] @@ -5524,10 +5870,13 @@ CALL R2 0 1 MOVE R1 R2 RETURN R1 1 RETURN R1 1 -)"); +)" + ); // inline function that returns one of the two arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b, c) if a then return b @@ -5539,7 +5888,9 @@ end local x = foo(true, 5, math.random()) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETIMPORT R2 3 [math.random] @@ -5547,13 +5898,16 @@ CALL R2 0 1 LOADN R1 5 RETURN R1 1 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineProhibited") { // we can't inline variadic functions - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(...) return 42 end @@ -5561,16 +5915,21 @@ end local x = foo() return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 CALL R1 0 1 RETURN R1 1 -)"); +)" + ); // we can't inline any functions in modules with getfenv/setfenv - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() return 42 end @@ -5579,7 +5938,9 @@ local x = foo() getfenv() return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 @@ -5587,7 +5948,8 @@ CALL R1 0 1 GETIMPORT R2 2 [getfenv] CALL R2 0 0 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineProhibitedRecursion") @@ -5596,14 +5958,18 @@ TEST_CASE("InlineProhibitedRecursion") // this is actually profitable in certain cases, but it complicates the compiler as it means a local has multiple registers/values // in this example, inlining is blocked because we're compiling fact() and we don't yet have the cost model / profitability data for fact() - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function fact(n) return if n <= 1 then 1 else fact(n-1)*n end return fact )", - 0, 2), + 0, + 2 + ), R"( LOADN R2 1 JUMPIFNOTLE R0 R2 L0 @@ -5614,10 +5980,13 @@ SUBK R3 R0 K0 [1] CALL R2 1 1 MUL R1 R2 R0 RETURN R1 1 -)"); +)" + ); // in this example, inlining of fact() succeeds, but the nested call to fact() fails since fact is already on the inline stack - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function fact(n) return if n <= 1 then 1 else fact(n-1)*n end @@ -5629,7 +5998,9 @@ end return factsafe )", - 1, 2), + 1, + 2 + ), R"( LOADN R3 1 JUMPIFLE R3 R0 L0 @@ -5647,13 +6018,16 @@ SUBK R3 R0 K2 [1] CALL R2 1 1 MUL R1 R2 R0 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineNestedLoops") { // functions with basic loops get inlined - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(t) for i=1,3 do t[i] = i @@ -5664,7 +6038,9 @@ end local x = foo({}) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] NEWTABLE R2 0 0 @@ -5676,10 +6052,13 @@ LOADN R3 3 SETTABLEN R3 R2 3 MOVE R1 R2 RETURN R1 1 -)"); +)" + ); // we can even unroll the loops based on inline argument - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(t, n) for i=1, n do t[i] = i @@ -5690,7 +6069,9 @@ end local x = foo({}, 3) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] NEWTABLE R2 0 0 @@ -5702,13 +6083,16 @@ LOADN R3 3 SETTABLEN R3 R2 3 MOVE R1 R2 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineNestedClosures") { // we can inline functions that contain/return functions - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(x) return function(y) return x + y end end @@ -5716,7 +6100,9 @@ end local x = foo(1)(2) return x )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R2 1 @@ -5725,13 +6111,16 @@ CAPTURE VAL R2 LOADN R2 2 CALL R1 1 1 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineMutate") { // if the argument is mutated, it gets a register even if the value is constant - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) a = a or 5 return a @@ -5740,17 +6129,22 @@ end local x = foo(42) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R2 42 ORK R2 R2 K1 [5] MOVE R1 R2 RETURN R1 1 -)"); +)" + ); // if the argument is a local, it can be used directly - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -5759,16 +6153,21 @@ local x = ... local y = foo(x) return y )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 MOVE R2 R1 RETURN R2 1 -)"); +)" + ); // ... but if it's mutated, we move it in case it is mutated through a capture during the inlined function - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -5778,7 +6177,9 @@ x = nil local y = foo(x) return y )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 @@ -5786,10 +6187,13 @@ LOADNIL R1 MOVE R3 R1 MOVE R2 R3 RETURN R2 1 -)"); +)" + ); // we also don't inline functions if they have been assigned to - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -5799,20 +6203,25 @@ foo = foo local x = foo(42) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 LOADN R2 42 CALL R1 1 1 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineUpval") { // if the argument is an upvalue, we naturally need to copy it to a local - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -5824,15 +6233,20 @@ function bar() return x end )", - 1, 2), + 1, + 2 + ), R"( GETUPVAL R1 0 MOVE R0 R1 RETURN R0 1 -)"); +)" + ); // if the function uses an upvalue it's more complicated, because the lexical upvalue may become a local - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local b = ... local function foo(a) @@ -5842,7 +6256,9 @@ end local x = foo(42) return x )", - 1, 2), + 1, + 2 + ), R"( GETVARARGS R0 1 DUPCLOSURE R1 K0 ['foo'] @@ -5850,10 +6266,13 @@ CAPTURE VAL R0 LOADN R3 42 ADD R2 R3 R0 RETURN R2 1 -)"); +)" + ); // sometimes the lexical upvalue is deep enough that it's still an upvalue though - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local b = ... function bar() @@ -5865,7 +6284,9 @@ function bar() return x end )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] CAPTURE UPVAL U0 @@ -5873,13 +6294,16 @@ LOADN R2 42 GETUPVAL R3 0 ADD R1 R2 R3 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineCapture") { // if the argument is captured by a nested closure, normally we can rely on capture by value - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return function() return a end end @@ -5888,17 +6312,22 @@ local x = ... local y = foo(x) return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 NEWCLOSURE R2 P1 CAPTURE VAL R1 RETURN R2 1 -)"); +)" + ); // if the argument is a constant, we move it to a register so that capture by value can happen - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return function() return a end end @@ -5906,17 +6335,22 @@ end local y = foo(42) return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R2 42 NEWCLOSURE R1 P1 CAPTURE VAL R2 RETURN R1 1 -)"); +)" + ); // if the argument is an externally mutated variable, we copy it to an argument and capture it by value - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return function() return a end end @@ -5925,7 +6359,9 @@ local x x = 42 local y = foo(x) return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R1 @@ -5934,10 +6370,13 @@ MOVE R3 R1 NEWCLOSURE R2 P1 CAPTURE VAL R3 RETURN R2 1 -)"); +)" + ); // finally, if the argument is mutated internally, we must capture it by reference and close the upvalue - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) a = a or 42 return function() return a end @@ -5946,7 +6385,9 @@ end local y = foo() return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R2 @@ -5955,10 +6396,13 @@ NEWCLOSURE R1 P1 CAPTURE REF R2 CLOSEUPVALS R2 RETURN R1 1 -)"); +)" + ); // note that capture might need to be performed during the fallthrough block - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) a = a or 42 print(function() return a end) @@ -5968,7 +6412,9 @@ local x = ... local y = foo(x) return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 @@ -5981,12 +6427,15 @@ CALL R4 1 0 LOADNIL R2 CLOSEUPVALS R3 RETURN R2 1 -)"); +)" + ); // note that mutation and capture might be inside internal control flow // TODO: this has an oddly redundant CLOSEUPVALS after JUMP; it's not due to inlining, and is an artifact of how StatBlock/StatReturn interact // fixing this would reduce the number of redundant CLOSEUPVALS a bit but it only affects bytecode size as these instructions aren't executed - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) if not a then local b b = 42 @@ -5998,7 +6447,9 @@ local x = ... local y = foo(x) return y, x )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 @@ -6014,29 +6465,37 @@ L0: LOADNIL R2 L1: MOVE R3 R2 MOVE R4 R1 RETURN R3 2 -)"); +)" + ); } TEST_CASE("InlineFallthrough") { // if the function doesn't return, we still fill the results with nil - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() end local a, b = foo() return a, b )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R1 LOADNIL R2 RETURN R1 2 -)"); +)" + ); // this happens even if the function returns conditionally - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) if a then return 42 end end @@ -6044,29 +6503,37 @@ end local a, b = foo(false) return a, b )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R1 LOADNIL R2 RETURN R1 2 -)"); +)" + ); // note though that we can't inline a function like this in multret context // this is because we don't have a SETTOP instruction - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() end return foo() )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 CALL R1 0 -1 RETURN R1 -1 -)"); +)" + ); } TEST_CASE("InlineArgMismatch") @@ -6074,7 +6541,9 @@ TEST_CASE("InlineArgMismatch") // when inlining a function, we must respect all the usual rules // caller might not have enough arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -6082,15 +6551,20 @@ end local x = foo() return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R1 RETURN R1 1 -)"); +)" + ); // caller might be using multret for arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6098,7 +6572,9 @@ end local x = foo(math.modf(1.5)) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADK R3 K1 [1.5] @@ -6107,10 +6583,13 @@ GETIMPORT R2 4 [math.modf] CALL R2 1 2 L0: ADD R1 R2 R3 RETURN R1 1 -)"); +)" + ); // caller might be using varargs for arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6118,16 +6597,21 @@ end local x = foo(...) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R2 2 ADD R1 R2 R3 RETURN R1 1 -)"); +)" + ); // caller might have too many arguments, but we still need to compute them for side effects - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -6135,17 +6619,22 @@ end local x = foo(42, print()) return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETIMPORT R2 2 [print] CALL R2 0 1 LOADN R1 42 RETURN R1 1 -)"); +)" + ); // caller might not have enough arguments, and the arg might be mutated so it needs a register - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) a = 42 return a @@ -6154,20 +6643,25 @@ end local x = foo() return x )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R2 LOADN R2 42 MOVE R1 R2 RETURN R1 1 -)"); +)" + ); } TEST_CASE("InlineMultiple") { // we call this with a different set of variable/constant args - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6179,7 +6673,9 @@ local c = foo(1, 2) local d = foo(x, y) return a, b, c, d )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 2 @@ -6189,13 +6685,16 @@ ADD R4 R5 R1 LOADN R5 3 ADD R6 R1 R2 RETURN R3 4 -)"); +)" + ); } TEST_CASE("InlineChain") { // inline a chain of functions - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6210,7 +6709,9 @@ end return (baz()) )", - 3, 2), + 3, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] DUPCLOSURE R1 K1 ['bar'] @@ -6219,7 +6720,8 @@ LOADN R4 43 LOADN R5 41 MUL R3 R4 R5 RETURN R3 1 -)"); +)" + ); } TEST_CASE("InlineThresholds") @@ -6231,39 +6733,51 @@ TEST_CASE("InlineThresholds") }; // this function has enormous register pressure (50 regs) so we choose not to inline it - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() return {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} end return (foo()) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 CALL R1 0 1 RETURN R1 1 -)"); +)" + ); // this function has less register pressure but a large cost - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo() return {},{},{},{},{} end return (foo()) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 CALL R1 0 1 RETURN R1 1 -)"); +)" + ); // this chain of function is of length 3 but our limit in this test is 2, so we call foo twice - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6278,7 +6792,9 @@ end return (baz()) )", - 3, 2), + 3, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] DUPCLOSURE R1 K1 ['bar'] @@ -6293,18 +6809,23 @@ LOADN R7 -1 CALL R5 2 1 MUL R3 R4 R5 RETURN R3 1 -)"); +)" + ); } TEST_CASE("InlineIIFE") { // IIFE with arguments - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function choose(a, b, c) return ((function(a, b, c) if a then return b else return c end end)(a, b, c)) end )", - 1, 2), + 1, + 2 + ), R"( JUMPIFNOT R0 L0 MOVE R3 R1 @@ -6312,15 +6833,20 @@ RETURN R3 1 L0: MOVE R3 R2 RETURN R3 1 RETURN R3 1 -)"); +)" + ); // IIFE with upvalues - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function choose(a, b, c) return ((function() if a then return b else return c end end)()) end )", - 1, 2), + 1, + 2 + ), R"( JUMPIFNOT R0 L0 MOVE R3 R1 @@ -6328,28 +6854,36 @@ RETURN R3 1 L0: MOVE R3 R2 RETURN R3 1 RETURN R3 1 -)"); +)" + ); } TEST_CASE("InlineRecurseArguments") { // the example looks silly but we preserve it verbatim as it was found by fuzzer for a previous version of the compiler - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) end foo(foo(foo,foo(foo,foo))[foo]) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADNIL R3 LOADNIL R2 GETTABLE R1 R2 R0 RETURN R0 0 -)"); +)" + ); // verify that invocations of the inlined function in any position for computing the arguments to itself compile - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6358,7 +6892,9 @@ local x, y, z = ... return foo(foo(x, y), foo(z, 1)) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 3 @@ -6366,11 +6902,14 @@ ADD R5 R1 R2 ADDK R6 R3 K1 [1] ADD R4 R5 R6 RETURN R4 1 -)"); +)" + ); // verify that invocations of the inlined function in any position for computing the arguments to itself compile, including constants and locals // note that foo(k1, k2) doesn't get constant folded, so there's still actual math emitted for some of the calls below - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a, b) return a + b end @@ -6388,7 +6927,9 @@ return foo(x+0, foo(y, z+0)), foo(1, foo(x, y)) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 3 @@ -6418,12 +6959,15 @@ ADD R13 R1 R2 LOADN R14 1 ADD R12 R14 R13 RETURN R4 9 -)"); +)" + ); } TEST_CASE("InlineFastCallK") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function set(l0) rawset({}, l0) end @@ -6431,7 +6975,9 @@ end set(false) set({}) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['set'] NEWTABLE R2 0 0 @@ -6446,12 +6992,15 @@ MOVE R4 R1 GETIMPORT R2 3 [rawset] CALL R2 2 0 L1: RETURN R0 0 -)"); +)" + ); } TEST_CASE("InlineExprIndexK") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local _ = function(l0) local _ = nil while _(_)[_] do @@ -6472,7 +7021,9 @@ end end end )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 [] L0: LOADNIL R4 @@ -6507,13 +7058,16 @@ RETURN R2 1 LOADB R2 1 RETURN R2 1 L3: RETURN R0 0 -)"); +)" + ); } TEST_CASE("InlineHiddenMutation") { // when the argument is assigned inside the function, we can't reuse the local - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) a = 42 return a @@ -6523,7 +7077,9 @@ local x = ... local y = foo(x :: number) return y )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 @@ -6531,10 +7087,13 @@ MOVE R3 R1 LOADN R3 42 MOVE R2 R3 RETURN R2 1 -)"); +)" + ); // and neither can we do that when it's assigned outside the function - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) mutator() return a @@ -6546,7 +7105,9 @@ mutator = function() x = 42 end local y = foo(x :: number) return y )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] GETVARARGS R1 1 @@ -6559,45 +7120,58 @@ CALL R4 0 0 MOVE R2 R3 CLOSEUPVALS R1 RETURN R2 1 -)"); +)" + ); } TEST_CASE("InlineMultret") { // inlining a function in multret context is prohibited since we can't adjust L->top outside of CALL/GETVARARGS - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a() end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 LOADN R2 42 CALL R1 1 -1 RETURN R1 -1 -)"); +)" + ); // however, if we can deduce statically that a function always returns a single value, the inlining will work - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); // this analysis will also propagate through other functions - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end @@ -6608,23 +7182,30 @@ end return bar(42) )", - 2, 2), + 2, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] DUPCLOSURE R1 K1 ['bar'] LOADN R2 42 RETURN R2 1 -)"); +)" + ); // we currently don't do this analysis fully for recursive functions since they can't be inlined anyway - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return foo(a) end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] CAPTURE VAL R0 @@ -6632,48 +7213,59 @@ MOVE R1 R0 LOADN R2 42 CALL R1 1 -1 RETURN R1 -1 -)"); +)" + ); // we do this for builtins though as we assume getfenv is not used or is not changing arity - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return math.abs(a) end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); } TEST_CASE("ReturnConsecutive") { // we can return a single local directly - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x = ... return x )"), R"( GETVARARGS R0 1 RETURN R0 1 -)"); +)" + ); // or multiple, when they are allocated in consecutive registers - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return x, y )"), R"( GETVARARGS R0 2 RETURN R0 2 -)"); +)" + ); // but not if it's an expression - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return x, y + 1 )"), @@ -6682,10 +7274,12 @@ GETVARARGS R0 2 MOVE R2 R0 ADDK R3 R1 K0 [1] RETURN R2 2 -)"); +)" + ); // or a local with wrong register number - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return y, x )"), @@ -6694,48 +7288,60 @@ GETVARARGS R0 2 MOVE R2 R1 MOVE R3 R0 RETURN R2 2 -)"); +)" + ); // also double check the optimization doesn't trip on no-argument return (these are rare) - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( return )"), R"( RETURN R0 0 -)"); +)" + ); // this optimization also works in presence of group / type casts - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return (x), y :: number )"), R"( GETVARARGS R0 2 RETURN R0 2 -)"); +)" + ); } TEST_CASE("OptimizationLevel") { // at optimization level 1, no inlining is performed - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end return foo(42) )", - 1, 1), + 1, + 1 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 LOADN R2 42 CALL R1 1 -1 RETURN R1 -1 -)"); +)" + ); // you can override the level from 1 to 2 to force it - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( --!optimize 2 local function foo(a) return a @@ -6743,30 +7349,40 @@ end return foo(42) )", - 1, 1), + 1, + 1 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); // you can also override it externally - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function foo(a) return a end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] LOADN R1 42 RETURN R1 1 -)"); +)" + ); // ... after which you can downgrade it back via hot comment - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( --!optimize 1 local function foo(a) return a @@ -6774,19 +7390,24 @@ end return foo(42) )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['foo'] MOVE R1 R0 LOADN R2 42 CALL R1 1 -1 RETURN R1 -1 -)"); +)" + ); } TEST_CASE("BuiltinFolding") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.abs(-42), math.acos(1), @@ -6841,7 +7462,9 @@ return typeof(nil), (type("fin")) )", - 0, 2), + 0, + 2 + ), R"( LOADN R0 42 LOADN R1 0 @@ -6896,12 +7519,15 @@ LOADN R49 2 LOADK R50 K3 ['nil'] LOADK R51 K4 ['string'] RETURN R0 52 -)"); +)" + ); } TEST_CASE("BuiltinFoldingProhibited") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.abs(), math.max(1, true), @@ -6914,7 +7540,9 @@ return bit32.btest(1, true), math.min(1, true) )", - 0, 2), + 0, + 2 + ), R"( FASTCALL 2 L0 GETIMPORT R0 2 [math.abs] @@ -6966,55 +7594,18 @@ LOADK R11 K3 [true] GETIMPORT R9 26 [math.min] CALL R9 2 1 L9: RETURN R0 10 -)"); +)" + ); } TEST_CASE("BuiltinFoldingProhibitedCoverage") { const char* builtins[] = { - "math.abs", - "math.acos", - "math.asin", - "math.atan2", - "math.atan", - "math.ceil", - "math.cosh", - "math.cos", - "math.deg", - "math.exp", - "math.floor", - "math.fmod", - "math.ldexp", - "math.log10", - "math.log", - "math.max", - "math.min", - "math.pow", - "math.rad", - "math.sinh", - "math.sin", - "math.sqrt", - "math.tanh", - "math.tan", - "bit32.arshift", - "bit32.band", - "bit32.bnot", - "bit32.bor", - "bit32.bxor", - "bit32.btest", - "bit32.extract", - "bit32.lrotate", - "bit32.lshift", - "bit32.replace", - "bit32.rrotate", - "bit32.rshift", - "type", - "string.byte", - "string.len", - "typeof", - "math.clamp", - "math.sign", - "math.round", + "math.abs", "math.acos", "math.asin", "math.atan2", "math.atan", "math.ceil", "math.cosh", "math.cos", "math.deg", + "math.exp", "math.floor", "math.fmod", "math.ldexp", "math.log10", "math.log", "math.max", "math.min", "math.pow", + "math.rad", "math.sinh", "math.sin", "math.sqrt", "math.tanh", "math.tan", "bit32.arshift", "bit32.band", "bit32.bnot", + "bit32.bor", "bit32.bxor", "bit32.btest", "bit32.extract", "bit32.lrotate", "bit32.lshift", "bit32.replace", "bit32.rrotate", "bit32.rshift", + "type", "string.byte", "string.len", "typeof", "math.clamp", "math.sign", "math.round", }; for (const char* func : builtins) @@ -7031,7 +7622,9 @@ TEST_CASE("BuiltinFoldingProhibitedCoverage") TEST_CASE("BuiltinFoldingMultret") { - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000 local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000 @@ -7046,7 +7639,9 @@ local function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes return NoLanes end )", - 0, 2), + 0, + 2 + ), R"( GETTABLEKS R2 R0 K0 ['pendingLanes'] FASTCALL2K 29 R2 K1 L0 [3221225471] @@ -7065,23 +7660,30 @@ LOADK R2 K6 [1073741824] RETURN R2 1 L3: LOADN R2 0 RETURN R2 1 -)"); +)" + ); // Note: similarly, here we should have folded the return value but haven't because it's the last call in the sequence - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.abs(-42) )", - 0, 2), + 0, + 2 + ), R"( LOADN R0 42 RETURN R0 1 -)"); +)" + ); } TEST_CASE("LocalReassign") { // locals can be re-assigned and the register gets reused - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local function test(a, b) local c = a return c + b @@ -7090,10 +7692,12 @@ end R"( ADD R2 R0 R1 RETURN R2 1 -)"); +)" + ); // this works if the expression is using type casts or grouping - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local function test(a, b) local c = (a :: number) return c + b @@ -7102,10 +7706,12 @@ end R"( ADD R2 R0 R1 RETURN R2 1 -)"); +)" + ); // the optimization requires that neither local is mutated - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local function test(a, b) local c = a c += 0 @@ -7121,10 +7727,12 @@ MOVE R3 R1 ADDK R1 R1 K0 [0] ADD R4 R2 R3 RETURN R4 1 -)"); +)" + ); // sanity check for two values - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local function test(a, b) local c = a local d = b @@ -7134,10 +7742,12 @@ end R"( ADD R2 R0 R1 RETURN R2 1 -)"); +)" + ); // note: we currently only support this for single assignments - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local function test(a, b) local c, d = a, b return c + d @@ -7148,29 +7758,35 @@ MOVE R2 R0 MOVE R3 R1 ADD R4 R2 R3 RETURN R4 1 -)"); +)" + ); // of course, captures capture the original register as well (by value since it's immutable) - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function test(a, b) local c = a local d = b return function() return c + d end end )", - 1), + 1 + ), R"( NEWCLOSURE R2 P0 CAPTURE VAL R0 CAPTURE VAL R1 RETURN R2 1 -)"); +)" + ); } TEST_CASE("MultipleAssignments") { // order of assignments is left to right - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b a, b = f(1), f(2) )"), @@ -7186,10 +7802,12 @@ LOADN R3 2 CALL R2 1 1 MOVE R1 R2 RETURN R0 0 -)"); +)" + ); // this includes table assignments - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t t[1], t[2] = 3, 4 )"), @@ -7201,10 +7819,12 @@ LOADN R3 4 SETTABLEN R2 R0 1 SETTABLEN R3 R1 2 RETURN R0 0 -)"); +)" + ); // semantically, we evaluate the right hand side first; this allows us to e.g swap elements in a table easily - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local t = ... t[1], t[2] = t[2], t[1] )"), @@ -7215,14 +7835,16 @@ GETTABLEN R2 R0 1 SETTABLEN R1 R0 1 SETTABLEN R2 R0 2 RETURN R0 0 -)"); +)" + ); // however, we need to optimize local assignments; to do this well, we need to handle assignment conflicts // let's first go through a few cases where there are no conflicts: // when multiple assignments have no conflicts (all local vars are read after being assigned), codegen is the same as a series of single // assignments - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local xm1, x, xp1, xi = ... xm1,x,xp1,xi = x,xp1,xp1+1,xi-1 @@ -7234,10 +7856,12 @@ MOVE R1 R2 ADDK R2 R2 K0 [1] SUBK R3 R3 K0 [1] RETURN R0 0 -)"); +)" + ); // similar example to above from a more complex case - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c, d, e, f, g, h, t1, t2 = ... h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 @@ -7253,11 +7877,13 @@ MOVE R2 R1 MOVE R1 R0 ADD R0 R8 R9 RETURN R0 0 -)"); +)" + ); // when locals have a conflict, we assign temporaries instead of locals, and at the end copy the values back // the basic example of this is a swap/rotate - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... a, b = b, a )"), @@ -7267,9 +7893,11 @@ MOVE R2 R1 MOVE R1 R0 MOVE R0 R2 RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c = ... a, b, c = c, a, b )"), @@ -7281,9 +7909,11 @@ MOVE R2 R1 MOVE R0 R3 MOVE R1 R4 RETURN R0 0 -)"); +)" + ); - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c = ... a, b, c = b, c, a )"), @@ -7294,10 +7924,12 @@ MOVE R1 R2 MOVE R2 R0 MOVE R0 R3 RETURN R0 0 -)"); +)" + ); // multiple assignments with multcall handling - foo() evalutes to temporary registers and they are copied out to target - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c, d = ... a, b, c, d = 1, foo() )"), @@ -7310,10 +7942,12 @@ MOVE R1 R4 MOVE R2 R5 MOVE R3 R6 RETURN R0 0 -)"); +)" + ); // note that during this we still need to handle local reassignment, eg when table assignments are performed - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c, d = ... a, b[a], c[d], d = 1, foo() )"), @@ -7327,11 +7961,13 @@ SETTABLE R7 R2 R3 MOVE R0 R4 MOVE R3 R8 RETURN R0 0 -)"); +)" + ); // multiple assignments with multcall handling - foo evaluates to a single argument so all remaining locals are assigned to nil // note that here we don't assign the locals directly, as this case is very rare so we use the similar code path as above - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b, c, d = ... a, b, c, d = 1, foo )"), @@ -7345,10 +7981,12 @@ MOVE R1 R4 MOVE R2 R5 MOVE R3 R6 RETURN R0 0 -)"); +)" + ); // note that we also try to use locals as a source of assignment directly when assigning fields; this works using old local value when possible - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... a[1], a[2] = b, b + 1 )"), @@ -7358,10 +7996,12 @@ ADDK R2 R1 K0 [1] SETTABLEN R1 R0 1 SETTABLEN R2 R0 2 RETURN R0 0 -)"); +)" + ); // ... of course if the local is reassigned, we defer the assignment until later - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... b, a[1] = 42, b )"), @@ -7371,10 +8011,12 @@ LOADN R2 42 SETTABLEN R1 R0 1 MOVE R1 R2 RETURN R0 0 -)"); +)" + ); // when there are more expressions when values, we evalute them for side effects, but they also participate in conflict handling - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... a, b = 1, 2, a + b )"), @@ -7386,10 +8028,12 @@ ADD R4 R0 R1 MOVE R0 R2 MOVE R1 R3 RETURN R0 0 -)"); +)" + ); // because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local a, b = ... a[1], b = b, b + 1 )"), @@ -7399,14 +8043,16 @@ ADDK R2 R1 K0 [1] SETTABLEN R1 R0 1 MOVE R1 R2 RETURN R0 0 -)"); +)" + ); } TEST_CASE("BuiltinExtractK") { // below, K0 refers to a packed f+w constant for bit32.extractk builtin // K1 and K2 refer to 1 and 3 and are only used during fallback path - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local v = ... return bit32.extract(v, 1, 3) @@ -7420,7 +8066,8 @@ LOADK R4 K2 [3] GETIMPORT R1 5 [bit32.extract] CALL R1 3 -1 L0: RETURN R1 -1 -)"); +)" + ); } TEST_CASE("SkipSelfAssignment") @@ -7451,7 +8098,8 @@ RETURN R0 0 TEST_CASE("ElideJumpAfterIf") { // break refers to outer loop => we can elide unconditional branches - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local foo, bar = ... repeat if foo then break @@ -7471,10 +8119,12 @@ CALL R2 1 0 JUMPIFEQ R0 R1 L2 JUMPBACK L0 L2: RETURN R0 0 -)"); +)" + ); // break refers to inner loop => branches remain - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local foo, bar = ... repeat if foo then while true do break end @@ -7498,16 +8148,21 @@ CALL R2 1 0 JUMPIFEQ R0 R1 L3 JUMPBACK L0 L3: RETURN R0 0 -)"); +)" + ); } TEST_CASE("BuiltinArity") { // by default we can't assume that we know parameter/result count for builtins as they can be overridden at runtime - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.abs(unknown()) )", - 0, 1), + 0, + 1 + ), R"( GETIMPORT R1 1 [unknown] CALL R1 0 -1 @@ -7515,14 +8170,19 @@ FASTCALL 2 L0 GETIMPORT R0 4 [math.abs] CALL R0 -1 -1 L0: RETURN R0 -1 -)"); +)" + ); // however, when using optimization level 2, we assume compile time knowledge about builtin behavior even if we can't deoptimize that with fenv // in the test case below, this allows us to synthesize a more efficient FASTCALL1 (and use a fixed-return call to unknown) - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.abs(unknown()) )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R1 1 [unknown] CALL R1 0 1 @@ -7530,13 +8190,18 @@ FASTCALL1 2 R1 L0 GETIMPORT R0 4 [math.abs] CALL R0 1 1 L0: RETURN R0 1 -)"); +)" + ); // some builtins are variadic, and as such they can't use fixed-length fastcall variants - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return math.max(0, unknown()) )", - 0, 2), + 0, + 2 + ), R"( LOADN R1 0 GETIMPORT R2 1 [unknown] @@ -7545,14 +8210,19 @@ FASTCALL 18 L0 GETIMPORT R0 4 [math.max] CALL R0 -1 1 L0: RETURN R0 1 -)"); +)" + ); // some builtins are not variadic but don't have a fixed number of arguments; we currently don't optimize this although we might start to in the // future - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return bit32.extract(0, 1, unknown()) )", - 0, 2), + 0, + 2 + ), R"( LOADN R1 0 LOADN R2 1 @@ -7562,14 +8232,19 @@ FASTCALL 34 L0 GETIMPORT R0 4 [bit32.extract] CALL R0 -1 1 L0: RETURN R0 1 -)"); +)" + ); // some builtins are not variadic and have a fixed number of arguments but are not none-safe, meaning that we can't replace calls that may // return none with calls that will return nil - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( return type(unknown()) )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R1 1 [unknown] CALL R1 0 -1 @@ -7577,17 +8252,22 @@ FASTCALL 40 L0 GETIMPORT R0 3 [type] CALL R0 -1 1 L0: RETURN R0 1 -)"); +)" + ); // importantly, this optimization also helps us get around the multret inlining restriction for builtin wrappers - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function new() return setmetatable({}, MT) end return new() )", - 1, 2), + 1, + 2 + ), R"( DUPCLOSURE R0 K0 ['new'] NEWTABLE R2 0 0 @@ -7596,16 +8276,21 @@ FASTCALL2 61 R2 R3 L0 GETIMPORT R1 4 [setmetatable] CALL R1 2 1 L0: RETURN R1 1 -)"); +)" + ); // note that the results of this optimization are benign in fixed-arg contexts which dampens the effect of fenv substitutions on correctness in // practice - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local x = ... local y, z = type(x) return type(y, z) )", - 0, 2), + 0, + 2 + ), R"( GETVARARGS R0 1 FASTCALL1 40 R0 L0 @@ -7618,12 +8303,14 @@ MOVE R5 R2 GETIMPORT R3 1 [type] CALL R3 2 1 L1: RETURN R3 1 -)"); +)" + ); } TEST_CASE("EncodedTypeTable") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) end @@ -7653,9 +8340,11 @@ myfunc('test') 2: function(string, number) 3: function(any, number) 5: function(function) -)"); +)" + ); - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( local Str = { a = 1 } @@ -7669,12 +8358,14 @@ Str:test(234) )"), R"( 0: function(table, number) -)"); +)" + ); } TEST_CASE("HostTypesAreUserdata") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) end @@ -7695,12 +8386,14 @@ end 1: function(userdata, number) 2: function(string, string) 3: function(any, userdata) -)"); +)" + ); } TEST_CASE("HostTypesVector") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( function myfunc(test: Instance, pos: Vector3) end @@ -7718,12 +8411,14 @@ end 0: function(userdata, vector) 1: function(userdata, any) 2: function(userdata, number) -)"); +)" + ); } TEST_CASE("TypeAliasScoping") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( do type Part = number end @@ -7752,12 +8447,14 @@ type Instance = string 1: function(number, number) 2: function(number, number) 3: function(string, number) -)"); +)" + ); } TEST_CASE("TypeAliasResolve") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( type Foo1 = number type Foo2 = { number } type Foo3 = Part @@ -7774,12 +8471,14 @@ end R"( 0: function(number, table, userdata, any, any) 1: function(number, any) -)"); +)" + ); } TEST_CASE("TypeUnionIntersection") { - CHECK_EQ("\n" + compileTypeTable(R"( + CHECK_EQ( + "\n" + compileTypeTable(R"( function myfunc(test: string | nil, foo: nil) end @@ -7797,64 +8496,84 @@ end 1: function(any, nil) 2: function(any, nil) 3: function(any, nil) -)"); +)" + ); } TEST_CASE("BuiltinFoldMathK") { // we can fold math.pi at optimization level 2 - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() return math.pi * 2 end )", - 0, 2), + 0, + 2 + ), R"( LOADK R0 K0 [6.2831853071795862] RETURN R0 1 -)"); +)" + ); // we don't do this at optimization level 1 because it may interfere with environment substitution - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() return math.pi * 2 end )", - 0, 1), + 0, + 1 + ), R"( GETIMPORT R1 3 [math.pi] MULK R0 R1 K0 [2] RETURN R0 1 -)"); +)" + ); // we also don't do it if math global is assigned to - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( function test() return math.pi * 2 end math = { pi = 4 } )", - 0, 2), + 0, + 2 + ), R"( GETGLOBAL R2 K1 ['math'] GETTABLEKS R1 R2 K2 ['pi'] MULK R0 R1 K0 [2] RETURN R0 1 -)"); +)" + ); } TEST_CASE("NoBuiltinFoldFenv") { // builtin folding is disabled when getfenv/setfenv is used in the module - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( getfenv() function test() return math.pi, math.sin(0) end )", - 0, 2), + 0, + 2 + ), R"( GETIMPORT R0 2 [math.pi] LOADN R2 0 @@ -7862,13 +8581,15 @@ FASTCALL1 24 R2 L0 GETIMPORT R1 4 [math.sin] CALL R1 1 1 L0: RETURN R0 2 -)"); +)" + ); } TEST_CASE("IfThenElseAndOr") { // if v then v else k can be optimized to ORK - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x = ... return if x then x else 0 )"), @@ -7876,10 +8597,12 @@ return if x then x else 0 GETVARARGS R0 1 ORK R1 R0 K0 [0] RETURN R1 1 -)"); +)" + ); // if v then v else l can be optimized to OR - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return if x then x else y )"), @@ -7887,10 +8610,12 @@ return if x then x else y GETVARARGS R0 2 OR R2 R0 R1 RETURN R2 1 -)"); +)" + ); // this also works in presence of type casts - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return if x then x :: number else 0 )"), @@ -7898,10 +8623,12 @@ return if x then x :: number else 0 GETVARARGS R0 2 ORK R2 R0 K0 [0] RETURN R2 1 -)"); +)" + ); // if v then k else v can be optimized to ANDK - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x = ... return if x then 0 else x )"), @@ -7909,10 +8636,12 @@ return if x then 0 else x GETVARARGS R0 1 ANDK R1 R0 K0 [0] RETURN R1 1 -)"); +)" + ); // if v then l else v can be optimized to AND - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return if x then y else x )"), @@ -7920,10 +8649,12 @@ return if x then y else x GETVARARGS R0 2 AND R2 R0 R1 RETURN R2 1 -)"); +)" + ); // this also works in presence of type casts - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... return if x then y else x :: number )"), @@ -7931,10 +8662,12 @@ return if x then y else x :: number GETVARARGS R0 2 AND R2 R0 R1 RETURN R2 1 -)"); +)" + ); // all of the above work when the target is a temporary register, which is safe because the value is only mutated once - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x, y = ... x = if x then x else y x = if x then y else x @@ -7944,10 +8677,12 @@ GETVARARGS R0 2 OR R0 R0 R1 AND R0 R0 R1 RETURN R0 0 -)"); +)" + ); // note that we can't do this transformation if the expression has possible side effects - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x = ... return if x.data then x.data else 0 )"), @@ -7959,13 +8694,15 @@ GETTABLEKS R1 R0 K0 ['data'] RETURN R1 1 L0: LOADN R1 0 RETURN R1 1 -)"); +)" + ); } TEST_CASE("SideEffects") { // we do not evaluate expressions in some cases when we know they can't carry side effects - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x = 5, print local y = 5, 42 local z = 5, table.find -- considered side effecting because of metamethods @@ -7976,10 +8713,13 @@ LOADN R1 5 LOADN R2 5 GETIMPORT R3 2 [table.find] RETURN R0 0 -)"); +)" + ); // this also applies to returns in cases where a function gets inlined - CHECK_EQ("\n" + compileFunction(R"( + CHECK_EQ( + "\n" + compileFunction( + R"( local function test1() return 42 end @@ -8001,7 +8741,9 @@ test2() test3() test4() )", - 5, 2), + 5, + 2 + ), R"( DUPCLOSURE R0 K0 ['test1'] DUPCLOSURE R1 K1 ['test2'] @@ -8010,7 +8752,8 @@ CAPTURE VAL R2 DUPCLOSURE R3 K3 ['test4'] GETIMPORT R4 6 [table.find] RETURN R0 0 -)"); +)" + ); } TEST_CASE("IfElimination") @@ -8084,7 +8827,8 @@ TEST_CASE("ArithRevK") { // - and / have special optimized form for reverse constants; in the future, + and * will likely get compiled to ADDK/MULK // other operators are not important enough to optimize reverse constant forms for - CHECK_EQ("\n" + compileFunction0(R"( + CHECK_EQ( + "\n" + compileFunction0(R"( local x: number = unknown return 2 + x, 2 - x, 2 * x, 2 / x, 2 % x, 2 // x, 2 ^ x )"), @@ -8103,7 +8847,8 @@ IDIV R6 R7 R0 LOADN R8 2 POW R7 R8 R0 RETURN R1 7 -)"); +)" + ); } TEST_SUITE_END(); diff --git a/tests/Config.test.cpp b/tests/Config.test.cpp index e6a72672..70d6d6d7 100644 --- a/tests/Config.test.cpp +++ b/tests/Config.test.cpp @@ -25,12 +25,14 @@ TEST_CASE("language_mode") TEST_CASE("disable_a_lint_rule") { Config config; - auto err = parseConfig(R"( + auto err = parseConfig( + R"( {"lint": { "UnknownGlobal": false, }} )", - config); + config + ); REQUIRE(!err); CHECK(!config.enabledLint.isEnabled(LintWarning::Code_UnknownGlobal)); @@ -40,12 +42,14 @@ TEST_CASE("disable_a_lint_rule") TEST_CASE("report_a_syntax_error") { Config config; - auto err = parseConfig(R"( + auto err = parseConfig( + R"( {"lint": { "UnknownGlobal": "oops" }} )", - config); + config + ); REQUIRE(err); CHECK_EQ("In key UnknownGlobal: Bad setting 'oops'. Valid options are true and false", *err); @@ -79,7 +83,8 @@ TEST_CASE("lint_warnings_are_ordered") TEST_CASE("comments") { Config config; - auto err = parseConfig(R"( + auto err = parseConfig( + R"( { "lint": { "*": false, @@ -92,7 +97,8 @@ TEST_CASE("comments") } } )", - config); + config + ); REQUIRE(!err); CHECK(!config.enabledLint.isEnabled(LintWarning::Code_LocalShadow)); @@ -105,13 +111,15 @@ TEST_CASE("issue_severity") CHECK(!config.lintErrors); CHECK(config.typeErrors); - auto err = parseConfig(R"( + auto err = parseConfig( + R"( { "lintErrors": true, "typeErrors": false, } )", - config); + config + ); REQUIRE(!err); CHECK(config.lintErrors); @@ -121,12 +129,14 @@ TEST_CASE("issue_severity") TEST_CASE("extra_globals") { Config config; - auto err = parseConfig(R"( + auto err = parseConfig( + R"( { "globals": ["it", "__DEV__"], } )", - config); + config + ); REQUIRE(!err); REQUIRE(config.globals.size() == 2); @@ -137,14 +147,17 @@ TEST_CASE("extra_globals") TEST_CASE("lint_rules_compat") { Config config; - auto err = parseConfig(R"( + auto err = parseConfig( + R"( {"lint": { "SameLineStatement": "enabled", "FunctionUnused": "disabled", "ImportUnused": "fatal", }} )", - config, true); + config, + true + ); REQUIRE(!err); CHECK(config.enabledLint.isEnabled(LintWarning::Code_SameLineStatement)); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index c3cc70c9..59f862cb 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -60,7 +60,8 @@ static int lua_collectgarbage(lua_State* L) { static const char* const opts[] = {"stop", "restart", "collect", "count", "isrunning", "step", "setgoal", "setstepmul", "setstepsize", nullptr}; static const int optsnum[] = { - LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCISRUNNING, LUA_GCSTEP, LUA_GCSETGOAL, LUA_GCSETSTEPMUL, LUA_GCSETSTEPSIZE}; + LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCISRUNNING, LUA_GCSTEP, LUA_GCSETGOAL, LUA_GCSETSTEPMUL, LUA_GCSETSTEPSIZE + }; int o = luaL_checkoption(L, 1, "collect", opts); int ex = luaL_optinteger(L, 2, 0); @@ -184,9 +185,15 @@ int lua_silence(lua_State* L) using StateRef = std::unique_ptr; -static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = nullptr, void (*yield)(lua_State* L) = nullptr, - lua_State* initialLuaState = nullptr, lua_CompileOptions* options = nullptr, bool skipCodegen = false, - Luau::CodeGen::CompilationOptions* codegenOptions = nullptr) +static StateRef runConformance( + const char* name, + void (*setup)(lua_State* L) = nullptr, + void (*yield)(lua_State* L) = nullptr, + lua_State* initialLuaState = nullptr, + lua_CompileOptions* options = nullptr, + bool skipCodegen = false, + Luau::CodeGen::CompilationOptions* codegenOptions = nullptr +) { #ifdef LUAU_CONFORMANCE_SOURCE_DIR std::string path = LUAU_CONFORMANCE_SOURCE_DIR; @@ -465,7 +472,8 @@ void setupUserdataHelpers(lua_State* L) lua_pushcclosurek( L, - [](lua_State* L) { + [](lua_State* L) + { Vec2* a = lua_vec2_get(L, 1); Vec2* b = lua_vec2_get(L, 2); Vec2* data = lua_vec2_push(L); @@ -475,12 +483,16 @@ void setupUserdataHelpers(lua_State* L) return 1; }, - nullptr, 0, nullptr); + nullptr, + 0, + nullptr + ); lua_setfield(L, -2, "__add"); lua_pushcclosurek( L, - [](lua_State* L) { + [](lua_State* L) + { Vec2* a = lua_vec2_get(L, 1); Vec2* b = lua_vec2_get(L, 2); Vec2* data = lua_vec2_push(L); @@ -490,12 +502,16 @@ void setupUserdataHelpers(lua_State* L) return 1; }, - nullptr, 0, nullptr); + nullptr, + 0, + nullptr + ); lua_setfield(L, -2, "__sub"); lua_pushcclosurek( L, - [](lua_State* L) { + [](lua_State* L) + { Vec2* a = lua_vec2_get(L, 1); Vec2* b = lua_vec2_get(L, 2); Vec2* data = lua_vec2_push(L); @@ -505,12 +521,16 @@ void setupUserdataHelpers(lua_State* L) return 1; }, - nullptr, 0, nullptr); + nullptr, + 0, + nullptr + ); lua_setfield(L, -2, "__mul"); lua_pushcclosurek( L, - [](lua_State* L) { + [](lua_State* L) + { Vec2* a = lua_vec2_get(L, 1); Vec2* b = lua_vec2_get(L, 2); Vec2* data = lua_vec2_push(L); @@ -520,12 +540,16 @@ void setupUserdataHelpers(lua_State* L) return 1; }, - nullptr, 0, nullptr); + nullptr, + 0, + nullptr + ); lua_setfield(L, -2, "__div"); lua_pushcclosurek( L, - [](lua_State* L) { + [](lua_State* L) + { Vec2* a = lua_vec2_get(L, 1); Vec2* data = lua_vec2_push(L); @@ -534,7 +558,10 @@ void setupUserdataHelpers(lua_State* L) return 1; }, - nullptr, 0, nullptr); + nullptr, + 0, + nullptr + ); lua_setfield(L, -2, "__unm"); lua_setreadonly(L, -1, true); @@ -550,13 +577,17 @@ static void setupNativeHelpers(lua_State* L) { lua_pushcclosurek( L, - [](lua_State* L) -> int { + [](lua_State* L) -> int + { extern int luaG_isnative(lua_State * L, int level); lua_pushboolean(L, luaG_isnative(L, 1)); return 1; }, - "is_native", 0, nullptr); + "is_native", + 0, + nullptr + ); lua_setglobal(L, "is_native"); } @@ -612,26 +643,32 @@ TEST_CASE("Math") TEST_CASE("Tables") { - runConformance("tables.lua", [](lua_State* L) { - lua_pushcfunction( - L, - [](lua_State* L) { - if (lua_type(L, 1) == LUA_TNUMBER) + runConformance( + "tables.lua", + [](lua_State* L) + { + lua_pushcfunction( + L, + [](lua_State* L) { - unsigned v = luaL_checkunsigned(L, 1); - lua_pushlightuserdata(L, reinterpret_cast(uintptr_t(v))); - } - else - { - const void* p = lua_topointer(L, 1); - LUAU_ASSERT(p); // we expect the test call to only pass GC values here - lua_pushlightuserdata(L, const_cast(p)); - } - return 1; - }, - "makelud"); - lua_setglobal(L, "makelud"); - }); + if (lua_type(L, 1) == LUA_TNUMBER) + { + unsigned v = luaL_checkunsigned(L, 1); + lua_pushlightuserdata(L, reinterpret_cast(uintptr_t(v))); + } + else + { + const void* p = lua_topointer(L, 1); + LUAU_ASSERT(p); // we expect the test call to only pass GC values here + lua_pushlightuserdata(L, const_cast(p)); + } + return 1; + }, + "makelud" + ); + lua_setglobal(L, "makelud"); + } + ); } TEST_CASE("PatternMatch") @@ -742,22 +779,27 @@ TEST_CASE("PCall") { runConformance( "pcall.lua", - [](lua_State* L) { + [](lua_State* L) + { lua_pushcfunction(L, cxxthrow, "cxxthrow"); lua_setglobal(L, "cxxthrow"); lua_pushcfunction( L, - [](lua_State* L) -> int { + [](lua_State* L) -> int + { lua_State* co = lua_tothread(L, 1); lua_xmove(L, co, 1); lua_resumeerror(co, L); return 0; }, - "resumeerror"); + "resumeerror" + ); lua_setglobal(L, "resumeerror"); }, - nullptr, lua_newstate(limitedRealloc, nullptr)); + nullptr, + lua_newstate(limitedRealloc, nullptr) + ); } TEST_CASE("Pack") @@ -808,10 +850,16 @@ TEST_CASE("Vector") runConformance( "vector.lua", - [](lua_State* L) { + [](lua_State* L) + { setupVectorHelpers(L); }, - nullptr, nullptr, &copts, false, &nativeOpts); + nullptr, + nullptr, + &copts, + false, + &nativeOpts + ); } static void populateRTTI(lua_State* L, Luau::TypeId type) @@ -881,24 +929,28 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { - runConformance("types.lua", [](lua_State* L) { - Luau::NullModuleResolver moduleResolver; - Luau::NullFileResolver fileResolver; - Luau::NullConfigResolver configResolver; - Luau::Frontend frontend{&fileResolver, &configResolver}; - Luau::registerBuiltinGlobals(frontend, frontend.globals); - Luau::freeze(frontend.globals.globalTypes); - - lua_newtable(L); - - for (const auto& [name, binding] : frontend.globals.globalScope->bindings) + runConformance( + "types.lua", + [](lua_State* L) { - populateRTTI(L, binding.typeId); - lua_setfield(L, -2, toString(name).c_str()); - } + Luau::NullModuleResolver moduleResolver; + Luau::NullFileResolver fileResolver; + Luau::NullConfigResolver configResolver; + Luau::Frontend frontend{&fileResolver, &configResolver}; + Luau::registerBuiltinGlobals(frontend, frontend.globals); + Luau::freeze(frontend.globals.globalTypes); - lua_setglobal(L, "RTTI"); - }); + lua_newtable(L); + + for (const auto& [name, binding] : frontend.globals.globalScope->bindings) + { + populateRTTI(L, binding.typeId); + lua_setfield(L, -2, toString(name).c_str()); + } + + lua_setglobal(L, "RTTI"); + } + ); } TEST_CASE("DateTime") @@ -936,18 +988,21 @@ TEST_CASE("Debugger") runConformance( "debugger.lua", - [](lua_State* L) { + [](lua_State* L) + { lua_Callbacks* cb = lua_callbacks(L); lua_singlestep(L, singlestep); // this will only be called in single-step mode - cb->debugstep = [](lua_State* L, lua_Debug* ar) { + cb->debugstep = [](lua_State* L, lua_Debug* ar) + { stephits++; }; // for breakpoints to work we should make sure debugbreak is installed - cb->debugbreak = [](lua_State* L, lua_Debug* ar) { + cb->debugbreak = [](lua_State* L, lua_Debug* ar) + { breakhits++; // make sure we can trace the stack for every breakpoint we hit @@ -961,7 +1016,8 @@ TEST_CASE("Debugger") }; // for resuming off a breakpoint inside a coroutine we need to resume the interrupted coroutine - cb->debuginterrupt = [](lua_State* L, lua_Debug* ar) { + cb->debuginterrupt = [](lua_State* L, lua_Debug* ar) + { CHECK(interruptedthread == nullptr); CHECK(ar->userdata); // userdata contains the interrupted thread @@ -971,7 +1027,8 @@ TEST_CASE("Debugger") // add breakpoint() function lua_pushcclosurek( L, - [](lua_State* L) -> int { + [](lua_State* L) -> int + { int line = luaL_checkinteger(L, 1); bool enabled = luaL_optboolean(L, 2, true); @@ -981,10 +1038,14 @@ TEST_CASE("Debugger") lua_breakpoint(L, -1, line, enabled); return 0; }, - "breakpoint", 0, nullptr); + "breakpoint", + 0, + nullptr + ); lua_setglobal(L, "breakpoint"); }, - [](lua_State* L) { + [](lua_State* L) + { CHECK(breakhits % 2 == 1); lua_checkstack(L, LUA_MINSTACK); @@ -1082,7 +1143,10 @@ TEST_CASE("Debugger") interruptedthread = nullptr; } }, - nullptr, &copts, /* skipCodegen */ true); // Native code doesn't support debugging yet + nullptr, + &copts, + /* skipCodegen */ true + ); // Native code doesn't support debugging yet CHECK(breakhits == 16); // 2 hits per breakpoint @@ -1098,8 +1162,10 @@ TEST_CASE("NDebugGetUpValue") copts.optimizationLevel = 0; runConformance( - "ndebug_upvalues.lua", nullptr, - [](lua_State* L) { + "ndebug_upvalues.lua", + nullptr, + [](lua_State* L) + { lua_checkstack(L, LUA_MINSTACK); // push the second frame's closure to the stack @@ -1114,7 +1180,10 @@ TEST_CASE("NDebugGetUpValue") CHECK(lua_tointeger(L, -1) == 5); lua_pop(L, 2); }, - nullptr, &copts, /* skipCodegen */ false); + nullptr, + &copts, + /* skipCodegen */ false + ); } TEST_CASE("SameHash") @@ -1145,12 +1214,22 @@ TEST_CASE("Reference") lua_State* L = globalState.get(); // note, we push two userdata objects but only pin one of them (the first one) - lua_newuserdatadtor(L, 0, [](void*) { - dtorhits++; - }); - lua_newuserdatadtor(L, 0, [](void*) { - dtorhits++; - }); + lua_newuserdatadtor( + L, + 0, + [](void*) + { + dtorhits++; + } + ); + lua_newuserdatadtor( + L, + 0, + [](void*) + { + dtorhits++; + } + ); lua_gc(L, LUA_GCCOLLECT, 0); CHECK(dtorhits == 0); @@ -1181,14 +1260,16 @@ TEST_CASE("NewUserdataOverflow") lua_pushcfunction( L, - [](lua_State* L1) { + [](lua_State* L1) + { // The following userdata request might cause an overflow. lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {}); // The overflow might segfault in the following call. lua_getmetatable(L1, -1); return 0; }, - nullptr); + nullptr + ); CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN); CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0); @@ -1446,7 +1527,8 @@ TEST_CASE("ApiAtoms") StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); - lua_callbacks(L)->useratom = [](const char* s, size_t l) -> int16_t { + lua_callbacks(L)->useratom = [](const char* s, size_t l) -> int16_t + { if (strcmp(s, "string") == 0) return 0; if (strcmp(s, "important") == 0) @@ -1578,7 +1660,8 @@ TEST_CASE("ExceptionObject") std::string description; }; - auto captureException = [](lua_State* L, const char* functionToRun) { + auto captureException = [](lua_State* L, const char* functionToRun) + { try { lua_State* threadState = lua_newthread(L); @@ -1672,10 +1755,12 @@ TEST_CASE("TagMethodError") runConformance( "tmerror.lua", - [](lua_State* L) { + [](lua_State* L) + { auto* cb = lua_callbacks(L); - cb->debugprotectederror = [](lua_State* L) { + cb->debugprotectederror = [](lua_State* L) + { std::optional ar = getFirstLuauFrameDebugInfo(L); CHECK(lua_isyieldable(L)); @@ -1692,7 +1777,8 @@ TEST_CASE("TagMethodError") } }; }, - yieldCallback); + yieldCallback + ); // Make sure the number of break points hit was the expected number CHECK(index == std::size(expectedHits)); @@ -1707,43 +1793,55 @@ TEST_CASE("Coverage") runConformance( "coverage.lua", - [](lua_State* L) { + [](lua_State* L) + { lua_pushcfunction( L, - [](lua_State* L) -> int { + [](lua_State* L) -> int + { luaL_argexpected(L, lua_isLfunction(L, 1), 1, "function"); lua_newtable(L); - lua_getcoverage(L, 1, L, [](void* context, const char* function, int linedefined, int depth, const int* hits, size_t size) { - lua_State* L = static_cast(context); + lua_getcoverage( + L, + 1, + L, + [](void* context, const char* function, int linedefined, int depth, const int* hits, size_t size) + { + lua_State* L = static_cast(context); - lua_newtable(L); + lua_newtable(L); - lua_pushstring(L, function); - lua_setfield(L, -2, "name"); + lua_pushstring(L, function); + lua_setfield(L, -2, "name"); - lua_pushinteger(L, linedefined); - lua_setfield(L, -2, "linedefined"); + lua_pushinteger(L, linedefined); + lua_setfield(L, -2, "linedefined"); - lua_pushinteger(L, depth); - lua_setfield(L, -2, "depth"); + lua_pushinteger(L, depth); + lua_setfield(L, -2, "depth"); - for (size_t i = 0; i < size; ++i) - if (hits[i] != -1) - { - lua_pushinteger(L, hits[i]); - lua_rawseti(L, -2, int(i)); - } + for (size_t i = 0; i < size; ++i) + if (hits[i] != -1) + { + lua_pushinteger(L, hits[i]); + lua_rawseti(L, -2, int(i)); + } - lua_rawseti(L, -2, lua_objlen(L, -2) + 1); - }); + lua_rawseti(L, -2, lua_objlen(L, -2) + 1); + } + ); return 1; }, - "getcoverage"); + "getcoverage" + ); lua_setglobal(L, "getcoverage"); }, - nullptr, nullptr, &copts); + nullptr, + nullptr, + &copts + ); } TEST_CASE("StringConversion") @@ -1754,10 +1852,13 @@ TEST_CASE("StringConversion") TEST_CASE("GCDump") { // internal function, declared in lgc.h - not exposed via lua.h - extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State * L, uint8_t memcat)); - extern void luaC_enumheap(lua_State * L, void* context, + extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); + extern void luaC_enumheap( + lua_State * L, + void* context, void (*node)(void* context, void* ptr, uint8_t tt, uint8_t memcat, size_t size, const char* name), - void (*edge)(void* context, void* from, void* to, const char* name)); + void (*edge)(void* context, void* from, void* to, const char* name) + ); StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); @@ -1829,8 +1930,10 @@ TEST_CASE("GCDump") } ctx; luaC_enumheap( - L, &ctx, - [](void* ctx, void* gco, uint8_t tt, uint8_t memcat, size_t size, const char* name) { + L, + &ctx, + [](void* ctx, void* gco, uint8_t tt, uint8_t memcat, size_t size, const char* name) + { EnumContext& context = *(EnumContext*)ctx; if (tt == LUA_TUSERDATA) @@ -1838,10 +1941,12 @@ TEST_CASE("GCDump") context.nodes[gco] = {gco, tt, memcat, size, name ? name : ""}; }, - [](void* ctx, void* s, void* t, const char*) { + [](void* ctx, void* s, void* t, const char*) + { EnumContext& context = *(EnumContext*)ctx; context.edges[s] = t; - }); + } + ); CHECK(!ctx.nodes.empty()); CHECK(!ctx.edges.empty()); @@ -1866,7 +1971,8 @@ TEST_CASE("Interrupt") // define the interrupt to check the expected hits static const int expectedhits[] = {11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 20, 15, 15, 15, 15, 18, 25, 23, 26}; - lua_callbacks(L)->interrupt = [](lua_State* L, int gc) { + lua_callbacks(L)->interrupt = [](lua_State* L, int gc) + { if (gc >= 0) return; @@ -1903,7 +2009,8 @@ TEST_CASE("Interrupt") // redefine the interrupt to break after 10 iterations of a loop that would otherwise be infinite // the test exposes a few global functions that we will call; the interrupt will force a yield - lua_callbacks(L)->interrupt = [](lua_State* L, int gc) { + lua_callbacks(L)->interrupt = [](lua_State* L, int gc) + { if (gc >= 0) return; @@ -1928,7 +2035,8 @@ TEST_CASE("Interrupt") lua_pop(L, 1); } - lua_callbacks(L)->interrupt = [](lua_State* L, int gc) { + lua_callbacks(L)->interrupt = [](lua_State* L, int gc) + { if (gc >= 0) return; @@ -1978,7 +2086,8 @@ TEST_CASE("UserdataApi") lua_State* L = globalState.get(); // setup dtor for tag 42 (created later) - auto dtor = [](lua_State* l, void* data) { + auto dtor = [](lua_State* l, void* data) + { dtorhits += *(int*)data; }; bool dtorIsNull = lua_getuserdatadtor(L, 42) == nullptr; @@ -2016,13 +2125,23 @@ TEST_CASE("UserdataApi") lua_setuserdatatag(L, -1, 42); // user data with inline dtor - void* ud3 = lua_newuserdatadtor(L, 4, [](void* data) { - dtorhits += *(int*)data; - }); + void* ud3 = lua_newuserdatadtor( + L, + 4, + [](void* data) + { + dtorhits += *(int*)data; + } + ); - void* ud4 = lua_newuserdatadtor(L, 1, [](void* data) { - dtorhits += *(char*)data; - }); + void* ud4 = lua_newuserdatadtor( + L, + 1, + [](void* data) + { + dtorhits += *(char*)data; + } + ); *(int*)ud3 = 43; *(char*)ud4 = 3; @@ -2174,191 +2293,225 @@ static void pushInt64(lua_State* L, int64_t value) TEST_CASE("Userdata") { - runConformance("userdata.lua", [](lua_State* L) { - // create metatable with all the metamethods - luaL_newmetatable(L, "int64"); + runConformance( + "userdata.lua", + [](lua_State* L) + { + // create metatable with all the metamethods + luaL_newmetatable(L, "int64"); - // __index - lua_pushcfunction( - L, - [](lua_State* L) { - void* p = lua_touserdatatagged(L, 1, kInt64Tag); - if (!p) - luaL_typeerror(L, 1, "int64"); - - const char* name = luaL_checkstring(L, 2); - - if (strcmp(name, "value") == 0) + // __index + lua_pushcfunction( + L, + [](lua_State* L) { - lua_pushnumber(L, double(*static_cast(p))); + void* p = lua_touserdatatagged(L, 1, kInt64Tag); + if (!p) + luaL_typeerror(L, 1, "int64"); + + const char* name = luaL_checkstring(L, 2); + + if (strcmp(name, "value") == 0) + { + lua_pushnumber(L, double(*static_cast(p))); + return 1; + } + + luaL_error(L, "unknown field %s", name); + }, + nullptr + ); + lua_setfield(L, -2, "__index"); + + // __newindex + lua_pushcfunction( + L, + [](lua_State* L) + { + void* p = lua_touserdatatagged(L, 1, kInt64Tag); + if (!p) + luaL_typeerror(L, 1, "int64"); + + const char* name = luaL_checkstring(L, 2); + + if (strcmp(name, "value") == 0) + { + double value = luaL_checknumber(L, 3); + *static_cast(p) = int64_t(value); + return 0; + } + + luaL_error(L, "unknown field %s", name); + }, + nullptr + ); + lua_setfield(L, -2, "__newindex"); + + // __eq + lua_pushcfunction( + L, + [](lua_State* L) + { + lua_pushboolean(L, getInt64(L, 1) == getInt64(L, 2)); return 1; - } + }, + nullptr + ); + lua_setfield(L, -2, "__eq"); - luaL_error(L, "unknown field %s", name); - }, - nullptr); - lua_setfield(L, -2, "__index"); - - // __newindex - lua_pushcfunction( - L, - [](lua_State* L) { - void* p = lua_touserdatatagged(L, 1, kInt64Tag); - if (!p) - luaL_typeerror(L, 1, "int64"); - - const char* name = luaL_checkstring(L, 2); - - if (strcmp(name, "value") == 0) + // __lt + lua_pushcfunction( + L, + [](lua_State* L) { - double value = luaL_checknumber(L, 3); - *static_cast(p) = int64_t(value); - return 0; - } + lua_pushboolean(L, getInt64(L, 1) < getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__lt"); - luaL_error(L, "unknown field %s", name); - }, - nullptr); - lua_setfield(L, -2, "__newindex"); + // __le + lua_pushcfunction( + L, + [](lua_State* L) + { + lua_pushboolean(L, getInt64(L, 1) <= getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__le"); - // __eq - lua_pushcfunction( - L, - [](lua_State* L) { - lua_pushboolean(L, getInt64(L, 1) == getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__eq"); + // __add + lua_pushcfunction( + L, + [](lua_State* L) + { + pushInt64(L, getInt64(L, 1) + getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__add"); - // __lt - lua_pushcfunction( - L, - [](lua_State* L) { - lua_pushboolean(L, getInt64(L, 1) < getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__lt"); + // __sub + lua_pushcfunction( + L, + [](lua_State* L) + { + pushInt64(L, getInt64(L, 1) - getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__sub"); - // __le - lua_pushcfunction( - L, - [](lua_State* L) { - lua_pushboolean(L, getInt64(L, 1) <= getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__le"); + // __mul + lua_pushcfunction( + L, + [](lua_State* L) + { + pushInt64(L, getInt64(L, 1) * getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__mul"); - // __add - lua_pushcfunction( - L, - [](lua_State* L) { - pushInt64(L, getInt64(L, 1) + getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__add"); + // __div + lua_pushcfunction( + L, + [](lua_State* L) + { + // ideally we'd guard against 0 but it's a test so eh + pushInt64(L, getInt64(L, 1) / getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__div"); - // __sub - lua_pushcfunction( - L, - [](lua_State* L) { - pushInt64(L, getInt64(L, 1) - getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__sub"); + // __idiv + lua_pushcfunction( + L, + [](lua_State* L) + { + // for testing we use different semantics here compared to __div: __idiv rounds to negative inf, __div truncates (rounds to zero) + // additionally, division loses precision here outside of 2^53 range + // we do not necessarily recommend this behavior in production code! + pushInt64(L, int64_t(floor(double(getInt64(L, 1)) / double(getInt64(L, 2))))); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__idiv"); - // __mul - lua_pushcfunction( - L, - [](lua_State* L) { - pushInt64(L, getInt64(L, 1) * getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__mul"); + // __mod + lua_pushcfunction( + L, + [](lua_State* L) + { + // ideally we'd guard against 0 and INT64_MIN but it's a test so eh + pushInt64(L, getInt64(L, 1) % getInt64(L, 2)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__mod"); - // __div - lua_pushcfunction( - L, - [](lua_State* L) { - // ideally we'd guard against 0 but it's a test so eh - pushInt64(L, getInt64(L, 1) / getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__div"); + // __pow + lua_pushcfunction( + L, + [](lua_State* L) + { + pushInt64(L, int64_t(pow(double(getInt64(L, 1)), double(getInt64(L, 2))))); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__pow"); - // __idiv - lua_pushcfunction( - L, - [](lua_State* L) { - // for testing we use different semantics here compared to __div: __idiv rounds to negative inf, __div truncates (rounds to zero) - // additionally, division loses precision here outside of 2^53 range - // we do not necessarily recommend this behavior in production code! - pushInt64(L, int64_t(floor(double(getInt64(L, 1)) / double(getInt64(L, 2))))); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__idiv"); + // __unm + lua_pushcfunction( + L, + [](lua_State* L) + { + pushInt64(L, -getInt64(L, 1)); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__unm"); - // __mod - lua_pushcfunction( - L, - [](lua_State* L) { - // ideally we'd guard against 0 and INT64_MIN but it's a test so eh - pushInt64(L, getInt64(L, 1) % getInt64(L, 2)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__mod"); + // __tostring + lua_pushcfunction( + L, + [](lua_State* L) + { + int64_t value = getInt64(L, 1); + std::string str = std::to_string(value); + lua_pushlstring(L, str.c_str(), str.length()); + return 1; + }, + nullptr + ); + lua_setfield(L, -2, "__tostring"); - // __pow - lua_pushcfunction( - L, - [](lua_State* L) { - pushInt64(L, int64_t(pow(double(getInt64(L, 1)), double(getInt64(L, 2))))); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__pow"); - - // __unm - lua_pushcfunction( - L, - [](lua_State* L) { - pushInt64(L, -getInt64(L, 1)); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__unm"); - - // __tostring - lua_pushcfunction( - L, - [](lua_State* L) { - int64_t value = getInt64(L, 1); - std::string str = std::to_string(value); - lua_pushlstring(L, str.c_str(), str.length()); - return 1; - }, - nullptr); - lua_setfield(L, -2, "__tostring"); - - // ctor - lua_pushcfunction( - L, - [](lua_State* L) { - double v = luaL_checknumber(L, 1); - pushInt64(L, int64_t(v)); - return 1; - }, - "int64"); - lua_setglobal(L, "int64"); - }); + // ctor + lua_pushcfunction( + L, + [](lua_State* L) + { + double v = luaL_checknumber(L, 1); + pushInt64(L, int64_t(v)); + return 1; + }, + "int64" + ); + lua_setglobal(L, "int64"); + } + ); } TEST_CASE("SafeEnv") @@ -2382,9 +2535,13 @@ TEST_CASE("Native") FFlag::DebugLuauAbortingChecks.value = false; } - runConformance("native.lua", [](lua_State* L) { - setupNativeHelpers(L); - }); + runConformance( + "native.lua", + [](lua_State* L) + { + setupNativeHelpers(L); + } + ); } TEST_CASE("NativeTypeAnnotations") @@ -2393,10 +2550,14 @@ TEST_CASE("NativeTypeAnnotations") if (!codegen || !luau_codegen_supported()) return; - runConformance("native_types.lua", [](lua_State* L) { - setupNativeHelpers(L); - setupVectorHelpers(L); - }); + runConformance( + "native_types.lua", + [](lua_State* L) + { + setupNativeHelpers(L); + setupVectorHelpers(L); + } + ); } TEST_CASE("NativeUserdata") @@ -2454,29 +2615,40 @@ TEST_CASE("NativeUserdata") runConformance( "native_userdata.lua", - [](lua_State* L) { - Luau::CodeGen::setUserdataRemapper(L, kUserdataRunTypes, [](void* context, const char* str, size_t len) -> uint8_t { - const char** types = (const char**)context; - - uint8_t index = 0; - - std::string_view sv{str, len}; - - for (; *types; ++types) + [](lua_State* L) + { + Luau::CodeGen::setUserdataRemapper( + L, + kUserdataRunTypes, + [](void* context, const char* str, size_t len) -> uint8_t { - if (sv == *types) - return index; + const char** types = (const char**)context; - index++; + uint8_t index = 0; + + std::string_view sv{str, len}; + + for (; *types; ++types) + { + if (sv == *types) + return index; + + index++; + } + + return 0xff; } - - return 0xff; - }); + ); setupVectorHelpers(L); setupUserdataHelpers(L); }, - nullptr, nullptr, &copts, false, &nativeOpts); + nullptr, + nullptr, + &copts, + false, + &nativeOpts + ); } [[nodiscard]] static std::string makeHugeFunctionSource() @@ -2553,7 +2725,8 @@ TEST_CASE("HugeFunctionLoadFailure") static size_t largeAllocationToFail = 0; static size_t largeAllocationCount = 0; - const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* { + const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* + { if (nsize == 0) { free(ptr); @@ -2697,28 +2870,28 @@ end CHECK_EQ(summaries[0].getName(), "inner"); CHECK_EQ(summaries[0].getLine(), 6); - CHECK_EQ(summaries[0].getCounts(0), - std::vector({0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + CHECK_EQ(summaries[0].getCounts(0), std::vector({0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0})); CHECK_EQ(summaries[1].getName(), "first"); CHECK_EQ(summaries[1].getLine(), 2); - CHECK_EQ(summaries[1].getCounts(0), - std::vector({0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + CHECK_EQ(summaries[1].getCounts(0), std::vector({0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); CHECK_EQ(summaries[2].getName(), "second"); CHECK_EQ(summaries[2].getLine(), 15); - CHECK_EQ(summaries[2].getCounts(0), - std::vector({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + CHECK_EQ(summaries[2].getCounts(0), std::vector({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); CHECK_EQ(summaries[3].getName(), ""); CHECK_EQ(summaries[3].getLine(), 1); - CHECK_EQ(summaries[3].getCounts(0), - std::vector({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + CHECK_EQ(summaries[3].getCounts(0), std::vector({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); } TEST_CASE("NativeAttribute") diff --git a/tests/ConformanceIrHooks.h b/tests/ConformanceIrHooks.h index ab5b86d4..07c721ba 100644 --- a/tests/ConformanceIrHooks.h +++ b/tests/ConformanceIrHooks.h @@ -112,7 +112,15 @@ inline uint8_t vectorNamecallBytecodeType(const char* member, size_t memberLengt } inline bool vectorNamecall( - Luau::CodeGen::IrBuilder& build, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos) + Luau::CodeGen::IrBuilder& build, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +) { using namespace Luau::CodeGen; @@ -225,7 +233,14 @@ inline uint8_t userdataAccessBytecodeType(uint8_t type, const char* member, size } inline bool userdataAccess( - Luau::CodeGen::IrBuilder& build, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos) + Luau::CodeGen::IrBuilder& build, + uint8_t type, + const char* member, + size_t memberLength, + int resultReg, + int sourceReg, + int pcpos +) { using namespace Luau::CodeGen; @@ -337,8 +352,16 @@ inline uint8_t userdataMetamethodBytecodeType(uint8_t lhsTy, uint8_t rhsTy, Luau return LBC_TYPE_ANY; } -inline bool userdataMetamethod(Luau::CodeGen::IrBuilder& build, uint8_t lhsTy, uint8_t rhsTy, int resultReg, Luau::CodeGen::IrOp lhs, - Luau::CodeGen::IrOp rhs, Luau::CodeGen::HostMetamethod method, int pcpos) +inline bool userdataMetamethod( + Luau::CodeGen::IrBuilder& build, + uint8_t lhsTy, + uint8_t rhsTy, + int resultReg, + Luau::CodeGen::IrOp lhs, + Luau::CodeGen::IrOp rhs, + Luau::CodeGen::HostMetamethod method, + int pcpos +) { using namespace Luau::CodeGen; @@ -460,8 +483,17 @@ inline uint8_t userdataNamecallBytecodeType(uint8_t type, const char* member, si return LBC_TYPE_ANY; } -inline bool userdataNamecall(Luau::CodeGen::IrBuilder& build, uint8_t type, const char* member, size_t memberLength, int argResReg, int sourceReg, - int params, int results, int pcpos) +inline bool userdataNamecall( + Luau::CodeGen::IrBuilder& build, + uint8_t type, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +) { using namespace Luau::CodeGen; diff --git a/tests/ConstraintGeneratorFixture.cpp b/tests/ConstraintGeneratorFixture.cpp index c6b3d2d9..5c16258e 100644 --- a/tests/ConstraintGeneratorFixture.cpp +++ b/tests/ConstraintGeneratorFixture.cpp @@ -22,8 +22,18 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code) { AstStatBlock* root = parse(code); dfg = std::make_unique(DataFlowGraphBuilder::build(root, NotNull{&ice})); - cg = std::make_unique(mainModule, NotNull{&normalizer}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice), - frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector()); + cg = std::make_unique( + mainModule, + NotNull{&normalizer}, + NotNull(&moduleResolver), + builtinTypes, + NotNull(&ice), + frontend.globals.globalScope, + /*prepareModuleScope*/ nullptr, + &logger, + NotNull{dfg.get()}, + std::vector() + ); cg->visitModuleRoot(root); rootScope = cg->rootScope; constraints = Luau::borrowConstraints(cg->constraints); diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp index 311ff5f3..a10056a7 100644 --- a/tests/Differ.test.cpp +++ b/tests/Differ.test.cpp @@ -60,9 +60,12 @@ TEST_CASE_FIXTURE(DifferFixture, "a_table_missing_property") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo is missing " - "the property y"); + "the property y" + ); } TEST_CASE_FIXTURE(DifferFixture, "left_table_missing_property") @@ -73,9 +76,12 @@ TEST_CASE_FIXTURE(DifferFixture, "left_table_missing_property") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at foo is missing the property z, while the right type at almostFoo.z " - "has type number"); + "has type number" + ); } TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type") @@ -86,9 +92,12 @@ TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo.y has type " - "string"); + "string" + ); } TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type") @@ -99,9 +108,12 @@ TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at has type string, while the right type at " - " has type number"); + " has type number" + ); } TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_type") @@ -112,9 +124,12 @@ TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_type") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.value has type number, while the right " - "type at almostFoo.inner.table.has.wrong.value has type string"); + "type at almostFoo.inner.table.has.wrong.value has type string" + ); } TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_match") @@ -125,9 +140,12 @@ TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_match") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", "DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.variant has type { because: { it: { goes: " - "{ on: string } } } }, while the right type at almostFoo.inner.table.has.wrong.variant has type string"); + "{ on: string } } } }, while the right type at almostFoo.inner.table.has.wrong.variant has type string" + ); } TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_missing_property") @@ -144,8 +162,11 @@ TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_missing_property )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo is missing the property foo)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo is missing the property foo)" + ); } TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_property_wrong") @@ -162,8 +183,11 @@ TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_property_wrong") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo.foo has type number)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo.foo has type number)" + ); } TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_missing_property") @@ -180,8 +204,11 @@ TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_missing_property )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("almostFoo", "foo", - R"(DiffError: these two types are not equal because the left type at almostFoo.x has type number, while the right type at is missing the property x)"); + compareTypesNe( + "almostFoo", + "foo", + R"(DiffError: these two types are not equal because the left type at almostFoo.x has type number, while the right type at is missing the property x)" + ); } TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong") @@ -198,8 +225,11 @@ TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("almostFoo", "foo", - R"(DiffError: these two types are not equal because the left type at almostFoo.foo has type number, while the right type at .foo has type t1 where t1 = { foo: t1 })"); + compareTypesNe( + "almostFoo", + "foo", + R"(DiffError: these two types are not equal because the left type at almostFoo.foo has type number, while the right type at .foo has type t1 where t1 = { foo: t1 })" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different") @@ -273,8 +303,11 @@ TEST_CASE_FIXTURE(DifferFixture, "table_left_circle_right_measuring_tape") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .foo.foo.foo.foo.foo is missing the property bar, while the right type at .foo.foo.foo.foo.foo.bar has type { })"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .foo.foo.foo.foo.foo is missing the property bar, while the right type at .foo.foo.foo.foo.foo.bar has type { })" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_table_measuring_tapes") @@ -660,11 +693,17 @@ TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Ret[1].bar.Ret[1] has type t1 where t1 = { bar: () -> t1 }, while the right type at .Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Ret[1].bar.Ret[1] has type t1 where t1 = { bar: () -> t1 }, while the right type at .Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)" + ); else - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at .Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at .Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic") @@ -730,8 +769,11 @@ TEST_CASE_FIXTURE(DifferFixture, "singleton") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type true)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type true)" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_singleton") @@ -753,8 +795,11 @@ TEST_CASE_FIXTURE(DifferFixture, "singleton_string") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type "world")"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type "world")" + ); } TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation") @@ -779,15 +824,19 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is a union containing type { x: { y: ~string } }, while the right type at is a union missing type { x: { y: ~string } })"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is a union containing type { x: { y: ~string } }, while the right type at is a union missing type { x: { y: ~string } })" + ); // TODO: a more desirable expected error here is as below, but `Differ` requires improvements to // dealing with unions to get something like this (recognizing that the union is identical // except in one component where they differ). // // compareTypesNe("foo", "almostFoo", - // R"(DiffError: these two types are not equal because the left type at .x.y.Negation has type string, while the right type at .x.y.Negation has type number)"); + // R"(DiffError: these two types are not equal because the left type at .x.y.Negation has type string, while the right type + // at .x.y.Negation has type number)"); } TEST_CASE_FIXTURE(DifferFixture, "union_missing_right") @@ -798,8 +847,11 @@ TEST_CASE_FIXTURE(DifferFixture, "union_missing_right") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is a union containing type number, while the right type at is a union missing type number)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is a union containing type number, while the right type at is a union missing type number)" + ); } TEST_CASE_FIXTURE(DifferFixture, "union_missing_left") @@ -810,8 +862,11 @@ TEST_CASE_FIXTURE(DifferFixture, "union_missing_left") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is a union missing type boolean, while the right type at is a union containing type boolean)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is a union missing type boolean, while the right type at is a union containing type boolean)" + ); } TEST_CASE_FIXTURE(DifferFixture, "union_missing") @@ -825,11 +880,17 @@ TEST_CASE_FIXTURE(DifferFixture, "union_missing") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is a union containing type { baz: boolean, rot: "singleton" }, while the right type at is a union missing type { baz: boolean, rot: "singleton" })"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is a union containing type { baz: boolean, rot: "singleton" }, while the right type at is a union missing type { baz: boolean, rot: "singleton" })" + ); else - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at is a union missing type {| baz: boolean, rot: "singleton" |})"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at is a union missing type {| baz: boolean, rot: "singleton" |})" + ); } TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_right") @@ -840,8 +901,11 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_right") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection containing type (number) -> (), while the right type at is an intersection missing type (number) -> ())"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection containing type (number) -> (), while the right type at is an intersection missing type (number) -> ())" + ); } TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_left") @@ -852,8 +916,11 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_left") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection missing type (boolean) -> (), while the right type at is an intersection containing type (boolean) -> ())"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection missing type (boolean) -> (), while the right type at is an intersection containing type (boolean) -> ())" + ); } TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_right") @@ -865,11 +932,17 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_right") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection containing type { x: number }, while the right type at is an intersection missing type { x: number })"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection containing type { x: number }, while the right type at is an intersection missing type { x: number })" + ); else - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection containing type {| x: number |}, while the right type at is an intersection missing type {| x: number |})"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection containing type {| x: number |}, while the right type at is an intersection missing type {| x: number |})" + ); } TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left") @@ -881,11 +954,17 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection missing type { z: boolean }, while the right type at is an intersection containing type { z: boolean })"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection missing type { z: boolean }, while the right type at is an intersection containing type { z: boolean })" + ); else - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at is an intersection missing type {| z: boolean |}, while the right type at is an intersection containing type {| z: boolean |})"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at is an intersection missing type {| z: boolean |}, while the right type at is an intersection containing type {| z: boolean |})" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_function") @@ -966,8 +1045,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Arg[3] has type number, while the right type at .Arg[3] has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Arg[3] has type number, while the right type at .Arg[3] has type string)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2") @@ -985,8 +1067,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Arg[2] has type number, while the right type at .Arg[2] has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Arg[2] has type number, while the right type at .Arg[2] has type string)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal") @@ -1004,8 +1089,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Ret[1] has type number, while the right type at .Ret[1] has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Ret[1] has type number, while the right type at .Ret[1] has type string)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_arg_length") @@ -1023,8 +1111,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 2 or more arguments, while the right type at takes 3 or more arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 2 or more arguments, while the right type at takes 3 or more arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2") @@ -1042,8 +1133,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 3 or more arguments, while the right type at takes 2 or more arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 3 or more arguments, while the right type at takes 2 or more arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none") @@ -1061,8 +1155,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 0 or more arguments, while the right type at takes 2 or more arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 0 or more arguments, while the right type at takes 2 or more arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2") @@ -1080,8 +1177,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 1 or more arguments, while the right type at takes 0 or more arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 1 or more arguments, while the right type at takes 0 or more arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_ret_length") @@ -1099,8 +1199,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at returns 1 values, while the right type at returns 2 values)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at returns 1 values, while the right type at returns 2 values)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2") @@ -1118,8 +1221,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at returns 3 values, while the right type at returns 2 values)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at returns 3 values, while the right type at returns 2 values)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none") @@ -1137,8 +1243,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at returns 0 values, while the right type at returns 1 values)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at returns 0 values, while the right type at returns 1 values)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2") @@ -1156,8 +1265,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at returns 1 values, while the right type at returns 0 values)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at returns 1 values, while the right type at returns 0 values)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal") @@ -1175,8 +1287,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type number, while the right type at .Arg[Variadic] has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type number, while the right type at .Arg[Variadic] has type string)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing") @@ -1194,8 +1309,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type number, while the right type at .Arg[Variadic] has type any)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type number, while the right type at .Arg[Variadic] has type any)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2") @@ -1213,8 +1331,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type any, while the right type at .Arg[Variadic] has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .Arg[Variadic] has type any, while the right type at .Arg[Variadic] has type string)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation") @@ -1232,8 +1353,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 2 or more arguments, while the right type at takes 2 arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 2 or more arguments, while the right type at takes 2 arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2") @@ -1251,8 +1375,11 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at takes 2 arguments, while the right type at takes 2 or more arguments)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at takes 2 arguments, while the right type at takes 2 or more arguments)" + ); } TEST_CASE_FIXTURE(DifferFixture, "generic") @@ -1270,8 +1397,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generic") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left generic at .Ret[1] cannot be the same type parameter as the right generic at .Ret[1])"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left generic at .Ret[1] cannot be the same type parameter as the right generic at .Ret[1])" + ); } TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two") @@ -1289,8 +1419,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left generic at .Arg[2] cannot be the same type parameter as the right generic at .Arg[2])"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left generic at .Arg[2] cannot be the same type parameter as the right generic at .Arg[2])" + ); } TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three") @@ -1308,8 +1441,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left generic at .Arg[2] cannot be the same type parameter as the right generic at .Arg[2])"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left generic at .Arg[2] cannot be the same type parameter as the right generic at .Arg[2])" + ); } TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable") @@ -1357,8 +1493,11 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .bar has type number, while the right type at .bar has type string)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .bar has type number, while the right type at .bar has type string)" + ); } TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metanormal") @@ -1381,8 +1520,11 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metanormal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .__metatable.metaBar has type string, while the right type at .__metatable.metaBar has type number)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .__metatable.metaBar has type string, while the right type at .__metatable.metaBar has type number)" + ); } TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_left") @@ -1406,8 +1548,11 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_left") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .__metatable is missing the property thisIsOnlyInRight, while the right type at .__metatable.thisIsOnlyInRight has type number)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .__metatable is missing the property thisIsOnlyInRight, while the right type at .__metatable.thisIsOnlyInRight has type number)" + ); } TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right") @@ -1431,8 +1576,11 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at .__metatable.thisIsOnlyInLeft has type number, while the right type at .__metatable is missing the property thisIsOnlyInLeft)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at .__metatable.thisIsOnlyInLeft has type number, while the right type at .__metatable is missing the property thisIsOnlyInLeft)" + ); } TEST_CASE_FIXTURE(DifferFixtureGeneric, "equal_class") @@ -1454,8 +1602,11 @@ TEST_CASE_FIXTURE(DifferFixtureGeneric, "class_normal") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at has type BaseClass, while the right type at has type ChildClass)"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at has type BaseClass, while the right type at has type ChildClass)" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_generictp") @@ -1477,8 +1628,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generictp_ne_fn") )"); LUAU_REQUIRE_NO_ERRORS(result); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left type at has type (...T) -> (U...), while the right type at has type (U...) -> (U...))"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left type at has type (...T) -> (U...), while the right type at has type (U...) -> (U...))" + ); } TEST_CASE_FIXTURE(DifferFixture, "generictp_normal") @@ -1505,8 +1659,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generictp_normal") INFO(Luau::toString(requireType("foo"))); INFO(Luau::toString(requireType("almostFoo"))); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left generic at .Arg[1].Ret[Variadic] cannot be the same type parameter as the right generic at .Arg[1].Ret[Variadic])"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left generic at .Arg[1].Ret[Variadic] cannot be the same type parameter as the right generic at .Arg[1].Ret[Variadic])" + ); } TEST_CASE_FIXTURE(DifferFixture, "generictp_normal_2") @@ -1530,8 +1687,11 @@ TEST_CASE_FIXTURE(DifferFixture, "generictp_normal_2") INFO(Luau::toString(requireType("foo"))); INFO(Luau::toString(requireType("almostFoo"))); - compareTypesNe("foo", "almostFoo", - R"(DiffError: these two types are not equal because the left generic at .Arg[2].Arg[Variadic] cannot be the same type parameter as the right generic at .Arg[2].Arg[Variadic])"); + compareTypesNe( + "foo", + "almostFoo", + R"(DiffError: these two types are not equal because the left generic at .Arg[2].Arg[Variadic] cannot be the same type parameter as the right generic at .Arg[2].Arg[Variadic])" + ); } TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic") @@ -1565,9 +1725,12 @@ TEST_CASE_FIXTURE(DifferFixture, "symbol_forward") INFO(Luau::toString(requireType("foo"))); INFO(Luau::toString(requireType("almostFoo"))); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", R"(DiffError: these two types are not equal because the left type at foo has type number, while the right type at almostFoo has type string)", - true); + true + ); } TEST_CASE_FIXTURE(DifferFixture, "newlines") @@ -1581,7 +1744,9 @@ TEST_CASE_FIXTURE(DifferFixture, "newlines") INFO(Luau::toString(requireType("foo"))); INFO(Luau::toString(requireType("almostFoo"))); - compareTypesNe("foo", "almostFoo", + compareTypesNe( + "foo", + "almostFoo", R"(DiffError: these two types are not equal because the left type at foo has type @@ -1590,7 +1755,9 @@ while the right type at almostFoo has type string)", - true, true); + true, + true + ); } TEST_SUITE_END(); diff --git a/tests/EqSat.propositional.test.cpp b/tests/EqSat.propositional.test.cpp index 5b2d34b4..679477ce 100644 --- a/tests/EqSat.propositional.test.cpp +++ b/tests/EqSat.propositional.test.cpp @@ -6,6 +6,7 @@ #include "Luau/Language.h" #include +#include LUAU_EQSAT_ATOM(Var, std::string); LUAU_EQSAT_ATOM(Bool, bool); diff --git a/tests/Error.test.cpp b/tests/Error.test.cpp index 431b326d..cc178a83 100644 --- a/tests/Error.test.cpp +++ b/tests/Error.test.cpp @@ -48,8 +48,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_function_errors") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add", - toString(result.errors[0])); + CHECK_EQ( + "Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add", + toString(result.errors[0]) + ); else CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); } @@ -68,7 +70,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_function_errors") { LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK_EQ( - "Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0])); + "Operator '-' could not be applied to operand of type string; there is no corresponding overload for __unm", toString(result.errors[0]) + ); CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[1])); } else diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index ef0731fa..83454aab 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -150,8 +150,11 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const Fixture::Fixture(bool freeze, bool prepareAutocomplete) : sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze) - , frontend(&fileResolver, &configResolver, - {/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed}) + , frontend( + &fileResolver, + &configResolver, + {/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed} + ) , builtinTypes(frontend.builtinTypes) { configResolver.defaultConfig.mode = Mode::Strict; @@ -165,7 +168,8 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete) if (FFlag::DebugLuauLogSolverToJsonFile) { - frontend.writeJsonLog = [&](const Luau::ModuleName& moduleName, std::string log) { + frontend.writeJsonLog = [&](const Luau::ModuleName& moduleName, std::string log) + { std::string path = moduleName + ".log.json"; size_t pos = moduleName.find_last_of('/'); if (pos != std::string::npos) @@ -203,8 +207,21 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars if (FFlag::DebugLuauDeferredConstraintResolution) { Mode mode = sourceModule->mode ? *sourceModule->mode : Mode::Strict; - ModulePtr module = Luau::check(*sourceModule, mode, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver}, - frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options, {}, false, {}); + ModulePtr module = Luau::check( + *sourceModule, + mode, + {}, + builtinTypes, + NotNull{&ice}, + NotNull{&moduleResolver}, + NotNull{&fileResolver}, + frontend.globals.globalScope, + /*prepareModuleScope*/ nullptr, + frontend.options, + {}, + false, + {} + ); Luau::lint(sourceModule->root, *sourceModule->names, frontend.globals.globalScope, module.get(), sourceModule->hotcomments, {}); } diff --git a/tests/Fixture.h b/tests/Fixture.h index e0c04e8b..e7aad61c 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -182,8 +182,14 @@ struct DifferFixtureGeneric : BaseFixture compareNe(left, std::nullopt, right, std::nullopt, expectedMessage, multiLine); } - void compareNe(TypeId left, std::optional symbolLeft, TypeId right, std::optional symbolRight, - const std::string& expectedMessage, bool multiLine) + void compareNe( + TypeId left, + std::optional symbolLeft, + TypeId right, + std::optional symbolRight, + const std::string& expectedMessage, + bool multiLine + ) { DifferResult diffRes = diffWithSymbols(left, right, symbolLeft, symbolRight); REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal"); @@ -191,18 +197,25 @@ struct DifferFixtureGeneric : BaseFixture CHECK_EQ(expectedMessage, diffMessage); } - void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage, bool forwardSymbol = false, - bool multiLine = false) + void compareTypesNe( + const std::string& leftSymbol, + const std::string& rightSymbol, + const std::string& expectedMessage, + bool forwardSymbol = false, + bool multiLine = false + ) { if (forwardSymbol) { compareNe( - BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine); + BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine + ); } else { compareNe( - BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine); + BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine + ); } } diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 3df6c0e7..d7fc1956 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -714,9 +714,14 @@ TEST_CASE_FIXTURE(FrontendFixture, "report_syntax_error_in_required_file") CHECK_EQ("Modules/A", result.errors[0].moduleName); - bool b = std::any_of(begin(result.errors), end(result.errors), [](auto&& e) -> bool { - return get(e); - }); + bool b = std::any_of( + begin(result.errors), + end(result.errors), + [](auto&& e) -> bool + { + return get(e); + } + ); if (!b) { CHECK_MESSAGE(false, "Expected a syntax error!"); @@ -809,8 +814,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "accumulate_cached_errors_in_consistent_order TEST_CASE_FIXTURE(FrontendFixture, "test_pruneParentSegments") { - CHECK_EQ(std::optional{"Modules/Enum/ButtonState"}, - pathExprToModuleName("", {"Modules", "LuaApp", "DeprecatedDarkTheme", "Parent", "Parent", "Enum", "ButtonState"})); + CHECK_EQ( + std::optional{"Modules/Enum/ButtonState"}, + pathExprToModuleName("", {"Modules", "LuaApp", "DeprecatedDarkTheme", "Parent", "Parent", "Enum", "ButtonState"}) + ); CHECK_EQ(std::optional{"workspace/Foo/Bar/Baz"}, pathExprToModuleName("workspace/Foo/Quux", {"script", "Parent", "Bar", "Baz"})); CHECK_EQ(std::nullopt, pathExprToModuleName("", {})); CHECK_EQ(std::optional{"script"}, pathExprToModuleName("", {"script"})); @@ -915,10 +922,13 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f R"(Type '{ count: string }' could not be converted into - '{ Count: number }')", toString(result.errors[0])); + '{ Count: number }')", + toString(result.errors[0]) + ); else REQUIRE_EQ( - "Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0])); + "Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode") @@ -960,10 +970,15 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments") ScopePtr testScope = frontend.addEnvironment("test"); unfreeze(frontend.globals.globalTypes); - frontend.loadDefinitionFile(frontend.globals, testScope, R"( + frontend.loadDefinitionFile( + frontend.globals, + testScope, + R"( export type Foo = number | string )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); fileResolver.source["A"] = R"( @@ -1230,7 +1245,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias") TEST_CASE_FIXTURE(BuiltinsFixture, "module_scope_check") { - frontend.prepareModuleScope = [this](const ModuleName& name, const ScopePtr& scope, bool forAutocomplete) { + frontend.prepareModuleScope = [this](const ModuleName& name, const ScopePtr& scope, bool forAutocomplete) + { scope->bindings[Luau::AstName{"x"}] = Luau::Binding{frontend.globals.builtinTypes->numberType}; }; diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index dabdf258..55af4919 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -125,8 +125,9 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "cache_fully_generalized_types") { CHECK(generalizedTypes->empty()); - TypeId tinyTable = arena.addType(TableType{ - TableType::Props{{"one", builtinTypes.numberType}, {"two", builtinTypes.stringType}}, std::nullopt, TypeLevel{}, TableState::Sealed}); + TypeId tinyTable = arena.addType( + TableType{TableType::Props{{"one", builtinTypes.numberType}, {"two", builtinTypes.stringType}}, std::nullopt, TypeLevel{}, TableState::Sealed} + ); generalize(tinyTable); @@ -141,8 +142,9 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_cache_types_that_arent_done_yet") TypeId fnTy = arena.addType(FunctionType{builtinTypes.emptyTypePack, arena.addTypePack(TypePack{{builtinTypes.numberType}})}); - TypeId tableTy = arena.addType(TableType{ - TableType::Props{{"one", builtinTypes.numberType}, {"two", freeTy}, {"three", fnTy}}, std::nullopt, TypeLevel{}, TableState::Sealed}); + TypeId tableTy = arena.addType( + TableType{TableType::Props{{"one", builtinTypes.numberType}, {"two", freeTy}, {"three", fnTy}}, std::nullopt, TypeLevel{}, TableState::Sealed} + ); generalize(tableTy); @@ -164,7 +166,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "functions_containing_cyclic_tables_can }); asMutable(selfTy)->ty.emplace( - TableType::Props{{"count", builtinTypes.numberType}, {"method", methodTy}}, std::nullopt, TypeLevel{}, TableState::Sealed); + TableType::Props{{"count", builtinTypes.numberType}, {"method", methodTy}}, std::nullopt, TypeLevel{}, TableState::Sealed + ); generalize(methodTy); diff --git a/tests/Instantiation2.test.cpp b/tests/Instantiation2.test.cpp index 0154a211..fff98e60 100644 --- a/tests/Instantiation2.test.cpp +++ b/tests/Instantiation2.test.cpp @@ -19,10 +19,12 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation") TypeId genericT = arena.addType(GenericType{"T"}); - TypeId idTy = arena.addType(FunctionType{/* generics */ {genericT}, + TypeId idTy = arena.addType(FunctionType{ + /* generics */ {genericT}, /* genericPacks */ {}, /* argTypes */ arena.addTypePack({genericT}), - /* retTypes */ arena.addTypePack({genericT})}); + /* retTypes */ arena.addTypePack({genericT}) + }); DenseHashMap genericSubstitutions{nullptr}; DenseHashMap genericPackSubstitutions{nullptr}; @@ -42,10 +44,8 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation") // Substitutions should not mutate the original type! CHECK("(T) -> T" == toString(idTy)); - // Weird looking because we haven't properly clipped the generic from the - // function type, but this is what we asked for. REQUIRE(res); - CHECK("<(T) -> T>((T) -> T) -> (T) -> T" == toString(*res)); + CHECK("(T) -> T" == toString(*res)); } TEST_SUITE_END(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 238cf161..2984fde1 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -362,7 +362,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric") build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::NOT_ANY, build.constTag(tnil), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))); build.inst( - IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))); + IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1))) + ); build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(0))); build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(1))); @@ -629,21 +630,33 @@ bb_0: TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq") { - withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnil), a, b); - }); + withTwoBlocks( + [this](IrOp a, IrOp b) + { + build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnil), a, b); + } + ); - withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnumber), a, b); - }); + withTwoBlocks( + [this](IrOp a, IrOp b) + { + build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnumber), a, b); + } + ); - withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(0), build.cond(IrCondition::Equal), a, b); - }); + withTwoBlocks( + [this](IrOp a, IrOp b) + { + build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(0), build.cond(IrCondition::Equal), a, b); + } + ); - withTwoBlocks([this](IrOp a, IrOp b) { - build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), a, b); - }); + withTwoBlocks( + [this](IrOp a, IrOp b) + { + build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), a, b); + } + ); updateUseCounts(build.function); constantFold(); @@ -678,21 +691,30 @@ bb_11: TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex") { - withOneBlock([this](IrOp a) { - build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(4), a)); - build.inst(IrCmd::RETURN, build.constUint(0)); - }); + withOneBlock( + [this](IrOp a) + { + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(4), a)); + build.inst(IrCmd::RETURN, build.constUint(0)); + } + ); - withOneBlock([this](IrOp a) { - build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(1.2), a)); - build.inst(IrCmd::RETURN, build.constUint(0)); - }); + withOneBlock( + [this](IrOp a) + { + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(1.2), a)); + build.inst(IrCmd::RETURN, build.constUint(0)); + } + ); - withOneBlock([this](IrOp a) { - IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); - build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, nan, a)); - build.inst(IrCmd::RETURN, build.constUint(0)); - }); + withOneBlock( + [this](IrOp a) + { + IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, nan, a)); + build.inst(IrCmd::RETURN, build.constUint(0)); + } + ); updateUseCounts(build.function); constantFold(); @@ -719,15 +741,21 @@ bb_5: TEST_CASE_FIXTURE(IrBuilderFixture, "Guards") { - withOneBlock([this](IrOp a) { - build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a); - build.inst(IrCmd::RETURN, build.constUint(0)); - }); + withOneBlock( + [this](IrOp a) + { + build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a); + build.inst(IrCmd::RETURN, build.constUint(0)); + } + ); - withOneBlock([this](IrOp a) { - build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a); - build.inst(IrCmd::RETURN, build.constUint(0)); - }); + withOneBlock( + [this](IrOp a) + { + build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a); + build.inst(IrCmd::RETURN, build.constUint(0)); + } + ); updateUseCounts(build.function); constantFold(); @@ -747,16 +775,21 @@ bb_3: TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowCmpNum") { - auto compareFold = [this](IrOp lhs, IrOp rhs, IrCondition cond, bool result) { + auto compareFold = [this](IrOp lhs, IrOp rhs, IrCondition cond, bool result) + { IrOp instOp; IrInst instExpected; - withTwoBlocks([&](IrOp a, IrOp b) { - IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); - instOp = build.inst( - IrCmd::JUMP_CMP_NUM, lhs.kind == IrOpKind::None ? nan : lhs, rhs.kind == IrOpKind::None ? nan : rhs, build.cond(cond), a, b); - instExpected = IrInst{IrCmd::JUMP, result ? a : b}; - }); + withTwoBlocks( + [&](IrOp a, IrOp b) + { + IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); + instOp = build.inst( + IrCmd::JUMP_CMP_NUM, lhs.kind == IrOpKind::None ? nan : lhs, rhs.kind == IrOpKind::None ? nan : rhs, build.cond(cond), a, b + ); + instExpected = IrInst{IrCmd::JUMP, result ? a : b}; + } + ); updateUseCounts(build.function); constantFold(); @@ -1134,8 +1167,16 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory") build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback); build.inst(IrCmd::CHECK_READONLY, table, fallback); - build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(LBF_SETMETATABLE), build.vmReg(1), build.vmReg(2), build.vmReg(3), build.undef(), - build.constInt(3), build.constInt(1)); + build.inst( + IrCmd::INVOKE_FASTCALL, + build.constUint(LBF_SETMETATABLE), + build.vmReg(1), + build.vmReg(2), + build.vmReg(3), + build.undef(), + build.constInt(3), + build.constInt(1) + ); build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback); build.inst(IrCmd::CHECK_READONLY, table, fallback); @@ -2818,8 +2859,16 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence") build.beginBlock(entry); build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1)); - IrOp results = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.undef(), - build.constInt(-1), build.constInt(-1)); + IrOp results = build.inst( + IrCmd::INVOKE_FASTCALL, + build.constUint(0), + build.vmReg(0), + build.vmReg(1), + build.vmReg(2), + build.undef(), + build.constInt(-1), + build.constInt(-1) + ); build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(0), results); build.inst(IrCmd::JUMP, exit); @@ -4353,7 +4402,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialOverFullValue") build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0)); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0)); build.inst( - IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(ttable), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32))); + IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(ttable), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)) + ); build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(8), build.constUint(16))); build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(4), build.constUint(8))); build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.constDouble(1.0)); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 8af6d51b..6d4aafeb 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -82,23 +82,28 @@ static std::string getCodegenAssembly(const char* source, bool includeIrTypes = // Type remapper requires the codegen runtime Luau::CodeGen::create(L); - Luau::CodeGen::setUserdataRemapper(L, kUserdataRunTypes, [](void* context, const char* str, size_t len) -> uint8_t { - const char** types = (const char**)context; - - uint8_t index = 0; - - std::string_view sv{str, len}; - - for (; *types; ++types) + Luau::CodeGen::setUserdataRemapper( + L, + kUserdataRunTypes, + [](void* context, const char* str, size_t len) -> uint8_t { - if (sv == *types) - return index; + const char** types = (const char**)context; - index++; + uint8_t index = 0; + + std::string_view sv{str, len}; + + for (; *types; ++types) + { + if (sv == *types) + return index; + + index++; + } + + return 0xff; } - - return 0xff; - }); + ); } if (luau_load(L, "name", bytecode.data(), bytecode.size(), 0) == 0) @@ -126,7 +131,8 @@ TEST_SUITE_BEGIN("IrLowering"); TEST_CASE("VectorReciprocal") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vecrcp(a: vector) return 1 / a end @@ -146,12 +152,14 @@ bb_bytecode_1: STORE_TVALUE R1, %9 INTERRUPT 1u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorComponentRead") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function compsum(a: vector) return a.X + a.Y + a.Z end @@ -173,12 +181,14 @@ bb_bytecode_1: STORE_TAG R1, tnumber INTERRUPT 8u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorAdd") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3add(a: vector, b: vector) return a + b end @@ -199,12 +209,14 @@ bb_bytecode_1: STORE_TVALUE R2, %13 INTERRUPT 1u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("VectorMinus") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3minus(a: vector) return -a end @@ -223,12 +235,14 @@ bb_bytecode_1: STORE_TVALUE R1, %8 INTERRUPT 1u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorSubMulDiv") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3combo(a: vector, b: vector, c: vector, d: vector) return a * b - c / d end @@ -255,12 +269,14 @@ bb_bytecode_1: STORE_TVALUE R4, %35 INTERRUPT 3u RETURN R4, 1i -)"); +)" + ); } TEST_CASE("VectorSubMulDiv2") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3combo(a: vector) local tmp = a * a return (tmp - tmp) / (tmp + tmp) @@ -283,12 +299,14 @@ bb_bytecode_1: STORE_TVALUE R2, %38 INTERRUPT 4u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("VectorMulDivMixed") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3combo(a: vector, b: vector, c: vector, d: vector) return a * 2 + b / 4 + 0.5 * c + 40 / d end @@ -323,12 +341,14 @@ bb_bytecode_1: STORE_TVALUE R4, %68 INTERRUPT 8u RETURN R4, 1i -)"); +)" + ); } TEST_CASE("ExtraMathMemoryOperands") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo(a: number, b: number, c: number, d: number, e: number) return math.floor(a) + math.ceil(b) + math.round(c) + math.sqrt(d) + math.abs(e) end @@ -359,12 +379,14 @@ bb_bytecode_1: STORE_TAG R5, tnumber INTERRUPT 29u RETURN R5, 1i -)"); +)" + ); } TEST_CASE("DseInitialStackState") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo() while {} do local _ = not _,{} @@ -397,14 +419,16 @@ bb_5: STORE_TAG R0, tnil INTERRUPT 9u JUMP bb_bytecode_0 -)"); +)" + ); } TEST_CASE("DseInitialStackState2") { ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo(a) math.frexp(a) return a @@ -418,12 +442,14 @@ bb_bytecode_0: FASTCALL 14u, R1, R0, 2i INTERRUPT 5u RETURN R0, 1i -)"); +)" + ); } TEST_CASE("VectorConstantTag") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vecrcp(a: vector) return vector(1, 2, 3) + a end @@ -443,12 +469,14 @@ bb_bytecode_1: STORE_TVALUE R1, %13 INTERRUPT 2u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorNamecall") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function abs(a: vector) return a:Abs() end @@ -467,12 +495,14 @@ bb_bytecode_1: CALL R1, 1i, -1i INTERRUPT 3u RETURN R1, -1i -)"); +)" + ); } TEST_CASE("VectorRandomProp") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo(a: vector) return a.XX + a.YY + a.ZZ end @@ -507,12 +537,14 @@ bb_4: bb_6: INTERRUPT 8u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorCustomAccess") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3magn(a: vector) return a.Magnitude * 2 end @@ -539,12 +571,14 @@ bb_bytecode_1: STORE_TAG R1, tnumber INTERRUPT 3u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("VectorCustomNamecall") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3dot(a: vector, b: vector) return (a:Dot(b)) end @@ -575,12 +609,14 @@ bb_bytecode_1: STORE_TAG R2, tnumber INTERRUPT 4u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("VectorCustomAccessChain") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo(a: vector, b: vector) return a.Unit * b.Magnitude end @@ -625,12 +661,14 @@ bb_bytecode_1: STORE_TVALUE R2, %44 INTERRUPT 5u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("VectorCustomNamecallChain") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function foo(n: vector, b: vector, t: vector) return n:Cross(t):Dot(b) + 1 end @@ -682,12 +720,14 @@ bb_bytecode_1: STORE_TAG R3, tnumber INTERRUPT 9u RETURN R3, 1i -)"); +)" + ); } TEST_CASE("VectorCustomNamecallChain2") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( type Vertex = {n: vector, b: vector} local function foo(v: Vertex, t: vector) @@ -756,12 +796,14 @@ bb_6: STORE_TAG R2, tnumber INTERRUPT 12u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("UserDataGetIndex") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function getxy(a: Point) return a.x + a.y end @@ -786,12 +828,14 @@ bb_bytecode_1: bb_4: INTERRUPT 5u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("UserDataSetIndex") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function setxy(a: Point) a.x = 3 a.y = 4 @@ -812,12 +856,14 @@ bb_bytecode_1: FALLBACK_SETTABLEKS 4u, R1, R0, K1 INTERRUPT 6u RETURN R0, 0i -)"); +)" + ); } TEST_CASE("UserDataNamecall") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function getxy(a: Point) return a:GetX() + a:GetY() end @@ -848,12 +894,15 @@ bb_bytecode_1: bb_4: INTERRUPT 7u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("ExplicitUpvalueAndLocalTypes") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local y: vector = ... local function getsum(t) @@ -861,7 +910,8 @@ local function getsum(t) return x.X + x.Y + y.X + y.Y end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function getsum($arg0) line 4 ; U0: vector @@ -889,14 +939,17 @@ bb_bytecode_0: STORE_TAG R1, tnumber INTERRUPT 13u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("FastcallTypeInferThroughLocal") { ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function getsum(x, c) local v = vector(x, 2, 3) if c then @@ -906,7 +959,8 @@ local function getsum(x, c) end end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function getsum($arg0, $arg1) line 2 ; R2: vector from 0 to 18 @@ -937,14 +991,17 @@ bb_bytecode_1: STORE_TAG R3, tnumber INTERRUPT 17u RETURN R3, 1i -)"); +)" + ); } TEST_CASE("FastcallTypeInferThroughUpvalue") { ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local v = ... local function getsum(x, c) @@ -956,7 +1013,8 @@ local function getsum(x, c) end end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function getsum($arg0, $arg1) line 4 ; U0: vector @@ -994,12 +1052,15 @@ bb_bytecode_1: STORE_TAG R2, tnumber INTERRUPT 21u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("LoadAndMoveTypePropagation") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function getsum(n) local seqsum = 0 for i = 1,n do @@ -1013,7 +1074,8 @@ local function getsum(n) return seqsum end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function getsum($arg0) line 2 ; R1: number from 0 to 13 @@ -1059,20 +1121,24 @@ bb_bytecode_3: bb_bytecode_4: INTERRUPT 12u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("ArgumentTypeRefinement") { ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function getsum(x, y) x = vector(1, y, 3) return x.Y + x.Z end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function getsum($arg0, $arg1) line 2 ; R0: vector [argument] @@ -1095,12 +1161,15 @@ bb_bytecode_0: STORE_TAG R2, tnumber INTERRUPT 14u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("InlineFunctionType") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function inl(v: vector, s: number) return v.Y * s end @@ -1109,7 +1178,8 @@ local function getsum(x) return inl(x, 2) + inl(x, 5) end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function inl($arg0, $arg1) line 2 ; R0: vector [argument] @@ -1141,12 +1211,15 @@ bb_bytecode_0: STORE_TAG R1, tnumber INTERRUPT 7u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("ResolveTablePathTypes") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( type Vertex = {pos: vector, normal: vector} local function foo(arr: {Vertex}, i) @@ -1155,7 +1228,9 @@ local function foo(arr: {Vertex}, i) return v.pos.Y end )", - /* includeIrTypes */ true, /* debugLevel */ 2), + /* includeIrTypes */ true, + /* debugLevel */ 2 + ), R"( ; function foo(arr, i) line 4 ; R0: table [argument 'arr'] @@ -1193,12 +1268,14 @@ bb_6: STORE_TAG R3, tnumber INTERRUPT 5u RETURN R3, 1i -)"); +)" + ); } TEST_CASE("ResolvableSimpleMath") { - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number } local mesh: { vertices: {Vertex}, indices: {number} } = ... @@ -1247,19 +1324,23 @@ end ; R12: vector from 75 to 76 ; R13: vector from 71 to 72 ; R14: vector from 71 to 72 -)"); +)" + ); } TEST_CASE("ResolveVectorNamecalls") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( type Vertex = {pos: vector, normal: vector} local function foo(arr: {Vertex}, i) return arr[i].normal:Dot(vector(0.707, 0, 0.707)) end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1) line 4 ; R0: table [argument] @@ -1309,17 +1390,21 @@ bb_6: ADJUST_STACK_TO_REG R2, 1i INTERRUPT 7u RETURN R2, -1i -)"); +)" + ); } TEST_CASE("ImmediateTypeAnnotationHelp") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(arr, i) return (arr[i] :: vector) / 5 end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1) line 2 ; R3: vector from 1 to 2 @@ -1345,14 +1430,16 @@ bb_2: STORE_TVALUE R2, %22 INTERRUPT 2u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("UnaryTypeResolve") { ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( local function foo(a, b: vector, c) local d = not a local e = -b @@ -1367,12 +1454,15 @@ end ; R4: vector from 1 to 17 [local 'e'] ; R5: number from 2 to 17 [local 'f'] ; R7: vector from 14 to 16 -)"); +)" + ); } TEST_CASE("ForInManualAnnotation") { - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( type Vertex = {pos: vector, normal: vector} local function foo(a: {Vertex}) @@ -1383,7 +1473,9 @@ local function foo(a: {Vertex}) return sum end )", - /* includeIrTypes */ true, /* debugLevel */ 2), + /* includeIrTypes */ true, + /* debugLevel */ 2 + ), R"( ; function foo(a) line 4 ; R0: table [argument 'a'] @@ -1460,12 +1552,14 @@ bb_14: bb_12: INTERRUPT 13u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("ForInAutoAnnotationIpairs") { - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( type Vertex = {pos: vector, normal: vector} local function foo(a: {Vertex}) @@ -1485,12 +1579,14 @@ end ; R6: table from 5 to 11 [local 'v'] ; R7: number from 6 to 11 [local 'n'] ; R8: vector from 8 to 10 -)"); +)" + ); } TEST_CASE("ForInAutoAnnotationPairs") { - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( type Vertex = {pos: vector, normal: vector} local function foo(a: {[string]: Vertex}) @@ -1510,12 +1606,14 @@ end ; R6: table from 5 to 11 [local 'v'] ; R7: number from 6 to 11 [local 'n'] ; R8: vector from 8 to 10 -)"); +)" + ); } TEST_CASE("ForInAutoAnnotationGeneric") { - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( type Vertex = {pos: vector, normal: vector} local function foo(a: {Vertex}) @@ -1535,7 +1633,8 @@ end ; R6: table from 4 to 10 [local 'v'] ; R7: number from 5 to 10 [local 'n'] ; R8: vector from 7 to 9 -)"); +)" + ); } // Temporary test, when we don't compile new typeinfo, but support loading it @@ -1547,7 +1646,8 @@ TEST_CASE("CustomUserdataTypesTemp") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, false}}; - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( local function foo(v: vec2, x: mat3) return v.X * x end @@ -1556,7 +1656,8 @@ end ; function foo(v, x) line 2 ; R0: userdata [argument 'v'] ; R1: userdata [argument 'x'] -)"); +)" + ); } TEST_CASE("CustomUserdataTypes") @@ -1567,7 +1668,8 @@ TEST_CASE("CustomUserdataTypes") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenHeader(R"( + CHECK_EQ( + "\n" + getCodegenHeader(R"( local function foo(v: vec2, x: mat3) return v.X * x end @@ -1576,7 +1678,8 @@ end ; function foo(v, x) line 2 ; R0: vec2 [argument 'v'] ; R1: mat3 [argument 'x'] -)"); +)" + ); } TEST_CASE("CustomUserdataPropertyAccess") @@ -1587,12 +1690,15 @@ TEST_CASE("CustomUserdataPropertyAccess") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(v: vec2) return v.X + v.Y end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0) line 2 ; R0: vec2 [argument] @@ -1611,7 +1717,8 @@ bb_bytecode_1: STORE_TAG R1, tnumber INTERRUPT 5u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("CustomUserdataPropertyAccess2") @@ -1622,12 +1729,15 @@ TEST_CASE("CustomUserdataPropertyAccess2") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: mat3) return a.Row1 * a.Row2 end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0) line 2 ; R0: mat3 [argument] @@ -1648,7 +1758,8 @@ bb_bytecode_1: STORE_TVALUE R1, %17 INTERRUPT 5u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("CustomUserdataNamecall1") @@ -1659,12 +1770,15 @@ TEST_CASE("CustomUserdataNamecall1") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: vec2, b: vec2) return a:Dot(b) end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1) line 2 ; R0: vec2 [argument] @@ -1694,7 +1808,8 @@ bb_bytecode_1: ADJUST_STACK_TO_REG R2, 1i INTERRUPT 4u RETURN R2, -1i -)"); +)" + ); } TEST_CASE("CustomUserdataNamecall2") @@ -1705,12 +1820,15 @@ TEST_CASE("CustomUserdataNamecall2") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: vec2, b: vec2) return a:Min(b) end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1) line 2 ; R0: vec2 [argument] @@ -1743,7 +1861,8 @@ bb_bytecode_1: ADJUST_STACK_TO_REG R2, 1i INTERRUPT 4u RETURN R2, -1i -)"); +)" + ); } TEST_CASE("CustomUserdataMetamethodDirectFlow") @@ -1754,12 +1873,15 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: mat3, b: mat3) return a * b end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1) line 2 ; R0: mat3 [argument] @@ -1775,7 +1897,8 @@ bb_bytecode_1: DO_ARITH R2, R0, R1, 10i INTERRUPT 1u RETURN R2, 1i -)"); +)" + ); } TEST_CASE("CustomUserdataMetamethodDirectFlow2") @@ -1786,12 +1909,15 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow2") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: mat3) return -a end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0) line 2 ; R0: mat3 [argument] @@ -1805,7 +1931,8 @@ bb_bytecode_1: DO_ARITH R1, R0, R0, 15i INTERRUPT 1u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("CustomUserdataMetamethodDirectFlow3") @@ -1816,12 +1943,15 @@ TEST_CASE("CustomUserdataMetamethodDirectFlow3") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: sequence) return #a end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0) line 2 ; R0: userdata [argument] @@ -1835,7 +1965,8 @@ bb_bytecode_1: DO_LEN R1, R0 INTERRUPT 1u RETURN R1, 1i -)"); +)" + ); } TEST_CASE("CustomUserdataMetamethod") @@ -1846,12 +1977,15 @@ TEST_CASE("CustomUserdataMetamethod") ScopedFastFlag sffs[]{{FFlag::LuauCompileUserdataInfo, true}}; - CHECK_EQ("\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( local function foo(a: vec2, b: vec2, c: vec2) return -c + a * b end )", - /* includeIrTypes */ true), + /* includeIrTypes */ true + ), R"( ; function foo($arg0, $arg1, $arg2) line 2 ; R0: vec2 [argument] @@ -1905,7 +2039,8 @@ bb_bytecode_1: STORE_TAG R3, tuserdata INTERRUPT 3u RETURN R3, 1i -)"); +)" + ); } TEST_SUITE_END(); diff --git a/tests/LValue.test.cpp b/tests/LValue.test.cpp index c71d97d1..931c3d59 100644 --- a/tests/LValue.test.cpp +++ b/tests/LValue.test.cpp @@ -10,23 +10,28 @@ using namespace Luau; static void merge(TypeArena& arena, RefinementMap& l, const RefinementMap& r) { - Luau::merge(l, r, [&arena](TypeId a, TypeId b) -> TypeId { - // TODO: normalize here also. - std::unordered_set s; + Luau::merge( + l, + r, + [&arena](TypeId a, TypeId b) -> TypeId + { + // TODO: normalize here also. + std::unordered_set s; - if (auto utv = get(follow(a))) - s.insert(begin(utv), end(utv)); - else - s.insert(a); + if (auto utv = get(follow(a))) + s.insert(begin(utv), end(utv)); + else + s.insert(a); - if (auto utv = get(follow(b))) - s.insert(begin(utv), end(utv)); - else - s.insert(b); + if (auto utv = get(follow(b))) + s.insert(begin(utv), end(utv)); + else + s.insert(b); - std::vector options(s.begin(), s.end()); - return options.size() == 1 ? options[0] : arena.addType(UnionType{std::move(options)}); - }); + std::vector options(s.begin(), s.end()); + return options.size() == 1 ? options[0] : arena.addType(UnionType{std::move(options)}); + } + ); } static LValue mkSymbol(const std::string& s) diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 73aa8905..b5cbd26a 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -313,8 +313,9 @@ fnB() -- prints "false", "nil" )"); REQUIRE(1 == result.warnings.size()); - CHECK_EQ(result.warnings[0].text, - "Global 'moreInternalLogic' is only used in the enclosing function defined at line 2; consider changing it to local"); + CHECK_EQ( + result.warnings[0].text, "Global 'moreInternalLogic' is only used in the enclosing function defined at line 2; consider changing it to local" + ); } TEST_CASE_FIXTURE(Fixture, "LocalShadowLocal") @@ -714,8 +715,10 @@ end CHECK_EQ(result.warnings[0].location.begin.line, 1); CHECK_EQ(result.warnings[0].text, "For loop starts at 0, but arrays start at 1"); CHECK_EQ(result.warnings[1].location.begin.line, 7); - CHECK_EQ(result.warnings[1].text, - "For loop should iterate backwards; did you forget to specify -1 as step? Also consider changing 0 to 1 since arrays start at 1"); + CHECK_EQ( + result.warnings[1].text, + "For loop should iterate backwards; did you forget to specify -1 as step? Also consider changing 0 to 1 since arrays start at 1" + ); } TEST_CASE_FIXTURE(Fixture, "UnbalancedAssignment") @@ -806,14 +809,20 @@ return f1,f2,f3,f4,f5,f6,f7 REQUIRE(3 == result.warnings.size()); CHECK_EQ(result.warnings[0].location.begin.line, 5); - CHECK_EQ(result.warnings[0].text, - "Function 'f1' can implicitly return no values even though there's an explicit return at line 5; add explicit return to silence"); + CHECK_EQ( + result.warnings[0].text, + "Function 'f1' can implicitly return no values even though there's an explicit return at line 5; add explicit return to silence" + ); CHECK_EQ(result.warnings[1].location.begin.line, 29); - CHECK_EQ(result.warnings[1].text, - "Function 'f4' can implicitly return no values even though there's an explicit return at line 26; add explicit return to silence"); + CHECK_EQ( + result.warnings[1].text, + "Function 'f4' can implicitly return no values even though there's an explicit return at line 26; add explicit return to silence" + ); CHECK_EQ(result.warnings[2].location.begin.line, 45); - CHECK_EQ(result.warnings[2].text, - "Function can implicitly return no values even though there's an explicit return at line 45; add explicit return to silence"); + CHECK_EQ( + result.warnings[2].text, + "Function can implicitly return no values even though there's an explicit return at line 45; add explicit return to silence" + ); } TEST_CASE_FIXTURE(Fixture, "ImplicitReturnInfiniteLoop") @@ -863,11 +872,15 @@ return f1,f2,f3,f4 REQUIRE(2 == result.warnings.size()); CHECK_EQ(result.warnings[0].location.begin.line, 26); - CHECK_EQ(result.warnings[0].text, - "Function 'f3' can implicitly return no values even though there's an explicit return at line 22; add explicit return to silence"); + CHECK_EQ( + result.warnings[0].text, + "Function 'f3' can implicitly return no values even though there's an explicit return at line 22; add explicit return to silence" + ); CHECK_EQ(result.warnings[1].location.begin.line, 37); - CHECK_EQ(result.warnings[1].text, - "Function 'f4' can implicitly return no values even though there's an explicit return at line 33; add explicit return to silence"); + CHECK_EQ( + result.warnings[1].text, + "Function 'f4' can implicitly return no values even though there's an explicit return at line 33; add explicit return to silence" + ); } TEST_CASE_FIXTURE(Fixture, "TypeAnnotationsShouldNotProduceWarnings") @@ -1312,10 +1325,15 @@ TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals") { ScopePtr testScope = frontend.addEnvironment("Test"); unfreeze(frontend.globals.globalTypes); - frontend.loadDefinitionFile(frontend.globals, testScope, R"( + frontend.loadDefinitionFile( + frontend.globals, + testScope, + R"( declare Foo: number )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); fileResolver.environments["A"] = "Test"; @@ -1395,7 +1413,8 @@ TEST_CASE_FIXTURE(Fixture, "DuplicateLocalFunction") options.enableWarning(LintWarning::Code_DuplicateFunction); options.enableWarning(LintWarning::Code_LocalShadow); - LintResult result = lint(R"( + LintResult result = lint( + R"( local function x() end print(x) @@ -1404,7 +1423,8 @@ TEST_CASE_FIXTURE(Fixture, "DuplicateLocalFunction") return x )", - options); + options + ); REQUIRE_EQ(1, result.warnings.size()); @@ -1609,21 +1629,31 @@ table.create(42, {} :: {}) )"); REQUIRE(10 == result.warnings.size()); - CHECK_EQ(result.warnings[0].text, "table.insert will insert the value before the last element, which is likely a bug; consider removing the " - "second argument or wrap it in parentheses to silence"); + CHECK_EQ( + result.warnings[0].text, + "table.insert will insert the value before the last element, which is likely a bug; consider removing the " + "second argument or wrap it in parentheses to silence" + ); CHECK_EQ(result.warnings[1].text, "table.insert will append the value to the table; consider removing the second argument for efficiency"); CHECK_EQ(result.warnings[2].text, "table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?"); CHECK_EQ(result.warnings[3].text, "table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?"); - CHECK_EQ(result.warnings[4].text, "table.remove will remove the value before the last element, which is likely a bug; consider removing the " - "second argument or wrap it in parentheses to silence"); - CHECK_EQ(result.warnings[5].text, - "table.insert may change behavior if the call returns more than one result; consider adding parentheses around second argument"); + CHECK_EQ( + result.warnings[4].text, + "table.remove will remove the value before the last element, which is likely a bug; consider removing the " + "second argument or wrap it in parentheses to silence" + ); + CHECK_EQ( + result.warnings[5].text, + "table.insert may change behavior if the call returns more than one result; consider adding parentheses around second argument" + ); CHECK_EQ(result.warnings[6].text, "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"); CHECK_EQ(result.warnings[7].text, "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?"); CHECK_EQ( - result.warnings[8].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"); + result.warnings[8].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead" + ); CHECK_EQ( - result.warnings[9].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"); + result.warnings[9].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer") @@ -1770,10 +1800,16 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored )"); REQUIRE(2 == result.warnings.size()); - CHECK_EQ(result.warnings[0].text, "The and-or expression always evaluates to the second alternative because the first alternative is false; " - "consider using if-then-else expression instead"); - CHECK_EQ(result.warnings[1].text, "The and-or expression always evaluates to the second alternative because the first alternative is nil; " - "consider using if-then-else expression instead"); + CHECK_EQ( + result.warnings[0].text, + "The and-or expression always evaluates to the second alternative because the first alternative is false; " + "consider using if-then-else expression instead" + ); + CHECK_EQ( + result.warnings[1].text, + "The and-or expression always evaluates to the second alternative because the first alternative is nil; " + "consider using if-then-else expression instead" + ); } TEST_CASE_FIXTURE(Fixture, "WrongComment") diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 3894881c..bcad38d3 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -240,17 +240,31 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection") TEST_CASE_FIXTURE(Fixture, "clone_class") { - Type exampleMetaClass{ClassType{"ExampleClassMeta", + Type exampleMetaClass{ClassType{ + "ExampleClassMeta", { {"__add", {builtinTypes->anyType}}, }, - std::nullopt, std::nullopt, {}, {}, "Test", {}}}; - Type exampleClass{ClassType{"ExampleClass", + std::nullopt, + std::nullopt, + {}, + {}, + "Test", + {} + }}; + Type exampleClass{ClassType{ + "ExampleClass", { {"PropOne", {builtinTypes->numberType}}, {"PropTwo", {builtinTypes->stringType}}, }, - std::nullopt, &exampleMetaClass, {}, {}, "Test", {}}}; + std::nullopt, + &exampleMetaClass, + {}, + {}, + "Test", + {} + }}; TypeArena dest; CloneState cloneState{builtinTypes}; diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index b2be38a7..b7198a9d 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -531,10 +531,12 @@ optionalArgsAtTheEnd2("a", "b", "c") -- error TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "non_testable_type_throws_ice") { - CHECK_THROWS_AS(checkNonStrict(R"( + CHECK_THROWS_AS( + checkNonStrict(R"( os.time({year = 0, month = 0, day = 0, min = 0, isdst = nil}) )"), - Luau::InternalCompilerError); + Luau::InternalCompilerError + ); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "non_strict_shouldnt_warn_on_require_module") diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index 7130a717..a22e1e16 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -5,6 +5,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" #include @@ -15,6 +16,7 @@ TEST_SUITE_BEGIN("NonstrictModeTests"); TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict function foo(x, y) end @@ -37,6 +39,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_could_return") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict function getMinCardCountForWidth(width) @@ -100,6 +103,7 @@ TEST_CASE_FIXTURE(Fixture, "inconsistent_return_types_are_ok") TEST_CASE_FIXTURE(Fixture, "locals_are_any_by_default") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict local m = 55 @@ -126,6 +130,7 @@ TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional") TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict local T = {} @@ -143,6 +148,7 @@ TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any") TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict local T = {} @@ -157,6 +163,7 @@ TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon") TEST_CASE_FIXTURE(Fixture, "table_props_are_any") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict local T = {} @@ -178,6 +185,7 @@ TEST_CASE_FIXTURE(Fixture, "table_props_are_any") TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict local T = { @@ -253,6 +261,7 @@ TEST_CASE_FIXTURE(Fixture, "delay_function_does_not_require_its_argument_to_retu TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( --!nonstrict diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 29dcc6d3..19c63b28 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -843,8 +843,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes") CHECK("Child" == toString(normal("Not & Child"))); CHECK("((class & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not | Child"))); CHECK("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not"))); - CHECK("(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" == - toString(normal("Not & Not & Not>"))); + CHECK( + "(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" == + toString(normal("Not & Not & Not>")) + ); } TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_unknown") @@ -991,4 +993,30 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_2") CHECK(normalized); } +TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_with_optional_prop") +{ + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; + + // { x: ~(false?) } + TypeId t1 = arena.addType(TableType{ + TableType::Props{{"x", builtinTypes->truthyType}}, std::nullopt, TypeLevel{}, TableState::Sealed + }); + + // { x: number? }? + TypeId t2 = arena.addType(UnionType{{ + arena.addType(TableType{ + TableType::Props{{"x", builtinTypes->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed + }), + builtinTypes->nilType + }}); + + TypeId intersection = arena.addType(IntersectionType{{t2, t1}}); + + auto norm = normalizer.normalize(intersection); + REQUIRE(norm); + + TypeId ty = normalizer.typeFromNormal(*norm); + CHECK("{ x: number }" == toString(ty)); +} + TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 481ca981..1be8024e 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -18,6 +18,7 @@ LUAU_FASTINT(LuauParseErrorLimit); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr); LUAU_FASTFLAG(LuauDeclarationExtraPropData); +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions); namespace { @@ -62,7 +63,8 @@ TEST_SUITE_BEGIN("AllocatorTests"); TEST_CASE("allocator_can_be_moved") { Counter* c = nullptr; - auto inner = [&]() { + auto inner = [&]() + { Luau::Allocator allocator; c = allocator.alloc(); Luau::Allocator moved{std::move(allocator)}; @@ -462,46 +464,62 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_span_is_correct") TEST_CASE_FIXTURE(Fixture, "parse_error_messages") { - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: (number, number) -> (string )"), - "Expected ')' (to close '(' at line 2), got "); + "Expected ')' (to close '(' at line 2), got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: (number, number) -> ( string )"), - "Expected ')' (to close '(' at line 2), got "); + "Expected ')' (to close '(' at line 2), got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: (number, number) )"), - "Expected '->' when parsing function type, got "); + "Expected '->' when parsing function type, got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: (number, number )"), - "Expected ')' (to close '(' at line 2), got "); + "Expected ')' (to close '(' at line 2), got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: {foo: string, )"), - "Expected identifier when parsing table field, got "); + "Expected identifier when parsing table field, got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: {foo: string )"), - "Expected '}' (to close '{' at line 2), got "); + "Expected '}' (to close '{' at line 2), got " + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(R"( local a: { [string]: number, [number]: string } )"), - "Cannot have more than one table indexer"); + "Cannot have more than one table indexer" + ); - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(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") @@ -636,10 +654,12 @@ TEST_CASE_FIXTURE(Fixture, "vertical_space") TEST_CASE_FIXTURE(Fixture, "parse_error_type_name") { - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(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") @@ -701,10 +721,12 @@ TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error") TEST_CASE_FIXTURE(Fixture, "error_on_unicode") { - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(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") @@ -715,10 +737,12 @@ TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string") TEST_CASE_FIXTURE(Fixture, "error_on_confusable") { - CHECK_EQ(getParseError(R"( + CHECK_EQ( + getParseError(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") @@ -929,7 +953,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid") TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace") { - auto columnOfEndBraceError = [this](const char* code) { + auto columnOfEndBraceError = [this](const char* code) + { try { parse(code); @@ -1106,8 +1131,9 @@ end } catch (const ParseErrors& e) { - CHECK_EQ("Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", - e.getErrors().front().getMessage()); + CHECK_EQ( + "Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", e.getErrors().front().getMessage() + ); } } @@ -1135,8 +1161,9 @@ end } catch (const ParseErrors& e) { - CHECK_EQ("Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 3?", - e.getErrors().front().getMessage()); + CHECK_EQ( + "Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 3?", e.getErrors().front().getMessage() + ); } } @@ -1156,8 +1183,10 @@ until false } catch (const ParseErrors& e) { - CHECK_EQ("Expected 'until' (to close 'repeat' at line 2), got ; did you forget to close 'repeat' at line 4?", - e.getErrors().front().getMessage()); + CHECK_EQ( + "Expected 'until' (to close 'repeat' at line 2), got ; did you forget to close 'repeat' at line 4?", + e.getErrors().front().getMessage() + ); } } @@ -1192,8 +1221,9 @@ end } catch (const ParseErrors& e) { - CHECK_EQ("Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", - e.getErrors().front().getMessage()); + CHECK_EQ( + "Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", e.getErrors().front().getMessage() + ); } } @@ -1261,8 +1291,9 @@ end } catch (const ParseErrors& e) { - CHECK_EQ("Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", - e.getErrors().front().getMessage()); + CHECK_EQ( + "Expected 'end' (to close 'function' at line 2), got ; did you forget to close 'else' at line 8?", e.getErrors().front().getMessage() + ); } } @@ -1281,7 +1312,8 @@ end catch (const ParseErrors& e) { CHECK_EQ( - "Expected ')' (to close '(' at column 17), got '='; did you mean to use '{' when defining a table?", e.getErrors().front().getMessage()); + "Expected ')' (to close '(' at column 17), got '='; did you mean to use '{' when defining a table?", e.getErrors().front().getMessage() + ); } } @@ -1328,18 +1360,25 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_type_group") ScopedFastInt sfis{FInt::LuauRecursionLimit, 10}; matchParseError( - "function f(): ((((((((((Fail)))))))))) end", "Exceeded allowed recursion depth; simplify your type annotation to make the code compile"); + "function f(): ((((((((((Fail)))))))))) end", "Exceeded allowed recursion depth; simplify your type annotation to make the code compile" + ); - matchParseError("function f(): () -> () -> () -> () -> () -> () -> () -> () -> () -> () -> () end", - "Exceeded allowed recursion depth; simplify your type annotation to make the code compile"); + matchParseError( + "function f(): () -> () -> () -> () -> () -> () -> () -> () -> () -> () -> () end", + "Exceeded allowed recursion depth; simplify your type annotation to make the code compile" + ); - matchParseError("local t: {a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {}}}}}}}}}}}", - "Exceeded allowed recursion depth; simplify your type annotation to make the code compile"); + matchParseError( + "local t: {a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {}}}}}}}}}}}", + "Exceeded allowed recursion depth; simplify your type annotation to make the code compile" + ); matchParseError("local f: ((((((((((Fail))))))))))", "Exceeded allowed recursion depth; simplify your type annotation to make the code compile"); - matchParseError("local t: a & (b & (c & (d & (e & (f & (g & (h & (i & (j & nil)))))))))", - "Exceeded allowed recursion depth; simplify your type annotation to make the code compile"); + matchParseError( + "local t: a & (b & (c & (d & (e & (f & (g & (h & (i & (j & nil)))))))))", + "Exceeded allowed recursion depth; simplify your type annotation to make the code compile" + ); } TEST_CASE_FIXTURE(Fixture, "can_parse_complex_unions_successfully") @@ -1369,8 +1408,9 @@ local f: local f: a? | b? | c? | d? | e? | f? | g? | h? )"); - matchParseError("local t: a & b & c & d & e & f & g & h & i & j & nil", - "Exceeded allowed type length; simplify your type annotation to make the code compile"); + matchParseError( + "local t: a & b & c & d & e & f & g & h & i & j & nil", "Exceeded allowed type length; simplify your type annotation to make the code compile" + ); } TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements") @@ -1380,7 +1420,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements") matchParseErrorPrefix( "function f() if true then if true then if true then if true then if true then if true then if true then if true then if true " "then if true then if true then end end end end end end end end end end end end", - "Exceeded allowed recursion depth;"); + "Exceeded allowed recursion depth;" + ); } TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements") @@ -1390,16 +1431,19 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements" matchParseErrorPrefix( "function f() if false then elseif false then elseif false then elseif false then elseif false then elseif false then elseif " "false then elseif false then elseif false then elseif false then elseif false then end end", - "Exceeded allowed recursion depth;"); + "Exceeded allowed recursion depth;" + ); } TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions1") { ScopedFastInt sfis{FInt::LuauRecursionLimit, 10}; - matchParseError("function f() return if true then 1 elseif true then 2 elseif true then 3 elseif true then 4 elseif true then 5 elseif true then " - "6 elseif true then 7 elseif true then 8 elseif true then 9 elseif true then 10 else 11 end", - "Exceeded allowed recursion depth; simplify your expression to make the code compile"); + matchParseError( + "function f() return if true then 1 elseif true then 2 elseif true then 3 elseif true then 4 elseif true then 5 elseif true then " + "6 elseif true then 7 elseif true then 8 elseif true then 9 elseif true then 10 else 11 end", + "Exceeded allowed recursion depth; simplify your expression to make the code compile" + ); } TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions2") @@ -1409,16 +1453,19 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions2 matchParseError( "function f() return if if if if if if if if if if true then false else true then false else true then false else true then false else true " "then false else true then false else true then false else true then false else true then false else true then 1 else 2 end", - "Exceeded allowed recursion depth; simplify your expression to make the code compile"); + "Exceeded allowed recursion depth; simplify your expression to make the code compile" + ); } TEST_CASE_FIXTURE(Fixture, "unparenthesized_function_return_type_list") { matchParseError( - "function foo(): string, number end", "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?"); + "function foo(): string, number end", "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?" + ); - matchParseError("function foo(): (number) -> string, string", - "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?"); + matchParseError( + "function foo(): (number) -> string, string", "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?" + ); // Will throw if the parse fails parse(R"( @@ -1726,12 +1773,14 @@ TEST_CASE_FIXTURE(Fixture, "end_extent_doesnt_consume_comments_even_with_capture ParseOptions opts; opts.captureComments = true; - AstStatBlock* block = parse(R"( + AstStatBlock* block = parse( + R"( type F = number --comment print('hello') )", - opts); + opts + ); REQUIRE_EQ(2, block->body.size); CHECK_EQ((Position{1, 23}), block->body.data[0]->location.end); @@ -1747,45 +1796,53 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_loop_control") TEST_CASE_FIXTURE(Fixture, "parse_error_confusing_function_call") { - auto result1 = matchParseError(R"( + auto result1 = matchParseError( + R"( function add(x, y) return x + y end add (4, 7) )", "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of new statement; use ';' to separate " - "statements"); + "statements" + ); CHECK(result1.errors.size() == 1); - auto result2 = matchParseError(R"( + auto result2 = matchParseError( + R"( function add(x, y) return x + y end local f = add (f :: any)['x'] = 2 )", "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of new statement; use ';' to separate " - "statements"); + "statements" + ); CHECK(result2.errors.size() == 1); - auto result3 = matchParseError(R"( + auto result3 = matchParseError( + R"( local x = {} function x:add(a, b) return a + b end x:add (1, 2) )", "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of new statement; use ';' to separate " - "statements"); + "statements" + ); CHECK(result3.errors.size() == 1); - auto result4 = matchParseError(R"( + auto result4 = matchParseError( + R"( local t = {} function f() return t end t.x, (f) ().y = 5, 6 )", "Ambiguous syntax: this looks like an argument list for a function call, but could also be a start of new statement; use ';' to separate " - "statements"); + "statements" + ); CHECK(result4.errors.size() == 1); } @@ -1797,17 +1854,21 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_varargs") TEST_CASE_FIXTURE(Fixture, "parse_error_assignment_lvalue") { - matchParseError(R"( + matchParseError( + R"( local a, b (2), b = b, a )", - "Assigned expression must be a variable or a field"); + "Assigned expression must be a variable or a field" + ); - matchParseError(R"( + matchParseError( + R"( local a, b a, (3) = b, a )", - "Assigned expression must be a variable or a field"); + "Assigned expression must be a variable or a field" + ); } TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation") @@ -1948,14 +2009,16 @@ TEST_CASE_FIXTURE(Fixture, "parse_class_declarations") TEST_CASE_FIXTURE(Fixture, "class_method_properties") { - const ParseResult p1 = matchParseError(R"( + const ParseResult p1 = matchParseError( + R"( declare class Foo -- method's first parameter must be 'self' function method(foo: number) function method2(self) end )", - "'self' must be present as the unannotated first parameter"); + "'self' must be present as the unannotated first parameter" + ); REQUIRE_EQ(1, p1.root->body.size); @@ -1964,13 +2027,15 @@ TEST_CASE_FIXTURE(Fixture, "class_method_properties") CHECK_EQ(2, klass->props.size); - const ParseResult p2 = matchParseError(R"( + const ParseResult p2 = matchParseError( + R"( declare class Foo function method(self, foo) function method2() end )", - "All declaration parameters aside from 'self' must be annotated"); + "All declaration parameters aside from 'self' must be annotated" + ); REQUIRE_EQ(1, p2.root->body.size); @@ -2000,14 +2065,16 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer") REQUIRE(declaredClass->indexer->resultType->is()); CHECK(declaredClass->indexer->resultType->as()->name == "number"); - const ParseResult p1 = matchParseError(R"( + const ParseResult p1 = matchParseError( + R"( declare class Foo [string]: number -- can only have one indexer [number]: number end )", - "Cannot have more than one class indexer"); + "Cannot have more than one class indexer" + ); REQUIRE_EQ(1, p1.root->body.size); @@ -2200,8 +2267,9 @@ TEST_CASE_FIXTURE(Fixture, "function_type_named_arguments") CHECK_EQ(funcRet->argNames.data[2]->first, "f"); } - matchParseError("type MyFunc = (a: number, b: string, c: number) -> (d: number, e: string, f: number)", - "Expected '->' when parsing function type, got "); + matchParseError( + "type MyFunc = (a: number, b: string, c: number) -> (d: number, e: string, f: number)", "Expected '->' when parsing function type, got " + ); matchParseError("type MyFunc = (number) -> (d: number) -> number", "Expected '->' when parsing function type, got '<'"); } @@ -2235,8 +2303,11 @@ TEST_CASE_FIXTURE(Fixture, "parse_type_alias_default_type_errors") TEST_CASE_FIXTURE(Fixture, "parse_type_pack_errors") { - matchParseError("type Y = {a: T..., b: number}", "Unexpected '...' after type name; type pack is not allowed in this context", - Location{{0, 20}, {0, 23}}); + matchParseError( + "type Y = {a: T..., b: number}", + "Unexpected '...' after type name; type pack is not allowed in this context", + Location{{0, 20}, {0, 23}} + ); matchParseError("type Y = {a: (number | string)...", "Unexpected '...' after type annotation", Location{{0, 36}, {0, 39}}); } @@ -2314,6 +2385,22 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms") matchParseError("type F = (T...) -> ()", "Expected '->' when parsing function type, got '>'"); } +TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") +{ + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctions, true}; + + AstStat* stat = parse(R"( + type function foo() + return + end + )"); + + REQUIRE(stat != nullptr); + AstStatTypeFunction* f = stat->as()->body.data[0]->as(); + REQUIRE(f != nullptr); + REQUIRE(f->name == "foo"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("ParseErrorRecovery"); @@ -2481,7 +2568,8 @@ public: TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions") { - auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code) { + auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code) + { try { parse(codeWithErrors); @@ -2501,7 +2589,8 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions") CHECK_EQ(counterWithErrors.count, counter.count); }; - auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount) { + auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount) + { try { parse(codeWithErrors); @@ -2519,8 +2608,9 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions") }; checkRecovery("function foo(a, b. c) return a + b end", "function foo(a, b) return a + b end", 1); - checkRecovery("function foo(a, b: { a: number, b: number. c:number }) return a + b end", - "function foo(a, b: { a: number, b: number }) return a + b end", 1); + checkRecovery( + "function foo(a, b: { a: number, b: number. c:number }) return a + b end", "function foo(a, b: { a: number, b: number }) return a + b end", 1 + ); checkRecovery("function foo(a, b): (number -> number return a + b end", "function foo(a, b): (number) -> number return a + b end", 1); checkRecovery("function foo(a, b): (number, number -> number return a + b end", "function foo(a, b): (number) -> number return a + b end", 1); @@ -2537,12 +2627,15 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions") checkRecovery("local n: (string | number = 2", "local n: (string | number) = 2", 1); // Check that we correctly stop at the end of a line - checkRecovery(R"( + checkRecovery( + R"( function foo(a, b return a + b end )", - "function foo(a, b) return a + b end", 1); + "function foo(a, b) return a + b end", + 1 + ); } TEST_CASE_FIXTURE(Fixture, "incomplete_method_call") @@ -2636,7 +2729,8 @@ TEST_CASE_FIXTURE(Fixture, "capture_comments") ParseOptions options; options.captureComments = true; - ParseResult result = parseEx(R"( + ParseResult result = parseEx( + R"( --!strict local a = 5 -- comment one @@ -2646,7 +2740,8 @@ TEST_CASE_FIXTURE(Fixture, "capture_comments") ]] local c = 'see' )", - options); + options + ); CHECK(result.errors.empty()); @@ -2662,10 +2757,12 @@ TEST_CASE_FIXTURE(Fixture, "capture_broken_comment_at_the_start_of_the_file") ParseOptions options; options.captureComments = true; - ParseResult result = tryParse(R"( + ParseResult result = tryParse( + R"( --[[ )", - options); + options + ); CHECK_EQ(1, result.commentLocations.size()); CHECK_EQ((Location{{1, 8}, {2, 4}}), result.commentLocations[0].location); @@ -2676,12 +2773,14 @@ TEST_CASE_FIXTURE(Fixture, "capture_broken_comment") ParseOptions options; options.captureComments = true; - ParseResult result = tryParse(R"( + ParseResult result = tryParse( + R"( local a = "test" --[[broken! )", - options); + options + ); CHECK_EQ(1, result.commentLocations.size()); CHECK_EQ((Location{{3, 8}, {4, 4}}), result.commentLocations[0].location); @@ -2855,8 +2954,10 @@ TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation" type Foo = function )"); REQUIRE_EQ(1, result.errors.size()); - CHECK_EQ("Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> ...any'", - result.errors[0].getMessage()); + CHECK_EQ( + "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> ...any'", + result.errors[0].getMessage() + ); } TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_function_argument_list") @@ -3115,10 +3216,12 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails") ParseOptions opts; opts.allowDeclarationSyntax = true; - ParseResult pr = tryParse(R"( + ParseResult pr = tryParse( + R"( local @checked = 3 )", - opts); + opts + ); LUAU_ASSERT(pr.errors.size() > 0); auto ts = pr.errors[1].getMessage(); } @@ -3128,11 +3231,13 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails") ParseOptions opts; opts.allowDeclarationSyntax = true; - auto pr = tryParse(R"( + auto pr = tryParse( + R"( local @checked = 3 @checked declare function abs(n: number): number )", - opts); + opts + ); LUAU_ASSERT(pr.errors.size() == 2); LUAU_ASSERT(pr.errors[0].getLocation().begin.line == 1); LUAU_ASSERT(pr.errors[1].getLocation().begin.line == 1); @@ -3143,11 +3248,13 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails") ParseOptions opts; opts.allowDeclarationSyntax = true; - auto pr = tryParse(R"( + auto pr = tryParse( + R"( @checked function(x: number) : number end )", - opts); + opts + ); LUAU_ASSERT(pr.errors.size() > 0); } @@ -3156,10 +3263,12 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name") ParseOptions opts; opts.allowDeclarationSyntax = true; - auto pr = tryParse(R"( + auto pr = tryParse( + R"( local @blah = 3 )", - opts); + opts + ); LUAU_ASSERT(pr.errors.size() > 0); } @@ -3296,8 +3405,12 @@ TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_stat") ParseResult pr1 = tryParse(R"( @checked if a<0 then a = 0 end)"); - checkFirstErrorForAttributes(pr1.errors, 1, Location(Position(2, 0), Position(2, 2)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'if' instead"); + checkFirstErrorForAttributes( + pr1.errors, + 1, + Location(Position(2, 0), Position(2, 2)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'if' instead" + ); ParseResult pr2 = tryParse(R"( local i = 1 @@ -3306,8 +3419,12 @@ while a[i] do print(a[i]) i = i + 1 end)"); - checkFirstErrorForAttributes(pr2.errors, 1, Location(Position(3, 0), Position(3, 5)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'while' instead"); + checkFirstErrorForAttributes( + pr2.errors, + 1, + Location(Position(3, 0), Position(3, 5)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'while' instead" + ); ParseResult pr3 = tryParse(R"( @checked @@ -3317,15 +3434,23 @@ do x1 = (-b + d)/a2 x2 = (-b - d)/a2 end)"); - checkFirstErrorForAttributes(pr3.errors, 1, Location(Position(2, 0), Position(2, 2)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'do' instead"); + checkFirstErrorForAttributes( + pr3.errors, + 1, + Location(Position(2, 0), Position(2, 2)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'do' instead" + ); ParseResult pr4 = tryParse(R"( @checked for i=1,10 do print(i) end )"); - checkFirstErrorForAttributes(pr4.errors, 1, Location(Position(2, 0), Position(2, 3)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'for' instead"); + checkFirstErrorForAttributes( + pr4.errors, + 1, + Location(Position(2, 0), Position(2, 3)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'for' instead" + ); ParseResult pr5 = tryParse(R"( @checked @@ -3333,8 +3458,12 @@ repeat line = io.read() until line ~= "" )"); - checkFirstErrorForAttributes(pr5.errors, 1, Location(Position(2, 0), Position(2, 6)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'repeat' instead"); + checkFirstErrorForAttributes( + pr5.errors, + 1, + Location(Position(2, 0), Position(2, 6)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'repeat' instead" + ); ParseResult pr6 = tryParse(R"( @@ -3342,7 +3471,8 @@ until line ~= "" local x = 10 )"); checkFirstErrorForAttributes( - pr6.errors, 1, Location(Position(2, 6), Position(2, 7)), "Expected 'function' after local declaration with attribute, but got 'x' instead"); + pr6.errors, 1, Location(Position(2, 6), Position(2, 7)), "Expected 'function' after local declaration with attribute, but got 'x' instead" + ); ParseResult pr7 = tryParse(R"( local i = 1 @@ -3351,15 +3481,23 @@ while a[i] do i = i + 1 end )"); - checkFirstErrorForAttributes(pr7.errors, 1, Location(Position(3, 31), Position(3, 36)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'break' instead"); + checkFirstErrorForAttributes( + pr7.errors, + 1, + Location(Position(3, 31), Position(3, 36)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'break' instead" + ); ParseResult pr8 = tryParse(R"( function foo1 () @checked return 'a' end )"); - checkFirstErrorForAttributes(pr8.errors, 1, Location(Position(1, 26), Position(1, 32)), - "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'return' instead"); + checkFirstErrorForAttributes( + pr8.errors, + 1, + Location(Position(1, 26), Position(1, 32)), + "Expected 'function', 'local function', 'declare function' or a function type declaration after attribute, but got 'return' instead" + ); } TEST_CASE_FIXTURE(Fixture, "dont_parse_attribute_on_argument_non_function") @@ -3375,7 +3513,8 @@ invoker(function(x) return (x + 2) end, @checked 1) )"); checkFirstErrorForAttributes( - pr.errors, 1, Location(Position(5, 40), Position(5, 48)), "Expected 'function' declaration after attribute, but got '1' instead"); + pr.errors, 1, Location(Position(5, 40), Position(5, 48)), "Expected 'function' declaration after attribute, but got '1' instead" + ); } TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_type_declaration") @@ -3444,32 +3583,41 @@ TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_type_declarati ParseOptions opts; opts.allowDeclarationSyntax = true; - ParseResult pr1 = tryParse(R"( + ParseResult pr1 = tryParse( + R"( @checked declare foo: number )", - opts); + opts + ); checkFirstErrorForAttributes( - pr1.errors, 1, Location(Position(1, 17), Position(1, 20)), "Expected a function type declaration after attribute, but got 'foo' instead"); + pr1.errors, 1, Location(Position(1, 17), Position(1, 20)), "Expected a function type declaration after attribute, but got 'foo' instead" + ); - ParseResult pr2 = tryParse(R"( + ParseResult pr2 = tryParse( + R"( @checked declare class Foo prop: number function method(self, foo: number): string end)", - opts); + opts + ); checkFirstErrorForAttributes( - pr2.errors, 1, Location(Position(1, 17), Position(1, 22)), "Expected a function type declaration after attribute, but got 'class' instead"); + pr2.errors, 1, Location(Position(1, 17), Position(1, 22)), "Expected a function type declaration after attribute, but got 'class' instead" + ); - ParseResult pr3 = tryParse(R"( + ParseResult pr3 = tryParse( + R"( declare bit32: { band: @checked number })", - opts); + opts + ); checkFirstErrorForAttributes( - pr3.errors, 1, Location(Position(2, 19), Position(2, 25)), "Expected '(' when parsing function parameters, got 'number'"); + pr3.errors, 1, Location(Position(2, 19), Position(2, 25)), "Expected '(' when parsing function parameters, got 'number'" + ); } TEST_CASE_FIXTURE(Fixture, "attributes_cannot_be_duplicated") diff --git a/tests/Repl.test.cpp b/tests/Repl.test.cpp index 3eceea17..a0de6f10 100644 --- a/tests/Repl.test.cpp +++ b/tests/Repl.test.cpp @@ -52,9 +52,14 @@ public: { CompletionSet result; int top = lua_gettop(L); - getCompletions(L, inputPrefix, [&result](const std::string& completion, const std::string& display) { - result.insert(Completion{completion, display}); - }); + getCompletions( + L, + inputPrefix, + [&result](const std::string& completion, const std::string& display) + { + result.insert(Completion{completion, display}); + } + ); // Ensure that generating completions doesn't change the position of luau's stack top. CHECK(top == lua_gettop(L)); diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index c4f1fb7c..7f2300a5 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -247,7 +247,8 @@ TEST_CASE("PathNormalization") CHECK(result); std::string normalized = *result; std::vector variants = { - "./.././.././modules/./module/", "placeholder/../../../modules/module", "../placeholder/placeholder2/../../../modules/module"}; + "./.././.././modules/./module/", "placeholder/../../../modules/module", "../placeholder/placeholder2/../../../modules/module" + }; for (const std::string& variant : variants) { result = normalizePath(variant); @@ -259,8 +260,11 @@ TEST_CASE("PathNormalization") result = normalizePath(prefix + "Users/modules/module"); CHECK(result); normalized = *result; - variants = {"Users/Users/Users/.././.././modules/./module/", "placeholder/../Users/..//Users/modules/module", - "Users/../placeholder/placeholder2/../../Users/modules/module"}; + variants = { + "Users/Users/Users/.././.././modules/./module/", + "placeholder/../Users/..//Users/modules/module", + "Users/../placeholder/placeholder2/../../Users/modules/module" + }; for (const std::string& variant : variants) { result = normalizePath(prefix + variant); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 75c9eda0..41418aa8 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -30,9 +30,14 @@ struct LimitFixture : BuiltinsFixture template bool hasError(const CheckResult& result, T* = nullptr) { - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& a) { - return nullptr != get(a); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](const TypeError& a) + { + return nullptr != get(a); + } + ); return it != result.errors.end(); } diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index b938b5f8..08b2fc4f 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -385,11 +385,38 @@ TEST_CASE_FIXTURE(SimplifyFixture, "tables") CHECK(t2 == intersect(t2, t1)); TypeId t3 = mkTable({}); - // {tag : string} intersect {{}} + // {tag : string} intersect {} CHECK(t1 == intersect(t1, t3)); CHECK(t1 == intersect(t3, t1)); } +TEST_CASE_FIXTURE(SimplifyFixture, "combine_disjoint_sealed_tables") +{ + TypeId t1 = mkTable({{"prop", stringTy}}); + TypeId t2 = mkTable({{"second_prop", numberTy}}); + + CHECK("{ prop: string, second_prop: number }" == toString(intersect(t1, t2))); +} + +TEST_CASE_FIXTURE(SimplifyFixture, "non_disjoint_tables_do_not_simplify") +{ + TypeId t1 = mkTable({{"prop", stringTy}}); + TypeId t2 = mkTable({{"prop", unknownTy}, {"second_prop", numberTy}}); + + CHECK("{ prop: string } & { prop: unknown, second_prop: number }" == toString(intersect(t1, t2))); +} + +// Simplification has an extra code path especially for intersections with +// single-property tables, so it's worthwhile to separately test the case where +// both tables have multiple properties. +TEST_CASE_FIXTURE(SimplifyFixture, "non_disjoint_tables_do_not_simplify_2") +{ + TypeId t1 = mkTable({{"prop", stringTy}, {"third_prop", numberTy}}); + TypeId t2 = mkTable({{"prop", unknownTy}, {"second_prop", numberTy}}); + + CHECK("{ prop: string, third_prop: number } & { prop: unknown, second_prop: number }" == toString(intersect(t1, t2))); +} + TEST_CASE_FIXTURE(SimplifyFixture, "tables_and_top_table") { TypeId notTableType = mkNegation(tableTy); @@ -424,16 +451,18 @@ TEST_CASE_FIXTURE(SimplifyFixture, "table_with_a_tag") TEST_CASE_FIXTURE(SimplifyFixture, "nested_table_tag_test") { TypeId t1 = mkTable({ - {"subtable", mkTable({ - {"tag", helloTy}, - {"subprop", numberTy}, - })}, + {"subtable", + mkTable({ + {"tag", helloTy}, + {"subprop", numberTy}, + })}, {"prop", stringTy}, }); TypeId t2 = mkTable({ - {"subtable", mkTable({ - {"tag", helloTy}, - })}, + {"subtable", + mkTable({ + {"tag", helloTy}, + })}, }); CHECK(t1 == intersect(t1, t2)); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index a748de62..64aa00ac 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -219,15 +219,19 @@ struct SubtypeFixture : Fixture TypeId anotherGrandchildOneClass = cls("AnotherGrandchildOne", anotherChildClass); TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass); - TypeId vec2Class = cls("Vec2", { - {"X", builtinTypes->numberType}, - {"Y", builtinTypes->numberType}, - }); + TypeId vec2Class = + cls("Vec2", + { + {"X", builtinTypes->numberType}, + {"Y", builtinTypes->numberType}, + }); - TypeId readOnlyVec2Class = cls("ReadOnlyVec2", { - {"X", Property::readonly(builtinTypes->numberType)}, - {"Y", Property::readonly(builtinTypes->numberType)}, - }); + TypeId readOnlyVec2Class = + cls("ReadOnlyVec2", + { + {"X", Property::readonly(builtinTypes->numberType)}, + {"Y", Property::readonly(builtinTypes->numberType)}, + }); // "hello" | "hello" TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); @@ -348,13 +352,16 @@ struct SubtypeFixture : Fixture /* we have to mark this as `static` to ensure the memory remains alive \ for the entirety of the test process */ \ static std::string name = der().testName; \ - doctest::detail::regTest(doctest::detail::TestCase(run, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite()) /* the test case's name, determined at runtime */ \ - * name.c_str() /* getCurrentTestSuite() only works at static initialization \ - time due to implementation details. To ensure that test cases \ - are grouped where they should be, manually override the suite \ - with the test_suite decorator. */ \ - * doctest::test_suite("Subtyping")); \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + run, __FILE__, __LINE__, doctest_detail_test_suite_ns::getCurrentTestSuite() \ + ) /* the test case's name, determined at runtime */ \ + * name.c_str() /* getCurrentTestSuite() only works at static initialization \ + time due to implementation details. To ensure that test cases \ + are grouped where they should be, manually override the suite \ + with the test_suite decorator. */ \ + * doctest::test_suite("Subtyping") \ + ); \ } \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), addTestCallback(reg)); @@ -461,12 +468,22 @@ TEST_CASE_FIXTURE(SubtypeFixture, "variadic_subpath_in_pack") SubtypingResult result = isSubtype(functionSub, functionSuper); - CHECK(result.reasoning == std::vector{SubtypingReasoning{TypePath::PathBuilder().rets().index(0).build(), - TypePath::PathBuilder().rets().index(0).build(), SubtypingVariance::Covariant}, - SubtypingReasoning{TypePath::PathBuilder().args().index(0).build(), TypePath::PathBuilder().args().index(0).build(), - SubtypingVariance::Contravariant}, - SubtypingReasoning{TypePath::PathBuilder().args().index(1).build(), - TypePath::PathBuilder().args().tail().variadic().build(), SubtypingVariance::Contravariant}}); + CHECK( + result.reasoning == + std::vector{ + SubtypingReasoning{ + TypePath::PathBuilder().rets().index(0).build(), TypePath::PathBuilder().rets().index(0).build(), SubtypingVariance::Covariant + }, + SubtypingReasoning{ + TypePath::PathBuilder().args().index(0).build(), TypePath::PathBuilder().args().index(0).build(), SubtypingVariance::Contravariant + }, + SubtypingReasoning{ + TypePath::PathBuilder().args().index(1).build(), + TypePath::PathBuilder().args().tail().variadic().build(), + SubtypingVariance::Contravariant + } + } + ); CHECK(!result.isSubtype); } @@ -996,13 +1013,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 wh [&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); - }); + } + ); TypeId t2 = cyclicTable( [&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); - }); + } + ); CHECK_IS_SUBTYPE(t1, t2); } @@ -1013,13 +1032,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} props["trim"] = fn({ty}, {builtinTypes->stringType}); - }); + } + ); TypeId t2 = cyclicTable( [&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {ty}); - }); + } + ); CHECK_IS_NOT_SUBTYPE(t1, t2); } @@ -1030,13 +1051,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} props["trim"] = fn({ty}, {ty}); - }); + } + ); TypeId t2 = cyclicTable( [&](TypeId ty, TableType* tt) { tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); - }); + } + ); CHECK_IS_NOT_SUBTYPE(t1, t2); } @@ -1107,7 +1130,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}})); + readOnlyVec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}) + ); } TEST_IS_SUBTYPE(vec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}})); @@ -1189,10 +1213,65 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: (T...) -> ()") // See https://github.com/luau-lang/luau/issues/767 TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: (T...) -> ()") { - TypeId anysToNothing = arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack}); + TypeId unknownsToNothing = arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack}); TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack}); - CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: (T...) -> ()"); + CHECK_MESSAGE(subtyping.isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: (T...) -> ()"); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "bill") +{ + TypeId a = arena.addType(TableType{ + {{"a", builtinTypes->stringType}}, + TableIndexer{ + builtinTypes->stringType, + builtinTypes->numberType + }, + TypeLevel{}, + nullptr, + TableState::Sealed + }); + + TypeId b = arena.addType(TableType{ + {{"a", builtinTypes->stringType}}, + TableIndexer{ + builtinTypes->stringType, + builtinTypes->numberType + }, + TypeLevel{}, + nullptr, + TableState::Sealed + }); + + CHECK(subtyping.isSubtype(a, b).isSubtype); + CHECK(subtyping.isSubtype(b, a).isSubtype); +} + +// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()") +TEST_CASE_FIXTURE(SubtypeFixture, "fred") +{ + auto makeTheType = [&]() { + TypeId argType = arena.addType(TableType{ + {{"a", builtinTypes->stringType}}, + TableIndexer{ + builtinTypes->stringType, + builtinTypes->numberType + }, + TypeLevel{}, + nullptr, + TableState::Sealed + }); + + return arena.addType(FunctionType { + arena.addTypePack({argType}), + builtinTypes->emptyTypePack + }); + }; + + TypeId a = makeTheType(); + TypeId b = makeTheType(); + + CHECK_MESSAGE(subtyping.isSubtype(a, b).isSubtype, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()"); } /* @@ -1230,14 +1309,22 @@ TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->nu TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}})); TEST_IS_NOT_SUBTYPE( - idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType)); + idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), + idx(builtinTypes->numberType, builtinTypes->numberType) +); TEST_IS_NOT_SUBTYPE( - idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType)); + idx(builtinTypes->numberType, builtinTypes->numberType), + idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType) +); TEST_IS_NOT_SUBTYPE( - idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType)); + idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), + idx(builtinTypes->numberType, builtinTypes->numberType) +); TEST_IS_NOT_SUBTYPE( - idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType))); + idx(builtinTypes->numberType, builtinTypes->numberType), + idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)) +); TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->stringType, builtinTypes->numberType)); TEST_IS_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}})); @@ -1302,8 +1389,16 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") { - TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType, - builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}}); + TypeId longTy = arena.addType(UnionType{ + {builtinTypes->booleanType, + builtinTypes->bufferType, + builtinTypes->classType, + builtinTypes->functionType, + builtinTypes->numberType, + builtinTypes->stringType, + builtinTypes->tableType, + builtinTypes->threadType} + }); TypeId tblTy = tbl({{"depth", builtinTypes->unknownType}}); TypeId combined = meet(longTy, tblTy); TypeId subTy = arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.unionFunc}, {combined, builtinTypes->neverType}, {}}); @@ -1336,9 +1431,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); REQUIRE(result.reasoning.size() == 1); - CHECK(*result.reasoning.begin() == SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), - /* superPath */ Path(TypePath::Property::read("X")), - /* variance */ SubtypingVariance::Invariant}); + CHECK( + *result.reasoning.begin() == SubtypingReasoning{ + /* subPath */ Path(TypePath::Property::read("X")), + /* superPath */ Path(TypePath::Property::read("X")), + /* variance */ SubtypingVariance::Invariant + } + ); } TEST_CASE_FIXTURE(SubtypeFixture, "table_indexers") @@ -1348,16 +1447,21 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_indexers") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ Path(TypePath::TypeField::IndexLookup), - /* superPath */ Path(TypePath::TypeField::IndexLookup), - /* variance */ SubtypingVariance::Invariant, - }, - SubtypingReasoning{ - /* subPath */ Path(TypePath::TypeField::IndexResult), - /* superPath */ Path(TypePath::TypeField::IndexResult), - /* variance */ SubtypingVariance::Invariant, - }}); + CHECK( + result.reasoning == + std::vector{ + SubtypingReasoning{ + /* subPath */ Path(TypePath::TypeField::IndexLookup), + /* superPath */ Path(TypePath::TypeField::IndexLookup), + /* variance */ SubtypingVariance::Invariant, + }, + SubtypingReasoning{ + /* subPath */ Path(TypePath::TypeField::IndexResult), + /* superPath */ Path(TypePath::TypeField::IndexResult), + /* variance */ SubtypingVariance::Invariant, + } + } + ); } TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments") @@ -1367,11 +1471,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().args().index(0).build(), - /* superPath */ TypePath::PathBuilder().args().index(0).build(), - /* variance */ SubtypingVariance::Contravariant, - }}); + CHECK( + result.reasoning == std::vector{SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().args().index(0).build(), + /* superPath */ TypePath::PathBuilder().args().index(0).build(), + /* variance */ SubtypingVariance::Contravariant, + }} + ); } TEST_CASE_FIXTURE(SubtypeFixture, "arity_mismatch") @@ -1381,11 +1487,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "arity_mismatch") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().args().build(), - /* superPath */ TypePath::PathBuilder().args().build(), - /* variance */ SubtypingVariance::Contravariant, - }}); + CHECK( + result.reasoning == std::vector{SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().args().build(), + /* superPath */ TypePath::PathBuilder().args().build(), + /* variance */ SubtypingVariance::Contravariant, + }} + ); } TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail") @@ -1395,11 +1503,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().args().tail().variadic().build(), - /* superPath */ TypePath::PathBuilder().args().tail().variadic().build(), - /* variance */ SubtypingVariance::Contravariant, - }}); + CHECK( + result.reasoning == std::vector{SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().args().tail().variadic().build(), + /* superPath */ TypePath::PathBuilder().args().tail().variadic().build(), + /* variance */ SubtypingVariance::Contravariant, + }} + ); } TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets") @@ -1410,10 +1520,12 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); REQUIRE(result.reasoning.size() == 1); - CHECK(*result.reasoning.begin() == SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().rets().index(0).build(), - /* superPath */ TypePath::PathBuilder().rets().index(0).build(), - }); + CHECK( + *result.reasoning.begin() == SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().rets().index(0).build(), + /* superPath */ TypePath::PathBuilder().rets().index(0).build(), + } + ); } TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets_tail") @@ -1424,10 +1536,12 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets_tail") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); REQUIRE(result.reasoning.size() == 1); - CHECK(*result.reasoning.begin() == SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().rets().tail().variadic().build(), - /* superPath */ TypePath::PathBuilder().rets().tail().variadic().build(), - }); + CHECK( + *result.reasoning.begin() == SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().rets().tail().variadic().build(), + /* superPath */ TypePath::PathBuilder().rets().tail().variadic().build(), + } + ); } TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties") @@ -1438,11 +1552,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); REQUIRE(result.reasoning.size() == 1); - CHECK(*result.reasoning.begin() == SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), - /* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), - /* variance */ SubtypingVariance::Invariant, - }); + CHECK( + *result.reasoning.begin() == SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), + /* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), + /* variance */ SubtypingVariance::Invariant, + } + ); } TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt") @@ -1456,10 +1572,12 @@ TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt") // the string metatable. That means subtyping will see that the entire // metatable is empty, and abort there, without looking at the metatable // properties (because there aren't any). - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(), - /* superPath */ TypePath::kEmpty, - }}); + CHECK( + result.reasoning == std::vector{SubtypingReasoning{ + /* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(), + /* superPath */ TypePath::kEmpty, + }} + ); } TEST_CASE_FIXTURE(SubtypeFixture, "negation") @@ -1469,10 +1587,12 @@ TEST_CASE_FIXTURE(SubtypeFixture, "negation") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ - /* subPath */ TypePath::kEmpty, - /* superPath */ Path(TypePath::TypeField::Negated), - }}); + CHECK( + result.reasoning == std::vector{SubtypingReasoning{ + /* subPath */ TypePath::kEmpty, + /* superPath */ Path(TypePath::TypeField::Negated), + }} + ); } TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings") @@ -1482,13 +1602,21 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == - std::vector{ - SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")), - /* variance */ SubtypingVariance::Invariant}, - SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), /* superPath */ Path(TypePath::Property::read("Y")), - /* variance */ SubtypingVariance::Invariant}, - }); + CHECK( + result.reasoning == + std::vector{ + SubtypingReasoning{ + /* subPath */ Path(TypePath::Property::read("X")), + /* superPath */ Path(TypePath::Property::read("X")), + /* variance */ SubtypingVariance::Invariant + }, + SubtypingReasoning{ + /* subPath */ Path(TypePath::Property::read("Y")), + /* superPath */ Path(TypePath::Property::read("Y")), + /* variance */ SubtypingVariance::Invariant + }, + } + ); } TEST_SUITE_END(); diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index 16f4dc48..de7f4e50 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -44,30 +44,40 @@ TEST_SUITE_BEGIN("ToDot"); TEST_CASE_FIXTURE(Fixture, "primitive") { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="nil"]; })", - toDot(builtinTypes->nilType)); + toDot(builtinTypes->nilType) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="number"]; })", - toDot(builtinTypes->numberType)); + toDot(builtinTypes->numberType) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="any"]; })", - toDot(builtinTypes->anyType)); + toDot(builtinTypes->anyType) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="unknown"]; })", - toDot(builtinTypes->unknownType)); + toDot(builtinTypes->unknownType) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="never"]; })", - toDot(builtinTypes->neverType)); + toDot(builtinTypes->neverType) + ); } TEST_CASE_FIXTURE(Fixture, "no_duplicatePrimitives") @@ -76,25 +86,33 @@ TEST_CASE_FIXTURE(Fixture, "no_duplicatePrimitives") opts.showPointers = false; opts.duplicatePrimitives = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="PrimitiveType number"]; })", - toDot(builtinTypes->numberType, opts)); + toDot(builtinTypes->numberType, opts) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="AnyType 1"]; })", - toDot(builtinTypes->anyType, opts)); + toDot(builtinTypes->anyType, opts) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="UnknownType 1"]; })", - toDot(builtinTypes->unknownType, opts)); + toDot(builtinTypes->unknownType, opts) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="NeverType 1"]; })", - toDot(builtinTypes->neverType, opts)); + toDot(builtinTypes->neverType, opts) + ); } TEST_CASE_FIXTURE(Fixture, "bound") @@ -105,12 +123,14 @@ TEST_CASE_FIXTURE(Fixture, "bound") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="BoundType 1"]; n1 -> n2; n2 [label="number"]; })", - toDot(ty, opts)); + toDot(ty, opts) + ); } TEST_CASE_FIXTURE(Fixture, "function") @@ -127,7 +147,8 @@ local function f(a, ...: string) return a end if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="FunctionType 1"]; n1 -> n2 [label="arg"]; n2 [label="TypePack 2"]; @@ -141,11 +162,13 @@ n1 -> n6 [label="ret"]; n6 [label="TypePack 6"]; n6 -> n3; })", - toDot(requireType("f"), opts)); + toDot(requireType("f"), opts) + ); } else { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="FunctionType 1"]; n1 -> n2 [label="arg"]; n2 [label="TypePack 2"]; @@ -161,7 +184,8 @@ n6 -> n7; n7 [label="TypePack 7"]; n7 -> n3; })", - toDot(requireType("f"), opts)); + toDot(requireType("f"), opts) + ); } } @@ -174,14 +198,16 @@ local a: string | number ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="UnionType 1"]; n1 -> n2; n2 [label="string"]; n1 -> n3; n3 [label="number"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts) + ); } TEST_CASE_FIXTURE(Fixture, "intersection") @@ -192,14 +218,16 @@ TEST_CASE_FIXTURE(Fixture, "intersection") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="IntersectionType 1"]; n1 -> n2; n2 [label="string"]; n1 -> n3; n3 [label="number"]; })", - toDot(ty, opts)); + toDot(ty, opts) + ); } TEST_CASE_FIXTURE(Fixture, "table") @@ -214,7 +242,8 @@ local a: A opts.showPointers = false; if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="TableType A"]; n1 -> n2 [label="x"]; n2 [label="number"]; @@ -234,11 +263,13 @@ n1 -> n9 [label="typeParam"]; n9 [label="number"]; n1 -> n4 [label="typePackParam"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts) + ); } else { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="TableType A"]; n1 -> n2 [label="x"]; n2 [label="number"]; @@ -258,7 +289,8 @@ n1 -> n9 [label="typeParam"]; n9 [label="number"]; n1 -> n4 [label="typePackParam"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts) + ); } // Extra coverage with pointers (unstable values) @@ -274,14 +306,16 @@ local a: typeof(setmetatable({}, {})) ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="MetatableType 1"]; n1 -> n2 [label="table"]; n2 [label="TableType 2"]; n1 -> n3 [label="metatable"]; n3 [label="TableType 3"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts) + ); } TEST_CASE_FIXTURE(Fixture, "free") @@ -294,10 +328,12 @@ TEST_CASE_FIXTURE(Fixture, "free") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="FreeType 1"]; })", - toDot(&type, opts)); + toDot(&type, opts) + ); } TEST_CASE_FIXTURE(Fixture, "free_with_constraints") @@ -310,7 +346,8 @@ TEST_CASE_FIXTURE(Fixture, "free_with_constraints") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="FreeType 1"]; n1 -> n2 [label="[lowerBound]"]; n2 [label="number"]; @@ -321,7 +358,8 @@ n4 [label="number"]; n3 -> n5; n5 [label="nil"]; })", - toDot(&type, opts)); + toDot(&type, opts) + ); } TEST_CASE_FIXTURE(Fixture, "error") @@ -330,10 +368,12 @@ TEST_CASE_FIXTURE(Fixture, "error") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="ErrorType 1"]; })", - toDot(&type, opts)); + toDot(&type, opts) + ); } TEST_CASE_FIXTURE(Fixture, "generic") @@ -342,10 +382,12 @@ TEST_CASE_FIXTURE(Fixture, "generic") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="GenericType T"]; })", - toDot(&type, opts)); + toDot(&type, opts) + ); } TEST_CASE_FIXTURE(ToDotClassFixture, "class") @@ -357,7 +399,8 @@ local a: ChildClass ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="ClassType ChildClass"]; n1 -> n2 [label="ChildField"]; n2 [label="string"]; @@ -368,7 +411,8 @@ n4 [label="number"]; n3 -> n5 [label="[metatable]"]; n5 [label="TableType 5"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts) + ); } TEST_CASE_FIXTURE(Fixture, "free_pack") @@ -377,10 +421,12 @@ TEST_CASE_FIXTURE(Fixture, "free_pack") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="FreeTypePack 1"]; })", - toDot(&pack, opts)); + toDot(&pack, opts) + ); } TEST_CASE_FIXTURE(Fixture, "error_pack") @@ -389,10 +435,12 @@ TEST_CASE_FIXTURE(Fixture, "error_pack") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="ErrorTypePack 1"]; })", - toDot(&pack, opts)); + toDot(&pack, opts) + ); // Extra coverage with pointers (unstable values) (void)toDot(&pack); @@ -405,15 +453,19 @@ TEST_CASE_FIXTURE(Fixture, "generic_pack") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="GenericTypePack 1"]; })", - toDot(&pack1, opts)); + toDot(&pack1, opts) + ); - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="GenericTypePack T"]; })", - toDot(&pack2, opts)); + toDot(&pack2, opts) + ); } TEST_CASE_FIXTURE(Fixture, "bound_pack") @@ -423,14 +475,16 @@ TEST_CASE_FIXTURE(Fixture, "bound_pack") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="BoundTypePack 1"]; n1 -> n2; n2 [label="TypePack 2"]; n2 -> n3; n3 [label="number"]; })", - toDot(&bound, opts)); + toDot(&bound, opts) + ); } TEST_CASE_FIXTURE(Fixture, "bound_table") @@ -446,14 +500,16 @@ TEST_CASE_FIXTURE(Fixture, "bound_table") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="TableType 1"]; n1 -> n2 [label="boundTo"]; n2 [label="TableType 2"]; n2 -> n3 [label="x"]; n3 [label="number"]; })", - toDot(boundTy, opts)); + toDot(boundTy, opts) + ); } TEST_CASE_FIXTURE(Fixture, "builtintypes") @@ -465,20 +521,22 @@ TEST_CASE_FIXTURE(Fixture, "builtintypes") ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + CHECK_EQ( + R"(digraph graphname { n1 [label="UnionType 1"]; n1 -> n2; n2 [label="SingletonType string: hi"]; n1 -> n3; )" - "n3 [label=\"SingletonType string: \\\"hello\\\"\"];" - R"( + "n3 [label=\"SingletonType string: \\\"hello\\\"\"];" + R"( n1 -> n4; n4 [label="SingletonType boolean: true"]; n1 -> n5; n5 [label="SingletonType boolean: false"]; })", - toDot(requireType("x"), opts)); + toDot(requireType("x"), opts) + ); } TEST_CASE_FIXTURE(Fixture, "negation") diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 18697797..828045d4 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -12,7 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(DebugLuauSharedSelf); +LUAU_FASTFLAG(LuauAttributeSyntax); TEST_SUITE_BEGIN("ToString"); @@ -97,19 +97,23 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break") //clang-format off if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("{\n" - " anotherProp: number,\n" - " prop: string,\n" - " thirdProp: boolean\n" - "}", - toString(requireType("a"), opts)); + CHECK_EQ( + "{\n" + " anotherProp: number,\n" + " prop: string,\n" + " thirdProp: boolean\n" + "}", + toString(requireType("a"), opts) + ); else - CHECK_EQ("{|\n" - " anotherProp: number,\n" - " prop: string,\n" - " thirdProp: boolean\n" - "|}", - toString(requireType("a"), opts)); + CHECK_EQ( + "{|\n" + " anotherProp: number,\n" + " prop: string,\n" + " thirdProp: boolean\n" + "|}", + toString(requireType("a"), opts) + ); //clang-format on } @@ -196,10 +200,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table") CHECK_EQ(std::string::npos, a.find("TRUNCATED")); //clang-format off - CHECK_EQ("t2 where " - "t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; " - "t2 = { @metatable t1, {| x: number, y: number, z: number |} }", - a); + CHECK_EQ( + "t2 where " + "t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; " + "t2 = { @metatable t1, {| x: number, y: number, z: number |} }", + a + ); //clang-format on } @@ -258,10 +264,12 @@ TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines") opts.compositeTypesSingleLineLimit = 2; //clang-format off - CHECK_EQ("boolean\n" - "& number\n" - "& string", - toString(requireType("a"), opts)); + CHECK_EQ( + "boolean\n" + "& number\n" + "& string", + toString(requireType("a"), opts) + ); //clang-format on } @@ -275,9 +283,11 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_line opts.useLineBreaks = true; //clang-format off - CHECK_EQ("((number) -> number)\n" - "& ((string) -> string)", - toString(requireType("a"), opts)); + CHECK_EQ( + "((number) -> number)\n" + "& ((string) -> string)", + toString(requireType("a"), opts) + ); //clang-format on } @@ -304,10 +314,12 @@ TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines") opts.useLineBreaks = true; //clang-format off - CHECK_EQ("boolean\n" - "| number\n" - "| string", - toString(requireType("a"), opts)); + CHECK_EQ( + "boolean\n" + "| number\n" + "| string", + toString(requireType("a"), opts) + ); //clang-format on } @@ -530,10 +542,12 @@ TEST_CASE_FIXTURE(Fixture, "generate_friendly_names_for_inferred_generics") CHECK_EQ("(a) -> a", toString(requireType("id"))); - CHECK_EQ("(a, b, c, d, e, f, g, h, i, j, k, l, " - "m, n, o, p, q, r, s, t, u, v, w, x, y, z, a1, b1, c1, d1) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, " - "x, y, z, a1, b1, c1, d1)", - toString(requireType("id2"))); + CHECK_EQ( + "(a, b, c, d, e, f, g, h, i, j, k, l, " + "m, n, o, p, q, r, s, t, u, v, w, x, y, z, a1, b1, c1, d1) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, " + "x, y, z, a1, b1, c1, d1)", + toString(requireType("id2")) + ); } TEST_CASE_FIXTURE(Fixture, "toStringDetailed") @@ -566,62 +580,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed") CHECK("c" == toString(params[2], opts)); } -TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2") -{ - ScopedFastFlag sff[] = { - {FFlag::DebugLuauSharedSelf, true}, - }; - - CheckResult result = check(R"( - local base = {} - function base:one() return 1 end - - local child = {} - setmetatable(child, {__index=base}) - function child:two() return 2 end - - local inst = {} - setmetatable(inst, {__index=child}) - )"); - LUAU_REQUIRE_NO_ERRORS(result); - - ToStringOptions opts; - - TypeId tType = requireType("inst"); - ToStringResult r = toStringDetailed(tType, opts); - CHECK_EQ("{ @metatable { __index: { @metatable {| __index: base |}, child } }, inst }", r.name); - CHECK(0 == opts.nameMap.types.size()); - - const MetatableType* tMeta = get(follow(tType)); - REQUIRE(tMeta); - - TableType* tMeta2 = getMutable(follow(tMeta->metatable)); - REQUIRE(tMeta2); - REQUIRE(tMeta2->props.count("__index")); - - const MetatableType* tMeta3 = get(follow(tMeta2->props["__index"].type())); - REQUIRE(tMeta3); - - TableType* tMeta4 = getMutable(follow(tMeta3->metatable)); - REQUIRE(tMeta4); - REQUIRE(tMeta4->props.count("__index")); - - TableType* tMeta5 = getMutable(follow(tMeta4->props["__index"].type())); - REQUIRE(tMeta5); - REQUIRE(tMeta5->props.count("one") > 0); - - TableType* tMeta6 = getMutable(follow(tMeta3->table)); - REQUIRE(tMeta6); - REQUIRE(tMeta6->props.count("two") > 0); - - ToStringResult oneResult = toStringDetailed(tMeta5->props["one"].type(), opts); - - std::string twoResult = toString(tMeta6->props["two"].type(), opts); - - CHECK_EQ("(a) -> number", oneResult.name); - CHECK_EQ("(b) -> number", twoResult); -} - TEST_CASE_FIXTURE(Fixture, "toStringErrorPack") { CheckResult result = check(R"( @@ -869,60 +827,6 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_ CHECK("(a, b) -> ()" == toString(requireType("foo"))); } -TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param") -{ - ScopedFastFlag sff[]{ - {FFlag::DebugLuauSharedSelf, true}, - }; - - CheckResult result = check(R"( - local foo = {} - function foo:method(arg: string): () - end - )"); - - TypeId parentTy = requireType("foo"); - auto ttv = get(follow(parentTy)); - REQUIRE(ttv); - - TypeId methodTy = ttv->props.at("method").type(); - auto ftv = get(follow(methodTy)); - REQUIRE_MESSAGE(ftv, methodTy); - - if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("foo:method(self: unknown, arg: string): ()", toStringNamedFunction("foo:method", *ftv)); - else - CHECK_EQ("foo:method(self: a, arg: string): ()", toStringNamedFunction("foo:method", *ftv)); -} - -TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param") -{ - ScopedFastFlag sff[]{ - {FFlag::DebugLuauSharedSelf, true}, - }; - - CheckResult result = check(R"( - local foo = {} - function foo:method(arg: string): () - end - )"); - - ToStringOptions opts; - opts.hideFunctionSelfArgument = true; - - TypeId parentTy = requireType("foo"); - auto ttv = get(follow(parentTy)); - REQUIRE_MESSAGE(ttv, "Expected a table but got " << toString(parentTy, opts)); - TypeId methodTy = follow(ttv->props.at("method").type()); - auto ftv = get(methodTy); - REQUIRE_MESSAGE(ftv, "Expected a function but got " << toString(methodTy, opts)); - - if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("foo:method(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts)); - else - CHECK_EQ("foo:method(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts)); -} - TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") { CheckResult result = check(R"( @@ -1015,8 +919,10 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack") TypePack* packPtr = getMutable(thePack); REQUIRE(packPtr); - const TableType::Props theProps = {{"BaseField", Property::readonly(builtinTypes->unknownType)}, - {"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))}}; + const TableType::Props theProps = { + {"BaseField", Property::readonly(builtinTypes->unknownType)}, + {"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))} + }; TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed}); diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index c429eb5b..edc2bf47 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -12,6 +12,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions); + TEST_SUITE_BEGIN("TranspilerTests"); TEST_CASE("test_1") @@ -694,4 +696,13 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") CHECK_EQ(code, transpile(code, {}, true).code); } +TEST_CASE_FIXTURE(Fixture, "transpile_type_functions") +{ + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctions, true}; + + std::string code = R"( type function foo(arg1, arg2) if arg1 == arg2 then return arg1 end return arg2 end )"; + + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 0c87a395..98aa82e8 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) struct TypeFunctionFixture : Fixture @@ -22,10 +23,12 @@ struct TypeFunctionFixture : Fixture TypeFunctionFixture() : Fixture(true, false) { - swapFunction = TypeFunction{/* name */ "Swap", + swapFunction = TypeFunction{ + /* name */ "Swap", /* reducer */ - [](TypeId instance, const std::vector& tys, const std::vector& tps, - NotNull ctx) -> TypeFunctionReductionResult { + [](TypeId instance, const std::vector& tys, const std::vector& tps, NotNull ctx + ) -> TypeFunctionReductionResult + { LUAU_ASSERT(tys.size() == 1); TypeId param = follow(tys.at(0)); @@ -46,7 +49,8 @@ struct TypeFunctionFixture : Fixture { return TypeFunctionReductionResult{std::nullopt, true, {}, {}}; } - }}; + } + }; unfreeze(frontend.globals.globalTypes); TypeId t = frontend.globals.globalTypes.addType(GenericType{"T"}); @@ -114,11 +118,10 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "function_as_fn_arg") )"); LUAU_REQUIRE_ERROR_COUNT(2, result); - // FIXME: Can we constrain these to `never` or `unknown`? - CHECK("a" == toString(requireType("a"))); - CHECK("a" == toString(requireType("b"))); - CHECK("Type function instance Swap is uninhabited" == toString(result.errors[0])); - CHECK("Type function instance Swap is uninhabited" == toString(result.errors[1])); + CHECK("unknown" == toString(requireType("a"))); + CHECK("unknown" == toString(requireType("b"))); + CHECK("Type 'number' could not be converted into 'never'" == toString(result.errors[0])); + CHECK("Type 'boolean' could not be converted into 'never'" == toString(result.errors[1])); } TEST_CASE_FIXTURE(TypeFunctionFixture, "resolve_deep_functions") @@ -145,11 +148,13 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function") local b = impossible(true) )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - for (size_t i = 0; i < 2; ++i) - { - CHECK(toString(result.errors[i]) == "Type function instance Swap is uninhabited"); - } + LUAU_REQUIRE_ERROR_COUNT(6, result); + CHECK(toString(result.errors[0]) == "Type function instance Swap> is uninhabited"); + CHECK(toString(result.errors[1]) == "Type function instance Swap is uninhabited"); + CHECK(toString(result.errors[2]) == "Type function instance Swap> is uninhabited"); + CHECK(toString(result.errors[3]) == "Type function instance Swap is uninhabited"); + CHECK(toString(result.errors[4]) == "Type function instance Swap> is uninhabited"); + CHECK(toString(result.errors[5]) == "Type function instance Swap is uninhabited"); } TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions") @@ -210,10 +215,10 @@ TEST_CASE_FIXTURE(Fixture, "add_function_at_work") LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK(toString(requireType("a")) == "number"); - CHECK(toString(requireType("b")) == "Add"); - CHECK(toString(requireType("c")) == "Add"); - CHECK(toString(result.errors[0]) == "Type function instance Add is uninhabited"); - CHECK(toString(result.errors[1]) == "Type function instance Add is uninhabited"); + CHECK(toString(requireType("b")) == "add"); + CHECK(toString(requireType("c")) == "add"); + CHECK(toString(result.errors[0]) == "Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add"); + CHECK(toString(result.errors[1]) == "Operator '+' could not be applied to operands of types string and number; there is no corresponding overload for __add"); } TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_function_at_work") @@ -284,8 +289,9 @@ TEST_CASE_FIXTURE(Fixture, "internal_functions_raise_errors") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Type function instance Add depends on generic function parameters but does not appear in the function " - "signature; this construct cannot be type-checked at this time"); + CHECK( + toString(result.errors[0]) == "Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_functions_can_be_shadowed") @@ -695,8 +701,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") fnB(result) )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(get(result.errors[0])); + CHECK(get(result.errors[1])); } TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType") @@ -1163,4 +1170,18 @@ TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes") CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'"); } +TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors") +{ + if (!FFlag::LuauUserDefinedTypeFunctions) + return; + + CheckResult result = check(R"( + type function foo() + return nil + end + )"); + LUAU_CHECK_ERROR_COUNT(1, result); + CHECK(toString(result.errors[0]) == "This syntax is not supported"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index f84c13e3..fbb0d154 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -9,7 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) -LUAU_FASTFLAG(DebugLuauSharedSelf); +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) TEST_SUITE_BEGIN("TypeAliases"); @@ -74,25 +74,31 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK(result.errors[0] == TypeError{ - Location{{1, 21}, {1, 26}}, - getMainSourceModule()->name, - TypeMismatch{ - builtinTypes->numberType, - builtinTypes->stringType, - }, - }); + CHECK( + result.errors[0] == + TypeError{ + Location{{1, 21}, {1, 26}}, + getMainSourceModule()->name, + TypeMismatch{ + builtinTypes->numberType, + builtinTypes->stringType, + }, + } + ); } else { - CHECK(result.errors[0] == TypeError{ - Location{{1, 8}, {1, 26}}, - getMainSourceModule()->name, - TypeMismatch{ - builtinTypes->numberType, - builtinTypes->stringType, - }, - }); + CHECK( + result.errors[0] == + TypeError{ + Location{{1, 8}, {1, 26}}, + getMainSourceModule()->name, + TypeMismatch{ + builtinTypes->numberType, + builtinTypes->stringType, + }, + } + ); } } @@ -103,8 +109,10 @@ TEST_CASE_FIXTURE(Fixture, "mismatched_generic_type_param") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == - "Generic type 'A' is used as a variadic type parameter; consider changing 'A' to 'A...' in the generic argument list"); + CHECK( + toString(result.errors[0]) == + "Generic type 'A' is used as a variadic type parameter; consider changing 'A' to 'A...' in the generic argument list" + ); CHECK(result.errors[0].location == Location{{1, 21}, {1, 25}}); } @@ -115,8 +123,10 @@ TEST_CASE_FIXTURE(Fixture, "mismatched_generic_pack_type_param") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == - "Variadic type parameter 'A...' is used as a regular generic type; consider changing 'A...' to 'A' in the generic argument list"); + CHECK( + toString(result.errors[0]) == + "Variadic type parameter 'A...' is used as a regular generic type; consider changing 'A...' to 'A' in the generic argument list" + ); CHECK(result.errors[0].location == Location{{1, 24}, {1, 25}}); } @@ -824,37 +834,6 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2") -{ - ScopedFastFlag sff[] = { - {FFlag::DebugLuauSharedSelf, true}, - }; - - CheckResult result = check(R"( - local B = {} - B.bar = 4 - - function B:smth1() - local self: FutureIntersection = self - self.foo = 4 - return 4 - end - - function B:smth2() - local self: FutureIntersection = self - self.bar = 5 -- error, even though we should have B part with bar - end - - type A = { foo: typeof(B.smth1({foo=3})) } -- trick toposort into sorting functions before types - type B = typeof(B) - - type FutureIntersection = A & B - )"); - - // TODO: shared self causes this test to break in bizarre ways. - LUAU_REQUIRE_ERRORS(result); -} - TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_ok") { CheckResult result = check(R"( @@ -1136,4 +1115,18 @@ type Foo = Foo | string REQUIRE(err); } +TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors") +{ + if (!FFlag::LuauUserDefinedTypeFunctions) + return; + + CheckResult result = check(R"( + type function foo() + return nil + end + )"); + LUAU_CHECK_ERROR_COUNT(1, result); + CHECK(toString(result.errors[0]) == "This syntax is not supported"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.annotations.test.cpp b/tests/TypeInfer.annotations.test.cpp index 9373e4aa..02f34054 100644 --- a/tests/TypeInfer.annotations.test.cpp +++ b/tests/TypeInfer.annotations.test.cpp @@ -228,14 +228,17 @@ TEST_CASE_FIXTURE(Fixture, "unknown_type_reference_generates_error") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(result.errors[0] == TypeError{ - Location{{1, 17}, {1, 28}}, - getMainSourceModule()->name, - UnknownSymbol{ - "IDoNotExist", - UnknownSymbol::Context::Type, - }, - }); + CHECK( + result.errors[0] == + TypeError{ + Location{{1, 17}, {1, 28}}, + getMainSourceModule()->name, + UnknownSymbol{ + "IDoNotExist", + UnknownSymbol::Context::Type, + }, + } + ); } TEST_CASE_FIXTURE(Fixture, "typeof_variable_type_annotation_should_return_its_type") @@ -267,8 +270,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_of_value_a_via_typeof_with_assignment") CHECK("nil" == toString(requireType("b"))); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(result.errors[0] == - (TypeError{Location{Position{2, 29}, Position{2, 30}}, TypeMismatch{builtinTypes->nilType, builtinTypes->numberType}})); + CHECK( + result.errors[0] == (TypeError{Location{Position{2, 29}, Position{2, 30}}, TypeMismatch{builtinTypes->nilType, builtinTypes->numberType}}) + ); } else { @@ -276,8 +280,10 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_of_value_a_via_typeof_with_assignment") CHECK_EQ(*builtinTypes->numberType, *requireType("b")); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(result.errors[0], - (TypeError{Location{Position{4, 12}, Position{4, 17}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}})); + CHECK_EQ( + result.errors[0], + (TypeError{Location{Position{4, 12}, Position{4, 17}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}) + ); } } @@ -749,7 +755,8 @@ struct AssertionCatcher { tripped = 0; oldhook = Luau::assertHandler(); - Luau::assertHandler() = [](const char* expr, const char* file, int line, const char* function) -> int { + Luau::assertHandler() = [](const char* expr, const char* file, int line, const char* function) -> int + { ++tripped; return 0; }; @@ -773,10 +780,12 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag") AssertionCatcher ac; - CHECK_THROWS_AS(check(R"( + CHECK_THROWS_AS( + check(R"( local a: _luau_ice = 55 )"), - InternalCompilerError); + InternalCompilerError + ); LUAU_ASSERT(1 == AssertionCatcher::tripped); } @@ -787,14 +796,17 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler bool caught = false; - frontend.iceHandler.onInternalError = [&](const char*) { + frontend.iceHandler.onInternalError = [&](const char*) + { caught = true; }; - CHECK_THROWS_AS(check(R"( + CHECK_THROWS_AS( + check(R"( local a: _luau_ice = 55 )"), - InternalCompilerError); + InternalCompilerError + ); CHECK_EQ(true, caught); } @@ -813,9 +825,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set") { static std::vector output; output.clear(); - Luau::setPrintLine([](const std::string& s) { - output.push_back(s); - }); + Luau::setPrintLine( + [](const std::string& s) + { + output.push_back(s); + } + ); ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true}; diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index f43537b2..d392f43f 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -9,7 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); TEST_SUITE_BEGIN("BuiltinTests"); @@ -133,10 +132,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate") TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - CheckResult result = check(R"( --!strict local t = {'one', 'two', 'three'} @@ -953,7 +948,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)", toString(result.errors[0])); + CHECK_EQ( + "Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)", + toString(result.errors[0]) + ); else CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0])); } diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 90271e29..d5475a05 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -14,7 +14,6 @@ using namespace Luau; using std::nullopt; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); TEST_SUITE_BEGIN("TypeInferClasses"); @@ -402,9 +401,6 @@ b.X = 2 -- real Vector2.X is also read-only TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) @@ -617,7 +613,8 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") 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" + ); } { CheckResult result = check(R"( @@ -626,7 +623,8 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") )"); 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" + ); } // Test type checking for the return type of the indexer (i.e. a number) @@ -716,9 +714,16 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") TypeId scriptType = arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test", {}}); - TypeId partType = arena.addType( - ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, - instanceType, nullopt, {}, {}, "Test", {}}); + TypeId partType = arena.addType(ClassType{ + "Part", + {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, + instanceType, + nullopt, + {}, + {}, + "Test", + {} + }); getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; @@ -751,7 +756,8 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_MESSAGE( - get(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]); + get(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0] + ); CHECK(builtinTypes->errorType == requireType("c")); } diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 688f27b7..631913dd 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -80,21 +80,31 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading") TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_scope") { unfreeze(frontend.globals.globalTypes); - LoadDefinitionFileResult parseFailResult = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"( + LoadDefinitionFileResult parseFailResult = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( declare foo )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); REQUIRE(!parseFailResult.success); std::optional fooTy = tryGetGlobalBinding(frontend.globals, "foo"); CHECK(!fooTy.has_value()); - LoadDefinitionFileResult checkFailResult = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"( + LoadDefinitionFileResult checkFailResult = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( local foo: string = 123 declare bar: typeof(foo) )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); REQUIRE(!checkFailResult.success); std::optional barTy = tryGetGlobalBinding(frontend.globals, "bar"); @@ -142,13 +152,18 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_classes") TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function") { unfreeze(frontend.globals.globalTypes); - LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"( + LoadDefinitionFileResult result = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( declare class A X: number X: string end )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); REQUIRE(!result.success); @@ -163,13 +178,18 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function") TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class") { unfreeze(frontend.globals.globalTypes); - LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"( + LoadDefinitionFileResult result = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( type NotAClass = {} declare class Foo extends NotAClass end )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); REQUIRE(!result.success); @@ -184,14 +204,19 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class") TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_classes") { unfreeze(frontend.globals.globalTypes); - LoadDefinitionFileResult result = frontend.loadDefinitionFile(frontend.globals, frontend.globals.globalScope, R"( + LoadDefinitionFileResult result = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( declare class Foo extends Bar end declare class Bar extends Foo end )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); REQUIRE(!result.success); @@ -452,7 +477,10 @@ 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"( + LoadDefinitionFileResult result = frontend.loadDefinitionFile( + frontend.globals, + frontend.globals.globalScope, + R"( declare class Channel Messages: { Message } OnMessage: (message: Message) -> () @@ -463,7 +491,9 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") Channel: Channel end )", - "@test", /* captureComments */ false); + "@test", + /* captureComments */ false + ); freeze(frontend.globals.globalTypes); REQUIRE(result.success); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index ef02a4fc..20880565 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -17,7 +17,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTINT(LuauTarjanChildLimit); LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) @@ -88,10 +87,16 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies") } else { - CHECK_EQ(result.errors[0], (TypeError{Location{Position{3, 16}, Position{3, 20}}, TypeMismatch{ - builtinTypes->numberType, - builtinTypes->booleanType, - }})); + CHECK_EQ( + result.errors[0], + (TypeError{ + Location{Position{3, 16}, Position{3, 20}}, + TypeMismatch{ + builtinTypes->numberType, + builtinTypes->booleanType, + } + }) + ); } } @@ -109,11 +114,17 @@ TEST_CASE_FIXTURE(Fixture, "cannot_hoist_interior_defns_into_signature") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(result.errors[0] == TypeError{Location{{1, 28}, {1, 29}}, getMainSourceModule()->name, - UnknownSymbol{ - "T", - UnknownSymbol::Context::Type, - }}); + CHECK( + result.errors[0] == + TypeError{ + Location{{1, 28}, {1, 29}}, + getMainSourceModule()->name, + UnknownSymbol{ + "T", + UnknownSymbol::Context::Type, + } + } + ); } TEST_CASE_FIXTURE(Fixture, "infer_return_type") @@ -931,15 +942,27 @@ TEST_CASE_FIXTURE(Fixture, "calling_function_with_incorrect_argument_type_yields LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(result.errors[0], (TypeError{Location{Position{3, 12}, Position{3, 18}}, TypeMismatch{ - builtinTypes->numberType, - builtinTypes->stringType, - }})); + CHECK_EQ( + result.errors[0], + (TypeError{ + Location{Position{3, 12}, Position{3, 18}}, + TypeMismatch{ + builtinTypes->numberType, + builtinTypes->stringType, + } + }) + ); - CHECK_EQ(result.errors[1], (TypeError{Location{Position{3, 20}, Position{3, 23}}, TypeMismatch{ - builtinTypes->stringType, - builtinTypes->numberType, - }})); + CHECK_EQ( + result.errors[1], + (TypeError{ + Location{Position{3, 20}, Position{3, 23}}, + TypeMismatch{ + builtinTypes->stringType, + builtinTypes->numberType, + } + }) + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_leak_free_types") @@ -1218,7 +1241,7 @@ caused by: else { expected = R"(Type - '(number, number, a) -> number' + '(number, number, *error-type*) -> number' could not be converted into '(number, number) -> number' caused by: @@ -1651,13 +1674,15 @@ function t:b() return 2 end -- not OK )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type + CHECK_EQ( + R"(Type '(*error-type*) -> number' could not be converted into '() -> number' caused by: Argument count mismatch. Function expects 1 argument, but none are specified)", - toString(result.errors[0])); + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic") @@ -2049,10 +2074,6 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - CheckResult result = check(R"( local function foo(x: a, y: a?) return x @@ -2105,8 +2126,6 @@ Table type '{ x: number }' not compatible with type 'vec2' because the former is TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2") { - ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}; - CheckResult result = check(R"( local function f(x: a, y: a): a return if math.random() > 0.5 then x else y @@ -2298,14 +2317,22 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK(toString(result.errors[0]) == - "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); - CHECK(toString(result.errors[1]) == - "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); - CHECK(toString(result.errors[2]) == - "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); - CHECK(toString(result.errors[3]) == - "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"); + CHECK( + toString(result.errors[0]) == + "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" + ); + CHECK( + toString(result.errors[1]) == + "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" + ); + CHECK( + toString(result.errors[2]) == + "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" + ); + CHECK( + toString(result.errors[3]) == + "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" + ); } else { diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 33ab45c8..8741da6a 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -11,7 +11,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(DebugLuauSharedSelf); using namespace Luau; @@ -371,26 +370,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_nested_generic_function") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "infer_generic_methods") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - - CheckResult result = check(R"( - local x = {} - function x:id(x) return x end - function x:f(): string return self:id("hello") end - function x:g(): number return self:id(37) end - )"); - - if (FFlag::DebugLuauDeferredConstraintResolution) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - // TODO: Quantification should be doing the conversion, not normalization. - LUAU_REQUIRE_ERRORS(result); - } -} - TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods") { CheckResult result = check(R"( @@ -802,8 +781,10 @@ local d: D = c LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - R"(Type '() -> ()' could not be converted into '() -> ()'; different number of generic type pack parameters)"); + CHECK_EQ( + toString(result.errors[0]), + R"(Type '() -> ()' could not be converted into '() -> ()'; different number of generic type pack parameters)" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "generic_functions_dont_cache_type_parameters") @@ -847,7 +828,8 @@ y.a.c = y if (FFlag::DebugLuauDeferredConstraintResolution) CHECK( toString(result.errors.at(0)) == - R"(Type '{ a: { c: nil, d: number }, b: number }' could not be converted into 'T'; type { a: { c: nil, d: number }, b: number }[read "a"][read "c"] (nil) is not exactly T[read "a"][read "c"][0] (T))"); + R"(Type '{ a: { c: nil, d: number }, b: number }' could not be converted into 'T'; type { a: { c: nil, d: number }, b: number }[read "a"][read "c"] (nil) is not exactly T[read "a"][read "c"][0] (T))" + ); else { const std::string expected = R"(Type 'y' could not be converted into 'T' @@ -1154,9 +1136,14 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") else CHECK_EQ("*error-type*", toString(*t0)); - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { - return get(err); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](TypeError& err) + { + return get(err); + } + ); CHECK(it != result.errors.end()); } @@ -1173,11 +1160,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") return sum(2, 3, function(a: T, b: T): add return a + b end) )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - InternalError* ie = get(result.errors[0]); - REQUIRE(ie); - CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message); + LUAU_REQUIRE_NO_ERRORS(result); } else { @@ -1280,11 +1263,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - InternalError* ie = get(result.errors[0]); - REQUIRE(ie); - CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message); + LUAU_REQUIRE_NO_ERRORS(result); } else { diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index e5d21d10..7e2b0645 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -421,7 +421,14 @@ local a: XYZ = 3 caused by: Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"; - CHECK_EQ(expected, toString(result.errors[0])); + const std::string dcrExprected = + R"(Type 'number' could not be converted into 'X & Y & Z'; type number (number) is not a subtype of X & Y & Z[0] (X) + type number (number) is not a subtype of X & Y & Z[1] (Y) + type number (number) is not a subtype of X & Y & Z[2] (Z))"; + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(dcrExprected, toString(result.errors[0])); + else + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") @@ -438,7 +445,16 @@ end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number) + type X & Y & Z[0][1] (Y) is not a subtype of number[0] (number) + type X & Y & Z[0][2] (Z) is not a subtype of number[0] (number))", + toString(result.errors[0])); + } + else + CHECK_EQ( + toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"); } TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function") @@ -479,7 +495,15 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true) + type boolean & false[1] (false) is not a subtype of true (true))", + toString(result.errors[0])); + } + else + CHECK_EQ( + toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"); } TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") @@ -493,25 +517,52 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") LUAU_REQUIRE_ERROR_COUNT(1, result); // TODO: odd stringification of `false & (boolean & false)`.) - CHECK_EQ(toString(result.errors[0]), - "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ( + R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true) + type boolean & false & false[1] (boolean) is not a subtype of true (true) + type boolean & false & false[2] (false) is not a subtype of true (true))", + toString(result.errors[0])); + else + CHECK_EQ(toString(result.errors[0]), + "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"); } TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { CheckResult result = check(R"( function foo(x: ((number?) -> number?) & ((string?) -> string?)) - local y : (nil) -> nil = x -- OK + local y : (nil) -> nil = x -- Not OK (fixed in DCR) local z : (number) -> number = x -- Not OK end )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + const std::string expected1 = R"(Type + '(nil) -> nil' +could not be converted into + '((number?) -> number?) & ((string?) -> string?)'; type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][0] (number) + type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string))"; + const std::string expected2 = R"(Type + '(number) -> number' +could not be converted into + '((number?) -> number?) & ((string?) -> string?)'; type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][1] (nil) + type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string) + type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][1] (nil) + type (number) -> number.returns()[0] (number) is not a subtype of ((number?) -> number?) & ((string?) -> string?)[1].returns()[0] (string?))"; + CHECK_EQ(expected1, toString(result.errors[0])); + CHECK_EQ(expected2, toString(result.errors[1])); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + const std::string expected = R"(Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") @@ -567,23 +618,27 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), + CHECK_EQ( + toString(result.errors[0]), "Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n" "caused by:\n" " Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n" "caused by:\n" " Not all union options are compatible. Type 'number' could not be converted into 'string?'\n" "caused by:\n" - " None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context"); + " None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context" + ); - CHECK_EQ(toString(result.errors[1]), + CHECK_EQ( + toString(result.errors[1]), "Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n" "caused by:\n" " Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n" "caused by:\n" " Not all union options are compatible. Type 'string' could not be converted into 'number?'\n" "caused by:\n" - " None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context"); + " None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context" + ); } else { @@ -793,8 +848,10 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible"); + CHECK_EQ( + toString(result.errors[0]), + "Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible" + ); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") @@ -809,8 +866,10 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible"); + CHECK_EQ( + toString(result.errors[0]), + "Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible" + ); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index ab96f205..f8c80bf8 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -767,10 +767,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(result.errors[0] == TypeError{ - Location{{2, 36}, {2, 37}}, - GenericError{"__iter must return at least one value"}, - }); + CHECK( + result.errors[0] == + TypeError{ + Location{{2, 36}, {2, 37}}, + GenericError{"__iter must return at least one value"}, + } + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 98f5ec8b..a39ef1a7 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -745,9 +745,11 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown") if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(ops.size(), result); - CHECK_EQ("Type function instance Add depends on generic function parameters but does not appear in the function signature; this " - "construct cannot be type-checked at this time", - toString(result.errors[0])); + CHECK_EQ( + "Type function instance Add depends on generic function parameters but does not appear in the function signature; this " + "construct cannot be type-checked at this time", + toString(result.errors[0]) + ); CHECK_EQ("Unknown type used in - operation; consider adding a type annotation to 'a'", toString(result.errors[1])); } else @@ -1473,7 +1475,10 @@ TEST_CASE_FIXTURE(Fixture, "add_type_function_works") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK(toString(requireType("a")) == "number"); CHECK(toString(requireType("b")) == "add"); - CHECK(toString(result.errors[0]) == "Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add"); + CHECK( + toString(result.errors[0]) == + "Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "normalize_strings_comparison") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 3072169c..c9d6f5db 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(DebugLuauSharedSelf); LUAU_FASTINT(LuauNormalizeCacheLimit); LUAU_FASTINT(LuauTarjanChildLimit); LUAU_FASTINT(LuauTypeInferIterationLimit); @@ -302,9 +301,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated end )LUA"); - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& a) { - return nullptr != get(a); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](TypeError& a) + { + return nullptr != get(a); + } + ); if (it == result.errors.end()) { dumpErrors(result); @@ -440,28 +444,6 @@ TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_any") CHECK_EQ("((any) -> (), any) -> ()", toString(requireType("foo"))); } -TEST_CASE_FIXTURE(BuiltinsFixture, "greedy_inference_with_shared_self_triggers_function_with_no_returns") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - - CheckResult result = check(R"( - local T = {} - T.__index = T - - function T.new() - local self = setmetatable({}, T) - return self:ctor() or self - end - - function T:ctor() - -- oops, no return! - end - )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Not all codepaths in this function return 'self, a...'.", toString(result.errors[0])); -} - TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint") { ScopedFastFlag sff[] = { diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index b0d509ac..60f25a24 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -14,7 +14,11 @@ using namespace Luau; namespace { std::optional> magicFunctionInstanceIsA( - TypeChecker& typeChecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) + TypeChecker& typeChecker, + const ScopePtr& scope, + const AstExprCall& expr, + WithPredicate withPredicate +) { if (expr.args.size != 1) return std::nullopt; @@ -591,7 +595,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil :) else @@ -630,7 +634,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b else @@ -761,7 +765,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table") CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" else CHECK_EQ("{| x: number |} | {| y: boolean |}", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" - CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table" + CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table" } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions") @@ -1951,14 +1955,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_annotations_arent_relevant_when_doing_d TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_with_colon_after_refining_not_to_be_nil") { - // don't run this test at all without DCR - if (!FFlag::DebugLuauDeferredConstraintResolution) - return; + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; CheckResult result = check(R"( --!strict export type Observer = { - complete: ((self: Observer) -> ())?, + read complete: ((self: Observer) -> ())?, } local function _f(handler: Observer) @@ -2172,4 +2174,43 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachab CHECK_EQ("{ [string]: number }", toString(requireTypeAtPosition({8, 12}), {true})); } +TEST_CASE_FIXTURE(Fixture, "long_disjunction_of_refinements_should_not_trip_recursion_counter") +{ + CHECK_NOTHROW(check(R"( +function(obj) + if script.Parent.SeatNumber.Value == "1D" or + script.Parent.SeatNumber.Value == "2D" or + script.Parent.SeatNumber.Value == "3D" or + script.Parent.SeatNumber.Value == "4D" or + script.Parent.SeatNumber.Value == "5D" or + script.Parent.SeatNumber.Value == "6D" or + script.Parent.SeatNumber.Value == "7D" or + script.Parent.SeatNumber.Value == "8D" or + script.Parent.SeatNumber.Value == "9D" or + script.Parent.SeatNumber.Value == "10D" or + script.Parent.SeatNumber.Value == "11D" or + script.Parent.SeatNumber.Value == "12D" or + script.Parent.SeatNumber.Value == "13D" or + script.Parent.SeatNumber.Value == "14D" or + script.Parent.SeatNumber.Value == "15D" or + script.Parent.SeatNumber.Value == "16D" or + script.Parent.SeatNumber.Value == "1C" or + script.Parent.SeatNumber.Value == "2C" or + script.Parent.SeatNumber.Value == "3C" or + script.Parent.SeatNumber.Value == "4C" or + script.Parent.SeatNumber.Value == "5C" or + script.Parent.SeatNumber.Value == "6C" or + script.Parent.SeatNumber.Value == "7C" or + script.Parent.SeatNumber.Value == "8C" or + script.Parent.SeatNumber.Value == "9C" or + script.Parent.SeatNumber.Value == "10C" or + script.Parent.SeatNumber.Value == "11C" or + script.Parent.SeatNumber.Value == "12C" or + script.Parent.SeatNumber.Value == "13C" or + script.Parent.SeatNumber.Value == "14C" or + script.Parent.SeatNumber.Value == "15C" or + script.Parent.SeatNumber.Value == "16C" then +end +)")); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index e089c7be..e4289649 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -192,8 +192,10 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Type '\"bang\"' could not be converted into '\"bar\" | \"baz\" | \"foo\"'; none of the union options are compatible", - toString(result.errors[0])); + CHECK_EQ( + "Type '\"bang\"' could not be converted into '\"bar\" | \"baz\" | \"foo\"'; none of the union options are compatible", + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping") @@ -335,8 +337,10 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", - toString(result.errors[0])); + CHECK_EQ( + R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index d6c93c23..a0a3aaf3 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -17,9 +17,7 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauInstantiateInSubtyping); -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering); -LUAU_FASTFLAG(DebugLuauSharedSelf); LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) @@ -318,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "call_method_with_explicit_self_argument") TEST_CASE_FIXTURE(Fixture, "used_dot_instead_of_colon") { + // CLI-114792 Dot vs colon warnings aren't in the new solver yet. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local T = {} T.x = 0 @@ -327,9 +328,14 @@ TEST_CASE_FIXTURE(Fixture, "used_dot_instead_of_colon") local a = T.method() )"); - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& e) { - return nullptr != get(e); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](const TypeError& e) + { + return nullptr != get(e); + } + ); REQUIRE(it != result.errors.end()); } @@ -363,6 +369,9 @@ TEST_CASE_FIXTURE(Fixture, "used_dot_instead_of_colon_but_correctly") TEST_CASE_FIXTURE(Fixture, "used_colon_instead_of_dot") { + // CLI-114792 Dot vs colon warnings aren't in the new solver yet. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local T = {} T.x = 0 @@ -372,14 +381,22 @@ TEST_CASE_FIXTURE(Fixture, "used_colon_instead_of_dot") local a = T:method() )"); - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](const TypeError& e) { - return nullptr != get(e); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](const TypeError& e) + { + return nullptr != get(e); + } + ); REQUIRE(it != result.errors.end()); } TEST_CASE_FIXTURE(Fixture, "open_table_unification_2") { + // CLI-114792 We don't report MissingProperties in many places where the old solver does. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local a = {} a.x = 99 @@ -447,13 +464,13 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_1") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2") +TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2") { CheckResult result = check(R"( --!strict function foo(o) - local a = o.bar - local b = o.baz + string.lower(o.bar) + string.lower(o.baz) end foo({bar='bar'}) @@ -461,11 +478,23 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2") LUAU_REQUIRE_ERROR_COUNT(1, result); - MissingProperties* error = get(result.errors[0]); - REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0])); - REQUIRE(error->properties.size() == 1); + // CLI 114792 We don't report MissingProperties in many places where the old solver does + if (FFlag::DebugLuauDeferredConstraintResolution) + { + TypeMismatch* error = get(result.errors[0]); + REQUIRE_MESSAGE(error != nullptr, "Expected TypeMismatch but got " << toString(result.errors[0])); - CHECK_EQ("baz", error->properties[0]); + CHECK("{ read bar: string }" == toString(error->givenType)); + CHECK("{ read bar: string, read baz: string }" == toString(error->wantedType)); + } + else + { + MissingProperties* error = get(result.errors[0]); + REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0])); + REQUIRE(error->properties.size() == 1); + + CHECK_EQ("baz", error->properties[0]); + } } TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3") @@ -480,25 +509,34 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypeError& err = result.errors[0]; - MissingProperties* error = get(err); - REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err)); - REQUIRE(error->properties.size() == 1); - CHECK_EQ("baz", error->properties[0]); + CHECK(result.errors[0].location == Location{Position{6, 8}, Position{6, 9}}); - // TODO(rblanckaert): Revist when we can bind self at function creation time - /* - CHECK_EQ(err->location, - (Location{ Position{4, 22}, Position{4, 30} }) - ); - */ + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK(toString(result.errors[0]) == "Type 'T' could not be converted into '{ read baz: unknown }'"); + else + { + TypeError& err = result.errors[0]; + MissingProperties* error = get(err); + REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err)); + REQUIRE(error->properties.size() == 1); - CHECK_EQ(err.location, (Location{Position{6, 8}, Position{6, 9}})); + CHECK_EQ("baz", error->properties[0]); + + // TODO(rblanckaert): Revist when we can bind self at function creation time + /* + CHECK_EQ(err->location, + (Location{ Position{4, 22}, Position{4, 30} }) + ); + */ + } } TEST_CASE_FIXTURE(Fixture, "table_unification_4") { + // CLI-114134 - Use egraphs to simplify types better. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function foo(o) if o.prop then @@ -527,6 +565,9 @@ TEST_CASE_FIXTURE(Fixture, "ok_to_add_property_to_free_table") TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_assignment") { + // CLI-114872 + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict local t = { u = {} } @@ -543,6 +584,9 @@ TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_assignmen TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_function_call") { + // CLI-114873 + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict function get(x) return x.opts["MYOPT"] end @@ -552,16 +596,8 @@ TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_function_ local x = get(t) )"); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("number", toString(requireType("x"))); - } - else - { - LUAU_REQUIRE_ERRORS(result); - // CHECK_EQ("number?", toString(requireType("x"))); - } + LUAU_REQUIRE_ERRORS(result); + // CHECK_EQ("number?", toString(requireType("x"))); } TEST_CASE_FIXTURE(Fixture, "width_subtyping") @@ -654,24 +690,29 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too") LUAU_REQUIRE_NO_ERRORS(result); - const FunctionType* ftv = get(requireType("swap")); - REQUIRE(ftv != nullptr); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("({unknown}) -> ()" == toString(requireType("swap"))); + else + { + const FunctionType* ftv = get(requireType("swap")); + REQUIRE(ftv != nullptr); - std::vector argVec = flatten(ftv->argTypes).first; + std::vector argVec = flatten(ftv->argTypes).first; - REQUIRE_EQ(1, argVec.size()); + REQUIRE_EQ(1, argVec.size()); - const TableType* ttv = get(follow(argVec[0])); - REQUIRE(ttv != nullptr); + const TableType* ttv = get(follow(argVec[0])); + REQUIRE(ttv != nullptr); - REQUIRE(bool(ttv->indexer)); + REQUIRE(bool(ttv->indexer)); - const TableIndexer& indexer = *ttv->indexer; + const TableIndexer& indexer = *ttv->indexer; - REQUIRE("number" == toString(indexer.indexType)); + REQUIRE("number" == toString(indexer.indexType)); - TypeId indexResultType = follow(indexer.indexResultType); - REQUIRE_MESSAGE(get(indexResultType), "Expected generic but got " << toString(indexResultType)); + TypeId indexResultType = follow(indexer.indexResultType); + REQUIRE_MESSAGE(get(indexResultType), "Expected generic but got " << toString(indexResultType)); + } } TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2") @@ -720,7 +761,14 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") const TableIndexer& indexer = *ttv->indexer; CHECK_EQ(*builtinTypes->numberType, *indexer.indexType); - CHECK_EQ(*builtinTypes->stringType, *indexer.indexResultType); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-114134 - Use egraphs to simplify types + CHECK("string | string | string" == toString(indexer.indexResultType)); + } + else + CHECK_EQ(*builtinTypes->stringType, *indexer.indexResultType); } TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal") @@ -761,6 +809,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal") TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable") { + // This code is totally different in the new solver. We instead create a new type state for t2. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local t1: { [string]: string } = {} local t2 = { "bar" } @@ -841,6 +892,8 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_value_can_infer_an_indexer") TEST_CASE_FIXTURE(Fixture, "array_factory_function") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function empty() return {} end local array: {string} = empty() @@ -859,11 +912,25 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_MESSAGE(nullptr != get(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-114879 - Error path reporting is not great + CHECK( + toString(result.errors[0]) == + "Type pack '{number}' could not be converted into '{string}'; at [0].indexResult(), number is not exactly string" + ); + } + else + CHECK_MESSAGE(nullptr != get(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]); } TEST_CASE_FIXTURE(Fixture, "indexer_on_sealed_table_must_unify_with_free_table") { + // CLI-114134 What should be happening here is that the type of `t` should + // be reduced from `{number} & {string}` to `never`, but that's not + // happening. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function F(t): {number} t[4] = "hi" @@ -925,6 +992,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_from_a_table_should_prefer_properti TEST_CASE_FIXTURE(Fixture, "any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!nonstrict @@ -960,7 +1029,10 @@ TEST_CASE_FIXTURE(Fixture, "disallow_indexing_into_an_unsealed_table_with_no_ind local k1 = getConstant("key1") )"); - CHECK("any" == toString(requireType("k1"))); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("unknown" == toString(requireType("k1"))); + else + CHECK("any" == toString(requireType("k1"))); LUAU_REQUIRE_NO_ERRORS(result); } @@ -1042,9 +1114,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add") // We'll want to change this one in particular when we add real syntax for metatables. CheckResult result = check(R"( - local a = setmetatable({}, {__add = function(l, r) return l end}) - type Vector = typeof(a) - local b:Vector + local mt = { + __add = function(l, r) + return l + end + } + local a = setmetatable({}, mt) + local b = setmetatable({}, mt) local c = a + b )"); @@ -1445,6 +1521,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "found_multiple_like_keys") TEST_CASE_FIXTURE(BuiltinsFixture, "dont_suggest_exact_match_keys") { + // CLI-114977 Unsealed table writes don't account for order properly + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local t = {} t.foO = 1 @@ -1485,6 +1564,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_pointer_to_metatable") TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_mismatch_should_fail") { + // This test is invalid because we now create a new type state for t1 at the assignment. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local t1 = {x = 1} local mt1 = {__index = {y = 2}} @@ -1526,6 +1608,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "property_lookup_through_tabletypevar_metatab TEST_CASE_FIXTURE(BuiltinsFixture, "missing_metatable_for_sealed_tables_do_not_get_inferred") { + // This test is invalid because we now create a new type state for t at the assignment. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local t = {x = 1} @@ -1578,6 +1663,9 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key") // Could be flaky if the fix has regressed. TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") { + // CLI-114792 We don't report MissingProperties + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function f(t: {}): { [string]: string, a: string } return t @@ -1598,8 +1686,6 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer") { - ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}; - CheckResult result = check(R"( type StringToStringMap = { [string]: string } local rt: StringToStringMap = { ["foo"] = 1 } @@ -1610,10 +1696,20 @@ TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_i ToStringOptions o{/* exhaustive= */ true}; TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o)); - // Should t now have an indexer? - // It would if the assignment to rt was correctly typed. - CHECK_EQ("{ [string]: string, foo: number }", toString(tm->givenType, o)); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o)); + CHECK_EQ("{ [string]: number }", toString(tm->givenType, o)); + } + else + { + CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o)); + + // Should t now have an indexer? + // It would if the assignment to rt was correctly typed. + CHECK_EQ("{ [string]: string, foo: number }", toString(tm->givenType, o)); + } } TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_indexer") @@ -1644,7 +1740,7 @@ TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_ind TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2") { CheckResult result = check(R"( - local function foo(a: {[string]: number, a: string}) end + local function foo(x: {[string]: number, a: string}) end foo({ a = "" }) )"); @@ -1653,8 +1749,6 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2") TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer3") { - ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}; - CheckResult result = check(R"( local function foo(a: {[string]: number, a: string}) end foo({ a = 1 }) @@ -1665,8 +1759,17 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer3") ToStringOptions o{/* exhaustive= */ true}; TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - CHECK_EQ("{| [string]: number, a: string |}", toString(tm->wantedType, o)); - CHECK_EQ("{ [string]: number, a: number }", toString(tm->givenType, o)); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK("string" == toString(tm->wantedType)); + CHECK("number" == toString(tm->givenType)); + } + else + { + CHECK_EQ("{| [string]: number, a: string |}", toString(tm->wantedType, o)); + CHECK_EQ("{ [string]: number, a: number }", toString(tm->givenType, o)); + } } TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4") @@ -1678,16 +1781,8 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4") local hi: number = foo({ a = "hi" }, "a") -- shouldn't typecheck since at runtime hi is "hi" )"); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'string' in an invariant context"); - } - else - { - // This typechecks but shouldn't - LUAU_REQUIRE_NO_ERRORS(result); - } + // This typechecks but shouldn't + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors") @@ -1702,9 +1797,11 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';" - " at [0], { x: number } is not a subtype of { x: number, y: number, z: number }", - toString(result.errors[0])); + CHECK_EQ( + "Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';" + " at [0], { x: number } is not a subtype of { x: number, y: number, z: number }", + toString(result.errors[0]) + ); } else { @@ -1723,17 +1820,30 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors2") { CheckResult result = check(R"( - type DumbMixedTable = {[number]: number, x: number} - local t: DumbMixedTable = {"fail"} + type MixedTable = {[number]: number, x: number} + local t: MixedTable = {"fail"} )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); - MissingProperties* mp = get(result.errors[1]); - REQUIRE(mp); - CHECK_EQ(mp->context, MissingProperties::Missing); - REQUIRE_EQ(1, mp->properties.size()); - CHECK_EQ(mp->properties[0], "x"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + + CHECK("MixedTable" == toString(tm->wantedType)); + CHECK("{string}" == toString(tm->givenType)); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + + MissingProperties* mp = get(result.errors[1]); + REQUIRE(mp); + CHECK_EQ(mp->context, MissingProperties::Missing); + REQUIRE_EQ(1, mp->properties.size()); + CHECK_EQ(mp->properties[0], "x"); + } } TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multiple_errors") @@ -1755,8 +1865,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multipl if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("vec1", toString(tm->wantedType)); - CHECK_EQ("vec3", toString(tm->givenType)); + CHECK_EQ("{{ x: number }}", toString(tm->wantedType)); + CHECK_EQ("{{ x: number, y: number, z: number }}", toString(tm->givenType)); } else { @@ -1782,15 +1892,7 @@ TEST_CASE_FIXTURE(Fixture, "type_mismatch_on_massive_table_is_cut_short") ScopedFastInt sfis{FInt::LuauTableTypeMaximumStringifierLength, 40}; CheckResult result = check(R"( - local t - t = {} - t.a = 1 - t.b = 1 - t.c = 1 - t.d = 1 - t.e = 1 - t.f = 1 - + local t: {a: number,b: number, c: number, d: number, e: number, f: number} = nil :: any t = 1 )"); @@ -1798,15 +1900,34 @@ TEST_CASE_FIXTURE(Fixture, "type_mismatch_on_massive_table_is_cut_short") TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - CHECK("{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }" == toString(requireType("t"))); - CHECK_EQ("number", toString(tm->givenType)); - CHECK_EQ("Type 'number' could not be converted into '{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }'", - toString(result.errors[0])); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK("{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }" == toString(requireType("t"))); + CHECK_EQ("number", toString(tm->givenType)); + + CHECK_EQ( + "Type 'number' could not be converted into '{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }'", + toString(result.errors[0]) + ); + } + else + { + CHECK("{| a: number, b: number, c: number, d: number, e: number, ... 1 more ... |}" == toString(requireType("t"))); + CHECK_EQ("number", toString(tm->givenType)); + + CHECK_EQ( + "Type 'number' could not be converted into '{| a: number, b: number, c: number, d: number, e: number, ... 1 more ... |}'", + toString(result.errors[0]) + ); + } } TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_even_on_non_lvalue_base_expr") { + // CLI-100076 Assigning nil to an indexer should always succeed + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local function f(): { [string]: number } return { ["foo"] = 1 } @@ -1826,7 +1947,14 @@ TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction") )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("{number | string}", toString(requireType("t"), {/*exhaustive*/ true})); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-114134 Use egraphs to simplify types more consistently + CHECK("{number | number | string}" == toString(requireType("t"), {/*exhaustive*/ true})); + } + else + CHECK_EQ("{number | string}", toString(requireType("t"), {/*exhaustive*/ true})); } TEST_CASE_FIXTURE(Fixture, "reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table") @@ -1840,10 +1968,20 @@ TEST_CASE_FIXTURE(Fixture, "reasonable_error_when_adding_a_nonexistent_property_ LUAU_REQUIRE_ERROR_COUNT(1, result); - UnknownProperty* up = get(result.errors[0]); - REQUIRE(up != nullptr); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CannotExtendTable* cet = get(result.errors[0]); + REQUIRE_MESSAGE(cet, "Expected CannotExtendTable but got " << result.errors[0]); - CHECK_EQ("B", up->key); + CHECK("B" == cet->prop); + } + else + { + UnknownProperty* up = get(result.errors[0]); + REQUIRE_MESSAGE(up != nullptr, "Expected an UnknownProperty but got " << result.errors[0]); + + CHECK_EQ("B", up->key); + } } TEST_CASE_FIXTURE(Fixture, "shorter_array_types_actually_work") @@ -1873,7 +2011,11 @@ TEST_CASE_FIXTURE(Fixture, "only_ascribe_synthetic_names_at_module_scope") LUAU_REQUIRE_ERROR_COUNT(0, result); CHECK_EQ("TopLevel", toString(requireType("TopLevel"))); - CHECK_EQ("{number}", toString(requireType("foo"))); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("{number}?", toString(requireType("foo"))); + else + CHECK_EQ("{number}", toString(requireType("foo"))); } TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties") @@ -1922,7 +2064,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names") TEST_CASE_FIXTURE(BuiltinsFixture, "persistent_sealed_table_is_immutable") { CheckResult result = check(R"( - --!nonstrict function os:bad() end )"); @@ -1953,16 +2094,19 @@ local Test: {Table} = { TEST_CASE_FIXTURE(Fixture, "common_table_element_general") { - CheckResult result = check(R"( -type Table = { - a: number, - b: number? -} + // CLI-115275 - Bidirectional inference does not always propagate indexer types into the expression + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; -local Test: {Table} = { - [2] = { a = 1 }, - [5] = { a = 2, b = 3 } -} + CheckResult result = check(R"( + type Table = { + a: number, + b: number? + } + + local Test: {Table} = { + [2] = { a = 1 }, + [5] = { a = 2, b = 3 } + } )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -2056,41 +2200,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works") REQUIRE_EQ(ttv->state, TableState::Sealed); } -TEST_CASE_FIXTURE(BuiltinsFixture, "less_exponential_blowup_please") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false}; - - CheckResult result = check(R"( - --!strict - - local Foo = setmetatable({}, {}) - Foo.__index = Foo - - function Foo.new() - local self = setmetatable({}, Foo) - return self:constructor() or self - end - function Foo:constructor() end - - function Foo:create() - local foo = Foo.new() - foo:First() - foo:Second() - foo:Third() - return foo - end - function Foo:First() end - function Foo:Second() end - function Foo:Third() end - - local newData = Foo:create() - newData:First() - )"); - - LUAU_REQUIRE_ERROR_COUNT(2, result); -} - TEST_CASE_FIXTURE(Fixture, "common_table_element_union_in_call") { CheckResult result = check(R"( @@ -2107,11 +2216,18 @@ foo({ TEST_CASE_FIXTURE(Fixture, "common_table_element_union_in_call_tail") { - CheckResult result = check(R"( -type Foo = {x: number | string} -local function foo(l: {Foo}, ...: {Foo}) end + // CLI-115239 - Bidirectional checking does not work for __call metamethods + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; -foo({{x = 1234567}, {x = "hello"}}, {{x = 1234567}, {x = "hello"}}, {{x = 1234567}, {x = "hello"}}) + CheckResult result = check(R"( + type Foo = {x: number | string} + local function foo(l: {Foo}, ...: {Foo}) end + + foo( + {{x = 1234567}, {x = "hello"}}, + {{x = 1234567}, {x = "hello"}}, + {{x = 1234567}, {x = "hello"}} + ) )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -2148,8 +2264,18 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table local c : string = t.m("hi") )"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + + CHECK(get(result.errors[0])); + CHECK(Location{{6, 45}, {6, 46}} == result.errors[0].location); + + CHECK(get(result.errors[1])); + } + // TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping - if (FFlag::LuauInstantiateInSubtyping) + else if (FFlag::LuauInstantiateInSubtyping) LUAU_REQUIRE_NO_ERRORS(result); else LUAU_REQUIRE_ERRORS(result); @@ -2268,7 +2394,25 @@ Type '(a, b) -> ()' could not be converted into '(a) -> ()'; different number of generic type parameters)"; - const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // The assignment of c2 to b2 is, surprisingly, allowed under the new + // solver for two reasons: + // + // First, both of the __call functions have hidden ...any arguments + // because their exact definition is available. + // + // Second, nil <: unknown, so we consider that parameter to be optional. + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK("Type 'b1' could not be converted into 'a1'; at [read \"y\"], string is not exactly number" == toString(result.errors[0])); + } + else if (FFlag::LuauInstantiateInSubtyping) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ(expected1, toString(result.errors[0])); + + const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: Type '{ __call: (a, b) -> () }' @@ -2281,14 +2425,13 @@ Type could not be converted into '(a) -> ()'; different number of generic type parameters)"; - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(expected1, toString(result.errors[0])); - if (FFlag::LuauInstantiateInSubtyping) - { CHECK_EQ(expected2, toString(result.errors[1])); } else { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ(expected1, toString(result.errors[0])); + std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: Type @@ -2316,11 +2459,19 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") )"); LUAU_REQUIRE_ERRORS(result); - const std::string expected = R"(Type 'A' could not be converted into 'B' + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK("Type 'A' could not be converted into 'B'; at indexer(), number is not exactly string" == toString(result.errors[0])); + } + else + { + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") @@ -2334,15 +2485,26 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") )"); LUAU_REQUIRE_ERRORS(result); - const std::string expected = R"(Type 'A' could not be converted into 'B' + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK("Type 'A' could not be converted into 'B'; at indexResult(), number is not exactly string" == toString(result.errors[0])); + } + else + { + const std::string expected = R"(Type 'A' could not be converted into 'B' caused by: Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table") { + // Table properties like HasSuper.p must be invariant. The new solver rightly rejects this program. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict type Super = { x : number } @@ -2372,23 +2534,32 @@ local y: number = tmp.p.y )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" == toString(result.errors[0])); + else + { + const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' caused by: Property 'p' is not compatible. Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") { + // CLI-114791 Bidirectional inference should be able to cause the inference engine to forget that a table literal has some property + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( ---!strict -type Super = { x : number } -type Sub = { x : number, y: number } -type HasSuper = { [string] : Super } -type HasSub = { [string] : Sub } -local a: HasSuper = { p = { x = 5, y = 7 }} -a.p = { x = 9 } + --!strict + type Super = { x : number } + type Sub = { x : number, y: number } + type HasSuper = { [string] : Super } + type HasSub = { [string] : Sub } + local a: HasSuper = { p = { x = 5, y = 7 }} + a.p = { x = 9 } )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -2396,6 +2567,9 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call") { + // CLI-114782 + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local b b = setmetatable({}, {__call = b}) @@ -2526,15 +2700,19 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing") local foo = f({p = "string"}) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-114781 Bidirectional checking can't see through the intersection + LUAU_REQUIRE_ERROR_COUNT(1, result); + } + else + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("number | string", toString(requireType("foo"))); } TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table") { - ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}; - CheckResult result = check(R"( local a: {x: number, y: number, [any]: any} | {y: number} @@ -2549,15 +2727,13 @@ TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b"))); + REQUIRE_EQ("{ y: number }", toString(requireType("b"))); else REQUIRE_EQ("{- y: number -}", toString(requireType("b"))); } TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a_table_2") { - ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}; - CheckResult result = check(R"( local a: {y: number} | {x: number, y: number, [any]: any} @@ -2572,7 +2748,7 @@ TEST_CASE_FIXTURE(Fixture, "pass_a_union_of_tables_to_a_function_that_requires_a LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) - REQUIRE_EQ("{| [any]: any, x: number, y: number |} | {| y: number |}", toString(requireType("b"))); + REQUIRE_EQ("{ y: number }", toString(requireType("b"))); else REQUIRE_EQ("{- y: number -}", toString(requireType("b"))); } @@ -2646,6 +2822,9 @@ TEST_CASE_FIXTURE(Fixture, "table_length") TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_indexer") { + // CLI-100076 - Assigning a table key to `nil` in the presence of an indexer should always be permitted + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check("local a = {} a[0] = 7 a[0] = nil"); LUAU_REQUIRE_ERROR_COUNT(0, result); } @@ -2673,10 +2852,16 @@ TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_no_indexer") a['a'] = nil )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(result.errors[0], (TypeError{Location{Position{2, 17}, Position{2, 20}}, TypeMismatch{ - builtinTypes->numberType, - builtinTypes->nilType, - }})); + CHECK_EQ( + result.errors[0], + (TypeError{ + Location{Position{2, 17}, Position{2, 20}}, + TypeMismatch{ + builtinTypes->numberType, + builtinTypes->nilType, + } + }) + ); } TEST_CASE_FIXTURE(Fixture, "free_rhs_table_can_also_be_bound") @@ -2774,7 +2959,6 @@ TEST_CASE_FIXTURE(Fixture, "generalize_table_argument") )"); LUAU_REQUIRE_NO_ERRORS(result); - dumpErrors(result); const FunctionType* fooType = get(requireType("foo")); REQUIRE(fooType); @@ -2785,7 +2969,10 @@ TEST_CASE_FIXTURE(Fixture, "generalize_table_argument") const TableType* fooArg1Table = get(follow(*fooArg1)); REQUIRE(fooArg1Table); - CHECK_EQ(fooArg1Table->state, TableState::Generic); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(fooArg1Table->state, TableState::Sealed); + else + CHECK_EQ(fooArg1Table->state, TableState::Generic); } /* @@ -2927,8 +3114,17 @@ do end TEST_CASE_FIXTURE(BuiltinsFixture, "dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar") { CheckResult result = check("local x = setmetatable({})"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Argument count mismatch. Function 'setmetatable' expects 2 arguments, but only 1 is specified", toString(result.errors[0])); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-114665: Generic parameters should not also be optional. + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Argument count mismatch. Function 'setmetatable' expects 2 arguments, but only 1 is specified", toString(result.errors[0])); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_table_cloning") @@ -3039,7 +3235,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_basic") local foo = a(12) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); CHECK(requireType("foo") == builtinTypes->numberType); } @@ -3058,7 +3260,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable") if (FFlag::DebugLuauDeferredConstraintResolution) { if (DFFlag::LuauImproveNonFunctionCallError) - CHECK("Cannot call a value of type { @metatable { __call: number }, { } }" == toString(result.errors[0])); + CHECK("Cannot call a value of type a" == toString(result.errors[0])); else CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0])); } @@ -3093,14 +3295,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_generic") TEST_CASE_FIXTURE(BuiltinsFixture, "table_simple_call") { + // The new solver can see that this function is safe to oversaturate. + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( -local a = setmetatable({ x = 2 }, { - __call = function(self) - return (self.x :: number) * 2 -- should work without annotation in the future - end -}) -local b = a() -local c = a(2) -- too many arguments + local a = setmetatable({ x = 2 }, { + __call = function(self) + return (self.x :: number) * 2 -- should work without annotation in the future + end + }) + local b = a() + local c = a(2) -- too many arguments )"); LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -3176,8 +3381,17 @@ TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0])); - CHECK_EQ("number | {| x: number? |}", toString(requireType("u"))); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); + CHECK_EQ("number | { x: number }", toString(requireType("u"))); + } + else + { + CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0])); + CHECK_EQ("number | {| x: number? |}", toString(requireType("u"))); + } } TEST_CASE_FIXTURE(Fixture, "accidentally_checked_prop_in_opposite_branch") @@ -3189,7 +3403,7 @@ TEST_CASE_FIXTURE(Fixture, "accidentally_checked_prop_in_opposite_branch") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ("Type 'nil' does not have key 'x'", toString(result.errors[0])); + CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); else CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0])); CHECK_EQ("boolean", toString(requireType("u"))); @@ -3267,37 +3481,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_leak_free_table_props") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("({+ blah: a +}) -> ()", toString(requireType("a"))); - CHECK_EQ("({+ gwar: a +}) -> ()", toString(requireType("b"))); - CHECK_EQ("() -> ({+ blah: a, gwar: b +}) -> ()", toString(getMainModule()->returnType)); -} - -TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table") -{ - ScopedFastFlag sff[] = { - {FFlag::DebugLuauSharedSelf, true}, - {FFlag::DebugLuauDeferredConstraintResolution, false}, - }; - - check(R"( - function Base64FileReader(data) - local reader = {} - local index: number - - function reader:PeekByte() - return data:byte(index) - end - - function reader:Byte() - return data:byte(index - 1) - end - - return reader - end - )"); - - CHECK_EQ("(t1) -> {| Byte: (a) -> (b...), PeekByte: (a) -> (b...) |} where t1 = {+ byte: (t1, number) -> (b...) +}", - toString(requireType("Base64FileReader"))); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ("({ read blah: unknown }) -> ()", toString(requireType("a"))); + CHECK_EQ("({ read gwar: unknown }) -> ()", toString(requireType("b"))); + CHECK_EQ("(...any) -> ({ read blah: unknown, read gwar: unknown }) -> ()", toString(getMainModule()->returnType)); + } + else + { + CHECK_EQ("({+ blah: a +}) -> ()", toString(requireType("a"))); + CHECK_EQ("({+ gwar: a +}) -> ()", toString(requireType("b"))); + CHECK_EQ("() -> ({+ blah: a, gwar: b +}) -> ()", toString(getMainModule()->returnType)); + } } TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") @@ -3309,7 +3504,10 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK("Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" == toString(result.errors[0])); + CHECK( + "Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" == + toString(result.errors[0]) + ); } else { @@ -3321,69 +3519,6 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") } } -TEST_CASE_FIXTURE(Fixture, "shared_selfs") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false}; - - CheckResult result = check(R"( - local t = {} - t.x = 5 - - function t:m1() return self.x end - function t:m2() return self.y end - - return t - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - ToStringOptions opts; - opts.exhaustive = true; - CHECK_EQ("{| m1: ({+ x: a, y: b +}) -> a, m2: ({+ x: a, y: b +}) -> b, x: number |}", toString(requireType("t"), opts)); -} - -TEST_CASE_FIXTURE(Fixture, "shared_selfs_from_free_param") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false}; - - CheckResult result = check(R"( - local function f(t) - function t:m1() return self.x end - function t:m2() return self.y end - end - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK_EQ("({+ m1: ({+ x: a, y: b +}) -> a, m2: ({+ x: a, y: b +}) -> b +}) -> ()", toString(requireType("f"))); -} - -TEST_CASE_FIXTURE(BuiltinsFixture, "shared_selfs_through_metatables") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false}; - - CheckResult result = check(R"( - local t = {} - t.__index = t - setmetatable({}, t) - - function t:m1() return self.x end - function t:m2() return self.y end - - return t - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - ToStringOptions opts; - opts.exhaustive = true; - CHECK_EQ( - toString(requireType("t"), opts), "t1 where t1 = {| __index: t1, m1: ({+ x: a, y: b +}) -> a, m2: ({+ x: a, y: b +}) -> b |}"); -} - TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra") { CheckResult result = check(R"( @@ -3439,62 +3574,6 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_unions_of_indexers_where_key_whose_ty CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0])); } -TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table") -{ - ScopedFastFlag sff[]{ - {FFlag::DebugLuauSharedSelf, true}, - {FFlag::DebugLuauDeferredConstraintResolution, false}, - }; - - CheckResult result = check(R"( - local T = {} - - function T:m() - return self.x, self.y - end - - function T:n() - end - - local U = setmetatable({}, {__index = T}) - - local V = setmetatable({}, {__index = U}) - - return V - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - ToStringOptions opts; - opts.exhaustive = true; - CHECK_EQ(toString(requireType("V"), opts), "{ @metatable { __index: { @metatable { __index: {| m: ({+ x: a, y: b +}) -> (a, b), n: ({+ x: a, y: b +}) -> () |} }, { } } }, { } }"); -} - -TEST_CASE_FIXTURE(Fixture, "quantify_even_that_table_was_never_exported_at_all") -{ - ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true}; - ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false}; - - CheckResult result = check(R"( - local T = {} - - function T:m() - return self.x - end - - function T:n() - return self.y - end - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - ToStringOptions opts; - opts.exhaustive = true; - CHECK_EQ("{| m: ({+ x: a, y: b +}) -> a, n: ({+ x: a, y: b +}) -> b |}", toString(requireType("T"), opts)); -} - TEST_CASE_FIXTURE(BuiltinsFixture, "leaking_bad_metatable_errors") { CheckResult result = check(R"( @@ -3509,6 +3588,9 @@ local b = a.x TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type") { + // CLI-115087 The new solver cannot infer that a table-like type is actually string + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local function f(s) return s:lower() @@ -3524,10 +3606,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - CheckResult result = check(R"( local function f(s) return s:absolutely_no_scalar_has_this_method() @@ -3538,24 +3616,52 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ f("baz" :: "bar" | "baz") )"); - LUAU_REQUIRE_ERROR_COUNT(3, result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // CLI-115090 Error reporting is quite bad in this case. - const std::string expected1 = - R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + // This should be just 3 + LUAU_REQUIRE_ERROR_COUNT(4, result); + + TypeMismatch* tm1 = get(result.errors[0]); + REQUIRE(tm1); + CHECK("typeof(string)" == toString(tm1->givenType)); + CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm1->wantedType)); + + TypeMismatch* tm2 = get(result.errors[1]); + REQUIRE(tm2); + CHECK("typeof(string)" == toString(tm2->givenType)); + CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm2->wantedType)); + + TypeMismatch* tm3 = get(result.errors[2]); + REQUIRE(tm3); + CHECK("typeof(string)" == toString(tm3->givenType)); + CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm3->wantedType)); + + TypeMismatch* tm4 = get(result.errors[3]); + REQUIRE(tm4); + CHECK("typeof(string)" == toString(tm4->givenType)); + CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType)); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(3, result); + + const std::string expected1 = + R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected1, toString(result.errors[0])); + CHECK_EQ(expected1, toString(result.errors[0])); - - const std::string expected2 = - R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' + const std::string expected2 = + R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected2, toString(result.errors[1])); + CHECK_EQ(expected2, toString(result.errors[1])); - const std::string expected3 = R"(Type + const std::string expected3 = R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' @@ -3565,11 +3671,15 @@ Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_ caused by: The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected3, toString(result.errors[2])); + CHECK_EQ(expected3, toString(result.errors[2])); + } } TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible") { + // CLI-115087 The new solver cannot infer that a table-like type is actually string + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local function f(s): string local foo = s:lower() @@ -3596,7 +3706,10 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_ 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."); + 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." + ); CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here."); CHECK(get(result.errors[3])); @@ -4312,9 +4425,11 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") LUAU_REQUIRE_NO_ERRORS(result); - CHECK("({{ read Character: t1 }}, { Character: t1 }) -> () " - "where " - "t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc"))); + CHECK( + "({{ read Character: t1 }}, { Character: t1 }) -> () " + "where " + "t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")) + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index fc61ecb3..237fbcf9 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -58,8 +58,10 @@ TEST_CASE_FIXTURE(Fixture, "tc_error") { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(result.errors[0], - (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}})); + CHECK_EQ( + result.errors[0], + (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}) + ); } } @@ -76,10 +78,16 @@ TEST_CASE_FIXTURE(Fixture, "tc_error_2") { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 18}, Position{0, 22}}, TypeMismatch{ - requireType("a"), - builtinTypes->stringType, - }})); + CHECK_EQ( + result.errors[0], + (TypeError{ + Location{Position{0, 18}, Position{0, 22}}, + TypeMismatch{ + requireType("a"), + builtinTypes->stringType, + } + }) + ); } } @@ -732,9 +740,14 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional") CHECK_EQ("*error-type*", toString(*t0)); - auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { - return get(err); - }); + auto it = std::find_if( + result.errors.begin(), + result.errors.end(), + [](TypeError& err) + { + return get(err); + } + ); CHECK(it != result.errors.end()); } diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 92f07c43..f2e73ede 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -12,7 +12,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTFLAG(LuauUnifierRecursionOnRestart); struct TryUnifyFixture : Fixture @@ -43,11 +42,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "primitives_unify") TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified") { - Type functionOne{ - TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))}}; + Type functionOne{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType})) + }}; - Type functionTwo{TypeVariant{ - FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}}; + Type functionTwo{ + TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))} + }; state.tryUnify(&functionTwo, &functionOne); CHECK(!state.failure); @@ -61,14 +61,14 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified") TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved") { TypePackVar argPackOne{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}}; - Type functionOne{ - TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))}}; + Type functionOne{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType})) + }}; Type functionOneSaved = functionOne; TypePackVar argPackTwo{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}}; - Type functionTwo{ - TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))}}; + Type functionTwo{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->stringType})) + }}; Type functionTwoSaved = functionTwo; @@ -105,13 +105,21 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified") TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved") { Type tableOne{TypeVariant{ - TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->numberType}}}, std::nullopt, globalScope->level, - TableState::Unsealed}, + TableType{ + {{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->numberType}}}, + std::nullopt, + globalScope->level, + TableState::Unsealed + }, }}; Type tableTwo{TypeVariant{ - TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->stringType}}}, std::nullopt, globalScope->level, - TableState::Unsealed}, + TableType{ + {{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->stringType}}}, + std::nullopt, + globalScope->level, + TableState::Unsealed + }, }}; CHECK_NE(*getMutable(&tableOne)->props["foo"].type(), *getMutable(&tableTwo)->props["foo"].type()); @@ -166,10 +174,6 @@ TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything") TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_with_errorType") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - CheckResult result = check(R"( function f(arg: number) end local a @@ -185,10 +189,6 @@ TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_w TEST_CASE_FIXTURE(Fixture, "result_of_failed_typepack_unification_is_constrained") { - ScopedFastFlag sff[] = { - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - CheckResult result = check(R"( function f(arg: number) return arg end local a @@ -405,13 +405,23 @@ static TypeId createTheType(TypeArena& arena, NotNull builtinTypes return arena.addType(FunctionType{ arena.addTypePack({arena.addType(TableType{ - TableType::Props{{{"render", - Property(arena.addType(FunctionType{ - arena.addTypePack({arena.addType(UnionType{{arena.addType(FunctionType{arena.addTypePack({freeTy}), emptyPack}), - arena.addType(TableType{TableType::Props{{"current", {freeTy}}}, std::nullopt, TypeLevel{}, scope, TableState::Sealed})}})}), - arena.addTypePack({builtinTypes->nilType})}))}}}, - std::nullopt, TypeLevel{}, scope, TableState::Sealed})}), - emptyPack}); + TableType::Props{ + {{"render", + Property(arena.addType(FunctionType{ + arena.addTypePack({arena.addType(UnionType{ + {arena.addType(FunctionType{arena.addTypePack({freeTy}), emptyPack}), + arena.addType(TableType{TableType::Props{{"current", {freeTy}}}, std::nullopt, TypeLevel{}, scope, TableState::Sealed})} + })}), + arena.addTypePack({builtinTypes->nilType}) + }))}} + }, + std::nullopt, + TypeLevel{}, + scope, + TableState::Sealed + })}), + emptyPack + }); }; // See CLI-71190 @@ -425,10 +435,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "unifying_two_unions_under_dcr_does_not_creat const TypeId innerType = arena.freshType(nestedScope.get()); - ScopedFastFlag sffs[]{ - {FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true}, - }; - state.enableNewSolver(); SUBCASE("equal_scopes") diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index cf05abf4..108b1fa9 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -230,10 +230,12 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs") CHECK(Location{Position{4, 29}, Position{4, 30}} == result.errors[1].location); CHECK_EQ( - result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}})); + result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}) + ); CHECK_EQ( - result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{builtinTypes->stringType, builtinTypes->numberType}})); + result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{builtinTypes->stringType, builtinTypes->numberType}}) + ); } TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax") @@ -459,9 +461,11 @@ type Packed4 = (Packed3, T...) -> (Packed3, T...) auto tf = lookupType("Packed4"); REQUIRE(tf); - CHECK_EQ(toString(*tf), + CHECK_EQ( + toString(*tf), "((((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...) -> (((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...), T...) -> " - "((((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...) -> (((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...), T...)"); + "((((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...) -> (((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...), T...)" + ); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_variadic") @@ -1054,8 +1058,10 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK(toString(result.errors.at(0)) == - "Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)"); + CHECK( + toString(result.errors.at(0)) == + "Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)" + ); else CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'"); } diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 539b8592..b2a8b084 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -517,10 +517,13 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(toString(result.errors[0]), "Type 'X | Y | Z' could not be converted into '{ w: number }'; type X | Y | Z[0] (X) is not a subtype " - "of { w: number } ({ w: number })\n\t" - "type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t" - "type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })"); + CHECK_EQ( + toString(result.errors[0]), + "Type 'X | Y | Z' could not be converted into '{ w: number }'; type X | Y | Z[0] (X) is not a subtype " + "of { w: number } ({ w: number })\n\t" + "type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t" + "type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })" + ); } else { @@ -592,7 +595,8 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash") u.options.push_back(badCyclicUnionTy); u.options.push_back(arena.addType(TableType{ - {}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed})); + {}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed + })); asMutable(badCyclicUnionTy)->ty.emplace(std::move(u)); @@ -696,8 +700,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "Type '(a) -> a?' could not be converted into '((b) -> b) | ((b?) -> nil)'; none of the union options are compatible"); + CHECK_EQ( + toString(result.errors[0]), + "Type '(a) -> a?' could not be converted into '((b) -> b) | ((b?) -> nil)'; none of the union options are compatible" + ); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index c5a7a2bb..54ec14b8 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -594,8 +594,10 @@ TEST_CASE("chained") { ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; - CHECK(PathBuilder().index(0).readProp("foo").mt().readProp("bar").args().index(1).build() == - Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}})); + CHECK( + PathBuilder().index(0).readProp("foo").mt().readProp("bar").args().index(1).build() == + Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}}) + ); } TEST_SUITE_END(); // TypePathBuilder diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 683e9027..17c0c34b 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -292,8 +292,14 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure") TypeId root = &ttvTweenResult; ModulePtr currentModule = std::make_shared(); - Anyification anyification(¤tModule->internalTypes, frontend.globals.globalScope, builtinTypes, &frontend.iceHandler, builtinTypes->anyType, - builtinTypes->anyTypePack); + Anyification anyification( + ¤tModule->internalTypes, + frontend.globals.globalScope, + builtinTypes, + &frontend.iceHandler, + builtinTypes->anyType, + builtinTypes->anyTypePack + ); std::optional any = anyification.substitute(root); REQUIRE(!anyification.normalizationTooComplex); diff --git a/tests/Variant.test.cpp b/tests/Variant.test.cpp index 83eec519..4abb77f0 100644 --- a/tests/Variant.test.cpp +++ b/tests/Variant.test.cpp @@ -177,15 +177,19 @@ TEST_CASE("Visit") // void-returning visitor, const variants std::string r1; visit( - [&](const auto& v) { + [&](const auto& v) + { r1 += ToStringVisitor()(v); }, - v1c); + v1c + ); visit( - [&](const auto& v) { + [&](const auto& v) + { r1 += ToStringVisitor()(v); }, - v2c); + v2c + ); CHECK(r1 == "12345"); // value-returning visitor, const variants @@ -203,17 +207,21 @@ TEST_CASE("Visit") // value-returning visitor, mutable variant std::string r3; r3 += visit( - [&](auto& v) { + [&](auto& v) + { IncrementVisitor()(v); return ToStringVisitor()(v); }, - v1); + v1 + ); r3 += visit( - [&](auto& v) { + [&](auto& v) + { IncrementVisitor()(v); return ToStringVisitor()(v); }, - v2); + v2 + ); CHECK(r3 == "1231147"); } diff --git a/tests/VecDeque.test.cpp b/tests/VecDeque.test.cpp index d5f32c3f..c8d10d4a 100644 --- a/tests/VecDeque.test.cpp +++ b/tests/VecDeque.test.cpp @@ -294,14 +294,16 @@ const static std::string testStringSet[2][10] = { // This list of non-SSO test strings consists of quotes from Ursula K. Le Guin. {"Love doesn't just sit there, like a stone, it has to be made, like bread; remade all the time, made new.", - "People who deny the existence of dragons are often eaten by dragons. From within.", - "It is good to have an end to journey toward; but it is the journey that matters, in the end.", - "We're each of us alone, to be sure. What can you do but hold your hand out in the dark?", "When you light a candle, you also cast a shadow.", - "You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your spirit, or it is nowhere.", - "To learn which questions are unanswerable, and not to answer them: this skill is most needful in times of stress and darkness.", - "What sane person could live in this world and not be crazy?", - "The only thing that makes life possible is permanent, intolerable uncertainty: not knowing what comes next.", - "My imagination makes me human and makes me a fool; it gives me all the world and exiles me from it."}}; + "People who deny the existence of dragons are often eaten by dragons. From within.", + "It is good to have an end to journey toward; but it is the journey that matters, in the end.", + "We're each of us alone, to be sure. What can you do but hold your hand out in the dark?", + "When you light a candle, you also cast a shadow.", + "You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your spirit, or it is nowhere.", + "To learn which questions are unanswerable, and not to answer them: this skill is most needful in times of stress and darkness.", + "What sane person could live in this world and not be crazy?", + "The only thing that makes life possible is permanent, intolerable uncertainty: not knowing what comes next.", + "My imagination makes me human and makes me a fool; it gives me all the world and exiles me from it."} +}; TEST_CASE("string_queue_test_no_initial_capacity") { diff --git a/tests/main.cpp b/tests/main.cpp index bda1e2fe..42c3ad80 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -205,12 +205,24 @@ struct TeamCityReporter : doctest::IReporter void test_case_end(const doctest::CurrentTestCaseStats& in) override { - printf("##teamcity[testMetadata testName='%s: %s' name='total_asserts' type='number' value='%d']\n", currentTest->m_test_suite, - currentTest->m_name, in.numAssertsCurrentTest); - printf("##teamcity[testMetadata testName='%s: %s' name='failed_asserts' type='number' value='%d']\n", currentTest->m_test_suite, - currentTest->m_name, in.numAssertsFailedCurrentTest); - printf("##teamcity[testMetadata testName='%s: %s' name='runtime' type='number' value='%f']\n", currentTest->m_test_suite, currentTest->m_name, - in.seconds); + printf( + "##teamcity[testMetadata testName='%s: %s' name='total_asserts' type='number' value='%d']\n", + currentTest->m_test_suite, + currentTest->m_name, + in.numAssertsCurrentTest + ); + printf( + "##teamcity[testMetadata testName='%s: %s' name='failed_asserts' type='number' value='%d']\n", + currentTest->m_test_suite, + currentTest->m_name, + in.numAssertsFailedCurrentTest + ); + printf( + "##teamcity[testMetadata testName='%s: %s' name='runtime' type='number' value='%f']\n", + currentTest->m_test_suite, + currentTest->m_name, + in.seconds + ); if (!in.testCaseSuccess) printf("##teamcity[testFailed name='%s: %s']\n", currentTest->m_test_suite, currentTest->m_name); @@ -220,8 +232,12 @@ struct TeamCityReporter : doctest::IReporter void test_case_exception(const doctest::TestCaseException& in) override { - printf("##teamcity[testFailed name='%s: %s' message='Unhandled exception' details='%s']\n", currentTest->m_test_suite, currentTest->m_name, - in.error_string.c_str()); + printf( + "##teamcity[testFailed name='%s: %s' message='Unhandled exception' details='%s']\n", + currentTest->m_test_suite, + currentTest->m_name, + in.error_string.c_str() + ); } void subcase_start(const doctest::SubcaseSignature& /*in*/) override {} diff --git a/tools/faillist.txt b/tools/faillist.txt index 918b761f..da08116f 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -2,7 +2,6 @@ AstQuery.last_argument_function_call_type AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg AutocompleteTest.autocomplete_string_singletons -AutocompleteTest.do_wrong_compatible_nonself_calls AutocompleteTest.string_singleton_as_table_key AutocompleteTest.suggest_table_keys AutocompleteTest.type_correct_suggestion_for_overloads @@ -11,13 +10,6 @@ BuiltinTests.aliased_string_format BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy BuiltinTests.coroutine_resume_anything_goes -BuiltinTests.gmatch_capture_types -BuiltinTests.gmatch_capture_types2 -BuiltinTests.gmatch_capture_types_balanced_escaped_parens -BuiltinTests.gmatch_capture_types_default_capture -BuiltinTests.gmatch_capture_types_parens_in_sets_are_ignored -BuiltinTests.gmatch_capture_types_set_containing_lbracket -BuiltinTests.gmatch_definition BuiltinTests.select_slightly_out_of_range BuiltinTests.select_way_out_of_range BuiltinTests.select_with_variadic_typepack_tail_and_string_head @@ -29,9 +21,7 @@ BuiltinTests.string_format_correctly_ordered_types BuiltinTests.string_format_report_all_type_errors_at_correct_positions BuiltinTests.string_format_use_correct_argument2 BuiltinTests.table_freeze_is_generic -GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.do_not_always_instantiate_generic_intersection_types -GenericsTests.dont_substitute_bound_types GenericsTests.error_detailed_function_mismatch_generic_pack GenericsTests.error_detailed_function_mismatch_generic_types GenericsTests.factories_of_generics @@ -47,10 +37,6 @@ GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments GenericsTests.instantiated_function_argument_names GenericsTests.properties_can_be_instantiated_polytypes GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic -IntersectionTypes.error_detailed_intersection_all -IntersectionTypes.error_detailed_intersection_part -IntersectionTypes.intersect_bool_and_false -IntersectionTypes.intersect_false_and_bool_and_false IntersectionTypes.intersect_metatables IntersectionTypes.intersect_saturate_overloaded_functions IntersectionTypes.intersection_of_tables @@ -74,14 +60,6 @@ IntersectionTypes.union_saturate_overloaded_functions Linter.TableOperationsIndexer ModuleTests.clone_self_property Negations.cofinite_strings_can_be_compared_for_equality -NonstrictModeTests.inconsistent_module_return_types_are_ok -NonstrictModeTests.infer_nullary_function -NonstrictModeTests.infer_the_maximum_number_of_values_the_function_could_return -NonstrictModeTests.inline_table_props_are_also_any -NonstrictModeTests.local_tables_are_not_any -NonstrictModeTests.locals_are_any_by_default -NonstrictModeTests.offer_a_hint_if_you_use_a_dot_instead_of_a_colon -NonstrictModeTests.table_props_are_any Normalize.higher_order_function_with_annotation Normalize.negations_of_tables Normalize.specific_functions_cannot_be_negated @@ -95,7 +73,6 @@ ProvisionalTests.free_options_can_be_unified_together ProvisionalTests.free_options_cannot_be_unified_together ProvisionalTests.generic_type_leak_to_module_interface ProvisionalTests.generic_type_leak_to_module_interface_variadic -ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns ProvisionalTests.luau-polyfill.Array.filter ProvisionalTests.luau_roact_useState_minimization ProvisionalTests.optional_class_instances_are_invariant @@ -125,64 +102,6 @@ RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_in_assert_position RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table RefinementTest.x_is_not_instance_or_else_not_part -TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible -TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode -TableTests.array_factory_function -TableTests.casting_tables_with_props_into_table_with_indexer2 -TableTests.casting_tables_with_props_into_table_with_indexer3 -TableTests.casting_unsealed_tables_with_props_into_table_with_indexer -TableTests.checked_prop_too_early -TableTests.common_table_element_general -TableTests.common_table_element_union_in_call_tail -TableTests.confusing_indexing -TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode -TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar -TableTests.dont_leak_free_table_props -TableTests.dont_suggest_exact_match_keys -TableTests.error_detailed_indexer_key -TableTests.error_detailed_indexer_value -TableTests.error_detailed_metatable_prop -TableTests.explicitly_typed_table -TableTests.explicitly_typed_table_error -TableTests.explicitly_typed_table_with_indexer -TableTests.generalize_table_argument -TableTests.indexer_on_sealed_table_must_unify_with_free_table -TableTests.indexers_get_quantified_too -TableTests.infer_indexer_from_array_like_table -TableTests.infer_indexer_from_its_variable_type_and_unifiable -TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound -TableTests.meta_add -TableTests.meta_add_inferred -TableTests.metatable_mismatch_should_fail -TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred -TableTests.nil_assign_doesnt_hit_indexer -TableTests.ok_to_provide_a_subtype_during_construction -TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr -TableTests.okay_to_add_property_to_unsealed_tables_by_assignment -TableTests.okay_to_add_property_to_unsealed_tables_by_function_call -TableTests.only_ascribe_synthetic_names_at_module_scope -TableTests.open_table_unification_2 -TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table -TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2 -TableTests.persistent_sealed_table_is_immutable -TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table -TableTests.recursive_metatable_type_call -TableTests.right_table_missing_key2 -TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type -TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type -TableTests.sealed_table_indexers_must_unify -TableTests.table_call_metamethod_basic -TableTests.table_call_metamethod_must_be_callable -TableTests.table_param_width_subtyping_2 -TableTests.table_param_width_subtyping_3 -TableTests.table_simple_call -TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors -TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2 -TableTests.table_unification_4 -TableTests.table_unifies_into_map -TableTests.type_mismatch_on_massive_table_is_cut_short -TableTests.used_colon_instead_of_dot -TableTests.used_dot_instead_of_colon ToDot.function ToString.exhaustive_toString_of_cyclic_table ToString.free_types @@ -190,7 +109,6 @@ ToString.named_metatable_toStringNamedFunction ToString.no_parentheses_around_cyclic_function_type_in_intersection ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics ToString.primitive -ToString.toStringDetailed2 ToString.toStringErrorPack TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.result_of_failed_typepack_unification_is_constrained @@ -208,18 +126,6 @@ TypeAliases.report_shadowed_aliases TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename TypeAliases.type_alias_of_an_imported_recursive_generic_type -TypeFunctionTests.add_function_at_work -TypeFunctionTests.cyclic_add_function_at_work -TypeFunctionTests.cyclic_concat_function_at_work -TypeFunctionTests.didnt_quite_exceed_distributivity_limits -TypeFunctionTests.ensure_equivalence_with_distributivity -TypeFunctionTests.function_as_fn_arg -TypeFunctionTests.index_type_function_works_w_generic_types -TypeFunctionTests.internal_functions_raise_errors -TypeFunctionTests.keyof_oss_crash_gh1161 -TypeFunctionTests.mul_function_with_union_of_multiplicatives -TypeFunctionTests.mul_function_with_union_of_multiplicatives_2 -TypeFunctionTests.unsolvable_function TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload TypeInfer.check_type_infer_recursion_count TypeInfer.checking_should_not_ice @@ -235,11 +141,9 @@ TypeInfer.no_stack_overflow_from_isoptional TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2 TypeInfer.tc_after_error_recovery_no_replacement_name_in_error -TypeInfer.tc_if_else_expressions_expected_type_3 TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_normalizer TypeInfer.unify_nearly_identical_recursive_types -TypeInferClasses.callable_classes TypeInferClasses.cannot_unify_class_instance_with_primitive TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.detailed_class_unification_error @@ -292,12 +196,10 @@ TypeInferFunctions.too_few_arguments_variadic TypeInferFunctions.too_few_arguments_variadic_generic TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_many_arguments -TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_no_function TypeInferFunctions.unifier_should_not_bind_free_types TypeInferLoops.cli_68448_iterators_need_not_accept_nil -TypeInferLoops.dcr_iteration_fragmented_keys TypeInferLoops.dcr_iteration_on_never_gives_never TypeInferLoops.dcr_xpath_candidates TypeInferLoops.for_in_loop @@ -306,7 +208,6 @@ TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given TypeInferLoops.for_in_loop_on_error TypeInferLoops.for_in_loop_on_non_function TypeInferLoops.for_in_loop_with_next -TypeInferLoops.for_in_with_just_one_iterator_is_ok TypeInferLoops.for_loop TypeInferLoops.ipairs_produces_integral_indices TypeInferLoops.iterate_over_properties diff --git a/tools/natvis/Analysis.natvis b/tools/natvis/Analysis.natvis index ca66cbe2..74cc18fe 100644 --- a/tools/natvis/Analysis.natvis +++ b/tools/natvis/Analysis.natvis @@ -6,70 +6,70 @@ - {{ typeId=0, value={*($T1*)storage} }} - {{ typeId=1, value={*($T2*)storage} }} - {{ typeId=2, value={*($T3*)storage} }} - {{ typeId=3, value={*($T4*)storage} }} - {{ typeId=4, value={*($T5*)storage} }} - {{ typeId=5, value={*($T6*)storage} }} - {{ typeId=6, value={*($T7*)storage} }} - {{ typeId=7, value={*($T8*)storage} }} - {{ typeId=8, value={*($T9*)storage} }} - {{ typeId=9, value={*($T10*)storage} }} - {{ typeId=10, value={*($T11*)storage} }} - {{ typeId=11, value={*($T12*)storage} }} - {{ typeId=12, value={*($T13*)storage} }} - {{ typeId=13, value={*($T14*)storage} }} - {{ typeId=14, value={*($T15*)storage} }} - {{ typeId=15, value={*($T16*)storage} }} - {{ typeId=16, value={*($T17*)storage} }} - {{ typeId=17, value={*($T18*)storage} }} - {{ typeId=18, value={*($T19*)storage} }} - {{ typeId=19, value={*($T20*)storage} }} - {{ typeId=20, value={*($T21*)storage} }} - {{ typeId=21, value={*($T22*)storage} }} - {{ typeId=22, value={*($T23*)storage} }} - {{ typeId=23, value={*($T24*)storage} }} - {{ typeId=24, value={*($T25*)storage} }} - {{ typeId=25, value={*($T26*)storage} }} - {{ typeId=26, value={*($T27*)storage} }} - {{ typeId=27, value={*($T28*)storage} }} - {{ typeId=28, value={*($T29*)storage} }} - {{ typeId=29, value={*($T30*)storage} }} - {{ typeId=30, value={*($T31*)storage} }} - {{ typeId=31, value={*($T32*)storage} }} - {{ typeId=32, value={*($T33*)storage} }} - {{ typeId=33, value={*($T34*)storage} }} - {{ typeId=34, value={*($T35*)storage} }} - {{ typeId=35, value={*($T36*)storage} }} - {{ typeId=36, value={*($T37*)storage} }} - {{ typeId=37, value={*($T38*)storage} }} - {{ typeId=38, value={*($T39*)storage} }} - {{ typeId=39, value={*($T40*)storage} }} - {{ typeId=40, value={*($T41*)storage} }} - {{ typeId=41, value={*($T42*)storage} }} - {{ typeId=42, value={*($T43*)storage} }} - {{ typeId=43, value={*($T44*)storage} }} - {{ typeId=44, value={*($T45*)storage} }} - {{ typeId=45, value={*($T46*)storage} }} - {{ typeId=46, value={*($T47*)storage} }} - {{ typeId=47, value={*($T48*)storage} }} - {{ typeId=48, value={*($T49*)storage} }} - {{ typeId=49, value={*($T50*)storage} }} - {{ typeId=50, value={*($T51*)storage} }} - {{ typeId=51, value={*($T52*)storage} }} - {{ typeId=52, value={*($T53*)storage} }} - {{ typeId=53, value={*($T54*)storage} }} - {{ typeId=54, value={*($T55*)storage} }} - {{ typeId=55, value={*($T56*)storage} }} - {{ typeId=56, value={*($T57*)storage} }} - {{ typeId=57, value={*($T58*)storage} }} - {{ typeId=58, value={*($T59*)storage} }} - {{ typeId=59, value={*($T60*)storage} }} - {{ typeId=60, value={*($T61*)storage} }} - {{ typeId=61, value={*($T62*)storage} }} - {{ typeId=62, value={*($T63*)storage} }} - {{ typeId=63, value={*($T64*)storage} }} + {{ {"$T1"}: {*($T1*)storage} }} + {{ {"$T2"}: {*($T2*)storage} }} + {{ {"$T3"}: {*($T3*)storage} }} + {{ {"$T4"}: {*($T4*)storage} }} + {{ {"$T5"}: {*($T5*)storage} }} + {{ {"$T6"}: {*($T6*)storage} }} + {{ {"$T7"}: {*($T7*)storage} }} + {{ {"$T8"}: {*($T8*)storage} }} + {{ {"$T9"}: {*($T9*)storage} }} + {{ {"$T10"}: {*($T10*)storage} }} + {{ {"$T11"}: {*($T11*)storage} }} + {{ {"$T12"}: {*($T12*)storage} }} + {{ {"$T13"}: {*($T13*)storage} }} + {{ {"$T14"}: {*($T14*)storage} }} + {{ {"$T15"}: {*($T15*)storage} }} + {{ {"$T16"}: {*($T16*)storage} }} + {{ {"$T17"}: {*($T17*)storage} }} + {{ {"$T18"}: {*($T18*)storage} }} + {{ {"$T19"}: {*($T19*)storage} }} + {{ {"$T20"}: {*($T20*)storage} }} + {{ {"$T21"}: {*($T21*)storage} }} + {{ {"$T22"}: {*($T22*)storage} }} + {{ {"$T23"}: {*($T23*)storage} }} + {{ {"$T24"}: {*($T24*)storage} }} + {{ {"$T25"}: {*($T25*)storage} }} + {{ {"$T26"}: {*($T26*)storage} }} + {{ {"$T27"}: {*($T27*)storage} }} + {{ {"$T28"}: {*($T28*)storage} }} + {{ {"$T29"}: {*($T29*)storage} }} + {{ {"$T30"}: {*($T30*)storage} }} + {{ {"$T31"}: {*($T31*)storage} }} + {{ {"$T32"}: {*($T32*)storage} }} + {{ {"$T33"}: {*($T33*)storage} }} + {{ {"$T34"}: {*($T34*)storage} }} + {{ {"$T35"}: {*($T35*)storage} }} + {{ {"$T36"}: {*($T36*)storage} }} + {{ {"$T37"}: {*($T37*)storage} }} + {{ {"$T38"}: {*($T38*)storage} }} + {{ {"$T39"}: {*($T39*)storage} }} + {{ {"$T40"}: {*($T40*)storage} }} + {{ {"$T41"}: {*($T41*)storage} }} + {{ {"$T42"}: {*($T42*)storage} }} + {{ {"$T43"}: {*($T43*)storage} }} + {{ {"$T44"}: {*($T44*)storage} }} + {{ {"$T45"}: {*($T45*)storage} }} + {{ {"$T46"}: {*($T46*)storage} }} + {{ {"$T47"}: {*($T47*)storage} }} + {{ {"$T48"}: {*($T48*)storage} }} + {{ {"$T49"}: {*($T49*)storage} }} + {{ {"$T50"}: {*($T50*)storage} }} + {{ {"$T51"}: {*($T51*)storage} }} + {{ {"$T52"}: {*($T52*)storage} }} + {{ {"$T53"}: {*($T53*)storage} }} + {{ {"$T54"}: {*($T54*)storage} }} + {{ {"$T55"}: {*($T55*)storage} }} + {{ {"$T56"}: {*($T56*)storage} }} + {{ {"$T57"}: {*($T57*)storage} }} + {{ {"$T58"}: {*($T58*)storage} }} + {{ {"$T59"}: {*($T59*)storage} }} + {{ {"$T60"}: {*($T60*)storage} }} + {{ {"$T61"}: {*($T61*)storage} }} + {{ {"$T62"}: {*($T62*)storage} }} + {{ {"$T63"}: {*($T63*)storage} }} + {{ {"$T64"}: {*($T64*)storage} }} typeId *($T1*)storage