diff --git a/.gitignore b/.gitignore index 5688dff5..af77f73c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ /default.prof* /fuzz-* /luau +/luau-tests +/luau-analyze +__pycache__ diff --git a/Analysis/include/Luau/ApplyTypeFunction.h b/Analysis/include/Luau/ApplyTypeFunction.h new file mode 100644 index 00000000..8da3bc42 --- /dev/null +++ b/Analysis/include/Luau/ApplyTypeFunction.h @@ -0,0 +1,32 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Substitution.h" +#include "Luau/TxnLog.h" +#include "Luau/TypeVar.h" + +namespace Luau +{ + +// A substitution which replaces the type parameters of a type function by arguments +struct ApplyTypeFunction : Substitution +{ + ApplyTypeFunction(TypeArena* arena) + : Substitution(TxnLog::empty(), arena) + , encounteredForwardedType(false) + { + } + + // Never set under deferred constraint resolution. + bool encounteredForwardedType; + std::unordered_map typeArguments; + std::unordered_map typePackArguments; + bool ignoreChildren(TypeId ty) override; + bool ignoreChildren(TypePackId tp) override; + bool isDirty(TypeId ty) override; + bool isDirty(TypePackId tp) override; + TypeId clean(TypeId ty) override; + TypePackId clean(TypePackId tp) override; +}; + +} // namespace Luau diff --git a/Analysis/include/Luau/JsonEncoder.h b/Analysis/include/Luau/AstJsonEncoder.h similarity index 100% rename from Analysis/include/Luau/JsonEncoder.h rename to Analysis/include/Luau/AstJsonEncoder.h diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index dcfb14b4..5b737146 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -4,6 +4,7 @@ #include "Luau/Ast.h" // Used for some of the enumerations #include "Luau/NotNull.h" #include "Luau/Variant.h" +#include "Luau/TypeVar.h" #include #include @@ -12,7 +13,8 @@ namespace Luau { -struct Scope2; +struct Scope; + struct TypeVar; using TypeId = const TypeVar*; @@ -38,7 +40,7 @@ struct GeneralizationConstraint { TypeId generalizedType; TypeId sourceType; - Scope2* scope; + Scope* scope; }; // subType ~ inst superType @@ -70,8 +72,15 @@ struct NameConstraint std::string name; }; +// target ~ inst target +struct TypeAliasExpansionConstraint +{ + // Must be a PendingExpansionTypeVar. + TypeId target; +}; + using ConstraintV = Variant; + BinaryConstraint, NameConstraint, TypeAliasExpansionConstraint>; using ConstraintPtr = std::unique_ptr; struct Constraint diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 513446fd..69f35d46 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -17,21 +17,22 @@ namespace Luau { -struct Scope2; +struct Scope; +using ScopePtr = std::shared_ptr; struct ConstraintGraphBuilder { // A list of all the scopes in the module. This vector holds ownership of the // scope pointers; the scopes themselves borrow pointers to other scopes to // define the scope hierarchy. - std::vector>> scopes; + std::vector> scopes; ModuleName moduleName; SingletonTypes& singletonTypes; const NotNull arena; // The root scope of the module we're generating constraints for. // This is null when the CGB is initially constructed. - Scope2* rootScope; + Scope* rootScope; // A mapping of AST node to TypeId. DenseHashMap astTypes{nullptr}; // A mapping of AST node to TypePackId. @@ -41,6 +42,8 @@ struct ConstraintGraphBuilder DenseHashMap astResolvedTypes{nullptr}; // Type packs resolved from type annotations. Analogous to astTypePacks. DenseHashMap astResolvedTypePacks{nullptr}; + // Defining scopes for AST nodes. + DenseHashMap astTypeAliasDefiningScopes{nullptr}; int recursionCount = 0; @@ -50,42 +53,42 @@ struct ConstraintGraphBuilder // Occasionally constraint generation needs to produce an ICE. const NotNull ice; - NotNull globalScope; + NotNull globalScope; - ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope); + ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope); /** * Fabricates a new free type belonging to a given scope. * @param scope the scope the free type belongs to. */ - TypeId freshType(NotNull scope); + TypeId freshType(const ScopePtr& scope); /** * Fabricates a new free type pack belonging to a given scope. * @param scope the scope the free type pack belongs to. */ - TypePackId freshTypePack(NotNull scope); + TypePackId freshTypePack(const ScopePtr& scope); /** * Fabricates a scope that is a child of another scope. * @param location the lexical extent of the scope in the source code. * @param parent the parent scope of the new scope. Must not be null. */ - NotNull childScope(Location location, NotNull parent); + ScopePtr childScope(Location location, const ScopePtr& parent); /** * Adds a new constraint with no dependencies to a given scope. * @param scope the scope to add the constraint to. * @param cv the constraint variant to add. */ - void addConstraint(NotNull scope, ConstraintV cv); + void addConstraint(const ScopePtr& scope, ConstraintV cv); /** * Adds a constraint to a given scope. * @param scope the scope to add the constraint to. Must not be null. * @param c the constraint to add. */ - void addConstraint(NotNull scope, std::unique_ptr c); + void addConstraint(const ScopePtr& scope, std::unique_ptr c); /** * The entry point to the ConstraintGraphBuilder. This will construct a set @@ -94,23 +97,26 @@ struct ConstraintGraphBuilder */ void visit(AstStatBlock* block); - void visitBlockWithoutChildScope(NotNull scope, AstStatBlock* block); + void visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); - void visit(NotNull scope, AstStat* stat); - void visit(NotNull scope, AstStatBlock* block); - void visit(NotNull scope, AstStatLocal* local); - void visit(NotNull scope, AstStatFor* for_); - void visit(NotNull scope, AstStatLocalFunction* function); - void visit(NotNull scope, AstStatFunction* function); - void visit(NotNull scope, AstStatReturn* ret); - void visit(NotNull scope, AstStatAssign* assign); - void visit(NotNull scope, AstStatIf* ifStatement); - void visit(NotNull scope, AstStatTypeAlias* alias); + void visit(const ScopePtr& scope, AstStat* stat); + void visit(const ScopePtr& scope, AstStatBlock* block); + void visit(const ScopePtr& scope, AstStatLocal* local); + void visit(const ScopePtr& scope, AstStatFor* for_); + void visit(const ScopePtr& scope, AstStatLocalFunction* function); + void visit(const ScopePtr& scope, AstStatFunction* function); + void visit(const ScopePtr& scope, AstStatReturn* ret); + void visit(const ScopePtr& scope, AstStatAssign* assign); + void visit(const ScopePtr& scope, AstStatIf* ifStatement); + void visit(const ScopePtr& scope, AstStatTypeAlias* alias); + void visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal); + void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass); + void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); - TypePackId checkExprList(NotNull scope, const AstArray& exprs); + TypePackId checkExprList(const ScopePtr& scope, const AstArray& exprs); - TypePackId checkPack(NotNull scope, AstArray exprs); - TypePackId checkPack(NotNull scope, AstExpr* expr); + TypePackId checkPack(const ScopePtr& scope, AstArray exprs); + TypePackId checkPack(const ScopePtr& scope, AstExpr* expr); /** * Checks an expression that is expected to evaluate to one type. @@ -118,13 +124,13 @@ struct ConstraintGraphBuilder * @param expr the expression to check. * @return the type of the expression. */ - TypeId check(NotNull scope, AstExpr* expr); + TypeId check(const ScopePtr& scope, AstExpr* expr); - TypeId checkExprTable(NotNull scope, AstExprTable* expr); - TypeId check(NotNull scope, AstExprIndexName* indexName); - TypeId check(NotNull scope, AstExprIndexExpr* indexExpr); - TypeId check(NotNull scope, AstExprUnary* unary); - TypeId check(NotNull scope, AstExprBinary* binary); + TypeId checkExprTable(const ScopePtr& scope, AstExprTable* expr); + TypeId check(const ScopePtr& scope, AstExprIndexName* indexName); + TypeId check(const ScopePtr& scope, AstExprIndexExpr* indexExpr); + TypeId check(const ScopePtr& scope, AstExprUnary* unary); + TypeId check(const ScopePtr& scope, AstExprBinary* binary); struct FunctionSignature { @@ -133,28 +139,29 @@ struct ConstraintGraphBuilder // The scope that encompasses the function's signature. May be nullptr // if there was no need for a signature scope (the function has no // generics). - Scope2* signatureScope; + ScopePtr signatureScope; // The scope that encompasses the function's body. Is a child scope of // signatureScope, if present. - NotNull bodyScope; + ScopePtr bodyScope; }; - FunctionSignature checkFunctionSignature(NotNull parent, AstExprFunction* fn); + FunctionSignature checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn); /** * Checks the body of a function expression. * @param scope the interior scope of the body of the function. * @param fn the function expression to check. */ - void checkFunctionBody(NotNull scope, AstExprFunction* fn); + void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn); /** * Resolves a type from its AST annotation. * @param scope the scope that the type annotation appears within. * @param ty the AST annotation to resolve. + * @param topLevel whether the annotation is a "top-level" annotation. * @return the type of the AST annotation. **/ - TypeId resolveType(NotNull scope, AstType* ty); + TypeId resolveType(const ScopePtr& scope, AstType* ty, bool topLevel = false); /** * Resolves a type pack from its AST annotation. @@ -162,14 +169,14 @@ struct ConstraintGraphBuilder * @param tp the AST annotation to resolve. * @return the type pack of the AST annotation. **/ - TypePackId resolveTypePack(NotNull scope, AstTypePack* tp); + TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp); - TypePackId resolveTypePack(NotNull scope, const AstTypeList& list); + TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list); - std::vector> createGenerics(NotNull scope, AstArray generics); - std::vector> createGenericPacks(NotNull scope, AstArray packs); + std::vector> createGenerics(const ScopePtr& scope, AstArray generics); + std::vector> createGenericPacks(const ScopePtr& scope, AstArray packs); - TypeId flattenPack(NotNull scope, Location location, TypePackId tp); + TypeId flattenPack(const ScopePtr& scope, Location location, TypePackId tp); void reportError(Location location, TypeErrorData err); void reportCodeTooComplex(Location location); @@ -180,7 +187,7 @@ struct ConstraintGraphBuilder * real" in a general way is going to be pretty hard, so we are choosing not to tackle that yet. For now, we do an * initial scan of the AST and note what globals are defined. */ - void prepopulateGlobalScope(NotNull globalScope, AstStatBlock* program); + void prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program); }; /** @@ -193,6 +200,6 @@ struct ConstraintGraphBuilder * @return a list of pointers to constraints contained within the scope graph. * None of these pointers should be null. */ -std::vector> collectConstraints(NotNull rootScope); +std::vector> collectConstraints(NotNull rootScope); } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index cf88efb6..9cc0e4cb 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -17,15 +17,35 @@ namespace Luau // never dereference this pointer. using BlockedConstraintId = const void*; +struct InstantiationSignature +{ + TypeFun fn; + std::vector arguments; + std::vector packArguments; + + bool operator==(const InstantiationSignature& rhs) const; + bool operator!=(const InstantiationSignature& rhs) const + { + return !((*this) == rhs); + } +}; + +struct HashInstantiationSignature +{ + size_t operator()(const InstantiationSignature& signature) const; +}; + struct ConstraintSolver { TypeArena* arena; InternalErrorReporter iceReporter; - // The entire set of constraints that the solver is trying to resolve. It - // is important to not add elements to this vector, lest the underlying - // storage that we retain pointers to be mutated underneath us. - const std::vector> constraints; - NotNull rootScope; + // The entire set of constraints that the solver is trying to resolve. + std::vector> constraints; + NotNull rootScope; + + // Constraints that the solver has generated, rather than sourcing from the + // scope tree. + std::vector> solverConstraints; // This includes every constraint that has not been fully solved. // A constraint can be both blocked and unsolved, for instance. @@ -37,10 +57,12 @@ struct ConstraintSolver std::unordered_map, size_t> blockedConstraints; // A mapping of type/pack pointers to the constraints they block. std::unordered_map>> blocked; + // Memoized instantiations of type aliases. + DenseHashMap instantiatedAliases{{}}; ConstraintSolverLogger logger; - explicit ConstraintSolver(TypeArena* arena, NotNull rootScope); + explicit ConstraintSolver(TypeArena* arena, NotNull rootScope); /** * Attempts to dispatch all pending constraints and reach a type solution @@ -62,6 +84,7 @@ struct ConstraintSolver bool tryDispatch(const UnaryConstraint& c, NotNull constraint, bool force); bool tryDispatch(const BinaryConstraint& c, NotNull constraint, bool force); bool tryDispatch(const NameConstraint& c, NotNull constraint); + bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint); void block(NotNull target, NotNull constraint); /** @@ -102,6 +125,11 @@ struct ConstraintSolver */ void unify(TypePackId subPack, TypePackId superPack); + /** Pushes a new solver constraint to the solver. + * @param cv the body of the constraint. + **/ + void pushConstraint(ConstraintV cv); + private: /** * Marks a constraint as being blocked on a type or type pack. The constraint @@ -121,6 +149,6 @@ private: void unblock_(BlockedConstraintId progressed); }; -void dump(NotNull rootScope, struct ToStringOptions& opts); +void dump(NotNull rootScope, struct ToStringOptions& opts); } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolverLogger.h b/Analysis/include/Luau/ConstraintSolverLogger.h index 55336a23..fe2177c4 100644 --- a/Analysis/include/Luau/ConstraintSolverLogger.h +++ b/Analysis/include/Luau/ConstraintSolverLogger.h @@ -15,8 +15,8 @@ namespace Luau struct ConstraintSolverLogger { std::string compileOutput(); - void captureBoundarySnapshot(const Scope2* rootScope, std::vector>& unsolvedConstraints); - void prepareStepSnapshot(const Scope2* rootScope, NotNull current, std::vector>& unsolvedConstraints); + void captureBoundarySnapshot(const Scope* rootScope, std::vector>& unsolvedConstraints); + void prepareStepSnapshot(const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints); void commitPreparedStepSnapshot(); private: diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 27fd4d7e..9b8ec19e 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -152,7 +152,9 @@ struct Frontend void registerBuiltinDefinition(const std::string& name, std::function); void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); - NotNull getGlobalScope2(); + LoadDefinitionFileResult loadDefinitionFile(std::string_view source, const std::string& packageName); + + NotNull getGlobalScope(); private: ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope); @@ -169,7 +171,7 @@ private: std::unordered_map environments; std::unordered_map> builtinDefinitions; - std::unique_ptr globalScope2; + ScopePtr globalScope; public: FileResolver* fileResolver; @@ -180,6 +182,7 @@ public: ConfigResolver* configResolver; FrontendOptions options; InternalErrorReporter iceHandler; + TypeArena globalTypes; TypeArena arenaForAutocomplete; std::unordered_map sourceNodes; diff --git a/Analysis/include/Luau/JsonEmitter.h b/Analysis/include/Luau/JsonEmitter.h new file mode 100644 index 00000000..0bf3327a --- /dev/null +++ b/Analysis/include/Luau/JsonEmitter.h @@ -0,0 +1,235 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include +#include +#include + +#include "Luau/NotNull.h" + +namespace Luau::Json +{ + +struct JsonEmitter; + +/// Writes a value to the JsonEmitter. Note that this can produce invalid JSON +/// if you do not insert commas or appropriate object / array syntax. +template +void write(JsonEmitter&, T) = delete; + +/// Writes a boolean to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param b the boolean to write. +void write(JsonEmitter& emitter, bool b); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, int i); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, long i); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, long long i); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, unsigned int i); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, unsigned long i); + +/// Writes an integer to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param i the integer to write. +void write(JsonEmitter& emitter, unsigned long long i); + +/// Writes a double to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param d the double to write. +void write(JsonEmitter& emitter, double d); + +/// Writes a string to a JsonEmitter. The string will be escaped. +/// @param emitter the emitter to write to. +/// @param sv the string to write. +void write(JsonEmitter& emitter, std::string_view sv); + +/// Writes a character to a JsonEmitter as a single-character string. The +/// character will be escaped. +/// @param emitter the emitter to write to. +/// @param c the string to write. +void write(JsonEmitter& emitter, char c); + +/// Writes a string to a JsonEmitter. The string will be escaped. +/// @param emitter the emitter to write to. +/// @param str the string to write. +void write(JsonEmitter& emitter, const char* str); + +/// Writes a string to a JsonEmitter. The string will be escaped. +/// @param emitter the emitter to write to. +/// @param str the string to write. +void write(JsonEmitter& emitter, const std::string& str); + +/// Writes null to a JsonEmitter. +/// @param emitter the emitter to write to. +void write(JsonEmitter& emitter, std::nullptr_t); + +/// Writes null to a JsonEmitter. +/// @param emitter the emitter to write to. +void write(JsonEmitter& emitter, std::nullopt_t); + +struct ObjectEmitter; +struct ArrayEmitter; + +struct JsonEmitter +{ + JsonEmitter(); + + /// Converts the current contents of the JsonEmitter to a string value. This + /// does not invalidate the emitter, but it does not clear it either. + std::string str(); + + /// Returns the current comma state and resets it to false. Use popComma to + /// restore the old state. + /// @returns the previous comma state. + bool pushComma(); + + /// Restores a previous comma state. + /// @param c the comma state to restore. + void popComma(bool c); + + /// Writes a raw sequence of characters to the buffer, without escaping or + /// other processing. + /// @param sv the character sequence to write. + void writeRaw(std::string_view sv); + + /// Writes a character to the buffer, without escaping or other processing. + /// @param c the character to write. + void writeRaw(char c); + + /// Writes a comma if this wasn't the first time writeComma has been + /// invoked. Otherwise, sets the comma state to true. + /// @see pushComma + /// @see popComma + void writeComma(); + + /// Begins writing an object to the emitter. + /// @returns an ObjectEmitter that can be used to write key-value pairs. + ObjectEmitter writeObject(); + + /// Begins writing an array to the emitter. + /// @returns an ArrayEmitter that can be used to write values. + ArrayEmitter writeArray(); + +private: + bool comma = false; + std::vector chunks; + + void newChunk(); +}; + +/// An interface for writing an object into a JsonEmitter instance. +/// @see JsonEmitter::writeObject +struct ObjectEmitter +{ + ObjectEmitter(NotNull emitter); + ~ObjectEmitter(); + + NotNull emitter; + bool comma; + bool finished; + + /// Writes a key-value pair to the associated JsonEmitter. Keys will be escaped. + /// @param name the name of the key-value pair. + /// @param value the value to write. + template + void writePair(std::string_view name, T value) + { + if (finished) + { + return; + } + + emitter->writeComma(); + write(*emitter, name); + emitter->writeRaw(':'); + write(*emitter, value); + } + + /// Finishes writing the object, appending a closing `}` character and + /// resetting the comma state of the associated emitter. This can only be + /// called once, and once called will render the emitter unusable. This + /// method is also called when the ObjectEmitter is destructed. + void finish(); +}; + +/// An interface for writing an array into a JsonEmitter instance. Array values +/// do not need to be the same type. +/// @see JsonEmitter::writeArray +struct ArrayEmitter +{ + ArrayEmitter(NotNull emitter); + ~ArrayEmitter(); + + NotNull emitter; + bool comma; + bool finished; + + /// Writes a value to the array. + /// @param value the value to write. + template + void writeValue(T value) + { + if (finished) + { + return; + } + + emitter->writeComma(); + write(*emitter, value); + } + + /// Finishes writing the object, appending a closing `]` character and + /// resetting the comma state of the associated emitter. This can only be + /// called once, and once called will render the emitter unusable. This + /// method is also called when the ArrayEmitter is destructed. + void finish(); +}; + +/// Writes a vector as an array to a JsonEmitter. +/// @param emitter the emitter to write to. +/// @param vec the vector to write. +template +void write(JsonEmitter& emitter, const std::vector& vec) +{ + ArrayEmitter a = emitter.writeArray(); + + for (const T& value : vec) + a.writeValue(value); + + a.finish(); +} + +/// Writes an optional to a JsonEmitter. Will write the contained value, if +/// present, or null, if no value is present. +/// @param emitter the emitter to write to. +/// @param v the value to write. +template +void write(JsonEmitter& emitter, const std::optional& v) +{ + if (v.has_value()) + write(emitter, *v); + else + emitter.writeRaw("null"); +} + +} // namespace Luau::Json diff --git a/Analysis/include/Luau/Linter.h b/Analysis/include/Luau/Linter.h index 6c7ce47f..2cd91d54 100644 --- a/Analysis/include/Luau/Linter.h +++ b/Analysis/include/Luau/Linter.h @@ -52,6 +52,7 @@ struct LintWarning Code_DuplicateCondition = 24, Code_MisleadingAndOr = 25, Code_CommentDirective = 26, + Code_IntegerParsing = 27, Code__Count }; diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index b3105b78..6f4c6098 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -68,8 +68,7 @@ struct Module std::shared_ptr allocator; std::shared_ptr names; - std::vector> scopes; // never empty - std::vector>> scope2s; // never empty + std::vector> scopes; // never empty DenseHashMap astTypes{nullptr}; DenseHashMap astTypePacks{nullptr}; @@ -86,7 +85,6 @@ struct Module bool timeout = false; ScopePtr getModuleScope() const; - Scope2* getModuleScope2() const; // Once a module has been typechecked, we clone its public interface into a separate arena. // This helps us to force TypeVar ownership into a DAG rather than a DCG. diff --git a/Analysis/include/Luau/Quantify.h b/Analysis/include/Luau/Quantify.h index f46f0cb5..7edf23b8 100644 --- a/Analysis/include/Luau/Quantify.h +++ b/Analysis/include/Luau/Quantify.h @@ -7,9 +7,9 @@ namespace Luau { struct TypeArena; -struct Scope2; +struct Scope; void quantify(TypeId ty, TypeLevel level); -TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope); +TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope); } // namespace Luau diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 0eaecf1d..55ca54c6 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -32,10 +32,16 @@ struct Scope explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr. const ScopePtr parent; // null for the root + + // All the children of this scope. + std::vector> children; std::unordered_map bindings; + std::unordered_map typeBindings; + std::unordered_map typePackBindings; TypePackId returnType; - bool breakOk = false; std::optional varargPack; + // All constraints belonging to this scope. + std::vector constraints; TypeLevel level; @@ -45,7 +51,9 @@ struct Scope std::unordered_map> importedTypeBindings; - std::optional lookup(const Symbol& name); + std::optional lookup(Symbol sym); + std::optional lookupTypeBinding(const Name& name); + std::optional lookupTypePackBinding(const Name& name); std::optional lookupType(const Name& name); std::optional lookupImportedType(const Name& moduleAlias, const Name& name); @@ -66,24 +74,4 @@ struct Scope std::unordered_map typeAliasTypePackParameters; }; -struct Scope2 -{ - // The parent scope of this scope. Null if there is no parent (i.e. this - // is the module-level scope). - Scope2* parent = nullptr; - // All the children of this scope. - std::vector> children; - std::unordered_map bindings; // TODO: I think this can be a DenseHashMap - std::unordered_map typeBindings; - std::unordered_map typePackBindings; - TypePackId returnType; - std::optional varargPack; - // All constraints belonging to this scope. - std::vector constraints; - - std::optional lookup(Symbol sym); - std::optional lookupTypeBinding(const Name& name); - std::optional lookupTypePackBinding(const Name& name); -}; - } // namespace Luau diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index f3c3ae9a..6ad38f9d 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -139,6 +139,8 @@ struct FindDirty : Tarjan { std::vector dirty; + void clearTarjan(); + // Get/set the dirty bit for an index (grows the vector if needed) bool getDirty(int index); void setDirty(int index, bool d); @@ -176,6 +178,8 @@ public: TypeArena* arena; DenseHashMap newTypes{nullptr}; DenseHashMap newPacks{nullptr}; + DenseHashSet replacedTypes{nullptr}; + DenseHashSet replacedTypePacks{nullptr}; std::optional substitute(TypeId ty); std::optional substitute(TypePackId tp); diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 829b5416..a255be4d 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -65,28 +65,6 @@ struct Anyification : Substitution } }; -// A substitution which replaces the type parameters of a type function by arguments -struct ApplyTypeFunction : Substitution -{ - ApplyTypeFunction(TypeArena* arena, TypeLevel level) - : Substitution(TxnLog::empty(), arena) - , level(level) - , encounteredForwardedType(false) - { - } - - TypeLevel level; - bool encounteredForwardedType; - std::unordered_map typeArguments; - std::unordered_map typePackArguments; - bool ignoreChildren(TypeId ty) override; - bool ignoreChildren(TypePackId tp) override; - bool isDirty(TypeId ty) override; - bool isDirty(TypePackId tp) override; - TypeId clean(TypeId ty) override; - TypePackId clean(TypePackId tp) override; -}; - struct GenericTypeDefinitions { std::vector genericTypes; diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index fb6093df..6a13b11c 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -24,7 +24,7 @@ namespace Luau { struct TypeArena; -struct Scope2; +struct Scope; /** * There are three kinds of type variables: @@ -143,7 +143,7 @@ struct ConstrainedTypeVar std::vector parts; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; }; // Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md @@ -223,12 +223,16 @@ struct GenericTypeDefinition { TypeId ty; std::optional defaultValue; + + bool operator==(const GenericTypeDefinition& rhs) const; }; struct GenericTypePackDefinition { TypePackId tp; std::optional defaultValue; + + bool operator==(const GenericTypePackDefinition& rhs) const; }; struct FunctionArgument @@ -275,7 +279,7 @@ struct FunctionTypeVar std::optional defn = {}, bool hasSelf = false); TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; /// These should all be generic std::vector generics; std::vector genericPacks; @@ -344,7 +348,7 @@ struct TableTypeVar TableState state = TableState::Unsealed; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; std::optional name; // Sometimes we throw a type on a name to make for nicer error messages, but without creating any entry in the type namespace @@ -426,6 +430,12 @@ struct TypeFun TypeId type; TypeFun() = default; + + explicit TypeFun(TypeId ty) + : type(ty) + { + } + TypeFun(std::vector typeParams, TypeId type) : typeParams(std::move(typeParams)) , type(type) @@ -438,6 +448,27 @@ struct TypeFun , type(type) { } + + bool operator==(const TypeFun& rhs) const; +}; + +/** Represents a pending type alias instantiation. + * + * In order to afford (co)recursive type aliases, we need to reason about a + * partially-complete instantiation. This requires encoding more information in + * a type variable than a BlockedTypeVar affords, hence this. Each + * PendingExpansionTypeVar has a corresponding TypeAliasExpansionConstraint + * enqueued in the solver to convert it to an actual instantiated type + */ +struct PendingExpansionTypeVar +{ + PendingExpansionTypeVar(TypeFun fn, std::vector typeArguments, std::vector packArguments); + TypeFun fn; + std::vector typeArguments; + std::vector packArguments; + size_t index; + + static size_t nextIndex; }; // Anything! All static checking is off. @@ -470,8 +501,10 @@ struct NeverTypeVar using ErrorTypeVar = Unifiable::Error; -using TypeVariant = Unifiable::Variant; +using TypeVariant = + Unifiable::Variant; + struct TypeVar final { diff --git a/Analysis/include/Luau/Unifiable.h b/Analysis/include/Luau/Unifiable.h index 4ff91714..e5eb4198 100644 --- a/Analysis/include/Luau/Unifiable.h +++ b/Analysis/include/Luau/Unifiable.h @@ -8,7 +8,7 @@ namespace Luau { -struct Scope2; +struct Scope; /** * The 'level' of a TypeVar is an indirect way to talk about the scope that it 'belongs' too. @@ -84,11 +84,11 @@ using Name = std::string; struct Free { explicit Free(TypeLevel level); - explicit Free(Scope2* scope); + explicit Free(Scope* scope); int index; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; // True if this free type variable is part of a mutually // recursive type alias whose definitions haven't been // resolved yet. @@ -115,13 +115,13 @@ struct Generic Generic(); explicit Generic(TypeLevel level); explicit Generic(const Name& name); - explicit Generic(Scope2* scope); + explicit Generic(Scope* scope); Generic(TypeLevel level, const Name& name); - Generic(Scope2* scope, const Name& name); + Generic(Scope* scope, const Name& name); int index; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; Name name; bool explicitName = false; diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index 7229dafc..7e5d71d6 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -9,7 +9,6 @@ #include "Luau/TypeVar.h" LUAU_FASTINT(LuauVisitRecursionLimit) -LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAG(LuauCompleteVisitor); namespace Luau @@ -150,6 +149,10 @@ struct GenericTypeVarVisitor { return visit(ty); } + virtual bool visit(TypeId ty, const PendingExpansionTypeVar& petv) + { + return visit(ty); + } virtual bool visit(TypeId ty, const SingletonTypeVar& stv) { return visit(ty); @@ -285,8 +288,6 @@ struct GenericTypeVarVisitor traverse(partTy); } } - else if (!FFlag::LuauCompleteVisitor) - return visit_detail::unsee(seen, ty); else if (get(ty)) { // Visiting into LazyTypeVar may necessarily cause infinite expansion, so we don't do that on purpose. @@ -301,6 +302,37 @@ struct GenericTypeVarVisitor visit(ty, *utv); else if (auto ntv = get(ty)) visit(ty, *ntv); + else if (auto petv = get(ty)) + { + if (visit(ty, *petv)) + { + traverse(petv->fn.type); + + for (const GenericTypeDefinition& p : petv->fn.typeParams) + { + traverse(p.ty); + + if (p.defaultValue) + traverse(*p.defaultValue); + } + + for (const GenericTypePackDefinition& p : petv->fn.typePackParams) + { + traverse(p.tp); + + if (p.defaultValue) + traverse(*p.defaultValue); + } + + for (TypeId a : petv->typeArguments) + traverse(a); + + for (TypePackId a : petv->packArguments) + traverse(a); + } + } + else if (!FFlag::LuauCompleteVisitor) + return visit_detail::unsee(seen, ty); else LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypeId) is not exhaustive!"); @@ -333,7 +365,7 @@ struct GenericTypeVarVisitor else if (auto pack = get(tp)) { bool res = visit(tp, *pack); - if (!FFlag::LuauNormalizeFlagIsConservative || res) + if (res) { for (TypeId ty : pack->head) traverse(ty); @@ -345,7 +377,7 @@ struct GenericTypeVarVisitor else if (auto pack = get(tp)) { bool res = visit(tp, *pack); - if (!FFlag::LuauNormalizeFlagIsConservative || res) + if (res) traverse(pack->ty); } else diff --git a/Analysis/src/ApplyTypeFunction.cpp b/Analysis/src/ApplyTypeFunction.cpp new file mode 100644 index 00000000..c6ac3e19 --- /dev/null +++ b/Analysis/src/ApplyTypeFunction.cpp @@ -0,0 +1,60 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/ApplyTypeFunction.h" + +namespace Luau +{ + +bool ApplyTypeFunction::isDirty(TypeId ty) +{ + if (typeArguments.count(ty)) + return true; + else if (const FreeTypeVar* ftv = get(ty)) + { + if (ftv->forwardedTypeAlias) + encounteredForwardedType = true; + return false; + } + else + return false; +} + +bool ApplyTypeFunction::isDirty(TypePackId tp) +{ + if (typePackArguments.count(tp)) + return true; + else + return false; +} + +bool ApplyTypeFunction::ignoreChildren(TypeId ty) +{ + if (get(ty)) + return true; + else + return false; +} + +bool ApplyTypeFunction::ignoreChildren(TypePackId tp) +{ + if (get(tp)) + return true; + else + return false; +} + +TypeId ApplyTypeFunction::clean(TypeId ty) +{ + TypeId& arg = typeArguments[ty]; + LUAU_ASSERT(arg); + return arg; +} + +TypePackId ApplyTypeFunction::clean(TypePackId tp) +{ + TypePackId& arg = typePackArguments[tp]; + LUAU_ASSERT(arg); + return arg; +} + +} // namespace Luau diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp similarity index 99% rename from Analysis/src/JsonEncoder.cpp rename to Analysis/src/AstJsonEncoder.cpp index 3a5e35d8..8d589037 100644 --- a/Analysis/src/JsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/JsonEncoder.h" +#include "Luau/AstJsonEncoder.h" #include "Luau/Ast.h" #include "Luau/ParseResult.h" @@ -103,8 +103,8 @@ struct AstJsonEncoder : public AstVisitor void write(double d) { - char b[256]; - sprintf(b, "%.17g", d); + char b[32]; + snprintf(b, sizeof(b), "%.17g", d); writeRaw(b); } diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 1124c29e..50299704 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -314,7 +314,7 @@ std::optional findBindingAtPosition(const Module& module, const SourceM auto iter = currentScope->bindings.find(name); if (iter != currentScope->bindings.end() && iter->second.location.begin <= pos) { - /* Ignore this binding if we're inside its definition. e.g. local abc = abc -- Will take the definition of abc from outer scope */ + // Ignore this binding if we're inside its definition. e.g. local abc = abc -- Will take the definition of abc from outer scope std::optional bindingStatement = findBindingLocalStatement(source, iter->second); if (!bindingStatement || !(*bindingStatement)->location.contains(pos)) return iter->second; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 513a791c..a57a789f 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -12,7 +12,7 @@ #include #include -LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2) +LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3) static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -149,7 +149,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ ty = follow(ty); auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); InternalErrorReporter iceReporter; UnifierSharedState unifierState(&iceReporter); @@ -168,7 +168,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ TypeId expectedType = follow(*typeAtPosition); auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) { - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) { if (std::optional firstRetTy = first(ftv->retTypes)) return checkTypeMatch(typeArena, *firstRetTy, expectedType); @@ -209,7 +209,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ } } - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; else return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; @@ -226,7 +226,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId const std::vector& nodes, AutocompleteEntryMap& result, std::unordered_set& seen, std::optional containingClass = std::nullopt) { - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) rootTy = follow(rootTy); ty = follow(ty); @@ -236,7 +236,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId seen.insert(ty); auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get(ty)](Luau::TypeId type) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); if (indexType == PropIndexType::Key) return false; @@ -269,7 +269,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId } }; auto isWrongIndexer = [typeArena, rootTy, indexType](Luau::TypeId type) { - LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3); if (indexType == PropIndexType::Key) return false; @@ -277,21 +277,20 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId bool calledWithSelf = indexType == PropIndexType::Colon; auto isCompatibleCall = [typeArena, rootTy, calledWithSelf](const FunctionTypeVar* ftv) { - if (get(rootTy)) - { - // Calls on classes require strict match between how function is declared and how it's called - return calledWithSelf == ftv->hasSelf; - } + // Strong match with definition is a success + if (calledWithSelf == ftv->hasSelf) + return true; - // If a call is made with ':', it is invalid if a function has incompatible first argument or no arguments at all - // If a call is made with '.', but it was declared with 'self', it is considered invalid if first argument is compatible - if (calledWithSelf || ftv->hasSelf) + // Calls on classes require strict match between how function is declared and how it's called + if (get(rootTy)) + return false; + + // When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all + // When called with '.', but declared with 'self', it is considered invalid if first argument is compatible + if (std::optional firstArgTy = first(ftv->argTypes)) { - if (std::optional firstArgTy = first(ftv->argTypes)) - { - if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) - return calledWithSelf; - } + if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) + return calledWithSelf; } return !calledWithSelf; @@ -333,7 +332,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryKind::Property, type, prop.deprecated, - FFlag::LuauSelfCallAutocompleteFix2 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), + FFlag::LuauSelfCallAutocompleteFix3 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), typeCorrect, containingClass, &prop, @@ -376,7 +375,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId { autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen); - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) { if (auto mtable = get(mt->metatable)) fillMetatableProps(mtable); @@ -442,7 +441,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryMap inner; std::unordered_set innerSeen; - if (!FFlag::LuauSelfCallAutocompleteFix2) + if (!FFlag::LuauSelfCallAutocompleteFix3) innerSeen = seen; if (isNil(*iter)) @@ -468,7 +467,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId ++iter; } } - else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix2) + else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix3) { if (pt->metatable) { @@ -476,7 +475,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId fillMetatableProps(mtable); } } - else if (FFlag::LuauSelfCallAutocompleteFix2 && get(get(ty))) + else if (FFlag::LuauSelfCallAutocompleteFix3 && get(get(ty))) { autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen); } @@ -1405,7 +1404,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M TypeId ty = follow(*it); PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point; - if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty)) + if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty)) return { autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry}; else diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index aeba2c13..826179b3 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -10,6 +10,7 @@ LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false) LUAU_FASTFLAG(LuauUnknownAndNeverType) +LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false) /** FIXME: Many of these type definitions are not quite completely accurate. * @@ -349,7 +350,7 @@ static std::optional> magicFunctionSetMetaTable( if (tableName == metatableName) mtv.syntheticName = tableName; - else + else if (!FFlag::LuauBuiltInMetatableNoBadSynthetic) mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }"; } diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 88c50318..51ad61d5 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -48,6 +48,7 @@ struct TypeCloner void operator()(const Unifiable::Bound& t); void operator()(const Unifiable::Error& t); void operator()(const BlockedTypeVar& t); + void operator()(const PendingExpansionTypeVar& t); void operator()(const PrimitiveTypeVar& t); void operator()(const ConstrainedTypeVar& t); void operator()(const SingletonTypeVar& t); @@ -166,6 +167,52 @@ void TypeCloner::operator()(const BlockedTypeVar& t) defaultClone(t); } +void TypeCloner::operator()(const PendingExpansionTypeVar& t) +{ + TypeId res = dest.addType(PendingExpansionTypeVar{t.fn, t.typeArguments, t.packArguments}); + PendingExpansionTypeVar* petv = getMutable(res); + LUAU_ASSERT(petv); + + seenTypes[typeId] = res; + + std::vector typeArguments; + for (TypeId arg : t.typeArguments) + typeArguments.push_back(clone(arg, dest, cloneState)); + + std::vector packArguments; + for (TypePackId arg : t.packArguments) + packArguments.push_back(clone(arg, dest, cloneState)); + + TypeFun fn; + fn.type = clone(t.fn.type, dest, cloneState); + + for (const GenericTypeDefinition& param : t.fn.typeParams) + { + TypeId ty = clone(param.ty, dest, cloneState); + std::optional defaultValue = param.defaultValue; + + if (defaultValue) + defaultValue = clone(*defaultValue, dest, cloneState); + + fn.typeParams.push_back(GenericTypeDefinition{ty, defaultValue}); + } + + for (const GenericTypePackDefinition& param : t.fn.typePackParams) + { + TypePackId tp = clone(param.tp, dest, cloneState); + std::optional defaultValue = param.defaultValue; + + if (defaultValue) + defaultValue = clone(*defaultValue, dest, cloneState); + + fn.typePackParams.push_back(GenericTypePackDefinition{tp, defaultValue}); + } + + petv->fn = std::move(fn); + petv->typeArguments = std::move(typeArguments); + petv->packArguments = std::move(packArguments); +} + void TypeCloner::operator()(const PrimitiveTypeVar& t) { defaultClone(t); @@ -452,6 +499,11 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) ConstrainedTypeVar clone{ctv->level, ctv->parts}; result = dest.addType(std::move(clone)); } + else if (const PendingExpansionTypeVar* petv = get(ty)) + { + PendingExpansionTypeVar clone{petv->fn, petv->typeArguments, petv->packArguments}; + result = dest.addType(std::move(clone)); + } else return result; diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 38895ceb..ea7037bf 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -14,7 +14,7 @@ namespace Luau const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp ConstraintGraphBuilder::ConstraintGraphBuilder( - const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope) + const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope) : moduleName(moduleName) , singletonTypes(getSingletonTypes()) , arena(arena) @@ -25,36 +25,34 @@ ConstraintGraphBuilder::ConstraintGraphBuilder( LUAU_ASSERT(arena); } -TypeId ConstraintGraphBuilder::freshType(NotNull scope) +TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope) { - return arena->addType(FreeTypeVar{scope}); + return arena->addType(FreeTypeVar{scope.get()}); } -TypePackId ConstraintGraphBuilder::freshTypePack(NotNull scope) +TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope) { - FreeTypePack f{scope}; + FreeTypePack f{scope.get()}; return arena->addTypePack(TypePackVar{std::move(f)}); } -NotNull ConstraintGraphBuilder::childScope(Location location, NotNull parent) +ScopePtr ConstraintGraphBuilder::childScope(Location location, const ScopePtr& parent) { - auto scope = std::make_unique(); - NotNull borrow = NotNull(scope.get()); - scopes.emplace_back(location, std::move(scope)); + auto scope = std::make_shared(parent); + scopes.emplace_back(location, scope); - borrow->parent = parent; - borrow->returnType = parent->returnType; - parent->children.push_back(borrow); + scope->returnType = parent->returnType; + parent->children.push_back(NotNull(scope.get())); - return borrow; + return scope; } -void ConstraintGraphBuilder::addConstraint(NotNull scope, ConstraintV cv) +void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv) { scope->constraints.emplace_back(new Constraint{std::move(cv)}); } -void ConstraintGraphBuilder::addConstraint(NotNull scope, std::unique_ptr c) +void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr c) { scope->constraints.emplace_back(std::move(c)); } @@ -63,25 +61,25 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block) { LUAU_ASSERT(scopes.empty()); LUAU_ASSERT(rootScope == nullptr); - scopes.emplace_back(block->location, std::make_unique()); - rootScope = scopes.back().second.get(); - NotNull borrow = NotNull(rootScope); + ScopePtr scope = std::make_shared(singletonTypes.anyTypePack); + rootScope = scope.get(); + scopes.emplace_back(block->location, scope); - rootScope->returnType = freshTypePack(borrow); + rootScope->returnType = freshTypePack(scope); - prepopulateGlobalScope(borrow, block); + prepopulateGlobalScope(scope, block); // TODO: We should share the global scope. - rootScope->typeBindings["nil"] = singletonTypes.nilType; - rootScope->typeBindings["number"] = singletonTypes.numberType; - rootScope->typeBindings["string"] = singletonTypes.stringType; - rootScope->typeBindings["boolean"] = singletonTypes.booleanType; - rootScope->typeBindings["thread"] = singletonTypes.threadType; + rootScope->typeBindings["nil"] = TypeFun{singletonTypes.nilType}; + rootScope->typeBindings["number"] = TypeFun{singletonTypes.numberType}; + rootScope->typeBindings["string"] = TypeFun{singletonTypes.stringType}; + rootScope->typeBindings["boolean"] = TypeFun{singletonTypes.booleanType}; + rootScope->typeBindings["thread"] = TypeFun{singletonTypes.threadType}; - visitBlockWithoutChildScope(borrow, block); + visitBlockWithoutChildScope(scope, block); } -void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull scope, AstStatBlock* block) +void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block) { RecursionCounter counter{&recursionCount}; @@ -91,11 +89,58 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull scope, return; } + std::unordered_map aliasDefinitionLocations; + + // In order to enable mutually-recursive type aliases, we need to + // populate the type bindings before we actually check any of the + // alias statements. Since we're not ready to actually resolve + // any of the annotations, we just use a fresh type for now. + for (AstStat* stat : block->body) + { + if (auto alias = stat->as()) + { + if (scope->typeBindings.count(alias->name.value) != 0) + { + auto it = aliasDefinitionLocations.find(alias->name.value); + LUAU_ASSERT(it != aliasDefinitionLocations.end()); + reportError(alias->location, DuplicateTypeDefinition{alias->name.value, it->second}); + continue; + } + + bool hasGenerics = alias->generics.size > 0 || alias->genericPacks.size > 0; + + ScopePtr defnScope = scope; + if (hasGenerics) + { + defnScope = childScope(alias->location, scope); + } + + TypeId initialType = freshType(scope); + TypeFun initialFun = TypeFun{initialType}; + + for (const auto& [name, gen] : createGenerics(defnScope, alias->generics)) + { + initialFun.typeParams.push_back(gen); + defnScope->typeBindings[name] = TypeFun{gen.ty}; + } + + for (const auto& [name, genPack] : createGenericPacks(defnScope, alias->genericPacks)) + { + initialFun.typePackParams.push_back(genPack); + defnScope->typePackBindings[name] = genPack.tp; + } + + scope->typeBindings[alias->name.value] = std::move(initialFun); + astTypeAliasDefiningScopes[alias] = defnScope; + aliasDefinitionLocations[alias->name.value] = alias->location; + } + } + for (AstStat* stat : block->body) visit(scope, stat); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStat* stat) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat) { RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit}; @@ -119,26 +164,34 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStat* stat) visit(scope, i); else if (auto a = stat->as()) visit(scope, a); + else if (auto s = stat->as()) + visit(scope, s); + else if (auto s = stat->as()) + visit(scope, s); + else if (auto s = stat->as()) + visit(scope, s); else LUAU_ASSERT(0); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocal* local) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) { std::vector varTypes; for (AstLocal* local : local->vars) { TypeId ty = freshType(scope); + Location location = local->location; if (local->annotation) { - TypeId annotation = resolveType(scope, local->annotation); + location = local->annotation->location; + TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true); addConstraint(scope, SubtypeConstraint{ty, annotation}); } varTypes.push_back(ty); - scope->bindings[local] = ty; + scope->bindings[local] = Binding{ty, location}; } for (size_t i = 0; i < local->values.size; ++i) @@ -169,13 +222,12 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocal* local) } } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatFor* for_) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) { - auto checkNumber = [&](AstExpr* expr) - { + auto checkNumber = [&](AstExpr* expr) { if (!expr) return; - + TypeId t = check(scope, expr); addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType}); }; @@ -184,24 +236,24 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFor* for_) checkNumber(for_->to); checkNumber(for_->step); - NotNull forScope = childScope(for_->location, scope); - forScope->bindings[for_->var] = singletonTypes.numberType; + ScopePtr forScope = childScope(for_->location, scope); + forScope->bindings[for_->var] = Binding{singletonTypes.numberType, for_->var->location}; visit(forScope, for_->body); } -void addConstraints(Constraint* constraint, NotNull scope) +void addConstraints(Constraint* constraint, NotNull scope) { scope->constraints.reserve(scope->constraints.size() + scope->constraints.size()); for (const auto& c : scope->constraints) constraint->dependencies.push_back(NotNull{c.get()}); - for (NotNull childScope : scope->children) + for (NotNull childScope : scope->children) addConstraints(constraint, childScope); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocalFunction* function) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function) { // Local // Global @@ -213,21 +265,21 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocalFunction* LUAU_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name. functionType = arena->addType(BlockedTypeVar{}); - scope->bindings[function->name] = functionType; + scope->bindings[function->name] = Binding{functionType, function->name->location}; FunctionSignature sig = checkFunctionSignature(scope, function->func); - sig.bodyScope->bindings[function->name] = sig.signature; + sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location}; checkFunctionBody(sig.bodyScope, function->func); std::unique_ptr c{ - new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}}; - addConstraints(c.get(), sig.bodyScope); + new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; + addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* function) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* function) { // Name could be AstStatLocal, AstStatGlobal, AstStatIndexName. // With or without self @@ -247,9 +299,9 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct else { functionType = arena->addType(BlockedTypeVar{}); - scope->bindings[localName->local] = functionType; + scope->bindings[localName->local] = Binding{functionType, localName->location}; } - sig.bodyScope->bindings[localName->local] = sig.signature; + sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; } else if (AstExprGlobal* globalName = function->name->as()) { @@ -262,9 +314,9 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct else { functionType = arena->addType(BlockedTypeVar{}); - rootScope->bindings[globalName->name] = functionType; + rootScope->bindings[globalName->name] = Binding{functionType, globalName->location}; } - sig.bodyScope->bindings[globalName->name] = sig.signature; + sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; } else if (AstExprIndexName* indexName = function->name->as()) { @@ -291,39 +343,26 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct checkFunctionBody(sig.bodyScope, function->func); std::unique_ptr c{ - new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}}; - addConstraints(c.get(), sig.bodyScope); + new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; + addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatReturn* ret) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret) { TypePackId exprTypes = checkPack(scope, ret->list); addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatBlock* block) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) { - NotNull innerScope = childScope(block->location, scope); - - // In order to enable mutually-recursive type aliases, we need to - // populate the type bindings before we actually check any of the - // alias statements. Since we're not ready to actually resolve - // any of the annotations, we just use a fresh type for now. - for (AstStat* stat : block->body) - { - if (auto alias = stat->as()) - { - TypeId initialType = freshType(scope); - scope->typeBindings[alias->name.value] = initialType; - } - } + ScopePtr innerScope = childScope(block->location, scope); visitBlockWithoutChildScope(innerScope, block); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatAssign* assign) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign) { TypePackId varPackId = checkExprList(scope, assign->vars); TypePackId valuePack = checkPack(scope, assign->values); @@ -331,47 +370,66 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatAssign* assign) addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatIf* ifStatement) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement) { check(scope, ifStatement->condition); - NotNull thenScope = childScope(ifStatement->thenbody->location, scope); + ScopePtr thenScope = childScope(ifStatement->thenbody->location, scope); visit(thenScope, ifStatement->thenbody); if (ifStatement->elsebody) { - NotNull elseScope = childScope(ifStatement->elsebody->location, scope); + ScopePtr elseScope = childScope(ifStatement->elsebody->location, scope); visit(elseScope, ifStatement->elsebody); } } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatTypeAlias* alias) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) { // TODO: Exported type aliases - // TODO: Generic type aliases - auto it = scope->typeBindings.find(alias->name.value); - // This should always be here since we do a separate pass over the - // AST to set up typeBindings. If it's not, we've somehow skipped - // this alias in that first pass. - LUAU_ASSERT(it != scope->typeBindings.end()); - if (it == scope->typeBindings.end()) + auto bindingIt = scope->typeBindings.find(alias->name.value); + ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias); + // These will be undefined if the alias was a duplicate definition, in which + // case we just skip over it. + if (bindingIt == scope->typeBindings.end() || defnIt == nullptr) { - ice->ice("Type alias does not have a pre-populated binding", alias->location); + return; } - TypeId ty = resolveType(scope, alias->type); + ScopePtr resolvingScope = *defnIt; + TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true); + + LUAU_ASSERT(get(bindingIt->second.type)); // Rather than using a subtype constraint, we instead directly bind // the free type we generated in the first pass to the resolved type. // This prevents a case where you could cause another constraint to // bind the free alias type to an unrelated type, causing havoc. - asMutable(it->second)->ty.emplace(ty); + asMutable(bindingIt->second.type)->ty.emplace(ty); addConstraint(scope, NameConstraint{ty, alias->name.value}); } -TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstArray exprs) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) +{ + LUAU_ASSERT(global->type); + + TypeId globalTy = resolveType(scope, global->type); + scope->bindings[global->name] = Binding{globalTy, global->location}; +} + +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* global) +{ + LUAU_ASSERT(false); // TODO: implement +} + +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction* global) +{ + LUAU_ASSERT(false); // TODO: implement +} + +TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray exprs) { if (exprs.size == 0) return arena->addTypePack({}); @@ -392,7 +450,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstArrayaddTypePack(TypePack{std::move(types), last}); } -TypePackId ConstraintGraphBuilder::checkExprList(NotNull scope, const AstArray& exprs) +TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray& exprs) { TypePackId result = arena->addTypePack({}); TypePack* resultPack = getMutable(result); @@ -413,7 +471,7 @@ TypePackId ConstraintGraphBuilder::checkExprList(NotNull scope, const As return result; } -TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstExpr* expr) +TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr) { RecursionCounter counter{&recursionCount}; @@ -468,7 +526,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstExpr* exp return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExpr* expr) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr) { RecursionCounter counter{&recursionCount}; @@ -548,7 +606,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExpr* expr) return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexName* indexName) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName) { TypeId obj = check(scope, indexName->expr); TypeId result = freshType(scope); @@ -564,7 +622,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexName* in return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexExpr* indexExpr) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr) { TypeId obj = check(scope, indexExpr->expr); TypeId indexType = check(scope, indexExpr->index); @@ -579,7 +637,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexExpr* in return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprUnary* unary) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary) { TypeId operandType = check(scope, unary->expr); @@ -599,7 +657,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprUnary* unary) return singletonTypes.errorRecoveryType(); } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprBinary* binary) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary) { TypeId leftType = check(scope, binary->left); TypeId rightType = check(scope, binary->right); @@ -624,7 +682,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprBinary* binar return nullptr; } -TypeId ConstraintGraphBuilder::checkExprTable(NotNull scope, AstExprTable* expr) +TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTable* expr) { TypeId ty = arena->addType(TableTypeVar{}); TableTypeVar* ttv = getMutable(ty); @@ -674,10 +732,10 @@ TypeId ConstraintGraphBuilder::checkExprTable(NotNull scope, AstExprTabl return ty; } -ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(NotNull parent, AstExprFunction* fn) +ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn) { - Scope2* signatureScope = nullptr; - Scope2* bodyScope = nullptr; + ScopePtr signatureScope = nullptr; + ScopePtr bodyScope = nullptr; TypePackId returnType = nullptr; std::vector genericTypes; @@ -690,25 +748,24 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS // generics properly. if (hasGenerics) { - NotNull signatureBorrow = childScope(fn->location, parent); - signatureScope = signatureBorrow.get(); + signatureScope = childScope(fn->location, parent); // We need to assign returnType before creating bodyScope so that the // return type gets propogated to bodyScope. - returnType = freshTypePack(signatureBorrow); + returnType = freshTypePack(signatureScope); signatureScope->returnType = returnType; - bodyScope = childScope(fn->body->location, signatureBorrow).get(); + bodyScope = childScope(fn->body->location, signatureScope); - std::vector> genericDefinitions = createGenerics(signatureBorrow, fn->generics); - std::vector> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks); + std::vector> genericDefinitions = createGenerics(signatureScope, fn->generics); + std::vector> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); // We do not support default values on function generics, so we only // care about the types involved. for (const auto& [name, g] : genericDefinitions) { genericTypes.push_back(g.ty); - signatureScope->typeBindings[name] = g.ty; + signatureScope->typeBindings[name] = TypeFun{g.ty}; } for (const auto& [name, g] : genericPackDefinitions) @@ -719,11 +776,10 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS } else { - NotNull bodyBorrow = childScope(fn->body->location, parent); - bodyScope = bodyBorrow.get(); + bodyScope = childScope(fn->body->location, parent); - returnType = freshTypePack(bodyBorrow); - bodyBorrow->returnType = returnType; + returnType = freshTypePack(bodyScope); + bodyScope->returnType = returnType; // To eliminate the need to branch on hasGenerics below, we say that the // signature scope is the body scope when there is no real signature @@ -731,27 +787,24 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS signatureScope = bodyScope; } - NotNull bodyBorrow = NotNull(bodyScope); - NotNull signatureBorrow = NotNull(signatureScope); - if (fn->returnAnnotation) { - TypePackId annotatedRetType = resolveTypePack(signatureBorrow, *fn->returnAnnotation); - addConstraint(signatureBorrow, PackSubtypeConstraint{returnType, annotatedRetType}); + TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); + addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType}); } std::vector argTypes; for (AstLocal* local : fn->args) { - TypeId t = freshType(signatureBorrow); + TypeId t = freshType(signatureScope); argTypes.push_back(t); - signatureScope->bindings[local] = t; + signatureScope->bindings[local] = Binding{t, local->location}; if (local->annotation) { - TypeId argAnnotation = resolveType(signatureBorrow, local->annotation); - addConstraint(signatureBorrow, SubtypeConstraint{t, argAnnotation}); + TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true); + addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation}); } } @@ -772,11 +825,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS // Undo the workaround we made above: if there's no signature scope, // don't report it. /* signatureScope */ hasGenerics ? signatureScope : nullptr, - /* bodyScope */ bodyBorrow, + /* bodyScope */ bodyScope, }; } -void ConstraintGraphBuilder::checkFunctionBody(NotNull scope, AstExprFunction* fn) +void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn) { visitBlockWithoutChildScope(scope, fn->body); @@ -789,20 +842,65 @@ void ConstraintGraphBuilder::checkFunctionBody(NotNull scope, AstExprFun } } -TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) +TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool topLevel) { TypeId result = nullptr; if (auto ref = ty->as()) { // TODO: Support imported types w/ require tracing. - // TODO: Support generic type references. LUAU_ASSERT(!ref->prefix); - LUAU_ASSERT(!ref->hasParameterList); - // TODO: If it doesn't exist, should we introduce a free binding? - // This is probably important for handling type aliases. - result = scope->lookupTypeBinding(ref->name.value).value_or(singletonTypes.errorRecoveryType()); + std::optional alias = scope->lookupTypeBinding(ref->name.value); + + if (alias.has_value()) + { + // If the alias is not generic, we don't need to set up a blocked + // type and an instantiation constraint. + if (alias->typeParams.empty() && alias->typePackParams.empty()) + { + result = alias->type; + } + else + { + std::vector parameters; + std::vector packParameters; + + for (const AstTypeOrPack& p : ref->parameters) + { + // We do not enforce the ordering of types vs. type packs here; + // that is done in the parser. + if (p.type) + { + parameters.push_back(resolveType(scope, p.type)); + } + else if (p.typePack) + { + packParameters.push_back(resolveTypePack(scope, p.typePack)); + } + else + { + // This indicates a parser bug: one of these two pointers + // should be set. + LUAU_ASSERT(false); + } + } + + result = arena->addType(PendingExpansionTypeVar{*alias, parameters, packParameters}); + + if (topLevel) + { + addConstraint(scope, TypeAliasExpansionConstraint{ + /* target */ result, + }); + } + } + } + else + { + reportError(ty->location, UnknownSymbol{ref->name.value, UnknownSymbol::Context::Type}); + result = singletonTypes.errorRecoveryType(); + } } else if (auto tab = ty->as()) { @@ -834,7 +932,7 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) { // TODO: Recursion limit. bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0; - Scope2* signatureScope = nullptr; + ScopePtr signatureScope = nullptr; std::vector genericTypes; std::vector genericTypePacks; @@ -843,22 +941,21 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) // for the generic bindings to live on. if (hasGenerics) { - NotNull signatureBorrow = childScope(fn->location, scope); - signatureScope = signatureBorrow.get(); + signatureScope = childScope(fn->location, scope); - std::vector> genericDefinitions = createGenerics(signatureBorrow, fn->generics); - std::vector> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks); + std::vector> genericDefinitions = createGenerics(signatureScope, fn->generics); + std::vector> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); for (const auto& [name, g] : genericDefinitions) { genericTypes.push_back(g.ty); - signatureBorrow->typeBindings[name] = g.ty; + signatureScope->typeBindings[name] = TypeFun{g.ty}; } for (const auto& [name, g] : genericPackDefinitions) { genericTypePacks.push_back(g.tp); - signatureBorrow->typePackBindings[name] = g.tp; + signatureScope->typePackBindings[name] = g.tp; } } else @@ -866,13 +963,11 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) // To eliminate the need to branch on hasGenerics below, we say that // the signature scope is the parent scope if we don't have // generics. - signatureScope = scope.get(); + signatureScope = scope; } - NotNull signatureBorrow(signatureScope); - - TypePackId argTypes = resolveTypePack(signatureBorrow, fn->argTypes); - TypePackId returnTypes = resolveTypePack(signatureBorrow, fn->returnTypes); + TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes); + TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes); // TODO: FunctionTypeVar needs a pointer to the scope so that we know // how to quantify/instantiate it. @@ -950,7 +1045,7 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTypePack* tp) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp) { TypePackId result; if (auto expl = tp->as()) @@ -964,7 +1059,15 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTyp } else if (auto gen = tp->as()) { - result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}}); + if (std::optional lookup = scope->lookupTypePackBinding(gen->genericName.value)) + { + result = *lookup; + } + else + { + reportError(tp->location, UnknownSymbol{gen->genericName.value, UnknownSymbol::Context::Type}); + result = singletonTypes.errorRecoveryTypePack(); + } } else { @@ -976,7 +1079,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTyp return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, const AstTypeList& list) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list) { std::vector head; @@ -994,12 +1097,12 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, const return arena->addTypePack(TypePack{head, tail}); } -std::vector> ConstraintGraphBuilder::createGenerics(NotNull scope, AstArray generics) +std::vector> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray generics) { std::vector> result; for (const auto& generic : generics) { - TypeId genericTy = arena->addType(GenericTypeVar{scope, generic.name.value}); + TypeId genericTy = arena->addType(GenericTypeVar{scope.get(), generic.name.value}); std::optional defaultTy = std::nullopt; if (generic.defaultValue) @@ -1015,12 +1118,12 @@ std::vector> ConstraintGraphBuilder::crea } std::vector> ConstraintGraphBuilder::createGenericPacks( - NotNull scope, AstArray generics) + const ScopePtr& scope, AstArray generics) { std::vector> result; for (const auto& generic : generics) { - TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope, generic.name.value}}); + TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}}); std::optional defaultTy = std::nullopt; if (generic.defaultValue) @@ -1035,7 +1138,7 @@ std::vector> ConstraintGraphBuilder:: return result; } -TypeId ConstraintGraphBuilder::flattenPack(NotNull scope, Location location, TypePackId tp) +TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, TypePackId tp) { if (auto f = first(tp)) return *f; @@ -1061,10 +1164,10 @@ void ConstraintGraphBuilder::reportCodeTooComplex(Location location) struct GlobalPrepopulator : AstVisitor { - const NotNull globalScope; + const NotNull globalScope; const NotNull arena; - GlobalPrepopulator(NotNull globalScope, NotNull arena) + GlobalPrepopulator(NotNull globalScope, NotNull arena) : globalScope(globalScope) , arena(arena) { @@ -1073,29 +1176,29 @@ struct GlobalPrepopulator : AstVisitor bool visit(AstStatFunction* function) override { if (AstExprGlobal* g = function->name->as()) - globalScope->bindings[g->name] = arena->addType(BlockedTypeVar{}); + globalScope->bindings[g->name] = Binding{arena->addType(BlockedTypeVar{})}; return true; } }; -void ConstraintGraphBuilder::prepopulateGlobalScope(NotNull globalScope, AstStatBlock* program) +void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) { - GlobalPrepopulator gp{NotNull{globalScope}, arena}; + GlobalPrepopulator gp{NotNull{globalScope.get()}, arena}; program->visit(&gp); } -void collectConstraints(std::vector>& result, NotNull scope) +void collectConstraints(std::vector>& result, NotNull scope) { for (const auto& c : scope->constraints) result.push_back(NotNull{c.get()}); - for (NotNull child : scope->children) + for (NotNull child : scope->children) collectConstraints(result, child); } -std::vector> collectConstraints(NotNull rootScope) +std::vector> collectConstraints(NotNull rootScope) { std::vector> result; collectConstraints(result, rootScope); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 077a4e28..0898f9aa 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1,11 +1,13 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/ApplyTypeFunction.h" #include "Luau/ConstraintSolver.h" #include "Luau/Instantiation.h" #include "Luau/Location.h" #include "Luau/Quantify.h" #include "Luau/ToString.h" #include "Luau/Unifier.h" +#include "Luau/VisitTypeVar.h" LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); @@ -13,31 +15,195 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); namespace Luau { -[[maybe_unused]] static void dumpBindings(NotNull scope, ToStringOptions& opts) +[[maybe_unused]] static void dumpBindings(NotNull scope, ToStringOptions& opts) { for (const auto& [k, v] : scope->bindings) { - auto d = toStringDetailed(v, opts); + auto d = toStringDetailed(v.typeId, opts); opts.nameMap = d.nameMap; printf("\t%s : %s\n", k.c_str(), d.name.c_str()); } - for (NotNull child : scope->children) + for (NotNull child : scope->children) dumpBindings(child, opts); } -static void dumpConstraints(NotNull scope, ToStringOptions& opts) +static void dumpConstraints(NotNull scope, ToStringOptions& opts) { for (const ConstraintPtr& c : scope->constraints) { printf("\t%s\n", toString(*c, opts).c_str()); } - for (NotNull child : scope->children) + for (NotNull child : scope->children) dumpConstraints(child, opts); } -void dump(NotNull rootScope, ToStringOptions& opts) +static std::pair, std::vector> saturateArguments( + const TypeFun& fn, const std::vector& rawTypeArguments, const std::vector& rawPackArguments, TypeArena* arena) +{ + std::vector saturatedTypeArguments; + std::vector extraTypes; + std::vector saturatedPackArguments; + + for (size_t i = 0; i < rawTypeArguments.size(); ++i) + { + TypeId ty = rawTypeArguments[i]; + + if (i < fn.typeParams.size()) + saturatedTypeArguments.push_back(ty); + else + extraTypes.push_back(ty); + } + + // If we collected extra types, put them in a type pack now. This case is + // mutually exclusive with the type pack -> type conversion we do below: + // extraTypes will only have elements in it if we have more types than we + // have parameter slots for them to go into. + if (!extraTypes.empty()) + { + saturatedPackArguments.push_back(arena->addTypePack(extraTypes)); + } + + for (size_t i = 0; i < rawPackArguments.size(); ++i) + { + TypePackId tp = rawPackArguments[i]; + + // If we are short on regular type saturatedTypeArguments and we have a single + // element type pack, we can decompose that to the type it contains and + // use that as a type parameter. + if (saturatedTypeArguments.size() < fn.typeParams.size() && size(tp) == 1 && finite(tp) && first(tp) && saturatedPackArguments.empty()) + { + saturatedTypeArguments.push_back(*first(tp)); + } + else + { + saturatedPackArguments.push_back(tp); + } + } + + size_t typesProvided = saturatedTypeArguments.size(); + size_t typesRequired = fn.typeParams.size(); + + size_t packsProvided = saturatedPackArguments.size(); + size_t packsRequired = fn.typePackParams.size(); + + // Extra types should be accumulated in extraTypes, not saturatedTypeArguments. Extra + // packs will be accumulated in saturatedPackArguments, so we don't have an + // assertion for that. + LUAU_ASSERT(typesProvided <= typesRequired); + + // If we didn't provide enough types, but we did provide a type pack, we + // don't want to use defaults. The rationale for this is that if the user + // provides a pack but doesn't provide enough types, we want to report an + // error, rather than simply using the default saturatedTypeArguments, if they exist. If + // they did provide enough types, but not enough packs, we of course want to + // use the default packs. + bool needsDefaults = (typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired); + + if (needsDefaults) + { + // Default types can reference earlier types. It's legal to write + // something like + // type T = (A, B) -> number + // and we need to respect that. We use an ApplyTypeFunction for this. + ApplyTypeFunction atf{arena}; + + for (size_t i = 0; i < typesProvided; ++i) + atf.typeArguments[fn.typeParams[i].ty] = saturatedTypeArguments[i]; + + for (size_t i = typesProvided; i < typesRequired; ++i) + { + TypeId defaultTy = fn.typeParams[i].defaultValue.value_or(nullptr); + + // We will fill this in with the error type later. + if (!defaultTy) + break; + + TypeId instantiatedDefault = atf.substitute(defaultTy).value_or(getSingletonTypes().errorRecoveryType()); + atf.typeArguments[fn.typeParams[i].ty] = instantiatedDefault; + saturatedTypeArguments.push_back(instantiatedDefault); + } + + for (size_t i = 0; i < packsProvided; ++i) + { + atf.typePackArguments[fn.typePackParams[i].tp] = saturatedPackArguments[i]; + } + + for (size_t i = packsProvided; i < packsRequired; ++i) + { + TypePackId defaultTp = fn.typePackParams[i].defaultValue.value_or(nullptr); + + // We will fill this in with the error type pack later. + if (!defaultTp) + break; + + TypePackId instantiatedDefault = atf.substitute(defaultTp).value_or(getSingletonTypes().errorRecoveryTypePack()); + atf.typePackArguments[fn.typePackParams[i].tp] = instantiatedDefault; + saturatedPackArguments.push_back(instantiatedDefault); + } + } + + // If we didn't create an extra type pack from overflowing parameter packs, + // and we're still missing a type pack, plug in an empty type pack as the + // value of the empty packs. + if (extraTypes.empty() && saturatedPackArguments.size() + 1 == fn.typePackParams.size()) + { + saturatedPackArguments.push_back(arena->addTypePack({})); + } + + // We need to have _something_ when we substitute the generic saturatedTypeArguments, + // even if they're missing, so we use the error type as a filler. + for (size_t i = saturatedTypeArguments.size(); i < typesRequired; ++i) + { + saturatedTypeArguments.push_back(getSingletonTypes().errorRecoveryType()); + } + + for (size_t i = saturatedPackArguments.size(); i < packsRequired; ++i) + { + saturatedPackArguments.push_back(getSingletonTypes().errorRecoveryTypePack()); + } + + // At this point, these two conditions should be true. If they aren't we + // will run into access violations. + LUAU_ASSERT(saturatedTypeArguments.size() == fn.typeParams.size()); + LUAU_ASSERT(saturatedPackArguments.size() == fn.typePackParams.size()); + + return {saturatedTypeArguments, saturatedPackArguments}; +} + +bool InstantiationSignature::operator==(const InstantiationSignature& rhs) const +{ + return fn == rhs.fn && arguments == rhs.arguments && packArguments == rhs.packArguments; +} + +size_t HashInstantiationSignature::operator()(const InstantiationSignature& signature) const +{ + size_t hash = std::hash{}(signature.fn.type); + for (const GenericTypeDefinition& p : signature.fn.typeParams) + { + hash ^= (std::hash{}(p.ty) << 1); + } + + for (const GenericTypePackDefinition& p : signature.fn.typePackParams) + { + hash ^= (std::hash{}(p.tp) << 1); + } + + for (const TypeId a : signature.arguments) + { + hash ^= (std::hash{}(a) << 1); + } + + for (const TypePackId a : signature.packArguments) + { + hash ^= (std::hash{}(a) << 1); + } + + return hash; +} + +void dump(NotNull rootScope, ToStringOptions& opts) { printf("constraints:\n"); dumpConstraints(rootScope, opts); @@ -55,7 +221,7 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) } } -ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope) +ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope) : arena(arena) , constraints(collectConstraints(rootScope)) , rootScope(rootScope) @@ -77,6 +243,7 @@ void ConstraintSolver::run() return; ToStringOptions opts; + opts.exhaustive = true; if (FFlag::DebugLuauLogSolver) { @@ -186,6 +353,8 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*bc, constraint, force); else if (auto nc = get(*constraint)) success = tryDispatch(*nc, constraint); + else if (auto taec = get(*constraint)) + success = tryDispatch(*taec, constraint); else LUAU_ASSERT(0); @@ -325,6 +494,198 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNullarena); + + if (follow(petv.fn.type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) + { + foundInfiniteType = true; + return false; + } + + return true; + } +}; + +struct InstantiationQueuer : TypeVarOnceVisitor +{ + ConstraintSolver* solver; + const InstantiationSignature& signature; + + explicit InstantiationQueuer(ConstraintSolver* solver, const InstantiationSignature& signature) + : solver(solver) + , signature(signature) + { + } + + bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override + { + solver->pushConstraint(TypeAliasExpansionConstraint{ty}); + return false; + } +}; + +bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint) +{ + const PendingExpansionTypeVar* petv = get(follow(c.target)); + if (!petv) + { + unblock(c.target); + return true; + } + + auto bindResult = [this, &c](TypeId result) { + asMutable(c.target)->ty.emplace(result); + unblock(c.target); + }; + + // If there are no parameters to the type function we can just use the type + // directly. + if (petv->fn.typeParams.empty() && petv->fn.typePackParams.empty()) + { + bindResult(petv->fn.type); + return true; + } + + auto [typeArguments, packArguments] = saturateArguments(petv->fn, petv->typeArguments, petv->packArguments, arena); + + bool sameTypes = + std::equal(typeArguments.begin(), typeArguments.end(), petv->fn.typeParams.begin(), petv->fn.typeParams.end(), [](auto&& itp, auto&& p) { + return itp == p.ty; + }); + + bool samePacks = std::equal( + packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.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 + // to the TypeFun's type. + if (sameTypes && samePacks) + { + bindResult(petv->fn.type); + return true; + } + + InstantiationSignature signature{ + petv->fn, + typeArguments, + packArguments, + }; + + // If we use the same signature, we don't need to bother trying to + // instantiate the alias again, since the instantiation should be + // deterministic. + if (TypeId* cached = instantiatedAliases.find(signature)) + { + bindResult(*cached); + return true; + } + + // In order to prevent infinite types from being expanded and causing us to + // cycle infinitely, we need to scan the type function for cases where we + // expand the same alias with different type saturatedTypeArguments. See + // https://github.com/Roblox/luau/pull/68 for the RFC responsible for this. + // This is a little nicer than using a recursion limit because we can catch + // the infinite expansion before actually trying to expand it. + InfiniteTypeFinder itf{this, signature}; + itf.traverse(petv->fn.type); + + if (itf.foundInfiniteType) + { + // TODO (CLI-56761): Report an error. + bindResult(getSingletonTypes().errorRecoveryType()); + return true; + } + + ApplyTypeFunction applyTypeFunction{arena}; + for (size_t i = 0; i < typeArguments.size(); ++i) + { + applyTypeFunction.typeArguments[petv->fn.typeParams[i].ty] = typeArguments[i]; + } + + for (size_t i = 0; i < packArguments.size(); ++i) + { + applyTypeFunction.typePackArguments[petv->fn.typePackParams[i].tp] = packArguments[i]; + } + + std::optional maybeInstantiated = applyTypeFunction.substitute(petv->fn.type); + // Note that ApplyTypeFunction::encounteredForwardedType is never set in + // DCR, because we do not use free types for forward-declared generic + // aliases. + + if (!maybeInstantiated.has_value()) + { + // TODO (CLI-56761): Report an error. + bindResult(getSingletonTypes().errorRecoveryType()); + return true; + } + + TypeId instantiated = *maybeInstantiated; + TypeId target = follow(instantiated); + // Type function application will happily give us the exact same type if + // there are e.g. generic saturatedTypeArguments that go unused. + bool needsClone = follow(petv->fn.type) == target; + // Only tables have the properties we're trying to set. + TableTypeVar* ttv = getMutableTableType(target); + + if (ttv) + { + if (needsClone) + { + // Substitution::clone is a shallow clone. If this is a + // metatable type, we want to mutate its table, so we need to + // explicitly clone that table as well. If we don't, we will + // mutate another module's type surface and cause a + // use-after-free. + if (get(target)) + { + instantiated = applyTypeFunction.clone(target); + MetatableTypeVar* mtv = getMutable(instantiated); + mtv->table = applyTypeFunction.clone(mtv->table); + ttv = getMutable(mtv->table); + } + else if (get(target)) + { + instantiated = applyTypeFunction.clone(target); + ttv = getMutable(instantiated); + } + + target = follow(instantiated); + } + + ttv->instantiatedTypeParams = typeArguments; + ttv->instantiatedTypePackParams = packArguments; + // TODO: Fill in definitionModuleName. + } + + bindResult(target); + + // The application is not recursive, so we need to queue up application of + // any child type function instantiations within the result in order for it + // to be complete. + InstantiationQueuer queuer{this, signature}; + queuer.traverse(target); + + instantiatedAliases[signature] = target; + + return true; +} + void ConstraintSolver::block_(BlockedConstraintId target, NotNull constraint) { blocked[target].push_back(constraint); @@ -388,7 +749,7 @@ void ConstraintSolver::unblock(TypePackId progressed) bool ConstraintSolver::isBlocked(TypeId ty) { - return nullptr != get(follow(ty)); + return nullptr != get(follow(ty)) || nullptr != get(follow(ty)); } bool ConstraintSolver::isBlocked(NotNull constraint) @@ -415,4 +776,12 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack) u.log.commit(); } +void ConstraintSolver::pushConstraint(ConstraintV cv) +{ + std::unique_ptr c = std::make_unique(std::move(cv)); + NotNull borrow = NotNull(c.get()); + solverConstraints.push_back(std::move(c)); + unsolvedConstraints.push_back(borrow); +} + } // namespace Luau diff --git a/Analysis/src/ConstraintSolverLogger.cpp b/Analysis/src/ConstraintSolverLogger.cpp index 2f93c280..adb9c54e 100644 --- a/Analysis/src/ConstraintSolverLogger.cpp +++ b/Analysis/src/ConstraintSolverLogger.cpp @@ -2,45 +2,39 @@ #include "Luau/ConstraintSolverLogger.h" +#include "Luau/JsonEmitter.h" + namespace Luau { -static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& opts) +static void dumpScopeAndChildren(const Scope* scope, Json::JsonEmitter& emitter, ToStringOptions& opts) { - std::string output = "{\"bindings\":{"; + emitter.writeRaw("{"); + Json::write(emitter, "bindings"); + emitter.writeRaw(":"); - bool comma = false; - for (const auto& [name, type] : scope->bindings) + Json::ObjectEmitter o = emitter.writeObject(); + + for (const auto& [name, binding] : scope->bindings) { - if (comma) - output += ","; - - output += "\""; - output += name.c_str(); - output += "\": \""; - - ToStringResult result = toStringDetailed(type, opts); + ToStringResult result = toStringDetailed(binding.typeId, opts); opts.nameMap = std::move(result.nameMap); - output += result.name; - output += "\""; - - comma = true; + o.writePair(name.c_str(), result.name); } - output += "},\"children\":["; - comma = false; + o.finish(); + emitter.writeRaw(","); + Json::write(emitter, "children"); + emitter.writeRaw(":"); - for (const Scope2* child : scope->children) + Json::ArrayEmitter a = emitter.writeArray(); + for (const Scope* child : scope->children) { - if (comma) - output += ","; - - output += dumpScopeAndChildren(child, opts); - comma = true; + dumpScopeAndChildren(child, emitter, opts); } - output += "]}"; - return output; + a.finish(); + emitter.writeRaw("}"); } static std::string dumpConstraintsToDot(std::vector>& constraints, ToStringOptions& opts) @@ -80,51 +74,49 @@ static std::string dumpConstraintsToDot(std::vector>& std::string ConstraintSolverLogger::compileOutput() { - std::string output = "["; - bool comma = false; - + Json::JsonEmitter emitter; + emitter.writeRaw("["); for (const std::string& snapshot : snapshots) { - if (comma) - output += ","; - output += snapshot; - - comma = true; + emitter.writeComma(); + emitter.writeRaw(snapshot); } - output += "]"; - return output; + emitter.writeRaw("]"); + return emitter.str(); } -void ConstraintSolverLogger::captureBoundarySnapshot(const Scope2* rootScope, std::vector>& unsolvedConstraints) +void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std::vector>& unsolvedConstraints) { - std::string snapshot = "{\"type\":\"boundary\",\"rootScope\":"; + Json::JsonEmitter emitter; + Json::ObjectEmitter o = emitter.writeObject(); + o.writePair("type", "boundary"); + o.writePair("constraintGraph", dumpConstraintsToDot(unsolvedConstraints, opts)); + emitter.writeComma(); + Json::write(emitter, "rootScope"); + emitter.writeRaw(":"); + dumpScopeAndChildren(rootScope, emitter, opts); + o.finish(); - snapshot += dumpScopeAndChildren(rootScope, opts); - snapshot += ",\"constraintGraph\":\""; - snapshot += dumpConstraintsToDot(unsolvedConstraints, opts); - snapshot += "\"}"; - - snapshots.push_back(std::move(snapshot)); + snapshots.push_back(emitter.str()); } void ConstraintSolverLogger::prepareStepSnapshot( - const Scope2* rootScope, NotNull current, std::vector>& unsolvedConstraints) + const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints) { - // LUAU_ASSERT(!preparedSnapshot); + Json::JsonEmitter emitter; + Json::ObjectEmitter o = emitter.writeObject(); + o.writePair("type", "step"); + o.writePair("constraintGraph", dumpConstraintsToDot(unsolvedConstraints, opts)); + o.writePair("currentId", std::to_string(reinterpret_cast(current.get()))); + o.writePair("current", toString(*current, opts)); + emitter.writeComma(); + Json::write(emitter, "rootScope"); + emitter.writeRaw(":"); + dumpScopeAndChildren(rootScope, emitter, opts); + o.finish(); - std::string snapshot = "{\"type\":\"step\",\"rootScope\":"; - - snapshot += dumpScopeAndChildren(rootScope, opts); - snapshot += ",\"constraintGraph\":\""; - snapshot += dumpConstraintsToDot(unsolvedConstraints, opts); - snapshot += "\",\"currentId\":\""; - snapshot += std::to_string(reinterpret_cast(current.get())); - snapshot += "\",\"current\":\""; - snapshot += toString(*current, opts); - snapshot += "\"}"; - - preparedSnapshot = std::move(snapshot); + preparedSnapshot = emitter.str(); } void ConstraintSolverLogger::commitPreparedStepSnapshot() diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index f93f65dd..45663531 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,7 +2,6 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAG(LuauUnknownAndNeverType) -LUAU_FASTFLAG(LuauCheckLenMT) namespace Luau { @@ -123,6 +122,7 @@ declare function tonumber(value: T, radix: number?): number? declare function rawequal(a: T1, b: T2): boolean declare function rawget(tab: {[K]: V}, k: K): V declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} +declare function rawlen(obj: {[K]: V} | string): number declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? @@ -206,10 +206,6 @@ std::string getBuiltinDefinitionSource() std::string result = kBuiltinDefinitionLuaSrc; - // TODO: move this into kBuiltinDefinitionLuaSrc - if (FFlag::LuauCheckLenMT) - result += "declare function rawlen(obj: {[K]: V} | string): number\n"; - if (FFlag::LuauUnknownAndNeverType) result += "declare function error(message: T, level: number?): never\n"; else diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index a7670de5..fe65853d 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -77,6 +77,58 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName) } } +LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, const std::string& packageName) +{ + if (!FFlag::DebugLuauDeferredConstraintResolution) + return Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, source, packageName); + + LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend"); + + Luau::Allocator allocator; + Luau::AstNameTable names(allocator); + + ParseOptions options; + options.allowDeclarationSyntax = true; + + Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options); + + if (parseResult.errors.size() > 0) + return LoadDefinitionFileResult{false, parseResult, nullptr}; + + Luau::SourceModule module; + module.root = parseResult.root; + module.mode = Mode::Definition; + + ModulePtr checkedModule = check(module, Mode::Definition, globalScope); + + if (checkedModule->errors.size() > 0) + return LoadDefinitionFileResult{false, parseResult, checkedModule}; + + CloneState cloneState; + + for (const auto& [name, ty] : checkedModule->declaredGlobals) + { + TypeId globalTy = clone(ty, globalTypes, cloneState); + std::string documentationSymbol = packageName + "/global/" + name; + generateDocumentationSymbols(globalTy, documentationSymbol); + globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol}; + + persist(globalTy); + } + + for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings) + { + TypeFun globalTy = clone(ty, globalTypes, cloneState); + std::string documentationSymbol = packageName + "/globaltype/" + name; + generateDocumentationSymbols(globalTy.type, documentationSymbol); + globalScope->exportedTypeBindings[name] = globalTy; + + persist(globalTy.type); + } + + return LoadDefinitionFileResult{true, parseResult, checkedModule}; +} + LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName) { LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend"); @@ -766,35 +818,28 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons return const_cast(this)->getSourceModule(moduleName); } -NotNull Frontend::getGlobalScope2() +NotNull Frontend::getGlobalScope() { - if (!globalScope2) + if (!globalScope) { - const SingletonTypes& singletonTypes = getSingletonTypes(); - - globalScope2 = std::make_unique(); - globalScope2->typeBindings["nil"] = singletonTypes.nilType; - globalScope2->typeBindings["number"] = singletonTypes.numberType; - globalScope2->typeBindings["string"] = singletonTypes.stringType; - globalScope2->typeBindings["boolean"] = singletonTypes.booleanType; - globalScope2->typeBindings["thread"] = singletonTypes.threadType; + globalScope = typeChecker.globalScope; } - return NotNull(globalScope2.get()); + return NotNull(globalScope.get()); } ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope) { ModulePtr result = std::make_shared(); - ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope2()}; + ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()}; cgb.visit(sourceModule.root); result->errors = std::move(cgb.errors); ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)}; cs.run(); - result->scope2s = std::move(cgb.scopes); + result->scopes = std::move(cgb.scopes); result->astTypes = std::move(cgb.astTypes); result->astTypePacks = std::move(cgb.astTypePacks); result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes); diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 77c62422..1a6013af 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -4,6 +4,8 @@ #include "Luau/TxnLog.h" #include "Luau/TypeArena.h" +LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) + namespace Luau { @@ -31,6 +33,8 @@ bool Instantiation::ignoreChildren(TypeId ty) { if (log->getMutable(ty)) return true; + else if (FFlag::LuauClassTypeVarsInSubstitution && get(ty)) + return true; else return false; } diff --git a/Analysis/src/JsonEmitter.cpp b/Analysis/src/JsonEmitter.cpp new file mode 100644 index 00000000..e99619ba --- /dev/null +++ b/Analysis/src/JsonEmitter.cpp @@ -0,0 +1,220 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/JsonEmitter.h" + +#include "Luau/StringUtils.h" + +#include + +namespace Luau::Json +{ + +static constexpr int CHUNK_SIZE = 1024; + +ObjectEmitter::ObjectEmitter(NotNull emitter) + : emitter(emitter), finished(false) +{ + comma = emitter->pushComma(); + emitter->writeRaw('{'); +} + +ObjectEmitter::~ObjectEmitter() +{ + finish(); +} + +void ObjectEmitter::finish() +{ + if (finished) + return; + + emitter->writeRaw('}'); + emitter->popComma(comma); + finished = true; +} + +ArrayEmitter::ArrayEmitter(NotNull emitter) + : emitter(emitter), finished(false) +{ + comma = emitter->pushComma(); + emitter->writeRaw('['); +} + +ArrayEmitter::~ArrayEmitter() +{ + finish(); +} + +void ArrayEmitter::finish() +{ + if (finished) + return; + + emitter->writeRaw(']'); + emitter->popComma(comma); + finished = true; +} + +JsonEmitter::JsonEmitter() +{ + newChunk(); +} + +std::string JsonEmitter::str() +{ + return join(chunks, ""); +} + +bool JsonEmitter::pushComma() +{ + bool current = comma; + comma = false; + return current; +} + +void JsonEmitter::popComma(bool c) +{ + comma = c; +} + +void JsonEmitter::writeRaw(std::string_view sv) +{ + if (sv.size() > CHUNK_SIZE) + { + chunks.emplace_back(sv); + newChunk(); + return; + } + + auto& chunk = chunks.back(); + if (chunk.size() + sv.size() < CHUNK_SIZE) + { + chunk.append(sv.data(), sv.size()); + return; + } + + size_t prefix = CHUNK_SIZE - chunk.size(); + chunk.append(sv.data(), prefix); + newChunk(); + + chunks.back().append(sv.data() + prefix, sv.size() - prefix); +} + +void JsonEmitter::writeRaw(char c) +{ + writeRaw(std::string_view{&c, 1}); +} + +void write(JsonEmitter& emitter, bool b) +{ + if (b) + emitter.writeRaw("true"); + else + emitter.writeRaw("false"); +} + +void write(JsonEmitter& emitter, double d) +{ + emitter.writeRaw(std::to_string(d)); +} + +void write(JsonEmitter& emitter, int i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, long i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, long long i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, unsigned int i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, unsigned long i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, unsigned long long i) +{ + emitter.writeRaw(std::to_string(i)); +} + +void write(JsonEmitter& emitter, std::string_view sv) +{ + emitter.writeRaw('\"'); + + for (char c : sv) + { + if (c == '"') + emitter.writeRaw("\\\""); + else if (c == '\\') + emitter.writeRaw("\\\\"); + else if (c == '\n') + emitter.writeRaw("\\n"); + else if (c < ' ') + emitter.writeRaw(format("\\u%04x", c)); + else + emitter.writeRaw(c); + } + + emitter.writeRaw('\"'); +} + +void write(JsonEmitter& emitter, char c) +{ + write(emitter, std::string_view{&c, 1}); +} + +void write(JsonEmitter& emitter, const char* str) +{ + write(emitter, std::string_view{str, strlen(str)}); +} + +void write(JsonEmitter& emitter, const std::string& str) +{ + write(emitter, std::string_view{str}); +} + +void write(JsonEmitter& emitter, std::nullptr_t) +{ + emitter.writeRaw("null"); +} + +void write(JsonEmitter& emitter, std::nullopt_t) +{ + emitter.writeRaw("null"); +} + +void JsonEmitter::writeComma() +{ + if (comma) + writeRaw(','); + else + comma = true; +} + +ObjectEmitter JsonEmitter::writeObject() +{ + return ObjectEmitter{NotNull(this)}; +} + +ArrayEmitter JsonEmitter::writeArray() +{ + return ArrayEmitter{NotNull(this)}; +} + +void JsonEmitter::newChunk() +{ + chunks.emplace_back(); + chunks.back().reserve(CHUNK_SIZE); +} + +} // namespace Luau::Json diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 07f97328..e77f1e00 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -48,6 +48,7 @@ static const char* kWarningNames[] = { "DuplicateCondition", "MisleadingAndOr", "CommentDirective", + "IntegerParsing", }; // clang-format on @@ -1451,7 +1452,7 @@ private: const char* checkStringFormat(const char* data, size_t size) { const char* flags = "-+ #0"; - const char* options = "cdiouxXeEfgGqs"; + const char* options = "cdiouxXeEfgGqs*"; for (size_t i = 0; i < size; ++i) { @@ -2607,6 +2608,45 @@ private: } }; +class LintIntegerParsing : AstVisitor +{ +public: + LUAU_NOINLINE static void process(LintContext& context) + { + LintIntegerParsing pass; + pass.context = &context; + + context.root->visit(&pass); + } + +private: + LintContext* context; + + bool visit(AstExprConstantNumber* node) override + { + switch (node->parseResult) + { + case ConstantNumberParseResult::Ok: + case ConstantNumberParseResult::Malformed: + break; + case ConstantNumberParseResult::BinOverflow: + emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, + "Binary number literal exceeded available precision and has been truncated to 2^64"); + break; + case ConstantNumberParseResult::HexOverflow: + emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, + "Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); + break; + case ConstantNumberParseResult::DoublePrefix: + emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, + "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); + break; + } + + return true; + } +}; + static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, const ScopePtr& env) { ScopePtr current = env; @@ -2828,6 +2868,9 @@ std::vector lint(AstStat* root, const AstNameTable& names, const Sc if (context.warningEnabled(LintWarning::Code_CommentDirective)) lintComments(context, hotcomments); + if (context.warningEnabled(LintWarning::Code_IntegerParsing)) + LintIntegerParsing::process(context); + std::sort(context.result.begin(), context.result.end(), WarningComparator()); return context.result; diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 0603a042..2b46da87 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,7 +15,6 @@ #include LUAU_FASTFLAG(LuauLowerBoundsCalculation); -LUAU_FASTFLAG(LuauNormalizeFlagIsConservative); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false); @@ -100,29 +99,20 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) CloneState cloneState; - ScopePtr moduleScope = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : getModuleScope(); - Scope2* moduleScope2 = FFlag::DebugLuauDeferredConstraintResolution ? getModuleScope2() : nullptr; + ScopePtr moduleScope = getModuleScope(); - TypePackId returnType = FFlag::DebugLuauDeferredConstraintResolution ? moduleScope2->returnType : moduleScope->returnType; + TypePackId returnType = moduleScope->returnType; std::optional varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack; std::unordered_map* exportedTypeBindings = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings; returnType = clone(returnType, interfaceTypes, cloneState); - if (moduleScope) + moduleScope->returnType = returnType; + if (varargPack) { - moduleScope->returnType = returnType; - if (varargPack) - { - varargPack = clone(*varargPack, interfaceTypes, cloneState); - moduleScope->varargPack = varargPack; - } - } - else - { - LUAU_ASSERT(moduleScope2); - moduleScope2->returnType = returnType; // TODO varargPack + varargPack = clone(*varargPack, interfaceTypes, cloneState); + moduleScope->varargPack = varargPack; } ForceNormal forceNormal{&interfaceTypes}; @@ -149,20 +139,17 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) { normalize(tf.type, interfaceTypes, ice); - if (FFlag::LuauNormalizeFlagIsConservative) + // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables + // won't be marked normal. If the types aren't normal by now, they never will be. + forceNormal.traverse(tf.type); + for (GenericTypeDefinition param : tf.typeParams) { - // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables - // won't be marked normal. If the types aren't normal by now, they never will be. - forceNormal.traverse(tf.type); - for (GenericTypeDefinition param : tf.typeParams) - { - forceNormal.traverse(param.ty); + forceNormal.traverse(param.ty); - if (param.defaultValue) - { - normalize(*param.defaultValue, interfaceTypes, ice); - forceNormal.traverse(*param.defaultValue); - } + if (param.defaultValue) + { + normalize(*param.defaultValue, interfaceTypes, ice); + forceNormal.traverse(*param.defaultValue); } } } @@ -201,10 +188,4 @@ ScopePtr Module::getModuleScope() const return scopes.front().second; } -Scope2* Module::getModuleScope2() const -{ - LUAU_ASSERT(!scope2s.empty()); - return scope2s.front().second.get(); -} - } // namespace Luau diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index a96b557d..9ae3b404 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -13,7 +13,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false) // This could theoretically be 2000 on amd64, but x86 requires this. LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); -LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false); LUAU_FASTFLAGVARIABLE(LuauFixNormalizationOfCyclicUnions, false); LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauQuantifyConstrained) @@ -89,13 +88,7 @@ static bool areNormal_(const T& t, const std::unordered_set& seen, Intern if (count >= FInt::LuauNormalizeIterationLimit) ice.ice("Luau::areNormal hit iteration limit"); - if (FFlag::LuauNormalizeFlagIsConservative) - return ty->normal; - else - { - // The follow is here because a bound type may not be normal, but the bound type is normal. - return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end(); - } + return ty->normal; }; return std::all_of(begin(t), end(t), isNormal); diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 294c479d..03049cca 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -7,7 +7,6 @@ #include "Luau/TxnLog.h" #include "Luau/VisitTypeVar.h" -LUAU_FASTFLAG(LuauAlwaysQuantify); LUAU_FASTFLAG(DebugLuauSharedSelf) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAGVARIABLE(LuauQuantifyConstrained, false) @@ -16,13 +15,13 @@ namespace Luau { /// @return true if outer encloses inner -static bool subsumes(Scope2* outer, Scope2* inner) +static bool subsumes(Scope* outer, Scope* inner) { while (inner) { if (inner == outer) return true; - inner = inner->parent; + inner = inner->parent.get(); } return false; @@ -33,7 +32,7 @@ struct Quantifier final : TypeVarOnceVisitor TypeLevel level; std::vector generics; std::vector genericPacks; - Scope2* scope = nullptr; + Scope* scope = nullptr; bool seenGenericType = false; bool seenMutableType = false; @@ -43,20 +42,20 @@ struct Quantifier final : TypeVarOnceVisitor LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution); } - explicit Quantifier(Scope2* scope) + explicit Quantifier(Scope* scope) : scope(scope) { LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); } /// @return true if outer encloses inner - bool subsumes(Scope2* outer, Scope2* inner) + bool subsumes(Scope* outer, Scope* inner) { while (inner) { if (inner == outer) return true; - inner = inner->parent; + inner = inner->parent.get(); } return false; @@ -203,36 +202,20 @@ void quantify(TypeId ty, TypeLevel level) FunctionTypeVar* ftv = getMutable(ty); LUAU_ASSERT(ftv); - if (FFlag::LuauAlwaysQuantify) - { - ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); - ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); - } - else - { - ftv->generics = q.generics; - ftv->genericPacks = q.genericPacks; - } + ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); + ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); } } -void quantify(TypeId ty, Scope2* scope) +void quantify(TypeId ty, Scope* scope) { Quantifier q{scope}; q.traverse(ty); FunctionTypeVar* ftv = getMutable(ty); LUAU_ASSERT(ftv); - if (FFlag::LuauAlwaysQuantify) - { - ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); - ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); - } - else - { - ftv->generics = q.generics; - ftv->genericPacks = q.genericPacks; - } + 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->hasNoGenerics = true; @@ -240,11 +223,11 @@ void quantify(TypeId ty, Scope2* scope) struct PureQuantifier : Substitution { - Scope2* scope; + Scope* scope; std::vector insertedGenerics; std::vector insertedGenericPacks; - PureQuantifier(TypeArena* arena, Scope2* scope) + PureQuantifier(TypeArena* arena, Scope* scope) : Substitution(TxnLog::empty(), arena) , scope(scope) { @@ -322,7 +305,7 @@ struct PureQuantifier : Substitution } }; -TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope) +TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope) { PureQuantifier quantifier{arena, scope}; std::optional result = quantifier.substitute(ty); diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index 247a9dd6..bee16908 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -21,22 +21,6 @@ Scope::Scope(const ScopePtr& parent, int subLevel) level.subLevel = subLevel; } -std::optional Scope::lookup(const Symbol& name) -{ - Scope* scope = this; - - while (scope) - { - auto it = scope->bindings.find(name); - if (it != scope->bindings.end()) - return it->second.typeId; - - scope = scope->parent.get(); - } - - return std::nullopt; -} - std::optional Scope::lookupType(const Name& name) { const Scope* scope = this; @@ -121,48 +105,48 @@ std::optional Scope::linearSearchForBinding(const std::string& name, bo return std::nullopt; } -std::optional Scope2::lookup(Symbol sym) +std::optional Scope::lookup(Symbol sym) { - Scope2* s = this; + Scope* s = this; while (true) { auto it = s->bindings.find(sym); if (it != s->bindings.end()) - return it->second; + return it->second.typeId; if (s->parent) - s = s->parent; + s = s->parent.get(); else return std::nullopt; } } -std::optional Scope2::lookupTypeBinding(const Name& name) +std::optional Scope::lookupTypeBinding(const Name& name) { - Scope2* s = this; + Scope* s = this; while (s) { auto it = s->typeBindings.find(name); if (it != s->typeBindings.end()) return it->second; - s = s->parent; + s = s->parent.get(); } return std::nullopt; } -std::optional Scope2::lookupTypePackBinding(const Name& name) +std::optional Scope::lookupTypePackBinding(const Name& name) { - Scope2* s = this; + Scope* s = this; while (s) { auto it = s->typePackBindings.find(name); if (it != s->typePackBindings.end()) return it->second; - s = s->parent; + s = s->parent.get(); } return std::nullopt; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 7245403c..148c9ee2 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -9,9 +9,12 @@ #include LUAU_FASTFLAGVARIABLE(LuauAnyificationMustClone, false) +LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false) LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) +LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false) LUAU_FASTFLAG(LuauUnknownAndNeverType) +LUAU_FASTFLAGVARIABLE(LuauSubstitutionReentrant, false) namespace Luau { @@ -28,6 +31,14 @@ void Tarjan::visitChildren(TypeId ty, int index) if (const FunctionTypeVar* ftv = get(ty)) { + if (FFlag::LuauSubstitutionFixMissingFields) + { + for (TypeId generic : ftv->generics) + visitChild(generic); + for (TypePackId genericPack : ftv->genericPacks) + visitChild(genericPack); + } + visitChild(ftv->argTypes); visitChild(ftv->retTypes); } @@ -68,6 +79,25 @@ void Tarjan::visitChildren(TypeId ty, int index) for (TypeId part : ctv->parts) visitChild(part); } + else if (const PendingExpansionTypeVar* petv = get(ty)) + { + for (TypeId a : petv->typeArguments) + visitChild(a); + + for (TypePackId a : petv->packArguments) + visitChild(a); + } + else if (const ClassTypeVar* ctv = get(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv) + { + for (auto [name, prop] : ctv->props) + visitChild(prop.type); + + if (ctv->parent) + visitChild(*ctv->parent); + + if (ctv->metatable) + visitChild(*ctv->metatable); + } } void Tarjan::visitChildren(TypePackId tp, int index) @@ -267,6 +297,24 @@ TarjanResult Tarjan::visitRoot(TypePackId tp) return loop(); } +void FindDirty::clearTarjan() +{ + dirty.clear(); + + typeToIndex.clear(); + packToIndex.clear(); + indexToType.clear(); + indexToPack.clear(); + + stack.clear(); + onStack.clear(); + lowlink.clear(); + + edgesTy.clear(); + edgesTp.clear(); + worklist.clear(); +} + bool FindDirty::getDirty(int index) { if (dirty.size() <= size_t(index)) @@ -330,16 +378,46 @@ std::optional Substitution::substitute(TypeId ty) { ty = log->follow(ty); + // clear algorithm state for reentrancy + if (FFlag::LuauSubstitutionReentrant) + clearTarjan(); + auto result = findDirty(ty); if (result != TarjanResult::Ok) return std::nullopt; for (auto [oldTy, newTy] : newTypes) - if (!ignoreChildren(oldTy)) - replaceChildren(newTy); + { + if (FFlag::LuauSubstitutionReentrant) + { + if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy)) + { + replaceChildren(newTy); + replacedTypes.insert(newTy); + } + } + else + { + if (!ignoreChildren(oldTy)) + replaceChildren(newTy); + } + } for (auto [oldTp, newTp] : newPacks) - if (!ignoreChildren(oldTp)) - replaceChildren(newTp); + { + if (FFlag::LuauSubstitutionReentrant) + { + if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp)) + { + replaceChildren(newTp); + replacedTypePacks.insert(newTp); + } + } + else + { + if (!ignoreChildren(oldTp)) + replaceChildren(newTp); + } + } TypeId newTy = replace(ty); return newTy; } @@ -348,16 +426,46 @@ std::optional Substitution::substitute(TypePackId tp) { tp = log->follow(tp); + // clear algorithm state for reentrancy + if (FFlag::LuauSubstitutionReentrant) + clearTarjan(); + auto result = findDirty(tp); if (result != TarjanResult::Ok) return std::nullopt; for (auto [oldTy, newTy] : newTypes) - if (!ignoreChildren(oldTy)) - replaceChildren(newTy); + { + if (FFlag::LuauSubstitutionReentrant) + { + if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy)) + { + replaceChildren(newTy); + replacedTypes.insert(newTy); + } + } + else + { + if (!ignoreChildren(oldTy)) + replaceChildren(newTy); + } + } for (auto [oldTp, newTp] : newPacks) - if (!ignoreChildren(oldTp)) - replaceChildren(newTp); + { + if (FFlag::LuauSubstitutionReentrant) + { + if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp)) + { + replaceChildren(newTp); + replacedTypePacks.insert(newTp); + } + } + else + { + if (!ignoreChildren(oldTp)) + replaceChildren(newTp); + } + } TypePackId newTp = replace(tp); return newTp; } @@ -385,6 +493,8 @@ TypePackId Substitution::clone(TypePackId tp) { VariadicTypePack clone; clone.ty = vtp->ty; + if (FFlag::LuauSubstitutionFixMissingFields) + clone.hidden = vtp->hidden; return addTypePack(std::move(clone)); } else @@ -395,6 +505,9 @@ void Substitution::foundDirty(TypeId ty) { ty = log->follow(ty); + if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty)) + return; + if (isDirty(ty)) newTypes[ty] = follow(clean(ty)); else @@ -405,6 +518,9 @@ void Substitution::foundDirty(TypePackId tp) { tp = log->follow(tp); + if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp)) + return; + if (isDirty(tp)) newPacks[tp] = follow(clean(tp)); else @@ -446,6 +562,14 @@ void Substitution::replaceChildren(TypeId ty) if (FunctionTypeVar* ftv = getMutable(ty)) { + if (FFlag::LuauSubstitutionFixMissingFields) + { + for (TypeId& generic : ftv->generics) + generic = replace(generic); + for (TypePackId& genericPack : ftv->genericPacks) + genericPack = replace(genericPack); + } + ftv->argTypes = replace(ftv->argTypes); ftv->retTypes = replace(ftv->retTypes); } @@ -486,6 +610,25 @@ void Substitution::replaceChildren(TypeId ty) for (TypeId& part : ctv->parts) part = replace(part); } + else if (PendingExpansionTypeVar* petv = getMutable(ty)) + { + for (TypeId& a : petv->typeArguments) + a = replace(a); + + for (TypePackId& a : petv->packArguments) + a = replace(a); + } + else if (ClassTypeVar* ctv = getMutable(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv) + { + for (auto& [name, prop] : ctv->props) + prop.type = replace(prop.type); + + if (ctv->parent) + ctv->parent = replace(*ctv->parent); + + if (ctv->metatable) + ctv->metatable = replace(*ctv->metatable); + } } void Substitution::replaceChildren(TypePackId tp) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 4c606922..79d2e125 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTFLAG(LuauUnknownAndNeverType) +LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false) /* * Prefix generic typenames with gen- @@ -231,6 +232,11 @@ struct StringifierState emit(std::to_string(i).c_str()); } + void emit(size_t i) + { + emit(std::to_string(i).c_str()); + } + void indent() { indentation += 4; @@ -277,7 +283,10 @@ struct TypeVarStringifier if (tv->ty.valueless_by_exception()) { state.result.error = true; - state.emit("< VALUELESS BY EXCEPTION >"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("* VALUELESS BY EXCEPTION *"); + else + state.emit("< VALUELESS BY EXCEPTION >"); return; } @@ -406,6 +415,13 @@ struct TypeVarStringifier state.emit("*"); } + void operator()(TypeId ty, const PendingExpansionTypeVar& petv) + { + state.emit("*pending-expansion-"); + state.emit(petv.index); + state.emit("*"); + } + void operator()(TypeId, const PrimitiveTypeVar& ptv) { switch (ptv.type) @@ -453,7 +469,10 @@ struct TypeVarStringifier if (state.hasSeen(&ftv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -561,7 +580,10 @@ struct TypeVarStringifier if (state.hasSeen(&ttv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -691,7 +713,10 @@ struct TypeVarStringifier if (state.hasSeen(&uv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -758,7 +783,10 @@ struct TypeVarStringifier if (state.hasSeen(&uv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -803,7 +831,10 @@ struct TypeVarStringifier void operator()(TypeId, const ErrorTypeVar& tv) { state.result.error = true; - state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*"); + else + state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); } void operator()(TypeId, const LazyTypeVar& ltv) @@ -857,7 +888,10 @@ struct TypePackStringifier if (tp->ty.valueless_by_exception()) { state.result.error = true; - state.emit("< VALUELESS TP BY EXCEPTION >"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("* VALUELESS TP BY EXCEPTION *"); + else + state.emit("< VALUELESS TP BY EXCEPTION >"); return; } @@ -881,7 +915,10 @@ struct TypePackStringifier if (state.hasSeen(&tp)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLETP*"); + else + state.emit(""); return; } @@ -926,14 +963,22 @@ struct TypePackStringifier void operator()(TypePackId, const Unifiable::Error& error) { state.result.error = true; - state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*"); + else + state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); } void operator()(TypePackId, const VariadicTypePack& pack) { state.emit("..."); if (FFlag::DebugLuauVerboseTypeNames && pack.hidden) - state.emit(""); + { + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*hidden*"); + else + state.emit(""); + } stringify(pack.ty); } @@ -1128,7 +1173,11 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts) if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) { result.truncated = true; - result.name += "... "; + + if (FFlag::LuauSpecialTypesAsterisked) + result.name += "... *TRUNCATED*"; + else + result.name += "... "; } return result; @@ -1199,7 +1248,12 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts) } if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) - result.name += "... "; + { + if (FFlag::LuauSpecialTypesAsterisked) + result.name += "... *TRUNCATED*"; + else + result.name += "... "; + } return result; } @@ -1417,6 +1471,12 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) opts.nameMap = std::move(namedStr.nameMap); return "@name(" + namedStr.name + ") = " + c.name; } + else if constexpr (std::is_same_v) + { + ToStringResult targetStr = toStringDetailed(c.target, opts); + opts.nameMap = std::move(targetStr.nameMap); + return "expand " + targetStr.name; + } else static_assert(always_false_v, "Non-exhaustive constraint switch"); }; diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index 2bc89cf6..f21a4fa9 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -99,6 +99,11 @@ public: return allocator->alloc(Location(), std::nullopt, AstName("*blocked*")); } + AstType* operator()(const PendingExpansionTypeVar& petv) + { + return allocator->alloc(Location(), std::nullopt, AstName("*pending-expansion*")); + } + AstType* operator()(const ConstrainedTypeVar& ctv) { AstArray types; diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index d3b9655d..53b069cf 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -67,8 +67,18 @@ struct TypeChecker2 : public AstVisitor return follow(*ty); } + TypePackId lookupPackAnnotation(AstTypePack* annotation) + { + TypePackId* tp = module->astResolvedTypePacks.find(annotation); + LUAU_ASSERT(tp); + return follow(*tp); + } + TypePackId reconstructPack(AstArray exprs, TypeArena& arena) { + if (exprs.size == 0) + return arena.addTypePack(TypePack{{}, std::nullopt}); + std::vector head; for (size_t i = 0; i < exprs.size - 1; ++i) @@ -80,14 +90,14 @@ struct TypeChecker2 : public AstVisitor return arena.addTypePack(TypePack{head, tail}); } - Scope2* findInnermostScope(Location location) + Scope* findInnermostScope(Location location) { - Scope2* bestScope = module->getModuleScope2(); - Location bestLocation = module->scope2s[0].first; + Scope* bestScope = module->getModuleScope().get(); + Location bestLocation = module->scopes[0].first; - for (size_t i = 0; i < module->scope2s.size(); ++i) + for (size_t i = 0; i < module->scopes.size(); ++i) { - auto& [scopeBounds, scope] = module->scope2s[i]; + auto& [scopeBounds, scope] = module->scopes[i]; if (scopeBounds.encloses(location)) { if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end) @@ -181,7 +191,7 @@ struct TypeChecker2 : public AstVisitor bool visit(AstStatReturn* ret) override { - Scope2* scope = findInnermostScope(ret->location); + Scope* scope = findInnermostScope(ret->location); TypePackId expectedRetType = scope->returnType; TypeArena arena; @@ -359,13 +369,154 @@ struct TypeChecker2 : public AstVisitor bool visit(AstTypeReference* ty) override { - Scope2* scope = findInnermostScope(ty->location); + Scope* scope = findInnermostScope(ty->location); + LUAU_ASSERT(scope); // TODO: Imported types - // TODO: Generic types - if (!scope->lookupTypeBinding(ty->name.value)) + + std::optional alias = scope->lookupTypeBinding(ty->name.value); + + if (alias.has_value()) { - reportError(UnknownSymbol{ty->name.value, UnknownSymbol::Context::Type}, ty->location); + 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 hasDefaultPacks = std::any_of(alias->typePackParams.begin(), alias->typePackParams.end(), [](auto&& el) { + return el.defaultValue.has_value(); + }); + + if (!ty->hasParameterList) + { + if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks)) + { + reportError(GenericError{"Type parameter list is required"}, ty->location); + } + } + + size_t typesProvided = 0; + size_t extraTypes = 0; + size_t packsProvided = 0; + + for (const AstTypeOrPack& p : ty->parameters) + { + if (p.type) + { + if (packsProvided != 0) + { + reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location); + } + + if (typesProvided < typesRequired) + { + typesProvided += 1; + } + else + { + extraTypes += 1; + } + } + else if (p.typePack) + { + TypePackId tp = lookupPackAnnotation(p.typePack); + + if (typesProvided < typesRequired && size(tp) == 1 && finite(tp) && first(tp)) + { + typesProvided += 1; + } + else + { + packsProvided += 1; + } + } + } + + if (extraTypes != 0 && packsProvided == 0) + { + packsProvided += 1; + } + + for (size_t i = typesProvided; i < typesRequired; ++i) + { + if (alias->typeParams[i].defaultValue) + { + typesProvided += 1; + } + } + + for (size_t i = packsProvided; i < packsProvided; ++i) + { + if (alias->typePackParams[i].defaultValue) + { + packsProvided += 1; + } + } + + if (extraTypes == 0 && packsProvided + 1 == packsRequired) + { + packsProvided += 1; + } + + if (typesProvided != typesRequired || packsProvided != packsRequired) + { + reportError(IncorrectGenericParameterCount{ + /* name */ ty->name.value, + /* typeFun */ *alias, + /* actualParameters */ typesProvided, + /* actualPackParameters */ packsProvided, + }, + ty->location); + } + } + else + { + if (scope->lookupTypePackBinding(ty->name.value)) + { + reportError( + SwappedGenericTypeParameter{ + ty->name.value, + SwappedGenericTypeParameter::Kind::Type, + }, + ty->location); + } + else + { + reportError(UnknownSymbol{ty->name.value, UnknownSymbol::Context::Type}, ty->location); + } + } + + return true; + } + + bool visit(AstTypePack*) override + { + return true; + } + + bool visit(AstTypePackGeneric* tp) override + { + Scope* scope = findInnermostScope(tp->location); + LUAU_ASSERT(scope); + + std::optional alias = scope->lookupTypePackBinding(tp->genericName.value); + if (!alias.has_value()) + { + if (scope->lookupTypeBinding(tp->genericName.value)) + { + reportError( + SwappedGenericTypeParameter{ + tp->genericName.value, + SwappedGenericTypeParameter::Kind::Pack, + }, + tp->location); + } + else + { + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + } } return true; diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 5931ca5b..7f79c234 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TypeInfer.h" +#include "Luau/ApplyTypeFunction.h" #include "Luau/Clone.h" #include "Luau/Common.h" #include "Luau/Instantiation.h" @@ -35,17 +36,13 @@ LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false) LUAU_FASTFLAGVARIABLE(LuauIndexSilenceErrors, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) -LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false) -LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false) +LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix3, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. -LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false); -LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false) LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false) LUAU_FASTFLAG(LuauQuantifyConstrained) LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false) -LUAU_FASTFLAGVARIABLE(LuauCheckLenMT, false) LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) @@ -1667,7 +1664,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) ftv->hasSelf = true; } } @@ -2111,35 +2108,16 @@ std::vector TypeChecker::reduceUnion(const std::vector& types) if (const UnionTypeVar* utv = get(t)) { - if (FFlag::LuauReduceUnionRecursion) + for (TypeId ty : utv) { - for (TypeId ty : utv) - { - if (FFlag::LuauNormalizeFlagIsConservative) - ty = follow(ty); - if (get(ty)) - continue; - if (get(ty) || get(ty)) - return {ty}; + ty = follow(ty); + if (get(ty)) + continue; + if (get(ty) || get(ty)) + return {ty}; - if (result.end() == std::find(result.begin(), result.end(), ty)) - result.push_back(ty); - } - } - else - { - std::vector r = reduceUnion(utv->options); - for (TypeId ty : r) - { - ty = follow(ty); - if (get(ty)) - continue; - if (get(ty) || get(ty)) - return {ty}; - - if (std::find(result.begin(), result.end(), ty) == result.end()) - result.push_back(ty); - } + if (result.end() == std::find(result.begin(), result.end(), ty)) + result.push_back(ty); } } else if (std::find(result.begin(), result.end(), t) == result.end()) @@ -2467,7 +2445,7 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp DenseHashSet seen{nullptr}; - if (FFlag::LuauCheckLenMT && typeCouldHaveMetatable(operandType)) + if (typeCouldHaveMetatable(operandType)) { if (auto fnt = findMetatableEntry(operandType, "__len", expr.location, /* addErrors= */ true)) { @@ -4781,16 +4759,8 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location { const FunctionTypeVar* ftv = get(ty); - if (FFlag::LuauAlwaysQuantify) - { - if (ftv) - Luau::quantify(ty, scope->level); - } - else - { - if (ftv && ftv->generics.empty() && ftv->genericPacks.empty()) - Luau::quantify(ty, scope->level); - } + if (ftv) + Luau::quantify(ty, scope->level); if (FFlag::LuauLowerBoundsCalculation && ftv) { @@ -5264,7 +5234,7 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno if (notEnoughParameters && hasDefaultParameters) { // 'applyTypeFunction' is used to substitute default types that reference previous generic types - ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; + ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes}; for (size_t i = 0; i < typesProvided; ++i) applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i]; @@ -5505,65 +5475,13 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack return result; } -bool ApplyTypeFunction::isDirty(TypeId ty) -{ - if (typeArguments.count(ty)) - return true; - else if (const FreeTypeVar* ftv = get(ty)) - { - if (ftv->forwardedTypeAlias) - encounteredForwardedType = true; - return false; - } - else - return false; -} - -bool ApplyTypeFunction::isDirty(TypePackId tp) -{ - if (typePackArguments.count(tp)) - return true; - else - return false; -} - -bool ApplyTypeFunction::ignoreChildren(TypeId ty) -{ - if (get(ty)) - return true; - else - return false; -} - -bool ApplyTypeFunction::ignoreChildren(TypePackId tp) -{ - if (get(tp)) - return true; - else - return false; -} - -TypeId ApplyTypeFunction::clean(TypeId ty) -{ - TypeId& arg = typeArguments[ty]; - LUAU_ASSERT(arg); - return arg; -} - -TypePackId ApplyTypeFunction::clean(TypePackId tp) -{ - TypePackId& arg = typePackArguments[tp]; - LUAU_ASSERT(arg); - return arg; -} - 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; - ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; + ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes}; for (size_t i = 0; i < tf.typeParams.size(); ++i) applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i]; diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index ebaf5906..ada2b012 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -445,6 +445,16 @@ BlockedTypeVar::BlockedTypeVar() int BlockedTypeVar::nextIndex = 0; +PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector typeArguments, std::vector packArguments) + : fn(fn) + , typeArguments(typeArguments) + , packArguments(packArguments) + , index(++nextIndex) +{ +} + +size_t PendingExpansionTypeVar::nextIndex = 0; + FunctionTypeVar::FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) : argTypes(argTypes) , retTypes(retTypes) @@ -1058,7 +1068,7 @@ ConstrainedTypeVarIterator end(const ConstrainedTypeVar* ctv) static std::vector parseFormatString(TypeChecker& typechecker, const char* data, size_t size) { - const char* options = "cdiouxXeEfgGqs"; + const char* options = "cdiouxXeEfgGqs*"; std::vector result; @@ -1072,7 +1082,7 @@ static std::vector parseFormatString(TypeChecker& typechecker, const cha continue; // we just ignore all characters (including flags/precision) up until first alphabetic character - while (i < size && !(data[i] > 0 && isalpha(data[i]))) + while (i < size && !(data[i] > 0 && (isalpha(data[i]) || data[i] == '*'))) i++; if (i == size) @@ -1080,6 +1090,8 @@ static std::vector parseFormatString(TypeChecker& typechecker, const cha if (data[i] == 'q' || data[i] == 's') result.push_back(typechecker.stringType); + else if (data[i] == '*') + result.push_back(typechecker.unknownType); else if (strchr(options, data[i])) result.push_back(typechecker.numberType); else @@ -1410,4 +1422,19 @@ bool hasTag(const Property& prop, const std::string& tagName) return hasTag(prop.tags, tagName); } +bool TypeFun::operator==(const TypeFun& rhs) const +{ + return type == rhs.type && typeParams == rhs.typeParams && typePackParams == rhs.typePackParams; +} + +bool GenericTypeDefinition::operator==(const GenericTypeDefinition& rhs) const +{ + return ty == rhs.ty && defaultValue == rhs.defaultValue; +} + +bool GenericTypePackDefinition::operator==(const GenericTypePackDefinition& rhs) const +{ + return tp == rhs.tp && defaultValue == rhs.defaultValue; +} + } // namespace Luau diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index 8d23aa49..63d8647d 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -12,7 +12,7 @@ Free::Free(TypeLevel level) { } -Free::Free(Scope2* scope) +Free::Free(Scope* scope) : scope(scope) { } @@ -39,7 +39,7 @@ Generic::Generic(const Name& name) { } -Generic::Generic(Scope2* scope) +Generic::Generic(Scope* scope) : index(++nextIndex) , scope(scope) { @@ -53,7 +53,7 @@ Generic::Generic(TypeLevel level, const Name& name) { } -Generic::Generic(Scope2* scope, const Name& name) +Generic::Generic(Scope* scope, const Name& name) : index(++nextIndex) , scope(scope) , name(name) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index b134ed31..5c040007 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -478,16 +478,26 @@ public: bool value; }; +enum class ConstantNumberParseResult +{ + Ok, + Malformed, + BinOverflow, + HexOverflow, + DoublePrefix, +}; + class AstExprConstantNumber : public AstExpr { public: LUAU_RTTI(AstExprConstantNumber) - AstExprConstantNumber(const Location& location, double value); + AstExprConstantNumber(const Location& location, double value, ConstantNumberParseResult parseResult = ConstantNumberParseResult::Ok); void visit(AstVisitor* visitor) override; double value; + ConstantNumberParseResult parseResult; }; class AstExprConstantString : public AstExpr diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 7c61c408..5ede3893 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -50,9 +50,10 @@ void AstExprConstantBool::visit(AstVisitor* visitor) visitor->visit(this); } -AstExprConstantNumber::AstExprConstantNumber(const Location& location, double value) +AstExprConstantNumber::AstExprConstantNumber(const Location& location, double value, ConstantNumberParseResult parseResult) : AstExpr(ClassIndex(), location) , value(value) + , parseResult(parseResult) { } diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 9f87444c..390a103b 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -21,11 +21,10 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false) bool lua_telemetry_parsed_named_non_function_type = false; -LUAU_FASTFLAGVARIABLE(LuauErrorParseIntegerIssues, false) +LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false) +LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) -LUAU_FASTFLAGVARIABLE(LuauAlwaysCaptureHotComments, false) - LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false) bool lua_telemetry_parsed_out_of_range_bin_integer = false; @@ -2050,8 +2049,10 @@ AstExpr* Parser::parseAssertionExpr() return expr; } -static const char* parseInteger(double& result, const char* data, int base) +static const char* parseInteger_DEPRECATED(double& result, const char* data, int base) { + LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues); + char* end = nullptr; unsigned long long value = strtoull(data, &end, base); @@ -2071,9 +2072,6 @@ static const char* parseInteger(double& result, const char* data, int base) else lua_telemetry_parsed_out_of_range_hex_integer = true; } - - if (FFlag::LuauErrorParseIntegerIssues) - return "Integer number value is out of range"; } } @@ -2081,11 +2079,13 @@ static const char* parseInteger(double& result, const char* data, int base) return *end == 0 ? nullptr : "Malformed number"; } -static const char* parseNumber(double& result, const char* data) +static const char* parseNumber_DEPRECATED2(double& result, const char* data) { + LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues); + // binary literal if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2]) - return parseInteger(result, data + 2, 2); + return parseInteger_DEPRECATED(result, data + 2, 2); // hexadecimal literal if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2]) @@ -2093,10 +2093,7 @@ static const char* parseNumber(double& result, const char* data) if (DFFlag::LuaReportParseIntegerIssues && data[2] == '0' && (data[3] == 'x' || data[3] == 'X')) lua_telemetry_parsed_double_prefix_hex_integer = true; - if (FFlag::LuauErrorParseIntegerIssues) - return parseInteger(result, data, 16); // keep prefix, it's handled by 'strtoull' - else - return parseInteger(result, data + 2, 16); + return parseInteger_DEPRECATED(result, data + 2, 16); } char* end = nullptr; @@ -2108,6 +2105,8 @@ static const char* parseNumber(double& result, const char* data) static bool parseNumber_DEPRECATED(double& result, const char* data) { + LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues); + // binary literal if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2]) { @@ -2136,6 +2135,73 @@ static bool parseNumber_DEPRECATED(double& result, const char* data) } } +static ConstantNumberParseResult parseInteger(double& result, const char* data, int base) +{ + LUAU_ASSERT(FFlag::LuauLintParseIntegerIssues); + LUAU_ASSERT(base == 2 || base == 16); + + char* end = nullptr; + unsigned long long value = strtoull(data, &end, base); + + if (*end != 0) + return ConstantNumberParseResult::Malformed; + + result = double(value); + + if (value == ULLONG_MAX && errno == ERANGE) + { + // 'errno' might have been set before we called 'strtoull', but we don't want the overhead of resetting a TLS variable on each call + // so we only reset it when we get a result that might be an out-of-range error and parse again to make sure + errno = 0; + value = strtoull(data, &end, base); + + if (errno == ERANGE) + { + if (DFFlag::LuaReportParseIntegerIssues) + { + if (base == 2) + lua_telemetry_parsed_out_of_range_bin_integer = true; + else + lua_telemetry_parsed_out_of_range_hex_integer = true; + } + + return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow; + } + } + + return ConstantNumberParseResult::Ok; +} + +static ConstantNumberParseResult parseNumber(double& result, const char* data) +{ + LUAU_ASSERT(FFlag::LuauLintParseIntegerIssues); + + // binary literal + if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2]) + return parseInteger(result, data + 2, 2); + + // hexadecimal literal + if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2]) + { + if (!FFlag::LuauErrorDoubleHexPrefix && data[2] == '0' && (data[3] == 'x' || data[3] == 'X')) + { + if (DFFlag::LuaReportParseIntegerIssues) + lua_telemetry_parsed_double_prefix_hex_integer = true; + + ConstantNumberParseResult parseResult = parseInteger(result, data + 2, 16); + return parseResult == ConstantNumberParseResult::Malformed ? parseResult : ConstantNumberParseResult::DoublePrefix; + } + + return parseInteger(result, data, 16); // pass in '0x' prefix, it's handled by 'strtoull' + } + + char* end = nullptr; + double value = strtod(data, &end); + + result = value; + return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed; +} + // simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp AstExpr* Parser::parseSimpleExpr() { @@ -2176,10 +2242,21 @@ AstExpr* Parser::parseSimpleExpr() scratchData.erase(std::remove(scratchData.begin(), scratchData.end(), '_'), scratchData.end()); } - if (DFFlag::LuaReportParseIntegerIssues || FFlag::LuauErrorParseIntegerIssues) + if (FFlag::LuauLintParseIntegerIssues) { double value = 0; - if (const char* error = parseNumber(value, scratchData.c_str())) + ConstantNumberParseResult result = parseNumber(value, scratchData.c_str()); + nextLexeme(); + + if (result == ConstantNumberParseResult::Malformed) + return reportExprError(start, {}, "Malformed number"); + + return allocator.alloc(start, value, result); + } + else if (DFFlag::LuaReportParseIntegerIssues) + { + double value = 0; + if (const char* error = parseNumber_DEPRECATED2(value, scratchData.c_str())) { nextLexeme(); @@ -3014,39 +3091,34 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const void Parser::nextLexeme() { - if (options.captureComments || FFlag::LuauAlwaysCaptureHotComments) + Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; + + while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment) { - Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; + const Lexeme& lexeme = lexer.current(); - while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment) + if (options.captureComments) + commentLocations.push_back(Comment{lexeme.type, lexeme.location}); + + // Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme. + // The parser will turn this into a proper syntax error. + if (lexeme.type == Lexeme::BrokenComment) + return; + + // Comments starting with ! are called "hot comments" and contain directives for type checking / linting / compiling + if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!') { - const Lexeme& lexeme = lexer.current(); + const char* text = lexeme.data; - if (options.captureComments) - commentLocations.push_back(Comment{lexeme.type, lexeme.location}); + unsigned int end = lexeme.length; + while (end > 0 && isSpace(text[end - 1])) + --end; - // Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme. - // The parser will turn this into a proper syntax error. - if (lexeme.type == Lexeme::BrokenComment) - return; - - // Comments starting with ! are called "hot comments" and contain directives for type checking / linting / compiling - if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!') - { - const char* text = lexeme.data; - - unsigned int end = lexeme.length; - while (end > 0 && isSpace(text[end - 1])) - --end; - - hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)}); - } - - type = lexer.next(/* skipComments= */ false, /* updatePrevLocation= */ false).type; + hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)}); } + + type = lexer.next(/* skipComments= */ false, /* updatePrevLocation= */ false).type; } - else - lexer.next(); } } // namespace Luau diff --git a/CLI/Ast.cpp b/CLI/Ast.cpp index 6ee608c4..fd99d225 100644 --- a/CLI/Ast.cpp +++ b/CLI/Ast.cpp @@ -3,7 +3,7 @@ #include "Luau/Common.h" #include "Luau/Ast.h" -#include "Luau/JsonEncoder.h" +#include "Luau/AstJsonEncoder.h" #include "Luau/Parser.h" #include "Luau/ParseOptions.h" diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 41360731..4d3beec9 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -20,6 +20,9 @@ #ifdef _WIN32 #include #include + +#define WIN32_LEAN_AND_MEAN +#include #endif #ifdef CALLGRIND @@ -27,6 +30,7 @@ #endif #include +#include LUAU_FASTFLAG(DebugLuauTimeTracing) @@ -47,6 +51,35 @@ enum class CompileFormat constexpr int MaxTraversalLimit = 50; +// Ctrl-C handling +static void sigintCallback(lua_State* L, int gc) +{ + if (gc >= 0) + return; + + lua_callbacks(L)->interrupt = NULL; + + lua_rawcheckstack(L, 1); // reserve space for error string + luaL_error(L, "Execution interrupted"); +} + +static lua_State* replState = NULL; + +#ifdef _WIN32 +BOOL WINAPI sigintHandler(DWORD signal) +{ + if (signal == CTRL_C_EVENT && replState) + lua_callbacks(replState)->interrupt = &sigintCallback; + return TRUE; +} +#else +static void sigintHandler(int signum) +{ + if (signum == SIGINT && replState) + lua_callbacks(replState)->interrupt = &sigintCallback; +} +#endif + struct GlobalOptions { int optimizationLevel = 1; @@ -76,8 +109,8 @@ static int lua_loadstring(lua_State* L) return 1; lua_pushnil(L); - lua_insert(L, -2); /* put before error message */ - return 2; /* return nil plus error message */ + lua_insert(L, -2); // put before error message + return 2; // return nil plus error message } static int finishrequire(lua_State* L) @@ -535,6 +568,15 @@ static void runRepl() lua_State* L = globalState.get(); setupState(L); + + // setup Ctrl+C handling + replState = L; +#ifdef _WIN32 + SetConsoleCtrlHandler(sigintHandler, TRUE); +#else + signal(SIGINT, sigintHandler); +#endif + luaL_sandboxthread(L); runReplImpl(L); } diff --git a/CLI/ReplEntry.cpp b/CLI/ReplEntry.cpp index 75995e6a..8543e3f7 100644 --- a/CLI/ReplEntry.cpp +++ b/CLI/ReplEntry.cpp @@ -1,8 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Repl.h" - - int main(int argc, char** argv) { return replMain(argc, argv); diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 0cb7e1d9..1d6b18e5 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -37,6 +37,14 @@ // Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected // that Luau users can recompile bytecode from source on Luau version upgrades if necessary. +// # Bytecode version history +// +// Note: due to limitations of the versioning scheme, some bytecode blobs that carry version 2 are using features from version 3. Starting from version 3, version should be sufficient to indicate bytecode compatibility. +// +// Version 1: Baseline version for the open-source release. Supported until 0.521. +// Version 2: Adds Proto::linedefined. Currently supported. +// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported. + // Bytecode opcode, part of the instruction header enum LuauOpcode { @@ -367,6 +375,20 @@ enum LuauOpcode // D: jump offset (-32768..32767) LOP_FORGPREP, + // JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX) + // A: source register 1 + // D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump") + // AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit + LOP_JUMPXEQKNIL, + LOP_JUMPXEQKB, + + // JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX) + // A: source register 1 + // D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump") + // AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit + LOP_JUMPXEQKN, + LOP_JUMPXEQKS, + // Enum entry for number of opcodes, not a valid opcode by itself! LOP__COUNT }; @@ -391,7 +413,7 @@ enum LuauBytecodeTag { // Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled LBC_VERSION_MIN = 2, - LBC_VERSION_MAX = 2, + LBC_VERSION_MAX = 3, LBC_VERSION_TARGET = 2, // Types of constant table entries LBC_CONSTANT_NIL = 0, diff --git a/Common/include/Luau/Common.h b/Common/include/Luau/Common.h index fbb03a9e..f1846ac3 100644 --- a/Common/include/Luau/Common.h +++ b/Common/include/Luau/Common.h @@ -20,12 +20,6 @@ #define LUAU_DEBUGBREAK() __builtin_trap() #endif - - - - - - namespace Luau { @@ -67,16 +61,13 @@ struct FValue const char* name; FValue* next; - FValue(const char* name, T def, bool dynamic, void (*reg)(const char*, T*, bool) = nullptr) + FValue(const char* name, T def, bool dynamic) : value(def) , dynamic(dynamic) , name(name) , next(list) { list = this; - - if (reg) - reg(name, &value, dynamic); } operator T() const @@ -98,7 +89,7 @@ FValue* FValue::list = nullptr; #define LUAU_FASTFLAGVARIABLE(flag, def) \ namespace FFlag \ { \ - Luau::FValue flag(#flag, def, false, nullptr); \ + Luau::FValue flag(#flag, def, false); \ } #define LUAU_FASTINT(flag) \ namespace FInt \ @@ -108,7 +99,7 @@ FValue* FValue::list = nullptr; #define LUAU_FASTINTVARIABLE(flag, def) \ namespace FInt \ { \ - Luau::FValue flag(#flag, def, false, nullptr); \ + Luau::FValue flag(#flag, def, false); \ } #define LUAU_DYNAMIC_FASTFLAG(flag) \ @@ -119,7 +110,7 @@ FValue* FValue::list = nullptr; #define LUAU_DYNAMIC_FASTFLAGVARIABLE(flag, def) \ namespace DFFlag \ { \ - Luau::FValue flag(#flag, def, true, nullptr); \ + Luau::FValue flag(#flag, def, true); \ } #define LUAU_DYNAMIC_FASTINT(flag) \ namespace DFInt \ @@ -129,5 +120,5 @@ FValue* FValue::list = nullptr; #define LUAU_DYNAMIC_FASTINTVARIABLE(flag, def) \ namespace DFInt \ { \ - Luau::FValue flag(#flag, def, true, nullptr); \ + Luau::FValue flag(#flag, def, true); \ } diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index 3525259d..71e76ffe 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -10,17 +10,16 @@ inline bool isFlagExperimental(const char* flag) { // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, // or critical bugs that are found after the code has been submitted. - static const char* kList[] = - { + static const char* kList[] = { "LuauLowerBoundsCalculation", nullptr, // makes sure we always have at least one entry }; - for (const char* item: kList) + for (const char* item : kList) if (item && strcmp(item, flag) == 0) return true; return false; } -} +} // namespace Luau diff --git a/Compiler/include/luacode.h b/Compiler/include/luacode.h index e235a2e7..5f69f69e 100644 --- a/Compiler/include/luacode.h +++ b/Compiler/include/luacode.h @@ -3,7 +3,7 @@ #include -/* Can be used to reconfigure visibility/exports for public APIs */ +// Can be used to reconfigure visibility/exports for public APIs #ifndef LUACODE_API #define LUACODE_API extern #endif @@ -35,5 +35,5 @@ struct lua_CompileOptions const char** mutableGlobals; }; -/* compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy */ +// compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy LUACODE_API char* luau_compile(const char* source, size_t size, lua_CompileOptions* options, size_t* outsize); diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index 3650c147..26933730 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -4,8 +4,6 @@ #include "Luau/Bytecode.h" #include "Luau/Compiler.h" -LUAU_FASTFLAGVARIABLE(LuauCompileRawlen, false) - namespace Luau { namespace Compile @@ -57,7 +55,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op return LBF_RAWGET; if (builtin.isGlobal("rawequal")) return LBF_RAWEQUAL; - if (FFlag::LuauCompileRawlen && builtin.isGlobal("rawlen")) + if (builtin.isGlobal("rawlen")) return LBF_RAWLEN; if (builtin.isGlobal("unpack")) diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 64327bd0..46ab2648 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -6,6 +6,8 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false) + namespace Luau { @@ -77,6 +79,10 @@ static int getOpLength(LuauOpcode op) case LOP_JUMPIFNOTEQK: case LOP_FASTCALL2: case LOP_FASTCALL2K: + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + case LOP_JUMPXEQKN: + case LOP_JUMPXEQKS: return 2; default: @@ -108,6 +114,10 @@ inline bool isJumpD(LuauOpcode op) case LOP_JUMPBACK: case LOP_JUMPIFEQK: case LOP_JUMPIFNOTEQK: + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + case LOP_JUMPXEQKN: + case LOP_JUMPXEQKS: return true; default: @@ -1069,6 +1079,9 @@ std::string BytecodeBuilder::getError(const std::string& message) uint8_t BytecodeBuilder::getVersion() { + if (FFlag::LuauCompileBytecodeV3) + return 3; + // This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags return LBC_VERSION_TARGET; } @@ -1246,6 +1259,24 @@ void BytecodeBuilder::validate() const VJUMP(LUAU_INSN_D(insn)); break; + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + VREG(LUAU_INSN_A(insn)); + VJUMP(LUAU_INSN_D(insn)); + break; + + case LOP_JUMPXEQKN: + VREG(LUAU_INSN_A(insn)); + VCONST(insns[i + 1] & 0xffffff, Number); + VJUMP(LUAU_INSN_D(insn)); + break; + + case LOP_JUMPXEQKS: + VREG(LUAU_INSN_A(insn)); + VCONST(insns[i + 1] & 0xffffff, String); + VJUMP(LUAU_INSN_D(insn)); + break; + case LOP_ADD: case LOP_SUB: case LOP_MUL: @@ -1779,6 +1810,26 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel); break; + case LOP_JUMPXEQKNIL: + formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : ""); + code++; + break; + + case LOP_JUMPXEQKB: + formatAppend(result, "JUMPXEQKB R%d %d L%d%s\n", LUAU_INSN_A(insn), *code & 1, targetLabel, *code >> 31 ? " NOT" : ""); + code++; + break; + + case LOP_JUMPXEQKN: + formatAppend(result, "JUMPXEQKN R%d K%d L%d%s\n", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : ""); + code++; + break; + + case LOP_JUMPXEQKS: + formatAppend(result, "JUMPXEQKS R%d K%d L%d%s\n", LUAU_INSN_A(insn), *code & 0xffffff, targetLabel, *code >> 31 ? " NOT" : ""); + code++; + break; + default: LUAU_ASSERT(!"Unsupported opcode"); } diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 53f51b4a..03779ffd 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,9 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTFLAGVARIABLE(LuauCompileNoIpairs, false) -LUAU_FASTFLAGVARIABLE(LuauCompileFoldBuiltins, false) -LUAU_FASTFLAGVARIABLE(LuauCompileBetterMultret, false) LUAU_FASTFLAGVARIABLE(LuauCompileFreeReassign, false) +LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false) LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport) @@ -279,9 +278,6 @@ struct Compiler // returns true if node can return multiple values; may conservatively return true even if expr is known to return just a single value bool isExprMultRet(AstExpr* node) { - if (!FFlag::LuauCompileBetterMultret) - return node->is() || node->is(); - AstExprCall* expr = node->as(); if (!expr) return node->is(); @@ -313,27 +309,10 @@ struct Compiler if (AstExprCall* expr = node->as()) { // Optimization: convert multret calls that always return one value to fixedret calls; this facilitates inlining/constant folding - if (options.optimizationLevel >= 2) + if (options.optimizationLevel >= 2 && !isExprMultRet(node)) { - if (FFlag::LuauCompileBetterMultret) - { - if (!isExprMultRet(node)) - { - compileExprTemp(node, target); - return false; - } - } - else - { - AstExprFunction* func = getFunctionExpr(expr->func); - Function* fi = func ? functions.find(func) : nullptr; - - if (fi && fi->returnsOne) - { - compileExprTemp(node, target); - return false; - } - } + compileExprTemp(node, target); + return false; } // We temporarily swap out regTop to have targetTop work correctly... @@ -1033,9 +1012,8 @@ struct Compiler size_t compileCompareJump(AstExprBinary* expr, bool not_ = false) { RegScope rs(this); - LuauOpcode opc = getJumpOpCompare(expr->op, not_); - bool isEq = (opc == LOP_JUMPIFEQ || opc == LOP_JUMPIFNOTEQ); + bool isEq = (expr->op == AstExprBinary::CompareEq || expr->op == AstExprBinary::CompareNe); AstExpr* left = expr->left; AstExpr* right = expr->right; @@ -1047,36 +1025,112 @@ struct Compiler std::swap(left, right); } - uint8_t rl = compileExprAuto(left, rs); - int32_t rr = -1; - - if (isEq && operandIsConstant) + if (FFlag::LuauCompileXEQ) { - if (opc == LOP_JUMPIFEQ) - opc = LOP_JUMPIFEQK; - else if (opc == LOP_JUMPIFNOTEQ) - opc = LOP_JUMPIFNOTEQK; + uint8_t rl = compileExprAuto(left, rs); - rr = getConstantIndex(right); - LUAU_ASSERT(rr >= 0); - } - else - rr = compileExprAuto(right, rs); + if (isEq && operandIsConstant) + { + const Constant* cv = constants.find(right); + LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown); - size_t jumpLabel = bytecode.emitLabel(); + LuauOpcode opc = LOP_NOP; + int32_t cid = -1; + uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0; - if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) - { - bytecode.emitAD(opc, uint8_t(rr), 0); - bytecode.emitAux(rl); + switch (cv->type) + { + case Constant::Type_Nil: + opc = LOP_JUMPXEQKNIL; + cid = 0; + break; + + case Constant::Type_Boolean: + opc = LOP_JUMPXEQKB; + cid = cv->valueBoolean; + break; + + case Constant::Type_Number: + opc = LOP_JUMPXEQKN; + cid = getConstantIndex(right); + break; + + case Constant::Type_String: + opc = LOP_JUMPXEQKS; + cid = getConstantIndex(right); + break; + + default: + LUAU_ASSERT(!"Unexpected constant type"); + } + + if (cid < 0) + CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); + + size_t jumpLabel = bytecode.emitLabel(); + + bytecode.emitAD(opc, rl, 0); + bytecode.emitAux(cid | flip); + + return jumpLabel; + } + else + { + LuauOpcode opc = getJumpOpCompare(expr->op, not_); + + uint8_t rr = compileExprAuto(right, rs); + + size_t jumpLabel = bytecode.emitLabel(); + + if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) + { + bytecode.emitAD(opc, rr, 0); + bytecode.emitAux(rl); + } + else + { + bytecode.emitAD(opc, rl, 0); + bytecode.emitAux(rr); + } + + return jumpLabel; + } } else { - bytecode.emitAD(opc, rl, 0); - bytecode.emitAux(rr); - } + LuauOpcode opc = getJumpOpCompare(expr->op, not_); - return jumpLabel; + uint8_t rl = compileExprAuto(left, rs); + int32_t rr = -1; + + if (isEq && operandIsConstant) + { + if (opc == LOP_JUMPIFEQ) + opc = LOP_JUMPIFEQK; + else if (opc == LOP_JUMPIFNOTEQ) + opc = LOP_JUMPIFNOTEQK; + + rr = getConstantIndex(right); + LUAU_ASSERT(rr >= 0); + } + else + rr = compileExprAuto(right, rs); + + size_t jumpLabel = bytecode.emitLabel(); + + if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) + { + bytecode.emitAD(opc, uint8_t(rr), 0); + bytecode.emitAux(rl); + } + else + { + bytecode.emitAD(opc, rl, 0); + bytecode.emitAux(rr); + } + + return jumpLabel; + } } int32_t getConstantNumber(AstExpr* node) @@ -3514,30 +3568,7 @@ struct Compiler bool visit(AstStatReturn* stat) override { - if (FFlag::LuauCompileBetterMultret) - { - returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]); - } - else if (stat->list.size == 1) - { - AstExpr* value = stat->list.data[0]; - - if (AstExprCall* expr = value->as()) - { - AstExprFunction* func = self->getFunctionExpr(expr->func); - Function* fi = func ? self->functions.find(func) : nullptr; - - returnsOne &= fi && fi->returnsOne; - } - else if (value->is()) - { - returnsOne = false; - } - } - else - { - returnsOne = false; - } + returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]); return false; } @@ -3679,7 +3710,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c trackValues(compiler.globals, compiler.variables, root); // builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime - if (options.optimizationLevel >= 2 && FFlag::LuauCompileFoldBuiltins) + if (options.optimizationLevel >= 2) compiler.builtinsFold = &compiler.builtins; if (options.optimizationLevel >= 1) diff --git a/Compiler/src/CostModel.cpp b/Compiler/src/CostModel.cpp index 6382de68..ffc1cb1f 100644 --- a/Compiler/src/CostModel.cpp +++ b/Compiler/src/CostModel.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCompileModelBuiltins, false) - namespace Luau { namespace Compile @@ -155,7 +153,7 @@ struct CostVisitor : AstVisitor { // builtin cost modeling is different from regular calls because we use FASTCALL to compile these // thus we use a cheaper baseline, don't account for function, and assume constant/local copy is free - bool builtin = FFlag::LuauCompileModelBuiltins && builtins.find(expr) != nullptr; + bool builtin = builtins.find(expr) != nullptr; bool builtinShort = builtin && expr->args.size <= 2; // FASTCALL1/2 Cost cost = builtin ? 2 : 3; diff --git a/Makefile b/Makefile index 33a2e5e1..dd32d237 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ ifneq ($(opt),) endif OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS) +EXECUTABLE_ALIASES = luau luau-analyze luau-tests # common flags CXXFLAGS=-g -Wall @@ -121,14 +122,14 @@ fuzz-proto fuzz-prototest: LDFLAGS+=build/libprotobuf-mutator/src/libfuzzer/libp all: $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(TESTS_TARGET) aliases -aliases: luau luau-analyze +aliases: $(EXECUTABLE_ALIASES) test: $(TESTS_TARGET) $(TESTS_TARGET) $(TESTS_ARGS) clean: rm -rf $(BUILD) - rm -rf luau luau-analyze + rm -rf $(EXECUTABLE_ALIASES) coverage: $(TESTS_TARGET) $(TESTS_TARGET) --fflags=true @@ -154,6 +155,9 @@ luau: $(REPL_CLI_TARGET) luau-analyze: $(ANALYZE_CLI_TARGET) ln -fs $^ $@ +luau-tests: $(TESTS_TARGET) + ln -fs $^ $@ + # executable targets $(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) $(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) diff --git a/Sources.cmake b/Sources.cmake index a4563019..9a6019a9 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -66,6 +66,8 @@ target_sources(Luau.CodeGen PRIVATE # Luau.Analysis Sources target_sources(Luau.Analysis PRIVATE + Analysis/include/Luau/ApplyTypeFunction.h + Analysis/include/Luau/AstJsonEncoder.h Analysis/include/Luau/AstQuery.h Analysis/include/Luau/Autocomplete.h Analysis/include/Luau/BuiltinDefinitions.h @@ -81,7 +83,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Frontend.h Analysis/include/Luau/Instantiation.h Analysis/include/Luau/IostreamHelpers.h - Analysis/include/Luau/JsonEncoder.h + Analysis/include/Luau/JsonEmitter.h Analysis/include/Luau/Linter.h Analysis/include/Luau/LValue.h Analysis/include/Luau/Module.h @@ -113,6 +115,8 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Variant.h Analysis/include/Luau/VisitTypeVar.h + Analysis/src/ApplyTypeFunction.cpp + Analysis/src/AstJsonEncoder.cpp Analysis/src/AstQuery.cpp Analysis/src/Autocomplete.cpp Analysis/src/BuiltinDefinitions.cpp @@ -126,7 +130,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Frontend.cpp Analysis/src/Instantiation.cpp Analysis/src/IostreamHelpers.cpp - Analysis/src/JsonEncoder.cpp + Analysis/src/JsonEmitter.cpp Analysis/src/Linter.cpp Analysis/src/LValue.cpp Analysis/src/Module.cpp @@ -255,6 +259,7 @@ if(TARGET Luau.UnitTest) tests/ScopedFlags.h tests/Fixture.cpp tests/AssemblyBuilderX64.test.cpp + tests/AstJsonEncoder.test.cpp tests/AstQuery.test.cpp tests/AstVisitor.test.cpp tests/Autocomplete.test.cpp @@ -266,7 +271,8 @@ if(TARGET Luau.UnitTest) tests/CostModel.test.cpp tests/Error.test.cpp tests/Frontend.test.cpp - tests/JsonEncoder.test.cpp + tests/JsonEmitter.test.cpp + tests/Lexer.test.cpp tests/Linter.test.cpp tests/LValue.test.cpp tests/Module.test.cpp diff --git a/VM/include/lua.h b/VM/include/lua.h index 187dd5c2..f986c2b3 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -11,7 +11,7 @@ -/* option for multiple returns in `lua_pcall' and `lua_call' */ +// option for multiple returns in `lua_pcall' and `lua_call' #define LUA_MULTRET (-1) /* @@ -23,7 +23,7 @@ #define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i)) #define lua_ispseudo(i) ((i) <= LUA_REGISTRYINDEX) -/* thread status; 0 is OK */ +// thread status; 0 is OK enum lua_Status { LUA_OK = 0, @@ -32,7 +32,7 @@ enum lua_Status LUA_ERRSYNTAX, LUA_ERRMEM, LUA_ERRERR, - LUA_BREAK, /* yielded for a debug breakpoint */ + LUA_BREAK, // yielded for a debug breakpoint }; typedef struct lua_State lua_State; @@ -46,7 +46,7 @@ typedef int (*lua_Continuation)(lua_State* L, int status); typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize); -/* non-return type */ +// non-return type #define l_noret void LUA_NORETURN /* @@ -61,15 +61,15 @@ typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize); // clang-format off enum lua_Type { - LUA_TNIL = 0, /* must be 0 due to lua_isnoneornil */ - LUA_TBOOLEAN = 1, /* must be 1 due to l_isfalse */ + LUA_TNIL = 0, // must be 0 due to lua_isnoneornil + LUA_TBOOLEAN = 1, // must be 1 due to l_isfalse LUA_TLIGHTUSERDATA, LUA_TNUMBER, LUA_TVECTOR, - LUA_TSTRING, /* all types above this must be value types, all types below this must be GC types - see iscollectable */ + LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable LUA_TTABLE, @@ -77,23 +77,23 @@ enum lua_Type LUA_TUSERDATA, LUA_TTHREAD, - /* values below this line are used in GCObject tags but may never show up in TValue type tags */ + // values below this line are used in GCObject tags but may never show up in TValue type tags LUA_TPROTO, LUA_TUPVAL, LUA_TDEADKEY, - /* the count of TValue type tags */ + // the count of TValue type tags LUA_T_COUNT = LUA_TPROTO }; // clang-format on -/* type of numbers in Luau */ +// type of numbers in Luau typedef double lua_Number; -/* type for integer functions */ +// type for integer functions typedef int lua_Integer; -/* unsigned integer type */ +// unsigned integer type typedef unsigned lua_Unsigned; /* @@ -117,7 +117,7 @@ LUA_API void lua_remove(lua_State* L, int idx); LUA_API void lua_insert(lua_State* L, int idx); LUA_API void lua_replace(lua_State* L, int idx); LUA_API int lua_checkstack(lua_State* L, int sz); -LUA_API void lua_rawcheckstack(lua_State* L, int sz); /* allows for unlimited stack frames */ +LUA_API void lua_rawcheckstack(lua_State* L, int sz); // allows for unlimited stack frames LUA_API void lua_xmove(lua_State* from, lua_State* to, int n); LUA_API void lua_xpush(lua_State* from, lua_State* to, int idx); @@ -231,18 +231,18 @@ LUA_API void lua_setthreaddata(lua_State* L, void* data); enum lua_GCOp { - /* stop and resume incremental garbage collection */ + // stop and resume incremental garbage collection LUA_GCSTOP, LUA_GCRESTART, - /* run a full GC cycle; not recommended for latency sensitive applications */ + // run a full GC cycle; not recommended for latency sensitive applications LUA_GCCOLLECT, - /* return the heap size in KB and the remainder in bytes */ + // return the heap size in KB and the remainder in bytes LUA_GCCOUNT, LUA_GCCOUNTB, - /* return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running */ + // return 1 if GC is active (not stopped); note that GC may not be actively collecting even if it's running LUA_GCISRUNNING, /* @@ -359,9 +359,9 @@ LUA_API void lua_unref(lua_State* L, int ref); ** ======================================================================= */ -typedef struct lua_Debug lua_Debug; /* activation record */ +typedef struct lua_Debug lua_Debug; // activation record -/* Functions to be called by the debugger in specific events */ +// Functions to be called by the debugger in specific events typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar); LUA_API int lua_stackdepth(lua_State* L); @@ -379,24 +379,24 @@ typedef void (*lua_Coverage)(void* context, const char* function, int linedefine LUA_API void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback); -/* Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging. */ +// Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging. LUA_API const char* lua_debugtrace(lua_State* L); struct lua_Debug { - const char* name; /* (n) */ - const char* what; /* (s) `Lua', `C', `main', `tail' */ - const char* source; /* (s) */ - int linedefined; /* (s) */ - int currentline; /* (l) */ - unsigned char nupvals; /* (u) number of upvalues */ - unsigned char nparams; /* (a) number of parameters */ - char isvararg; /* (a) */ - char short_src[LUA_IDSIZE]; /* (s) */ - void* userdata; /* only valid in luau_callhook */ + const char* name; // (n) + const char* what; // (s) `Lua', `C', `main', `tail' + const char* source; // (s) + int linedefined; // (s) + int currentline; // (l) + unsigned char nupvals; // (u) number of upvalues + unsigned char nparams; // (a) number of parameters + char isvararg; // (a) + char short_src[LUA_IDSIZE]; // (s) + void* userdata; // only valid in luau_callhook }; -/* }====================================================================== */ +// }====================================================================== /* Callbacks that can be used to reconfigure behavior of the VM dynamically. * These are shared between all coroutines. @@ -405,18 +405,18 @@ struct lua_Debug * can only be changed when the VM is not running any code */ struct lua_Callbacks { - void* userdata; /* arbitrary userdata pointer that is never overwritten by Luau */ + void* userdata; // arbitrary userdata pointer that is never overwritten by Luau - void (*interrupt)(lua_State* L, int gc); /* gets called at safepoints (loop back edges, call/ret, gc) if set */ - void (*panic)(lua_State* L, int errcode); /* gets called when an unprotected error is raised (if longjmp is used) */ + void (*interrupt)(lua_State* L, int gc); // gets called at safepoints (loop back edges, call/ret, gc) if set + void (*panic)(lua_State* L, int errcode); // gets called when an unprotected error is raised (if longjmp is used) - void (*userthread)(lua_State* LP, lua_State* L); /* gets called when L is created (LP == parent) or destroyed (LP == NULL) */ - int16_t (*useratom)(const char* s, size_t l); /* gets called when a string is created; returned atom can be retrieved via tostringatom */ + void (*userthread)(lua_State* LP, lua_State* L); // gets called when L is created (LP == parent) or destroyed (LP == NULL) + int16_t (*useratom)(const char* s, size_t l); // gets called when a string is created; returned atom can be retrieved via tostringatom - void (*debugbreak)(lua_State* L, lua_Debug* ar); /* gets called when BREAK instruction is encountered */ - void (*debugstep)(lua_State* L, lua_Debug* ar); /* gets called after each instruction in single step mode */ - void (*debuginterrupt)(lua_State* L, lua_Debug* ar); /* gets called when thread execution is interrupted by break in another thread */ - void (*debugprotectederror)(lua_State* L); /* gets called when protected call results in an error */ + void (*debugbreak)(lua_State* L, lua_Debug* ar); // gets called when BREAK instruction is encountered + void (*debugstep)(lua_State* L, lua_Debug* ar); // gets called after each instruction in single step mode + void (*debuginterrupt)(lua_State* L, lua_Debug* ar); // gets called when thread execution is interrupted by break in another thread + void (*debugprotectederror)(lua_State* L); // gets called when protected call results in an error }; typedef struct lua_Callbacks lua_Callbacks; diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index b93cbf7c..7b0f4c30 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -33,14 +33,14 @@ #define LUA_NORETURN __attribute__((__noreturn__)) #endif -/* Can be used to reconfigure visibility/exports for public APIs */ +// Can be used to reconfigure visibility/exports for public APIs #ifndef LUA_API #define LUA_API extern #endif #define LUALIB_API LUA_API -/* Can be used to reconfigure visibility for internal APIs */ +// Can be used to reconfigure visibility for internal APIs #if defined(__GNUC__) #define LUAI_FUNC __attribute__((visibility("hidden"))) extern #define LUAI_DATA LUAI_FUNC @@ -49,67 +49,67 @@ #define LUAI_DATA extern #endif -/* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */ +// Can be used to reconfigure internal error handling to use longjmp instead of C++ EH #ifndef LUA_USE_LONGJMP #define LUA_USE_LONGJMP 0 #endif -/* LUA_IDSIZE gives the maximum size for the description of the source */ +// LUA_IDSIZE gives the maximum size for the description of the source #ifndef LUA_IDSIZE #define LUA_IDSIZE 256 #endif -/* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */ +// LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function #ifndef LUA_MINSTACK #define LUA_MINSTACK 20 #endif -/* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */ +// LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use #ifndef LUAI_MAXCSTACK #define LUAI_MAXCSTACK 8000 #endif -/* LUAI_MAXCALLS limits the number of nested calls */ +// LUAI_MAXCALLS limits the number of nested calls #ifndef LUAI_MAXCALLS #define LUAI_MAXCALLS 20000 #endif -/* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */ +// LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size #ifndef LUAI_MAXCCALLS #define LUAI_MAXCCALLS 200 #endif -/* buffer size used for on-stack string operations; this limit depends on native stack size */ +// buffer size used for on-stack string operations; this limit depends on native stack size #ifndef LUA_BUFFERSIZE #define LUA_BUFFERSIZE 512 #endif -/* number of valid Lua userdata tags */ +// number of valid Lua userdata tags #ifndef LUA_UTAG_LIMIT #define LUA_UTAG_LIMIT 128 #endif -/* upper bound for number of size classes used by page allocator */ +// upper bound for number of size classes used by page allocator #ifndef LUA_SIZECLASSES #define LUA_SIZECLASSES 32 #endif -/* available number of separate memory categories */ +// available number of separate memory categories #ifndef LUA_MEMORY_CATEGORIES #define LUA_MEMORY_CATEGORIES 256 #endif -/* minimum size for the string table (must be power of 2) */ +// minimum size for the string table (must be power of 2) #ifndef LUA_MINSTRTABSIZE #define LUA_MINSTRTABSIZE 32 #endif -/* maximum number of captures supported by pattern matching */ +// maximum number of captures supported by pattern matching #ifndef LUA_MAXCAPTURES #define LUA_MAXCAPTURES 32 #endif -/* }================================================================== */ +// }================================================================== /* @@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment. @@ -126,6 +126,6 @@ long l; \ } -#define LUA_VECTOR_SIZE 3 /* must be 3 or 4 */ +#define LUA_VECTOR_SIZE 3 // must be 3 or 4 #define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2 diff --git a/VM/include/lualib.h b/VM/include/lualib.h index bebd0a0f..955604de 100644 --- a/VM/include/lualib.h +++ b/VM/include/lualib.h @@ -72,7 +72,7 @@ LUALIB_API const char* luaL_typename(lua_State* L, int idx); #define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n))) -/* generic buffer manipulation */ +// generic buffer manipulation struct luaL_Buffer { @@ -102,7 +102,7 @@ LUALIB_API void luaL_addvalue(luaL_Buffer* B); LUALIB_API void luaL_pushresult(luaL_Buffer* B); LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size); -/* builtin libraries */ +// builtin libraries LUALIB_API int luaopen_base(lua_State* L); #define LUA_COLIBNAME "coroutine" @@ -129,9 +129,9 @@ LUALIB_API int luaopen_math(lua_State* L); #define LUA_DBLIBNAME "debug" LUALIB_API int luaopen_debug(lua_State* L); -/* open all builtin libraries */ +// open all builtin libraries LUALIB_API void luaL_openlibs(lua_State* L); -/* sandbox libraries and globals */ +// sandbox libraries and globals LUALIB_API void luaL_sandbox(lua_State* L); LUALIB_API void luaL_sandboxthread(lua_State* L); diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index e3354ea2..bb994fb4 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -34,8 +34,6 @@ * therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread. */ -LUAU_FASTFLAG(LuauLazyAtoms) - const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; @@ -54,7 +52,6 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" } #define updateatom(L, ts) \ - if (FFlag::LuauLazyAtoms) \ { \ if (ts->atom == ATOM_UNDEF) \ ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \ @@ -62,8 +59,8 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" static Table* getcurrenv(lua_State* L) { - if (L->ci == L->base_ci) /* no enclosing function? */ - return L->gt; /* use global table as environment */ + if (L->ci == L->base_ci) // no enclosing function? + return L->gt; // use global table as environment else return curr_func(L)->env; } @@ -72,7 +69,7 @@ static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx) { api_check(L, lua_ispseudo(idx)); switch (idx) - { /* pseudo-indices */ + { // pseudo-indices case LUA_REGISTRYINDEX: return registry(L); case LUA_ENVIRONINDEX: @@ -132,7 +129,7 @@ int lua_checkstack(lua_State* L, int size) { int res = 1; if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK) - res = 0; /* stack overflow */ + res = 0; // stack overflow else if (size > 0) { luaD_checkstack(L, size); @@ -222,7 +219,7 @@ void lua_settop(lua_State* L, int idx) else { api_check(L, -(idx + 1) <= (L->top - L->base)); - L->top += idx + 1; /* `subtract' index (index is negative) */ + L->top += idx + 1; // `subtract' index (index is negative) } return; } @@ -270,7 +267,7 @@ void lua_replace(lua_State* L, int idx) else { setobj(L, o, L->top - 1); - if (idx < LUA_GLOBALSINDEX) /* function upvalue? */ + if (idx < LUA_GLOBALSINDEX) // function upvalue? luaC_barrier(L, curr_func(L), L->top - 1); } L->top--; @@ -432,13 +429,13 @@ const char* lua_tolstring(lua_State* L, int idx, size_t* len) { luaC_checkthreadsleep(L); if (!luaV_tostring(L, o)) - { /* conversion failed? */ + { // conversion failed? if (len != NULL) *len = 0; return NULL; } luaC_checkGC(L); - o = index2addr(L, idx); /* previous call may reallocate the stack */ + o = index2addr(L, idx); // previous call may reallocate the stack } if (len != NULL) *len = tsvalue(o)->len; @@ -663,7 +660,7 @@ void lua_pushcclosurek(lua_State* L, lua_CFunction fn, const char* debugname, in void lua_pushboolean(lua_State* L, int b) { - setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ + setbvalue(L->top, (b != 0)); // ensure that true is 1 api_incr_top(L); return; } @@ -832,7 +829,7 @@ void lua_settable(lua_State* L, int idx) StkId t = index2addr(L, idx); api_checkvalidindex(L, t); luaV_settable(L, t, L->top - 2, L->top - 1); - L->top -= 2; /* pop index and value */ + L->top -= 2; // pop index and value return; } @@ -970,7 +967,7 @@ void lua_call(lua_State* L, int nargs, int nresults) ** Execute a protected call. */ struct CallS -{ /* data to `f_call' */ +{ // data to `f_call' StkId func; int nresults; }; @@ -995,7 +992,7 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) func = savestack(L, o); } struct CallS c; - c.func = L->top - (nargs + 1); /* function to be called */ + c.func = L->top - (nargs + 1); // function to be called c.nresults = nresults; int status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); @@ -1047,7 +1044,7 @@ int lua_gc(lua_State* L, int what, int data) } case LUA_GCCOUNT: { - /* GC values are expressed in Kbytes: #bytes/2^10 */ + // GC values are expressed in Kbytes: #bytes/2^10 res = cast_int(g->totalbytes >> 10); break; } @@ -1087,8 +1084,8 @@ int lua_gc(lua_State* L, int what, int data) actualwork += stepsize; if (g->gcstate == GCSpause) - { /* end of cycle? */ - res = 1; /* signal it */ + { // end of cycle? + res = 1; // signal it break; } } @@ -1140,13 +1137,13 @@ int lua_gc(lua_State* L, int what, int data) } case LUA_GCSETSTEPSIZE: { - /* GC values are expressed in Kbytes: #bytes/2^10 */ + // GC values are expressed in Kbytes: #bytes/2^10 res = g->gcstepsize >> 10; g->gcstepsize = data << 10; break; } default: - res = -1; /* invalid option */ + res = -1; // invalid option } return res; } @@ -1172,8 +1169,8 @@ int lua_next(lua_State* L, int idx) { api_incr_top(L); } - else /* no more elements */ - L->top -= 1; /* remove key */ + else // no more elements + L->top -= 1; // remove key return more; } @@ -1188,12 +1185,12 @@ void lua_concat(lua_State* L, int n) L->top -= (n - 1); } else if (n == 0) - { /* push empty string */ + { // push empty string luaC_checkthreadsleep(L); setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); api_incr_top(L); } - /* else n == 1; nothing to do */ + // else n == 1; nothing to do return; } @@ -1280,7 +1277,7 @@ uintptr_t lua_encodepointer(lua_State* L, uintptr_t p) int lua_ref(lua_State* L, int idx) { - api_check(L, idx != LUA_REGISTRYINDEX); /* idx is a stack index for value */ + api_check(L, idx != LUA_REGISTRYINDEX); // idx is a stack index for value int ref = LUA_REFNIL; global_State* g = L->global; StkId p = index2addr(L, idx); @@ -1289,13 +1286,13 @@ int lua_ref(lua_State* L, int idx) Table* reg = hvalue(registry(L)); if (g->registryfree != 0) - { /* reuse existing slot */ + { // reuse existing slot ref = g->registryfree; } else - { /* no free elements */ + { // no free elements ref = luaH_getn(reg); - ref++; /* create new reference */ + ref++; // create new reference } TValue* slot = luaH_setnum(L, reg, ref); @@ -1315,7 +1312,7 @@ void lua_unref(lua_State* L, int ref) global_State* g = L->global; Table* reg = hvalue(registry(L)); TValue* slot = luaH_setnum(L, reg, ref); - setnvalue(slot, g->registryfree); /* NB: no barrier needed because value isn't collectable */ + setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable g->registryfree = ref; return; } diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 72169a86..c42e5ccc 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,7 +11,7 @@ #include -/* convert a stack index to positive */ +// convert a stack index to positive #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) /* @@ -75,7 +75,7 @@ void luaL_where(lua_State* L, int level) lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); return; } - lua_pushliteral(L, ""); /* else, no information available... */ + lua_pushliteral(L, ""); // else, no information available... } l_noret luaL_errorL(lua_State* L, const char* fmt, ...) @@ -89,7 +89,7 @@ l_noret luaL_errorL(lua_State* L, const char* fmt, ...) lua_error(L); } -/* }====================================================== */ +// }====================================================== int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]) { @@ -104,13 +104,13 @@ int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const int luaL_newmetatable(lua_State* L, const char* tname) { - lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ - if (!lua_isnil(L, -1)) /* name already in use? */ - return 0; /* leave previous value on top, but return 0 */ + lua_getfield(L, LUA_REGISTRYINDEX, tname); // get registry.name + if (!lua_isnil(L, -1)) // name already in use? + return 0; // leave previous value on top, but return 0 lua_pop(L, 1); - lua_newtable(L); /* create metatable */ + lua_newtable(L); // create metatable lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ + lua_setfield(L, LUA_REGISTRYINDEX, tname); // registry.name = metatable return 1; } @@ -118,18 +118,18 @@ void* luaL_checkudata(lua_State* L, int ud, const char* tname) { void* p = lua_touserdata(L, ud); if (p != NULL) - { /* value is a userdata? */ + { // value is a userdata? if (lua_getmetatable(L, ud)) - { /* does it have a metatable? */ - lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + { // does it have a metatable? + lua_getfield(L, LUA_REGISTRYINDEX, tname); // get correct metatable if (lua_rawequal(L, -1, -2)) - { /* does it have the correct mt? */ - lua_pop(L, 2); /* remove both metatables */ + { // does it have the correct mt? + lua_pop(L, 2); // remove both metatables return p; } } } - luaL_typeerrorL(L, ud, tname); /* else error */ + luaL_typeerrorL(L, ud, tname); // else error } void luaL_checkstack(lua_State* L, int space, const char* mes) @@ -243,18 +243,18 @@ const float* luaL_optvector(lua_State* L, int narg, const float* def) int luaL_getmetafield(lua_State* L, int obj, const char* event) { - if (!lua_getmetatable(L, obj)) /* no metatable? */ + if (!lua_getmetatable(L, obj)) // no metatable? return 0; lua_pushstring(L, event); lua_rawget(L, -2); if (lua_isnil(L, -1)) { - lua_pop(L, 2); /* remove metatable and metafield */ + lua_pop(L, 2); // remove metatable and metafield return 0; } else { - lua_remove(L, -2); /* remove only metatable */ + lua_remove(L, -2); // remove only metatable return 1; } } @@ -262,7 +262,7 @@ int luaL_getmetafield(lua_State* L, int obj, const char* event) int luaL_callmeta(lua_State* L, int obj, const char* event) { obj = abs_index(L, obj); - if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ + if (!luaL_getmetafield(L, obj, event)) // no metafield? return 0; lua_pushvalue(L, obj); lua_call(L, 1, 1); @@ -282,19 +282,19 @@ void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l) if (libname) { int size = libsize(l); - /* check whether lib already exists */ + // check whether lib already exists luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); - lua_getfield(L, -1, libname); /* get _LOADED[libname] */ + lua_getfield(L, -1, libname); // get _LOADED[libname] if (!lua_istable(L, -1)) - { /* not found? */ - lua_pop(L, 1); /* remove previous result */ - /* try global variable (and create one if it does not exist) */ + { // not found? + lua_pop(L, 1); // remove previous result + // try global variable (and create one if it does not exist) if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL) luaL_error(L, "name conflict for module '%s'", libname); lua_pushvalue(L, -1); - lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */ + lua_setfield(L, -3, libname); // _LOADED[libname] = new table } - lua_remove(L, -2); /* remove _LOADED table */ + lua_remove(L, -2); // remove _LOADED table } for (; l->name; l++) { @@ -315,19 +315,19 @@ const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint) lua_pushlstring(L, fname, e - fname); lua_rawget(L, -2); if (lua_isnil(L, -1)) - { /* no such field? */ - lua_pop(L, 1); /* remove this nil */ - lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ + { // no such field? + lua_pop(L, 1); // remove this nil + lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); // new table for field lua_pushlstring(L, fname, e - fname); lua_pushvalue(L, -2); - lua_settable(L, -4); /* set new table into field */ + lua_settable(L, -4); // set new table into field } else if (!lua_istable(L, -1)) - { /* field has a non-table value? */ - lua_pop(L, 2); /* remove table and value */ - return fname; /* return problematic part of the name */ + { // field has a non-table value? + lua_pop(L, 2); // remove table and value + return fname; // return problematic part of the name } - lua_remove(L, -2); /* remove previous table */ + lua_remove(L, -2); // remove previous table fname = e + 1; } while (*e == '.'); return NULL; @@ -470,11 +470,11 @@ void luaL_pushresultsize(luaL_Buffer* B, size_t size) luaL_pushresult(B); } -/* }====================================================== */ +// }====================================================== const char* luaL_tolstring(lua_State* L, int idx, size_t* len) { - if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */ + if (luaL_callmeta(L, idx, "__tostring")) // is there a metafield? { if (!lua_isstring(L, -1)) luaL_error(L, "'__tostring' must return a string"); diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index 4fc5033e..f4dac61f 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -11,8 +11,6 @@ #include #include -LUAU_FASTFLAG(LuauLenTM) - static void writestring(const char* s, size_t l) { fwrite(s, 1, l, stdout); @@ -20,15 +18,15 @@ static void writestring(const char* s, size_t l) static int luaB_print(lua_State* L) { - int n = lua_gettop(L); /* number of arguments */ + int n = lua_gettop(L); // number of arguments for (int i = 1; i <= n; i++) { size_t l; - const char* s = luaL_tolstring(L, i, &l); /* convert to string using __tostring et al */ + const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al if (i > 1) writestring("\t", 1); writestring(s, l); - lua_pop(L, 1); /* pop result */ + lua_pop(L, 1); // pop result } writestring("\n", 1); return 0; @@ -38,7 +36,7 @@ static int luaB_tonumber(lua_State* L) { int base = luaL_optinteger(L, 2, 10); if (base == 10) - { /* standard conversion */ + { // standard conversion int isnum = 0; double n = lua_tonumberx(L, 1, &isnum); if (isnum) @@ -46,7 +44,7 @@ static int luaB_tonumber(lua_State* L) lua_pushnumber(L, n); return 1; } - luaL_checkany(L, 1); /* error if we don't have any argument */ + luaL_checkany(L, 1); // error if we don't have any argument } else { @@ -56,17 +54,17 @@ static int luaB_tonumber(lua_State* L) unsigned long long n; n = strtoull(s1, &s2, base); if (s1 != s2) - { /* at least one valid digit? */ + { // at least one valid digit? while (isspace((unsigned char)(*s2))) - s2++; /* skip trailing spaces */ + s2++; // skip trailing spaces if (*s2 == '\0') - { /* no invalid trailing characters? */ + { // no invalid trailing characters? lua_pushnumber(L, (double)n); return 1; } } } - lua_pushnil(L); /* else not a number */ + lua_pushnil(L); // else not a number return 1; } @@ -75,7 +73,7 @@ static int luaB_error(lua_State* L) int level = luaL_optinteger(L, 2, 1); lua_settop(L, 1); if (lua_isstring(L, 1) && level > 0) - { /* add extra information? */ + { // add extra information? luaL_where(L, level); lua_pushvalue(L, 1); lua_concat(L, 2); @@ -89,10 +87,10 @@ static int luaB_getmetatable(lua_State* L) if (!lua_getmetatable(L, 1)) { lua_pushnil(L); - return 1; /* no metatable */ + return 1; // no metatable } luaL_getmetafield(L, 1, "__metatable"); - return 1; /* returns either __metatable field (if present) or metatable */ + return 1; // returns either __metatable field (if present) or metatable } static int luaB_setmetatable(lua_State* L) @@ -126,8 +124,8 @@ static void getfunc(lua_State* L, int opt) static int luaB_getfenv(lua_State* L) { getfunc(L, 1); - if (lua_iscfunction(L, -1)) /* is a C function? */ - lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */ + if (lua_iscfunction(L, -1)) // is a C function? + lua_pushvalue(L, LUA_GLOBALSINDEX); // return the thread's global env. else lua_getfenv(L, -1); lua_setsafeenv(L, -1, false); @@ -142,7 +140,7 @@ static int luaB_setfenv(lua_State* L) lua_setsafeenv(L, -1, false); if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0) { - /* change environment of current thread */ + // change environment of current thread lua_pushthread(L); lua_insert(L, -2); lua_setfenv(L, -2); @@ -182,9 +180,6 @@ static int luaB_rawset(lua_State* L) static int luaB_rawlen(lua_State* L) { - if (!FFlag::LuauLenTM) - luaL_error(L, "'rawlen' is not available"); - int tt = lua_type(L, 1); luaL_argcheck(L, tt == LUA_TTABLE || tt == LUA_TSTRING, 1, "table or string expected"); int len = lua_objlen(L, 1); @@ -201,7 +196,7 @@ static int luaB_gcinfo(lua_State* L) static int luaB_type(lua_State* L) { luaL_checkany(L, 1); - /* resulting name doesn't differentiate between userdata types */ + // resulting name doesn't differentiate between userdata types lua_pushstring(L, lua_typename(L, lua_type(L, 1))); return 1; } @@ -209,7 +204,7 @@ static int luaB_type(lua_State* L) static int luaB_typeof(lua_State* L) { luaL_checkany(L, 1); - /* resulting name returns __type if specified unless the input is a newproxy-created userdata */ + // resulting name returns __type if specified unless the input is a newproxy-created userdata lua_pushstring(L, luaL_typename(L, 1)); return 1; } @@ -217,7 +212,7 @@ static int luaB_typeof(lua_State* L) int luaB_next(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + lua_settop(L, 2); // create a 2nd argument if there isn't one if (lua_next(L, 1)) return 2; else @@ -230,9 +225,9 @@ int luaB_next(lua_State* L) static int luaB_pairs(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ - lua_pushvalue(L, 1); /* state, */ - lua_pushnil(L); /* and initial value */ + lua_pushvalue(L, lua_upvalueindex(1)); // return generator, + lua_pushvalue(L, 1); // state, + lua_pushnil(L); // and initial value return 3; } @@ -240,7 +235,7 @@ int luaB_inext(lua_State* L) { int i = luaL_checkinteger(L, 2); luaL_checktype(L, 1, LUA_TTABLE); - i++; /* next value */ + i++; // next value lua_pushinteger(L, i); lua_rawgeti(L, 1, i); return (lua_isnil(L, -1)) ? 0 : 2; @@ -249,9 +244,9 @@ int luaB_inext(lua_State* L) static int luaB_ipairs(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ - lua_pushvalue(L, 1); /* state, */ - lua_pushinteger(L, 0); /* and initial value */ + lua_pushvalue(L, lua_upvalueindex(1)); // return generator, + lua_pushvalue(L, 1); // state, + lua_pushinteger(L, 0); // and initial value return 3; } @@ -340,12 +335,12 @@ static int luaB_xpcally(lua_State* L) { luaL_checktype(L, 2, LUA_TFUNCTION); - /* swap function & error function */ + // swap function & error function lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_replace(L, 1); lua_replace(L, 2); - /* at this point the stack looks like err, f, args */ + // at this point the stack looks like err, f, args // any errors from this point on are handled by continuation L->ci->flags |= LUA_CALLINFO_HANDLE; @@ -386,7 +381,7 @@ static int luaB_xpcallcont(lua_State* L, int status) lua_rawcheckstack(L, 1); lua_pushboolean(L, true); lua_replace(L, 1); // replace error function with status - return lua_gettop(L); /* return status + all results */ + return lua_gettop(L); // return status + all results } else { @@ -462,16 +457,16 @@ static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFuncti int luaopen_base(lua_State* L) { - /* set global _G */ + // set global _G lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setglobal(L, "_G"); - /* open lib into global table */ + // open lib into global table luaL_register(L, "_G", base_funcs); lua_pushliteral(L, "Luau"); - lua_setglobal(L, "_VERSION"); /* set global _VERSION */ + lua_setglobal(L, "_VERSION"); // set global _VERSION - /* `ipairs' and `pairs' need auxiliary functions as upvalues */ + // `ipairs' and `pairs' need auxiliary functions as upvalues auxopen(L, "ipairs", luaB_ipairs, luaB_inext); auxopen(L, "pairs", luaB_pairs, luaB_next); diff --git a/VM/src/lbitlib.cpp b/VM/src/lbitlib.cpp index 093400f2..47445b80 100644 --- a/VM/src/lbitlib.cpp +++ b/VM/src/lbitlib.cpp @@ -8,10 +8,10 @@ #define ALLONES ~0u #define NBITS int(8 * sizeof(unsigned)) -/* macro to trim extra bits */ +// macro to trim extra bits #define trim(x) ((x)&ALLONES) -/* builds a number with 'n' ones (1 <= n <= NBITS) */ +// builds a number with 'n' ones (1 <= n <= NBITS) #define mask(n) (~((ALLONES << 1) << ((n)-1))) typedef unsigned b_uint; @@ -69,7 +69,7 @@ static int b_not(lua_State* L) static int b_shift(lua_State* L, b_uint r, int i) { if (i < 0) - { /* shift right? */ + { // shift right? i = -i; r = trim(r); if (i >= NBITS) @@ -78,7 +78,7 @@ static int b_shift(lua_State* L, b_uint r, int i) r >>= i; } else - { /* shift left */ + { // shift left if (i >= NBITS) r = 0; else @@ -106,11 +106,11 @@ static int b_arshift(lua_State* L) if (i < 0 || !(r & ((b_uint)1 << (NBITS - 1)))) return b_shift(L, r, -i); else - { /* arithmetic shift for 'negative' number */ + { // arithmetic shift for 'negative' number if (i >= NBITS) r = ALLONES; else - r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */ + r = trim((r >> i) | ~(~(b_uint)0 >> i)); // add signal bit lua_pushunsigned(L, r); return 1; } @@ -119,9 +119,9 @@ static int b_arshift(lua_State* L) static int b_rot(lua_State* L, int i) { b_uint r = luaL_checkunsigned(L, 1); - i &= (NBITS - 1); /* i = i % NBITS */ + i &= (NBITS - 1); // i = i % NBITS r = trim(r); - if (i != 0) /* avoid undefined shift of NBITS when i == 0 */ + if (i != 0) // avoid undefined shift of NBITS when i == 0 r = (r << i) | (r >> (NBITS - i)); lua_pushunsigned(L, trim(r)); return 1; @@ -172,7 +172,7 @@ static int b_replace(lua_State* L) b_uint v = luaL_checkunsigned(L, 2); int f = fieldargs(L, 3, &w); int m = mask(w); - v &= m; /* erase bits outside given width */ + v &= m; // erase bits outside given width r = (r & ~(m << f)) | (v << f); lua_pushunsigned(L, r); return 1; diff --git a/VM/src/lcommon.h b/VM/src/lcommon.h index ac79cd97..c9d95c77 100644 --- a/VM/src/lcommon.h +++ b/VM/src/lcommon.h @@ -11,7 +11,7 @@ typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; -/* internal assertions for in-house debugging */ +// internal assertions for in-house debugging #define check_exp(c, e) (LUAU_ASSERT(c), (e)) #define api_check(l, e) LUAU_ASSERT(e) diff --git a/VM/src/lcorolib.cpp b/VM/src/lcorolib.cpp index 7592a14c..7b967e34 100644 --- a/VM/src/lcorolib.cpp +++ b/VM/src/lcorolib.cpp @@ -5,9 +5,9 @@ #include "lstate.h" #include "lvm.h" -#define CO_RUN 0 /* running */ -#define CO_SUS 1 /* suspended */ -#define CO_NOR 2 /* 'normal' (it resumed another coroutine) */ +#define CO_RUN 0 // running +#define CO_SUS 1 // suspended +#define CO_NOR 2 // 'normal' (it resumed another coroutine) #define CO_DEAD 3 #define CO_STATUS_ERROR -1 @@ -23,13 +23,13 @@ static int auxstatus(lua_State* L, lua_State* co) return CO_SUS; if (co->status == LUA_BREAK) return CO_NOR; - if (co->status != 0) /* some error occurred */ + if (co->status != 0) // some error occurred return CO_DEAD; - if (co->ci != co->base_ci) /* does it have frames? */ + if (co->ci != co->base_ci) // does it have frames? return CO_NOR; if (co->top == co->base) return CO_DEAD; - return CO_SUS; /* initial state */ + return CO_SUS; // initial state } static int costatus(lua_State* L) @@ -68,10 +68,10 @@ static int auxresume(lua_State* L, lua_State* co, int narg) int nres = cast_int(co->top - co->base); if (nres) { - /* +1 accounts for true/false status in resumefinish */ + // +1 accounts for true/false status in resumefinish if (nres + 1 > LUA_MINSTACK && !lua_checkstack(L, nres + 1)) luaL_error(L, "too many results to resume"); - lua_xmove(co, L, nres); /* move yielded values */ + lua_xmove(co, L, nres); // move yielded values } return nres; } @@ -81,7 +81,7 @@ static int auxresume(lua_State* L, lua_State* co, int narg) } else { - lua_xmove(co, L, 1); /* move error message */ + lua_xmove(co, L, 1); // move error message return CO_STATUS_ERROR; } } @@ -102,13 +102,13 @@ static int auxresumecont(lua_State* L, lua_State* co) int nres = cast_int(co->top - co->base); if (!lua_checkstack(L, nres + 1)) luaL_error(L, "too many results to resume"); - lua_xmove(co, L, nres); /* move yielded values */ + lua_xmove(co, L, nres); // move yielded values return nres; } else { lua_rawcheckstack(L, 2); - lua_xmove(co, L, 1); /* move error message */ + lua_xmove(co, L, 1); // move error message return CO_STATUS_ERROR; } } @@ -119,13 +119,13 @@ static int coresumefinish(lua_State* L, int r) { lua_pushboolean(L, 0); lua_insert(L, -2); - return 2; /* return false + error message */ + return 2; // return false + error message } else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); - return r + 1; /* return true + `resume' returns */ + return r + 1; // return true + `resume' returns } } @@ -161,12 +161,12 @@ static int auxwrapfinish(lua_State* L, int r) if (r < 0) { if (lua_isstring(L, -1)) - { /* error object is a string? */ - luaL_where(L, 1); /* add extra info */ + { // error object is a string? + luaL_where(L, 1); // add extra info lua_insert(L, -2); lua_concat(L, 2); } - lua_error(L); /* propagate error */ + lua_error(L); // propagate error } return r; } @@ -221,7 +221,7 @@ static int coyield(lua_State* L) static int corunning(lua_State* L) { if (lua_pushthread(L)) - lua_pushnil(L); /* main thread is not a coroutine */ + lua_pushnil(L); // main thread is not a coroutine return 1; } @@ -250,7 +250,7 @@ static int coclose(lua_State* L) { lua_pushboolean(L, false); if (lua_gettop(co)) - lua_xmove(co, L, 1); /* move error message */ + lua_xmove(co, L, 1); // move error message lua_resetthread(co); return 2; } diff --git a/VM/src/ldblib.cpp b/VM/src/ldblib.cpp index 93d8703a..ece4f551 100644 --- a/VM/src/ldblib.cpp +++ b/VM/src/ldblib.cpp @@ -82,9 +82,9 @@ static int db_info(lua_State* L) case 'f': if (L1 == L) - lua_pushvalue(L, -1 - results); /* function is right before results */ + lua_pushvalue(L, -1 - results); // function is right before results else - lua_xmove(L1, L, 1); /* function is at top of L1 */ + lua_xmove(L1, L, 1); // function is at top of L1 results++; break; @@ -130,15 +130,14 @@ static int db_traceback(lua_State* L) if (ar.currentline > 0) { - char line[32]; -#ifdef _MSC_VER - _itoa(ar.currentline, line, 10); // 5x faster than sprintf -#else - sprintf(line, "%d", ar.currentline); -#endif + char line[32]; // manual conversion for performance + char* lineend = line + sizeof(line); + char* lineptr = lineend; + for (unsigned int r = ar.currentline; r > 0; r /= 10) + *--lineptr = '0' + (r % 10); luaL_addchar(&buf, ':'); - luaL_addstring(&buf, line); + luaL_addlstring(&buf, lineptr, lineend - lineptr); } if (ar.name) diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 29015565..c44ccbed 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -86,7 +86,7 @@ const char* lua_setlocal(lua_State* L, int level, int n) const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL; if (var) setobjs2s(L, ci->base + var->reg, L->top - 1); - L->top--; /* pop value */ + L->top--; // pop value const char* name = var ? getstr(var->varname) : NULL; return name; } @@ -269,6 +269,13 @@ l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2) luaG_runerror(L, "attempt to index %s with %s", t1, t2); } +l_noret luaG_methoderror(lua_State* L, const TValue* p1, const TString* p2) +{ + const char* t1 = luaT_objtypename(L, p1); + + luaG_runerror(L, "attempt to call missing method '%s' of %s", getstr(p2), t1); +} + l_noret luaG_readonlyerror(lua_State* L) { luaG_runerror(L, "attempt to modify a readonly table"); @@ -279,7 +286,7 @@ static void pusherror(lua_State* L, const char* msg) CallInfo* ci = L->ci; if (isLua(ci)) { - char buff[LUA_IDSIZE]; /* add file:line information */ + char buff[LUA_IDSIZE]; // add file:line information luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE); int line = currentline(L, ci); luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); @@ -529,7 +536,7 @@ const char* lua_debugtrace(lua_State* L) if (ar.currentline > 0) { char line[32]; - sprintf(line, ":%d", ar.currentline); + snprintf(line, sizeof(line), ":%d", ar.currentline); offset = append(buf, sizeof(buf), offset, line); } @@ -545,7 +552,7 @@ const char* lua_debugtrace(lua_State* L) if (depth > limit1 + limit2 && level == limit1 - 1) { char skip[32]; - sprintf(skip, "... (+%d frames)\n", int(depth - limit1 - limit2)); + snprintf(skip, sizeof(skip), "... (+%d frames)\n", int(depth - limit1 - limit2)); offset = append(buf, sizeof(buf), offset, skip); diff --git a/VM/src/ldebug.h b/VM/src/ldebug.h index 8e03db36..a93e412f 100644 --- a/VM/src/ldebug.h +++ b/VM/src/ldebug.h @@ -19,6 +19,7 @@ LUAI_FUNC l_noret luaG_concaterror(lua_State* L, StkId p1, StkId p2); LUAI_FUNC l_noret luaG_aritherror(lua_State* L, const TValue* p1, const TValue* p2, TMS op); LUAI_FUNC l_noret luaG_ordererror(lua_State* L, const TValue* p1, const TValue* p2, TMS op); LUAI_FUNC l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2); +LUAI_FUNC l_noret luaG_methoderror(lua_State* L, const TValue* p1, const TString* p2); LUAI_FUNC l_noret luaG_readonlyerror(lua_State* L); LUAI_FUNC LUA_PRINTF_ATTR(2, 3) l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...); diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 0642cb6d..6016e41f 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -31,7 +31,7 @@ struct lua_jmpbuf jmp_buf buf; }; -/* use POSIX versions of setjmp/longjmp if possible: they don't save/restore signal mask and are therefore faster */ +// use POSIX versions of setjmp/longjmp if possible: they don't save/restore signal mask and are therefore faster #if defined(__linux__) || defined(__APPLE__) #define LUAU_SETJMP(buf) _setjmp(buf) #define LUAU_LONGJMP(buf, code) _longjmp(buf, code) @@ -153,7 +153,7 @@ l_noret luaD_throw(lua_State* L, int errcode) } #endif -/* }====================================================== */ +// }====================================================== static void correctstack(lua_State* L, TValue* oldstack) { @@ -177,7 +177,7 @@ void luaD_reallocstack(lua_State* L, int newsize) luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat); TValue* newstack = L->stack; for (int i = L->stacksize; i < realsize; i++) - setnilvalue(newstack + i); /* erase new segment */ + setnilvalue(newstack + i); // erase new segment L->stacksize = realsize; L->stack_last = newstack + newsize; correctstack(L, oldstack); @@ -194,7 +194,7 @@ void luaD_reallocCI(lua_State* L, int newsize) void luaD_growstack(lua_State* L, int n) { - if (n <= L->stacksize) /* double size is enough? */ + if (n <= L->stacksize) // double size is enough? luaD_reallocstack(L, 2 * L->stacksize); else luaD_reallocstack(L, L->stacksize + n); @@ -202,11 +202,11 @@ void luaD_growstack(lua_State* L, int n) CallInfo* luaD_growCI(lua_State* L) { - /* allow extra stack space to handle stack overflow in xpcall */ + // allow extra stack space to handle stack overflow in xpcall const int hardlimit = LUAI_MAXCALLS + (LUAI_MAXCALLS >> 3); if (L->size_ci >= hardlimit) - luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ + luaD_throw(L, LUA_ERRERR); // error while handling stack error int request = L->size_ci * 2; luaD_reallocCI(L, L->size_ci >= LUAI_MAXCALLS ? hardlimit : request < LUAI_MAXCALLS ? request : LUAI_MAXCALLS); @@ -219,13 +219,13 @@ CallInfo* luaD_growCI(lua_State* L) void luaD_checkCstack(lua_State* L) { - /* allow extra stack space to handle stack overflow in xpcall */ + // allow extra stack space to handle stack overflow in xpcall const int hardlimit = LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3); if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); else if (L->nCcalls >= hardlimit) - luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ + luaD_throw(L, LUA_ERRERR); // error while handling stack error } /* @@ -240,14 +240,14 @@ void luaD_call(lua_State* L, StkId func, int nResults) luaD_checkCstack(L); if (luau_precall(L, func, nResults) == PCRLUA) - { /* is a Lua function? */ - L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */ + { // is a Lua function? + L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame int oldactive = luaC_threadactive(L); l_setbit(L->stackstate, THREAD_ACTIVEBIT); luaC_checkthreadsleep(L); - luau_execute(L); /* call it */ + luau_execute(L); // call it if (!oldactive) resetbit(L->stackstate, THREAD_ACTIVEBIT); @@ -263,18 +263,18 @@ static void seterrorobj(lua_State* L, int errcode, StkId oldtop) { case LUA_ERRMEM: { - setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); /* can not fail because string is pinned in luaopen */ + setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen break; } case LUA_ERRERR: { - setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); /* can not fail because string is pinned in luaopen */ + setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen break; } case LUA_ERRSYNTAX: case LUA_ERRRUN: { - setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ + setobjs2s(L, oldtop, L->top - 1); // error message on current top break; } } @@ -430,8 +430,8 @@ static void resume_finish(lua_State* L, int status) resetbit(L->stackstate, THREAD_ACTIVEBIT); if (status != 0) - { /* error? */ - L->status = cast_byte(status); /* mark thread as `dead' */ + { // error? + L->status = cast_byte(status); // mark thread as `dead' seterrorobj(L, status, L->top); L->ci->top = L->top; } @@ -503,7 +503,7 @@ int lua_yield(lua_State* L, int nresults) { if (L->nCcalls > L->baseCcalls) luaG_runerror(L, "attempt to yield across metamethod/C-call boundary"); - L->base = L->top - nresults; /* protect stack slots below */ + L->base = L->top - nresults; // protect stack slots below L->status = LUA_YIELD; return -1; } @@ -535,9 +535,9 @@ static void restore_stack_limit(lua_State* L) { LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK); if (L->size_ci > LUAI_MAXCALLS) - { /* there was an overflow? */ + { // there was an overflow? int inuse = cast_int(L->ci - L->base_ci); - if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */ + if (inuse + 1 < LUAI_MAXCALLS) // can `undo' overflow? luaD_reallocCI(L, LUAI_MAXCALLS); } } @@ -576,7 +576,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e } StkId oldtop = restorestack(L, old_top); - luaF_close(L, oldtop); /* close eventual pending closures */ + luaF_close(L, oldtop); // close eventual pending closures seterrorobj(L, status, oldtop); L->ci = restoreci(L, old_ci); L->base = L->ci->base; diff --git a/VM/src/ldo.h b/VM/src/ldo.h index 5e9472bf..eac9927c 100644 --- a/VM/src/ldo.h +++ b/VM/src/ldo.h @@ -34,12 +34,12 @@ #define saveci(L, p) ((char*)(p) - (char*)L->base_ci) #define restoreci(L, n) ((CallInfo*)((char*)L->base_ci + (n))) -/* results from luaD_precall */ -#define PCRLUA 0 /* initiated a call to a Lua function */ -#define PCRC 1 /* did a call to a C function */ -#define PCRYIELD 2 /* C function yielded */ +// results from luaD_precall +#define PCRLUA 0 // initiated a call to a Lua function +#define PCRC 1 // did a call to a C function +#define PCRYIELD 2 // C function yielded -/* type of protected functions, to be ran by `runprotected' */ +// type of protected functions, to be ran by `runprotected' typedef void (*Pfunc)(lua_State* L, void* ud); LUAI_FUNC CallInfo* luaD_growCI(lua_State* L); diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 66447a95..dfde6dcb 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -73,20 +73,20 @@ UpVal* luaF_findupval(lua_State* L, StkId level) { LUAU_ASSERT(p->v != &p->u.value); if (p->v == level) - { /* found a corresponding upvalue? */ - if (isdead(g, obj2gco(p))) /* is it dead? */ - changewhite(obj2gco(p)); /* resurrect it */ + { // found a corresponding upvalue? + if (isdead(g, obj2gco(p))) // is it dead? + changewhite(obj2gco(p)); // resurrect it return p; } pp = &p->u.l.threadnext; } - UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); /* not found: create a new one */ + UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); // not found: create a new one uv->tt = LUA_TUPVAL; uv->marked = luaC_white(g); uv->memcat = L->activememcat; - uv->v = level; /* current value lives in the stack */ + uv->v = level; // current value lives in the stack // chain the upvalue in the threads open upvalue list at the proper position UpVal* next = *pp; @@ -121,9 +121,9 @@ void luaF_unlinkupval(UpVal* uv) void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page) { - if (uv->v != &uv->u.value) /* is it open? */ - luaF_unlinkupval(uv); /* remove from open list */ - luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); /* free upvalue */ + if (uv->v != &uv->u.value) // is it open? + luaF_unlinkupval(uv); // remove from open list + luaM_freegco(L, uv, sizeof(UpVal), uv->memcat, page); // free upvalue } void luaF_close(lua_State* L, StkId level) @@ -179,11 +179,11 @@ const LocVar* luaF_getlocal(const Proto* f, int local_number, int pc) for (i = 0; i < f->sizelocvars; i++) { if (pc >= f->locvars[i].startpc && pc < f->locvars[i].endpc) - { /* is variable active? */ + { // is variable active? local_number--; if (local_number == 0) return &f->locvars[i]; } } - return NULL; /* not found */ + return NULL; // not found } diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 70b4dbf9..f7a851f4 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -125,7 +125,7 @@ static void removeentry(LuaNode* n) { LUAU_ASSERT(ttisnil(gval(n))); if (iscollectable(gkey(n))) - setttype(gkey(n), LUA_TDEADKEY); /* dead key; remove it */ + setttype(gkey(n), LUA_TDEADKEY); // dead key; remove it } static void reallymarkobject(global_State* g, GCObject* o) @@ -141,7 +141,7 @@ static void reallymarkobject(global_State* g, GCObject* o) case LUA_TUSERDATA: { Table* mt = gco2u(o)->metatable; - gray2black(o); /* udata are never gray */ + gray2black(o); // udata are never gray if (mt) markobject(g, mt); return; @@ -150,8 +150,8 @@ static void reallymarkobject(global_State* g, GCObject* o) { UpVal* uv = gco2uv(o); markvalue(g, uv->v); - if (uv->v == &uv->u.value) /* closed? */ - gray2black(o); /* open upvalues are never black */ + if (uv->v == &uv->u.value) // closed? + gray2black(o); // open upvalues are never black return; } case LUA_TFUNCTION: @@ -201,15 +201,15 @@ static int traversetable(global_State* g, Table* h) if (h->metatable) markobject(g, cast_to(Table*, h->metatable)); - /* is there a weak mode? */ + // is there a weak mode? if (const char* modev = gettablemode(g, h)) { weakkey = (strchr(modev, 'k') != NULL); weakvalue = (strchr(modev, 'v') != NULL); if (weakkey || weakvalue) - { /* is really weak? */ - h->gclist = g->weak; /* must be cleared after GC, ... */ - g->weak = obj2gco(h); /* ... so put in the appropriate list */ + { // is really weak? + h->gclist = g->weak; // must be cleared after GC, ... + g->weak = obj2gco(h); // ... so put in the appropriate list } } @@ -227,7 +227,7 @@ static int traversetable(global_State* g, Table* h) LuaNode* n = gnode(h, i); LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); if (ttisnil(gval(n))) - removeentry(n); /* remove empty entries */ + removeentry(n); // remove empty entries else { LUAU_ASSERT(!ttisnil(gkey(n))); @@ -251,20 +251,20 @@ static void traverseproto(global_State* g, Proto* f) stringmark(f->source); if (f->debugname) stringmark(f->debugname); - for (i = 0; i < f->sizek; i++) /* mark literals */ + for (i = 0; i < f->sizek; i++) // mark literals markvalue(g, &f->k[i]); for (i = 0; i < f->sizeupvalues; i++) - { /* mark upvalue names */ + { // mark upvalue names if (f->upvalues[i]) stringmark(f->upvalues[i]); } for (i = 0; i < f->sizep; i++) - { /* mark nested protos */ + { // mark nested protos if (f->p[i]) markobject(g, f->p[i]); } for (i = 0; i < f->sizelocvars; i++) - { /* mark local-variable names */ + { // mark local-variable names if (f->locvars[i].varname) stringmark(f->locvars[i].varname); } @@ -276,7 +276,7 @@ static void traverseclosure(global_State* g, Closure* cl) if (cl->isC) { int i; - for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + for (i = 0; i < cl->nupvalues; i++) // mark its upvalues markvalue(g, &cl->c.upvals[i]); } else @@ -284,7 +284,7 @@ static void traverseclosure(global_State* g, Closure* cl) int i; LUAU_ASSERT(cl->nupvalues == cl->l.p->nups); markobject(g, cast_to(Proto*, cl->l.p)); - for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + for (i = 0; i < cl->nupvalues; i++) // mark its upvalues markvalue(g, &cl->l.uprefs[i]); } } @@ -296,11 +296,11 @@ static void traversestack(global_State* g, lua_State* l, bool clearstack) stringmark(l->namecall); for (StkId o = l->stack; o < l->top; o++) markvalue(g, o); - /* final traversal? */ + // final traversal? if (g->gcstate == GCSatomic || clearstack) { StkId stack_end = l->stack + l->stacksize; - for (StkId o = l->top; o < stack_end; o++) /* clear not-marked stack slice */ + for (StkId o = l->top; o < stack_end; o++) // clear not-marked stack slice setnilvalue(o); } } @@ -320,8 +320,8 @@ static size_t propagatemark(global_State* g) { Table* h = gco2h(o); g->gray = h->gclist; - if (traversetable(g, h)) /* table is weak? */ - black2gray(o); /* keep it gray */ + if (traversetable(g, h)) // table is weak? + black2gray(o); // keep it gray return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); } case LUA_TFUNCTION: @@ -393,7 +393,7 @@ static int isobjcleared(GCObject* o) { if (o->gch.tt == LUA_TSTRING) { - stringmark(&o->ts); /* strings are `values', so are never weak */ + stringmark(&o->ts); // strings are `values', so are never weak return 0; } @@ -417,8 +417,8 @@ static size_t cleartable(lua_State* L, GCObject* l) while (i--) { TValue* o = &h->array[i]; - if (iscleared(o)) /* value was collected? */ - setnilvalue(o); /* remove value */ + if (iscleared(o)) // value was collected? + setnilvalue(o); // remove value } i = sizenode(h); int activevalues = 0; @@ -432,8 +432,8 @@ static size_t cleartable(lua_State* L, GCObject* l) // can we clear key or value? if (iscleared(gkey(n)) || iscleared(gval(n))) { - setnilvalue(gval(n)); /* remove value ... */ - removeentry(n); /* remove entry from table */ + setnilvalue(gval(n)); // remove value ... + removeentry(n); // remove entry from table } else { @@ -460,7 +460,7 @@ static size_t cleartable(lua_State* L, GCObject* l) static void shrinkstack(lua_State* L) { - /* compute used stack - note that we can't use th->top if we're in the middle of vararg call */ + // compute used stack - note that we can't use th->top if we're in the middle of vararg call StkId lim = L->top; for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++) { @@ -469,16 +469,16 @@ static void shrinkstack(lua_State* L) lim = ci->top; } - /* shrink stack and callinfo arrays if we aren't using most of the space */ - int ci_used = cast_int(L->ci - L->base_ci); /* number of `ci' in use */ - int s_used = cast_int(lim - L->stack); /* part of stack in use */ - if (L->size_ci > LUAI_MAXCALLS) /* handling overflow? */ - return; /* do not touch the stacks */ + // shrink stack and callinfo arrays if we aren't using most of the space + int ci_used = cast_int(L->ci - L->base_ci); // number of `ci' in use + int s_used = cast_int(lim - L->stack); // part of stack in use + if (L->size_ci > LUAI_MAXCALLS) // handling overflow? + return; // do not touch the stacks if (3 * ci_used < L->size_ci && 2 * BASIC_CI_SIZE < L->size_ci) - luaD_reallocCI(L, L->size_ci / 2); /* still big enough... */ + luaD_reallocCI(L, L->size_ci / 2); // still big enough... condhardstacktests(luaD_reallocCI(L, ci_used + 1)); if (3 * s_used < L->stacksize && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize) - luaD_reallocstack(L, L->stacksize / 2); /* still big enough... */ + luaD_reallocstack(L, L->stacksize / 2); // still big enough... condhardstacktests(luaD_reallocstack(L, s_used)); } @@ -516,20 +516,20 @@ static void freeobj(lua_State* L, GCObject* o, lua_Page* page) static void shrinkbuffers(lua_State* L) { global_State* g = L->global; - /* check size of string hash */ + // check size of string hash if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2) - luaS_resize(L, g->strt.size / 2); /* table is too big */ + luaS_resize(L, g->strt.size / 2); // table is too big } static void shrinkbuffersfull(lua_State* L) { global_State* g = L->global; - /* check size of string hash */ + // check size of string hash int hashsize = g->strt.size; while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2) hashsize /= 2; if (hashsize != g->strt.size) - luaS_resize(L, hashsize); /* table is too big */ + luaS_resize(L, hashsize); // table is too big } static bool deletegco(void* context, lua_Page* page, GCObject* gco) @@ -562,7 +562,7 @@ void luaC_freeall(lua_State* L) luaM_visitgco(L, L, deletegco); - for (int i = 0; i < g->strt.size; i++) /* free all string lists */ + for (int i = 0; i < g->strt.size; i++) // free all string lists LUAU_ASSERT(g->strt.hash[i] == NULL); LUAU_ASSERT(L->global->strt.nuse == 0); @@ -577,7 +577,7 @@ static void markmt(global_State* g) markobject(g, g->mt[i]); } -/* mark root set */ +// mark root set static void markroot(lua_State* L) { global_State* g = L->global; @@ -585,7 +585,7 @@ static void markroot(lua_State* L) g->grayagain = NULL; g->weak = NULL; markobject(g, g->mainthread); - /* make global table be traversed before main stack */ + // make global table be traversed before main stack markobject(g, g->mainthread->gt); markvalue(g, registry(L)); markmt(g); @@ -616,28 +616,28 @@ static size_t atomic(lua_State* L) double currts = lua_clock(); #endif - /* remark occasional upvalues of (maybe) dead threads */ + // remark occasional upvalues of (maybe) dead threads work += remarkupvals(g); - /* traverse objects caught by write barrier and by 'remarkupvals' */ + // traverse objects caught by write barrier and by 'remarkupvals' work += propagateall(g); #ifdef LUAI_GCMETRICS g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts); #endif - /* remark weak tables */ + // remark weak tables g->gray = g->weak; g->weak = NULL; LUAU_ASSERT(!iswhite(obj2gco(g->mainthread))); - markobject(g, L); /* mark running thread */ - markmt(g); /* mark basic metatables (again) */ + markobject(g, L); // mark running thread + markmt(g); // mark basic metatables (again) work += propagateall(g); #ifdef LUAI_GCMETRICS g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts); #endif - /* remark gray again */ + // remark gray again g->gray = g->grayagain; g->grayagain = NULL; work += propagateall(g); @@ -646,7 +646,7 @@ static size_t atomic(lua_State* L) g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts); #endif - /* remove collected objects from weak tables */ + // remove collected objects from weak tables work += cleartable(L, g->weak); g->weak = NULL; @@ -654,7 +654,7 @@ static size_t atomic(lua_State* L) g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts); #endif - /* flip current white */ + // flip current white g->currentwhite = cast_byte(otherwhite(g)); g->sweepgcopage = g->allgcopages; g->gcstate = GCSsweep; @@ -733,7 +733,7 @@ static size_t gcstep(lua_State* L, size_t limit) { case GCSpause: { - markroot(L); /* start a new collection */ + markroot(L); // start a new collection LUAU_ASSERT(g->gcstate == GCSpropagate); break; } @@ -765,7 +765,7 @@ static size_t gcstep(lua_State* L, size_t limit) cost += propagatemark(g); } - if (!g->gray) /* no more `gray' objects */ + if (!g->gray) // no more `gray' objects { #ifdef LUAI_GCMETRICS g->gcmetrics.currcycle.propagateagainwork = @@ -786,7 +786,7 @@ static size_t gcstep(lua_State* L, size_t limit) g->gcstats.atomicstarttimestamp = lua_clock(); g->gcstats.atomicstarttotalsizebytes = g->totalbytes; - cost = atomic(L); /* finish mark phase */ + cost = atomic(L); // finish mark phase LUAU_ASSERT(g->gcstate == GCSsweep); break; @@ -810,7 +810,7 @@ static size_t gcstep(lua_State* L, size_t limit) sweepgco(L, NULL, obj2gco(g->mainthread)); shrinkbuffers(L); - g->gcstate = GCSpause; /* end collection */ + g->gcstate = GCSpause; // end collection } break; } @@ -878,7 +878,7 @@ size_t luaC_step(lua_State* L, bool assist) { global_State* g = L->global; - int lim = g->gcstepsize * g->gcstepmul / 100; /* how much to work */ + int lim = g->gcstepsize * g->gcstepmul / 100; // how much to work LUAU_ASSERT(g->totalbytes >= g->GCthreshold); size_t debt = g->totalbytes - g->GCthreshold; @@ -947,16 +947,16 @@ void luaC_fullgc(lua_State* L) if (g->gcstate <= GCSatomic) { - /* reset sweep marks to sweep all elements (returning them to white) */ + // reset sweep marks to sweep all elements (returning them to white) g->sweepgcopage = g->allgcopages; - /* reset other collector lists */ + // reset other collector lists g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->gcstate = GCSsweep; } LUAU_ASSERT(g->gcstate == GCSsweep); - /* finish any pending sweep phase */ + // finish any pending sweep phase while (g->gcstate != GCSpause) { LUAU_ASSERT(g->gcstate == GCSsweep); @@ -968,13 +968,13 @@ void luaC_fullgc(lua_State* L) startGcCycleMetrics(g); #endif - /* run a full collection cycle */ + // run a full collection cycle markroot(L); while (g->gcstate != GCSpause) { gcstep(L, SIZE_MAX); } - /* reclaim as much buffer memory as possible (shrinkbuffers() called during sweep is incremental) */ + // reclaim as much buffer memory as possible (shrinkbuffers() called during sweep is incremental) shrinkbuffersfull(L); size_t heapgoalsizebytes = (g->totalbytes / 100) * g->gcgoal; @@ -1011,11 +1011,11 @@ void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v) global_State* g = L->global; LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); LUAU_ASSERT(g->gcstate != GCSpause); - /* must keep invariant? */ + // must keep invariant? if (keepinvariant(g)) - reallymarkobject(g, v); /* restore invariant */ - else /* don't mind */ - makewhite(g, o); /* mark as white just to avoid other barriers */ + reallymarkobject(g, v); // restore invariant + else // don't mind + makewhite(g, o); // mark as white just to avoid other barriers } void luaC_barriertable(lua_State* L, Table* t, GCObject* v) @@ -1033,7 +1033,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v) LUAU_ASSERT(isblack(o) && !isdead(g, o)); LUAU_ASSERT(g->gcstate != GCSpause); - black2gray(o); /* make table gray (again) */ + black2gray(o); // make table gray (again) t->gclist = g->grayagain; g->grayagain = o; } @@ -1044,7 +1044,7 @@ void luaC_barrierback(lua_State* L, Table* t) GCObject* o = obj2gco(t); LUAU_ASSERT(isblack(o) && !isdead(g, o)); LUAU_ASSERT(g->gcstate != GCSpause); - black2gray(o); /* make table gray (again) */ + black2gray(o); // make table gray (again) t->gclist = g->grayagain; g->grayagain = o; } @@ -1066,11 +1066,11 @@ void luaC_initupval(lua_State* L, UpVal* uv) { if (keepinvariant(g)) { - gray2black(o); /* closed upvalues need barrier */ + gray2black(o); // closed upvalues need barrier luaC_barrier(L, uv, uv->v); } else - { /* sweep phase: sweep it (turning it into white) */ + { // sweep phase: sweep it (turning it into white) makewhite(g, o); LUAU_ASSERT(g->gcstate != GCSpause); } diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 797284a2..7b03a25d 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -9,9 +9,9 @@ /* ** Default settings for GC tunables (settable via lua_gc) */ -#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */ -#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */ -#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */ +#define LUAI_GCGOAL 200 // 200% (allow heap to double compared to live heap size) +#define LUAI_GCSTEPMUL 200 // GC runs 'twice the speed' of memory allocation +#define LUAI_GCSTEPSIZE 1 // GC runs every KB of memory allocation /* ** Possible states of the Garbage Collector diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 2b38619b..bc997d44 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -19,7 +19,7 @@ static void validateobjref(global_State* g, GCObject* f, GCObject* t) if (keepinvariant(g)) { - /* basic incremental invariant: black can't point to white */ + // basic incremental invariant: black can't point to white LUAU_ASSERT(!(isblack(f) && iswhite(t))); } } @@ -135,7 +135,7 @@ static void validateproto(global_State* g, Proto* f) static void validateobj(global_State* g, GCObject* o) { - /* dead objects can only occur during sweep */ + // dead objects can only occur during sweep if (isdead(g, o)) { LUAU_ASSERT(g->gcstate == GCSsweep); diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index a6e7b494..0693b846 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -195,7 +195,7 @@ static int math_ldexp(lua_State* L) static int math_min(lua_State* L) { - int n = lua_gettop(L); /* number of arguments */ + int n = lua_gettop(L); // number of arguments double dmin = luaL_checknumber(L, 1); int i; for (i = 2; i <= n; i++) @@ -210,7 +210,7 @@ static int math_min(lua_State* L) static int math_max(lua_State* L) { - int n = lua_gettop(L); /* number of arguments */ + int n = lua_gettop(L); // number of arguments double dmax = luaL_checknumber(L, 1); int i; for (i = 2; i <= n; i++) @@ -227,29 +227,29 @@ static int math_random(lua_State* L) { global_State* g = L->global; switch (lua_gettop(L)) - { /* check number of arguments */ + { // check number of arguments case 0: - { /* no arguments */ + { // no arguments // Using ldexp instead of division for speed & clarity. // See http://mumble.net/~campbell/tmp/random_real.c for details on generating doubles from integer ranges. uint32_t rl = pcg32_random(&g->rngstate); uint32_t rh = pcg32_random(&g->rngstate); double rd = ldexp(double(rl | (uint64_t(rh) << 32)), -64); - lua_pushnumber(L, rd); /* number between 0 and 1 */ + lua_pushnumber(L, rd); // number between 0 and 1 break; } case 1: - { /* only upper limit */ + { // only upper limit int u = luaL_checkinteger(L, 1); luaL_argcheck(L, 1 <= u, 1, "interval is empty"); uint64_t x = uint64_t(u) * pcg32_random(&g->rngstate); int r = int(1 + (x >> 32)); - lua_pushinteger(L, r); /* int between 1 and `u' */ + lua_pushinteger(L, r); // int between 1 and `u' break; } case 2: - { /* lower and upper limits */ + { // lower and upper limits int l = luaL_checkinteger(L, 1); int u = luaL_checkinteger(L, 2); luaL_argcheck(L, l <= u, 2, "interval is empty"); @@ -258,7 +258,7 @@ static int math_random(lua_State* L) luaL_argcheck(L, ul < UINT_MAX, 2, "interval is too large"); // -INT_MIN..INT_MAX interval can result in integer overflow uint64_t x = uint64_t(ul + 1) * pcg32_random(&g->rngstate); int r = int(l + (x >> 32)); - lua_pushinteger(L, r); /* int between `l' and `u' */ + lua_pushinteger(L, r); // int between `l' and `u' break; } default: diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index 549b4630..5b27e2b8 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -42,7 +42,7 @@ LUAU_FASTMATH_END #define luai_num2int(i, d) ((i) = (int)(d)) -/* On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually */ +// On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually #if defined(_MSC_VER) && defined(_M_IX86) #define luai_num2unsigned(i, n) \ { \ diff --git a/VM/src/lobject.cpp b/VM/src/lobject.cpp index d5bd76a8..b6a40bb6 100644 --- a/VM/src/lobject.cpp +++ b/VM/src/lobject.cpp @@ -48,7 +48,7 @@ int luaO_rawequalObj(const TValue* t1, const TValue* t2) case LUA_TVECTOR: return luai_veceq(vvalue(t1), vvalue(t2)); case LUA_TBOOLEAN: - return bvalue(t1) == bvalue(t2); /* boolean true must be 1 !! */ + return bvalue(t1) == bvalue(t2); // boolean true must be 1 !! case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); default: @@ -71,7 +71,7 @@ int luaO_rawequalKey(const TKey* t1, const TValue* t2) case LUA_TVECTOR: return luai_veceq(vvalue(t1), vvalue(t2)); case LUA_TBOOLEAN: - return bvalue(t1) == bvalue(t2); /* boolean true must be 1 !! */ + return bvalue(t1) == bvalue(t2); // boolean true must be 1 !! case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); default: @@ -85,15 +85,15 @@ int luaO_str2d(const char* s, double* result) char* endptr; *result = luai_str2num(s, &endptr); if (endptr == s) - return 0; /* conversion failed */ - if (*endptr == 'x' || *endptr == 'X') /* maybe an hexadecimal constant? */ + return 0; // conversion failed + if (*endptr == 'x' || *endptr == 'X') // maybe an hexadecimal constant? *result = cast_num(strtoul(s, &endptr, 16)); if (*endptr == '\0') - return 1; /* most common case */ + return 1; // most common case while (isspace(cast_to(unsigned char, *endptr))) endptr++; if (*endptr != '\0') - return 0; /* invalid trailing characters? */ + return 0; // invalid trailing characters? return 1; } @@ -121,7 +121,7 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen) { if (*source == '=') { - source++; /* skip the `=' */ + source++; // skip the `=' size_t srclen = strlen(source); size_t dstlen = srclen < bufflen ? srclen : bufflen - 1; memcpy(out, source, dstlen); @@ -130,26 +130,26 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen) else if (*source == '@') { size_t l; - source++; /* skip the `@' */ + source++; // skip the `@' bufflen -= sizeof("..."); l = strlen(source); strcpy(out, ""); if (l > bufflen) { - source += (l - bufflen); /* get last part of file name */ + source += (l - bufflen); // get last part of file name strcat(out, "..."); } strcat(out, source); } else - { /* out = [string "string"] */ - size_t len = strcspn(source, "\n\r"); /* stop at first newline */ + { // out = [string "string"] + size_t len = strcspn(source, "\n\r"); // stop at first newline bufflen -= sizeof("[string \"...\"]"); if (len > bufflen) len = bufflen; strcpy(out, "[string \""); if (source[len] != '\0') - { /* must truncate? */ + { // must truncate? strncat(out, source, len); strcat(out, "..."); } diff --git a/VM/src/lobject.h b/VM/src/lobject.h index bdcb85cb..2097e335 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -49,7 +49,7 @@ typedef struct lua_TValue int tt; } TValue; -/* Macros to test type */ +// Macros to test type #define ttisnil(o) (ttype(o) == LUA_TNIL) #define ttisnumber(o) (ttype(o) == LUA_TNUMBER) #define ttisstring(o) (ttype(o) == LUA_TSTRING) @@ -62,7 +62,7 @@ typedef struct lua_TValue #define ttisvector(o) (ttype(o) == LUA_TVECTOR) #define ttisupval(o) (ttype(o) == LUA_TUPVAL) -/* Macros to access values */ +// Macros to access values #define ttype(o) ((o)->tt) #define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) #define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) @@ -85,7 +85,7 @@ typedef struct lua_TValue #define checkliveness(g, obj) LUAU_ASSERT(!iscollectable(obj) || ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc))) -/* Macros to set values */ +// Macros to set values #define setnilvalue(obj) ((obj)->tt = LUA_TNIL) #define setnvalue(obj, x) \ @@ -200,18 +200,18 @@ typedef struct lua_TValue ** different types of sets, according to destination */ -/* from stack to (same) stack */ +// from stack to (same) stack #define setobjs2s setobj -/* to stack (not from same stack) */ +// to stack (not from same stack) #define setobj2s setobj #define setsvalue2s setsvalue #define sethvalue2s sethvalue #define setptvalue2s setptvalue -/* from table to same table */ +// from table to same table #define setobjt2t setobj -/* to table */ +// to table #define setobj2t setobj -/* to new object */ +// to new object #define setobj2n setobj #define setsvalue2n setsvalue @@ -219,7 +219,7 @@ typedef struct lua_TValue #define iscollectable(o) (ttype(o) >= LUA_TSTRING) -typedef TValue* StkId; /* index to stack elements */ +typedef TValue* StkId; // index to stack elements /* ** String headers for string table @@ -269,13 +269,13 @@ typedef struct Proto CommonHeader; - TValue* k; /* constants used by the function */ - Instruction* code; /* function bytecode */ - struct Proto** p; /* functions defined inside the function */ - uint8_t* lineinfo; /* for each instruction, line number as a delta from baseline */ - int* abslineinfo; /* baseline line info, one entry for each 1<global, i_o); \ } -/* copy a value from a key */ +// copy a value from a key #define getnodekey(L, obj, node) \ { \ TValue* i_o = (obj); \ @@ -418,22 +418,22 @@ typedef struct Table CommonHeader; - uint8_t tmcache; /* 1<

tm_sec); setfield(L, "min", stm->tm_min); setfield(L, "hour", stm->tm_hour); @@ -122,7 +122,7 @@ static int os_date(lua_State* L) luaL_buffinit(L, &b); for (; *s; s++) { - if (*s != '%' || *(s + 1) == '\0') /* no conversion specifier? */ + if (*s != '%' || *(s + 1) == '\0') // no conversion specifier? { luaL_addchar(&b, *s); } @@ -133,7 +133,7 @@ static int os_date(lua_State* L) else { size_t reslen; - char buff[200]; /* should be big enough for any conversion result */ + char buff[200]; // should be big enough for any conversion result cc[1] = *(++s); reslen = strftime(buff, sizeof(buff), cc, stm); luaL_addlstring(&b, buff, reslen); @@ -147,13 +147,13 @@ static int os_date(lua_State* L) static int os_time(lua_State* L) { time_t t; - if (lua_isnoneornil(L, 1)) /* called without args? */ - t = time(NULL); /* get current time */ + if (lua_isnoneornil(L, 1)) // called without args? + t = time(NULL); // get current time else { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); - lua_settop(L, 1); /* make sure table is at the top */ + lua_settop(L, 1); // make sure table is at the top ts.tm_sec = getfield(L, "sec", 0); ts.tm_min = getfield(L, "min", 0); ts.tm_hour = getfield(L, "hour", 12); diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index fbc6fb1e..4489f840 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -21,22 +21,22 @@ typedef struct LG static void stack_init(lua_State* L1, lua_State* L) { - /* initialize CallInfo array */ + // initialize CallInfo array L1->base_ci = luaM_newarray(L, BASIC_CI_SIZE, CallInfo, L1->memcat); L1->ci = L1->base_ci; L1->size_ci = BASIC_CI_SIZE; L1->end_ci = L1->base_ci + L1->size_ci - 1; - /* initialize stack array */ + // initialize stack array L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat); L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; TValue* stack = L1->stack; for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) - setnilvalue(stack + i); /* erase new stack */ + setnilvalue(stack + i); // erase new stack L1->top = stack; L1->stack_last = stack + (L1->stacksize - EXTRA_STACK); - /* initialize first ci */ + // initialize first ci L1->ci->func = L1->top; - setnilvalue(L1->top++); /* `function' entry for this `ci' */ + setnilvalue(L1->top++); // `function' entry for this `ci' L1->base = L1->ci->base = L1->top; L1->ci->top = L1->top + LUA_MINSTACK; } @@ -53,13 +53,13 @@ static void freestack(lua_State* L, lua_State* L1) static void f_luaopen(lua_State* L, void* ud) { global_State* g = L->global; - stack_init(L, L); /* init stack */ - L->gt = luaH_new(L, 0, 2); /* table of globals */ - sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */ - luaS_resize(L, LUA_MINSTRTABSIZE); /* initial size of string table */ + stack_init(L, L); // init stack + L->gt = luaH_new(L, 0, 2); // table of globals + sethvalue(L, registry(L), luaH_new(L, 0, 2)); // registry + luaS_resize(L, LUA_MINSTRTABSIZE); // initial size of string table luaT_init(L); - luaS_fix(luaS_newliteral(L, LUA_MEMERRMSG)); /* pin to make sure we can always throw this error */ - luaS_fix(luaS_newliteral(L, LUA_ERRERRMSG)); /* pin to make sure we can always throw this error */ + luaS_fix(luaS_newliteral(L, LUA_MEMERRMSG)); // pin to make sure we can always throw this error + luaS_fix(luaS_newliteral(L, LUA_ERRERRMSG)); // pin to make sure we can always throw this error g->GCthreshold = 4 * g->totalbytes; } @@ -85,8 +85,8 @@ static void preinit_state(lua_State* L, global_State* g) static void close_state(lua_State* L) { global_State* g = L->global; - luaF_close(L, L->stack); /* close all upvalues for this thread */ - luaC_freeall(L); /* collect all objects */ + luaF_close(L, L->stack); // close all upvalues for this thread + luaC_freeall(L); // collect all objects LUAU_ASSERT(g->strbufgc == NULL); LUAU_ASSERT(g->strt.nuse == 0); luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0); @@ -110,8 +110,8 @@ lua_State* luaE_newthread(lua_State* L) luaC_init(L, L1, LUA_TTHREAD); preinit_state(L1, L->global); L1->activememcat = L->activememcat; // inherit the active memory category - stack_init(L1, L); /* init stack */ - L1->gt = L->gt; /* share table of globals */ + stack_init(L1, L); // init stack + L1->gt = L->gt; // share table of globals L1->singlestep = L->singlestep; LUAU_ASSERT(iswhite(obj2gco(L1))); return L1; @@ -119,7 +119,7 @@ lua_State* luaE_newthread(lua_State* L) void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page) { - luaF_close(L1, L1->stack); /* close all upvalues for this thread */ + luaF_close(L1, L1->stack); // close all upvalues for this thread LUAU_ASSERT(L1->openupval == NULL); global_State* g = L->global; if (g->cb.userthread) @@ -130,9 +130,9 @@ void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page) void lua_resetthread(lua_State* L) { - /* close upvalues before clearing anything */ + // close upvalues before clearing anything luaF_close(L, L->stack); - /* clear call frames */ + // clear call frames CallInfo* ci = L->base_ci; ci->func = L->stack; ci->base = ci->func + 1; @@ -141,12 +141,12 @@ void lua_resetthread(lua_State* L) L->ci = ci; if (L->size_ci != BASIC_CI_SIZE) luaD_reallocCI(L, BASIC_CI_SIZE); - /* clear thread state */ + // clear thread state L->status = LUA_OK; L->base = L->ci->base; L->top = L->ci->base; L->nCcalls = L->baseCcalls = 0; - /* clear thread stack */ + // clear thread stack if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK) luaD_reallocstack(L, BASIC_STACK_SIZE); for (int i = 0; i < L->stacksize; i++) @@ -177,7 +177,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) g->mainthread = L; g->uvhead.u.l.prev = &g->uvhead; g->uvhead.u.l.next = &g->uvhead; - g->GCthreshold = 0; /* mark it as unfinished state */ + g->GCthreshold = 0; // mark it as unfinished state g->registryfree = 0; g->errorjmp = NULL; g->rngstate = 0; @@ -224,7 +224,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) { - /* memory allocation error: free partial state */ + // memory allocation error: free partial state close_state(L); L = NULL; } @@ -233,7 +233,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) void lua_close(lua_State* L) { - L = L->global->mainthread; /* only the main thread can be closed */ - luaF_close(L, L->stack); /* close all upvalues for this thread */ + L = L->global->mainthread; // only the main thread can be closed + luaF_close(L, L->stack); // close all upvalues for this thread close_state(L); } diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 423514a7..72a09713 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -5,10 +5,10 @@ #include "lobject.h" #include "ltm.h" -/* registry */ +// registry #define registry(L) (&L->global->registry) -/* extra stack space to handle TM calls and some other extras */ +// extra stack space to handle TM calls and some other extras #define EXTRA_STACK 5 #define BASIC_CI_SIZE 8 @@ -20,7 +20,7 @@ typedef struct stringtable { TString** hash; - uint32_t nuse; /* number of elements */ + uint32_t nuse; // number of elements int size; } stringtable; // clang-format on @@ -57,18 +57,18 @@ typedef struct stringtable typedef struct CallInfo { - StkId base; /* base for this function */ - StkId func; /* function index in the stack */ - StkId top; /* top for this function */ + StkId base; // base for this function + StkId func; // function index in the stack + StkId top; // top for this function const Instruction* savedpc; - int nresults; /* expected number of results from this function */ - unsigned int flags; /* call frame flags, see LUA_CALLINFO_* */ + int nresults; // expected number of results from this function + unsigned int flags; // call frame flags, see LUA_CALLINFO_* } CallInfo; // clang-format on -#define LUA_CALLINFO_RETURN (1 << 0) /* should the interpreter return after returning from this callinfo? first frame must have this set */ -#define LUA_CALLINFO_HANDLE (1 << 1) /* should the error thrown during execution get handled by continuation from this callinfo? func must be C */ +#define LUA_CALLINFO_RETURN (1 << 0) // should the interpreter return after returning from this callinfo? first frame must have this set +#define LUA_CALLINFO_HANDLE (1 << 1) // should the error thrown during execution get handled by continuation from this callinfo? func must be C #define curr_func(L) (clvalue(L->ci->func)) #define ci_func(ci) (clvalue((ci)->func)) @@ -152,55 +152,55 @@ struct GCMetrics // clang-format off typedef struct global_State { - stringtable strt; /* hash table for strings */ + stringtable strt; // hash table for strings - lua_Alloc frealloc; /* function to reallocate memory */ - void* ud; /* auxiliary data to `frealloc' */ + lua_Alloc frealloc; // function to reallocate memory + void* ud; // auxiliary data to `frealloc' uint8_t currentwhite; - uint8_t gcstate; /* state of garbage collector */ + uint8_t gcstate; // state of garbage collector - GCObject* gray; /* list of gray objects */ - GCObject* grayagain; /* list of objects to be traversed atomically */ - GCObject* weak; /* list of weak tables (to be cleared) */ + GCObject* gray; // list of gray objects + GCObject* grayagain; // list of objects to be traversed atomically + GCObject* weak; // list of weak tables (to be cleared) TString* strbufgc; // list of all string buffer objects - size_t GCthreshold; // when totalbytes > GCthreshold; run GC step + size_t GCthreshold; // when totalbytes > GCthreshold, run GC step size_t totalbytes; // number of bytes currently allocated int gcgoal; // see LUAI_GCGOAL int gcstepmul; // see LUAI_GCSTEPMUL int gcstepsize; // see LUAI_GCSTEPSIZE struct lua_Page* freepages[LUA_SIZECLASSES]; // free page linked list for each size class for non-collectable objects - struct lua_Page* freegcopages[LUA_SIZECLASSES]; // free page linked list for each size class for collectable objects + struct lua_Page* freegcopages[LUA_SIZECLASSES]; // free page linked list for each size class for collectable objects struct lua_Page* allgcopages; // page linked list with all pages for all classes struct lua_Page* sweepgcopage; // position of the sweep in `allgcopages' - size_t memcatbytes[LUA_MEMORY_CATEGORIES]; /* total amount of memory used by each memory category */ + size_t memcatbytes[LUA_MEMORY_CATEGORIES]; // total amount of memory used by each memory category struct lua_State* mainthread; - UpVal uvhead; /* head of double-linked list of all open upvalues */ - struct Table* mt[LUA_T_COUNT]; /* metatables for basic types */ - TString* ttname[LUA_T_COUNT]; /* names for basic types */ - TString* tmname[TM_N]; /* array with tag-method names */ + UpVal uvhead; // head of double-linked list of all open upvalues + struct Table* mt[LUA_T_COUNT]; // metatables for basic types + TString* ttname[LUA_T_COUNT]; // names for basic types + TString* tmname[TM_N]; // array with tag-method names - TValue pseudotemp; /* storage for temporary values used in pseudo2addr */ + TValue pseudotemp; // storage for temporary values used in pseudo2addr - TValue registry; /* registry table, used by lua_ref and LUA_REGISTRYINDEX */ - int registryfree; /* next free slot in registry */ + TValue registry; // registry table, used by lua_ref and LUA_REGISTRYINDEX + int registryfree; // next free slot in registry - struct lua_jmpbuf* errorjmp; /* jump buffer data for longjmp-style error handling */ + struct lua_jmpbuf* errorjmp; // jump buffer data for longjmp-style error handling - uint64_t rngstate; /* PCG random number generator state */ - uint64_t ptrenckey[4]; /* pointer encoding key for display */ + uint64_t rngstate; // PCG random number generator state + uint64_t ptrenckey[4]; // pointer encoding key for display - void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); /* for each userdata tag, a gc callback to be called immediately before freeing memory */ + void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory lua_Callbacks cb; @@ -221,39 +221,39 @@ struct lua_State CommonHeader; uint8_t status; - uint8_t activememcat; /* memory category that is used for new GC object allocations */ + uint8_t activememcat; // memory category that is used for new GC object allocations uint8_t stackstate; - bool singlestep; /* call debugstep hook after each instruction */ + bool singlestep; // call debugstep hook after each instruction - StkId top; /* first free slot in the stack */ - StkId base; /* base of current function */ + StkId top; // first free slot in the stack + StkId base; // base of current function global_State* global; - CallInfo* ci; /* call info for current function */ - StkId stack_last; /* last free slot in the stack */ - StkId stack; /* stack base */ + CallInfo* ci; // call info for current function + StkId stack_last; // last free slot in the stack + StkId stack; // stack base - CallInfo* end_ci; /* points after end of ci array*/ - CallInfo* base_ci; /* array of CallInfo's */ + CallInfo* end_ci; // points after end of ci array + CallInfo* base_ci; // array of CallInfo's int stacksize; - int size_ci; /* size of array `base_ci' */ + int size_ci; // size of array `base_ci' - unsigned short nCcalls; /* number of nested C calls */ - unsigned short baseCcalls; /* nested C calls when resuming coroutine */ + unsigned short nCcalls; // number of nested C calls + unsigned short baseCcalls; // nested C calls when resuming coroutine - int cachedslot; /* when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? */ + int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? - Table* gt; /* table of globals */ - UpVal* openupval; /* list of open upvalues in this stack */ + Table* gt; // table of globals + UpVal* openupval; // list of open upvalues in this stack GCObject* gclist; - TString* namecall; /* when invoked from Luau using NAMECALL, what method do we need to invoke? */ + TString* namecall; // when invoked from Luau using NAMECALL, what method do we need to invoke? void* userdata; }; @@ -271,10 +271,10 @@ union GCObject struct Table h; struct Proto p; struct UpVal uv; - struct lua_State th; /* thread */ + struct lua_State th; // thread }; -/* macros to convert a GCObject into a specific value */ +// macros to convert a GCObject into a specific value #define gco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts)) #define gco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) #define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl)) @@ -283,7 +283,7 @@ union GCObject #define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) #define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) -/* macro to convert any Lua object into a GCObject */ +// macro to convert any Lua object into a GCObject #define obj2gco(v) check_exp(iscollectable(v), cast_to(GCObject*, (v) + 0)) LUAI_FUNC lua_State* luaE_newthread(lua_State* L); diff --git a/VM/src/lstring.cpp b/VM/src/lstring.cpp index 12bd67d5..9c266031 100644 --- a/VM/src/lstring.cpp +++ b/VM/src/lstring.cpp @@ -7,8 +7,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLazyAtoms, false) - unsigned int luaS_hash(const char* str, size_t len) { // Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash @@ -50,17 +48,17 @@ void luaS_resize(lua_State* L, int newsize) stringtable* tb = &L->global->strt; for (int i = 0; i < newsize; i++) newhash[i] = NULL; - /* rehash */ + // rehash for (int i = 0; i < tb->size; i++) { TString* p = tb->hash[i]; while (p) - { /* for each node in the list */ - TString* next = p->next; /* save next */ + { // for each node in the list + TString* next = p->next; // save next unsigned int h = p->hash; - int h1 = lmod(h, newsize); /* new position */ + int h1 = lmod(h, newsize); // new position LUAU_ASSERT(cast_int(h % newsize) == lmod(h, newsize)); - p->next = newhash[h1]; /* chain it */ + p->next = newhash[h1]; // chain it newhash[h1] = p; p = next; } @@ -83,15 +81,15 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h) ts->tt = LUA_TSTRING; ts->memcat = L->activememcat; memcpy(ts->data, str, l); - ts->data[l] = '\0'; /* ending 0 */ - ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1; + ts->data[l] = '\0'; // ending 0 + ts->atom = ATOM_UNDEF; tb = &L->global->strt; h = lmod(h, tb->size); - ts->next = tb->hash[h]; /* chain new entry */ + ts->next = tb->hash[h]; // chain new entry tb->hash[h] = ts; tb->nuse++; if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2) - luaS_resize(L, tb->size * 2); /* too crowded */ + luaS_resize(L, tb->size * 2); // too crowded return ts; } @@ -165,9 +163,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts) ts->hash = h; ts->data[ts->len] = '\0'; // ending 0 - - // Complete string object - ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; + ts->atom = ATOM_UNDEF; ts->next = tb->hash[bucket]; // chain new entry tb->hash[bucket] = ts; @@ -185,13 +181,13 @@ TString* luaS_newlstr(lua_State* L, const char* str, size_t l) { if (el->len == l && (memcmp(str, getstr(el), l) == 0)) { - /* string may be dead */ + // string may be dead if (isdead(L->global, obj2gco(el))) changewhite(obj2gco(el)); return el; } } - return newlstr(L, str, l, h); /* not found */ + return newlstr(L, str, l, h); // not found } static bool unlinkstr(lua_State* L, TString* ts) diff --git a/VM/src/lstring.h b/VM/src/lstring.h index acdf9a1e..41f9df9a 100644 --- a/VM/src/lstring.h +++ b/VM/src/lstring.h @@ -5,10 +5,10 @@ #include "lobject.h" #include "lstate.h" -/* string size limit */ +// string size limit #define MAXSSIZE (1 << 30) -/* string atoms are not defined by default; the storage is 16-bit integer */ +// string atoms are not defined by default; the storage is 16-bit integer #define ATOM_UNDEF -32768 #define sizestring(len) (offsetof(TString, data) + len + 1) diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 79161612..a4dd5a79 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -10,7 +10,7 @@ LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false); -/* macro to `unsign' a character */ +// macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) static int str_len(lua_State* L) @@ -23,7 +23,7 @@ static int str_len(lua_State* L) static int posrelat(int pos, size_t len) { - /* relative string position: negative means back from end */ + // relative string position: negative means back from end if (pos < 0) pos += (int)len + 1; return (pos >= 0) ? pos : 0; @@ -139,9 +139,9 @@ static int str_byte(lua_State* L) if ((size_t)pose > l) pose = (int)l; if (posi > pose) - return 0; /* empty interval; return no values */ + return 0; // empty interval; return no values n = (int)(pose - posi + 1); - if (posi + n <= pose) /* overflow? */ + if (posi + n <= pose) // overflow? luaL_error(L, "string slice too long"); luaL_checkstack(L, n, "string slice too long"); for (i = 0; i < n; i++) @@ -151,7 +151,7 @@ static int str_byte(lua_State* L) static int str_char(lua_State* L) { - int n = lua_gettop(L); /* number of arguments */ + int n = lua_gettop(L); // number of arguments luaL_Buffer b; char* ptr = luaL_buffinitsize(L, &b, n); @@ -178,12 +178,12 @@ static int str_char(lua_State* L) typedef struct MatchState { - int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ - const char* src_init; /* init of source string */ - const char* src_end; /* end ('\0') of source string */ - const char* p_end; /* end ('\0') of pattern */ + int matchdepth; // control for recursive depth (to avoid C stack overflow) + const char* src_init; // init of source string + const char* src_end; // end ('\0') of source string + const char* p_end; // end ('\0') of pattern lua_State* L; - int level; /* total number of captures (finished or unfinished) */ + int level; // total number of captures (finished or unfinished) struct { const char* init; @@ -191,7 +191,7 @@ typedef struct MatchState } capture[LUA_MAXCAPTURES]; } MatchState; -/* recursive function */ +// recursive function static const char* match(MatchState* ms, const char* s, const char* p); #define L_ESC '%' @@ -229,11 +229,11 @@ static const char* classend(MatchState* ms, const char* p) if (*p == '^') p++; do - { /* look for a `]' */ + { // look for a `]' if (p == ms->p_end) luaL_error(ms->L, "malformed pattern (missing ']')"); if (*(p++) == L_ESC && p < ms->p_end) - p++; /* skip escapes (e.g. `%]') */ + p++; // skip escapes (e.g. `%]') } while (*p != ']'); return p + 1; } @@ -281,7 +281,7 @@ static int match_class(int c, int cl) break; case 'z': res = (c == 0); - break; /* deprecated option */ + break; // deprecated option default: return (cl == c); } @@ -294,7 +294,7 @@ static int matchbracketclass(int c, const char* p, const char* ec) if (*(p + 1) == '^') { sig = 0; - p++; /* skip the `^' */ + p++; // skip the `^' } while (++p < ec) { @@ -326,7 +326,7 @@ static int singlematch(MatchState* ms, const char* s, const char* p, const char* switch (*p) { case '.': - return 1; /* matches any char */ + return 1; // matches any char case L_ESC: return match_class(c, uchar(*(p + 1))); case '[': @@ -359,21 +359,21 @@ static const char* matchbalance(MatchState* ms, const char* s, const char* p) cont++; } } - return NULL; /* string ends out of balance */ + return NULL; // string ends out of balance } static const char* max_expand(MatchState* ms, const char* s, const char* p, const char* ep) { - ptrdiff_t i = 0; /* counts maximum expand for item */ + ptrdiff_t i = 0; // counts maximum expand for item while (singlematch(ms, s + i, p, ep)) i++; - /* keeps trying to match with the maximum repetitions */ + // keeps trying to match with the maximum repetitions while (i >= 0) { const char* res = match(ms, (s + i), ep + 1); if (res) return res; - i--; /* else didn't match; reduce 1 repetition to try again */ + i--; // else didn't match; reduce 1 repetition to try again } return NULL; } @@ -386,7 +386,7 @@ static const char* min_expand(MatchState* ms, const char* s, const char* p, cons if (res != NULL) return res; else if (singlematch(ms, s, p, ep)) - s++; /* try with one more repetition */ + s++; // try with one more repetition else return NULL; } @@ -401,8 +401,8 @@ static const char* start_capture(MatchState* ms, const char* s, const char* p, i ms->capture[level].init = s; ms->capture[level].len = what; ms->level = level + 1; - if ((res = match(ms, s, p)) == NULL) /* match failed? */ - ms->level--; /* undo capture */ + if ((res = match(ms, s, p)) == NULL) // match failed? + ms->level--; // undo capture return res; } @@ -410,9 +410,9 @@ static const char* end_capture(MatchState* ms, const char* s, const char* p) { int l = capture_to_close(ms); const char* res; - ms->capture[l].len = s - ms->capture[l].init; /* close capture */ - if ((res = match(ms, s, p)) == NULL) /* match failed? */ - ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + ms->capture[l].len = s - ms->capture[l].init; // close capture + if ((res = match(ms, s, p)) == NULL) // match failed? + ms->capture[l].len = CAP_UNFINISHED; // undo capture return res; } @@ -431,60 +431,60 @@ static const char* match(MatchState* ms, const char* s, const char* p) { if (ms->matchdepth-- == 0) luaL_error(ms->L, "pattern too complex"); -init: /* using goto's to optimize tail recursion */ +init: // using goto's to optimize tail recursion if (p != ms->p_end) - { /* end of pattern? */ + { // end of pattern? switch (*p) { case '(': - { /* start capture */ - if (*(p + 1) == ')') /* position capture? */ + { // start capture + if (*(p + 1) == ')') // position capture? s = start_capture(ms, s, p + 2, CAP_POSITION); else s = start_capture(ms, s, p + 1, CAP_UNFINISHED); break; } case ')': - { /* end capture */ + { // end capture s = end_capture(ms, s, p + 1); break; } case '$': { - if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ - goto dflt; /* no; go to default */ - s = (s == ms->src_end) ? s : NULL; /* check end of string */ + if ((p + 1) != ms->p_end) // is the `$' the last char in pattern? + goto dflt; // no; go to default + s = (s == ms->src_end) ? s : NULL; // check end of string break; } case L_ESC: - { /* escaped sequences not in the format class[*+?-]? */ + { // escaped sequences not in the format class[*+?-]? switch (*(p + 1)) { case 'b': - { /* balanced string? */ + { // balanced string? s = matchbalance(ms, s, p + 2); if (s != NULL) { p += 4; - goto init; /* return match(ms, s, p + 4); */ - } /* else fail (s == NULL) */ + goto init; // return match(ms, s, p + 4); + } // else fail (s == NULL) break; } case 'f': - { /* frontier? */ + { // frontier? const char* ep; char previous; p += 2; if (*p != '[') luaL_error(ms->L, "missing '[' after '%%f' in pattern"); - ep = classend(ms, p); /* points to what is next */ + ep = classend(ms, p); // points to what is next previous = (s == ms->src_init) ? '\0' : *(s - 1); if (!matchbracketclass(uchar(previous), p, ep - 1) && matchbracketclass(uchar(*s), p, ep - 1)) { p = ep; - goto init; /* return match(ms, s, ep); */ + goto init; // return match(ms, s, ep); } - s = NULL; /* match failed */ + s = NULL; // match failed break; } case '0': @@ -497,12 +497,12 @@ init: /* using goto's to optimize tail recursion */ case '7': case '8': case '9': - { /* capture results (%0-%9)? */ + { // capture results (%0-%9)? s = match_capture(ms, s, uchar(*(p + 1))); if (s != NULL) { p += 2; - goto init; /* return match(ms, s, p + 2) */ + goto init; // return match(ms, s, p + 2) } break; } @@ -513,48 +513,48 @@ init: /* using goto's to optimize tail recursion */ } default: dflt: - { /* pattern class plus optional suffix */ - const char* ep = classend(ms, p); /* points to optional suffix */ - /* does not match at least once? */ + { // pattern class plus optional suffix + const char* ep = classend(ms, p); // points to optional suffix + // does not match at least once? if (!singlematch(ms, s, p, ep)) { if (*ep == '*' || *ep == '?' || *ep == '-') - { /* accept empty? */ + { // accept empty? p = ep + 1; - goto init; /* return match(ms, s, ep + 1); */ + goto init; // return match(ms, s, ep + 1); } - else /* '+' or no suffix */ - s = NULL; /* fail */ + else // '+' or no suffix + s = NULL; // fail } else - { /* matched once */ + { // matched once switch (*ep) - { /* handle optional suffix */ + { // handle optional suffix case '?': - { /* optional */ + { // optional const char* res; if ((res = match(ms, s + 1, ep + 1)) != NULL) s = res; else { p = ep + 1; - goto init; /* else return match(ms, s, ep + 1); */ + goto init; // else return match(ms, s, ep + 1); } break; } - case '+': /* 1 or more repetitions */ - s++; /* 1 match already done */ - /* go through */ - case '*': /* 0 or more repetitions */ + case '+': // 1 or more repetitions + s++; // 1 match already done + // go through + case '*': // 0 or more repetitions s = max_expand(ms, s, p, ep); break; - case '-': /* 0 or more repetitions (minimum) */ + case '-': // 0 or more repetitions (minimum) s = min_expand(ms, s, p, ep); break; - default: /* no suffix */ + default: // no suffix s++; p = ep; - goto init; /* return match(ms, s + 1, ep); */ + goto init; // return match(ms, s + 1, ep); } } break; @@ -568,26 +568,26 @@ init: /* using goto's to optimize tail recursion */ static const char* lmemfind(const char* s1, size_t l1, const char* s2, size_t l2) { if (l2 == 0) - return s1; /* empty strings are everywhere */ + return s1; // empty strings are everywhere else if (l2 > l1) - return NULL; /* avoids a negative `l1' */ + return NULL; // avoids a negative `l1' else { - const char* init; /* to search for a `*s2' inside `s1' */ - l2--; /* 1st char will be checked by `memchr' */ - l1 = l1 - l2; /* `s2' cannot be found after that */ + const char* init; // to search for a `*s2' inside `s1' + l2--; // 1st char will be checked by `memchr' + l1 = l1 - l2; // `s2' cannot be found after that while (l1 > 0 && (init = (const char*)memchr(s1, *s2, l1)) != NULL) { - init++; /* 1st char is already checked */ + init++; // 1st char is already checked if (memcmp(init, s2 + 1, l2) == 0) return init - 1; else - { /* correct `l1' and `s1' to try again */ + { // correct `l1' and `s1' to try again l1 -= init - s1; s1 = init; } } - return NULL; /* not found */ + return NULL; // not found } } @@ -595,8 +595,8 @@ static void push_onecapture(MatchState* ms, int i, const char* s, const char* e) { if (i >= ms->level) { - if (i == 0) /* ms->level == 0, too */ - lua_pushlstring(ms->L, s, e - s); /* add whole match */ + if (i == 0) // ms->level == 0, too + lua_pushlstring(ms->L, s, e - s); // add whole match else luaL_error(ms->L, "invalid capture index"); } @@ -619,20 +619,20 @@ static int push_captures(MatchState* ms, const char* s, const char* e) luaL_checkstack(ms->L, nlevels, "too many captures"); for (i = 0; i < nlevels; i++) push_onecapture(ms, i, s, e); - return nlevels; /* number of strings pushed */ + return nlevels; // number of strings pushed } -/* check whether pattern has no special characters */ +// check whether pattern has no special characters static int nospecials(const char* p, size_t l) { size_t upto = 0; do { if (strpbrk(p + upto, SPECIALS)) - return 0; /* pattern has a special character */ - upto += strlen(p + upto) + 1; /* may have more after \0 */ + return 0; // pattern has a special character + upto += strlen(p + upto) + 1; // may have more after \0 } while (upto <= l); - return 1; /* no special chars found */ + return 1; // no special chars found } static void prepstate(MatchState* ms, lua_State* L, const char* s, size_t ls, const char* p, size_t lp) @@ -659,14 +659,14 @@ static int str_find_aux(lua_State* L, int find) if (init < 1) init = 1; else if (init > (int)ls + 1) - { /* start after string's end? */ - lua_pushnil(L); /* cannot find anything */ + { // start after string's end? + lua_pushnil(L); // cannot find anything return 1; } - /* explicit request or no special characters? */ + // explicit request or no special characters? if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { - /* do a plain search */ + // do a plain search const char* s2 = lmemfind(s + init - 1, ls - init + 1, p, lp); if (s2) { @@ -683,7 +683,7 @@ static int str_find_aux(lua_State* L, int find) if (anchor) { p++; - lp--; /* skip anchor character */ + lp--; // skip anchor character } prepstate(&ms, L, s, ls, p, lp); do @@ -694,8 +694,8 @@ static int str_find_aux(lua_State* L, int find) { if (find) { - lua_pushinteger(L, (int)(s1 - s + 1)); /* start */ - lua_pushinteger(L, (int)(res - s)); /* end */ + lua_pushinteger(L, (int)(s1 - s + 1)); // start + lua_pushinteger(L, (int)(res - s)); // end return push_captures(&ms, NULL, 0) + 2; } else @@ -703,7 +703,7 @@ static int str_find_aux(lua_State* L, int find) } } while (s1++ < ms.src_end && !anchor); } - lua_pushnil(L); /* not found */ + lua_pushnil(L); // not found return 1; } @@ -733,13 +733,13 @@ static int gmatch_aux(lua_State* L) { int newstart = (int)(e - s); if (e == src) - newstart++; /* empty match? go at least one position */ + newstart++; // empty match? go at least one position lua_pushinteger(L, newstart); lua_replace(L, lua_upvalueindex(3)); return push_captures(&ms, src, e); } } - return 0; /* not found */ + return 0; // not found } static int gmatch(lua_State* L) @@ -765,7 +765,7 @@ static void add_s(MatchState* ms, luaL_Buffer* b, const char* s, const char* e) luaL_addchar(b, news[i]); else { - i++; /* skip ESC */ + i++; // skip ESC if (!isdigit(uchar(news[i]))) { if (news[i] != L_ESC) @@ -777,7 +777,7 @@ static void add_s(MatchState* ms, luaL_Buffer* b, const char* s, const char* e) else { push_onecapture(ms, news[i] - '1', s, e); - luaL_addvalue(b); /* add capture to accumulated result */ + luaL_addvalue(b); // add capture to accumulated result } } } @@ -803,19 +803,19 @@ static void add_value(MatchState* ms, luaL_Buffer* b, const char* s, const char* break; } default: - { /* LUA_TNUMBER or LUA_TSTRING */ + { // LUA_TNUMBER or LUA_TSTRING add_s(ms, b, s, e); return; } } if (!lua_toboolean(L, -1)) - { /* nil or false? */ + { // nil or false? lua_pop(L, 1); - lua_pushlstring(L, s, e - s); /* keep original text */ + lua_pushlstring(L, s, e - s); // keep original text } else if (!lua_isstring(L, -1)) luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); - luaL_addvalue(b); /* add result to accumulator */ + luaL_addvalue(b); // add result to accumulator } static int str_gsub(lua_State* L) @@ -834,7 +834,7 @@ static int str_gsub(lua_State* L) if (anchor) { p++; - lp--; /* skip anchor character */ + lp--; // skip anchor character } prepstate(&ms, L, src, srcl, p, lp); while (n < max_s) @@ -847,8 +847,8 @@ static int str_gsub(lua_State* L) n++; add_value(&ms, &b, src, e, tr); } - if (e && e > src) /* non empty match? */ - src = e; /* skip it */ + if (e && e > src) // non empty match? + src = e; // skip it else if (src < ms.src_end) luaL_addchar(&b, *src++); else @@ -858,17 +858,17 @@ static int str_gsub(lua_State* L) } luaL_addlstring(&b, src, ms.src_end - src); luaL_pushresult(&b); - lua_pushinteger(L, n); /* number of substitutions */ + lua_pushinteger(L, n); // number of substitutions return 2; } -/* }====================================================== */ +// }====================================================== -/* valid flags in a format specification */ +// valid flags in a format specification #define FLAGS "-+ #0" -/* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ +// maximum size of each formatted item (> len(format('%99.99f', -1e308))) #define MAX_ITEM 512 -/* maximum size of each format specification (such as '%-099.99d') */ +// maximum size of each format specification (such as '%-099.99d') #define MAX_FORMAT 32 static void addquoted(lua_State* L, luaL_Buffer* b, int arg) @@ -916,20 +916,20 @@ static const char* scanformat(lua_State* L, const char* strfrmt, char* form, siz { const char* p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) - p++; /* skip flags */ + p++; // skip flags if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit(uchar(*p))) - p++; /* skip width */ + p++; // skip width if (isdigit(uchar(*p))) - p++; /* (2 digits at most) */ + p++; // (2 digits at most) if (*p == '.') { p++; if (isdigit(uchar(*p))) - p++; /* skip precision */ + p++; // skip precision if (isdigit(uchar(*p))) - p++; /* (2 digits at most) */ + p++; // (2 digits at most) } if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); @@ -967,11 +967,11 @@ static int str_format(lua_State* L) if (*strfrmt != L_ESC) luaL_addchar(&b, *strfrmt++); else if (*++strfrmt == L_ESC) - luaL_addchar(&b, *strfrmt++); /* %% */ + luaL_addchar(&b, *strfrmt++); // %% else - { /* format item */ - char form[MAX_FORMAT]; /* to store the format (`%...') */ - char buff[MAX_ITEM]; /* to store the formatted item */ + { // format item + char form[MAX_FORMAT]; // to store the format (`%...') + char buff[MAX_ITEM]; // to store the formatted item if (++arg > top) luaL_error(L, "missing argument #%d", arg); size_t formatItemSize = 0; @@ -981,14 +981,14 @@ static int str_format(lua_State* L) { case 'c': { - sprintf(buff, form, (int)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addInt64Format(form, formatIndicator, formatItemSize); - sprintf(buff, form, (long long)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (long long)luaL_checknumber(L, arg)); break; } case 'o': @@ -999,7 +999,7 @@ static int str_format(lua_State* L) double argValue = luaL_checknumber(L, arg); addInt64Format(form, formatIndicator, formatItemSize); unsigned long long v = (argValue < 0) ? (unsigned long long)(long long)argValue : (unsigned long long)argValue; - sprintf(buff, form, v); + snprintf(buff, sizeof(buff), form, v); break; } case 'e': @@ -1008,13 +1008,13 @@ static int str_format(lua_State* L) case 'g': case 'G': { - sprintf(buff, form, (double)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (double)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, &b, arg); - continue; /* skip the 'addsize' at the end */ + continue; // skip the 'addsize' at the end } case 's': { @@ -1026,60 +1026,35 @@ static int str_format(lua_State* L) keep original string */ lua_pushvalue(L, arg); luaL_addvalue(&b); - continue; /* skip the `addsize' at the end */ + continue; // skip the `addsize' at the end } else { - sprintf(buff, form, s); + snprintf(buff, sizeof(buff), form, s); break; } } case '*': { if (!FFlag::LuauTostringFormatSpecifier) - { luaL_error(L, "invalid option '%%*' to 'format'"); - break; - } if (formatItemSize != 1) - { luaL_error(L, "'%%*' does not take a form"); - } size_t length; const char* string = luaL_tolstring(L, arg, &length); luaL_addlstring(&b, string, length); - continue; /* skip the `addsize' at the end */ } default: - { /* also treat cases `pnLlh' */ + { // also treat cases `pnLlh' luaL_error(L, "invalid option '%%%c' to 'format'", *(strfrmt - 1)); } } - luaL_addlstring(&b, buff, strlen(buff)); - } - } - luaL_pushresult(&b); - return 1; -} - -static int str_split(lua_State* L) -{ - size_t haystackLen; - const char* haystack = luaL_checklstring(L, 1, &haystackLen); - size_t needleLen; - const char* needle = luaL_optlstring(L, 2, ",", &needleLen); - - const char* begin = haystack; - const char* end = haystack + haystackLen; const char* spanStart = begin; int numMatches = 0; - - lua_createtable(L, 0, 0); - if (needleLen == 0) begin++; @@ -1120,31 +1095,31 @@ static int str_split(lua_State* L) ** ======================================================= */ -/* value used for padding */ +// value used for padding #if !defined(LUAL_PACKPADBYTE) #define LUAL_PACKPADBYTE 0x00 #endif -/* maximum size for the binary representation of an integer */ +// maximum size for the binary representation of an integer #define MAXINTSIZE 16 -/* number of bits in a character */ +// number of bits in a character #define NB CHAR_BIT -/* mask for one character (NB 1's) */ +// mask for one character (NB 1's) #define MC ((1 << NB) - 1) -/* internal size of integers used for pack/unpack */ +// internal size of integers used for pack/unpack #define SZINT (int)sizeof(long long) -/* dummy union to get native endianness */ +// dummy union to get native endianness static const union { int dummy; - char little; /* true iff machine is little endian */ + char little; // true iff machine is little endian } nativeendian = {1}; -/* assume we need to align for double & pointers */ +// assume we need to align for double & pointers #define MAXALIGN 8 /* @@ -1155,7 +1130,7 @@ typedef union Ftypes float f; double d; double n; - char buff[5 * sizeof(double)]; /* enough for any float type */ + char buff[5 * sizeof(double)]; // enough for any float type } Ftypes; /* @@ -1173,15 +1148,15 @@ typedef struct Header */ typedef enum KOption { - Kint, /* signed integers */ - Kuint, /* unsigned integers */ - Kfloat, /* floating-point numbers */ - Kchar, /* fixed-length strings */ - Kstring, /* strings with prefixed length */ - Kzstr, /* zero-terminated strings */ - Kpadding, /* padding */ - Kpaddalign, /* padding for alignment */ - Knop /* no-op (configuration or spaces) */ + Kint, // signed integers + Kuint, // unsigned integers + Kfloat, // floating-point numbers + Kchar, // fixed-length strings + Kstring, // strings with prefixed length + Kzstr, // zero-terminated strings + Kpadding, // padding + Kpaddalign, // padding for alignment + Knop // no-op (configuration or spaces) } KOption; /* @@ -1195,8 +1170,8 @@ static int digit(int c) static int getnum(Header* h, const char** fmt, int df) { - if (!digit(**fmt)) /* no number? */ - return df; /* return default value */ + if (!digit(**fmt)) // no number? + return df; // return default value else { int a = 0; @@ -1238,7 +1213,7 @@ static void initheader(lua_State* L, Header* h) static KOption getoption(Header* h, const char** fmt, int* size) { int opt = *((*fmt)++); - *size = 0; /* default */ + *size = 0; // default switch (opt) { case 'b': @@ -1330,19 +1305,19 @@ static KOption getoption(Header* h, const char** fmt, int* size) static KOption getdetails(Header* h, size_t totalsize, const char** fmt, int* psize, int* ntoalign) { KOption opt = getoption(h, fmt, psize); - int align = *psize; /* usually, alignment follows size */ + int align = *psize; // usually, alignment follows size if (opt == Kpaddalign) - { /* 'X' gets alignment from following option */ + { // 'X' gets alignment from following option if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0) luaL_argerror(h->L, 1, "invalid next option for option 'X'"); } - if (align <= 1 || opt == Kchar) /* need no alignment? */ + if (align <= 1 || opt == Kchar) // need no alignment? *ntoalign = 0; else { - if (align > h->maxalign) /* enforce maximum alignment */ + if (align > h->maxalign) // enforce maximum alignment align = h->maxalign; - if ((align & (align - 1)) != 0) /* is 'align' not a power of 2? */ + if ((align & (align - 1)) != 0) // is 'align' not a power of 2? luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); } @@ -1360,18 +1335,18 @@ static void packint(luaL_Buffer* b, unsigned long long n, int islittle, int size LUAU_ASSERT(size <= MAXINTSIZE); char buff[MAXINTSIZE]; int i; - buff[islittle ? 0 : size - 1] = (char)(n & MC); /* first byte */ + buff[islittle ? 0 : size - 1] = (char)(n & MC); // first byte for (i = 1; i < size; i++) { n >>= NB; buff[islittle ? i : size - 1 - i] = (char)(n & MC); } if (neg && size > SZINT) - { /* negative number need sign extension? */ - for (i = SZINT; i < size; i++) /* correct extra bytes */ + { // negative number need sign extension? + for (i = SZINT; i < size; i++) // correct extra bytes buff[islittle ? i : size - 1 - i] = (char)MC; } - luaL_addlstring(b, buff, size); /* add result to buffer */ + luaL_addlstring(b, buff, size); // add result to buffer } /* @@ -1397,11 +1372,11 @@ static int str_pack(lua_State* L) { luaL_Buffer b; Header h; - const char* fmt = luaL_checkstring(L, 1); /* format string */ - int arg = 1; /* current argument to pack */ - size_t totalsize = 0; /* accumulate total size of result */ + const char* fmt = luaL_checkstring(L, 1); // format string + int arg = 1; // current argument to pack + size_t totalsize = 0; // accumulate total size of result initheader(L, &h); - lua_pushnil(L); /* mark to separate arguments from string buffer */ + lua_pushnil(L); // mark to separate arguments from string buffer luaL_buffinit(L, &b); while (*fmt != '\0') { @@ -1409,15 +1384,15 @@ static int str_pack(lua_State* L) KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); totalsize += ntoalign + size; while (ntoalign-- > 0) - luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ + luaL_addchar(&b, LUAL_PACKPADBYTE); // fill alignment arg++; switch (opt) { case Kint: - { /* signed integers */ + { // signed integers long long n = (long long)luaL_checknumber(L, arg); if (size < SZINT) - { /* need overflow check? */ + { // need overflow check? long long lim = (long long)1 << ((size * NB) - 1); luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); } @@ -1425,64 +1400,64 @@ static int str_pack(lua_State* L) break; } case Kuint: - { /* unsigned integers */ + { // unsigned integers long long n = (long long)luaL_checknumber(L, arg); - if (size < SZINT) /* need overflow check? */ + if (size < SZINT) // need overflow check? luaL_argcheck(L, (unsigned long long)n < ((unsigned long long)1 << (size * NB)), arg, "unsigned overflow"); packint(&b, (unsigned long long)n, h.islittle, size, 0); break; } case Kfloat: - { /* floating-point options */ + { // floating-point options volatile Ftypes u; char buff[MAXINTSIZE]; - double n = luaL_checknumber(L, arg); /* get argument */ + double n = luaL_checknumber(L, arg); // get argument if (size == sizeof(u.f)) - u.f = (float)n; /* copy it into 'u' */ + u.f = (float)n; // copy it into 'u' else if (size == sizeof(u.d)) u.d = (double)n; else u.n = n; - /* move 'u' to final result, correcting endianness if needed */ + // move 'u' to final result, correcting endianness if needed copywithendian(buff, u.buff, size, h.islittle); luaL_addlstring(&b, buff, size); break; } case Kchar: - { /* fixed-size string */ + { // fixed-size string size_t len; const char* s = luaL_checklstring(L, arg, &len); luaL_argcheck(L, len <= (size_t)size, arg, "string longer than given size"); - luaL_addlstring(&b, s, len); /* add string */ - while (len++ < (size_t)size) /* pad extra space */ + luaL_addlstring(&b, s, len); // add string + while (len++ < (size_t)size) // pad extra space luaL_addchar(&b, LUAL_PACKPADBYTE); break; } case Kstring: - { /* strings with length count */ + { // strings with length count size_t len; const char* s = luaL_checklstring(L, arg, &len); luaL_argcheck(L, size >= (int)sizeof(size_t) || len < ((size_t)1 << (size * NB)), arg, "string length does not fit in given size"); - packint(&b, len, h.islittle, size, 0); /* pack length */ + packint(&b, len, h.islittle, size, 0); // pack length luaL_addlstring(&b, s, len); totalsize += len; break; } case Kzstr: - { /* zero-terminated string */ + { // zero-terminated string size_t len; const char* s = luaL_checklstring(L, arg, &len); luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros"); luaL_addlstring(&b, s, len); - luaL_addchar(&b, '\0'); /* add zero at the end */ + luaL_addchar(&b, '\0'); // add zero at the end totalsize += len + 1; break; } case Kpadding: - luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ + luaL_addchar(&b, LUAL_PACKPADBYTE); // FALLTHROUGH case Kpaddalign: case Knop: - arg--; /* undo increment */ + arg--; // undo increment break; } } @@ -1493,15 +1468,15 @@ static int str_pack(lua_State* L) static int str_packsize(lua_State* L) { Header h; - const char* fmt = luaL_checkstring(L, 1); /* format string */ - int totalsize = 0; /* accumulate total size of result */ + const char* fmt = luaL_checkstring(L, 1); // format string + int totalsize = 0; // accumulate total size of result initheader(L, &h); while (*fmt != '\0') { int size, ntoalign; KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1, "variable-length format"); - size += ntoalign; /* total space used by option */ + size += ntoalign; // total space used by option luaL_argcheck(L, totalsize <= MAXSSIZE - size, 1, "format result too large"); totalsize += size; } @@ -1528,15 +1503,15 @@ static long long unpackint(lua_State* L, const char* str, int islittle, int size res |= (unsigned char)str[islittle ? i : size - 1 - i]; } if (size < SZINT) - { /* real size smaller than int? */ + { // real size smaller than int? if (issigned) - { /* needs sign extension? */ + { // needs sign extension? unsigned long long mask = (unsigned long long)1 << (size * NB - 1); - res = ((res ^ mask) - mask); /* do sign extension */ + res = ((res ^ mask) - mask); // do sign extension } } else if (size > SZINT) - { /* must check unread bytes */ + { // must check unread bytes int mask = (!issigned || (long long)res >= 0) ? 0 : MC; for (i = limit; i < size; i++) { @@ -1556,7 +1531,7 @@ static int str_unpack(lua_State* L) int pos = posrelat(luaL_optinteger(L, 3, 1), ld) - 1; if (pos < 0) pos = 0; - int n = 0; /* number of results */ + int n = 0; // number of results luaL_argcheck(L, size_t(pos) <= ld, 3, "initial position out of string"); initheader(L, &h); while (*fmt != '\0') @@ -1564,8 +1539,8 @@ static int str_unpack(lua_State* L) int size, ntoalign; KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2, "data string too short"); - pos += ntoalign; /* skip alignment */ - /* stack space for item + next position */ + pos += ntoalign; // skip alignment + // stack space for item + next position luaL_checkstack(L, 2, "too many results"); n++; switch (opt) @@ -1606,7 +1581,7 @@ static int str_unpack(lua_State* L) size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short"); lua_pushlstring(L, data + pos + size, len); - pos += (int)len; /* skip string */ + pos += (int)len; // skip string break; } case Kzstr: @@ -1614,22 +1589,22 @@ static int str_unpack(lua_State* L) size_t len = strlen(data + pos); luaL_argcheck(L, pos + len < ld, 2, "unfinished string for format 'z'"); lua_pushlstring(L, data + pos, len); - pos += (int)len + 1; /* skip string plus final '\0' */ + pos += (int)len + 1; // skip string plus final '\0' break; } case Kpaddalign: case Kpadding: case Knop: - n--; /* undo increment */ + n--; // undo increment break; } pos += size; } - lua_pushinteger(L, pos + 1); /* next position */ + lua_pushinteger(L, pos + 1); // next position return n + 1; } -/* }====================================================== */ +// }====================================================== static const luaL_Reg strlib[] = { {"byte", str_byte}, @@ -1654,14 +1629,14 @@ static const luaL_Reg strlib[] = { static void createmetatable(lua_State* L) { - lua_createtable(L, 0, 1); /* create metatable for strings */ - lua_pushliteral(L, ""); /* dummy string */ + lua_createtable(L, 0, 1); // create metatable for strings + lua_pushliteral(L, ""); // dummy string lua_pushvalue(L, -2); - lua_setmetatable(L, -2); /* set string metatable */ - lua_pop(L, 1); /* pop dummy string */ - lua_pushvalue(L, -2); /* string library... */ - lua_setfield(L, -2, "__index"); /* ...is the __index metamethod */ - lua_pop(L, 1); /* pop metatable */ + lua_setmetatable(L, -2); // set string metatable + lua_pop(L, 1); // pop dummy string + lua_pushvalue(L, -2); // string library... + lua_setfield(L, -2, "__index"); // ...is the __index metamethod + lua_pop(L, 1); // pop metatable } /* diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 79e65919..8d59ecbc 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -44,13 +44,10 @@ static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); -// reset cache of absent metamethods, cache is updated in luaT_gettm -#define invalidateTMcache(t) t->tmcache = 0 - // empty hash data points to dummynode so that we can always dereference it const LuaNode luaH_dummynode = { - {{NULL}, {0}, LUA_TNIL}, /* value */ - {{NULL}, {0}, LUA_TNIL, 0} /* key */ + {{NULL}, {0}, LUA_TNIL}, // value + {{NULL}, {0}, LUA_TNIL, 0} // key }; #define dummynode (&luaH_dummynode) @@ -173,52 +170,52 @@ static int findindex(lua_State* L, Table* t, StkId key) { int i; if (ttisnil(key)) - return -1; /* first iteration */ + return -1; // first iteration i = ttisnumber(key) ? arrayindex(nvalue(key)) : -1; - if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ - return i - 1; /* yes; that's the index (corrected to C) */ + if (0 < i && i <= t->sizearray) // is `key' inside array part? + return i - 1; // yes; that's the index (corrected to C) else { LuaNode* n = mainposition(t, key); for (;;) - { /* check whether `key' is somewhere in the chain */ - /* key may be dead already, but it is ok to use it in `next' */ + { // check whether `key' is somewhere in the chain + // key may be dead already, but it is ok to use it in `next' if (luaO_rawequalKey(gkey(n), key) || (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && gcvalue(gkey(n)) == gcvalue(key))) { - i = cast_int(n - gnode(t, 0)); /* key index in hash table */ - /* hash elements are numbered after array ones */ + i = cast_int(n - gnode(t, 0)); // key index in hash table + // hash elements are numbered after array ones return i + t->sizearray; } if (gnext(n) == 0) break; n += gnext(n); } - luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + luaG_runerror(L, "invalid key to 'next'"); // key not found } } int luaH_next(lua_State* L, Table* t, StkId key) { - int i = findindex(L, t, key); /* find original element */ + int i = findindex(L, t, key); // find original element for (i++; i < t->sizearray; i++) - { /* try first array part */ + { // try first array part if (!ttisnil(&t->array[i])) - { /* a non-nil value? */ + { // a non-nil value? setnvalue(key, cast_num(i + 1)); setobj2s(L, key + 1, &t->array[i]); return 1; } } for (i -= t->sizearray; i < sizenode(t); i++) - { /* then hash part */ + { // then hash part if (!ttisnil(gval(gnode(t, i)))) - { /* a non-nil value? */ + { // a non-nil value? getnodekey(L, key, gnode(t, i)); setobj2s(L, key + 1, gval(gnode(t, i))); return 1; } } - return 0; /* no more elements */ + return 0; // no more elements } /* @@ -238,23 +235,23 @@ int luaH_next(lua_State* L, Table* t, StkId key) static int computesizes(int nums[], int* narray) { int i; - int twotoi; /* 2^i */ - int a = 0; /* number of elements smaller than 2^i */ - int na = 0; /* number of elements to go to array part */ - int n = 0; /* optimal size for array part */ + int twotoi; // 2^i + int a = 0; // number of elements smaller than 2^i + int na = 0; // number of elements to go to array part + int n = 0; // optimal size for array part for (i = 0, twotoi = 1; twotoi / 2 < *narray; i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi / 2) - { /* more than half elements present? */ - n = twotoi; /* optimal size (till now) */ - na = a; /* all elements smaller than n will go to array part */ + { // more than half elements present? + n = twotoi; // optimal size (till now) + na = a; // all elements smaller than n will go to array part } } if (a == *narray) - break; /* all elements already counted */ + break; // all elements already counted } *narray = n; LUAU_ASSERT(*narray / 2 <= na && na <= *narray); @@ -265,8 +262,8 @@ static int countint(double key, int* nums) { int k = arrayindex(key); if (0 < k && k <= MAXSIZE) - { /* is `key' an appropriate array index? */ - nums[ceillog2(k)]++; /* count as such */ + { // is `key' an appropriate array index? + nums[ceillog2(k)]++; // count as such return 1; } else @@ -276,20 +273,20 @@ static int countint(double key, int* nums) static int numusearray(const Table* t, int* nums) { int lg; - int ttlg; /* 2^lg */ - int ause = 0; /* summation of `nums' */ - int i = 1; /* count to traverse all array keys */ + int ttlg; // 2^lg + int ause = 0; // summation of `nums' + int i = 1; // count to traverse all array keys for (lg = 0, ttlg = 1; lg <= MAXBITS; lg++, ttlg *= 2) - { /* for each slice */ - int lc = 0; /* counter */ + { // for each slice + int lc = 0; // counter int lim = ttlg; if (lim > t->sizearray) { - lim = t->sizearray; /* adjust upper limit */ + lim = t->sizearray; // adjust upper limit if (i > lim) - break; /* no more elements to count */ + break; // no more elements to count } - /* count elements in range (2^(lg-1), 2^lg] */ + // count elements in range (2^(lg-1), 2^lg] for (; i <= lim; i++) { if (!ttisnil(&t->array[i - 1])) @@ -303,8 +300,8 @@ static int numusearray(const Table* t, int* nums) static int numusehash(const Table* t, int* nums, int* pnasize) { - int totaluse = 0; /* total number of elements */ - int ause = 0; /* summation of `nums' */ + int totaluse = 0; // total number of elements + int ause = 0; // summation of `nums' int i = sizenode(t); while (i--) { @@ -335,8 +332,8 @@ static void setnodevector(lua_State* L, Table* t, int size) { int lsize; if (size == 0) - { /* no elements to hash part? */ - t->node = cast_to(LuaNode*, dummynode); /* use common `dummynode' */ + { // no elements to hash part? + t->node = cast_to(LuaNode*, dummynode); // use common `dummynode' lsize = 0; } else @@ -357,7 +354,7 @@ static void setnodevector(lua_State* L, Table* t, int size) } t->lsizenode = cast_byte(lsize); t->nodemask8 = cast_byte((1 << lsize) - 1); - t->lastfree = size; /* all positions are free */ + t->lastfree = size; // all positions are free } static TValue* newkey(lua_State* L, Table* t, const TValue* key); @@ -382,17 +379,17 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) luaG_runerror(L, "table overflow"); int oldasize = t->sizearray; int oldhsize = t->lsizenode; - LuaNode* nold = t->node; /* save old hash ... */ - if (nasize > oldasize) /* array part must grow? */ + LuaNode* nold = t->node; // save old hash ... + if (nasize > oldasize) // array part must grow? setarrayvector(L, t, nasize); - /* create new hash part with appropriate size */ + // create new hash part with appropriate size setnodevector(L, t, nhsize); - /* used for the migration check at the end */ + // used for the migration check at the end LuaNode* nnew = t->node; if (nasize < oldasize) - { /* array part must shrink? */ + { // array part must shrink? t->sizearray = nasize; - /* re-insert elements from vanishing slice */ + // re-insert elements from vanishing slice for (int i = nasize; i < oldasize; i++) { if (!ttisnil(&t->array[i])) @@ -402,12 +399,12 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) setobjt2t(L, newkey(L, t, &ok), &t->array[i]); } } - /* shrink array */ + // shrink array luaM_reallocarray(L, t->array, oldasize, nasize, TValue, t->memcat); } - /* used for the migration check at the end */ + // used for the migration check at the end TValue* anew = t->array; - /* re-insert elements from hash part */ + // re-insert elements from hash part for (int i = twoto(oldhsize) - 1; i >= 0; i--) { LuaNode* old = nold + i; @@ -419,19 +416,19 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) } } - /* make sure we haven't recursively rehashed during element migration */ + // make sure we haven't recursively rehashed during element migration LUAU_ASSERT(nnew == t->node); LUAU_ASSERT(anew == t->array); if (nold != dummynode) - luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); /* free old array */ + luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); // free old array } static int adjustasize(Table* t, int size, const TValue* ek) { bool tbound = t->node != dummynode || size < t->sizearray; int ekindex = ek && ttisnumber(ek) ? arrayindex(nvalue(ek)) : -1; - /* move the array size up until the boundary is guaranteed to be inside the array part */ + // move the array size up until the boundary is guaranteed to be inside the array part while (size + 1 == ekindex || (tbound && !ttisnil(luaH_getnum(t, size + 1)))) size++; return size; @@ -451,22 +448,22 @@ void luaH_resizehash(lua_State* L, Table* t, int nhsize) static void rehash(lua_State* L, Table* t, const TValue* ek) { - int nums[MAXBITS + 1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */ + int nums[MAXBITS + 1]; // nums[i] = number of keys between 2^(i-1) and 2^i for (int i = 0; i <= MAXBITS; i++) - nums[i] = 0; /* reset counts */ - int nasize = numusearray(t, nums); /* count keys in array part */ - int totaluse = nasize; /* all those keys are integer keys */ - totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ - /* count extra key */ + nums[i] = 0; // reset counts + int nasize = numusearray(t, nums); // count keys in array part + int totaluse = nasize; // all those keys are integer keys + totaluse += numusehash(t, nums, &nasize); // count keys in hash part + // count extra key if (ttisnumber(ek)) nasize += countint(nvalue(ek), nums); totaluse++; - /* compute new size for array part */ + // compute new size for array part int na = computesizes(nums, &nasize); int nh = totaluse - na; - /* enforce the boundary invariant; for performance, only do hash lookups if we must */ + // enforce the boundary invariant; for performance, only do hash lookups if we must nasize = adjustasize(t, nasize, ek); - /* resize the table to new computed sizes */ + // resize the table to new computed sizes resize(L, t, nasize, nh); } @@ -514,7 +511,7 @@ static LuaNode* getfreepos(Table* t) if (ttisnil(gkey(n))) return n; } - return NULL; /* could not find a free place */ + return NULL; // could not find a free place } /* @@ -526,24 +523,24 @@ static LuaNode* getfreepos(Table* t) */ static TValue* newkey(lua_State* L, Table* t, const TValue* key) { - /* enforce boundary invariant */ + // enforce boundary invariant if (ttisnumber(key) && nvalue(key) == t->sizearray + 1) { - rehash(L, t, key); /* grow table */ + rehash(L, t, key); // grow table - /* after rehash, numeric keys might be located in the new array part, but won't be found in the node part */ + // after rehash, numeric keys might be located in the new array part, but won't be found in the node part return arrayornewkey(L, t, key); } LuaNode* mp = mainposition(t, key); if (!ttisnil(gval(mp)) || mp == dummynode) { - LuaNode* n = getfreepos(t); /* get a free place */ + LuaNode* n = getfreepos(t); // get a free place if (n == NULL) - { /* cannot find a free place? */ - rehash(L, t, key); /* grow table */ + { // cannot find a free place? + rehash(L, t, key); // grow table - /* after rehash, numeric keys might be located in the new array part, but won't be found in the node part */ + // after rehash, numeric keys might be located in the new array part, but won't be found in the node part return arrayornewkey(L, t, key); } LUAU_ASSERT(n != dummynode); @@ -551,24 +548,24 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key) getnodekey(L, &mk, mp); LuaNode* othern = mainposition(t, &mk); if (othern != mp) - { /* is colliding node out of its main position? */ - /* yes; move colliding node into free position */ + { // is colliding node out of its main position? + // yes; move colliding node into free position while (othern + gnext(othern) != mp) - othern += gnext(othern); /* find previous */ - gnext(othern) = cast_int(n - othern); /* redo the chain with `n' in place of `mp' */ - *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + othern += gnext(othern); // find previous + gnext(othern) = cast_int(n - othern); // redo the chain with `n' in place of `mp' + *n = *mp; // copy colliding node into free pos. (mp->next also goes) if (gnext(mp) != 0) { - gnext(n) += cast_int(mp - n); /* correct 'next' */ - gnext(mp) = 0; /* now 'mp' is free */ + gnext(n) += cast_int(mp - n); // correct 'next' + gnext(mp) = 0; // now 'mp' is free } setnilvalue(gval(mp)); } else - { /* colliding node is in its own main position */ - /* new node will go into free position */ + { // colliding node is in its own main position + // new node will go into free position if (gnext(mp) != 0) - gnext(n) = cast_int((mp + gnext(mp)) - n); /* chain new position */ + gnext(n) = cast_int((mp + gnext(mp)) - n); // chain new position else LUAU_ASSERT(gnext(n) == 0); gnext(mp) = cast_int(n - mp); @@ -586,7 +583,7 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key) */ const TValue* luaH_getnum(Table* t, int key) { - /* (1 <= key && key <= t->sizearray) */ + // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) return &t->array[key - 1]; else if (t->node != dummynode) @@ -594,9 +591,9 @@ const TValue* luaH_getnum(Table* t, int key) double nk = cast_num(key); LuaNode* n = hashnum(t, nk); for (;;) - { /* check whether `key' is somewhere in the chain */ + { // check whether `key' is somewhere in the chain if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) - return gval(n); /* that's it */ + return gval(n); // that's it if (gnext(n) == 0) break; n += gnext(n); @@ -614,9 +611,9 @@ const TValue* luaH_getstr(Table* t, TString* key) { LuaNode* n = hashstr(t, key); for (;;) - { /* check whether `key' is somewhere in the chain */ + { // check whether `key' is somewhere in the chain if (ttisstring(gkey(n)) && tsvalue(gkey(n)) == key) - return gval(n); /* that's it */ + return gval(n); // that's it if (gnext(n) == 0) break; n += gnext(n); @@ -640,17 +637,17 @@ const TValue* luaH_get(Table* t, const TValue* key) int k; double n = nvalue(key); luai_num2int(k, n); - if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */ - return luaH_getnum(t, k); /* use specialized version */ - /* else go through */ + if (luai_numeq(cast_num(k), nvalue(key))) // index is int? + return luaH_getnum(t, k); // use specialized version + // else go through } default: { LuaNode* n = mainposition(t, key); for (;;) - { /* check whether `key' is somewhere in the chain */ + { // check whether `key' is somewhere in the chain if (luaO_rawequalKey(gkey(n), key)) - return gval(n); /* that's it */ + return gval(n); // that's it if (gnext(n) == 0) break; n += gnext(n); @@ -667,23 +664,26 @@ TValue* luaH_set(lua_State* L, Table* t, const TValue* key) if (p != luaO_nilobject) return cast_to(TValue*, p); else - { - if (ttisnil(key)) - luaG_runerror(L, "table index is nil"); - else if (ttisnumber(key) && luai_numisnan(nvalue(key))) - luaG_runerror(L, "table index is NaN"); - else if (ttisvector(key) && luai_vecisnan(vvalue(key))) - luaG_runerror(L, "table index contains NaN"); - return newkey(L, t, key); - } + return luaH_newkey(L, t, key); +} + +TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) +{ + if (ttisnil(key)) + luaG_runerror(L, "table index is nil"); + else if (ttisnumber(key) && luai_numisnan(nvalue(key))) + luaG_runerror(L, "table index is NaN"); + else if (ttisvector(key) && luai_vecisnan(vvalue(key))) + luaG_runerror(L, "table index contains NaN"); + return newkey(L, t, key); } TValue* luaH_setnum(lua_State* L, Table* t, int key) { - /* (1 <= key && key <= t->sizearray) */ + // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) return &t->array[key - 1]; - /* hash fallback */ + // hash fallback const TValue* p = luaH_getnum(t, key); if (p != luaO_nilobject) return cast_to(TValue*, p); @@ -739,9 +739,9 @@ int luaH_getn(Table* t) if (boundary > 0) { if (!ttisnil(&t->array[t->sizearray - 1]) && t->node == dummynode) - return t->sizearray; /* fast-path: the end of the array in `t' already refers to a boundary */ + return t->sizearray; // fast-path: the end of the array in `t' already refers to a boundary if (boundary < t->sizearray && !ttisnil(&t->array[boundary - 1]) && ttisnil(&t->array[boundary])) - return boundary; /* fast-path: boundary already refers to a boundary in `t' */ + return boundary; // fast-path: boundary already refers to a boundary in `t' int foundboundary = updateaboundary(t, boundary); if (foundboundary > 0) @@ -767,7 +767,7 @@ int luaH_getn(Table* t) } else { - /* validate boundary invariant */ + // validate boundary invariant LUAU_ASSERT(t->node == dummynode || ttisnil(luaH_getnum(t, j + 1))); return j; } @@ -812,7 +812,7 @@ Table* luaH_clone(lua_State* L, Table* tt) void luaH_clear(Table* tt) { - /* clear array part */ + // clear array part for (int i = 0; i < tt->sizearray; ++i) { setnilvalue(&tt->array[i]); @@ -820,7 +820,7 @@ void luaH_clear(Table* tt) maybesetaboundary(tt, 0); - /* clear hash part */ + // clear hash part if (tt->node != dummynode) { int size = sizenode(tt); @@ -834,6 +834,6 @@ void luaH_clear(Table* tt) } } - /* back to empty -> no tag methods present */ + // back to empty -> no tag methods present tt->tmcache = cast_byte(~0); } diff --git a/VM/src/ltable.h b/VM/src/ltable.h index e8413c85..021f21bf 100644 --- a/VM/src/ltable.h +++ b/VM/src/ltable.h @@ -11,12 +11,16 @@ #define gval2slot(t, v) int(cast_to(LuaNode*, static_cast(v)) - t->node) +// reset cache of absent metamethods, cache is updated in luaT_gettm +#define invalidateTMcache(t) t->tmcache = 0 + LUAI_FUNC const TValue* luaH_getnum(Table* t, int key); LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key); LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key); LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key); LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key); LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key); +LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key); LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash); LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize); LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize); @@ -26,4 +30,6 @@ LUAI_FUNC int luaH_getn(Table* t); LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt); LUAI_FUNC void luaH_clear(Table* tt); +#define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot))) + extern const LuaNode luaH_dummynode; diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index dc653338..6dd94149 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -18,13 +18,13 @@ static int foreachi(lua_State* L) int n = lua_objlen(L, 1); for (i = 1; i <= n; i++) { - lua_pushvalue(L, 2); /* function */ - lua_pushinteger(L, i); /* 1st argument */ - lua_rawgeti(L, 1, i); /* 2nd argument */ + lua_pushvalue(L, 2); // function + lua_pushinteger(L, i); // 1st argument + lua_rawgeti(L, 1, i); // 2nd argument lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; - lua_pop(L, 1); /* remove nil result */ + lua_pop(L, 1); // remove nil result } return 0; } @@ -33,16 +33,16 @@ static int foreach (lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 2, LUA_TFUNCTION); - lua_pushnil(L); /* first key */ + lua_pushnil(L); // first key while (lua_next(L, 1)) { - lua_pushvalue(L, 2); /* function */ - lua_pushvalue(L, -3); /* key */ - lua_pushvalue(L, -3); /* value */ + lua_pushvalue(L, 2); // function + lua_pushvalue(L, -3); // key + lua_pushvalue(L, -3); // value lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; - lua_pop(L, 2); /* remove value and result */ + lua_pop(L, 2); // remove value and result } return 0; } @@ -51,10 +51,10 @@ static int maxn(lua_State* L) { double max = 0; luaL_checktype(L, 1, LUA_TTABLE); - lua_pushnil(L); /* first key */ + lua_pushnil(L); // first key while (lua_next(L, 1)) { - lua_pop(L, 1); /* remove value */ + lua_pop(L, 1); // remove value if (lua_type(L, -1) == LUA_TNUMBER) { double v = lua_tonumber(L, -1); @@ -81,7 +81,7 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) if (dst->readonly) luaG_readonlyerror(L); - int n = e - f + 1; /* number of elements to move */ + int n = e - f + 1; // number of elements to move if (cast_to(unsigned int, f - 1) < cast_to(unsigned int, src->sizearray) && cast_to(unsigned int, t - 1) < cast_to(unsigned int, dst->sizearray) && @@ -137,19 +137,19 @@ static int tinsert(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); int n = lua_objlen(L, 1); - int pos; /* where to insert new element */ + int pos; // where to insert new element switch (lua_gettop(L)) { case 2: - { /* called with only 2 arguments */ - pos = n + 1; /* insert new element at the end */ + { // called with only 2 arguments + pos = n + 1; // insert new element at the end break; } case 3: { - pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ + pos = luaL_checkinteger(L, 2); // 2nd argument is the position - /* move up elements if necessary */ + // move up elements if necessary if (1 <= pos && pos <= n) moveelements(L, 1, 1, pos, n, pos + 1); break; @@ -159,7 +159,7 @@ static int tinsert(lua_State* L) luaL_error(L, "wrong number of arguments to 'insert'"); } } - lua_rawseti(L, 1, pos); /* t[pos] = v */ + lua_rawseti(L, 1, pos); // t[pos] = v return 0; } @@ -169,14 +169,14 @@ static int tremove(lua_State* L) int n = lua_objlen(L, 1); int pos = luaL_optinteger(L, 2, n); - if (!(1 <= pos && pos <= n)) /* position is outside bounds? */ - return 0; /* nothing to remove */ - lua_rawgeti(L, 1, pos); /* result = t[pos] */ + if (!(1 <= pos && pos <= n)) // position is outside bounds? + return 0; // nothing to remove + lua_rawgeti(L, 1, pos); // result = t[pos] moveelements(L, 1, 1, pos + 1, n, pos); lua_pushnil(L); - lua_rawseti(L, 1, n); /* t[n] = nil */ + lua_rawseti(L, 1, n); // t[n] = nil return 1; } @@ -192,28 +192,28 @@ static int tmove(lua_State* L) int f = luaL_checkinteger(L, 2); int e = luaL_checkinteger(L, 3); int t = luaL_checkinteger(L, 4); - int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + int tt = !lua_isnoneornil(L, 5) ? 5 : 1; // destination table luaL_checktype(L, tt, LUA_TTABLE); if (e >= f) - { /* otherwise, nothing to move */ + { // otherwise, nothing to move luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move"); - int n = e - f + 1; /* number of elements to move */ + int n = e - f + 1; // number of elements to move luaL_argcheck(L, t <= INT_MAX - n + 1, 4, "destination wrap around"); Table* dst = hvalue(L->base + (tt - 1)); - if (dst->readonly) /* also checked in moveelements, but this blocks resizes of r/o tables */ + if (dst->readonly) // also checked in moveelements, but this blocks resizes of r/o tables luaG_readonlyerror(L); if (t > 0 && (t - 1) <= dst->sizearray && (t - 1 + n) > dst->sizearray) - { /* grow the destination table array */ + { // grow the destination table array luaH_resizearray(L, dst, t - 1 + n); } moveelements(L, 1, tt, f, e, t); } - lua_pushvalue(L, tt); /* return destination table */ + lua_pushvalue(L, tt); // return destination table return 1; } @@ -240,7 +240,7 @@ static int tconcat(lua_State* L) addfield(L, &b, i); luaL_addlstring(&b, sep, lsep); } - if (i == last) /* add last value (if interval was not empty) */ + if (i == last) // add last value (if interval was not empty) addfield(L, &b, i); luaL_pushresult(&b); return 1; @@ -248,8 +248,8 @@ static int tconcat(lua_State* L) static int tpack(lua_State* L) { - int n = lua_gettop(L); /* number of elements to pack */ - lua_createtable(L, n, 1); /* create result table */ + int n = lua_gettop(L); // number of elements to pack + lua_createtable(L, n, 1); // create result table Table* t = hvalue(L->top - 1); @@ -259,11 +259,11 @@ static int tpack(lua_State* L) setobj2t(L, e, L->base + i); } - /* t.n = number of elements */ + // t.n = number of elements TValue* nv = luaH_setstr(L, t, luaS_newliteral(L, "n")); setnvalue(nv, n); - return 1; /* return table */ + return 1; // return table } static int tunpack(lua_State* L) @@ -274,8 +274,8 @@ static int tunpack(lua_State* L) int i = luaL_optinteger(L, 2, 1); int e = luaL_opt(L, luaL_checkinteger, 3, lua_objlen(L, 1)); if (i > e) - return 0; /* empty range */ - unsigned n = (unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ + return 0; // empty range + unsigned n = (unsigned)e - i; // number of elements minus 1 (avoid overflows) if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) luaL_error(L, "too many results to unpack"); @@ -288,10 +288,10 @@ static int tunpack(lua_State* L) } else { - /* push arg[i..e - 1] (to avoid overflows) */ + // push arg[i..e - 1] (to avoid overflows) for (; i < e; i++) lua_rawgeti(L, 1, i); - lua_rawgeti(L, 1, e); /* push last element */ + lua_rawgeti(L, 1, e); // push last element } return (int)n; } @@ -312,85 +312,85 @@ static void set2(lua_State* L, int i, int j) static int sort_comp(lua_State* L, int a, int b) { if (!lua_isnil(L, 2)) - { /* function? */ + { // function? int res; lua_pushvalue(L, 2); - lua_pushvalue(L, a - 1); /* -1 to compensate function */ - lua_pushvalue(L, b - 2); /* -2 to compensate function and `a' */ + lua_pushvalue(L, a - 1); // -1 to compensate function + lua_pushvalue(L, b - 2); // -2 to compensate function and `a' lua_call(L, 2, 1); res = lua_toboolean(L, -1); lua_pop(L, 1); return res; } - else /* a < b? */ + else // a < b? return lua_lessthan(L, a, b); } static void auxsort(lua_State* L, int l, int u) { while (l < u) - { /* for tail recursion */ + { // for tail recursion int i, j; - /* sort elements a[l], a[(l+u)/2] and a[u] */ + // sort elements a[l], a[(l+u)/2] and a[u] lua_rawgeti(L, 1, l); lua_rawgeti(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ - set2(L, l, u); /* swap a[l] - a[u] */ + if (sort_comp(L, -1, -2)) // a[u] < a[l]? + set2(L, l, u); // swap a[l] - a[u] else lua_pop(L, 2); if (u - l == 1) - break; /* only 2 elements */ + break; // only 2 elements i = (l + u) / 2; lua_rawgeti(L, 1, i); lua_rawgeti(L, 1, l); - if (sort_comp(L, -2, -1)) /* a[i]= P */ + { // invariant: a[l..i] <= P <= a[j..u] + // repeat ++i until a[i] >= P while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { if (i >= u) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[i] */ + lua_pop(L, 1); // remove a[i] } - /* repeat --j until a[j] <= P */ + // repeat --j until a[j] <= P while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { if (j <= l) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[j] */ + lua_pop(L, 1); // remove a[j] } if (j < i) { - lua_pop(L, 3); /* pop pivot, a[i], a[j] */ + lua_pop(L, 3); // pop pivot, a[i], a[j] break; } set2(L, i, j); } lua_rawgeti(L, 1, u - 1); lua_rawgeti(L, 1, i); - set2(L, u - 1, i); /* swap pivot (a[u-1]) with a[i] */ - /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ - /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ + set2(L, u - 1, i); // swap pivot (a[u-1]) with a[i] + // a[l..i-1] <= a[i] == P <= a[i+1..u] + // adjust so that smaller half is in [j..i] and larger one in [l..u] if (i - l < u - i) { j = l; @@ -403,23 +403,23 @@ static void auxsort(lua_State* L, int l, int u) i = u; u = j - 2; } - auxsort(L, j, i); /* call recursively the smaller one */ - } /* repeat the routine for the larger one */ + auxsort(L, j, i); // call recursively the smaller one + } // repeat the routine for the larger one } static int sort(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); int n = lua_objlen(L, 1); - luaL_checkstack(L, 40, ""); /* assume array is smaller than 2^40 */ - if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checkstack(L, 40, ""); // assume array is smaller than 2^40 + if (!lua_isnoneornil(L, 2)) // is there a 2nd argument? luaL_checktype(L, 2, LUA_TFUNCTION); - lua_settop(L, 2); /* make sure there is two arguments */ + lua_settop(L, 2); // make sure there is two arguments auxsort(L, 1, n); return 0; } -/* }====================================================== */ +// }====================================================== static int tcreate(lua_State* L) { diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index 49982b28..cb7ba097 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -12,7 +12,7 @@ // clang-format off const char* const luaT_typenames[] = { - /* ORDER TYPE */ + // ORDER TYPE "nil", "boolean", @@ -31,7 +31,7 @@ const char* const luaT_typenames[] = { }; const char* const luaT_eventname[] = { - /* ORDER TM */ + // ORDER TM "__index", "__newindex", @@ -70,12 +70,12 @@ void luaT_init(lua_State* L) for (i = 0; i < LUA_T_COUNT; i++) { L->global->ttname[i] = luaS_new(L, luaT_typenames[i]); - luaS_fix(L->global->ttname[i]); /* never collect these names */ + luaS_fix(L->global->ttname[i]); // never collect these names } for (i = 0; i < TM_N; i++) { L->global->tmname[i] = luaS_new(L, luaT_eventname[i]); - luaS_fix(L->global->tmname[i]); /* never collect these names */ + luaS_fix(L->global->tmname[i]); // never collect these names } } @@ -88,8 +88,8 @@ const TValue* luaT_gettm(Table* events, TMS event, TString* ename) const TValue* tm = luaH_getstr(events, ename); LUAU_ASSERT(event <= TM_EQ); if (ttisnil(tm)) - { /* no tag method? */ - events->tmcache |= cast_byte(1u << event); /* cache this fact */ + { // no tag method? + events->tmcache |= cast_byte(1u << event); // cache this fact return NULL; } else diff --git a/VM/src/ltm.h b/VM/src/ltm.h index e11ddb3a..f20ce1b2 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -20,7 +20,7 @@ typedef enum TM_ITER, TM_LEN, - TM_EQ, /* last tag method with `fast' access */ + TM_EQ, // last tag method with `fast' access TM_ADD, @@ -37,7 +37,7 @@ typedef enum TM_CONCAT, TM_TYPE, - TM_N /* number of elements in the enum */ + TM_N // number of elements in the enum } TMS; // clang-format on diff --git a/VM/src/ludata.h b/VM/src/ludata.h index f24e4a32..9b7ba26a 100644 --- a/VM/src/ludata.h +++ b/VM/src/ludata.h @@ -4,10 +4,10 @@ #include "lobject.h" -/* special tag value is used for user data with inline dtors */ +// special tag value is used for user data with inline dtors #define UTAG_IDTOR LUA_UTAG_LIMIT -/* special tag value is used for newproxy-created user data (all other user data objects are host-exposed) */ +// special tag value is used for newproxy-created user data (all other user data objects are host-exposed) #define UTAG_PROXY (LUA_UTAG_LIMIT + 1) #define sizeudata(len) (offsetof(Udata, data) + len) diff --git a/VM/src/lutf8lib.cpp b/VM/src/lutf8lib.cpp index 8bc8200a..837d0e12 100644 --- a/VM/src/lutf8lib.cpp +++ b/VM/src/lutf8lib.cpp @@ -8,8 +8,8 @@ #define iscont(p) ((*(p)&0xC0) == 0x80) -/* from strlib */ -/* translate a relative string position: negative means back from end */ +// from strlib +// translate a relative string position: negative means back from end static int u_posrelat(int pos, size_t len) { if (pos >= 0) @@ -28,28 +28,28 @@ static const char* utf8_decode(const char* o, int* val) static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF}; const unsigned char* s = (const unsigned char*)o; unsigned int c = s[0]; - unsigned int res = 0; /* final result */ - if (c < 0x80) /* ascii? */ + unsigned int res = 0; // final result + if (c < 0x80) // ascii? res = c; else { - int count = 0; /* to count number of continuation bytes */ + int count = 0; // to count number of continuation bytes while (c & 0x40) - { /* still have continuation bytes? */ - int cc = s[++count]; /* read next byte */ - if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ - return NULL; /* invalid byte sequence */ - res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ - c <<= 1; /* to test next bit */ + { // still have continuation bytes? + int cc = s[++count]; // read next byte + if ((cc & 0xC0) != 0x80) // not a continuation byte? + return NULL; // invalid byte sequence + res = (res << 6) | (cc & 0x3F); // add lower 6 bits from cont. byte + c <<= 1; // to test next bit } - res |= ((c & 0x7F) << (count * 5)); /* add first byte */ + res |= ((c & 0x7F) << (count * 5)); // add first byte if (count > 3 || res > MAXUNICODE || res <= limits[count]) - return NULL; /* invalid byte sequence */ - s += count; /* skip continuation bytes read */ + return NULL; // invalid byte sequence + s += count; // skip continuation bytes read } if (val) *val = res; - return (const char*)s + 1; /* +1 to include first byte */ + return (const char*)s + 1; // +1 to include first byte } /* @@ -70,9 +70,9 @@ static int utflen(lua_State* L) { const char* s1 = utf8_decode(s + posi, NULL); if (s1 == NULL) - { /* conversion error? */ - lua_pushnil(L); /* return nil ... */ - lua_pushinteger(L, posi + 1); /* ... and current position */ + { // conversion error? + lua_pushnil(L); // return nil ... + lua_pushinteger(L, posi + 1); // ... and current position return 2; } posi = (int)(s1 - s); @@ -97,8 +97,8 @@ static int codepoint(lua_State* L) luaL_argcheck(L, posi >= 1, 2, "out of range"); luaL_argcheck(L, pose <= (int)len, 3, "out of range"); if (posi > pose) - return 0; /* empty interval; return no values */ - if (pose - posi >= INT_MAX) /* (int -> int) overflow? */ + return 0; // empty interval; return no values + if (pose - posi >= INT_MAX) // (int -> int) overflow? luaL_error(L, "string slice too long"); n = (int)(pose - posi) + 1; luaL_checkstack(L, n, "string slice too long"); @@ -122,20 +122,20 @@ static int codepoint(lua_State* L) // from Lua 5.3 lobject.c, copied verbatim + static static int luaO_utf8esc(char* buff, unsigned long x) { - int n = 1; /* number of bytes put in buffer (backwards) */ + int n = 1; // number of bytes put in buffer (backwards) LUAU_ASSERT(x <= 0x10FFFF); - if (x < 0x80) /* ascii? */ + if (x < 0x80) // ascii? buff[UTF8BUFFSZ - 1] = cast_to(char, x); else - { /* need continuation bytes */ - unsigned int mfb = 0x3f; /* maximum that fits in first byte */ + { // need continuation bytes + unsigned int mfb = 0x3f; // maximum that fits in first byte do - { /* add continuation bytes */ + { // add continuation bytes buff[UTF8BUFFSZ - (n++)] = cast_to(char, 0x80 | (x & 0x3f)); - x >>= 6; /* remove added bits */ - mfb >>= 1; /* now there is one less bit available in first byte */ - } while (x > mfb); /* still needs continuation byte? */ - buff[UTF8BUFFSZ - n] = cast_to(char, (~mfb << 1) | x); /* add first byte */ + x >>= 6; // remove added bits + mfb >>= 1; // now there is one less bit available in first byte + } while (x > mfb); // still needs continuation byte? + buff[UTF8BUFFSZ - n] = cast_to(char, (~mfb << 1) | x); // add first byte } return n; } @@ -162,9 +162,9 @@ static int utfchar(lua_State* L) char buff[UTF8BUFFSZ]; const char* charstr; - int n = lua_gettop(L); /* number of arguments */ + int n = lua_gettop(L); // number of arguments if (n == 1) - { /* optimize common case of single char */ + { // optimize common case of single char int l = buffutfchar(L, 1, buff, &charstr); lua_pushlstring(L, charstr, l); } @@ -196,7 +196,7 @@ static int byteoffset(lua_State* L) luaL_argcheck(L, 1 <= posi && --posi <= (int)len, 3, "position out of range"); if (n == 0) { - /* find beginning of current byte sequence */ + // find beginning of current byte sequence while (posi > 0 && iscont(s + posi)) posi--; } @@ -207,9 +207,9 @@ static int byteoffset(lua_State* L) if (n < 0) { while (n < 0 && posi > 0) - { /* move back */ + { // move back do - { /* find beginning of previous character */ + { // find beginning of previous character posi--; } while (posi > 0 && iscont(s + posi)); n++; @@ -217,20 +217,20 @@ static int byteoffset(lua_State* L) } else { - n--; /* do not move for 1st character */ + n--; // do not move for 1st character while (n > 0 && posi < (int)len) { do - { /* find beginning of next character */ + { // find beginning of next character posi++; - } while (iscont(s + posi)); /* (cannot pass final '\0') */ + } while (iscont(s + posi)); // (cannot pass final '\0') n--; } } } - if (n == 0) /* did it find given character? */ + if (n == 0) // did it find given character? lua_pushinteger(L, posi + 1); - else /* no such character */ + else // no such character lua_pushnil(L); return 1; } @@ -240,16 +240,16 @@ static int iter_aux(lua_State* L) size_t len; const char* s = luaL_checklstring(L, 1, &len); int n = lua_tointeger(L, 2) - 1; - if (n < 0) /* first iteration? */ - n = 0; /* start from here */ + if (n < 0) // first iteration? + n = 0; // start from here else if (n < (int)len) { - n++; /* skip current byte */ + n++; // skip current byte while (iscont(s + n)) - n++; /* and its continuations */ + n++; // and its continuations } if (n >= (int)len) - return 0; /* no more codepoints */ + return 0; // no more codepoints else { int code; @@ -271,7 +271,7 @@ static int iter_codes(lua_State* L) return 3; } -/* pattern to match a single UTF-8 character */ +// pattern to match a single UTF-8 character #define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*" static const luaL_Reg funcs[] = { diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 02b39313..376dd400 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,7 +16,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLenTM, false) +LUAU_FASTFLAGVARIABLE(LuauNicerMethodErrors, false) // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ @@ -33,7 +33,7 @@ LUAU_FASTFLAGVARIABLE(LuauLenTM, false) // 3. VM_PROTECT macro saves savedpc and restores base for you; most external calls need to be wrapped into that. However, it does NOT restore // ra/rb/rc! // 4. When copying an object to any existing object as a field, generally speaking you need to call luaC_barrier! Be careful with all setobj calls -// 5. To make 4 easier to follow, please use setobj2s for copies to stack and setobj for other copies. +// 5. To make 4 easier to follow, please use setobj2s for copies to stack, setobj2t for writes to tables, and setobj for other copies. // 6. You can define HARDSTACKTESTS in llimits.h which will aggressively realloc stack; with address sanitizer this should be effective at finding // stack corruption bugs // 7. Many external Lua functions can call GC! GC will *not* traverse pointers to new objects that aren't reachable from Lua root. Be careful when @@ -110,7 +110,8 @@ LUAU_FASTFLAGVARIABLE(LuauLenTM, false) VM_DISPATCH_OP(LOP_FORGLOOP_NEXT), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \ VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \ VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_JUMPIFEQK), VM_DISPATCH_OP(LOP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \ - VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), + VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \ + VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), \ #if defined(__GNUC__) || defined(__clang__) #define VM_USE_CGOTO 1 @@ -158,7 +159,7 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c) setobjs2s(L, ra + 3 + 1, ra + 1); setobjs2s(L, ra + 3, ra); - L->top = ra + 3 + 3; /* func. + 2 args (state and index) */ + L->top = ra + 3 + 3; // func. + 2 args (state and index) LUAU_ASSERT(L->top <= L->stack_last); luaD_call(L, ra + 3, c); @@ -236,10 +237,10 @@ LUAU_NOINLINE static void luau_tryfuncTM(lua_State* L, StkId func) const TValue* tm = luaT_gettmbyobj(L, func, TM_CALL); if (!ttisfunction(tm)) luaG_typeerror(L, func, "call"); - for (StkId p = L->top; p > func; p--) /* open space for metamethod */ + for (StkId p = L->top; p > func; p--) // open space for metamethod setobjs2s(L, p, p - 1); - L->top++; /* stack space pre-allocated by the caller */ - setobj2s(L, func, tm); /* tag method is the new function to be called */ + L->top++; // stack space pre-allocated by the caller + setobj2s(L, func, tm); // tag method is the new function to be called } LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) @@ -256,7 +257,7 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) L->base = L->ci->base; } - luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size L->ci->top = L->top + LUA_MINSTACK; LUAU_ASSERT(L->ci->top <= L->stack_last); @@ -458,7 +459,7 @@ static void luau_execute(lua_State* L) if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly)) { - setobj(L, gval(n), ra); + setobj2t(L, gval(n), ra); luaC_barriert(L, h, ra); VM_NEXT(); } @@ -672,7 +673,7 @@ static void luau_execute(lua_State* L) // fast-path: value is in expected slot if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly)) { - setobj(L, gval(n), ra); + setobj2t(L, gval(n), ra); luaC_barriert(L, h, ra); VM_NEXT(); } @@ -684,7 +685,7 @@ static void luau_execute(lua_State* L) int cachedslot = gval2slot(h, res); // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++ VM_PATCH_C(pc - 2, cachedslot); - setobj(L, res, ra); + setobj2t(L, res, ra); luaC_barriert(L, h, ra); VM_NEXT(); } @@ -929,6 +930,10 @@ static void luau_execute(lua_State* L) VM_PROTECT(luaV_gettable(L, rb, kv, ra)); // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++ VM_PATCH_C(pc - 2, L->cachedslot); + // recompute ra since stack might have been reallocated + ra = VM_REG(LUAU_INSN_A(insn)); + if (FFlag::LuauNicerMethodErrors && ttisnil(ra)) + luaG_methoderror(L, ra + 1, tsvalue(kv)); } } else @@ -966,6 +971,10 @@ static void luau_execute(lua_State* L) VM_PROTECT(luaV_gettable(L, rb, kv, ra)); // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++ VM_PATCH_C(pc - 2, L->cachedslot); + // recompute ra since stack might have been reallocated + ra = VM_REG(LUAU_INSN_A(insn)); + if (FFlag::LuauNicerMethodErrors && ttisnil(ra)) + luaG_methoderror(L, ra + 1, tsvalue(kv)); } } else @@ -973,6 +982,10 @@ static void luau_execute(lua_State* L) // slow-path: handles non-table __index setobj2s(L, ra + 1, rb); VM_PROTECT(luaV_gettable(L, rb, kv, ra)); + // recompute ra since stack might have been reallocated + ra = VM_REG(LUAU_INSN_A(insn)); + if (FFlag::LuauNicerMethodErrors && ttisnil(ra)) + luaG_methoderror(L, ra + 1, tsvalue(kv)); } } @@ -1028,7 +1041,7 @@ static void luau_execute(lua_State* L) StkId argi = L->top; StkId argend = L->base + p->numparams; while (argi < argend) - setnilvalue(argi++); /* complete missing arguments */ + setnilvalue(argi++); // complete missing arguments L->top = p->is_vararg ? argi : ci->top; // reentry @@ -2074,7 +2087,7 @@ static void luau_execute(lua_State* L) { Table* h = hvalue(rb); - if (!FFlag::LuauLenTM || fastnotm(h->metatable, TM_LEN)) + if (fastnotm(h->metatable, TM_LEN)) { setnvalue(ra, cast_num(luaH_getn(h))); VM_NEXT(); @@ -2214,7 +2227,7 @@ static void luau_execute(lua_State* L) if (ttisfunction(ra)) { - /* will be called during FORGLOOP */ + // will be called during FORGLOOP } else { @@ -2225,16 +2238,16 @@ static void luau_execute(lua_State* L) setobj2s(L, ra + 1, ra); setobj2s(L, ra, fn); - L->top = ra + 2; /* func + self arg */ + L->top = ra + 2; // func + self arg LUAU_ASSERT(L->top <= L->stack_last); VM_PROTECT(luaD_call(L, ra, 3)); L->top = L->ci->top; - /* recompute ra since stack might have been reallocated */ + // recompute ra since stack might have been reallocated ra = VM_REG(LUAU_INSN_A(insn)); - /* protect against __iter returning nil, since nil is used as a marker for builtin iteration in FORGLOOP */ + // protect against __iter returning nil, since nil is used as a marker for builtin iteration in FORGLOOP if (ttisnil(ra)) { VM_PROTECT(luaG_typeerror(L, ra, "call")); @@ -2242,12 +2255,12 @@ static void luau_execute(lua_State* L) } else if (fasttm(L, mt, TM_CALL)) { - /* table or userdata with __call, will be called during FORGLOOP */ - /* TODO: we might be able to stop supporting this depending on whether it's used in practice */ + // table or userdata with __call, will be called during FORGLOOP + // TODO: we might be able to stop supporting this depending on whether it's used in practice } else if (ttistable(ra)) { - /* set up registers for builtin iteration */ + // set up registers for builtin iteration setobj2s(L, ra + 1, ra); setpvalue(ra + 2, reinterpret_cast(uintptr_t(0))); setnilvalue(ra); @@ -2344,7 +2357,7 @@ static void luau_execute(lua_State* L) setobjs2s(L, ra + 3 + 1, ra + 1); setobjs2s(L, ra + 3, ra); - L->top = ra + 3 + 3; /* func + 2 args (state and index) */ + L->top = ra + 3 + 3; // func + 2 args (state and index) LUAU_ASSERT(L->top <= L->stack_last); VM_PROTECT(luaD_call(L, ra + 3, uint8_t(aux))); @@ -2372,7 +2385,7 @@ static void luau_execute(lua_State* L) if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0) { setnilvalue(ra); - /* ra+1 is already the table */ + // ra+1 is already the table setpvalue(ra + 2, reinterpret_cast(uintptr_t(0))); } else if (!ttisfunction(ra)) @@ -2444,7 +2457,7 @@ static void luau_execute(lua_State* L) if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2)) { setnilvalue(ra); - /* ra+1 is already the table */ + // ra+1 is already the table setpvalue(ra + 2, reinterpret_cast(uintptr_t(0))); } else if (!ttisfunction(ra)) @@ -2619,8 +2632,8 @@ static void luau_execute(lua_State* L) LUAU_ASSERT(cast_int(L->top - base) >= numparams); // move fixed parameters to final position - StkId fixed = base; /* first fixed argument */ - base = L->top; /* final position of first argument */ + StkId fixed = base; // first fixed argument + base = L->top; // final position of first argument for (int i = 0; i < numparams; ++i) { @@ -2983,6 +2996,56 @@ static void luau_execute(lua_State* L) VM_CONTINUE(op); } + VM_CASE(LOP_JUMPXEQKNIL) + { + Instruction insn = *pc++; + uint32_t aux = *pc; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + + static_assert(LUA_TNIL == 0, "we expect type-1 to be negative iff type is nil"); + // condition is equivalent to: int(ttisnil(ra)) != (aux >> 31) + pc += int((ttype(ra) - 1) ^ aux) < 0 ? LUAU_INSN_D(insn) : 1; + LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); + VM_NEXT(); + } + + VM_CASE(LOP_JUMPXEQKB) + { + Instruction insn = *pc++; + uint32_t aux = *pc; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + + pc += int(ttisboolean(ra) && bvalue(ra) == int(aux & 1)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); + VM_NEXT(); + } + + VM_CASE(LOP_JUMPXEQKN) + { + Instruction insn = *pc++; + uint32_t aux = *pc; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + TValue* kv = VM_KV(aux & 0xffffff); + LUAU_ASSERT(ttisnumber(kv)); + + pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); + VM_NEXT(); + } + + VM_CASE(LOP_JUMPXEQKS) + { + Instruction insn = *pc++; + uint32_t aux = *pc; + StkId ra = VM_REG(LUAU_INSN_A(insn)); + TValue* kv = VM_KV(aux & 0xffffff); + LUAU_ASSERT(ttisstring(kv)); + + pc += int(ttisstring(ra) && gcvalue(ra) == gcvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); + VM_NEXT(); + } + #if !VM_USE_CGOTO default: LUAU_ASSERT(!"Unknown opcode"); @@ -3032,7 +3095,7 @@ int luau_precall(lua_State* L, StkId func, int nresults) StkId argi = L->top; StkId argend = L->base + ccl->l.p->numparams; while (argi < argend) - setnilvalue(argi++); /* complete missing arguments */ + setnilvalue(argi++); // complete missing arguments L->top = ccl->l.p->is_vararg ? argi : ci->top; L->ci->savedpc = ccl->l.p->code; diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index be4e99a9..8be241e0 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -12,9 +12,9 @@ #include #include -LUAU_FASTFLAG(LuauLenTM) +LUAU_FASTFLAGVARIABLE(LuauBetterNewindex, false) -/* limit for table tag-method chains (to avoid loops) */ +// limit for table tag-method chains (to avoid loops) #define MAXTAGLOOP 100 const TValue* luaV_tonumber(const TValue* obj, TValue* n) @@ -65,9 +65,9 @@ static StkId callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p // * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these // values will be preserved even if they go past stack_last LUAU_ASSERT((L->top + 3) < (L->stack + L->stacksize)); - setobj2s(L, L->top, f); /* push function */ - setobj2s(L, L->top + 1, p1); /* 1st argument */ - setobj2s(L, L->top + 2, p2); /* 2nd argument */ + setobj2s(L, L->top, f); // push function + setobj2s(L, L->top + 1, p1); // 1st argument + setobj2s(L, L->top + 2, p2); // 2nd argument luaD_checkstack(L, 3); L->top += 3; luaD_call(L, L->top - 3, 1); @@ -87,10 +87,10 @@ static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue // * during stack reallocation all of the allocated stack is copied (even beyond stack_last) so these // values will be preserved even if they go past stack_last LUAU_ASSERT((L->top + 4) < (L->stack + L->stacksize)); - setobj2s(L, L->top, f); /* push function */ - setobj2s(L, L->top + 1, p1); /* 1st argument */ - setobj2s(L, L->top + 2, p2); /* 2nd argument */ - setobj2s(L, L->top + 3, p3); /* 3th argument */ + setobj2s(L, L->top, f); // push function + setobj2s(L, L->top + 1, p1); // 1st argument + setobj2s(L, L->top + 2, p2); // 2nd argument + setobj2s(L, L->top + 3, p3); // 3th argument luaD_checkstack(L, 4); L->top += 4; luaD_call(L, L->top - 4, 0); @@ -103,21 +103,21 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) { const TValue* tm; if (ttistable(t)) - { /* `t' is a table? */ + { // `t' is a table? Table* h = hvalue(t); - const TValue* res = luaH_get(h, key); /* do a primitive get */ + const TValue* res = luaH_get(h, key); // do a primitive get if (res != luaO_nilobject) - L->cachedslot = gval2slot(h, res); /* remember slot to accelerate future lookups */ + L->cachedslot = gval2slot(h, res); // remember slot to accelerate future lookups - if (!ttisnil(res) /* result is no nil? */ + if (!ttisnil(res) // result is no nil? || (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) - { /* or no TM? */ + { // or no TM? setobj2s(L, val, res); return; } - /* t isn't a table, so see if it has an INDEX meta-method to look up the key with */ + // t isn't a table, so see if it has an INDEX meta-method to look up the key with } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) luaG_indexerror(L, t, key); @@ -126,7 +126,7 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) callTMres(L, val, tm, t, key); return; } - t = tm; /* else repeat with `tm' */ + t = tm; // else repeat with `tm' } luaG_runerror(L, "'__index' chain too long; possible loop"); } @@ -139,34 +139,60 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) { const TValue* tm; if (ttistable(t)) - { /* `t' is a table? */ + { // `t' is a table? Table* h = hvalue(t); - if (h->readonly) - luaG_readonlyerror(L); + if (FFlag::LuauBetterNewindex) + { + const TValue* oldval = luaH_get(h, key); - TValue* oldval = luaH_set(L, h, key); /* do a primitive set */ + // should we assign the key? (if key is valid or __newindex is not set) + if (!ttisnil(oldval) || (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) + { + if (h->readonly) + luaG_readonlyerror(L); - L->cachedslot = gval2slot(h, oldval); /* remember slot to accelerate future lookups */ + // luaH_set would work but would repeat the lookup so we use luaH_setslot that can reuse oldval if it's safe + TValue* newval = luaH_setslot(L, h, oldval, key); - if (!ttisnil(oldval) || /* result is no nil? */ - (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) - { /* or no TM? */ - setobj2t(L, oldval, val); - luaC_barriert(L, h, val); - return; + L->cachedslot = gval2slot(h, newval); // remember slot to accelerate future lookups + + setobj2t(L, newval, val); + luaC_barriert(L, h, val); + return; + } + + // fallthrough to metamethod + } + else + { + if (h->readonly) + luaG_readonlyerror(L); + + TValue* oldval = luaH_set(L, h, key); // do a primitive set + + L->cachedslot = gval2slot(h, oldval); // remember slot to accelerate future lookups + + if (!ttisnil(oldval) || // result is no nil? + (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) + { // or no TM? + setobj2t(L, oldval, val); + luaC_barriert(L, h, val); + return; + } + // else will try the tag method } - /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_indexerror(L, t, key); + if (ttisfunction(tm)) { callTM(L, tm, t, key, val); return; } - /* else repeat with `tm' */ - setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */ + // else repeat with `tm' + setobj(L, &temp, tm); // avoid pointing inside table (may rehash) t = &temp; } luaG_runerror(L, "'__newindex' chain too long; possible loop"); @@ -174,9 +200,9 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId res, TMS event) { - const TValue* tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + const TValue* tm = luaT_gettmbyobj(L, p1, event); // try first operand if (ttisnil(tm)) - tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + tm = luaT_gettmbyobj(L, p2, event); // try second operand if (ttisnil(tm)) return 0; callTMres(L, res, tm, p1, p2); @@ -188,13 +214,13 @@ static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event) const TValue* tm1 = fasttm(L, mt1, event); const TValue* tm2; if (tm1 == NULL) - return NULL; /* no metamethod */ + return NULL; // no metamethod if (mt1 == mt2) - return tm1; /* same metatables => same metamethods */ + return tm1; // same metatables => same metamethods tm2 = fasttm(L, mt2, event); if (tm2 == NULL) - return NULL; /* no metamethod */ - if (luaO_rawequalObj(tm1, tm2)) /* same metamethods? */ + return NULL; // no metamethod + if (luaO_rawequalObj(tm1, tm2)) // same metamethods? return tm1; return NULL; } @@ -204,9 +230,9 @@ static int call_orderTM(lua_State* L, const TValue* p1, const TValue* p2, TMS ev const TValue* tm1 = luaT_gettmbyobj(L, p1, event); const TValue* tm2; if (ttisnil(tm1)) - return -1; /* no metamethod? */ + return -1; // no metamethod? tm2 = luaT_gettmbyobj(L, p2, event); - if (!luaO_rawequalObj(tm1, tm2)) /* different metamethods? */ + if (!luaO_rawequalObj(tm1, tm2)) // different metamethods? return -1; callTMres(L, L->top, tm1, p1, p2); return !l_isfalse(L->top); @@ -253,9 +279,9 @@ int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r) return luai_numle(nvalue(l), nvalue(r)); else if (ttisstring(l)) return luaV_strcmp(tsvalue(l), tsvalue(r)) <= 0; - else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) /* first try `le' */ + else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) // first try `le' return res; - else if ((res = call_orderTM(L, r, l, TM_LT)) == -1) /* error if not `lt' */ + else if ((res = call_orderTM(L, r, l, TM_LT)) == -1) // error if not `lt' luaG_ordererror(L, l, r, TM_LE); return !res; } @@ -273,7 +299,7 @@ int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2) case LUA_TVECTOR: return luai_veceq(vvalue(t1), vvalue(t2)); case LUA_TBOOLEAN: - return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ + return bvalue(t1) == bvalue(t2); // true must be 1 !! case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); case LUA_TUSERDATA: @@ -281,19 +307,19 @@ int luaV_equalval(lua_State* L, const TValue* t1, const TValue* t2) tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); if (!tm) return uvalue(t1) == uvalue(t2); - break; /* will try TM */ + break; // will try TM } case LUA_TTABLE: { tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); if (!tm) return hvalue(t1) == hvalue(t2); - break; /* will try TM */ + break; // will try TM } default: return gcvalue(t1) == gcvalue(t2); } - callTMres(L, L->top, tm, t1, t2); /* call TM */ + callTMres(L, L->top, tm, t1, t2); // call TM return !l_isfalse(L->top); } @@ -302,21 +328,21 @@ void luaV_concat(lua_State* L, int total, int last) do { StkId top = L->base + last + 1; - int n = 2; /* number of elements handled in this pass (at least 2) */ + int n = 2; // number of elements handled in this pass (at least 2) if (!(ttisstring(top - 2) || ttisnumber(top - 2)) || !tostring(L, top - 1)) { if (!call_binTM(L, top - 2, top - 1, top - 2, TM_CONCAT)) luaG_concaterror(L, top - 2, top - 1); } - else if (tsvalue(top - 1)->len == 0) /* second op is empty? */ - (void)tostring(L, top - 2); /* result is first op (as string) */ + else if (tsvalue(top - 1)->len == 0) // second op is empty? + (void)tostring(L, top - 2); // result is first op (as string) else { - /* at least two string values; get as many as possible */ + // at least two string values; get as many as possible size_t tl = tsvalue(top - 1)->len; char* buffer; int i; - /* collect total length */ + // collect total length for (n = 1; n < total && tostring(L, top - n - 1); n++) { size_t l = tsvalue(top - n - 1)->len; @@ -340,7 +366,7 @@ void luaV_concat(lua_State* L, int total, int last) tl = 0; for (i = n; i > 0; i--) - { /* concat all strings */ + { // concat all strings size_t l = tsvalue(top - i)->len; memcpy(buffer + tl, svalue(top - i), l); tl += l; @@ -355,9 +381,9 @@ void luaV_concat(lua_State* L, int total, int last) setsvalue2s(L, top - n, luaS_buffinish(L, ts)); } } - total -= n - 1; /* got `n' strings to create 1 new */ + total -= n - 1; // got `n' strings to create 1 new last -= n - 1; - } while (total > 1); /* repeat until only 1 result left */ + } while (total > 1); // repeat until only 1 result left } void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TMS op) @@ -476,29 +502,6 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM void luaV_dolen(lua_State* L, StkId ra, const TValue* rb) { - if (!FFlag::LuauLenTM) - { - switch (ttype(rb)) - { - case LUA_TTABLE: - { - setnvalue(ra, cast_num(luaH_getn(hvalue(rb)))); - break; - } - case LUA_TSTRING: - { - setnvalue(ra, cast_num(tsvalue(rb)->len)); - break; - } - default: - { /* try metamethod */ - if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN)) - luaG_typeerror(L, rb, "get length of"); - } - } - return; - } - const TValue* tm = NULL; switch (ttype(rb)) { @@ -527,5 +530,5 @@ void luaV_dolen(lua_State* L, StkId ra, const TValue* rb) StkId res = callTMres(L, ra, tm, rb, luaO_nilobject); if (!ttisnumber(res)) - luaG_runerror(L, "'__len' must return a number"); /* note, we can't access rb since stack may have been reallocated */ + luaG_runerror(L, "'__len' must return a number"); // note, we can't access rb since stack may have been reallocated } diff --git a/rfcs/STATUS.md b/rfcs/STATUS.md index 23a1be83..d2fe86f0 100644 --- a/rfcs/STATUS.md +++ b/rfcs/STATUS.md @@ -26,15 +26,3 @@ This document tracks unimplemented RFCs. [RFC: Lower bounds calculation](https://github.com/Roblox/luau/blob/master/rfcs/lower-bounds-calculation.md) **Status**: Implemented but not fully rolled out yet. - -## never and unknown types - -[RFC: never and unknown types](https://github.com/Roblox/luau/blob/master/rfcs/never-and-unknown-types.md) - -**Status**: Needs implementation - -## __len metamethod for tables and rawlen function - -[RFC: Support __len metamethod for tables and rawlen function](https://github.com/Roblox/luau/blob/master/rfcs/len-metamethod-rawlen.md) - -**Status**: Needs implementation diff --git a/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md b/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md new file mode 100644 index 00000000..d9c5c7d7 --- /dev/null +++ b/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md @@ -0,0 +1,129 @@ +# Disallow `name T` and `name(T)` in future syntactic extensions for type annotations + +## Summary + +We propose to disallow the syntax `` `('`` as well as ` ` in future syntax extensions for type annotations to ensure that all existing programs continue to parse correctly. This still keeps the door open for future syntax extensions of different forms such as `` `<' `>'``. + +## Motivation + +Lua and by extension Luau's syntax is very free form, which means that when the parser finishes parsing a node, it doesn't try to look for a semi-colon or any termination token e.g. a `{` to start a block, or `;` to end a statement, or a newline, etc. It just immediately invokes the next parser to figure out how to parse the next node based on the remainder's starting token. + +That feature is sometimes quite troublesome when we want to add new syntax. + +We have had cases where we talked about using syntax like `setmetatable(T, MT)` and `keyof T`. They all look innocent, but when you look beyond that, and try to apply it onto Luau's grammar, things break down really fast. + +### `F(T)`? + +An example that _will_ cause a change in semantics: + +``` +local t: F +(u):m() +``` + +where today, `local t: F` is one statement, and `(u):m()` is another. If we had the syntax for `F(T)` here, it becomes invalid input because it gets parsed as + +``` +local t: F(u) +:m() +``` + +This is important because of the `setmetatable(T, MT)` case: + +``` +type Foo = setmetatable({ x: number }, { ... }) +``` + +For `setmetatable`, the parser isn't sure whether `{}` is actually a type or an expression, because _today_ `setmetatable` is parsed as a type reference, and `({}, {})` is the remainder that we'll attempt to parse as a statement. This means `{ x: number }` is invalid table _literal_. Recovery by backtracking is technically possible here, but this means performance loss on invalid input + may introduce false positives wrt how things are parsed. We'd much rather take a very strict stance about how things get parsed. + +### `F T`? + +An example that _will_ cause a change in semantics: + +``` +local function f(t): F T + (t or u):m() +end +``` + +where today, the return type annotation `F T` is simply parsed as just `F`, followed by a ambiguous parse error from the statement `T(t or u)` because its `(` is on the next line. If at some point in the future we were to allow `T` followed by `(` on the next line, then there's yet another semantic change. `F T` could be parsed as a type annotation and the first statement is `(t or u):m()` instead of `F` followed by `T(t or u):m()`. + +For `keyof`, here's a practical example of the above issue: + +``` +type Vec2 = {x: number, y: number} + +local function f(t, u): keyof Vec2 + (t or u):m() +end +``` + +There's three possible outcomes: + 1. Return type of `f` is `keyof`, statement throws a parse error because `(` is on the next line after `Vec2`, + 2. Return type of `f` is `keyof Vec2` and next statement is `(t or u):m()`, or + 3. Return type of `f` is `keyof` and next statement is `Vec2(t or u):m()` (if we allow `(` on the next line to be part of previous line). + +This particular case is even worse when we keep going: + +``` +local function f(t): F + T(t or u):m() +end +``` + +``` +local function f(t): F T + {1, 2, 3} +end +``` + +where today, `F` is the return type annotation of `f`, and `T(t or u):m()`/`T{1, 2, 3}` is the first statement, respectively. + +Adding some syntax for `F T` **will** cause the parser to change the semantics of the above three examples. + +### But what about `typeof(...)`? + +This syntax is grandfathered in because the parser supported `typeof(...)` before we stabilized our syntax, and especially before type annotations were released to the public, so we didn't need to worry about compatibility here. We are very glad that we used parentheses in this case, because it's natural for expressions to belong within parentheses `()`, and types to belong within angles `<>`. + +## The One Exception with a caveat + +This is a strict requirement! + +`function() -> ()` has been talked about in the past, and this one is different despite falling under the same category as `` `('``. The token `function` is in actual fact a "hard keyword," meaning that it cannot be parsed as a type annotation because it is not an identifier, just a keyword. + +Likewise, we also have talked about adding standalone `function` as a type annotation (semantics of it is irrelevant for this RFC) + +It's possible that we may end up adding both, but the requirements are as such: + 1. `function() -> ()` must be added first before standalone `function`, OR + 2. `function` can be added first, but with a future-proofing parse error if `<` or `(` follows after it + +If #1 is what ends up happening, there's not much to worry about because the type annotation parser will parse greedily already, so any new valid input will remain valid and have same semantics, except it also allows omitting of `(` and `<`. + +If #2 is what ends up happening, there could be a problem if we didn't future-proof against `<` and `(` to follow `function`: + +``` + return f :: function(T) -> U +``` + +which would be a parse error because at the point of `(` we expect one of `until`, `end`, or `EOF`, and + +``` + return f :: function(a) -> a +``` + +which would also be a parse error by the time we reach `->`, that is the production of the above is semantically equivalent to `(f < a) > (a)` which would compare whether the value of `f` is less than the value of `a`, then whether the result of that value is greater than `a`. + +## Alternatives + +Only allow these syntax when used inside parentheses e.g. `(F T)` or `(F(T))`. This makes it inconsistent with the existing `typeof(...)` type annotation, and changing that over is also breaking change. + +Support backtracking in the parser, so if `: MyType(t or u):m()` is invalid syntax, revert and parse `MyType` as a type, and `(t or u):m()` as an expression statement. Even so, this option is terrible for: + 1. parsing performance (backtracking means losing progress on invalid input), + 2. user experience (why was this annotation parsed as `X(...)` instead of `X` followed by a statement `(...)`), + 3. has false positives (`foo(bar)(baz)` may be parsed as `foo(bar)` as the type annotation and `(baz)` is the remainder to parse) + +## Drawbacks + +To be able to expose some kind of type-level operations using `F` syntax, means one of the following must be chosen: + 1. introduce the concept of "magic type functions" into type inference, or + 2. introduce them into the prelude as `export type F = ...` (where `...` is to be read as "we haven't decided") diff --git a/rfcs/len-metamethod-rawlen.md b/rfcs/len-metamethod-rawlen.md index 45284b71..60278dda 100644 --- a/rfcs/len-metamethod-rawlen.md +++ b/rfcs/len-metamethod-rawlen.md @@ -1,5 +1,7 @@ # Support `__len` metamethod for tables and `rawlen` function +**Status**: Implemented + ## Summary `__len` metamethod will be called by `#` operator on tables, matching Lua 5.2 diff --git a/rfcs/never-and-unknown-types.md b/rfcs/never-and-unknown-types.md index d996afc6..5ad216ef 100644 --- a/rfcs/never-and-unknown-types.md +++ b/rfcs/never-and-unknown-types.md @@ -1,5 +1,7 @@ # never and unknown types +**Status**: Implemented + ## Summary Add `unknown` and `never` types that are inhabited by everything and nothing respectively. diff --git a/rfcs/syntax-string-interpolation.md b/rfcs/syntax-string-interpolation.md index 208143a0..ad182620 100644 --- a/rfcs/syntax-string-interpolation.md +++ b/rfcs/syntax-string-interpolation.md @@ -31,9 +31,9 @@ Because we care about backward compatibility, we need some new syntax in order t 1. A string chunk (`` `...{ ``, `}...{`, and `` }...` ``) where `...` is a range of 0 to many characters. * `\` escapes `` ` ``, `{`, and itself `\`. - * Restriction: the string interpolation literal must have at least one value to interpolate. We do not need 3 ways to express a single line string literal. * The pairs must be on the same line (unless a `\` escapes the newline) but expressions needn't be on the same line. 2. An expression between the braces. This is the value that will be interpolated into the string. + * Restriction: we explicitly reject `{{` as it is considered an attempt to escape and get a single `{` character at runtime. 3. Formatting specification may follow after the expression, delimited by an unambiguous character. * Restriction: the formatting specification must be constant at parse time. * In the absence of an explicit formatting specification, the `%*` token will be used. @@ -61,7 +61,6 @@ local set2 = Set.new({0, 5, 4}) print(`{set1} ∪ {set2} = {Set.union(set1, set2)}`) --> {0, 1, 3} ∪ {0, 5, 4} = {0, 1, 3, 4, 5} --- For illustrative purposes. These are illegal specifically because they don't interpolate anything. print(`Some example escaping the braces \{like so}`) print(`backslash \ that escapes the space is not a part of the string...`) print(`backslash \\ will escape the second backslash...`) @@ -88,13 +87,25 @@ print(`Welcome to \ -- Luau! ``` -This expression will not be allowed to come after a `prefixexp`. I believe this is fully additive, so a future RFC may allow this. So for now, we explicitly reject the following: +This expression can also come after a `prefixexp`: ``` local name = "world" print`Hello {name}` ``` +The restriction on `{{` exists solely for the people coming from languages e.g. C#, Rust, or Python which uses `{{` to escape and get the character `{` at runtime. We're also rejecting this at parse time too, since the proper way to escape it is `\{`, so: + +```lua +print(`{{1, 2, 3}} = {myCoolSet}`) -- parse error +``` + +If we did not apply this as a parse error, then the above would wind up printing as the following, which is obviously a gotcha we can and should avoid. + +``` +--> table: 0xSOMEADDRESS = {1, 2, 3} +``` + Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called. ```lua @@ -121,6 +132,13 @@ print(string.format("%* %* %*", return_two_nils())) --> error: value #3 is missing, got 2 ``` +It must be said that we are not allowing this style of string literals in type annotations at this time, regardless of zero or many interpolating expressions, so the following two type annotations below are illegal syntax: + +```lua +local foo: `foo` +local bar: `bar{baz}` +``` + ## Drawbacks If we want to use backticks for other purposes, it may introduce some potential ambiguity. One option to solve that is to only ever produce string interpolation tokens from the context of an expression. This is messy but doable because the parser and the lexer are already implemented to work in tandem. The other option is to pick a different delimiter syntax to keep backticks available for use in the future. diff --git a/tests/JsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp similarity index 99% rename from tests/JsonEncoder.test.cpp rename to tests/AstJsonEncoder.test.cpp index 7d7b18d7..a23f6f47 100644 --- a/tests/JsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -1,6 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Ast.h" -#include "Luau/JsonEncoder.h" +#include "Luau/AstJsonEncoder.h" #include "Luau/Parser.h" #include "ScopedFlags.h" @@ -193,7 +193,8 @@ 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/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index d8ad3340..75c5a606 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -1974,7 +1974,7 @@ TEST_CASE_FIXTURE(ACFixture, "function_result_passed_to_function_has_parentheses check(R"( local function foo() return 1 end local function bar(a: number) return -a end -local abc = bar(@1) +local abc = bar(@1) )"); auto ac = autocomplete('1'); @@ -2845,7 +2845,7 @@ local abc = b@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_on_class") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; loadDefinition(R"( declare class Foo @@ -2883,9 +2883,25 @@ t.@1 } } +TEST_CASE_FIXTURE(ACFixture, "do_compatible_self_calls") +{ + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; + + check(R"( +local t = {} +function t:m() end +t:@1 + )"); + + auto ac = autocomplete('1'); + + REQUIRE(ac.entryMap.count("m")); + CHECK(!ac.entryMap["m"].wrongIndexType); +} + TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local t = {} @@ -2901,7 +2917,7 @@ t:@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_2") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local f: (() -> number) & ((number) -> number) = function(x: number?) return 2 end @@ -2916,7 +2932,7 @@ t:@1 CHECK(ac.entryMap["f"].wrongIndexType); } -TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_provisional") +TEST_CASE_FIXTURE(ACFixture, "do_wrong_compatible_self_calls") { check(R"( local t = {} @@ -2931,9 +2947,26 @@ t:@1 CHECK(!ac.entryMap["m"].wrongIndexType); } +TEST_CASE_FIXTURE(ACFixture, "no_wrong_compatible_self_calls_with_generics") +{ + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; + + check(R"( +local t = {} +function t.m(a: T) end +t:@1 + )"); + + auto ac = autocomplete('1'); + + REQUIRE(ac.entryMap.count("m")); + // While this call is compatible with the type, this requires instantiation of a generic type which we don't perform + CHECK(ac.entryMap["m"].wrongIndexType); +} + TEST_CASE_FIXTURE(ACFixture, "string_prim_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local s = "hello" @@ -2952,7 +2985,7 @@ s:@1 TEST_CASE_FIXTURE(ACFixture, "string_prim_non_self_calls_are_avoided") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local s = "hello" @@ -2969,7 +3002,7 @@ s.@1 TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_non_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( string.@1 @@ -3000,7 +3033,7 @@ table.@1 TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_self_calls_are_invalid") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( string:@1 @@ -3012,8 +3045,11 @@ string:@1 CHECK(ac.entryMap["byte"].wrongIndexType == true); REQUIRE(ac.entryMap.count("char")); CHECK(ac.entryMap["char"].wrongIndexType == true); + + // We want the next test to evaluate to 'true', but we have to allow function defined with 'self' to be callable with ':' + // We may change the definition of the string metatable to not use 'self' types in the future (like byte/char/pack/unpack) REQUIRE(ac.entryMap.count("sub")); - CHECK(ac.entryMap["sub"].wrongIndexType == true); + CHECK(ac.entryMap["sub"].wrongIndexType == false); } TEST_CASE_FIXTURE(ACFixture, "source_module_preservation_and_invalidation") diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index ac0e7f1e..90aa7139 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -2434,6 +2434,8 @@ end TEST_CASE("DebugLineInfoRepeatUntil") { + ScopedFastFlag sff("LuauCompileXEQ", true); + CHECK_EQ("\n" + compileFunction0Coverage(R"( local f = 0 repeat @@ -2449,13 +2451,13 @@ until f == 0 R"( 2: LOADN R0 0 4: L0: ADDK R0 R0 K0 -5: JUMPIFNOTEQK R0 K0 L1 +5: JUMPXEQKN R0 K0 L1 NOT 6: GETIMPORT R1 2 6: MOVE R2 R0 6: CALL R1 1 0 6: JUMP L2 8: L1: LOADN R0 0 -10: L2: JUMPIFEQK R0 K3 L3 +10: L2: JUMPXEQKN R0 K3 L3 10: JUMPBACK L0 11: L3: RETURN R0 0 )"); @@ -3561,13 +3563,15 @@ RETURN R0 1 TEST_CASE("ConstantJumpCompare") { + ScopedFastFlag sff("LuauCompileXEQ", true); + CHECK_EQ("\n" + compileFunction0(R"( local obj = ... local b = obj == 1 )"), R"( GETVARARGS R0 1 -JUMPIFEQK R0 K0 L0 +JUMPXEQKN R0 K0 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -3579,7 +3583,7 @@ local b = 1 == obj )"), R"( GETVARARGS R0 1 -JUMPIFEQK R0 K0 L0 +JUMPXEQKN R0 K0 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -3591,7 +3595,7 @@ local b = "Hello, Sailor!" == obj )"), R"( GETVARARGS R0 1 -JUMPIFEQK R0 K0 L0 +JUMPXEQKS R0 K0 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -3603,7 +3607,7 @@ local b = nil == obj )"), R"( GETVARARGS R0 1 -JUMPIFEQK R0 K0 L0 +JUMPXEQKNIL R0 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -3615,7 +3619,7 @@ local b = true == obj )"), R"( GETVARARGS R0 1 -JUMPIFEQK R0 K0 L0 +JUMPXEQKB R0 1 L0 LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -3627,7 +3631,7 @@ local b = nil ~= obj )"), R"( GETVARARGS R0 1 -JUMPIFNOTEQK R0 K0 L0 +JUMPXEQKNIL R0 L0 NOT LOADB R1 0 +1 L0: LOADB R1 1 L1: RETURN R0 0 @@ -4404,8 +4408,6 @@ TEST_CASE("LoopUnrollControlFlow") {"LuauCompileLoopUnrollThresholdMaxBoost", 300}, }; - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - // break jumps to the end CHECK_EQ("\n" + compileFunction(R"( for i=1,3 do @@ -4721,8 +4723,6 @@ TEST_CASE("LoopUnrollCostBuiltins") {"LuauCompileLoopUnrollThresholdMaxBoost", 300}, }; - ScopedFastFlag sff("LuauCompileModelBuiltins", true); - // 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"( function cipher(block, nonce) @@ -5945,8 +5945,6 @@ RETURN R0 2 TEST_CASE("OptimizationLevel") { - ScopedFastFlag sff("LuauAlwaysCaptureHotComments", true); - // at optimization level 1, no inlining is performed CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -6016,8 +6014,6 @@ RETURN R1 -1 TEST_CASE("BuiltinFolding") { - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - CHECK_EQ("\n" + compileFunction(R"( return math.abs(-42), @@ -6125,8 +6121,6 @@ RETURN R0 48 TEST_CASE("BuiltinFoldingProhibited") { - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - CHECK_EQ("\n" + compileFunction(R"( return math.abs(), @@ -6160,8 +6154,7 @@ L3: RETURN R0 -1 TEST_CASE("BuiltinFoldingMultret") { - ScopedFastFlag sff1("LuauCompileFoldBuiltins", true); - ScopedFastFlag sff2("LuauCompileBetterMultret", true); + ScopedFastFlag sff("LuauCompileXEQ", true); CHECK_EQ("\n" + compileFunction(R"( local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000 @@ -6185,14 +6178,14 @@ FASTCALL2K 29 R2 K1 L0 LOADK R3 K1 GETIMPORT R1 4 CALL R1 2 1 -L0: JUMPIFEQK R1 K5 L1 +L0: JUMPXEQKN R1 K5 L1 RETURN R1 1 L1: FASTCALL2K 29 R1 K6 L2 MOVE R3 R1 LOADK R4 K6 GETIMPORT R2 4 CALL R2 2 1 -L2: JUMPIFEQK R2 K5 L3 +L2: JUMPXEQKN R2 K5 L3 LOADK R2 K6 RETURN R2 1 L3: LOADN R2 0 @@ -6220,7 +6213,8 @@ local function test(a, b) local c = a return c + b end -)"), R"( +)"), + R"( ADD R2 R0 R1 RETURN R2 1 )"); @@ -6231,7 +6225,8 @@ local function test(a, b) local c = (a :: number) return c + b end -)"), R"( +)"), + R"( ADD R2 R0 R1 RETURN R2 1 )"); @@ -6245,7 +6240,8 @@ local function test(a, b) b += 0 return c + d end -)"), R"( +)"), + R"( MOVE R2 R0 ADDK R2 R2 K0 MOVE R3 R1 @@ -6261,7 +6257,8 @@ local function test(a, b) local d = b return c + d end -)"), R"( +)"), + R"( ADD R2 R0 R1 RETURN R2 1 )"); @@ -6272,7 +6269,8 @@ local function test(a, b) local c, d = a, b return c + d end -)"), R"( +)"), + R"( MOVE R2 R0 MOVE R3 R1 ADD R4 R2 R3 @@ -6286,7 +6284,9 @@ local function test(a, b) local d = b return function() return c + d end end -)", 1), R"( +)", + 1), + R"( NEWCLOSURE R2 P0 CAPTURE VAL R0 CAPTURE VAL R1 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 2880c4d7..0739ca79 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -70,8 +70,8 @@ static int lua_loadstring(lua_State* L) return 1; lua_pushnil(L); - lua_insert(L, -2); /* put before error message */ - return 2; /* return nil plus error message */ + lua_insert(L, -2); // put before error message + return 2; // return nil plus error message } static int lua_vector(lua_State* L) @@ -244,8 +244,6 @@ TEST_CASE("Assert") TEST_CASE("Basic") { - ScopedFastFlag sff("LuauLenTM", true); - runConformance("basic.lua"); } @@ -321,12 +319,14 @@ TEST_CASE("Literals") TEST_CASE("Errors") { + ScopedFastFlag sff("LuauNicerMethodErrors", true); + runConformance("errors.lua"); } TEST_CASE("Events") { - ScopedFastFlag sff("LuauLenTM", true); + ScopedFastFlag sff("LuauBetterNewindex", true); runConformance("events.lua"); } @@ -500,8 +500,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { - ScopedFastFlag sff("LuauCheckLenMT", true); - runConformance("types.lua", [](lua_State* L) { Luau::NullModuleResolver moduleResolver; Luau::InternalErrorReporter iceHandler; @@ -872,8 +870,6 @@ TEST_CASE("ApiCalls") TEST_CASE("ApiAtoms") { - ScopedFastFlag sff("LuauLazyAtoms", true); - StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); diff --git a/tests/ConstraintSolver.test.cpp b/tests/ConstraintSolver.test.cpp index f521c667..e33f6570 100644 --- a/tests/ConstraintSolver.test.cpp +++ b/tests/ConstraintSolver.test.cpp @@ -9,7 +9,7 @@ using namespace Luau; -static TypeId requireBinding(NotNull scope, const char* name) +static TypeId requireBinding(NotNull scope, const char* name) { auto b = linearSearchForBinding(scope, name); LUAU_ASSERT(b.has_value()); @@ -26,7 +26,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ConstraintSolver cs{&arena, rootScope}; @@ -46,7 +46,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ConstraintSolver cs{&arena, rootScope}; @@ -73,7 +73,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ToStringOptions opts; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index c92c4457..f51a9d1b 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -258,7 +258,7 @@ std::optional Fixture::getType(const std::string& name) REQUIRE(module); if (FFlag::DebugLuauDeferredConstraintResolution) - return linearSearchForBinding(module->getModuleScope2(), name.c_str()); + return linearSearchForBinding(module->getModuleScope().get(), name.c_str()); else return lookupName(module->getModuleScope(), name); } @@ -410,7 +410,7 @@ void Fixture::validateErrors(const std::vector& errors) LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source) { unfreeze(typeChecker.globalTypes); - LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, source, "@test"); + LoadDefinitionFileResult result = frontend.loadDefinitionFile(source, "@test"); freeze(typeChecker.globalTypes); REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file"); @@ -434,7 +434,7 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete) ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() : Fixture() - , cgb(mainModuleName, &arena, NotNull(&ice), frontend.getGlobalScope2()) + , cgb(mainModuleName, &arena, NotNull(&ice), frontend.getGlobalScope()) , forceTheFlag{"DebugLuauDeferredConstraintResolution", true} { BlockedTypeVar::nextIndex = 0; @@ -479,17 +479,17 @@ std::optional lookupName(ScopePtr scope, const std::string& name) return std::nullopt; } -std::optional linearSearchForBinding(Scope2* scope, const char* name) +std::optional linearSearchForBinding(Scope* scope, const char* name) { while (scope) { for (const auto& [n, ty] : scope->bindings) { if (n.astName() == name) - return ty; + return ty.typeId; } - scope = scope->parent; + scope = scope->parent.get(); } return std::nullopt; diff --git a/tests/Fixture.h b/tests/Fixture.h index 4bd6f1ea..a716fe9b 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -192,7 +192,7 @@ void dump(const std::vector& constraints); std::optional lookupName(ScopePtr scope, const std::string& name); // Warning: This function runs in O(n**2) -std::optional linearSearchForBinding(Scope2* scope, const char* name); +std::optional linearSearchForBinding(Scope* scope, const char* name); } // namespace Luau diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 0382f227..3c27ad54 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1030,7 +1030,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type") ScopedFastFlag sff[] = { {"LuauForceExportSurfacesToBeNormal", true}, {"LuauLowerBoundsCalculation", true}, - {"LuauNormalizeFlagIsConservative", true}, }; fileResolver.source["Module/A"] = R"( @@ -1067,7 +1066,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias") ScopedFastFlag sff[] = { {"LuauForceExportSurfacesToBeNormal", true}, {"LuauLowerBoundsCalculation", true}, - {"LuauNormalizeFlagIsConservative", true}, }; fileResolver.source["Module/A"] = R"( diff --git a/tests/JsonEmitter.test.cpp b/tests/JsonEmitter.test.cpp new file mode 100644 index 00000000..ebe83209 --- /dev/null +++ b/tests/JsonEmitter.test.cpp @@ -0,0 +1,195 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/JsonEmitter.h" + +#include "doctest.h" + +using namespace Luau::Json; + +TEST_SUITE_BEGIN("JsonEmitter"); + +TEST_CASE("write_array") +{ + JsonEmitter emitter; + ArrayEmitter a = emitter.writeArray(); + a.writeValue(123); + a.writeValue("foo"); + a.finish(); + + std::string result = emitter.str(); + CHECK(result == "[123,\"foo\"]"); +} + +TEST_CASE("write_object") +{ + JsonEmitter emitter; + ObjectEmitter o = emitter.writeObject(); + o.writePair("foo", "bar"); + o.writePair("bar", "baz"); + o.finish(); + + std::string result = emitter.str(); + CHECK(result == "{\"foo\":\"bar\",\"bar\":\"baz\"}"); +} + +TEST_CASE("write_bool") +{ + JsonEmitter emitter; + write(emitter, false); + CHECK(emitter.str() == "false"); + + emitter = JsonEmitter{}; + write(emitter, true); + CHECK(emitter.str() == "true"); +} + +TEST_CASE("write_null") +{ + JsonEmitter emitter; + write(emitter, nullptr); + CHECK(emitter.str() == "null"); +} + +TEST_CASE("write_string") +{ + JsonEmitter emitter; + write(emitter, R"(foo,bar,baz, +"this should be escaped")"); + CHECK(emitter.str() == "\"foo,bar,baz,\\n\\\"this should be escaped\\\"\""); +} + +TEST_CASE("write_comma") +{ + JsonEmitter emitter; + emitter.writeComma(); + write(emitter, true); + emitter.writeComma(); + write(emitter, false); + CHECK(emitter.str() == "true,false"); +} + +TEST_CASE("push_and_pop_comma") +{ + JsonEmitter emitter; + emitter.writeComma(); + write(emitter, true); + emitter.writeComma(); + emitter.writeRaw('['); + bool comma = emitter.pushComma(); + emitter.writeComma(); + write(emitter, true); + emitter.writeComma(); + write(emitter, false); + emitter.writeRaw(']'); + emitter.popComma(comma); + emitter.writeComma(); + write(emitter, false); + + CHECK(emitter.str() == "true,[true,false],false"); +} + +TEST_CASE("write_optional") +{ + JsonEmitter emitter; + emitter.writeComma(); + write(emitter, std::optional{true}); + emitter.writeComma(); + write(emitter, std::nullopt); + + CHECK(emitter.str() == "true,null"); +} + +TEST_CASE("write_vector") +{ + std::vector values{1, 2, 3, 4}; + JsonEmitter emitter; + write(emitter, values); + CHECK(emitter.str() == "[1,2,3,4]"); +} + +TEST_CASE("prevent_multiple_object_finish") +{ + JsonEmitter emitter; + ObjectEmitter o = emitter.writeObject(); + o.writePair("a", "b"); + o.finish(); + o.finish(); + + CHECK(emitter.str() == "{\"a\":\"b\"}"); +} + +TEST_CASE("prevent_multiple_array_finish") +{ + JsonEmitter emitter; + ArrayEmitter a = emitter.writeArray(); + a.writeValue(1); + a.finish(); + a.finish(); + + CHECK(emitter.str() == "[1]"); +} + +TEST_CASE("cannot_write_pair_after_finished") +{ + JsonEmitter emitter; + ObjectEmitter o = emitter.writeObject(); + o.finish(); + o.writePair("a", "b"); + + CHECK(emitter.str() == "{}"); +} + +TEST_CASE("cannot_write_value_after_finished") +{ + JsonEmitter emitter; + ArrayEmitter a = emitter.writeArray(); + a.finish(); + a.writeValue(1); + + CHECK(emitter.str() == "[]"); +} + +TEST_CASE("finish_when_destructing_object") +{ + JsonEmitter emitter; + emitter.writeObject(); + + CHECK(emitter.str() == "{}"); +} + +TEST_CASE("finish_when_destructing_array") +{ + JsonEmitter emitter; + emitter.writeArray(); + + CHECK(emitter.str() == "[]"); +} + +namespace Luau::Json +{ + +struct Special +{ + int foo; + int bar; +}; + +void write(JsonEmitter& emitter, const Special& value) +{ + ObjectEmitter o = emitter.writeObject(); + o.writePair("foo", value.foo); + o.writePair("bar", value.bar); +} + +} // namespace Luau::Json + +TEST_CASE("afford_extensibility") +{ + std::vector vec{Special{1, 2}, Special{3, 4}}; + JsonEmitter e; + write(e, vec); + + std::string result = e.str(); + CHECK(result == R"([{"foo":1,"bar":2},{"foo":3,"bar":4}])"); +} + +TEST_SUITE_END(); diff --git a/tests/Lexer.test.cpp b/tests/Lexer.test.cpp new file mode 100644 index 00000000..20d8d0d5 --- /dev/null +++ b/tests/Lexer.test.cpp @@ -0,0 +1,141 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Lexer.h" + +#include "Fixture.h" +#include "ScopedFlags.h" + +#include "doctest.h" + +using namespace Luau; + +TEST_SUITE_BEGIN("LexerTests"); + +TEST_CASE("broken_string_works") +{ + const std::string testInput = "[["; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme lexeme = lexer.next(); + CHECK_EQ(lexeme.type, Lexeme::Type::BrokenString); + CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 2))); +} + +TEST_CASE("broken_comment") +{ + const std::string testInput = "--[[ "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme lexeme = lexer.next(); + CHECK_EQ(lexeme.type, Lexeme::Type::BrokenComment); + CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 6))); +} + +TEST_CASE("broken_comment_kept") +{ + const std::string testInput = "--[[ "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + CHECK_EQ(lexer.next().type, Lexeme::Type::BrokenComment); +} + +TEST_CASE("comment_skipped") +{ + const std::string testInput = "-- "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + CHECK_EQ(lexer.next().type, Lexeme::Type::Eof); +} + +TEST_CASE("multilineCommentWithLexemeInAndAfter") +{ + const std::string testInput = "--[[ function \n" + "]] end"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme comment = lexer.next(); + Lexeme end = lexer.next(); + + CHECK_EQ(comment.type, Lexeme::Type::BlockComment); + CHECK_EQ(comment.location, Luau::Location(Luau::Position(0, 0), Luau::Position(1, 2))); + CHECK_EQ(end.type, Lexeme::Type::ReservedEnd); + CHECK_EQ(end.location, Luau::Location(Luau::Position(1, 3), Luau::Position(1, 6))); +} + +TEST_CASE("testBrokenEscapeTolerant") +{ + const std::string testInput = "'\\3729472897292378'"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme item = lexer.next(); + + CHECK_EQ(item.type, Lexeme::QuotedString); + CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, int(testInput.size())))); +} + +TEST_CASE("testBigDelimiters") +{ + const std::string testInput = "--[===[\n" + "\n" + "\n" + "\n" + "]===]"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme item = lexer.next(); + + CHECK_EQ(item.type, Lexeme::Type::BlockComment); + CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(4, 5))); +} + +TEST_CASE("lookahead") +{ + const std::string testInput = "foo --[[ comment ]] bar : nil end"; + + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + lexer.next(); // must call next() before reading data from lexer at least once + + CHECK_EQ(lexer.current().type, Lexeme::Name); + CHECK_EQ(lexer.current().name, std::string("foo")); + CHECK_EQ(lexer.lookahead().type, Lexeme::Name); + CHECK_EQ(lexer.lookahead().name, std::string("bar")); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::Name); + CHECK_EQ(lexer.current().name, std::string("bar")); + CHECK_EQ(lexer.lookahead().type, ':'); + + lexer.next(); + + CHECK_EQ(lexer.current().type, ':'); + CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedNil); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::ReservedNil); + CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedEnd); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::ReservedEnd); + CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::Eof); + CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); +} + +TEST_SUITE_END(); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index b698eac0..5a32cc84 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1689,4 +1689,36 @@ TEST_CASE_FIXTURE(Fixture, "TestStringInterpolation") REQUIRE_EQ(result.warnings.size(), 1); } +TEST_CASE_FIXTURE(Fixture, "LintIntegerParsing") +{ + ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true}; + + LintResult result = lint(R"( +local _ = 0b10000000000000000000000000000000000000000000000000000000000000000 +local _ = 0x10000000000000000 +)"); + + REQUIRE_EQ(result.warnings.size(), 2); + CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and has been truncated to 2^64"); + CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); +} + +// TODO: remove with FFlagLuauErrorDoubleHexPrefix +TEST_CASE_FIXTURE(Fixture, "LintIntegerParsingDoublePrefix") +{ + ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true}; + ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; // Lint will be available until we start rejecting code + + LintResult result = lint(R"( +local _ = 0x0x123 +local _ = 0x0xffffffffffffffffffffffffffffffffff +)"); + + REQUIRE_EQ(result.warnings.size(), 2); + CHECK_EQ(result.warnings[0].text, + "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); + CHECK_EQ(result.warnings[1].text, + "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); +} + TEST_SUITE_END(); diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 84a5a380..c64c41c5 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -680,26 +680,12 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation") CHECK_EQ("((a) -> b, a) -> b", toString(requireType("apply"))); } -TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal") -{ - ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", false}}; - - check(R"( - type Fiber = { - return_: Fiber? - } - - local f: Fiber - )"); - - TypeId t = requireType("f"); - CHECK(t->normal); -} - // Unfortunately, getting this right in the general case is difficult. TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal") { - ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", true}}; + ScopedFastFlag flags[] = { + {"LuauLowerBoundsCalculation", true}, + }; check(R"( type Fiber = { @@ -1081,7 +1067,6 @@ TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their { ScopedFastFlag sff[]{ {"LuauLowerBoundsCalculation", true}, - {"LuauNormalizeFlagIsConservative", true}, }; CheckResult result = check(R"( diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 9e8ff250..c975a375 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -97,138 +97,6 @@ TEST_CASE("initial_double_is_aligned") TEST_SUITE_END(); -TEST_SUITE_BEGIN("LexerTests"); - -TEST_CASE("broken_string_works") -{ - const std::string testInput = "[["; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme lexeme = lexer.next(); - CHECK_EQ(lexeme.type, Lexeme::Type::BrokenString); - CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 2))); -} - -TEST_CASE("broken_comment") -{ - const std::string testInput = "--[[ "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme lexeme = lexer.next(); - CHECK_EQ(lexeme.type, Lexeme::Type::BrokenComment); - CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 6))); -} - -TEST_CASE("broken_comment_kept") -{ - const std::string testInput = "--[[ "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - CHECK_EQ(lexer.next().type, Lexeme::Type::BrokenComment); -} - -TEST_CASE("comment_skipped") -{ - const std::string testInput = "-- "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - CHECK_EQ(lexer.next().type, Lexeme::Type::Eof); -} - -TEST_CASE("multilineCommentWithLexemeInAndAfter") -{ - const std::string testInput = "--[[ function \n" - "]] end"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme comment = lexer.next(); - Lexeme end = lexer.next(); - - CHECK_EQ(comment.type, Lexeme::Type::BlockComment); - CHECK_EQ(comment.location, Luau::Location(Luau::Position(0, 0), Luau::Position(1, 2))); - CHECK_EQ(end.type, Lexeme::Type::ReservedEnd); - CHECK_EQ(end.location, Luau::Location(Luau::Position(1, 3), Luau::Position(1, 6))); -} - -TEST_CASE("testBrokenEscapeTolerant") -{ - const std::string testInput = "'\\3729472897292378'"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme item = lexer.next(); - - CHECK_EQ(item.type, Lexeme::QuotedString); - CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, int(testInput.size())))); -} - -TEST_CASE("testBigDelimiters") -{ - const std::string testInput = "--[===[\n" - "\n" - "\n" - "\n" - "]===]"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme item = lexer.next(); - - CHECK_EQ(item.type, Lexeme::Type::BlockComment); - CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(4, 5))); -} - -TEST_CASE("lookahead") -{ - const std::string testInput = "foo --[[ comment ]] bar : nil end"; - - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - lexer.next(); // must call next() before reading data from lexer at least once - - CHECK_EQ(lexer.current().type, Lexeme::Name); - CHECK_EQ(lexer.current().name, std::string("foo")); - CHECK_EQ(lexer.lookahead().type, Lexeme::Name); - CHECK_EQ(lexer.lookahead().name, std::string("bar")); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::Name); - CHECK_EQ(lexer.current().name, std::string("bar")); - CHECK_EQ(lexer.lookahead().type, ':'); - - lexer.next(); - - CHECK_EQ(lexer.current().type, ':'); - CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedNil); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::ReservedNil); - CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedEnd); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::ReservedEnd); - CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::Eof); - CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); -} - -TEST_SUITE_END(); - TEST_SUITE_BEGIN("ParserTests"); TEST_CASE_FIXTURE(Fixture, "basic_parse") @@ -814,20 +682,23 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary") TEST_CASE_FIXTURE(Fixture, "parse_numbers_error") { - ScopedFastFlag luauErrorParseIntegerIssues{"LuauErrorParseIntegerIssues", true}; + ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true}; + ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true}; CHECK_EQ(getParseError("return 0b123"), "Malformed number"); CHECK_EQ(getParseError("return 123x"), "Malformed number"); CHECK_EQ(getParseError("return 0xg"), "Malformed number"); CHECK_EQ(getParseError("return 0x0x123"), "Malformed number"); + CHECK_EQ(getParseError("return 0xffffffffffffffffffffllllllg"), "Malformed number"); + CHECK_EQ(getParseError("return 0x0xffffffffffffffffffffffffffff"), "Malformed number"); } -TEST_CASE_FIXTURE(Fixture, "parse_numbers_range_error") +TEST_CASE_FIXTURE(Fixture, "parse_numbers_error_soft") { - ScopedFastFlag luauErrorParseIntegerIssues{"LuauErrorParseIntegerIssues", true}; + ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true}; + ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; - CHECK_EQ(getParseError("return 0x10000000000000000"), "Integer number value is out of range"); - CHECK_EQ(getParseError("return 0b10000000000000000000000000000000000000000000000000000000000000000"), "Integer number value is out of range"); + CHECK_EQ(getParseError("return 0x0x0x0x0x0x0x0"), "Malformed number"); } TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error") diff --git a/tests/Repl.test.cpp b/tests/Repl.test.cpp index 87a1e1e2..18c243b0 100644 --- a/tests/Repl.test.cpp +++ b/tests/Repl.test.cpp @@ -82,7 +82,7 @@ private: capturedoutput = "" function arraytostring(arr) - local strings = {} + local strings = {} table.foreachi(arr, function(k,v) table.insert(strings, pptostring(v)) end ) return "{" .. table.concat(strings, ", ") .. "}" end diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 288af8da..fe376d86 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("ToString"); @@ -267,8 +268,16 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded") o.maxTypeLength = 40; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()"); - CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); - CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + } + else + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + } } TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") @@ -286,8 +295,16 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") o.maxTypeLength = 40; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()"); - CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); - CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + } + else + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + } } TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table_state_braces") @@ -497,7 +514,10 @@ local function target(callback: nil) return callback(4, "hello") end )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("(nil) -> ()", toString(requireType("target"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(nil) -> (*error-type*)", toString(requireType("target"))); + else + CHECK_EQ("(nil) -> ()", toString(requireType("target"))); } TEST_CASE_FIXTURE(Fixture, "toStringGenericPack") @@ -705,10 +725,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names") TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_generics") { - ScopedFastFlag sff[] = { - {"LuauAlwaysQuantify", true}, - }; - CheckResult result = check(R"( function foo(x: a, y) end )"); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index bdd4d6fd..e487fd48 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -94,6 +94,65 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias") } } +TEST_CASE_FIXTURE(Fixture, "mismatched_generic_type_param") +{ + CheckResult result = check(R"( + type T = (A...) -> () + )"); + + 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(result.errors[0].location == Location{{1, 21}, {1, 25}}); +} + +TEST_CASE_FIXTURE(Fixture, "mismatched_generic_pack_type_param") +{ + CheckResult result = check(R"( + type T = (A) -> () + )"); + + 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(result.errors[0].location == Location{{1, 24}, {1, 25}}); +} + +TEST_CASE_FIXTURE(Fixture, "default_type_parameter") +{ + CheckResult result = check(R"( + type T = { a: A, b: B } + local x: T = { a = "foo", b = "bar" } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("x")) == "T"); +} + +TEST_CASE_FIXTURE(Fixture, "default_pack_parameter") +{ + CheckResult result = check(R"( + type T = { fn: (A...) -> () } + local x: T + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("x")) == "T"); +} + +TEST_CASE_FIXTURE(Fixture, "saturate_to_first_type_pack") +{ + CheckResult result = check(R"( + type T = { fn: (A, B) -> C... } + local x: T + local f = x.fn + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("x")) == "T"); + CHECK(toString(requireType("f")) == "(string, number) -> (string, boolean)"); +} + TEST_CASE_FIXTURE(Fixture, "cyclic_types_of_named_table_fields_do_not_expand_when_stringified") { CheckResult result = check(R"( @@ -126,6 +185,40 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "generic_aliases") +{ + ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + type T = { v: a } + local x: T = { v = 123 } + local y: T = { v = "foo" } + local bad: T = { v = "foo" } + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); + CHECK(toString(result.errors[0]) == "Type '{ v: string }' could not be converted into 'T'"); +} + +TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") +{ + ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + type T = { v: a } + type U = { t: T } + local x: U = { t = { v = 123 } } + local bad: U = { t = { v = "foo" } } + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); + CHECK(toString(result.errors[0]) == "Type '{ t: { v: string } }' could not be converted into 'U'"); +} + TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") { CheckResult result = check(R"( @@ -360,6 +453,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_optional_parameterized_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); auto e = get(result.errors[0]); + REQUIRE(e != nullptr); CHECK_EQ("Node?", toString(e->givenType)); CHECK_EQ("Node", toString(e->wantedType)); } diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index 4c5309e0..f4766104 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -13,6 +13,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferAnyError"); TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any") @@ -94,7 +96,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2") @@ -110,7 +115,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "length_of_error_type_does_not_produce_an_error") @@ -225,7 +233,10 @@ TEST_CASE_FIXTURE(Fixture, "calling_error_type_yields_error") CHECK_EQ("unknown", err->name); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") @@ -234,7 +245,10 @@ TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") local a = Utility.Create "Foo" {} )"); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_complex_function_with_any") diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 4f6adf97..10da0efa 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -9,6 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauLowerBoundsCalculation); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("BuiltinTests"); @@ -555,6 +556,29 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_correctly_ordered_types") CHECK_EQ(tm->givenType, typeChecker.numberType); } +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_tostring_specifier") +{ + CheckResult result = check(R"( + --!strict + string.format("%* %* %* %*", "string", 1, true, function() end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_tostring_specifier_type_constraint") +{ + CheckResult result = check(R"( + local function f(x): string + local _ = string.format("%*", x) + return x + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("(string) -> string", toString(requireType("f"))); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall") { CheckResult result = check(R"( @@ -952,7 +976,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic") CHECK_EQ("number", toString(requireType("a"))); CHECK_EQ("string", toString(requireType("b"))); CHECK_EQ("boolean", toString(requireType("c"))); - CHECK_EQ("", toString(requireType("d"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("d"))); + else + CHECK_EQ("", toString(requireType("d"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index a634ba09..074c86c3 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauLowerBoundsCalculation); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -907,13 +908,19 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language") REQUIRE(tm1); CHECK_EQ("(string) -> number", toString(tm1->wantedType)); - CHECK_EQ("(string, ) -> number", toString(tm1->givenType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(string, *error-type*) -> number", toString(tm1->givenType)); + else + CHECK_EQ("(string, ) -> number", toString(tm1->givenType)); auto tm2 = get(result.errors[1]); REQUIRE(tm2); CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType)); - CHECK_EQ("(string, ) -> number", toString(tm2->givenType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(string, *error-type*) -> number", toString(tm2->givenType)); + else + CHECK_EQ("(string, ) -> number", toString(tm2->givenType)); } TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type") @@ -1526,10 +1533,20 @@ function t:b() return 2 end -- not OK )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '() -> number' could not be converted into '() -> number' + if (FFlag::LuauSpecialTypesAsterisked) + { + 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])); + } + else + { + CHECK_EQ(R"(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])); + } } TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 46258072..a8325727 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -10,6 +10,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauCheckGenericHOFTypes) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -1003,7 +1004,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") std::optional t0 = getMainModule()->getModuleScope()->lookupType("t0"); REQUIRE(t0); - CHECK_EQ("", toString(t0->type)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(t0->type)); + else + CHECK_EQ("", toString(t0->type)); auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { return get(err); @@ -1182,10 +1186,6 @@ end) TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic") { - ScopedFastFlag sff[] = { - {"LuauAlwaysQuantify", true}, - }; - CheckResult result = check(R"( function foo(f, x: X) return f(x) diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 354b3996..9b10092c 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -13,6 +13,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferLoops"); TEST_CASE_FIXTURE(Fixture, "for_loop") @@ -142,7 +144,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error") CHECK_EQ(2, result.errors.size()); TypeId p = requireType("p"); - CHECK_EQ("", toString(p)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(p)); + else + CHECK_EQ("", toString(p)); } TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function") diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 2343a7fa..a1d41339 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -12,6 +12,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferModules"); TEST_CASE_FIXTURE(BuiltinsFixture, "require") @@ -143,7 +145,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require_module_that_does_not_export") auto hootyType = requireType(bModule, "Hooty"); - CHECK_EQ("", toString(hootyType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(hootyType)); + else + CHECK_EQ("", toString(hootyType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "warn_if_you_try_to_require_a_non_modulescript") @@ -244,7 +249,11 @@ local ModuleA = require(game.A) LUAU_REQUIRE_NO_ERRORS(result); std::optional oty = requireType("ModuleA"); - CHECK_EQ("", toString(*oty)); + + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(*oty)); + else + CHECK_EQ("", toString(*oty)); } TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index c90f0a4d..3d6c0193 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -490,8 +490,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error") { - ScopedFastFlag sff("LuauCheckLenMT", true); - CheckResult result = check(R"( --!strict local foo = { diff --git a/tests/TypeInfer.primitives.test.cpp b/tests/TypeInfer.primitives.test.cpp index d60c96e0..a06fd749 100644 --- a/tests/TypeInfer.primitives.test.cpp +++ b/tests/TypeInfer.primitives.test.cpp @@ -12,6 +12,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -49,7 +50,10 @@ TEST_CASE_FIXTURE(Fixture, "string_index") REQUIRE(nat); CHECK_EQ("string", toString(nat->ty)); - CHECK_EQ("", toString(requireType("t"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("t"))); + else + CHECK_EQ("", toString(requireType("t"))); } TEST_CASE_FIXTURE(Fixture, "string_method") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 34afd560..01923f38 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -485,7 +485,6 @@ TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent") { ScopedFastFlag sff[]{ {"LuauLowerBoundsCalculation", true}, - {"LuauNormalizeFlagIsConservative", true}, {"LuauQuantifyConstrained", true}, }; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index cc8cdee3..8a1cadcf 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -8,6 +8,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauLowerBoundsCalculation) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -526,7 +527,10 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("", toString(requireTypeAtPosition({3, 28}))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28}))); + else + CHECK_EQ("", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(Fixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 5da4a340..0a130d49 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -262,7 +262,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") { CheckResult result = check(R"( --!strict - local x: { ["<>"] : number } + local x: { ["<>"] : number } x = { ["\n"] = 5 } )"); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index b92818ed..e1dc5023 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); using namespace Luau; @@ -238,10 +239,20 @@ TEST_CASE_FIXTURE(Fixture, "type_errors_infer_types") // TODO: Should we assert anything about these tests when DCR is being used? if (!FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("", toString(requireType("c"))); - CHECK_EQ("", toString(requireType("d"))); - CHECK_EQ("", toString(requireType("e"))); - CHECK_EQ("", toString(requireType("f"))); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ("*error-type*", toString(requireType("c"))); + CHECK_EQ("*error-type*", toString(requireType("d"))); + CHECK_EQ("*error-type*", toString(requireType("e"))); + CHECK_EQ("*error-type*", toString(requireType("f"))); + } + else + { + CHECK_EQ("", toString(requireType("c"))); + CHECK_EQ("", toString(requireType("d"))); + CHECK_EQ("", toString(requireType("e"))); + CHECK_EQ("", toString(requireType("f"))); + } } } @@ -651,7 +662,11 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional") std::optional t0 = getMainModule()->getModuleScope()->lookupType("t0"); REQUIRE(t0); - CHECK_EQ("", toString(t0->type)); + + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(t0->type)); + else + CHECK_EQ("", toString(t0->type)); auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { return get(err); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index d51a38f8..e0a0e5b5 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -9,6 +9,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + struct TryUnifyFixture : Fixture { TypeArena arena; @@ -121,7 +123,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_u LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ("a", toString(requireType("a"))); - CHECK_EQ("", toString(requireType("b"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("b"))); + else + CHECK_EQ("", toString(requireType("b"))); } TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_constrained") @@ -136,7 +141,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_con LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ("a", toString(requireType("a"))); - CHECK_EQ("", toString(requireType("b"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("b"))); + else + CHECK_EQ("", toString(requireType("b"))); CHECK_EQ("number", toString(requireType("c"))); } diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index bcd30498..7aefa00d 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -203,7 +203,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs") ), "@test" ); - addGlobalBinding(typeChecker, "bar", + addGlobalBinding(typeChecker, "bar", arena.addType( FunctionTypeVar{ arena.addTypePack({{typeChecker.numberType}, listOfStrings}), diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 94918692..8eb485e9 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -7,6 +7,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauLowerBoundsCalculation) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -199,7 +200,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property") CHECK_EQ(mup->missing[0], *bTy); CHECK_EQ(mup->key, "x"); - CHECK_EQ("", toString(requireType("r"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("r"))); + else + CHECK_EQ("", toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any") diff --git a/tests/TypePack.test.cpp b/tests/TypePack.test.cpp index 35852c05..1087a24c 100644 --- a/tests/TypePack.test.cpp +++ b/tests/TypePack.test.cpp @@ -25,7 +25,7 @@ struct TypePackFixture TypePackId freshTypePack() { - typePacks.emplace_back(new TypePackVar{Unifiable::Free{{}}}); + typePacks.emplace_back(new TypePackVar{Unifiable::Free{TypeLevel{}}}); return typePacks.back().get(); } diff --git a/tests/conformance/errors.lua b/tests/conformance/errors.lua index 0b5aafed..b13e7a82 100644 --- a/tests/conformance/errors.lua +++ b/tests/conformance/errors.lua @@ -295,8 +295,9 @@ end -- testing syntax limits +local syntaxdepth = if limitedstack then 200 else 1000 local function testrep (init, rep) - local s = "local a; "..init .. string.rep(rep, 300) + local s = "local a; "..init .. string.rep(rep, syntaxdepth) local a,b = loadstring(s) assert(not a) -- and string.find(b, "syntax levels")) end @@ -380,11 +381,19 @@ assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design) assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number") -assert(ecall(function() local t = {} setmetatable(t, { __newindex = function(t,i,v) end }) t[nil] = 2 end) == "table index is nil") +assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil") +assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN") -- for loop type errors assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)") assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)") assert(ecall(function() for i=1,2,'a' do end end) == "invalid 'for' step (number expected, got string)") +-- method call errors +assert(ecall(function() ({}):foo() end) == "attempt to call missing method 'foo' of table") +assert(ecall(function() (""):foo() end) == "attempt to call missing method 'foo' of string") +assert(ecall(function() (42):foo() end) == "attempt to index number with 'foo'") +assert(ecall(function() ({foo=42}):foo() end) == "attempt to call a number value") +assert(ecall(function() local ud = newproxy(true) getmetatable(ud).__index = {} ud:foo() end) == "attempt to call missing method 'foo' of userdata") + return('OK') diff --git a/tests/conformance/events.lua b/tests/conformance/events.lua index 42f1beda..6dcdbf0e 100644 --- a/tests/conformance/events.lua +++ b/tests/conformance/events.lua @@ -424,4 +424,57 @@ do assert(not ok and err:match("table or string expected")) end +-- verify that NaN/nil keys are passed to __newindex even though table assignment with them anywhere in the chain fails +do + assert(pcall(function() local t = {} t[nil] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[nil] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[nil] = 5 end) == true) + + assert(pcall(function() local t = {} t[0/0] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[0/0] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[0/0] = 5 end) == true) +end + +-- verify that __newindex gets called for frozen tables but only if the assignment is to a key absent from the table +do + local ni = {} + local t = table.create(2) + + t[1] = 42 + -- t[2] is semantically absent with storage allocated for it + + t.a = 1 + t.b = 2 + t.b = nil -- this sets 'b' value to nil but leaves key as is to exercise more internal paths -- no observable behavior change expected between b and other absent keys + + setmetatable(t, { __newindex = function(_, k, v) + assert(v == 42) + table.insert(ni, k) + end }) + table.freeze(t) + + -- "redundant" combinations are there to test all three of SETTABLEN/SETTABLEKS/SETTABLE + assert(pcall(function() t.a = 42 end) == false) + assert(pcall(function() t[1] = 42 end) == false) + assert(pcall(function() local key key = "a" t[key] = 42 end) == false) + assert(pcall(function() local key key = 1 t[key] = 42 end) == false) + + -- now repeat the same for keys absent from the table: b (semantically absent), c (physically absent), 2 (semantically absent), 3 (physically absent) + assert(pcall(function() t.b = 42 end) == true) + assert(pcall(function() t.c = 42 end) == true) + assert(pcall(function() local key key = "b" t[key] = 42 end) == true) + assert(pcall(function() local key key = "c" t[key] = 42 end) == true) + assert(pcall(function() t[2] = 42 end) == true) + assert(pcall(function() t[3] = 42 end) == true) + assert(pcall(function() local key key = 2 t[key] = 42 end) == true) + assert(pcall(function() local key key = 3 t[key] = 42 end) == true) + + -- validate the assignment sequence + local ei = { "b", "c", "b", "c", 2, 3, 2, 3 } + assert(#ni == #ei) + for k,v in ni do + assert(ei[k] == v) + end +end + return 'OK' diff --git a/tests/main.cpp b/tests/main.cpp index e7c4aed6..3e480c9f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -298,5 +298,3 @@ int main(int argc, char** argv) } return result; } - - diff --git a/tools/faillist.txt b/tools/faillist.txt index dc74a6a9..6e93345b 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -4,14 +4,11 @@ AnnotationTests.as_expr_warns_on_unrelated_cast AnnotationTests.builtin_types_are_not_exported AnnotationTests.cannot_use_nonexported_type AnnotationTests.cloned_interface_maintains_pointers_between_definitions -AnnotationTests.corecursive_types_error_on_tight_loop AnnotationTests.define_generic_type_alias AnnotationTests.duplicate_type_param_name AnnotationTests.for_loop_counter_annotation_is_checked AnnotationTests.function_return_annotations_are_checked AnnotationTests.generic_aliases_are_cloned_properly -AnnotationTests.instantiate_type_fun_should_not_trip_rbxassert -AnnotationTests.instantiation_clone_has_to_follow AnnotationTests.interface_types_belong_to_interface_arena AnnotationTests.luau_ice_triggers_an_ice AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag @@ -24,14 +21,9 @@ AnnotationTests.occurs_check_on_cyclic_union_typevar AnnotationTests.self_referential_type_alias AnnotationTests.too_many_type_params AnnotationTests.two_type_params -AnnotationTests.type_alias_always_resolve_to_a_real_type -AnnotationTests.type_alias_B_should_check_with_another_aliases_until_a_non_aliased_type -AnnotationTests.type_alias_should_alias_to_number -AnnotationTests.type_aliasing_to_number_should_not_check_given_a_string AnnotationTests.type_annotations_inside_function_bodies AnnotationTests.type_assertion_expr -AnnotationTests.typeof_variable_type_annotation_should_return_its_type -AnnotationTests.use_generic_type_alias +AnnotationTests.unknown_type_reference_generates_error AnnotationTests.use_type_required_from_another_file AstQuery.last_argument_function_call_type AstQuery::getDocumentationSymbolAtPosition.binding @@ -42,12 +34,8 @@ AutocompleteTest.argument_types AutocompleteTest.arguments_to_global_lambda AutocompleteTest.as_types AutocompleteTest.autocomplete_boolean_singleton -AutocompleteTest.autocomplete_default_type_pack_parameters -AutocompleteTest.autocomplete_default_type_parameters -AutocompleteTest.autocomplete_documentation_symbols AutocompleteTest.autocomplete_end_with_fn_exprs AutocompleteTest.autocomplete_end_with_lambda -AutocompleteTest.autocomplete_explicit_type_pack AutocompleteTest.autocomplete_first_function_arg_expected_type AutocompleteTest.autocomplete_for_in_middle_keywords AutocompleteTest.autocomplete_for_middle_keywords @@ -65,65 +53,46 @@ AutocompleteTest.autocomplete_until_in_repeat AutocompleteTest.autocomplete_while_middle_keywords AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic AutocompleteTest.bias_toward_inner_scope -AutocompleteTest.comments AutocompleteTest.cyclic_table +AutocompleteTest.do_compatible_self_calls AutocompleteTest.do_not_overwrite_context_sensitive_kws AutocompleteTest.do_not_suggest_internal_module_type -AutocompleteTest.do_not_suggest_synthetic_table_name -AutocompleteTest.dont_offer_any_suggestions_from_the_end_of_a_comment +AutocompleteTest.do_wrong_compatible_self_calls AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment_at_the_very_end_of_the_file AutocompleteTest.dont_offer_any_suggestions_from_within_a_comment AutocompleteTest.dont_suggest_local_before_its_definition -AutocompleteTest.empty_program AutocompleteTest.function_expr_params AutocompleteTest.function_in_assignment_has_parentheses AutocompleteTest.function_in_assignment_has_parentheses_2 AutocompleteTest.function_parameters AutocompleteTest.function_result_passed_to_function_has_parentheses -AutocompleteTest.function_type_types AutocompleteTest.generic_types -AutocompleteTest.get_member_completions -AutocompleteTest.get_string_completions -AutocompleteTest.get_suggestions_for_new_statement AutocompleteTest.get_suggestions_for_the_very_start_of_the_script AutocompleteTest.global_function_params AutocompleteTest.global_functions_are_not_scoped_lexically AutocompleteTest.if_then_else_elseif_completions AutocompleteTest.if_then_else_full_keywords -AutocompleteTest.keyword_members AutocompleteTest.keyword_methods AutocompleteTest.keyword_types -AutocompleteTest.leave_numbers_alone AutocompleteTest.library_non_self_calls_are_fine AutocompleteTest.library_self_calls_are_invalid AutocompleteTest.local_function AutocompleteTest.local_function_params AutocompleteTest.local_functions_fall_out_of_scope -AutocompleteTest.local_initializer -AutocompleteTest.local_initializer_2 -AutocompleteTest.local_names -AutocompleteTest.local_types_builtin AutocompleteTest.method_call_inside_function_body -AutocompleteTest.method_call_inside_if_conditional AutocompleteTest.module_type_members -AutocompleteTest.modules_with_types AutocompleteTest.nested_member_completions AutocompleteTest.nested_recursive_function AutocompleteTest.no_function_name_suggestions AutocompleteTest.no_incompatible_self_calls AutocompleteTest.no_incompatible_self_calls_2 AutocompleteTest.no_incompatible_self_calls_on_class -AutocompleteTest.no_incompatible_self_calls_provisional -AutocompleteTest.not_the_var_we_are_defining -AutocompleteTest.optional_members -AutocompleteTest.private_types -AutocompleteTest.recommend_statement_starting_keywords +AutocompleteTest.no_wrong_compatible_self_calls_with_generics AutocompleteTest.recursive_function AutocompleteTest.recursive_function_global AutocompleteTest.recursive_function_local AutocompleteTest.return_types -AutocompleteTest.skip_current_local AutocompleteTest.sometimes_the_metatable_is_an_error AutocompleteTest.source_module_preservation_and_invalidation AutocompleteTest.statement_between_two_statements @@ -131,7 +100,6 @@ AutocompleteTest.stop_at_first_stat_when_recommending_keywords AutocompleteTest.string_prim_non_self_calls_are_avoided AutocompleteTest.string_prim_self_calls_are_fine AutocompleteTest.suggest_external_module_type -AutocompleteTest.suggest_table_keys AutocompleteTest.table_intersection AutocompleteTest.table_union AutocompleteTest.type_correct_argument_type_suggestion @@ -150,13 +118,9 @@ AutocompleteTest.type_correct_local_type_suggestion AutocompleteTest.type_correct_sealed_table AutocompleteTest.type_correct_suggestion_for_overloads AutocompleteTest.type_correct_suggestion_in_argument -AutocompleteTest.type_correct_suggestion_in_table -AutocompleteTest.type_scoping_easy AutocompleteTest.unsealed_table AutocompleteTest.unsealed_table_2 -AutocompleteTest.user_defined_globals AutocompleteTest.user_defined_local_functions_in_own_definition -BuiltinDefinitionsTest.lib_documentation_symbols BuiltinTests.aliased_string_format BuiltinTests.assert_removes_falsy_types BuiltinTests.assert_removes_falsy_types2 @@ -231,16 +195,10 @@ BuiltinTests.tonumber_returns_optional_number_type BuiltinTests.tonumber_returns_optional_number_type2 BuiltinTests.xpcall DefinitionTests.class_definition_function_prop -DefinitionTests.class_definitions_cannot_extend_non_class -DefinitionTests.class_definitions_cannot_overload_non_function DefinitionTests.declaring_generic_functions DefinitionTests.definition_file_class_function_args DefinitionTests.definition_file_classes DefinitionTests.definition_file_loading -DefinitionTests.definitions_documentation_symbols -DefinitionTests.documentation_symbols_dont_attach_to_persistent_types -DefinitionTests.load_definition_file_errors_do_not_pollute_global_scope -DefinitionTests.no_cyclic_defined_classes DefinitionTests.single_class_type_identity_in_global_types FrontendTest.accumulate_cached_errors FrontendTest.accumulate_cached_errors_in_consistent_order @@ -256,12 +214,9 @@ FrontendTest.cycle_error_paths FrontendTest.cycle_errors_can_be_fixed FrontendTest.cycle_incremental_type_surface FrontendTest.cycle_incremental_type_surface_longer -FrontendTest.discard_type_graphs FrontendTest.dont_recheck_script_that_hasnt_been_marked_dirty FrontendTest.dont_reparse_clean_file_when_linting FrontendTest.environments -FrontendTest.find_a_require -FrontendTest.find_a_require_inside_a_function FrontendTest.ignore_require_to_nonexistent_file FrontendTest.imported_table_modification_2 FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded @@ -269,30 +224,90 @@ FrontendTest.no_use_after_free_with_type_fun_instantiation FrontendTest.nocheck_cycle_used_by_checked FrontendTest.nocheck_modules_are_typed FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error -FrontendTest.produce_errors_for_unchanged_file_with_errors FrontendTest.re_report_type_error_in_required_file -FrontendTest.real_source FrontendTest.recheck_if_dependent_script_is_dirty +FrontendTest.reexport_cyclic_type +FrontendTest.reexport_type_alias FrontendTest.report_require_to_nonexistent_file FrontendTest.report_syntax_error_in_required_file FrontendTest.reports_errors_from_multiple_sources FrontendTest.stats_are_not_reset_between_checks -FrontendTest.test_lint_uses_correct_config -FrontendTest.test_pruneParentSegments FrontendTest.trace_requires_in_nonstrict_mode -FrontendTest.typecheck_twice_for_ast_types +GenericsTests.apply_type_function_nested_generics1 +GenericsTests.apply_type_function_nested_generics2 +GenericsTests.better_mismatch_error_messages +GenericsTests.bound_tables_do_not_clone_original_fields +GenericsTests.check_generic_typepack_function +GenericsTests.check_mutual_generic_functions +GenericsTests.correctly_instantiate_polymorphic_member_functions +GenericsTests.do_not_always_instantiate_generic_intersection_types +GenericsTests.do_not_infer_generic_functions +GenericsTests.dont_unify_bound_types +GenericsTests.duplicate_generic_type_packs +GenericsTests.duplicate_generic_types +GenericsTests.error_detailed_function_mismatch_generic_pack +GenericsTests.error_detailed_function_mismatch_generic_types +GenericsTests.factories_of_generics +GenericsTests.function_arguments_can_be_polytypes +GenericsTests.function_results_can_be_polytypes +GenericsTests.generic_argument_count_too_few +GenericsTests.generic_argument_count_too_many +GenericsTests.generic_factories +GenericsTests.generic_functions_dont_cache_type_parameters +GenericsTests.generic_functions_in_types +GenericsTests.generic_functions_should_be_memory_safe +GenericsTests.generic_table_method +GenericsTests.generic_type_pack_parentheses +GenericsTests.generic_type_pack_syntax +GenericsTests.generic_type_pack_unification1 +GenericsTests.generic_type_pack_unification2 +GenericsTests.generic_type_pack_unification3 +GenericsTests.infer_generic_function_function_argument +GenericsTests.infer_generic_function_function_argument_overloaded +GenericsTests.infer_generic_lib_function_function_argument +GenericsTests.infer_generic_property +GenericsTests.inferred_local_vars_can_be_polytypes +GenericsTests.instantiate_cyclic_generic_function +GenericsTests.instantiate_generic_function_in_assignments +GenericsTests.instantiate_generic_function_in_assignments2 +GenericsTests.instantiated_function_argument_names +GenericsTests.instantiation_sharing_types +GenericsTests.local_vars_can_be_instantiated_polytypes +GenericsTests.mutable_state_polymorphism +GenericsTests.no_stack_overflow_from_quantifying +GenericsTests.properties_can_be_instantiated_polytypes +GenericsTests.properties_can_be_polytypes +GenericsTests.rank_N_types_via_typeof +GenericsTests.reject_clashing_generic_and_pack_names +GenericsTests.self_recursive_instantiated_param +GenericsTests.variadic_generics +IntersectionTypes.argument_is_intersection +IntersectionTypes.error_detailed_intersection_all +IntersectionTypes.error_detailed_intersection_part +IntersectionTypes.fx_intersection_as_argument +IntersectionTypes.fx_union_as_argument_fails +IntersectionTypes.index_on_an_intersection_type_with_mixed_types +IntersectionTypes.index_on_an_intersection_type_with_one_part_missing_the_property +IntersectionTypes.index_on_an_intersection_type_with_one_property_of_type_any +IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist +IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth +IntersectionTypes.no_stack_overflow_from_flattenintersection +IntersectionTypes.overload_is_not_a_function +IntersectionTypes.select_correct_union_fn +IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions +IntersectionTypes.table_intersection_setmetatable +IntersectionTypes.table_intersection_write +IntersectionTypes.table_intersection_write_sealed +IntersectionTypes.table_intersection_write_sealed_indirect +IntersectionTypes.table_write_sealed_indirect isSubtype.functions_and_any isSubtype.intersection_of_functions_of_different_arities isSubtype.intersection_of_tables isSubtype.table_with_any_prop isSubtype.table_with_table_prop isSubtype.tables -Linter.BuiltinGlobalWrite Linter.DeprecatedApi -Linter.LocalShadowGlobal Linter.TableOperations -Linter.use_all_parent_scopes_for_globals -ModuleTests.any_persistance_does_not_leak ModuleTests.builtin_types_point_into_globalTypes_arena ModuleTests.clone_self_property ModuleTests.deepClone_cyclic_table @@ -315,8 +330,6 @@ NonstrictModeTests.table_props_are_any Normalize.any_wins_the_battle_over_unknown_in_unions Normalize.constrained_intersection_of_intersections Normalize.cyclic_intersection -Normalize.cyclic_table_is_marked_normal -Normalize.cyclic_table_is_not_marked_normal Normalize.cyclic_table_normalizes_sensibly Normalize.cyclic_union Normalize.fuzz_failure_bound_type_is_normal_but_not_its_bounded_to @@ -342,45 +355,228 @@ Normalize.skip_force_normal_on_external_types Normalize.union_of_distinct_free_types Normalize.variadic_tail_is_marked_normal Normalize.visiting_a_type_twice_is_not_considered_normal -ParseErrorRecovery.empty_function_type_error_recovery -ParseErrorRecovery.extra_table_indexer_recovery -ParseErrorRecovery.extra_token_in_consume -ParseErrorRecovery.extra_token_in_consume_match -ParseErrorRecovery.extra_token_in_consume_match_end ParseErrorRecovery.generic_type_list_recovery -ParseErrorRecovery.multiple_parse_errors ParseErrorRecovery.recovery_of_parenthesized_expressions -ParseErrorRecovery.statement_error_recovery_expected -ParseErrorRecovery.statement_error_recovery_unexpected -ParserTests.break_return_not_last_error -ParserTests.continue_not_last_error -ParserTests.error_on_confusable -ParserTests.error_on_non_utf8_sequence -ParserTests.error_on_unicode -ParserTests.export_is_an_identifier_only_when_followed_by_type -ParserTests.functions_cannot_have_return_annotations_if_extensions_are_disabled -ParserTests.illegal_type_alias_if_extensions_are_disabled -ParserTests.incomplete_statement_error -ParserTests.local_cannot_have_annotation_with_extensions_disabled -ParserTests.parse_compound_assignment_error_call -ParserTests.parse_compound_assignment_error_multiple -ParserTests.parse_compound_assignment_error_not_lvalue -ParserTests.parse_error_function_call -ParserTests.parse_error_function_call_newline -ParserTests.parse_error_messages -ParserTests.parse_error_table_literal -ParserTests.parse_error_type_name -ParserTests.parse_nesting_based_end_detection ParserTests.parse_nesting_based_end_detection_failsafe_earlier ParserTests.parse_nesting_based_end_detection_local_function -ParserTests.parse_nesting_based_end_detection_local_repeat -ParserTests.parse_nesting_based_end_detection_nested -ParserTests.parse_nesting_based_end_detection_single_line -ParserTests.parse_numbers_error -ParserTests.parse_numbers_range_error -ParserTests.stop_if_line_ends_with_hyphen -ParserTests.type_alias_error_messages +ProvisionalTests.bail_early_if_unification_is_too_complicated +ProvisionalTests.choose_the_right_overload_for_pcall +ProvisionalTests.constrained_is_level_dependent +ProvisionalTests.discriminate_from_x_not_equal_to_nil +ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack +ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean +ProvisionalTests.free_is_not_bound_to_any +ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten +ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns +ProvisionalTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound +ProvisionalTests.it_should_be_agnostic_of_actual_size +ProvisionalTests.lower_bounds_calculation_is_too_permissive_with_overloaded_higher_order_functions +ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap +ProvisionalTests.normalization_fails_on_certain_kinds_of_cyclic_tables +ProvisionalTests.operator_eq_completely_incompatible +ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing +ProvisionalTests.setmetatable_constrains_free_type_into_free_table +ProvisionalTests.typeguard_inference_incomplete +ProvisionalTests.weird_fail_to_unify_type_pack +ProvisionalTests.weirditer_should_not_loop_forever +ProvisionalTests.while_body_are_also_refined +ProvisionalTests.xpcall_returns_what_f_returns +RefinementTest.and_constraint +RefinementTest.and_or_peephole_refinement +RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string +RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number +RefinementTest.assert_non_binary_expressions_actually_resolve_constraints +RefinementTest.assign_table_with_refined_property_with_a_similar_type_is_illegal +RefinementTest.call_a_more_specific_function_using_typeguard +RefinementTest.correctly_lookup_a_shadowed_local_that_which_was_previously_refined +RefinementTest.correctly_lookup_property_whose_base_was_previously_refined +RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2 +RefinementTest.discriminate_from_isa_of_x +RefinementTest.discriminate_from_truthiness_of_x +RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false +RefinementTest.discriminate_tag +RefinementTest.either_number_or_string +RefinementTest.eliminate_subclasses_of_instance +RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil +RefinementTest.free_type_is_equal_to_an_lvalue +RefinementTest.impossible_type_narrow_is_not_an_error +RefinementTest.index_on_a_refined_property +RefinementTest.invert_is_truthy_constraint +RefinementTest.invert_is_truthy_constraint_ifelse_expression +RefinementTest.is_truthy_constraint +RefinementTest.is_truthy_constraint_ifelse_expression +RefinementTest.lvalue_is_equal_to_a_term +RefinementTest.lvalue_is_equal_to_another_lvalue +RefinementTest.lvalue_is_not_nil +RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering +RefinementTest.narrow_property_of_a_bounded_variable +RefinementTest.narrow_this_large_union +RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true +RefinementTest.not_a_and_not_b +RefinementTest.not_a_and_not_b2 +RefinementTest.not_a_or_not_b +RefinementTest.not_a_or_not_b2 +RefinementTest.not_and_constraint +RefinementTest.not_t_or_some_prop_of_t +RefinementTest.or_predicate_with_truthy_predicates +RefinementTest.parenthesized_expressions_are_followed_through +RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table +RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string +RefinementTest.refine_unknowns +RefinementTest.string_not_equal_to_string_or_nil +RefinementTest.term_is_equal_to_an_lvalue +RefinementTest.truthy_constraint_on_properties +RefinementTest.type_assertion_expr_carry_its_constraints +RefinementTest.type_comparison_ifelse_expression +RefinementTest.type_guard_can_filter_for_intersection_of_tables +RefinementTest.type_guard_can_filter_for_overloaded_function +RefinementTest.type_guard_narrowed_into_nothingness +RefinementTest.type_narrow_for_all_the_userdata +RefinementTest.type_narrow_to_vector +RefinementTest.typeguard_cast_free_table_to_vector +RefinementTest.typeguard_cast_instance_or_vector3_to_vector +RefinementTest.typeguard_doesnt_leak_to_elseif +RefinementTest.typeguard_in_assert_position +RefinementTest.typeguard_in_if_condition_position +RefinementTest.typeguard_narrows_for_functions +RefinementTest.typeguard_narrows_for_table +RefinementTest.typeguard_not_to_be_string +RefinementTest.typeguard_only_look_up_types_from_global_scope +RefinementTest.unknown_lvalue_is_not_synonymous_with_other_on_not_equal +RefinementTest.what_nonsensical_condition +RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table +RefinementTest.x_is_not_instance_or_else_not_part RuntimeLimits.typescript_port_of_Result_type +TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible +TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible +TableTests.access_index_metamethod_that_returns_variadic +TableTests.accidentally_checked_prop_in_opposite_branch +TableTests.assigning_to_an_unsealed_table_with_string_literal_should_infer_new_properties_over_indexer +TableTests.augment_nested_table +TableTests.augment_table +TableTests.builtin_table_names +TableTests.call_method +TableTests.call_method_with_explicit_self_argument +TableTests.cannot_augment_sealed_table +TableTests.cannot_call_tables +TableTests.cannot_change_type_of_unsealed_table_prop +TableTests.casting_sealed_tables_with_props_into_table_with_indexer +TableTests.casting_tables_with_props_into_table_with_indexer3 +TableTests.casting_tables_with_props_into_table_with_indexer4 +TableTests.checked_prop_too_early +TableTests.common_table_element_union_in_call +TableTests.common_table_element_union_in_call_tail +TableTests.confusing_indexing +TableTests.defining_a_method_for_a_builtin_sealed_table_must_fail +TableTests.defining_a_method_for_a_local_sealed_table_must_fail +TableTests.defining_a_method_for_a_local_unsealed_table_is_ok +TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail +TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail +TableTests.defining_a_self_method_for_a_local_unsealed_table_is_ok +TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar +TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index +TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back +TableTests.dont_leak_free_table_props +TableTests.dont_quantify_table_that_belongs_to_outer_scope +TableTests.dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table +TableTests.dont_suggest_exact_match_keys +TableTests.error_detailed_indexer_key +TableTests.error_detailed_indexer_value +TableTests.error_detailed_metatable_prop +TableTests.error_detailed_prop +TableTests.error_detailed_prop_nested +TableTests.expected_indexer_from_table_union +TableTests.expected_indexer_value_type_extra +TableTests.expected_indexer_value_type_extra_2 +TableTests.explicitly_typed_table +TableTests.explicitly_typed_table_error +TableTests.explicitly_typed_table_with_indexer +TableTests.found_like_key_in_table_function_call +TableTests.found_like_key_in_table_property_access +TableTests.found_multiple_like_keys +TableTests.function_calls_produces_sealed_table_given_unsealed_table +TableTests.generalize_table_argument +TableTests.getmetatable_returns_pointer_to_metatable +TableTests.give_up_after_one_metatable_index_look_up +TableTests.hide_table_error_properties +TableTests.indexer_fn +TableTests.indexer_on_sealed_table_must_unify_with_free_table +TableTests.indexer_table +TableTests.indexing_from_a_table_should_prefer_properties_when_possible +TableTests.inequality_operators_imply_exactly_matching_types +TableTests.infer_array_2 +TableTests.infer_indexer_from_value_property_in_literal +TableTests.inferred_return_type_of_free_table +TableTests.inferring_crazy_table_should_also_be_quick +TableTests.instantiate_table_cloning +TableTests.instantiate_table_cloning_2 +TableTests.instantiate_table_cloning_3 +TableTests.instantiate_tables_at_scope_level +TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound +TableTests.leaking_bad_metatable_errors +TableTests.length_operator_intersection +TableTests.length_operator_non_table_union +TableTests.length_operator_union +TableTests.length_operator_union_errors +TableTests.less_exponential_blowup_please +TableTests.meta_add +TableTests.meta_add_both_ways +TableTests.meta_add_inferred +TableTests.metatable_mismatch_should_fail +TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred +TableTests.mixed_tables_with_implicit_numbered_keys +TableTests.MixedPropertiesAndIndexers +TableTests.nil_assign_doesnt_hit_indexer +TableTests.okay_to_add_property_to_unsealed_tables_by_function_call +TableTests.only_ascribe_synthetic_names_at_module_scope +TableTests.oop_indexer_works +TableTests.oop_polymorphic +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.pass_incompatible_union_to_a_generic_table_without_crashing +TableTests.passing_compatible_unions_to_a_generic_table_without_crashing +TableTests.persistent_sealed_table_is_immutable +TableTests.property_lookup_through_tabletypevar_metatable +TableTests.quantify_even_that_table_was_never_exported_at_all +TableTests.quantify_metatables_of_metatables_of_table +TableTests.quantifying_a_bound_var_works +TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table +TableTests.recursive_metatable_type_call +TableTests.result_is_always_any_if_lhs_is_any +TableTests.result_is_bool_for_equality_operators_if_lhs_is_any +TableTests.right_table_missing_key +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.shared_selfs +TableTests.shared_selfs_from_free_param +TableTests.shared_selfs_through_metatables +TableTests.table_function_check_use_after_free +TableTests.table_indexing_error_location +TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict +TableTests.table_insert_should_cope_with_optional_properties_in_strict +TableTests.table_length +TableTests.table_param_row_polymorphism_2 +TableTests.table_param_row_polymorphism_3 +TableTests.table_simple_call +TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors +TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors +TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2 +TableTests.table_unifies_into_map +TableTests.tables_get_names_from_their_locals +TableTests.tc_member_function +TableTests.tc_member_function_2 +TableTests.top_table_type +TableTests.type_mismatch_on_massive_table_is_cut_short +TableTests.unification_of_unions_in_a_self_referential_type +TableTests.unifying_tables_shouldnt_uaf1 +TableTests.unifying_tables_shouldnt_uaf2 +TableTests.used_colon_correctly +TableTests.used_colon_instead_of_dot +TableTests.used_dot_instead_of_colon +TableTests.used_dot_instead_of_colon_but_correctly +TableTests.width_subtyping ToDot.bound_table ToDot.class ToDot.function @@ -388,7 +584,6 @@ ToDot.metatable ToDot.primitive ToDot.table ToString.exhaustive_toString_of_cyclic_table -ToString.function_type_with_argument_names ToString.function_type_with_argument_names_and_self ToString.function_type_with_argument_names_generic ToString.named_metatable_toStringNamedFunction @@ -401,51 +596,63 @@ ToString.toStringNamedFunction_id ToString.toStringNamedFunction_map ToString.toStringNamedFunction_overrides_param_names ToString.toStringNamedFunction_variadics -TranspilerTests.attach_types TranspilerTests.type_lists_should_be_emitted_correctly TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive -TypeAliases.basic_alias -TypeAliases.cannot_steal_hoisted_type_alias +TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification +TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType +TryUnifyTests.result_of_failed_typepack_unification_is_constrained +TryUnifyTests.typepack_unification_should_trim_free_tails +TryUnifyTests.variadics_should_use_reversed_properly TypeAliases.cli_38393_recursive_intersection_oom -TypeAliases.corecursive_function_types TypeAliases.corecursive_types_generic -TypeAliases.cyclic_function_type_in_type_alias -TypeAliases.cyclic_types_of_named_table_fields_do_not_expand_when_stringified TypeAliases.do_not_quantify_unresolved_aliases -TypeAliases.dont_stop_typechecking_after_reporting_duplicate_type_definition -TypeAliases.export_type_and_type_alias_are_duplicates TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any -TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2 -TypeAliases.free_variables_from_typeof_in_aliases TypeAliases.general_require_multi_assign TypeAliases.generic_param_remap -TypeAliases.generic_typevars_are_not_considered_to_escape_their_scope_if_they_are_reused_in_multiple_aliases -TypeAliases.module_export_free_type_leak -TypeAliases.module_export_wrapped_free_type_leak -TypeAliases.mutually_recursive_aliases +TypeAliases.mismatched_generic_pack_type_param +TypeAliases.mismatched_generic_type_param TypeAliases.mutually_recursive_generic_aliases -TypeAliases.mutually_recursive_types_errors TypeAliases.mutually_recursive_types_restriction_not_ok_1 TypeAliases.mutually_recursive_types_restriction_not_ok_2 -TypeAliases.mutually_recursive_types_restriction_ok TypeAliases.mutually_recursive_types_swapsies_not_ok -TypeAliases.mutually_recursive_types_swapsies_ok -TypeAliases.names_are_ascribed -TypeAliases.non_recursive_aliases_that_reuse_a_generic_name TypeAliases.recursive_types_restriction_not_ok -TypeAliases.recursive_types_restriction_ok -TypeAliases.reported_location_is_correct_when_type_alias_are_duplicates TypeAliases.stringify_optional_parameterized_alias TypeAliases.stringify_type_alias_of_recursive_template_table_type TypeAliases.stringify_type_alias_of_recursive_template_table_type2 -TypeAliases.type_alias_fwd_declaration_is_precise TypeAliases.type_alias_import_mutation TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename -TypeAliases.type_alias_local_synthetic_mutation TypeAliases.type_alias_of_an_imported_recursive_generic_type TypeAliases.type_alias_of_an_imported_recursive_type -TypeAliases.use_table_name_and_generic_params_in_errors +TypeInfer.check_expr_recursion_limit +TypeInfer.checking_should_not_ice +TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error +TypeInfer.cyclic_follow +TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table +TypeInfer.dont_report_type_errors_within_an_AstStatError +TypeInfer.follow_on_new_types_in_substitution +TypeInfer.free_typevars_introduced_within_control_flow_constructs_do_not_get_an_elevated_TypeLevel +TypeInfer.globals +TypeInfer.globals2 +TypeInfer.index_expr_should_be_checked +TypeInfer.infer_assignment_value_types +TypeInfer.infer_assignment_value_types_mutable_lval +TypeInfer.infer_through_group_expr +TypeInfer.infer_type_assertion_value_type +TypeInfer.no_heap_use_after_free_error +TypeInfer.no_stack_overflow_from_isoptional +TypeInfer.recursive_metatable_crash +TypeInfer.tc_after_error_recovery_no_replacement_name_in_error +TypeInfer.tc_if_else_expressions1 +TypeInfer.tc_if_else_expressions2 +TypeInfer.tc_if_else_expressions_expected_type_1 +TypeInfer.tc_if_else_expressions_expected_type_2 +TypeInfer.tc_if_else_expressions_expected_type_3 +TypeInfer.tc_if_else_expressions_type_union +TypeInfer.type_infer_recursion_limit_no_ice +TypeInfer.types stored in astResolvedTypes +TypeInfer.warn_on_lowercase_parent_property +TypeInfer.weird_case TypeInferAnyError.any_type_propagates TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any TypeInferAnyError.call_to_any_yields_any @@ -496,26 +703,280 @@ TypeInferClasses.we_can_infer_that_a_parameter_must_be_a_particular_class TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments TypeInferFunctions.another_recursive_local_function +TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types +TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.check_function_before_lambda_that_uses_it TypeInferFunctions.complicated_return_types_require_an_explicit_annotation TypeInferFunctions.cyclic_function_type_in_args TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists +TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site +TypeInferFunctions.dont_mutate_the_underlying_head_of_typepack_when_calling_with_self TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict +TypeInferFunctions.error_detailed_function_mismatch_arg +TypeInferFunctions.error_detailed_function_mismatch_arg_count +TypeInferFunctions.error_detailed_function_mismatch_ret +TypeInferFunctions.error_detailed_function_mismatch_ret_count +TypeInferFunctions.error_detailed_function_mismatch_ret_mult TypeInferFunctions.first_argument_can_be_optional +TypeInferFunctions.free_is_not_bound_to_unknown TypeInferFunctions.func_expr_doesnt_leak_free +TypeInferFunctions.function_cast_error_uses_correct_language +TypeInferFunctions.function_decl_non_self_sealed_overwrite +TypeInferFunctions.function_decl_non_self_sealed_overwrite_2 +TypeInferFunctions.function_decl_non_self_unsealed_overwrite +TypeInferFunctions.function_decl_quantify_right_type +TypeInferFunctions.function_does_not_return_enough_values +TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer TypeInferFunctions.higher_order_function_2 TypeInferFunctions.higher_order_function_4 +TypeInferFunctions.ignored_return_values +TypeInferFunctions.inconsistent_higher_order_function +TypeInferFunctions.inconsistent_return_types +TypeInferFunctions.infer_anonymous_function_arguments +TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_return_type_from_selected_overload TypeInferFunctions.infer_that_function_does_not_return_a_table +TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time +TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time2 TypeInferFunctions.it_is_ok_not_to_supply_enough_retvals TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count TypeInferFunctions.mutual_recursion +TypeInferFunctions.no_lossy_function_type +TypeInferFunctions.occurs_check_failure_in_function_return_type +TypeInferFunctions.quantify_constrained_types +TypeInferFunctions.record_matching_overload TypeInferFunctions.recursive_function TypeInferFunctions.recursive_local_function +TypeInferFunctions.report_exiting_without_return_nonstrict +TypeInferFunctions.report_exiting_without_return_strict +TypeInferFunctions.return_type_by_overload +TypeInferFunctions.strict_mode_ok_with_missing_arguments +TypeInferFunctions.too_few_arguments_variadic +TypeInferFunctions.too_few_arguments_variadic_generic +TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_many_arguments +TypeInferFunctions.too_many_return_values TypeInferFunctions.toposort_doesnt_break_mutual_recursion TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size +TypeInferLoops.correctly_scope_locals_while +TypeInferLoops.for_in_loop +TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values +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_should_fail_with_non_function_iterator +TypeInferLoops.for_in_loop_where_iteratee_is_free +TypeInferLoops.for_in_loop_with_custom_iterator +TypeInferLoops.for_in_loop_with_next +TypeInferLoops.for_in_with_a_custom_iterator_should_type_check +TypeInferLoops.for_in_with_an_iterator_of_type_any +TypeInferLoops.for_in_with_just_one_iterator_is_ok +TypeInferLoops.fuzz_fail_missing_instantitation_follow +TypeInferLoops.ipairs_produces_integral_indices +TypeInferLoops.loop_iter_basic +TypeInferLoops.loop_iter_iter_metamethod +TypeInferLoops.loop_iter_no_indexer_nonstrict +TypeInferLoops.loop_iter_no_indexer_strict +TypeInferLoops.loop_iter_trailing_nil +TypeInferLoops.loop_typecheck_crash_on_empty_optional +TypeInferLoops.properly_infer_iteratee_is_a_free_table +TypeInferLoops.repeat_loop +TypeInferLoops.repeat_loop_condition_binds_to_its_block +TypeInferLoops.symbols_in_repeat_block_should_not_be_visible_beyond_until_condition +TypeInferLoops.unreachable_code_after_infinite_loop +TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free +TypeInferLoops.while_loop +TypeInferModules.bound_free_table_export_is_ok +TypeInferModules.constrained_anyification_clone_immutable_types +TypeInferModules.custom_require_global +TypeInferModules.do_not_modify_imported_types +TypeInferModules.do_not_modify_imported_types_2 +TypeInferModules.do_not_modify_imported_types_3 +TypeInferModules.do_not_modify_imported_types_4 +TypeInferModules.general_require_call_expression +TypeInferModules.general_require_type_mismatch +TypeInferModules.module_type_conflict +TypeInferModules.module_type_conflict_instantiated +TypeInferModules.require +TypeInferModules.require_a_variadic_function +TypeInferModules.require_failed_module +TypeInferModules.require_module_that_does_not_export +TypeInferModules.require_types +TypeInferModules.type_error_of_unknown_qualified_type +TypeInferModules.warn_if_you_try_to_require_a_non_modulescript +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon +TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table +TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory +TypeInferOOP.method_depends_on_table +TypeInferOOP.methods_are_topologically_sorted +TypeInferOOP.nonstrict_self_mismatch_tail +TypeInferOOP.object_constructor_can_refer_to_method_of_self +TypeInferOOP.table_oop +TypeInferOperators.and_adds_boolean +TypeInferOperators.and_adds_boolean_no_superfluous_union +TypeInferOperators.and_binexps_dont_unify +TypeInferOperators.and_or_ternary +TypeInferOperators.CallAndOrOfFunctions +TypeInferOperators.cannot_compare_tables_that_do_not_have_the_same_metatable +TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable +TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators +TypeInferOperators.cli_38355_recursive_union +TypeInferOperators.compare_numbers +TypeInferOperators.compare_strings +TypeInferOperators.compound_assign_basic +TypeInferOperators.compound_assign_metatable +TypeInferOperators.compound_assign_mismatch_metatable +TypeInferOperators.compound_assign_mismatch_op +TypeInferOperators.compound_assign_mismatch_result +TypeInferOperators.concat_op_on_free_lhs_and_string_rhs +TypeInferOperators.concat_op_on_string_lhs_and_free_rhs +TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops +TypeInferOperators.dont_strip_nil_from_rhs_or_operator +TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds +TypeInferOperators.error_on_invalid_operand_types_to_relational_operators +TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2 +TypeInferOperators.expected_types_through_binary_and +TypeInferOperators.expected_types_through_binary_or +TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators +TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown +TypeInferOperators.operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap +TypeInferOperators.operator_eq_verifies_types_do_intersect +TypeInferOperators.or_joins_types +TypeInferOperators.or_joins_types_with_no_extras +TypeInferOperators.primitive_arith_no_metatable +TypeInferOperators.primitive_arith_no_metatable_with_follows +TypeInferOperators.primitive_arith_possible_metatable +TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not +TypeInferOperators.refine_and_or +TypeInferOperators.some_primitive_binary_ops +TypeInferOperators.strict_binary_op_where_lhs_unknown +TypeInferOperators.strip_nil_from_lhs_or_operator +TypeInferOperators.strip_nil_from_lhs_or_operator2 +TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection +TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs +TypeInferOperators.typecheck_unary_len_error +TypeInferOperators.typecheck_unary_minus +TypeInferOperators.typecheck_unary_minus_error +TypeInferOperators.unary_not_is_boolean +TypeInferOperators.unknown_type_in_comparison +TypeInferOperators.UnknownGlobalCompoundAssign +TypeInferPrimitives.cannot_call_primitives +TypeInferPrimitives.CheckMethodsOfNumber +TypeInferPrimitives.string_function_other +TypeInferPrimitives.string_index +TypeInferPrimitives.string_length +TypeInferPrimitives.string_method +TypeInferUnknownNever.array_like_table_of_never_is_inhabitable +TypeInferUnknownNever.assign_to_global_which_is_never +TypeInferUnknownNever.assign_to_local_which_is_never +TypeInferUnknownNever.assign_to_prop_which_is_never +TypeInferUnknownNever.assign_to_subscript_which_is_never +TypeInferUnknownNever.call_never +TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators +TypeInferUnknownNever.index_on_never +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never +TypeInferUnknownNever.length_of_never +TypeInferUnknownNever.math_operators_and_never +TypeInferUnknownNever.never_is_reflexive +TypeInferUnknownNever.never_subtype_and_string_supertype +TypeInferUnknownNever.pick_never_from_variadic_type_pack +TypeInferUnknownNever.string_subtype_and_never_supertype +TypeInferUnknownNever.string_subtype_and_unknown_supertype +TypeInferUnknownNever.table_with_prop_of_type_never_is_also_reflexive +TypeInferUnknownNever.table_with_prop_of_type_never_is_uninhabitable +TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable +TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2 +TypeInferUnknownNever.unary_minus_of_never +TypeInferUnknownNever.unknown_is_reflexive +TypeInferUnknownNever.unknown_subtype_and_string_supertype +TypePackTests.cyclic_type_packs +TypePackTests.higher_order_function +TypePackTests.multiple_varargs_inference_are_not_confused +TypePackTests.no_return_size_should_be_zero +TypePackTests.pack_tail_unification_check +TypePackTests.parenthesized_varargs_returns_any +TypePackTests.self_and_varargs_should_work +TypePackTests.type_alias_backwards_compatible +TypePackTests.type_alias_default_export +TypePackTests.type_alias_default_mixed_self +TypePackTests.type_alias_default_type_chained +TypePackTests.type_alias_default_type_errors +TypePackTests.type_alias_default_type_pack_self_chained_tp +TypePackTests.type_alias_default_type_pack_self_tp +TypePackTests.type_alias_default_type_self +TypePackTests.type_alias_defaults_confusing_types +TypePackTests.type_alias_defaults_recursive_type +TypePackTests.type_alias_type_pack_explicit +TypePackTests.type_alias_type_pack_explicit_multi +TypePackTests.type_alias_type_pack_multi +TypePackTests.type_alias_type_pack_variadic +TypePackTests.type_alias_type_packs +TypePackTests.type_alias_type_packs_errors +TypePackTests.type_alias_type_packs_import +TypePackTests.type_alias_type_packs_nested +TypePackTests.type_pack_hidden_free_tail_infinite_growth +TypePackTests.type_pack_type_parameters +TypePackTests.varargs_inference_through_multiple_scopes +TypePackTests.variadic_argument_tail +TypePackTests.variadic_pack_syntax +TypePackTests.variadic_packs +TypeSingletons.bool_singleton_subtype +TypeSingletons.bool_singletons +TypeSingletons.bool_singletons_mismatch +TypeSingletons.enums_using_singletons +TypeSingletons.enums_using_singletons_mismatch +TypeSingletons.enums_using_singletons_subtyping +TypeSingletons.error_detailed_tagged_union_mismatch_bool +TypeSingletons.error_detailed_tagged_union_mismatch_string +TypeSingletons.function_call_with_singletons_mismatch +TypeSingletons.if_then_else_expression_singleton_options +TypeSingletons.indexing_on_string_singletons +TypeSingletons.indexing_on_union_of_string_singletons +TypeSingletons.no_widening_from_callsites +TypeSingletons.overloaded_function_call_with_singletons +TypeSingletons.overloaded_function_call_with_singletons_mismatch +TypeSingletons.return_type_of_f_is_not_widened +TypeSingletons.string_singleton_subtype +TypeSingletons.string_singletons +TypeSingletons.string_singletons_escape_chars +TypeSingletons.string_singletons_mismatch +TypeSingletons.table_insert_with_a_singleton_argument +TypeSingletons.table_properties_type_error_escapes +TypeSingletons.tagged_unions_using_singletons +TypeSingletons.taking_the_length_of_string_singleton +TypeSingletons.taking_the_length_of_union_of_string_singleton +TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton +TypeSingletons.widening_happens_almost_everywhere +TypeSingletons.widening_happens_almost_everywhere_except_for_tables +UnionTypes.error_detailed_optional +UnionTypes.error_detailed_union_all +UnionTypes.error_detailed_union_part +UnionTypes.error_takes_optional_arguments +UnionTypes.index_on_a_union_type_with_missing_property +UnionTypes.index_on_a_union_type_with_mixed_types +UnionTypes.index_on_a_union_type_with_one_optional_property +UnionTypes.index_on_a_union_type_with_one_property_of_type_any +UnionTypes.index_on_a_union_type_with_property_guaranteed_to_exist +UnionTypes.index_on_a_union_type_works_at_arbitrary_depth +UnionTypes.optional_arguments +UnionTypes.optional_assignment_errors +UnionTypes.optional_call_error +UnionTypes.optional_field_access_error +UnionTypes.optional_index_error +UnionTypes.optional_length_error +UnionTypes.optional_missing_key_error_details +UnionTypes.optional_union_follow +UnionTypes.optional_union_functions +UnionTypes.optional_union_members +UnionTypes.optional_union_methods +UnionTypes.return_types_can_be_disjoint +UnionTypes.table_union_write_indirect +UnionTypes.unify_unsealed_table_union_check +UnionTypes.union_equality_comparisons diff --git a/tools/perfgraph.py b/tools/perfgraph.py index 95baef9c..ae74b7d6 100644 --- a/tools/perfgraph.py +++ b/tools/perfgraph.py @@ -6,6 +6,12 @@ import sys import svg +import argparse +import json + +argumentParser = argparse.ArgumentParser(description='Generate flamegraph SVG from Luau sampling profiler dumps') +argumentParser.add_argument('source_file', type=open) +argumentParser.add_argument('--json', dest='useJson',action='store_const',const=1,default=0,help='Parse source_file as JSON') class Node(svg.Node): def __init__(self): @@ -27,26 +33,64 @@ class Node(svg.Node): def details(self, root): return "Function: {} [{}:{}] ({:,} usec, {:.1%}); self: {:,} usec".format(self.function, self.source, self.line, self.width, self.width / root.width, self.ticks) -with open(sys.argv[1]) as f: - dump = f.readlines() -root = Node() +def nodeFromCallstackListFile(source_file): + dump = source_file.readlines() + root = Node() -for l in dump: - ticks, stack = l.strip().split(" ", 1) - node = root + for l in dump: + ticks, stack = l.strip().split(" ", 1) + node = root - for f in reversed(stack.split(";")): - source, function, line = f.split(",") + for f in reversed(stack.split(";")): + source, function, line = f.split(",") - child = node.child(f) - child.function = function - child.source = source - child.line = int(line) if len(line) > 0 else 0 + child = node.child(f) + child.function = function + child.source = source + child.line = int(line) if len(line) > 0 else 0 + + node = child + + node.ticks += int(ticks) + + return root + + +def nodeFromJSONbject(node, key, obj): + source, function, line = key.split(",") + + node.function = function + node.source = source + node.line = int(line) if len(line) > 0 else 0 + + node.ticks = obj['Duration'] + + for key, obj in obj['Children'].items(): + nodeFromJSONbject(node.child(key), key, obj) + + return node + + +def nodeFromJSONFile(source_file): + dump = json.load(source_file) + + root = Node() + + for key, obj in dump['Children'].items(): + nodeFromJSONbject(root.child(key), key, obj) + + return root + + +arguments = argumentParser.parse_args() + +if arguments.useJson: + root = nodeFromJSONFile(arguments.source_file) +else: + root = nodeFromCallstackListFile(arguments.source_file) - node = child - node.ticks += int(ticks) svg.layout(root, lambda n: n.ticks) svg.display(root, "Flame Graph", "hot", flip = True) diff --git a/tools/test_dcr.py b/tools/test_dcr.py index 8b090bcd..0efea3c4 100644 --- a/tools/test_dcr.py +++ b/tools/test_dcr.py @@ -14,6 +14,11 @@ def loadFailList(): with open(FAIL_LIST_PATH) as f: return set(map(str.strip, f.readlines())) +def safeParseInt(i, default=0): + try: + return int(i) + except ValueError: + return default class Handler(x.ContentHandler): def __init__(self, failList): @@ -22,6 +27,8 @@ class Handler(x.ContentHandler): self.results = {} # {DottedName: TrueIfTheTestPassed} + self.numSkippedTests = 0 + def startElement(self, name, attrs): if name == "TestSuite": self.currentTest.append(attrs["name"]) @@ -30,20 +37,18 @@ class Handler(x.ContentHandler): elif name == "OverallResultsAsserts": if self.currentTest: - try: - failed = 0 != int(attrs["failures"]) - except ValueError: - failed = False + passed = 0 == safeParseInt(attrs["failures"]) dottedName = ".".join(self.currentTest) - shouldFail = dottedName in self.failList - if failed and not shouldFail: - print("UNEXPECTED: {} should have passed".format(dottedName)) - elif not failed and shouldFail: - print("UNEXPECTED: {} should have failed".format(dottedName)) + # Sometimes we get multiple XML trees for the same test. All of + # them must report a pass in order for us to consider the test + # to have passed. + r = self.results.get(dottedName, True) + self.results[dottedName] = r and passed - self.results[dottedName] = not failed + elif name == 'OverallResultsTestCases': + self.numSkippedTests = safeParseInt(attrs.get("skipped", 0)) def endElement(self, name): if name == "TestCase": @@ -97,6 +102,12 @@ def main(): p.wait() + for testName, passed in handler.results.items(): + if passed and testName in failList: + print('UNEXPECTED: {} should have failed'.format(testName)) + elif not passed and testName not in failList: + print('UNEXPECTED: {} should have passed'.format(testName)) + if args.write: newFailList = sorted( ( @@ -111,6 +122,10 @@ def main(): print(name, file=f) print("Updated faillist.txt") + if handler.numSkippedTests > 0: + print('{} test(s) were skipped! That probably means that a test segfaulted!'.format(handler.numSkippedTests), file=sys.stderr) + sys.exit(1) + sys.exit( 0 if all(