Merge branch 'master' of https://github.com/Roblox/luau into environment-scope

This commit is contained in:
JohnnyMorganz 2022-08-07 11:41:28 +01:00
commit dfbc7e0282
140 changed files with 5190 additions and 2192 deletions

3
.gitignore vendored
View file

@ -8,3 +8,6 @@
/default.prof*
/fuzz-*
/luau
/luau-tests
/luau-analyze
__pycache__

View file

@ -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<TypeId, TypeId> typeArguments;
std::unordered_map<TypePackId, TypePackId> 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

View file

@ -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 <string>
#include <memory>
@ -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<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, NameConstraint>;
BinaryConstraint, NameConstraint, TypeAliasExpansionConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct Constraint

View file

@ -17,21 +17,22 @@
namespace Luau
{
struct Scope2;
struct Scope;
using ScopePtr = std::shared_ptr<Scope>;
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<std::pair<Location, std::unique_ptr<Scope2>>> scopes;
std::vector<std::pair<Location, ScopePtr>> scopes;
ModuleName moduleName;
SingletonTypes& singletonTypes;
const NotNull<TypeArena> 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<const AstExpr*, TypeId> astTypes{nullptr};
// A mapping of AST node to TypePackId.
@ -41,6 +42,8 @@ struct ConstraintGraphBuilder
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
// Type packs resolved from type annotations. Analogous to astTypePacks.
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
// Defining scopes for AST nodes.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
int recursionCount = 0;
@ -50,42 +53,42 @@ struct ConstraintGraphBuilder
// Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice;
NotNull<Scope2> globalScope;
NotNull<Scope> globalScope;
ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope2> globalScope);
ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope> globalScope);
/**
* Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to.
*/
TypeId freshType(NotNull<Scope2> 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<Scope2> 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<Scope2> childScope(Location location, NotNull<Scope2> 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<Scope2> 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<Scope2> scope, std::unique_ptr<Constraint> c);
void addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> 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<Scope2> scope, AstStatBlock* block);
void visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
void visit(NotNull<Scope2> scope, AstStat* stat);
void visit(NotNull<Scope2> scope, AstStatBlock* block);
void visit(NotNull<Scope2> scope, AstStatLocal* local);
void visit(NotNull<Scope2> scope, AstStatFor* for_);
void visit(NotNull<Scope2> scope, AstStatLocalFunction* function);
void visit(NotNull<Scope2> scope, AstStatFunction* function);
void visit(NotNull<Scope2> scope, AstStatReturn* ret);
void visit(NotNull<Scope2> scope, AstStatAssign* assign);
void visit(NotNull<Scope2> scope, AstStatIf* ifStatement);
void visit(NotNull<Scope2> 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<Scope2> scope, const AstArray<AstExpr*>& exprs);
TypePackId checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs);
TypePackId checkPack(NotNull<Scope2> scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(NotNull<Scope2> scope, AstExpr* expr);
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> 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<Scope2> scope, AstExpr* expr);
TypeId check(const ScopePtr& scope, AstExpr* expr);
TypeId checkExprTable(NotNull<Scope2> scope, AstExprTable* expr);
TypeId check(NotNull<Scope2> scope, AstExprIndexName* indexName);
TypeId check(NotNull<Scope2> scope, AstExprIndexExpr* indexExpr);
TypeId check(NotNull<Scope2> scope, AstExprUnary* unary);
TypeId check(NotNull<Scope2> 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<Scope2> bodyScope;
ScopePtr bodyScope;
};
FunctionSignature checkFunctionSignature(NotNull<Scope2> 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<Scope2> 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<Scope2> 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<Scope2> scope, AstTypePack* tp);
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp);
TypePackId resolveTypePack(NotNull<Scope2> scope, const AstTypeList& list);
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list);
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(NotNull<Scope2> scope, AstArray<AstGenericType> generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(NotNull<Scope2> scope, AstArray<AstGenericTypePack> packs);
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(const ScopePtr& scope, AstArray<AstGenericTypePack> packs);
TypeId flattenPack(NotNull<Scope2> 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<Scope2> 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<NotNull<Constraint>> collectConstraints(NotNull<Scope2> rootScope);
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope> rootScope);
} // namespace Luau

View file

@ -17,15 +17,35 @@ namespace Luau
// never dereference this pointer.
using BlockedConstraintId = const void*;
struct InstantiationSignature
{
TypeFun fn;
std::vector<TypeId> arguments;
std::vector<TypePackId> 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<NotNull<Constraint>> constraints;
NotNull<Scope2> rootScope;
// The entire set of constraints that the solver is trying to resolve.
std::vector<NotNull<Constraint>> constraints;
NotNull<Scope> rootScope;
// Constraints that the solver has generated, rather than sourcing from the
// scope tree.
std::vector<std::unique_ptr<Constraint>> 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<NotNull<const Constraint>, size_t> blockedConstraints;
// A mapping of type/pack pointers to the constraints they block.
std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>> blocked;
// Memoized instantiations of type aliases.
DenseHashMap<InstantiationSignature, TypeId, HashInstantiationSignature> instantiatedAliases{{}};
ConstraintSolverLogger logger;
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope2> rootScope);
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope);
/**
* Attempts to dispatch all pending constraints and reach a type solution
@ -62,6 +84,7 @@ struct ConstraintSolver
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
void block(NotNull<const Constraint> target, NotNull<const Constraint> 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<Scope2> rootScope, struct ToStringOptions& opts);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau

View file

@ -15,8 +15,8 @@ namespace Luau
struct ConstraintSolverLogger
{
std::string compileOutput();
void captureBoundarySnapshot(const Scope2* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void prepareStepSnapshot(const Scope2* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void commitPreparedStepSnapshot();
private:

View file

@ -152,7 +152,9 @@ struct Frontend
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)>);
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
NotNull<Scope2> getGlobalScope2();
LoadDefinitionFileResult loadDefinitionFile(std::string_view source, const std::string& packageName);
NotNull<Scope> getGlobalScope();
private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
@ -169,7 +171,7 @@ private:
std::unordered_map<std::string, ScopePtr> environments;
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
std::unique_ptr<Scope2> globalScope2;
ScopePtr globalScope;
public:
FileResolver* fileResolver;
@ -180,6 +182,7 @@ public:
ConfigResolver* configResolver;
FrontendOptions options;
InternalErrorReporter iceHandler;
TypeArena globalTypes;
TypeArena arenaForAutocomplete;
std::unordered_map<ModuleName, SourceNode> sourceNodes;

View file

@ -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 <type_traits>
#include <string>
#include <optional>
#include <vector>
#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<typename T>
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<std::string> chunks;
void newChunk();
};
/// An interface for writing an object into a JsonEmitter instance.
/// @see JsonEmitter::writeObject
struct ObjectEmitter
{
ObjectEmitter(NotNull<JsonEmitter> emitter);
~ObjectEmitter();
NotNull<JsonEmitter> 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<typename T>
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<JsonEmitter> emitter);
~ArrayEmitter();
NotNull<JsonEmitter> emitter;
bool comma;
bool finished;
/// Writes a value to the array.
/// @param value the value to write.
template<typename T>
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<typename T>
void write(JsonEmitter& emitter, const std::vector<T>& 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<typename T>
void write(JsonEmitter& emitter, const std::optional<T>& v)
{
if (v.has_value())
write(emitter, *v);
else
emitter.writeRaw("null");
}
} // namespace Luau::Json

View file

@ -52,6 +52,7 @@ struct LintWarning
Code_DuplicateCondition = 24,
Code_MisleadingAndOr = 25,
Code_CommentDirective = 26,
Code_IntegerParsing = 27,
Code__Count
};

View file

@ -68,8 +68,7 @@ struct Module
std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scope2s; // never empty
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
DenseHashMap<const AstExpr*, TypePackId> 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.

View file

@ -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

View file

@ -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<NotNull<Scope>> children;
std::unordered_map<Symbol, Binding> bindings;
std::unordered_map<Name, TypeFun> typeBindings;
std::unordered_map<Name, TypePackId> typePackBindings;
TypePackId returnType;
bool breakOk = false;
std::optional<TypePackId> varargPack;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
TypeLevel level;
@ -45,7 +51,9 @@ struct Scope
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
std::optional<TypeId> lookup(const Symbol& name);
std::optional<TypeId> lookup(Symbol sym);
std::optional<TypeFun> lookupTypeBinding(const Name& name);
std::optional<TypePackId> lookupTypePackBinding(const Name& name);
std::optional<TypeFun> lookupType(const Name& name);
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name);
@ -66,24 +74,4 @@ struct Scope
std::unordered_map<Name, TypePackId> 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<NotNull<Scope2>> children;
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
std::unordered_map<Name, TypeId> typeBindings;
std::unordered_map<Name, TypePackId> typePackBindings;
TypePackId returnType;
std::optional<TypePackId> varargPack;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
std::optional<TypeId> lookup(Symbol sym);
std::optional<TypeId> lookupTypeBinding(const Name& name);
std::optional<TypePackId> lookupTypePackBinding(const Name& name);
};
} // namespace Luau

View file

@ -139,6 +139,8 @@ struct FindDirty : Tarjan
{
std::vector<bool> 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<TypeId, TypeId> newTypes{nullptr};
DenseHashMap<TypePackId, TypePackId> newPacks{nullptr};
DenseHashSet<TypeId> replacedTypes{nullptr};
DenseHashSet<TypePackId> replacedTypePacks{nullptr};
std::optional<TypeId> substitute(TypeId ty);
std::optional<TypePackId> substitute(TypePackId tp);

View file

@ -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<TypeId, TypeId> typeArguments;
std::unordered_map<TypePackId, TypePackId> 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<GenericTypeDefinition> genericTypes;

View file

@ -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<TypeId> 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<TypeId> defaultValue;
bool operator==(const GenericTypeDefinition& rhs) const;
};
struct GenericTypePackDefinition
{
TypePackId tp;
std::optional<TypePackId> defaultValue;
bool operator==(const GenericTypePackDefinition& rhs) const;
};
struct FunctionArgument
@ -275,7 +279,7 @@ struct FunctionTypeVar
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
TypeLevel level;
Scope2* scope = nullptr;
Scope* scope = nullptr;
/// These should all be generic
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
@ -344,7 +348,7 @@ struct TableTypeVar
TableState state = TableState::Unsealed;
TypeLevel level;
Scope2* scope = nullptr;
Scope* scope = nullptr;
std::optional<std::string> 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<GenericTypeDefinition> 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<TypeId> typeArguments, std::vector<TypePackId> packArguments);
TypeFun fn;
std::vector<TypeId> typeArguments;
std::vector<TypePackId> 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<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, BlockedTypeVar, SingletonTypeVar, FunctionTypeVar, TableTypeVar,
MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar, UnknownTypeVar, NeverTypeVar>;
using TypeVariant =
Unifiable::Variant<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, BlockedTypeVar, PendingExpansionTypeVar, SingletonTypeVar, FunctionTypeVar,
TableTypeVar, MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar, UnknownTypeVar, NeverTypeVar>;
struct TypeVar final
{

View file

@ -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;

View file

@ -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<LazyTypeVar>(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<NeverTypeVar>(ty))
visit(ty, *ntv);
else if (auto petv = get<PendingExpansionTypeVar>(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<TypePack>(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<VariadicTypePack>(tp))
{
bool res = visit(tp, *pack);
if (!FFlag::LuauNormalizeFlagIsConservative || res)
if (res)
traverse(pack->ty);
}
else

View file

@ -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<FreeTypeVar>(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<GenericTypeVar>(ty))
return true;
else
return false;
}
bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
{
if (get<GenericTypePack>(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

View file

@ -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);
}
@ -773,7 +773,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(indexer);
});
}
void write(struct AstTableIndexer* indexer)
{
if (indexer)
@ -1178,7 +1178,6 @@ struct AstJsonEncoder : public AstVisitor
write("location", comment.location);
popComma(c);
writeRaw("}");
}
}
};

View file

@ -314,7 +314,7 @@ std::optional<Binding> 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<AstStatLocal*> bindingStatement = findBindingLocalStatement(source, iter->second);
if (!bindingStatement || !(*bindingStatement)->location.contains(pos))
return iter->second;

View file

@ -12,7 +12,7 @@
#include <unordered_set>
#include <utility>
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2)
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3)
static const std::unordered_set<std::string> 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<TypeId> 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<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> 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<ClassTypeVar>(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<ClassTypeVar>(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<ClassTypeVar>(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<TypeId> firstArgTy = first(ftv->argTypes))
{
if (std::optional<TypeId> 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<TableTypeVar>(mt->metatable))
fillMetatableProps(mtable);
@ -442,7 +441,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryMap inner;
std::unordered_set<TypeId> 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<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix2)
else if (auto pt = get<PrimitiveTypeVar>(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<StringSingleton>(get<SingletonTypeVar>(ty)))
else if (FFlag::LuauSelfCallAutocompleteFix3 && get<StringSingleton>(get<SingletonTypeVar>(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

View file

@ -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<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
if (tableName == metatableName)
mtv.syntheticName = tableName;
else
else if (!FFlag::LuauBuiltInMetatableNoBadSynthetic)
mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }";
}

View file

@ -48,6 +48,7 @@ struct TypeCloner
void operator()(const Unifiable::Bound<TypeId>& 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<PendingExpansionTypeVar>(res);
LUAU_ASSERT(petv);
seenTypes[typeId] = res;
std::vector<TypeId> typeArguments;
for (TypeId arg : t.typeArguments)
typeArguments.push_back(clone(arg, dest, cloneState));
std::vector<TypePackId> 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<TypeId> 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<TypePackId> 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<PendingExpansionTypeVar>(ty))
{
PendingExpansionTypeVar clone{petv->fn, petv->typeArguments, petv->packArguments};
result = dest.addType(std::move(clone));
}
else
return result;

View file

@ -14,7 +14,7 @@ namespace Luau
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
ConstraintGraphBuilder::ConstraintGraphBuilder(
const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope2> globalScope)
const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope> globalScope)
: moduleName(moduleName)
, singletonTypes(getSingletonTypes())
, arena(arena)
@ -25,36 +25,34 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(
LUAU_ASSERT(arena);
}
TypeId ConstraintGraphBuilder::freshType(NotNull<Scope2> scope)
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope)
{
return arena->addType(FreeTypeVar{scope});
return arena->addType(FreeTypeVar{scope.get()});
}
TypePackId ConstraintGraphBuilder::freshTypePack(NotNull<Scope2> scope)
TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope)
{
FreeTypePack f{scope};
FreeTypePack f{scope.get()};
return arena->addTypePack(TypePackVar{std::move(f)});
}
NotNull<Scope2> ConstraintGraphBuilder::childScope(Location location, NotNull<Scope2> parent)
ScopePtr ConstraintGraphBuilder::childScope(Location location, const ScopePtr& parent)
{
auto scope = std::make_unique<Scope2>();
NotNull<Scope2> borrow = NotNull(scope.get());
scopes.emplace_back(location, std::move(scope));
auto scope = std::make_shared<Scope>(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<Scope2> scope, ConstraintV cv)
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv)
{
scope->constraints.emplace_back(new Constraint{std::move(cv)});
}
void ConstraintGraphBuilder::addConstraint(NotNull<Scope2> scope, std::unique_ptr<Constraint> c)
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> 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<Scope2>());
rootScope = scopes.back().second.get();
NotNull<Scope2> borrow = NotNull(rootScope);
ScopePtr scope = std::make_shared<Scope>(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<Scope2> scope, AstStatBlock* block)
void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
{
RecursionCounter counter{&recursionCount};
@ -91,11 +89,58 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull<Scope2> scope,
return;
}
std::unordered_map<Name, Location> 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<AstStatTypeAlias>())
{
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<Scope2> scope, AstStat* stat)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
{
RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit};
@ -119,26 +164,34 @@ void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStat* stat)
visit(scope, i);
else if (auto a = stat->as<AstStatTypeAlias>())
visit(scope, a);
else if (auto s = stat->as<AstStatDeclareGlobal>())
visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>())
visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>())
visit(scope, s);
else
LUAU_ASSERT(0);
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatLocal* local)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
{
std::vector<TypeId> 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<Scope2> scope, AstStatLocal* local)
}
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> 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<Scope2> scope, AstStatFor* for_)
checkNumber(for_->to);
checkNumber(for_->step);
NotNull<Scope2> 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<Scope2> scope)
void addConstraints(Constraint* constraint, NotNull<Scope> 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<Scope2> childScope : scope->children)
for (NotNull<Scope> childScope : scope->children)
addConstraints(constraint, childScope);
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatLocalFunction* function)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function)
{
// Local
// Global
@ -213,21 +265,21 @@ void ConstraintGraphBuilder::visit(NotNull<Scope2> 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<Constraint> 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<Scope2> 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<Scope2> 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<AstExprGlobal>())
{
@ -262,9 +314,9 @@ void ConstraintGraphBuilder::visit(NotNull<Scope2> 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<AstExprIndexName>())
{
@ -291,39 +343,26 @@ void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatFunction* funct
checkFunctionBody(sig.bodyScope, function->func);
std::unique_ptr<Constraint> 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<Scope2> 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<Scope2> scope, AstStatBlock* block)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
{
NotNull<Scope2> 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<AstStatTypeAlias>())
{
TypeId initialType = freshType(scope);
scope->typeBindings[alias->name.value] = initialType;
}
}
ScopePtr innerScope = childScope(block->location, scope);
visitBlockWithoutChildScope(innerScope, block);
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> 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<Scope2> scope, AstStatAssign* assign)
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatIf* ifStatement)
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
{
check(scope, ifStatement->condition);
NotNull<Scope2> thenScope = childScope(ifStatement->thenbody->location, scope);
ScopePtr thenScope = childScope(ifStatement->thenbody->location, scope);
visit(thenScope, ifStatement->thenbody);
if (ifStatement->elsebody)
{
NotNull<Scope2> elseScope = childScope(ifStatement->elsebody->location, scope);
ScopePtr elseScope = childScope(ifStatement->elsebody->location, scope);
visit(elseScope, ifStatement->elsebody);
}
}
void ConstraintGraphBuilder::visit(NotNull<Scope2> 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<FreeTypeVar>(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<BoundTypeVar>(ty);
asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(ty);
addConstraint(scope, NameConstraint{ty, alias->name.value});
}
TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstArray<AstExpr*> 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<AstExpr*> exprs)
{
if (exprs.size == 0)
return arena->addTypePack({});
@ -392,7 +450,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstArray<Ast
return arena->addTypePack(TypePack{std::move(types), last});
}
TypePackId ConstraintGraphBuilder::checkExprList(NotNull<Scope2> scope, const AstArray<AstExpr*>& exprs)
TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs)
{
TypePackId result = arena->addTypePack({});
TypePack* resultPack = getMutable<TypePack>(result);
@ -413,7 +471,7 @@ TypePackId ConstraintGraphBuilder::checkExprList(NotNull<Scope2> scope, const As
return result;
}
TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstExpr* expr)
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
{
RecursionCounter counter{&recursionCount};
@ -468,7 +526,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstExpr* exp
return result;
}
TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExpr* expr)
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
{
RecursionCounter counter{&recursionCount};
@ -548,7 +606,7 @@ TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExpr* expr)
return result;
}
TypeId ConstraintGraphBuilder::check(NotNull<Scope2> 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<Scope2> scope, AstExprIndexName* in
return result;
}
TypeId ConstraintGraphBuilder::check(NotNull<Scope2> 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<Scope2> scope, AstExprIndexExpr* in
return result;
}
TypeId ConstraintGraphBuilder::check(NotNull<Scope2> 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<Scope2> scope, AstExprUnary* unary)
return singletonTypes.errorRecoveryType();
}
TypeId ConstraintGraphBuilder::check(NotNull<Scope2> 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<Scope2> scope, AstExprBinary* binar
return nullptr;
}
TypeId ConstraintGraphBuilder::checkExprTable(NotNull<Scope2> scope, AstExprTable* expr)
TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTable* expr)
{
TypeId ty = arena->addType(TableTypeVar{});
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
@ -674,10 +732,10 @@ TypeId ConstraintGraphBuilder::checkExprTable(NotNull<Scope2> scope, AstExprTabl
return ty;
}
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(NotNull<Scope2> 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<TypeId> 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<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureBorrow, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks);
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> 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<TypeId> 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<Scope2> scope, AstExprFunction* fn)
void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
{
visitBlockWithoutChildScope(scope, fn->body);
@ -789,20 +842,65 @@ void ConstraintGraphBuilder::checkFunctionBody(NotNull<Scope2> scope, AstExprFun
}
}
TypeId ConstraintGraphBuilder::resolveType(NotNull<Scope2> scope, AstType* ty)
TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool topLevel)
{
TypeId result = nullptr;
if (auto ref = ty->as<AstTypeReference>())
{
// 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<TypeFun> 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<TypeId> parameters;
std::vector<TypePackId> 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<AstTypeTable>())
{
@ -834,7 +932,7 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull<Scope2> scope, AstType* ty)
{
// TODO: Recursion limit.
bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0;
Scope2* signatureScope = nullptr;
ScopePtr signatureScope = nullptr;
std::vector<TypeId> genericTypes;
std::vector<TypePackId> genericTypePacks;
@ -843,22 +941,21 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull<Scope2> scope, AstType* ty)
// for the generic bindings to live on.
if (hasGenerics)
{
NotNull<Scope2> signatureBorrow = childScope(fn->location, scope);
signatureScope = signatureBorrow.get();
signatureScope = childScope(fn->location, scope);
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureBorrow, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks);
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> 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<Scope2> 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<Scope2> 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<Scope2> scope, AstType* ty)
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, AstTypePack* tp)
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp)
{
TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>())
@ -964,7 +1059,15 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, AstTyp
}
else if (auto gen = tp->as<AstTypePackGeneric>())
{
result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}});
if (std::optional<TypePackId> 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<Scope2> scope, AstTyp
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, const AstTypeList& list)
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list)
{
std::vector<TypeId> head;
@ -994,12 +1097,12 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, const
return arena->addTypePack(TypePack{head, tail});
}
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(NotNull<Scope2> scope, AstArray<AstGenericType> generics)
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics)
{
std::vector<std::pair<Name, GenericTypeDefinition>> 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<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue)
@ -1015,12 +1118,12 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
}
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks(
NotNull<Scope2> scope, AstArray<AstGenericTypePack> generics)
const ScopePtr& scope, AstArray<AstGenericTypePack> generics)
{
std::vector<std::pair<Name, GenericTypePackDefinition>> 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<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue)
@ -1035,7 +1138,7 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
return result;
}
TypeId ConstraintGraphBuilder::flattenPack(NotNull<Scope2> 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<Scope2> globalScope;
const NotNull<Scope> globalScope;
const NotNull<TypeArena> arena;
GlobalPrepopulator(NotNull<Scope2> globalScope, NotNull<TypeArena> arena)
GlobalPrepopulator(NotNull<Scope> globalScope, NotNull<TypeArena> arena)
: globalScope(globalScope)
, arena(arena)
{
@ -1073,29 +1176,29 @@ struct GlobalPrepopulator : AstVisitor
bool visit(AstStatFunction* function) override
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
globalScope->bindings[g->name] = arena->addType(BlockedTypeVar{});
globalScope->bindings[g->name] = Binding{arena->addType(BlockedTypeVar{})};
return true;
}
};
void ConstraintGraphBuilder::prepopulateGlobalScope(NotNull<Scope2> 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<NotNull<Constraint>>& result, NotNull<Scope2> scope)
void collectConstraints(std::vector<NotNull<Constraint>>& result, NotNull<Scope> scope)
{
for (const auto& c : scope->constraints)
result.push_back(NotNull{c.get()});
for (NotNull<Scope2> child : scope->children)
for (NotNull<Scope> child : scope->children)
collectConstraints(result, child);
}
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope2> rootScope)
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope> rootScope)
{
std::vector<NotNull<Constraint>> result;
collectConstraints(result, rootScope);

View file

@ -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<Scope2> scope, ToStringOptions& opts)
[[maybe_unused]] static void dumpBindings(NotNull<Scope> 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<Scope2> child : scope->children)
for (NotNull<Scope> child : scope->children)
dumpBindings(child, opts);
}
static void dumpConstraints(NotNull<Scope2> scope, ToStringOptions& opts)
static void dumpConstraints(NotNull<Scope> scope, ToStringOptions& opts)
{
for (const ConstraintPtr& c : scope->constraints)
{
printf("\t%s\n", toString(*c, opts).c_str());
}
for (NotNull<Scope2> child : scope->children)
for (NotNull<Scope> child : scope->children)
dumpConstraints(child, opts);
}
void dump(NotNull<Scope2> rootScope, ToStringOptions& opts)
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(
const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments, TypeArena* arena)
{
std::vector<TypeId> saturatedTypeArguments;
std::vector<TypeId> extraTypes;
std::vector<TypePackId> 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 = A> = (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<TypeId>{}(signature.fn.type);
for (const GenericTypeDefinition& p : signature.fn.typeParams)
{
hash ^= (std::hash<TypeId>{}(p.ty) << 1);
}
for (const GenericTypePackDefinition& p : signature.fn.typePackParams)
{
hash ^= (std::hash<TypePackId>{}(p.tp) << 1);
}
for (const TypeId a : signature.arguments)
{
hash ^= (std::hash<TypeId>{}(a) << 1);
}
for (const TypePackId a : signature.packArguments)
{
hash ^= (std::hash<TypePackId>{}(a) << 1);
}
return hash;
}
void dump(NotNull<Scope> rootScope, ToStringOptions& opts)
{
printf("constraints:\n");
dumpConstraints(rootScope, opts);
@ -55,7 +221,7 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
}
}
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope2> rootScope)
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> 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<const Constraint> constraint, bool fo
success = tryDispatch(*bc, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint))
success = tryDispatch(*nc, constraint);
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
success = tryDispatch(*taec, constraint);
else
LUAU_ASSERT(0);
@ -325,6 +494,198 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
return true;
}
struct InfiniteTypeFinder : TypeVarOnceVisitor
{
ConstraintSolver* solver;
const InstantiationSignature& signature;
bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature)
: solver(solver)
, signature(signature)
{
}
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
{
auto [typeArguments, packArguments] = saturateArguments(petv.fn, petv.typeArguments, petv.packArguments, solver->arena);
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<const Constraint> constraint)
{
const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(follow(c.target));
if (!petv)
{
unblock(c.target);
return true;
}
auto bindResult = [this, &c](TypeId result) {
asMutable(c.target)->ty.emplace<BoundTypeVar>(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<TypeId> 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<MetatableTypeVar>(target))
{
instantiated = applyTypeFunction.clone(target);
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(instantiated);
mtv->table = applyTypeFunction.clone(mtv->table);
ttv = getMutable<TableTypeVar>(mtv->table);
}
else if (get<TableTypeVar>(target))
{
instantiated = applyTypeFunction.clone(target);
ttv = getMutable<TableTypeVar>(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<const Constraint> constraint)
{
blocked[target].push_back(constraint);
@ -388,7 +749,7 @@ void ConstraintSolver::unblock(TypePackId progressed)
bool ConstraintSolver::isBlocked(TypeId ty)
{
return nullptr != get<BlockedTypeVar>(follow(ty));
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
}
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
@ -415,4 +776,12 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
u.log.commit();
}
void ConstraintSolver::pushConstraint(ConstraintV cv)
{
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(std::move(cv));
NotNull<Constraint> borrow = NotNull(c.get());
solverConstraints.push_back(std::move(c));
unsolvedConstraints.push_back(borrow);
}
} // namespace Luau

View file

@ -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<NotNull<const Constraint>>& constraints, ToStringOptions& opts)
@ -80,51 +74,49 @@ static std::string dumpConstraintsToDot(std::vector<NotNull<const Constraint>>&
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<NotNull<const Constraint>>& unsolvedConstraints)
void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& 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<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& 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<size_t>(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<size_t>(current.get()));
snapshot += "\",\"current\":\"";
snapshot += toString(*current, opts);
snapshot += "\"}";
preparedSnapshot = std::move(snapshot);
preparedSnapshot = emitter.str();
}
void ConstraintSolverLogger::commitPreparedStepSnapshot()

View file

@ -2,7 +2,6 @@
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAG(LuauCheckLenMT)
namespace Luau
{
@ -123,6 +122,7 @@ declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(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<K, V>(obj: {[K]: V} | string): number\n";
if (FFlag::LuauUnknownAndNeverType)
result += "declare function error<T>(message: T, level: number?): never\n";
else

View file

@ -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");
@ -770,35 +822,28 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
}
NotNull<Scope2> Frontend::getGlobalScope2()
NotNull<Scope> Frontend::getGlobalScope()
{
if (!globalScope2)
if (!globalScope)
{
const SingletonTypes& singletonTypes = getSingletonTypes();
globalScope2 = std::make_unique<Scope2>();
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<Module>();
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);

View file

@ -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<FunctionTypeVar>(ty))
return true;
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassTypeVar>(ty))
return true;
else
return false;
}

View file

@ -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 <string.h>
namespace Luau::Json
{
static constexpr int CHUNK_SIZE = 1024;
ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> 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<JsonEmitter> 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

View file

@ -48,6 +48,7 @@ static const char* kWarningNames[] = {
"DuplicateCondition",
"MisleadingAndOr",
"CommentDirective",
"IntegerParsing",
};
// clang-format on
@ -1433,7 +1434,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)
{
@ -2589,6 +2590,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;
@ -2810,6 +2850,9 @@ std::vector<LintWarning> 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;

View file

@ -15,7 +15,6 @@
#include <algorithm>
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<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
std::unordered_map<Name, TypeFun>* 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

View file

@ -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<void*>& 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);

View file

@ -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<TypeId> generics;
std::vector<TypePackId> 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<FunctionTypeVar>(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<FunctionTypeVar>(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<TypeId> insertedGenerics;
std::vector<TypePackId> 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<TypeId> result = quantifier.substitute(ty);

View file

@ -21,22 +21,6 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
level.subLevel = subLevel;
}
std::optional<TypeId> 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<TypeFun> Scope::lookupType(const Name& name)
{
const Scope* scope = this;
@ -121,48 +105,48 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt;
}
std::optional<TypeId> Scope2::lookup(Symbol sym)
std::optional<TypeId> 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<TypeId> Scope2::lookupTypeBinding(const Name& name)
std::optional<TypeFun> 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<TypePackId> Scope2::lookupTypePackBinding(const Name& name)
std::optional<TypePackId> 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;

View file

@ -9,9 +9,12 @@
#include <stdexcept>
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<FunctionTypeVar>(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<PendingExpansionTypeVar>(ty))
{
for (TypeId a : petv->typeArguments)
visitChild(a);
for (TypePackId a : petv->packArguments)
visitChild(a);
}
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(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<TypeId> 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<TypePackId> 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<FunctionTypeVar>(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<PendingExpansionTypeVar>(ty))
{
for (TypeId& a : petv->typeArguments)
a = replace(a);
for (TypePackId& a : petv->packArguments)
a = replace(a);
}
else if (ClassTypeVar* ctv = getMutable<ClassTypeVar>(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)

View file

@ -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("<CYCLE>");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return;
}
@ -561,7 +580,10 @@ struct TypeVarStringifier
if (state.hasSeen(&ttv))
{
state.result.cycle = true;
state.emit("<CYCLE>");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return;
}
@ -691,7 +713,10 @@ struct TypeVarStringifier
if (state.hasSeen(&uv))
{
state.result.cycle = true;
state.emit("<CYCLE>");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return;
}
@ -758,7 +783,10 @@ struct TypeVarStringifier
if (state.hasSeen(&uv))
{
state.result.cycle = true;
state.emit("<CYCLE>");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return;
}
@ -803,7 +831,10 @@ struct TypeVarStringifier
void operator()(TypeId, const ErrorTypeVar& tv)
{
state.result.error = true;
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
else
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*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("<CYCLETP>");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*CYCLETP*");
else
state.emit("<CYCLETP>");
return;
}
@ -926,14 +963,22 @@ struct TypePackStringifier
void operator()(TypePackId, const Unifiable::Error& error)
{
state.result.error = true;
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
if (FFlag::LuauSpecialTypesAsterisked)
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
else
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
}
void operator()(TypePackId, const VariadicTypePack& pack)
{
state.emit("...");
if (FFlag::DebugLuauVerboseTypeNames && pack.hidden)
state.emit("<hidden>");
{
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*hidden*");
else
state.emit("<hidden>");
}
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 += "... <TRUNCATED>";
if (FFlag::LuauSpecialTypesAsterisked)
result.name += "... *TRUNCATED*";
else
result.name += "... <TRUNCATED>";
}
return result;
@ -1199,7 +1248,12 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
}
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
result.name += "... <TRUNCATED>";
{
if (FFlag::LuauSpecialTypesAsterisked)
result.name += "... *TRUNCATED*";
else
result.name += "... <TRUNCATED>";
}
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<T, TypeAliasExpansionConstraint>)
{
ToStringResult targetStr = toStringDetailed(c.target, opts);
opts.nameMap = std::move(targetStr.nameMap);
return "expand " + targetStr.name;
}
else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
};

View file

@ -99,6 +99,11 @@ public:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*blocked*"));
}
AstType* operator()(const PendingExpansionTypeVar& petv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*pending-expansion*"));
}
AstType* operator()(const ConstrainedTypeVar& ctv)
{
AstArray<AstType*> types;

View file

@ -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<AstExpr*> exprs, TypeArena& arena)
{
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> 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<TypeFun> 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<TypePackId> 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;

View file

@ -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;
}
}
@ -2109,35 +2106,16 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
if (const UnionTypeVar* utv = get<UnionTypeVar>(t))
{
if (FFlag::LuauReduceUnionRecursion)
for (TypeId ty : utv)
{
for (TypeId ty : utv)
{
if (FFlag::LuauNormalizeFlagIsConservative)
ty = follow(ty);
if (get<NeverTypeVar>(ty))
continue;
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
ty = follow(ty);
if (get<NeverTypeVar>(ty))
continue;
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
return {ty};
if (result.end() == std::find(result.begin(), result.end(), ty))
result.push_back(ty);
}
}
else
{
std::vector<TypeId> r = reduceUnion(utv->options);
for (TypeId ty : r)
{
ty = follow(ty);
if (get<NeverTypeVar>(ty))
continue;
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(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())
@ -2465,7 +2443,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
DenseHashSet<TypeId> seen{nullptr};
if (FFlag::LuauCheckLenMT && typeCouldHaveMetatable(operandType))
if (typeCouldHaveMetatable(operandType))
{
if (auto fnt = findMetatableEntry(operandType, "__len", expr.location, /* addErrors= */ true))
{
@ -3640,6 +3618,9 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
}
}
if (!currentModule->astTypes.find(&function))
currentModule->astTypes[&function] = ty;
}
else
ice("Checking non functional type");
@ -4768,16 +4749,8 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
{
const FunctionTypeVar* ftv = get<FunctionTypeVar>(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)
{
@ -5251,7 +5224,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{&currentModule->internalTypes, scope->level};
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes};
for (size_t i = 0; i < typesProvided; ++i)
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
@ -5492,65 +5465,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<FreeTypeVar>(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<GenericTypeVar>(ty))
return true;
else
return false;
}
bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
{
if (get<GenericTypePack>(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<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location)
{
if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type;
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes};
for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i];

View file

@ -445,6 +445,16 @@ BlockedTypeVar::BlockedTypeVar()
int BlockedTypeVar::nextIndex = 0;
PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: fn(fn)
, typeArguments(typeArguments)
, packArguments(packArguments)
, index(++nextIndex)
{
}
size_t PendingExpansionTypeVar::nextIndex = 0;
FunctionTypeVar::FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
: argTypes(argTypes)
, retTypes(retTypes)
@ -1058,7 +1068,7 @@ ConstrainedTypeVarIterator end(const ConstrainedTypeVar* ctv)
static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size)
{
const char* options = "cdiouxXeEfgGqs";
const char* options = "cdiouxXeEfgGqs*";
std::vector<TypeId> result;
@ -1072,7 +1082,7 @@ static std::vector<TypeId> 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<TypeId> 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

View file

@ -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)

View file

@ -474,16 +474,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

View file

@ -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)
{
}

View file

@ -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)
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
@ -2034,8 +2033,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);
@ -2055,9 +2056,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";
}
}
@ -2065,11 +2063,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])
@ -2077,10 +2077,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;
@ -2092,6 +2089,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])
{
@ -2120,6 +2119,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()
{
@ -2160,10 +2226,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<AstExprConstantNumber>(start, value, result);
}
else if (DFFlag::LuaReportParseIntegerIssues)
{
double value = 0;
if (const char* error = parseNumber_DEPRECATED2(value, scratchData.c_str()))
{
nextLexeme();
@ -2920,39 +2997,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

View file

@ -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"

View file

@ -20,6 +20,9 @@
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifdef CALLGRIND
@ -27,6 +30,7 @@
#endif
#include <locale.h>
#include <signal.h>
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);
}

View file

@ -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);

View file

@ -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,

View file

@ -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<T>* FValue<T>::list = nullptr;
#define LUAU_FASTFLAGVARIABLE(flag, def) \
namespace FFlag \
{ \
Luau::FValue<bool> flag(#flag, def, false, nullptr); \
Luau::FValue<bool> flag(#flag, def, false); \
}
#define LUAU_FASTINT(flag) \
namespace FInt \
@ -108,7 +99,7 @@ FValue<T>* FValue<T>::list = nullptr;
#define LUAU_FASTINTVARIABLE(flag, def) \
namespace FInt \
{ \
Luau::FValue<int> flag(#flag, def, false, nullptr); \
Luau::FValue<int> flag(#flag, def, false); \
}
#define LUAU_DYNAMIC_FASTFLAG(flag) \
@ -119,7 +110,7 @@ FValue<T>* FValue<T>::list = nullptr;
#define LUAU_DYNAMIC_FASTFLAGVARIABLE(flag, def) \
namespace DFFlag \
{ \
Luau::FValue<bool> flag(#flag, def, true, nullptr); \
Luau::FValue<bool> flag(#flag, def, true); \
}
#define LUAU_DYNAMIC_FASTINT(flag) \
namespace DFInt \
@ -129,5 +120,5 @@ FValue<T>* FValue<T>::list = nullptr;
#define LUAU_DYNAMIC_FASTINTVARIABLE(flag, def) \
namespace DFInt \
{ \
Luau::FValue<int> flag(#flag, def, true, nullptr); \
Luau::FValue<int> flag(#flag, def, true); \
}

View file

@ -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

View file

@ -3,7 +3,7 @@
#include <stddef.h>
/* 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);

View file

@ -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"))

View file

@ -6,6 +6,8 @@
#include <algorithm>
#include <string.h>
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");
}

View file

@ -25,9 +25,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)
namespace Luau
{
@ -276,9 +275,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<AstExprCall>() || node->is<AstExprVarargs>();
AstExprCall* expr = node->as<AstExprCall>();
if (!expr)
return node->is<AstExprVarargs>();
@ -310,27 +306,10 @@ struct Compiler
if (AstExprCall* expr = node->as<AstExprCall>())
{
// 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...
@ -1030,9 +1009,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;
@ -1044,36 +1022,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)
@ -3437,30 +3491,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<AstExprCall>())
{
AstExprFunction* func = self->getFunctionExpr(expr->func);
Function* fi = func ? self->functions.find(func) : nullptr;
returnsOne &= fi && fi->returnsOne;
}
else if (value->is<AstExprVarargs>())
{
returnsOne = false;
}
}
else
{
returnsOne = false;
}
returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]);
return false;
}
@ -3601,7 +3632,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)

View file

@ -6,8 +6,6 @@
#include <limits.h>
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;

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -11,7 +11,7 @@
#include <string.h>
/* 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");

View file

@ -11,8 +11,6 @@
#include <stdio.h>
#include <stdlib.h>
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);

View file

@ -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;

View file

@ -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)

View file

@ -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;
}

View file

@ -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)

View file

@ -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);

View file

@ -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, ...);

View file

@ -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;

View file

@ -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);

View file

@ -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
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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:

View file

@ -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) \
{ \

View file

@ -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, "...");
}

View file

@ -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<<linegaplog2 instructions; allocated after lineinfo */
struct LocVar* locvars; /* information about local variables */
TString** upvalues; /* upvalue names */
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<<linegaplog2 instructions; allocated after lineinfo
struct LocVar* locvars; // information about local variables
TString** upvalues; // upvalue names
TString* source;
TString* debugname;
@ -294,7 +294,7 @@ typedef struct Proto
int linedefined;
uint8_t nups; /* number of upvalues */
uint8_t nups; // number of upvalues
uint8_t numparams;
uint8_t is_vararg;
uint8_t maxstacksize;
@ -304,9 +304,9 @@ typedef struct Proto
typedef struct LocVar
{
TString* varname;
int startpc; /* first point where variable is active */
int endpc; /* first point where variable is dead */
uint8_t reg; /* register slot, relative to base, where variable is stored */
int startpc; // first point where variable is active
int endpc; // first point where variable is dead
uint8_t reg; // register slot, relative to base, where variable is stored
} LocVar;
/*
@ -317,19 +317,19 @@ typedef struct UpVal
{
CommonHeader;
// 1 (x86) or 5 (x64) byte padding
TValue* v; /* points to stack or to its own value */
TValue* v; // points to stack or to its own value
union
{
TValue value; /* the value (when closed) */
TValue value; // the value (when closed)
struct
{
/* global double linked list (when open) */
// global double linked list (when open)
struct UpVal* prev;
struct UpVal* next;
/* thread double linked list (when open) */
// thread double linked list (when open)
struct UpVal* threadnext;
/* note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State */
// note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State
struct UpVal** threadprev;
} l;
} u;
@ -381,7 +381,7 @@ typedef struct TKey
::Value value;
int extra[LUA_EXTRA_SIZE];
unsigned tt : 4;
int next : 28; /* for chaining */
int next : 28; // for chaining
} TKey;
typedef struct LuaNode
@ -390,7 +390,7 @@ typedef struct LuaNode
TKey key;
} LuaNode;
/* copy a value into a key */
// copy a value into a key
#define setnodekey(L, node, obj) \
{ \
LuaNode* n_ = (node); \
@ -401,7 +401,7 @@ typedef struct LuaNode
checkliveness(L->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<<p means tagmethod(p) is not present */
uint8_t readonly; /* sandboxing feature to prohibit writes to table */
uint8_t safeenv; /* environment doesn't share globals with other scripts */
uint8_t lsizenode; /* log2 of size of `node' array */
uint8_t nodemask8; /* (1<<lsizenode)-1, truncated to 8 bits */
uint8_t tmcache; // 1<<p means tagmethod(p) is not present
uint8_t readonly; // sandboxing feature to prohibit writes to table
uint8_t safeenv; // environment doesn't share globals with other scripts
uint8_t lsizenode; // log2 of size of `node' array
uint8_t nodemask8; // (1<<lsizenode)-1, truncated to 8 bits
int sizearray; /* size of `array' array */
int sizearray; // size of `array' array
union
{
int lastfree; /* any free position is before this position */
int aboundary; /* negated 'boundary' of `array' array; iff aboundary < 0 */
int lastfree; // any free position is before this position
int aboundary; // negated 'boundary' of `array' array; iff aboundary < 0
};
struct Table* metatable;
TValue* array; /* array part */
TValue* array; // array part
LuaNode* node;
GCObject* gclist;
} Table;

View file

@ -46,8 +46,8 @@ static void setfield(lua_State* L, const char* key, int value)
static void setboolfield(lua_State* L, const char* key, int value)
{
if (value < 0) /* undefined? */
return; /* does not set field */
if (value < 0) // undefined?
return; // does not set field
lua_pushboolean(L, value);
lua_setfield(L, -2, key);
}
@ -85,9 +85,9 @@ static int os_date(lua_State* L)
struct tm tm;
struct tm* stm;
if (*s == '!')
{ /* UTC? */
{ // UTC?
stm = gmtime_r(&t, &tm);
s++; /* skip `!' */
s++; // skip `!'
}
else
{
@ -95,13 +95,13 @@ static int os_date(lua_State* L)
stm = t < 0 ? NULL : localtime_r(&t, &tm);
}
if (stm == NULL) /* invalid date? */
if (stm == NULL) // invalid date?
{
lua_pushnil(L);
}
else if (strcmp(s, "*t") == 0)
{
lua_createtable(L, 0, 9); /* 9 = number of fields */
lua_createtable(L, 0, 9); // 9 = number of fields
setfield(L, "sec", stm->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);

View file

@ -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);
}

View file

@ -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);

View file

@ -7,8 +7,6 @@
#include <string.h>
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)

View file

@ -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)

View file

@ -8,7 +8,9 @@
#include <string.h>
#include <stdio.h>
/* macro to `unsign' a character */
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
// macro to `unsign' a character
#define uchar(c) ((unsigned char)(c))
static int str_len(lua_State* L)
@ -21,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;
@ -137,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++)
@ -149,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);
@ -176,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;
@ -189,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 '%'
@ -227,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;
}
@ -279,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);
}
@ -292,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)
{
@ -324,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 '[':
@ -357,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;
}
@ -384,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;
}
@ -399,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;
}
@ -408,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;
}
@ -429,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':
@ -495,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;
}
@ -511,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;
@ -566,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
}
}
@ -593,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");
}
@ -617,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)
@ -657,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)
{
@ -681,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
@ -692,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
@ -701,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;
}
@ -731,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)
@ -763,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)
@ -775,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
}
}
}
@ -801,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)
@ -832,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)
@ -845,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
@ -856,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)
@ -914,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)");
@ -965,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;
@ -979,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':
@ -997,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':
@ -1006,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':
{
@ -1024,16 +1026,30 @@ 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'");
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));
}
}
@ -1098,31 +1114,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
/*
@ -1133,7 +1149,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;
/*
@ -1151,15 +1167,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;
/*
@ -1173,8 +1189,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;
@ -1216,7 +1232,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':
@ -1308,19 +1324,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);
}
@ -1338,18 +1354,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
}
/*
@ -1375,11 +1391,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')
{
@ -1387,15 +1403,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");
}
@ -1403,64 +1419,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;
}
}
@ -1471,15 +1487,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;
}
@ -1506,15 +1522,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++)
{
@ -1534,7 +1550,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')
@ -1542,8 +1558,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)
@ -1584,7 +1600,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:
@ -1592,22 +1608,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},
@ -1632,14 +1648,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
}
/*

View file

@ -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);
}

View file

@ -11,12 +11,16 @@
#define gval2slot(t, v) int(cast_to(LuaNode*, static_cast<const TValue*>(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;

View file

@ -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]<a[l]? */
if (sort_comp(L, -2, -1)) // a[i]<a[l]?
set2(L, i, l);
else
{
lua_pop(L, 1); /* remove a[l] */
lua_pop(L, 1); // remove a[l]
lua_rawgeti(L, 1, u);
if (sort_comp(L, -1, -2)) /* a[u]<a[i]? */
if (sort_comp(L, -1, -2)) // a[u]<a[i]?
set2(L, i, u);
else
lua_pop(L, 2);
}
if (u - l == 2)
break; /* only 3 elements */
lua_rawgeti(L, 1, i); /* Pivot */
break; // only 3 elements
lua_rawgeti(L, 1, i); // Pivot
lua_pushvalue(L, -1);
lua_rawgeti(L, 1, u - 1);
set2(L, i, u - 1);
/* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */
// a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2
i = l;
j = u - 1;
for (;;)
{ /* invariant: a[l..i] <= P <= a[j..u] */
/* repeat ++i until 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)
{

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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[] = {

View file

@ -16,7 +16,7 @@
#include <string.h>
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<void*>(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<void*>(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<void*>(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;

View file

@ -12,9 +12,9 @@
#include <string.h>
#include <stdio.h>
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
}

View file

@ -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

View file

@ -0,0 +1,129 @@
# Disallow `name T` and `name(T)` in future syntactic extensions for type annotations
## Summary
We propose to disallow the syntax ``<name> `('`` as well as `<name> <type>` 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 ``<name> `<' <type> `>'``.
## 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 ``<name> `('``. 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) -> 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<T>` 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<T> = ...` (where `...` is to be read as "we haven't decided")

View file

@ -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

View file

@ -1,5 +1,7 @@
# never and unknown types
**Status**: Implemented
## Summary
Add `unknown` and `never` types that are inhabited by everything and nothing respectively.

Some files were not shown because too many files have changed in this diff Show more