mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/543 (#657)
- Improve ComparisonPrecedence lint suggestions for three-way comparisons (X < Y < Z) - Improve type checking stability - Improve location information for errors when parsing invalid type annotations - Compiler now generates bytecode version 3 in all configurations - Improve performance of comparisons against numeric constants on AArch64
This commit is contained in:
parent
42c24f98d9
commit
ae35ada579
@ -19,6 +19,7 @@ using ScopePtr = std::shared_ptr<Scope>;
|
|||||||
// A substitution which replaces free types by any
|
// A substitution which replaces free types by any
|
||||||
struct Anyification : Substitution
|
struct Anyification : Substitution
|
||||||
{
|
{
|
||||||
|
Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
|
||||||
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
|
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
InternalErrorReporter* iceHandler;
|
InternalErrorReporter* iceHandler;
|
||||||
@ -35,4 +36,4 @@ struct Anyification : Substitution
|
|||||||
bool ignoreChildren(TypePackId ty) override;
|
bool ignoreChildren(TypePackId ty) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
@ -34,6 +34,7 @@ TypeId makeFunction( // Polymorphic
|
|||||||
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
|
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
|
||||||
|
|
||||||
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
||||||
|
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
|
||||||
|
|
||||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);
|
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);
|
||||||
|
@ -56,6 +56,10 @@ struct UnaryConstraint
|
|||||||
TypeId resultType;
|
TypeId resultType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// let L : leftType
|
||||||
|
// let R : rightType
|
||||||
|
// in
|
||||||
|
// L op R : resultType
|
||||||
struct BinaryConstraint
|
struct BinaryConstraint
|
||||||
{
|
{
|
||||||
AstExprBinary::Op op;
|
AstExprBinary::Op op;
|
||||||
@ -64,6 +68,14 @@ struct BinaryConstraint
|
|||||||
TypeId resultType;
|
TypeId resultType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// iteratee is iterable
|
||||||
|
// iterators is the iteration types.
|
||||||
|
struct IterableConstraint
|
||||||
|
{
|
||||||
|
TypePackId iterator;
|
||||||
|
TypePackId variables;
|
||||||
|
};
|
||||||
|
|
||||||
// name(namedType) = name
|
// name(namedType) = name
|
||||||
struct NameConstraint
|
struct NameConstraint
|
||||||
{
|
{
|
||||||
@ -78,20 +90,31 @@ struct TypeAliasExpansionConstraint
|
|||||||
TypeId target;
|
TypeId target;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
|
||||||
BinaryConstraint, NameConstraint, TypeAliasExpansionConstraint>;
|
|
||||||
using ConstraintPtr = std::unique_ptr<struct Constraint>;
|
using ConstraintPtr = std::unique_ptr<struct Constraint>;
|
||||||
|
|
||||||
|
struct FunctionCallConstraint
|
||||||
|
{
|
||||||
|
std::vector<NotNull<const Constraint>> innerConstraints;
|
||||||
|
TypeId fn;
|
||||||
|
TypePackId result;
|
||||||
|
class AstExprCall* astFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
||||||
|
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint>;
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
Constraint(ConstraintV&& c, NotNull<Scope> scope);
|
Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c);
|
||||||
|
|
||||||
Constraint(const Constraint&) = delete;
|
Constraint(const Constraint&) = delete;
|
||||||
Constraint& operator=(const Constraint&) = delete;
|
Constraint& operator=(const Constraint&) = delete;
|
||||||
|
|
||||||
ConstraintV c;
|
|
||||||
std::vector<NotNull<Constraint>> dependencies;
|
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
Location location;
|
||||||
|
ConstraintV c;
|
||||||
|
|
||||||
|
std::vector<NotNull<Constraint>> dependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline Constraint& asMutable(const Constraint& c)
|
inline Constraint& asMutable(const Constraint& c)
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Symbol.h"
|
#include "Luau/Symbol.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
@ -51,12 +52,15 @@ struct ConstraintGraphBuilder
|
|||||||
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
|
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
|
||||||
std::vector<TypeError> errors;
|
std::vector<TypeError> errors;
|
||||||
|
|
||||||
|
// Needed to resolve modules to make 'require' import types properly.
|
||||||
|
NotNull<ModuleResolver> moduleResolver;
|
||||||
// Occasionally constraint generation needs to produce an ICE.
|
// Occasionally constraint generation needs to produce an ICE.
|
||||||
const NotNull<InternalErrorReporter> ice;
|
const NotNull<InternalErrorReporter> ice;
|
||||||
|
|
||||||
ScopePtr globalScope;
|
ScopePtr globalScope;
|
||||||
|
|
||||||
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope);
|
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
|
||||||
|
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type belonging to a given scope.
|
* Fabricates a new free type belonging to a given scope.
|
||||||
@ -82,7 +86,7 @@ struct ConstraintGraphBuilder
|
|||||||
* @param scope the scope to add the constraint to.
|
* @param scope the scope to add the constraint to.
|
||||||
* @param cv the constraint variant to add.
|
* @param cv the constraint variant to add.
|
||||||
*/
|
*/
|
||||||
void addConstraint(const ScopePtr& scope, ConstraintV cv);
|
void addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a constraint to a given scope.
|
* Adds a constraint to a given scope.
|
||||||
@ -104,6 +108,7 @@ struct ConstraintGraphBuilder
|
|||||||
void visit(const ScopePtr& scope, AstStatBlock* block);
|
void visit(const ScopePtr& scope, AstStatBlock* block);
|
||||||
void visit(const ScopePtr& scope, AstStatLocal* local);
|
void visit(const ScopePtr& scope, AstStatLocal* local);
|
||||||
void visit(const ScopePtr& scope, AstStatFor* for_);
|
void visit(const ScopePtr& scope, AstStatFor* for_);
|
||||||
|
void visit(const ScopePtr& scope, AstStatForIn* forIn);
|
||||||
void visit(const ScopePtr& scope, AstStatWhile* while_);
|
void visit(const ScopePtr& scope, AstStatWhile* while_);
|
||||||
void visit(const ScopePtr& scope, AstStatRepeat* repeat);
|
void visit(const ScopePtr& scope, AstStatRepeat* repeat);
|
||||||
void visit(const ScopePtr& scope, AstStatLocalFunction* function);
|
void visit(const ScopePtr& scope, AstStatLocalFunction* function);
|
||||||
@ -117,8 +122,6 @@ struct ConstraintGraphBuilder
|
|||||||
void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
|
void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
|
||||||
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
||||||
|
|
||||||
TypePackId checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs);
|
|
||||||
|
|
||||||
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||||
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr);
|
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr);
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ namespace Luau
|
|||||||
// never dereference this pointer.
|
// never dereference this pointer.
|
||||||
using BlockedConstraintId = const void*;
|
using BlockedConstraintId = const void*;
|
||||||
|
|
||||||
|
struct ModuleResolver;
|
||||||
|
|
||||||
struct InstantiationSignature
|
struct InstantiationSignature
|
||||||
{
|
{
|
||||||
TypeFun fn;
|
TypeFun fn;
|
||||||
@ -42,6 +44,7 @@ struct ConstraintSolver
|
|||||||
// The entire set of constraints that the solver is trying to resolve.
|
// The entire set of constraints that the solver is trying to resolve.
|
||||||
std::vector<NotNull<Constraint>> constraints;
|
std::vector<NotNull<Constraint>> constraints;
|
||||||
NotNull<Scope> rootScope;
|
NotNull<Scope> rootScope;
|
||||||
|
ModuleName currentModuleName;
|
||||||
|
|
||||||
// Constraints that the solver has generated, rather than sourcing from the
|
// Constraints that the solver has generated, rather than sourcing from the
|
||||||
// scope tree.
|
// scope tree.
|
||||||
@ -63,9 +66,13 @@ struct ConstraintSolver
|
|||||||
// Recorded errors that take place within the solver.
|
// Recorded errors that take place within the solver.
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
|
|
||||||
|
NotNull<ModuleResolver> moduleResolver;
|
||||||
|
std::vector<RequireCycle> requireCycles;
|
||||||
|
|
||||||
ConstraintSolverLogger logger;
|
ConstraintSolverLogger logger;
|
||||||
|
|
||||||
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope);
|
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
|
||||||
|
std::vector<RequireCycle> requireCycles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to dispatch all pending constraints and reach a type solution
|
* Attempts to dispatch all pending constraints and reach a type solution
|
||||||
@ -86,8 +93,17 @@ struct ConstraintSolver
|
|||||||
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
|
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 BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
|
// for a, ... in some_table do
|
||||||
|
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
|
// for a, ... in next_function, t, ... do
|
||||||
|
bool tryDispatchIterableFunction(
|
||||||
|
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||||
/**
|
/**
|
||||||
@ -108,6 +124,11 @@ struct ConstraintSolver
|
|||||||
*/
|
*/
|
||||||
bool isBlocked(TypeId ty);
|
bool isBlocked(TypeId ty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the TypePackId is in a blocked state.
|
||||||
|
*/
|
||||||
|
bool isBlocked(TypePackId tp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the constraint is blocked on anything.
|
* Returns whether the constraint is blocked on anything.
|
||||||
* @param constraint the constraint to check.
|
* @param constraint the constraint to check.
|
||||||
@ -133,10 +154,22 @@ struct ConstraintSolver
|
|||||||
/** Pushes a new solver constraint to the solver.
|
/** Pushes a new solver constraint to the solver.
|
||||||
* @param cv the body of the constraint.
|
* @param cv the body of the constraint.
|
||||||
**/
|
**/
|
||||||
void pushConstraint(ConstraintV cv, NotNull<Scope> scope);
|
void pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to resolve a module from its module information. Returns the
|
||||||
|
* module-level return type of the module, or the error type if one cannot
|
||||||
|
* be found. Reports errors to the solver if the module cannot be found or
|
||||||
|
* the require is illegal.
|
||||||
|
* @param module the module information to look up.
|
||||||
|
* @param location the location where the require is taking place; used for
|
||||||
|
* error locations.
|
||||||
|
**/
|
||||||
|
TypeId resolveModule(const ModuleInfo& module, const Location& location);
|
||||||
|
|
||||||
void reportError(TypeErrorData&& data, const Location& location);
|
void reportError(TypeErrorData&& data, const Location& location);
|
||||||
void reportError(TypeError e);
|
void reportError(TypeError e);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Marks a constraint as being blocked on a type or type pack. The constraint
|
* Marks a constraint as being blocked on a type or type pack. The constraint
|
||||||
@ -154,6 +187,8 @@ private:
|
|||||||
* @param progressed the type or type pack pointer that has progressed.
|
* @param progressed the type or type pack pointer that has progressed.
|
||||||
**/
|
**/
|
||||||
void unblock_(BlockedConstraintId progressed);
|
void unblock_(BlockedConstraintId progressed);
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
};
|
};
|
||||||
|
|
||||||
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
|
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
|
||||||
|
@ -16,7 +16,8 @@ struct ConstraintSolverLogger
|
|||||||
{
|
{
|
||||||
std::string compileOutput();
|
std::string compileOutput();
|
||||||
void captureBoundarySnapshot(const Scope* rootScope, 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, bool force);
|
void prepareStepSnapshot(
|
||||||
|
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force);
|
||||||
void commitPreparedStepSnapshot();
|
void commitPreparedStepSnapshot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -157,7 +157,7 @@ struct Frontend
|
|||||||
ScopePtr getGlobalScope();
|
ScopePtr getGlobalScope();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
|
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles);
|
||||||
|
|
||||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
|
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
|
||||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||||
|
@ -40,6 +40,9 @@ struct Scope
|
|||||||
std::optional<TypePackId> varargPack;
|
std::optional<TypePackId> varargPack;
|
||||||
// All constraints belonging to this scope.
|
// All constraints belonging to this scope.
|
||||||
std::vector<ConstraintPtr> constraints;
|
std::vector<ConstraintPtr> constraints;
|
||||||
|
// Constraints belonging to this scope that are queued manually by other
|
||||||
|
// constraints.
|
||||||
|
std::vector<ConstraintPtr> unqueuedConstraints;
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
|
||||||
|
@ -92,7 +92,6 @@ inline std::string toString(const Constraint& c)
|
|||||||
return toString(c, ToStringOptions{});
|
return toString(c, ToStringOptions{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string toString(const TypeVar& tv, ToStringOptions& opts);
|
std::string toString(const TypeVar& tv, ToStringOptions& opts);
|
||||||
std::string toString(const TypePackVar& tp, ToStringOptions& opts);
|
std::string toString(const TypePackVar& tp, ToStringOptions& opts);
|
||||||
|
|
||||||
|
@ -29,9 +29,10 @@ struct TypeArena
|
|||||||
TypeId addTV(TypeVar&& tv);
|
TypeId addTV(TypeVar&& tv);
|
||||||
|
|
||||||
TypeId freshType(TypeLevel level);
|
TypeId freshType(TypeLevel level);
|
||||||
|
TypeId freshType(Scope* scope);
|
||||||
|
|
||||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||||
TypePackId addTypePack(std::vector<TypeId> types);
|
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
|
||||||
TypePackId addTypePack(TypePack pack);
|
TypePackId addTypePack(TypePack pack);
|
||||||
TypePackId addTypePack(TypePackVar pack);
|
TypePackId addTypePack(TypePackVar pack);
|
||||||
|
|
||||||
|
@ -166,7 +166,8 @@ struct TypeChecker
|
|||||||
*/
|
*/
|
||||||
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
||||||
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options);
|
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options);
|
||||||
bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location,
|
||||||
|
CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||||
|
|
||||||
/** Attempt to unify the types.
|
/** Attempt to unify the types.
|
||||||
* If this fails, and the subTy type can be instantiated, do so and try unification again.
|
* If this fails, and the subTy type can be instantiated, do so and try unification again.
|
||||||
|
@ -15,6 +15,7 @@ struct TypeArena;
|
|||||||
|
|
||||||
struct TypePack;
|
struct TypePack;
|
||||||
struct VariadicTypePack;
|
struct VariadicTypePack;
|
||||||
|
struct BlockedTypePack;
|
||||||
|
|
||||||
struct TypePackVar;
|
struct TypePackVar;
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ using TypePackId = const TypePackVar*;
|
|||||||
using FreeTypePack = Unifiable::Free;
|
using FreeTypePack = Unifiable::Free;
|
||||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||||
using GenericTypePack = Unifiable::Generic;
|
using GenericTypePack = Unifiable::Generic;
|
||||||
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack>;
|
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack, BlockedTypePack>;
|
||||||
|
|
||||||
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
||||||
* notions like packs of unknown length and packs of any length, as well as more
|
* notions like packs of unknown length and packs of any length, as well as more
|
||||||
@ -43,6 +44,17 @@ struct VariadicTypePack
|
|||||||
bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail.
|
bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analogous to a BlockedTypeVar.
|
||||||
|
*/
|
||||||
|
struct BlockedTypePack
|
||||||
|
{
|
||||||
|
BlockedTypePack();
|
||||||
|
size_t index;
|
||||||
|
|
||||||
|
static size_t nextIndex;
|
||||||
|
};
|
||||||
|
|
||||||
struct TypePackVar
|
struct TypePackVar
|
||||||
{
|
{
|
||||||
explicit TypePackVar(const TypePackVariant& ty);
|
explicit TypePackVar(const TypePackVariant& ty);
|
||||||
|
@ -11,12 +11,16 @@
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct TxnLog;
|
||||||
|
|
||||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||||
|
|
||||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
||||||
std::optional<TypeId> getIndexTypeFromType(
|
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
|
||||||
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors,
|
const Location& location, bool addErrors, InternalErrorReporter& handle);
|
||||||
InternalErrorReporter& handle);
|
|
||||||
|
// Returns the minimum and maximum number of types the argument list can accept.
|
||||||
|
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Predicate.h"
|
#include "Luau/Predicate.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -262,6 +264,8 @@ struct WithPredicate
|
|||||||
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
|
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
|
||||||
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
|
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
|
||||||
|
|
||||||
|
using DcrMagicFunction = std::function<bool(NotNull<struct ConstraintSolver>, TypePackId, const class AstExprCall*)>;
|
||||||
|
|
||||||
struct FunctionTypeVar
|
struct FunctionTypeVar
|
||||||
{
|
{
|
||||||
// Global monomorphic function
|
// Global monomorphic function
|
||||||
@ -287,7 +291,8 @@ struct FunctionTypeVar
|
|||||||
std::vector<std::optional<FunctionArgument>> argNames;
|
std::vector<std::optional<FunctionArgument>> argNames;
|
||||||
TypePackId retTypes;
|
TypePackId retTypes;
|
||||||
std::optional<FunctionDefinition> definition;
|
std::optional<FunctionDefinition> definition;
|
||||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||||
|
DcrMagicFunction dcrMagicFunction = nullptr; // can be nullptr
|
||||||
bool hasSelf;
|
bool hasSelf;
|
||||||
Tags tags;
|
Tags tags;
|
||||||
bool hasNoGenerics = false;
|
bool hasNoGenerics = false;
|
||||||
@ -462,8 +467,9 @@ struct TypeFun
|
|||||||
*/
|
*/
|
||||||
struct PendingExpansionTypeVar
|
struct PendingExpansionTypeVar
|
||||||
{
|
{
|
||||||
PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments);
|
PendingExpansionTypeVar(std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments);
|
||||||
TypeFun fn;
|
std::optional<AstName> prefix;
|
||||||
|
AstName name;
|
||||||
std::vector<TypeId> typeArguments;
|
std::vector<TypeId> typeArguments;
|
||||||
std::vector<TypePackId> packArguments;
|
std::vector<TypePackId> packArguments;
|
||||||
size_t index;
|
size_t index;
|
||||||
|
@ -59,7 +59,8 @@ struct Unifier
|
|||||||
|
|
||||||
UnifierSharedState& sharedState;
|
UnifierSharedState& sharedState;
|
||||||
|
|
||||||
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||||
|
TxnLog* parentLog = nullptr);
|
||||||
|
|
||||||
// Test whether the two type vars unify. Never commits the result.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||||
|
@ -188,6 +188,10 @@ struct GenericTypeVarVisitor
|
|||||||
{
|
{
|
||||||
return visit(tp);
|
return visit(tp);
|
||||||
}
|
}
|
||||||
|
virtual bool visit(TypePackId tp, const BlockedTypePack& btp)
|
||||||
|
{
|
||||||
|
return visit(tp);
|
||||||
|
}
|
||||||
|
|
||||||
void traverse(TypeId ty)
|
void traverse(TypeId ty)
|
||||||
{
|
{
|
||||||
@ -314,24 +318,6 @@ struct GenericTypeVarVisitor
|
|||||||
{
|
{
|
||||||
if (visit(ty, *petv))
|
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)
|
for (TypeId a : petv->typeArguments)
|
||||||
traverse(a);
|
traverse(a);
|
||||||
|
|
||||||
@ -388,6 +374,9 @@ struct GenericTypeVarVisitor
|
|||||||
if (res)
|
if (res)
|
||||||
traverse(pack->ty);
|
traverse(pack->ty);
|
||||||
}
|
}
|
||||||
|
else if (auto btp = get<BlockedTypePack>(tp))
|
||||||
|
visit(tp, *btp);
|
||||||
|
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");
|
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");
|
||||||
|
|
||||||
|
@ -11,15 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
|
Anyification::Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
|
||||||
: Substitution(TxnLog::empty(), arena)
|
: Substitution(TxnLog::empty(), arena)
|
||||||
, scope(NotNull{scope.get()})
|
, scope(scope)
|
||||||
, iceHandler(iceHandler)
|
, iceHandler(iceHandler)
|
||||||
, anyType(anyType)
|
, anyType(anyType)
|
||||||
, anyTypePack(anyTypePack)
|
, anyTypePack(anyTypePack)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
|
||||||
|
: Anyification(arena, NotNull{scope.get()}, iceHandler, anyType, anyTypePack)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool Anyification::isDirty(TypeId ty)
|
bool Anyification::isDirty(TypeId ty)
|
||||||
{
|
{
|
||||||
if (ty->persistent)
|
if (ty->persistent)
|
||||||
@ -93,4 +98,4 @@ bool Anyification::ignoreChildren(TypePackId ty)
|
|||||||
return ty->persistent;
|
return ty->persistent;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -1215,8 +1215,8 @@ static bool autocompleteIfElseExpression(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena,
|
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
|
||||||
const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
|
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!ancestry.empty());
|
LUAU_ASSERT(!ancestry.empty());
|
||||||
|
|
||||||
@ -1422,8 +1422,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
||||||
|
|
||||||
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
|
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
|
||||||
return {
|
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry,
|
||||||
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
AutocompleteContext::Property};
|
||||||
else
|
else
|
||||||
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||||
}
|
}
|
||||||
@ -1522,8 +1522,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
|
|
||||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
||||||
{
|
{
|
||||||
return {
|
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||||
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
ancestry, AutocompleteContext::Keyword};
|
||||||
}
|
}
|
||||||
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "Luau/Symbol.h"
|
#include "Luau/Symbol.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/ConstraintSolver.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -32,6 +33,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
|||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
|
|
||||||
|
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr);
|
||||||
|
|
||||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||||
{
|
{
|
||||||
return arena.addType(UnionTypeVar{std::move(types)});
|
return arena.addType(UnionTypeVar{std::move(types)});
|
||||||
@ -105,6 +108,14 @@ void attachMagicFunction(TypeId ty, MagicFunction fn)
|
|||||||
LUAU_ASSERT(!"Got a non functional type");
|
LUAU_ASSERT(!"Got a non functional type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
|
||||||
|
{
|
||||||
|
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||||
|
ftv->dcrMagicFunction = fn;
|
||||||
|
else
|
||||||
|
LUAU_ASSERT(!"Got a non functional type");
|
||||||
|
}
|
||||||
|
|
||||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
@ -263,6 +274,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||||
|
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||||
@ -509,4 +521,49 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
|
||||||
|
{
|
||||||
|
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
|
||||||
|
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
|
||||||
|
// we'll warn here if we see it.
|
||||||
|
bool good = true;
|
||||||
|
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
|
||||||
|
|
||||||
|
while (indexExpr)
|
||||||
|
{
|
||||||
|
if (indexExpr->index == "parent")
|
||||||
|
{
|
||||||
|
solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation);
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexExpr = indexExpr->expr->as<AstExprIndexName>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr)
|
||||||
|
{
|
||||||
|
if (expr->args.size != 1)
|
||||||
|
{
|
||||||
|
solver->reportError(GenericError{"require takes 1 argument"}, expr->location);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkRequirePathDcr(solver, expr->args.data[0]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (auto moduleInfo = solver->moduleResolver->resolveModuleInfo(solver->currentModuleName, *expr))
|
||||||
|
{
|
||||||
|
TypeId moduleType = solver->resolveModule(*moduleInfo, expr->location);
|
||||||
|
TypePackId moduleResult = solver->arena->addTypePack({moduleType});
|
||||||
|
asMutable(result)->ty.emplace<BoundTypePack>(moduleResult);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -102,6 +102,11 @@ struct TypePackCloner
|
|||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(const BlockedTypePack& t)
|
||||||
|
{
|
||||||
|
defaultClone(t);
|
||||||
|
}
|
||||||
|
|
||||||
// While we are a-cloning, we can flatten out bound TypeVars and make things a bit tighter.
|
// While we are a-cloning, we can flatten out bound TypeVars and make things a bit tighter.
|
||||||
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
|
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
|
||||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||||
@ -170,7 +175,7 @@ void TypeCloner::operator()(const BlockedTypeVar& t)
|
|||||||
|
|
||||||
void TypeCloner::operator()(const PendingExpansionTypeVar& t)
|
void TypeCloner::operator()(const PendingExpansionTypeVar& t)
|
||||||
{
|
{
|
||||||
TypeId res = dest.addType(PendingExpansionTypeVar{t.fn, t.typeArguments, t.packArguments});
|
TypeId res = dest.addType(PendingExpansionTypeVar{t.prefix, t.name, t.typeArguments, t.packArguments});
|
||||||
PendingExpansionTypeVar* petv = getMutable<PendingExpansionTypeVar>(res);
|
PendingExpansionTypeVar* petv = getMutable<PendingExpansionTypeVar>(res);
|
||||||
LUAU_ASSERT(petv);
|
LUAU_ASSERT(petv);
|
||||||
|
|
||||||
@ -184,32 +189,6 @@ void TypeCloner::operator()(const PendingExpansionTypeVar& t)
|
|||||||
for (TypePackId arg : t.packArguments)
|
for (TypePackId arg : t.packArguments)
|
||||||
packArguments.push_back(clone(arg, dest, cloneState));
|
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->typeArguments = std::move(typeArguments);
|
||||||
petv->packArguments = std::move(packArguments);
|
petv->packArguments = std::move(packArguments);
|
||||||
}
|
}
|
||||||
@ -461,6 +440,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
|
|||||||
clone.generics = ftv->generics;
|
clone.generics = ftv->generics;
|
||||||
clone.genericPacks = ftv->genericPacks;
|
clone.genericPacks = ftv->genericPacks;
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
|
clone.dcrMagicFunction = ftv->dcrMagicFunction;
|
||||||
clone.tags = ftv->tags;
|
clone.tags = ftv->tags;
|
||||||
clone.argNames = ftv->argNames;
|
clone.argNames = ftv->argNames;
|
||||||
result = dest.addType(std::move(clone));
|
result = dest.addType(std::move(clone));
|
||||||
@ -502,7 +482,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
|
|||||||
}
|
}
|
||||||
else if (const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(ty))
|
else if (const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
PendingExpansionTypeVar clone{petv->fn, petv->typeArguments, petv->packArguments};
|
PendingExpansionTypeVar clone{petv->prefix, petv->name, petv->typeArguments, petv->packArguments};
|
||||||
result = dest.addType(std::move(clone));
|
result = dest.addType(std::move(clone));
|
||||||
}
|
}
|
||||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone)
|
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone)
|
||||||
|
@ -5,9 +5,10 @@
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
Constraint::Constraint(ConstraintV&& c, NotNull<Scope> scope)
|
Constraint::Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c)
|
||||||
: c(std::move(c))
|
: scope(scope)
|
||||||
, scope(scope)
|
, location(location)
|
||||||
|
, c(std::move(c))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
|
||||||
@ -16,13 +17,31 @@ namespace Luau
|
|||||||
|
|
||||||
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
|
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
|
||||||
|
|
||||||
ConstraintGraphBuilder::ConstraintGraphBuilder(
|
static std::optional<AstExpr*> matchRequire(const AstExprCall& call)
|
||||||
const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope)
|
{
|
||||||
|
const char* require = "require";
|
||||||
|
|
||||||
|
if (call.args.size != 1)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
|
||||||
|
if (!funcAsGlobal || funcAsGlobal->name != require)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (call.args.size != 1)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return call.args.data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
|
||||||
|
NotNull<ModuleResolver> moduleResolver, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope)
|
||||||
: moduleName(moduleName)
|
: moduleName(moduleName)
|
||||||
, module(module)
|
, module(module)
|
||||||
, singletonTypes(getSingletonTypes())
|
, singletonTypes(getSingletonTypes())
|
||||||
, arena(arena)
|
, arena(arena)
|
||||||
, rootScope(nullptr)
|
, rootScope(nullptr)
|
||||||
|
, moduleResolver(moduleResolver)
|
||||||
, ice(ice)
|
, ice(ice)
|
||||||
, globalScope(globalScope)
|
, globalScope(globalScope)
|
||||||
{
|
{
|
||||||
@ -54,9 +73,9 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv)
|
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv)
|
||||||
{
|
{
|
||||||
scope->constraints.emplace_back(new Constraint{std::move(cv), NotNull{scope.get()}});
|
scope->constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
|
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
|
||||||
@ -77,13 +96,6 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block)
|
|||||||
|
|
||||||
prepopulateGlobalScope(scope, block);
|
prepopulateGlobalScope(scope, block);
|
||||||
|
|
||||||
// TODO: We should share the global scope.
|
|
||||||
rootScope->privateTypeBindings["nil"] = TypeFun{singletonTypes.nilType};
|
|
||||||
rootScope->privateTypeBindings["number"] = TypeFun{singletonTypes.numberType};
|
|
||||||
rootScope->privateTypeBindings["string"] = TypeFun{singletonTypes.stringType};
|
|
||||||
rootScope->privateTypeBindings["boolean"] = TypeFun{singletonTypes.booleanType};
|
|
||||||
rootScope->privateTypeBindings["thread"] = TypeFun{singletonTypes.threadType};
|
|
||||||
|
|
||||||
visitBlockWithoutChildScope(scope, block);
|
visitBlockWithoutChildScope(scope, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +170,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
|||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
else if (auto s = stat->as<AstStatFor>())
|
else if (auto s = stat->as<AstStatFor>())
|
||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
|
else if (auto s = stat->as<AstStatForIn>())
|
||||||
|
visit(scope, s);
|
||||||
else if (auto s = stat->as<AstStatWhile>())
|
else if (auto s = stat->as<AstStatWhile>())
|
||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
else if (auto s = stat->as<AstStatRepeat>())
|
else if (auto s = stat->as<AstStatRepeat>())
|
||||||
@ -201,7 +215,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
|||||||
{
|
{
|
||||||
location = local->annotation->location;
|
location = local->annotation->location;
|
||||||
TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true);
|
TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true);
|
||||||
addConstraint(scope, SubtypeConstraint{ty, annotation});
|
addConstraint(scope, location, SubtypeConstraint{ty, annotation});
|
||||||
}
|
}
|
||||||
|
|
||||||
varTypes.push_back(ty);
|
varTypes.push_back(ty);
|
||||||
@ -225,14 +239,38 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
|||||||
{
|
{
|
||||||
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
|
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
|
||||||
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
|
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
|
||||||
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack});
|
addConstraint(scope, local->location, PackSubtypeConstraint{exprPack, tailPack});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId exprType = check(scope, value);
|
TypeId exprType = check(scope, value);
|
||||||
if (i < varTypes.size())
|
if (i < varTypes.size())
|
||||||
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
|
addConstraint(scope, local->location, SubtypeConstraint{varTypes[i], exprType});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local->values.size > 0)
|
||||||
|
{
|
||||||
|
// To correctly handle 'require', we need to import the exported type bindings into the variable 'namespace'.
|
||||||
|
for (size_t i = 0; i < local->values.size && i < local->vars.size; ++i)
|
||||||
|
{
|
||||||
|
const AstExprCall* call = local->values.data[i]->as<AstExprCall>();
|
||||||
|
if (!call)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (auto maybeRequire = matchRequire(*call))
|
||||||
|
{
|
||||||
|
AstExpr* require = *maybeRequire;
|
||||||
|
|
||||||
|
if (auto moduleInfo = moduleResolver->resolveModuleInfo(moduleName, *require))
|
||||||
|
{
|
||||||
|
const Name name{local->vars.data[i]->name.value};
|
||||||
|
|
||||||
|
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
|
||||||
|
scope->importedTypeBindings[name] = module->getModuleScope()->exportedTypeBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,7 +282,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
TypeId t = check(scope, expr);
|
TypeId t = check(scope, expr);
|
||||||
addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType});
|
addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes.numberType});
|
||||||
};
|
};
|
||||||
|
|
||||||
checkNumber(for_->from);
|
checkNumber(for_->from);
|
||||||
@ -257,6 +295,29 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
|||||||
visit(forScope, for_->body);
|
visit(forScope, for_->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
||||||
|
{
|
||||||
|
ScopePtr loopScope = childScope(forIn, scope);
|
||||||
|
|
||||||
|
TypePackId iterator = checkPack(scope, forIn->values);
|
||||||
|
|
||||||
|
std::vector<TypeId> variableTypes;
|
||||||
|
variableTypes.reserve(forIn->vars.size);
|
||||||
|
for (AstLocal* var : forIn->vars)
|
||||||
|
{
|
||||||
|
TypeId ty = freshType(loopScope);
|
||||||
|
loopScope->bindings[var] = Binding{ty, var->location};
|
||||||
|
variableTypes.push_back(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is always ok to provide too few variables, so we give this pack a free tail.
|
||||||
|
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()}));
|
||||||
|
|
||||||
|
addConstraint(loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack});
|
||||||
|
|
||||||
|
visit(loopScope, forIn->body);
|
||||||
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_)
|
||||||
{
|
{
|
||||||
check(scope, while_->condition);
|
check(scope, while_->condition);
|
||||||
@ -284,6 +345,9 @@ void addConstraints(Constraint* constraint, NotNull<Scope> scope)
|
|||||||
for (const auto& c : scope->constraints)
|
for (const auto& c : scope->constraints)
|
||||||
constraint->dependencies.push_back(NotNull{c.get()});
|
constraint->dependencies.push_back(NotNull{c.get()});
|
||||||
|
|
||||||
|
for (const auto& c : scope->unqueuedConstraints)
|
||||||
|
constraint->dependencies.push_back(NotNull{c.get()});
|
||||||
|
|
||||||
for (NotNull<Scope> childScope : scope->children)
|
for (NotNull<Scope> childScope : scope->children)
|
||||||
addConstraints(constraint, childScope);
|
addConstraints(constraint, childScope);
|
||||||
}
|
}
|
||||||
@ -308,7 +372,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
|
|||||||
checkFunctionBody(sig.bodyScope, function->func);
|
checkFunctionBody(sig.bodyScope, function->func);
|
||||||
|
|
||||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||||
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope);
|
std::unique_ptr<Constraint> c =
|
||||||
|
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||||
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
||||||
|
|
||||||
addConstraint(scope, std::move(c));
|
addConstraint(scope, std::move(c));
|
||||||
@ -366,7 +431,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||||||
prop.type = functionType;
|
prop.type = functionType;
|
||||||
prop.location = function->name->location;
|
prop.location = function->name->location;
|
||||||
|
|
||||||
addConstraint(scope, SubtypeConstraint{containingTableType, prospectiveTableType});
|
addConstraint(scope, indexName->location, SubtypeConstraint{containingTableType, prospectiveTableType});
|
||||||
}
|
}
|
||||||
else if (AstExprError* err = function->name->as<AstExprError>())
|
else if (AstExprError* err = function->name->as<AstExprError>())
|
||||||
{
|
{
|
||||||
@ -378,7 +443,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||||||
checkFunctionBody(sig.bodyScope, function->func);
|
checkFunctionBody(sig.bodyScope, function->func);
|
||||||
|
|
||||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||||
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope);
|
std::unique_ptr<Constraint> c =
|
||||||
|
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||||
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
||||||
|
|
||||||
addConstraint(scope, std::move(c));
|
addConstraint(scope, std::move(c));
|
||||||
@ -387,7 +453,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
|
||||||
{
|
{
|
||||||
TypePackId exprTypes = checkPack(scope, ret->list);
|
TypePackId exprTypes = checkPack(scope, ret->list);
|
||||||
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
|
addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
||||||
@ -399,10 +465,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
|||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||||
{
|
{
|
||||||
TypePackId varPackId = checkExprList(scope, assign->vars);
|
TypePackId varPackId = checkPack(scope, assign->vars);
|
||||||
TypePackId valuePack = checkPack(scope, assign->values);
|
TypePackId valuePack = checkPack(scope, assign->values);
|
||||||
|
|
||||||
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
|
addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
|
||||||
@ -435,8 +501,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement
|
|||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
|
||||||
{
|
{
|
||||||
// TODO: Exported type aliases
|
|
||||||
|
|
||||||
auto bindingIt = scope->privateTypeBindings.find(alias->name.value);
|
auto bindingIt = scope->privateTypeBindings.find(alias->name.value);
|
||||||
ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias);
|
ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias);
|
||||||
// These will be undefined if the alias was a duplicate definition, in which
|
// These will be undefined if the alias was a duplicate definition, in which
|
||||||
@ -449,6 +513,12 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
|
|||||||
ScopePtr resolvingScope = *defnIt;
|
ScopePtr resolvingScope = *defnIt;
|
||||||
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true);
|
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true);
|
||||||
|
|
||||||
|
if (alias->exported)
|
||||||
|
{
|
||||||
|
Name typeName(alias->name.value);
|
||||||
|
scope->exportedTypeBindings[typeName] = TypeFun{ty};
|
||||||
|
}
|
||||||
|
|
||||||
LUAU_ASSERT(get<FreeTypeVar>(bindingIt->second.type));
|
LUAU_ASSERT(get<FreeTypeVar>(bindingIt->second.type));
|
||||||
|
|
||||||
// Rather than using a subtype constraint, we instead directly bind
|
// Rather than using a subtype constraint, we instead directly bind
|
||||||
@ -457,7 +527,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
|
|||||||
// bind the free alias type to an unrelated type, causing havoc.
|
// bind the free alias type to an unrelated type, causing havoc.
|
||||||
asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(ty);
|
asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(ty);
|
||||||
|
|
||||||
addConstraint(scope, NameConstraint{ty, alias->name.value});
|
addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
|
||||||
@ -615,44 +685,22 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
|
|||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs)
|
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs)
|
||||||
{
|
{
|
||||||
if (exprs.size == 0)
|
std::vector<TypeId> head;
|
||||||
return arena->addTypePack({});
|
std::optional<TypePackId> tail;
|
||||||
|
|
||||||
std::vector<TypeId> types;
|
|
||||||
TypePackId last = nullptr;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < exprs.size; ++i)
|
|
||||||
{
|
|
||||||
if (i < exprs.size - 1)
|
|
||||||
types.push_back(check(scope, exprs.data[i]));
|
|
||||||
else
|
|
||||||
last = checkPack(scope, exprs.data[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_ASSERT(last != nullptr);
|
|
||||||
|
|
||||||
return arena->addTypePack(TypePack{std::move(types), last});
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs)
|
|
||||||
{
|
|
||||||
TypePackId result = arena->addTypePack({});
|
|
||||||
TypePack* resultPack = getMutable<TypePack>(result);
|
|
||||||
LUAU_ASSERT(resultPack);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < exprs.size; ++i)
|
for (size_t i = 0; i < exprs.size; ++i)
|
||||||
{
|
{
|
||||||
AstExpr* expr = exprs.data[i];
|
AstExpr* expr = exprs.data[i];
|
||||||
if (i < exprs.size - 1)
|
if (i < exprs.size - 1)
|
||||||
resultPack->head.push_back(check(scope, expr));
|
head.push_back(check(scope, expr));
|
||||||
else
|
else
|
||||||
resultPack->tail = checkPack(scope, expr);
|
tail = checkPack(scope, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultPack->head.empty() && resultPack->tail)
|
if (head.empty() && tail)
|
||||||
return *resultPack->tail;
|
return *tail;
|
||||||
else
|
else
|
||||||
return result;
|
return arena->addTypePack(TypePack{std::move(head), tail});
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
|
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
|
||||||
@ -683,13 +731,26 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
|
|||||||
astOriginalCallTypes[call->func] = fnType;
|
astOriginalCallTypes[call->func] = fnType;
|
||||||
|
|
||||||
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
||||||
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
|
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||||
|
|
||||||
TypePackId rets = freshTypePack(scope);
|
|
||||||
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
||||||
TypeId inferredFnType = arena->addType(ftv);
|
TypeId inferredFnType = arena->addType(ftv);
|
||||||
|
|
||||||
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
|
scope->unqueuedConstraints.push_back(
|
||||||
|
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
|
||||||
|
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
|
||||||
|
|
||||||
|
scope->unqueuedConstraints.push_back(
|
||||||
|
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
|
||||||
|
NotNull<const Constraint> sc(scope->unqueuedConstraints.back().get());
|
||||||
|
|
||||||
|
addConstraint(scope, call->func->location,
|
||||||
|
FunctionCallConstraint{
|
||||||
|
{ic, sc},
|
||||||
|
fnType,
|
||||||
|
rets,
|
||||||
|
call,
|
||||||
|
});
|
||||||
|
|
||||||
result = rets;
|
result = rets;
|
||||||
}
|
}
|
||||||
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
|
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
|
||||||
@ -805,7 +866,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
|
|||||||
|
|
||||||
TypeId expectedTableType = arena->addType(std::move(ttv));
|
TypeId expectedTableType = arena->addType(std::move(ttv));
|
||||||
|
|
||||||
addConstraint(scope, SubtypeConstraint{obj, expectedTableType});
|
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -820,7 +881,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
|
|||||||
TableIndexer indexer{indexType, result};
|
TableIndexer indexer{indexType, result};
|
||||||
TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free});
|
TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free});
|
||||||
|
|
||||||
addConstraint(scope, SubtypeConstraint{obj, tableType});
|
addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -834,7 +895,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
|||||||
case AstExprUnary::Minus:
|
case AstExprUnary::Minus:
|
||||||
{
|
{
|
||||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||||
addConstraint(scope, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
|
addConstraint(scope, unary->location, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -853,19 +914,19 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
|
|||||||
{
|
{
|
||||||
case AstExprBinary::Or:
|
case AstExprBinary::Or:
|
||||||
{
|
{
|
||||||
addConstraint(scope, SubtypeConstraint{leftType, rightType});
|
addConstraint(scope, binary->location, SubtypeConstraint{leftType, rightType});
|
||||||
return leftType;
|
return leftType;
|
||||||
}
|
}
|
||||||
case AstExprBinary::Add:
|
case AstExprBinary::Add:
|
||||||
{
|
{
|
||||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||||
addConstraint(scope, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType});
|
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType});
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
case AstExprBinary::Sub:
|
case AstExprBinary::Sub:
|
||||||
{
|
{
|
||||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||||
addConstraint(scope, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType});
|
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType});
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -886,8 +947,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifEls
|
|||||||
if (ifElse->hasElse)
|
if (ifElse->hasElse)
|
||||||
{
|
{
|
||||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||||
addConstraint(scope, SubtypeConstraint{thenType, resultType});
|
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
|
||||||
addConstraint(scope, SubtypeConstraint{elseType, resultType});
|
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,7 +967,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
|
|||||||
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
||||||
LUAU_ASSERT(ttv);
|
LUAU_ASSERT(ttv);
|
||||||
|
|
||||||
auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) {
|
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
||||||
if (!ttv->indexer)
|
if (!ttv->indexer)
|
||||||
{
|
{
|
||||||
TypeId indexType = this->freshType(scope);
|
TypeId indexType = this->freshType(scope);
|
||||||
@ -914,8 +975,8 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
|
|||||||
ttv->indexer = TableIndexer{indexType, resultType};
|
ttv->indexer = TableIndexer{indexType, resultType};
|
||||||
}
|
}
|
||||||
|
|
||||||
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
|
addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
|
||||||
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
|
addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const AstExprTable::Item& item : expr->items)
|
for (const AstExprTable::Item& item : expr->items)
|
||||||
@ -937,13 +998,15 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
createIndexer(keyTy, itemTy);
|
createIndexer(item.key->location, keyTy, itemTy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId numberType = singletonTypes.numberType;
|
TypeId numberType = singletonTypes.numberType;
|
||||||
createIndexer(numberType, itemTy);
|
// FIXME? The location isn't quite right here. Not sure what is
|
||||||
|
// right.
|
||||||
|
createIndexer(item.value->location, numberType, itemTy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,7 +1071,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||||||
if (fn->returnAnnotation)
|
if (fn->returnAnnotation)
|
||||||
{
|
{
|
||||||
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
|
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
|
||||||
addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType});
|
addConstraint(signatureScope, getLocation(*fn->returnAnnotation), PackSubtypeConstraint{returnType, annotatedRetType});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TypeId> argTypes;
|
std::vector<TypeId> argTypes;
|
||||||
@ -1022,7 +1085,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||||||
if (local->annotation)
|
if (local->annotation)
|
||||||
{
|
{
|
||||||
TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true);
|
TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true);
|
||||||
addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation});
|
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, argAnnotation});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1056,7 +1119,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
|
|||||||
if (nullptr != getFallthrough(fn->body))
|
if (nullptr != getFallthrough(fn->body))
|
||||||
{
|
{
|
||||||
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
|
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
|
||||||
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty});
|
addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,16 +1129,13 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
|
|
||||||
if (auto ref = ty->as<AstTypeReference>())
|
if (auto ref = ty->as<AstTypeReference>())
|
||||||
{
|
{
|
||||||
// TODO: Support imported types w/ require tracing.
|
|
||||||
LUAU_ASSERT(!ref->prefix);
|
|
||||||
|
|
||||||
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
|
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
|
||||||
|
|
||||||
if (alias.has_value())
|
if (alias.has_value() || ref->prefix.has_value())
|
||||||
{
|
{
|
||||||
// If the alias is not generic, we don't need to set up a blocked
|
// If the alias is not generic, we don't need to set up a blocked
|
||||||
// type and an instantiation constraint.
|
// type and an instantiation constraint.
|
||||||
if (alias->typeParams.empty() && alias->typePackParams.empty())
|
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty())
|
||||||
{
|
{
|
||||||
result = alias->type;
|
result = alias->type;
|
||||||
}
|
}
|
||||||
@ -1104,11 +1164,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = arena->addType(PendingExpansionTypeVar{*alias, parameters, packParameters});
|
result = arena->addType(PendingExpansionTypeVar{ref->prefix, ref->name, parameters, packParameters});
|
||||||
|
|
||||||
if (topLevel)
|
if (topLevel)
|
||||||
{
|
{
|
||||||
addConstraint(scope, TypeAliasExpansionConstraint{ /* target */ result });
|
addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1141,8 +1201,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove TypeLevel{} here, we don't need it.
|
result = arena->addType(TableTypeVar{props, indexer, scope->level, TableState::Sealed});
|
||||||
result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed});
|
|
||||||
}
|
}
|
||||||
else if (auto fn = ty->as<AstTypeFunction>())
|
else if (auto fn = ty->as<AstTypeFunction>())
|
||||||
{
|
{
|
||||||
@ -1363,7 +1422,7 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat
|
|||||||
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
||||||
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
|
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
|
||||||
|
|
||||||
addConstraint(scope, PackSubtypeConstraint{tp, oneTypePack});
|
addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
|
||||||
|
|
||||||
return typeResult;
|
return typeResult;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/Anyification.h"
|
||||||
#include "Luau/ApplyTypeFunction.h"
|
#include "Luau/ApplyTypeFunction.h"
|
||||||
#include "Luau/ConstraintSolver.h"
|
#include "Luau/ConstraintSolver.h"
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/Quantify.h"
|
#include "Luau/Quantify.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
@ -240,11 +242,17 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope)
|
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
|
||||||
|
std::vector<RequireCycle> requireCycles)
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
, constraints(collectConstraints(rootScope))
|
, constraints(collectConstraints(rootScope))
|
||||||
, rootScope(rootScope)
|
, rootScope(rootScope)
|
||||||
|
, currentModuleName(std::move(moduleName))
|
||||||
|
, moduleResolver(moduleResolver)
|
||||||
|
, requireCycles(requireCycles)
|
||||||
{
|
{
|
||||||
|
opts.exhaustive = true;
|
||||||
|
|
||||||
for (NotNull<Constraint> c : constraints)
|
for (NotNull<Constraint> c : constraints)
|
||||||
{
|
{
|
||||||
unsolvedConstraints.push_back(c);
|
unsolvedConstraints.push_back(c);
|
||||||
@ -261,9 +269,6 @@ void ConstraintSolver::run()
|
|||||||
if (done())
|
if (done())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ToStringOptions opts;
|
|
||||||
opts.exhaustive = true;
|
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
{
|
{
|
||||||
printf("Starting solver\n");
|
printf("Starting solver\n");
|
||||||
@ -371,10 +376,14 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||||||
success = tryDispatch(*uc, constraint, force);
|
success = tryDispatch(*uc, constraint, force);
|
||||||
else if (auto bc = get<BinaryConstraint>(*constraint))
|
else if (auto bc = get<BinaryConstraint>(*constraint))
|
||||||
success = tryDispatch(*bc, constraint, force);
|
success = tryDispatch(*bc, constraint, force);
|
||||||
|
else if (auto ic = get<IterableConstraint>(*constraint))
|
||||||
|
success = tryDispatch(*ic, constraint, force);
|
||||||
else if (auto nc = get<NameConstraint>(*constraint))
|
else if (auto nc = get<NameConstraint>(*constraint))
|
||||||
success = tryDispatch(*nc, constraint);
|
success = tryDispatch(*nc, constraint);
|
||||||
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
|
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
|
||||||
success = tryDispatch(*taec, constraint);
|
success = tryDispatch(*taec, constraint);
|
||||||
|
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
|
||||||
|
success = tryDispatch(*fcc, constraint);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(0);
|
LUAU_ASSERT(0);
|
||||||
|
|
||||||
@ -400,6 +409,11 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
|
|||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
|
if (isBlocked(c.subPack))
|
||||||
|
return block(c.subPack, constraint);
|
||||||
|
else if (isBlocked(c.superPack))
|
||||||
|
return block(c.superPack, constraint);
|
||||||
|
|
||||||
unify(c.subPack, c.superPack, constraint->scope);
|
unify(c.subPack, c.superPack, constraint->scope);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -512,6 +526,82 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* for .. in loops can play out in a bunch of different ways depending on
|
||||||
|
* the shape of iteratee.
|
||||||
|
*
|
||||||
|
* iteratee might be:
|
||||||
|
* * (nextFn)
|
||||||
|
* * (nextFn, table)
|
||||||
|
* * (nextFn, table, firstIndex)
|
||||||
|
* * table with a metatable and __index
|
||||||
|
* * table with a metatable and __call but no __index (if the metatable has
|
||||||
|
* both, __index takes precedence)
|
||||||
|
* * table with an indexer but no __index or __call (or no metatable)
|
||||||
|
*
|
||||||
|
* To dispatch this constraint, we need first to know enough about iteratee
|
||||||
|
* to figure out which of the above shapes we are actually working with.
|
||||||
|
*
|
||||||
|
* If `force` is true and we still do not know, we must flag a warning. Type
|
||||||
|
* families are the fix for this.
|
||||||
|
*
|
||||||
|
* Since we need to know all of this stuff about the types of the iteratee,
|
||||||
|
* we have no choice but for ConstraintSolver to also be the thing that
|
||||||
|
* applies constraints to the types of the iterators.
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto block_ = [&](auto&& t) {
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
// If we haven't figured out the type of the iteratee by now,
|
||||||
|
// there's nothing we can do.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
block(t, constraint);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto [iteratorTypes, iteratorTail] = flatten(c.iterator);
|
||||||
|
if (iteratorTail)
|
||||||
|
return block_(*iteratorTail);
|
||||||
|
|
||||||
|
if (0 == iteratorTypes.size())
|
||||||
|
{
|
||||||
|
Anyification anyify{
|
||||||
|
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()};
|
||||||
|
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
||||||
|
LUAU_ASSERT(anyified);
|
||||||
|
unify(*anyified, c.variables, constraint->scope);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId nextTy = follow(iteratorTypes[0]);
|
||||||
|
if (get<FreeTypeVar>(nextTy))
|
||||||
|
return block_(nextTy);
|
||||||
|
|
||||||
|
if (get<FunctionTypeVar>(nextTy))
|
||||||
|
{
|
||||||
|
TypeId tableTy = getSingletonTypes().nilType;
|
||||||
|
if (iteratorTypes.size() >= 2)
|
||||||
|
tableTy = iteratorTypes[1];
|
||||||
|
|
||||||
|
TypeId firstIndexTy = getSingletonTypes().nilType;
|
||||||
|
if (iteratorTypes.size() >= 3)
|
||||||
|
firstIndexTy = iteratorTypes[2];
|
||||||
|
|
||||||
|
return tryDispatchIterableFunction(nextTy, tableTy, firstIndexTy, c, constraint, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
return tryDispatchIterableTable(iteratorTypes[0], c, constraint, force);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
|
bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
if (isBlocked(c.namedType))
|
if (isBlocked(c.namedType))
|
||||||
@ -519,7 +609,7 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
|
|||||||
|
|
||||||
TypeId target = follow(c.namedType);
|
TypeId target = follow(c.namedType);
|
||||||
|
|
||||||
if (target->persistent)
|
if (target->persistent || target->owningArena != arena)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
|
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
|
||||||
@ -536,19 +626,27 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor
|
|||||||
{
|
{
|
||||||
ConstraintSolver* solver;
|
ConstraintSolver* solver;
|
||||||
const InstantiationSignature& signature;
|
const InstantiationSignature& signature;
|
||||||
|
NotNull<Scope> scope;
|
||||||
bool foundInfiniteType = false;
|
bool foundInfiniteType = false;
|
||||||
|
|
||||||
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature)
|
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
|
||||||
: solver(solver)
|
: solver(solver)
|
||||||
, signature(signature)
|
, signature(signature)
|
||||||
|
, scope(scope)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
||||||
{
|
{
|
||||||
auto [typeArguments, packArguments] = saturateArguments(petv.fn, petv.typeArguments, petv.packArguments, solver->arena);
|
std::optional<TypeFun> tf =
|
||||||
|
(petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value);
|
||||||
|
|
||||||
if (follow(petv.fn.type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
|
if (!tf.has_value())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto [typeArguments, packArguments] = saturateArguments(*tf, petv.typeArguments, petv.packArguments, solver->arena);
|
||||||
|
|
||||||
|
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
|
||||||
{
|
{
|
||||||
foundInfiniteType = true;
|
foundInfiniteType = true;
|
||||||
return false;
|
return false;
|
||||||
@ -563,17 +661,19 @@ struct InstantiationQueuer : TypeVarOnceVisitor
|
|||||||
ConstraintSolver* solver;
|
ConstraintSolver* solver;
|
||||||
const InstantiationSignature& signature;
|
const InstantiationSignature& signature;
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
Location location;
|
||||||
|
|
||||||
explicit InstantiationQueuer(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
|
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature)
|
||||||
: solver(solver)
|
: solver(solver)
|
||||||
, signature(signature)
|
, signature(signature)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
|
, location(location)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
||||||
{
|
{
|
||||||
solver->pushConstraint(TypeAliasExpansionConstraint{ty}, scope);
|
solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -592,23 +692,32 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
unblock(c.target);
|
unblock(c.target);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If there are no parameters to the type function we can just use the type
|
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
|
||||||
// directly.
|
: constraint->scope->lookupType(petv->name.value);
|
||||||
if (petv->fn.typeParams.empty() && petv->fn.typePackParams.empty())
|
|
||||||
|
if (!tf.has_value())
|
||||||
{
|
{
|
||||||
bindResult(petv->fn.type);
|
reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
|
||||||
|
bindResult(getSingletonTypes().errorRecoveryType());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [typeArguments, packArguments] = saturateArguments(petv->fn, petv->typeArguments, petv->packArguments, arena);
|
// If there are no parameters to the type function we can just use the type
|
||||||
|
// directly.
|
||||||
|
if (tf->typeParams.empty() && tf->typePackParams.empty())
|
||||||
|
{
|
||||||
|
bindResult(tf->type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool sameTypes =
|
auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena);
|
||||||
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(
|
bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
|
||||||
packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.typePackParams.end(), [](auto&& itp, auto&& p) {
|
return itp == p.ty;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool samePacks =
|
||||||
|
std::equal(packArguments.begin(), packArguments.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itp, auto&& p) {
|
||||||
return itp == p.tp;
|
return itp == p.tp;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -617,12 +726,12 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
// to the TypeFun's type.
|
// to the TypeFun's type.
|
||||||
if (sameTypes && samePacks)
|
if (sameTypes && samePacks)
|
||||||
{
|
{
|
||||||
bindResult(petv->fn.type);
|
bindResult(tf->type);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
InstantiationSignature signature{
|
InstantiationSignature signature{
|
||||||
petv->fn,
|
*tf,
|
||||||
typeArguments,
|
typeArguments,
|
||||||
packArguments,
|
packArguments,
|
||||||
};
|
};
|
||||||
@ -642,8 +751,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
|
// 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
|
// This is a little nicer than using a recursion limit because we can catch
|
||||||
// the infinite expansion before actually trying to expand it.
|
// the infinite expansion before actually trying to expand it.
|
||||||
InfiniteTypeFinder itf{this, signature};
|
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
||||||
itf.traverse(petv->fn.type);
|
itf.traverse(tf->type);
|
||||||
|
|
||||||
if (itf.foundInfiniteType)
|
if (itf.foundInfiniteType)
|
||||||
{
|
{
|
||||||
@ -655,15 +764,15 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
ApplyTypeFunction applyTypeFunction{arena};
|
ApplyTypeFunction applyTypeFunction{arena};
|
||||||
for (size_t i = 0; i < typeArguments.size(); ++i)
|
for (size_t i = 0; i < typeArguments.size(); ++i)
|
||||||
{
|
{
|
||||||
applyTypeFunction.typeArguments[petv->fn.typeParams[i].ty] = typeArguments[i];
|
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeArguments[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < packArguments.size(); ++i)
|
for (size_t i = 0; i < packArguments.size(); ++i)
|
||||||
{
|
{
|
||||||
applyTypeFunction.typePackArguments[petv->fn.typePackParams[i].tp] = packArguments[i];
|
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = packArguments[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(petv->fn.type);
|
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf->type);
|
||||||
// Note that ApplyTypeFunction::encounteredForwardedType is never set in
|
// Note that ApplyTypeFunction::encounteredForwardedType is never set in
|
||||||
// DCR, because we do not use free types for forward-declared generic
|
// DCR, because we do not use free types for forward-declared generic
|
||||||
// aliases.
|
// aliases.
|
||||||
@ -683,7 +792,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
|
|
||||||
// Type function application will happily give us the exact same type if
|
// Type function application will happily give us the exact same type if
|
||||||
// there are e.g. generic saturatedTypeArguments that go unused.
|
// there are e.g. generic saturatedTypeArguments that go unused.
|
||||||
bool needsClone = follow(petv->fn.type) == target;
|
bool needsClone = follow(tf->type) == target;
|
||||||
// Only tables have the properties we're trying to set.
|
// Only tables have the properties we're trying to set.
|
||||||
TableTypeVar* ttv = getMutableTableType(target);
|
TableTypeVar* ttv = getMutableTableType(target);
|
||||||
|
|
||||||
@ -722,7 +831,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
// The application is not recursive, so we need to queue up application of
|
// 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
|
// any child type function instantiations within the result in order for it
|
||||||
// to be complete.
|
// to be complete.
|
||||||
InstantiationQueuer queuer{this, signature, constraint->scope};
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature};
|
||||||
queuer.traverse(target);
|
queuer.traverse(target);
|
||||||
|
|
||||||
instantiatedAliases[signature] = target;
|
instantiatedAliases[signature] = target;
|
||||||
@ -730,6 +839,152 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
|
||||||
|
{
|
||||||
|
TypeId fn = follow(c.fn);
|
||||||
|
TypePackId result = follow(c.result);
|
||||||
|
|
||||||
|
if (isBlocked(c.fn))
|
||||||
|
{
|
||||||
|
return block(c.fn, constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
|
||||||
|
bool usedMagic = false;
|
||||||
|
|
||||||
|
if (ftv && ftv->dcrMagicFunction != nullptr)
|
||||||
|
{
|
||||||
|
usedMagic = ftv->dcrMagicFunction(NotNull(this), result, c.astFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usedMagic)
|
||||||
|
{
|
||||||
|
for (const auto& inner : c.innerConstraints)
|
||||||
|
{
|
||||||
|
unsolvedConstraints.push_back(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock(c.result);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
|
{
|
||||||
|
auto block_ = [&](auto&& t) {
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
// TODO: I believe it is the case that, if we are asked to force
|
||||||
|
// this constraint, then we can do nothing but fail. I'd like to
|
||||||
|
// find a code sample that gets here.
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
block(t, constraint);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We may have to block here if we don't know what the iteratee type is,
|
||||||
|
// if it's a free table, if we don't know it has a metatable, and so on.
|
||||||
|
iteratorTy = follow(iteratorTy);
|
||||||
|
if (get<FreeTypeVar>(iteratorTy))
|
||||||
|
return block_(iteratorTy);
|
||||||
|
|
||||||
|
auto anyify = [&](auto ty) {
|
||||||
|
Anyification anyify{arena, constraint->scope, &iceReporter, getSingletonTypes().anyType, getSingletonTypes().anyTypePack};
|
||||||
|
std::optional anyified = anyify.substitute(ty);
|
||||||
|
if (!anyified)
|
||||||
|
reportError(CodeTooComplex{}, constraint->location);
|
||||||
|
else
|
||||||
|
unify(*anyified, ty, constraint->scope);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto errorify = [&](auto ty) {
|
||||||
|
Anyification anyify{
|
||||||
|
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()};
|
||||||
|
std::optional errorified = anyify.substitute(ty);
|
||||||
|
if (!errorified)
|
||||||
|
reportError(CodeTooComplex{}, constraint->location);
|
||||||
|
else
|
||||||
|
unify(*errorified, ty, constraint->scope);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (get<AnyTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
anyify(c.variables);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get<ErrorTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
errorify(c.variables);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Irksome: I don't think we have any way to guarantee that this table
|
||||||
|
// type never has a metatable.
|
||||||
|
|
||||||
|
if (auto iteratorTable = get<TableTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
if (iteratorTable->state == TableState::Free)
|
||||||
|
return block_(iteratorTy);
|
||||||
|
|
||||||
|
if (iteratorTable->indexer)
|
||||||
|
{
|
||||||
|
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
|
||||||
|
unify(c.variables, expectedVariablePack, constraint->scope);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
errorify(c.variables);
|
||||||
|
}
|
||||||
|
else if (auto iteratorMetatable = get<MetatableTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
TypeId metaTy = follow(iteratorMetatable->metatable);
|
||||||
|
if (get<FreeTypeVar>(metaTy))
|
||||||
|
return block_(metaTy);
|
||||||
|
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
errorify(c.variables);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatchIterableFunction(
|
||||||
|
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
|
{
|
||||||
|
// We need to know whether or not this type is nil or not.
|
||||||
|
// If we don't know, block and reschedule ourselves.
|
||||||
|
firstIndexTy = follow(firstIndexTy);
|
||||||
|
if (get<FreeTypeVar>(firstIndexTy))
|
||||||
|
{
|
||||||
|
if (force)
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
else
|
||||||
|
block(firstIndexTy, constraint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeId firstIndex = isNil(firstIndexTy) ? arena->freshType(constraint->scope) // FIXME: Surely this should be a union (free | nil)
|
||||||
|
: firstIndexTy;
|
||||||
|
|
||||||
|
// nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...)
|
||||||
|
const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, getSingletonTypes().nilType}})});
|
||||||
|
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
|
||||||
|
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
||||||
|
|
||||||
|
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{nextArgPack, nextRetPack});
|
||||||
|
unify(nextTy, expectedNextTy, constraint->scope);
|
||||||
|
|
||||||
|
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
|
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
blocked[target].push_back(constraint);
|
blocked[target].push_back(constraint);
|
||||||
@ -741,14 +996,14 @@ void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constrai
|
|||||||
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
|
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("block Constraint %s on\t%s\n", toString(*target).c_str(), toString(*constraint).c_str());
|
printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str());
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
|
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str());
|
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -756,7 +1011,7 @@ bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint
|
|||||||
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
|
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str());
|
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -772,7 +1027,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
|||||||
{
|
{
|
||||||
auto& count = blockedConstraints[unblockedConstraint];
|
auto& count = blockedConstraints[unblockedConstraint];
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint).c_str());
|
printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint, opts).c_str());
|
||||||
|
|
||||||
// This assertion being hit indicates that `blocked` and
|
// This assertion being hit indicates that `blocked` and
|
||||||
// `blockedConstraints` desynchronized at some point. This is problematic
|
// `blockedConstraints` desynchronized at some point. This is problematic
|
||||||
@ -817,6 +1072,11 @@ bool ConstraintSolver::isBlocked(TypeId ty)
|
|||||||
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
|
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::isBlocked(TypePackId tp)
|
||||||
|
{
|
||||||
|
return nullptr != get<BlockedTypePack>(follow(tp));
|
||||||
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
auto blockedIt = blockedConstraints.find(constraint);
|
auto blockedIt = blockedConstraints.find(constraint);
|
||||||
@ -830,6 +1090,13 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
|
|||||||
|
|
||||||
u.tryUnify(subType, superType);
|
u.tryUnify(subType, superType);
|
||||||
|
|
||||||
|
if (!u.errors.empty())
|
||||||
|
{
|
||||||
|
TypeId errorType = getSingletonTypes().errorRecoveryType();
|
||||||
|
u.tryUnify(subType, errorType);
|
||||||
|
u.tryUnify(superType, errorType);
|
||||||
|
}
|
||||||
|
|
||||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||||
|
|
||||||
u.log.commit();
|
u.log.commit();
|
||||||
@ -853,22 +1120,69 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
|
|||||||
unblock(changedPacks);
|
unblock(changedPacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::pushConstraint(ConstraintV cv, NotNull<Scope> scope)
|
void ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(std::move(cv), scope);
|
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
|
||||||
NotNull<Constraint> borrow = NotNull(c.get());
|
NotNull<Constraint> borrow = NotNull(c.get());
|
||||||
solverConstraints.push_back(std::move(c));
|
solverConstraints.push_back(std::move(c));
|
||||||
unsolvedConstraints.push_back(borrow);
|
unsolvedConstraints.push_back(borrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location)
|
||||||
|
{
|
||||||
|
if (info.name.empty())
|
||||||
|
{
|
||||||
|
reportError(UnknownRequire{}, location);
|
||||||
|
return getSingletonTypes().errorRecoveryType();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name);
|
||||||
|
|
||||||
|
for (const auto& [location, path] : requireCycles)
|
||||||
|
{
|
||||||
|
if (!path.empty() && path.front() == humanReadableName)
|
||||||
|
return getSingletonTypes().anyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModulePtr module = moduleResolver->getModule(info.name);
|
||||||
|
if (!module)
|
||||||
|
{
|
||||||
|
if (!moduleResolver->moduleExists(info.name) && !info.optional)
|
||||||
|
reportError(UnknownRequire{humanReadableName}, location);
|
||||||
|
|
||||||
|
return getSingletonTypes().errorRecoveryType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module->type != SourceCode::Type::Module)
|
||||||
|
{
|
||||||
|
reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location);
|
||||||
|
return getSingletonTypes().errorRecoveryType();
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId modulePack = module->getModuleScope()->returnType;
|
||||||
|
if (get<Unifiable::Error>(modulePack))
|
||||||
|
return getSingletonTypes().errorRecoveryType();
|
||||||
|
|
||||||
|
std::optional<TypeId> moduleType = first(modulePack);
|
||||||
|
if (!moduleType)
|
||||||
|
{
|
||||||
|
reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location);
|
||||||
|
return getSingletonTypes().errorRecoveryType();
|
||||||
|
}
|
||||||
|
|
||||||
|
return *moduleType;
|
||||||
|
}
|
||||||
|
|
||||||
void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location)
|
void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location)
|
||||||
{
|
{
|
||||||
errors.emplace_back(location, std::move(data));
|
errors.emplace_back(location, std::move(data));
|
||||||
|
errors.back().moduleName = currentModuleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::reportError(TypeError e)
|
void ConstraintSolver::reportError(TypeError e)
|
||||||
{
|
{
|
||||||
errors.emplace_back(std::move(e));
|
errors.emplace_back(std::move(e));
|
||||||
|
errors.back().moduleName = currentModuleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "Luau/ConstraintSolverLogger.h"
|
#include "Luau/ConstraintSolverLogger.h"
|
||||||
|
|
||||||
#include "Luau/JsonEmitter.h"
|
#include "Luau/JsonEmitter.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauFixNameMaps);
|
LUAU_FASTFLAG(LuauFixNameMaps);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "Luau/TypeChecker2.h"
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@ -99,7 +100,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
|||||||
module.root = parseResult.root;
|
module.root = parseResult.root;
|
||||||
module.mode = Mode::Definition;
|
module.mode = Mode::Definition;
|
||||||
|
|
||||||
ModulePtr checkedModule = check(module, Mode::Definition, globalScope);
|
ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {});
|
||||||
|
|
||||||
if (checkedModule->errors.size() > 0)
|
if (checkedModule->errors.size() > 0)
|
||||||
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
||||||
@ -526,7 +527,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||||||
|
|
||||||
typeChecker.requireCycles = requireCycles;
|
typeChecker.requireCycles = requireCycles;
|
||||||
|
|
||||||
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
|
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope, requireCycles)
|
||||||
: typeChecker.check(sourceModule, mode, environmentScope);
|
: typeChecker.check(sourceModule, mode, environmentScope);
|
||||||
|
|
||||||
stats.timeCheck += getTimestamp() - timestamp;
|
stats.timeCheck += getTimestamp() - timestamp;
|
||||||
@ -832,15 +833,15 @@ ScopePtr Frontend::getGlobalScope()
|
|||||||
return globalScope;
|
return globalScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope)
|
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles)
|
||||||
{
|
{
|
||||||
ModulePtr result = std::make_shared<Module>();
|
ModulePtr result = std::make_shared<Module>();
|
||||||
|
|
||||||
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()};
|
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), NotNull(&iceHandler), getGlobalScope()};
|
||||||
cgb.visit(sourceModule.root);
|
cgb.visit(sourceModule.root);
|
||||||
result->errors = std::move(cgb.errors);
|
result->errors = std::move(cgb.errors);
|
||||||
|
|
||||||
ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)};
|
ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles};
|
||||||
cs.run();
|
cs.run();
|
||||||
|
|
||||||
for (TypeError& e : cs.errors)
|
for (TypeError& e : cs.errors)
|
||||||
@ -852,11 +853,12 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
|
|||||||
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
|
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
|
||||||
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
|
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
|
||||||
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
|
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
|
||||||
|
result->type = sourceModule.type;
|
||||||
result->clonePublicInterface(iceHandler);
|
|
||||||
|
|
||||||
Luau::check(sourceModule, result.get());
|
Luau::check(sourceModule, result.get());
|
||||||
|
|
||||||
|
result->clonePublicInterface(iceHandler);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ TypeId Instantiation::clean(TypeId ty)
|
|||||||
|
|
||||||
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
|
clone.dcrMagicFunction = ftv->dcrMagicFunction;
|
||||||
clone.tags = ftv->tags;
|
clone.tags = ftv->tags;
|
||||||
clone.argNames = ftv->argNames;
|
clone.argNames = ftv->argNames;
|
||||||
TypeId result = addType(std::move(clone));
|
TypeId result = addType(std::move(clone));
|
||||||
|
@ -11,7 +11,8 @@ namespace Luau::Json
|
|||||||
static constexpr int CHUNK_SIZE = 1024;
|
static constexpr int CHUNK_SIZE = 1024;
|
||||||
|
|
||||||
ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> emitter)
|
ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> emitter)
|
||||||
: emitter(emitter), finished(false)
|
: emitter(emitter)
|
||||||
|
, finished(false)
|
||||||
{
|
{
|
||||||
comma = emitter->pushComma();
|
comma = emitter->pushComma();
|
||||||
emitter->writeRaw('{');
|
emitter->writeRaw('{');
|
||||||
@ -33,7 +34,8 @@ void ObjectEmitter::finish()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArrayEmitter::ArrayEmitter(NotNull<JsonEmitter> emitter)
|
ArrayEmitter::ArrayEmitter(NotNull<JsonEmitter> emitter)
|
||||||
: emitter(emitter), finished(false)
|
: emitter(emitter)
|
||||||
|
, finished(false)
|
||||||
{
|
{
|
||||||
comma = emitter->pushComma();
|
comma = emitter->pushComma();
|
||||||
emitter->writeRaw('[');
|
emitter->writeRaw('[');
|
||||||
|
@ -216,7 +216,8 @@ static bool similar(AstExpr* lhs, AstExpr* rhs)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (size_t i = 0; i < le->strings.size; ++i)
|
for (size_t i = 0; i < le->strings.size; ++i)
|
||||||
if (le->strings.data[i].size != re->strings.data[i].size || memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0)
|
if (le->strings.data[i].size != re->strings.data[i].size ||
|
||||||
|
memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (size_t i = 0; i < le->expressions.size; ++i)
|
for (size_t i = 0; i < le->expressions.size; ++i)
|
||||||
@ -2675,13 +2676,18 @@ public:
|
|||||||
private:
|
private:
|
||||||
LintContext* context;
|
LintContext* context;
|
||||||
|
|
||||||
bool isComparison(AstExprBinary::Op op)
|
static bool isEquality(AstExprBinary::Op op)
|
||||||
|
{
|
||||||
|
return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isComparison(AstExprBinary::Op op)
|
||||||
{
|
{
|
||||||
return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq || op == AstExprBinary::CompareLt || op == AstExprBinary::CompareLe ||
|
return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq || op == AstExprBinary::CompareLt || op == AstExprBinary::CompareLe ||
|
||||||
op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe;
|
op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isNot(AstExpr* node)
|
static bool isNot(AstExpr* node)
|
||||||
{
|
{
|
||||||
AstExprUnary* expr = node->as<AstExprUnary>();
|
AstExprUnary* expr = node->as<AstExprUnary>();
|
||||||
|
|
||||||
@ -2698,22 +2704,26 @@ private:
|
|||||||
{
|
{
|
||||||
std::string op = toString(node->op);
|
std::string op = toString(node->op);
|
||||||
|
|
||||||
if (node->op == AstExprBinary::CompareEq || node->op == AstExprBinary::CompareNe)
|
if (isEquality(node->op))
|
||||||
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
||||||
"not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or wrap one of the expressions in parentheses to silence",
|
"not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or add parentheses to silence", op.c_str(), op.c_str(),
|
||||||
op.c_str(), op.c_str(), node->op == AstExprBinary::CompareEq ? "~=" : "==");
|
node->op == AstExprBinary::CompareEq ? "~=" : "==");
|
||||||
else
|
else
|
||||||
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
||||||
"not X %s Y is equivalent to (not X) %s Y; wrap one of the expressions in parentheses to silence", op.c_str(), op.c_str());
|
"not X %s Y is equivalent to (not X) %s Y; add parentheses to silence", op.c_str(), op.c_str());
|
||||||
}
|
}
|
||||||
else if (AstExprBinary* left = node->left->as<AstExprBinary>(); left && isComparison(left->op))
|
else if (AstExprBinary* left = node->left->as<AstExprBinary>(); left && isComparison(left->op))
|
||||||
{
|
{
|
||||||
std::string lop = toString(left->op);
|
std::string lop = toString(left->op);
|
||||||
std::string rop = toString(node->op);
|
std::string rop = toString(node->op);
|
||||||
|
|
||||||
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
if (isEquality(left->op) || isEquality(node->op))
|
||||||
"X %s Y %s Z is equivalent to (X %s Y) %s Z; wrap one of the expressions in parentheses to silence", lop.c_str(), rop.c_str(),
|
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
||||||
lop.c_str(), rop.c_str());
|
"X %s Y %s Z is equivalent to (X %s Y) %s Z; add parentheses to silence", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str());
|
||||||
|
else
|
||||||
|
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
|
||||||
|
"X %s Y %s Z is equivalent to (X %s Y) %s Z; did you mean X %s Y and Y %s Z?", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str(),
|
||||||
|
lop.c_str(), rop.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -219,8 +219,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||||||
|
|
||||||
TypePackId returnType = moduleScope->returnType;
|
TypePackId returnType = moduleScope->returnType;
|
||||||
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
|
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
|
||||||
std::unordered_map<Name, TypeFun>* exportedTypeBindings =
|
std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
|
||||||
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
|
|
||||||
|
|
||||||
TxnLog log;
|
TxnLog log;
|
||||||
ClonePublicInterface clonePublicInterface{&log, this};
|
ClonePublicInterface clonePublicInterface{&log, this};
|
||||||
|
@ -510,7 +510,7 @@ void Substitution::foundDirty(TypeId ty)
|
|||||||
ty = log->follow(ty);
|
ty = log->follow(ty);
|
||||||
|
|
||||||
if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty))
|
if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isDirty(ty))
|
if (isDirty(ty))
|
||||||
newTypes[ty] = follow(clean(ty));
|
newTypes[ty] = follow(clean(ty));
|
||||||
@ -523,7 +523,7 @@ void Substitution::foundDirty(TypePackId tp)
|
|||||||
tp = log->follow(tp);
|
tp = log->follow(tp);
|
||||||
|
|
||||||
if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp))
|
if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isDirty(tp))
|
if (isDirty(tp))
|
||||||
newPacks[tp] = follow(clean(tp));
|
newPacks[tp] = follow(clean(tp));
|
||||||
|
@ -1036,6 +1036,13 @@ struct TypePackStringifier
|
|||||||
{
|
{
|
||||||
stringify(btv.boundTo);
|
stringify(btv.boundTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(TypePackId, const BlockedTypePack& btp)
|
||||||
|
{
|
||||||
|
state.emit("*blocked-tp-");
|
||||||
|
state.emit(btp.index);
|
||||||
|
state.emit("*");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void TypeVarStringifier::stringify(TypePackId tp)
|
void TypeVarStringifier::stringify(TypePackId tp)
|
||||||
@ -1099,9 +1106,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
|||||||
|
|
||||||
ToStringResult result;
|
ToStringResult result;
|
||||||
|
|
||||||
StringifierState state = FFlag::LuauFixNameMaps
|
StringifierState state =
|
||||||
? StringifierState{opts, result, opts.nameMap}
|
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||||
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
|
||||||
|
|
||||||
std::set<TypeId> cycles;
|
std::set<TypeId> cycles;
|
||||||
std::set<TypePackId> cycleTPs;
|
std::set<TypePackId> cycleTPs;
|
||||||
@ -1211,9 +1217,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
|
|||||||
* 4. Print out the root of the type using the same algorithm as step 3.
|
* 4. Print out the root of the type using the same algorithm as step 3.
|
||||||
*/
|
*/
|
||||||
ToStringResult result;
|
ToStringResult result;
|
||||||
StringifierState state = FFlag::LuauFixNameMaps
|
StringifierState state =
|
||||||
? StringifierState{opts, result, opts.nameMap}
|
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||||
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
|
||||||
|
|
||||||
std::set<TypeId> cycles;
|
std::set<TypeId> cycles;
|
||||||
std::set<TypePackId> cycleTPs;
|
std::set<TypePackId> cycleTPs;
|
||||||
@ -1302,9 +1307,8 @@ std::string toString(const TypePackVar& tp, ToStringOptions& opts)
|
|||||||
std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts)
|
std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts)
|
||||||
{
|
{
|
||||||
ToStringResult result;
|
ToStringResult result;
|
||||||
StringifierState state = FFlag::LuauFixNameMaps
|
StringifierState state =
|
||||||
? StringifierState{opts, result, opts.nameMap}
|
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||||
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
|
||||||
TypeVarStringifier tvs{state};
|
TypeVarStringifier tvs{state};
|
||||||
|
|
||||||
state.emit(funcName);
|
state.emit(funcName);
|
||||||
@ -1437,8 +1441,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||||||
using T = std::decay_t<decltype(c)>;
|
using T = std::decay_t<decltype(c)>;
|
||||||
|
|
||||||
// TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps
|
// TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps
|
||||||
auto tos = [](auto&& a, ToStringOptions& opts)
|
auto tos = [](auto&& a, ToStringOptions& opts) {
|
||||||
{
|
|
||||||
if (FFlag::LuauFixNameMaps)
|
if (FFlag::LuauFixNameMaps)
|
||||||
return toString(a, opts);
|
return toString(a, opts);
|
||||||
else
|
else
|
||||||
@ -1488,6 +1491,13 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||||||
|
|
||||||
return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">";
|
return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">";
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, IterableConstraint>)
|
||||||
|
{
|
||||||
|
std::string iteratorStr = tos(c.iterator, opts);
|
||||||
|
std::string variableStr = tos(c.variables, opts);
|
||||||
|
|
||||||
|
return variableStr + " ~ Iterate<" + iteratorStr + ">";
|
||||||
|
}
|
||||||
else if constexpr (std::is_same_v<T, NameConstraint>)
|
else if constexpr (std::is_same_v<T, NameConstraint>)
|
||||||
{
|
{
|
||||||
std::string namedStr = tos(c.namedType, opts);
|
std::string namedStr = tos(c.namedType, opts);
|
||||||
@ -1498,6 +1508,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||||||
std::string targetStr = tos(c.target, opts);
|
std::string targetStr = tos(c.target, opts);
|
||||||
return "expand " + targetStr;
|
return "expand " + targetStr;
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, FunctionCallConstraint>)
|
||||||
|
{
|
||||||
|
return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,15 @@ TypeId TypeArena::freshType(TypeLevel level)
|
|||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::freshType(Scope* scope)
|
||||||
|
{
|
||||||
|
TypeId allocated = typeVars.allocate(FreeTypeVar{scope});
|
||||||
|
|
||||||
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
||||||
{
|
{
|
||||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||||
@ -40,9 +49,9 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
|||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
|
TypePackId TypeArena::addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail)
|
||||||
{
|
{
|
||||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
TypePackId allocated = typePacks.allocate(TypePack{std::move(types), tail});
|
||||||
|
|
||||||
asMutable(allocated)->owningArena = this;
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
@ -373,6 +373,11 @@ public:
|
|||||||
return Luau::visit(*this, btp.boundTo->ty);
|
return Luau::visit(*this, btp.boundTo->ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstTypePack* operator()(const BlockedTypePack& btp) const
|
||||||
|
{
|
||||||
|
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("*blocked*"));
|
||||||
|
}
|
||||||
|
|
||||||
AstTypePack* operator()(const TypePack& tp) const
|
AstTypePack* operator()(const TypePack& tp) const
|
||||||
{
|
{
|
||||||
AstArray<AstType*> head;
|
AstArray<AstType*> head;
|
||||||
|
@ -166,7 +166,8 @@ struct TypeChecker2
|
|||||||
auto pusher = pushStack(stat);
|
auto pusher = pushStack(stat);
|
||||||
|
|
||||||
if (0)
|
if (0)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
else if (auto s = stat->as<AstStatBlock>())
|
else if (auto s = stat->as<AstStatBlock>())
|
||||||
return visit(s);
|
return visit(s);
|
||||||
else if (auto s = stat->as<AstStatIf>())
|
else if (auto s = stat->as<AstStatIf>())
|
||||||
@ -239,11 +240,9 @@ struct TypeChecker2
|
|||||||
visit(repeatStatement->condition);
|
visit(repeatStatement->condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstStatBreak*)
|
void visit(AstStatBreak*) {}
|
||||||
{}
|
|
||||||
|
|
||||||
void visit(AstStatContinue*)
|
void visit(AstStatContinue*) {}
|
||||||
{}
|
|
||||||
|
|
||||||
void visit(AstStatReturn* ret)
|
void visit(AstStatReturn* ret)
|
||||||
{
|
{
|
||||||
@ -339,6 +338,50 @@ struct TypeChecker2
|
|||||||
visit(forStatement->body);
|
visit(forStatement->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "Render" a type pack out to an array of a given length. Expands
|
||||||
|
// variadics and various other things to get there.
|
||||||
|
static std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
|
||||||
|
{
|
||||||
|
std::vector<TypeId> result;
|
||||||
|
|
||||||
|
auto it = begin(pack);
|
||||||
|
auto endIt = end(pack);
|
||||||
|
|
||||||
|
while (it != endIt)
|
||||||
|
{
|
||||||
|
result.push_back(*it);
|
||||||
|
|
||||||
|
if (result.size() >= length)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!it.tail())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
TypePackId tail = *it.tail();
|
||||||
|
if (get<TypePack>(tail))
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
else if (auto vtp = get<VariadicTypePack>(tail))
|
||||||
|
{
|
||||||
|
while (result.size() < length)
|
||||||
|
result.push_back(vtp->ty);
|
||||||
|
}
|
||||||
|
else if (get<FreeTypePack>(tail) || get<GenericTypePack>(tail))
|
||||||
|
{
|
||||||
|
while (result.size() < length)
|
||||||
|
result.push_back(arena.addType(FreeTypeVar{nullptr}));
|
||||||
|
}
|
||||||
|
else if (auto etp = get<Unifiable::Error>(tail))
|
||||||
|
{
|
||||||
|
while (result.size() < length)
|
||||||
|
result.push_back(getSingletonTypes().errorRecoveryType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void visit(AstStatForIn* forInStatement)
|
void visit(AstStatForIn* forInStatement)
|
||||||
{
|
{
|
||||||
for (AstLocal* local : forInStatement->vars)
|
for (AstLocal* local : forInStatement->vars)
|
||||||
@ -351,6 +394,128 @@ struct TypeChecker2
|
|||||||
visit(expr);
|
visit(expr);
|
||||||
|
|
||||||
visit(forInStatement->body);
|
visit(forInStatement->body);
|
||||||
|
|
||||||
|
// Rule out crazy stuff. Maybe possible if the file is not syntactically valid.
|
||||||
|
if (!forInStatement->vars.size || !forInStatement->values.size)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NotNull<Scope> scope = stack.back();
|
||||||
|
TypeArena tempArena;
|
||||||
|
|
||||||
|
std::vector<TypeId> variableTypes;
|
||||||
|
for (AstLocal* var : forInStatement->vars)
|
||||||
|
{
|
||||||
|
std::optional<TypeId> ty = scope->lookup(var);
|
||||||
|
LUAU_ASSERT(ty);
|
||||||
|
variableTypes.emplace_back(*ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ugh. There's nothing in the AST to hang a whole type pack on for the
|
||||||
|
// set of iteratees, so we have to piece it back together by hand.
|
||||||
|
std::vector<TypeId> valueTypes;
|
||||||
|
for (size_t i = 0; i < forInStatement->values.size - 1; ++i)
|
||||||
|
valueTypes.emplace_back(lookupType(forInStatement->values.data[i]));
|
||||||
|
TypePackId iteratorTail = lookupPack(forInStatement->values.data[forInStatement->values.size - 1]);
|
||||||
|
TypePackId iteratorPack = tempArena.addTypePack(valueTypes, iteratorTail);
|
||||||
|
|
||||||
|
// ... and then expand it out to 3 values (if possible)
|
||||||
|
const std::vector<TypeId> iteratorTypes = flatten(tempArena, iteratorPack, 3);
|
||||||
|
if (iteratorTypes.empty())
|
||||||
|
{
|
||||||
|
reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeId iteratorTy = follow(iteratorTypes[0]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the first iterator argument is a function
|
||||||
|
* * There must be 1 to 3 iterator arguments. Name them (nextTy,
|
||||||
|
* arrayTy, startIndexTy)
|
||||||
|
* * The return type of nextTy() must correspond to the variables'
|
||||||
|
* types and counts. HOWEVER the first iterator will never be nil.
|
||||||
|
* * The first return value of nextTy must be compatible with
|
||||||
|
* startIndexTy.
|
||||||
|
* * The first argument to nextTy() must be compatible with arrayTy if
|
||||||
|
* present. nil if not.
|
||||||
|
* * The second argument to nextTy() must be compatible with
|
||||||
|
* startIndexTy if it is present. Else, it must be compatible with
|
||||||
|
* nil.
|
||||||
|
* * nextTy() must be callable with only 2 arguments.
|
||||||
|
*/
|
||||||
|
if (const FunctionTypeVar* nextFn = get<FunctionTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
if (iteratorTypes.size() < 1 || iteratorTypes.size() > 3)
|
||||||
|
reportError(GenericError{"for..in loops must be passed (next, [table[, state]])"}, getLocation(forInStatement->values));
|
||||||
|
|
||||||
|
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
|
||||||
|
std::vector<TypeId> expectedVariableTypes = flatten(tempArena, nextFn->retTypes, variableTypes.size());
|
||||||
|
if (expectedVariableTypes.size() < variableTypes.size())
|
||||||
|
reportError(GenericError{"next() does not return enough values"}, forInStatement->vars.data[0]->location);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < std::min(expectedVariableTypes.size(), variableTypes.size()); ++i)
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes[i]));
|
||||||
|
|
||||||
|
// nextFn is going to be invoked with (arrayTy, startIndexTy)
|
||||||
|
|
||||||
|
// It will be passed two arguments on every iteration save the
|
||||||
|
// first.
|
||||||
|
|
||||||
|
// It may be invoked with 0 or 1 argument on the first iteration.
|
||||||
|
// This depends on the types in iterateePack and therefore
|
||||||
|
// iteratorTypes.
|
||||||
|
|
||||||
|
// If iteratorTypes is too short to be a valid call to nextFn, we have to report a count mismatch error.
|
||||||
|
// If 2 is too short to be a valid call to nextFn, we have to report a count mismatch error.
|
||||||
|
// If 2 is too long to be a valid call to nextFn, we have to report a count mismatch error.
|
||||||
|
auto [minCount, maxCount] = getParameterExtents(TxnLog::empty(), nextFn->argTypes);
|
||||||
|
|
||||||
|
if (minCount > 2)
|
||||||
|
reportError(CountMismatch{2, minCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
if (maxCount && *maxCount < 2)
|
||||||
|
reportError(CountMismatch{2, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
|
||||||
|
const std::vector<TypeId> flattenedArgTypes = flatten(tempArena, nextFn->argTypes, 2);
|
||||||
|
const auto [argTypes, argsTail] = Luau::flatten(nextFn->argTypes);
|
||||||
|
|
||||||
|
size_t firstIterationArgCount = iteratorTypes.empty() ? 0 : iteratorTypes.size() - 1;
|
||||||
|
size_t actualArgCount = expectedVariableTypes.size();
|
||||||
|
|
||||||
|
if (firstIterationArgCount < minCount)
|
||||||
|
reportError(CountMismatch{2, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
else if (actualArgCount < minCount)
|
||||||
|
reportError(CountMismatch{2, actualArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
|
||||||
|
if (iteratorTypes.size() >= 2 && flattenedArgTypes.size() > 0)
|
||||||
|
{
|
||||||
|
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[1], flattenedArgTypes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iteratorTypes.size() == 3 && flattenedArgTypes.size() > 1)
|
||||||
|
{
|
||||||
|
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[2], flattenedArgTypes[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer)
|
||||||
|
{
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->vars.data[0]->location, variableTypes[0], ttv->indexer->indexType));
|
||||||
|
if (variableTypes.size() == 2)
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->vars.data[1]->location, variableTypes[1], ttv->indexer->indexResultType));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
else if (get<AnyTypeVar>(iteratorTy) || get<ErrorTypeVar>(iteratorTy))
|
||||||
|
{
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstStatAssign* assign)
|
void visit(AstStatAssign* assign)
|
||||||
@ -456,7 +621,8 @@ struct TypeChecker2
|
|||||||
auto StackPusher = pushStack(expr);
|
auto StackPusher = pushStack(expr);
|
||||||
|
|
||||||
if (0)
|
if (0)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
else if (auto e = expr->as<AstExprGroup>())
|
else if (auto e = expr->as<AstExprGroup>())
|
||||||
return visit(e);
|
return visit(e);
|
||||||
else if (auto e = expr->as<AstExprConstantNil>())
|
else if (auto e = expr->as<AstExprConstantNil>())
|
||||||
@ -561,9 +727,21 @@ struct TypeChecker2
|
|||||||
|
|
||||||
TypePackId expectedRetType = lookupPack(call);
|
TypePackId expectedRetType = lookupPack(call);
|
||||||
TypeId functionType = lookupType(call->func);
|
TypeId functionType = lookupType(call->func);
|
||||||
TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr);
|
|
||||||
LUAU_ASSERT(functionType);
|
LUAU_ASSERT(functionType);
|
||||||
|
|
||||||
|
if (get<AnyTypeVar>(functionType) || get<ErrorTypeVar>(functionType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: Lots of other types are callable: intersections of functions
|
||||||
|
// and things with the __call metamethod.
|
||||||
|
if (!get<FunctionTypeVar>(functionType))
|
||||||
|
{
|
||||||
|
reportError(CannotCallNonFunction{functionType}, call->func->location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId instantiatedFunctionType = follow(instantiation.substitute(functionType).value_or(nullptr));
|
||||||
|
|
||||||
TypePack args;
|
TypePack args;
|
||||||
for (AstExpr* arg : call->args)
|
for (AstExpr* arg : call->args)
|
||||||
{
|
{
|
||||||
@ -575,12 +753,11 @@ struct TypeChecker2
|
|||||||
TypePackId argsTp = arena.addTypePack(args);
|
TypePackId argsTp = arena.addTypePack(args);
|
||||||
FunctionTypeVar ftv{argsTp, expectedRetType};
|
FunctionTypeVar ftv{argsTp, expectedRetType};
|
||||||
TypeId expectedType = arena.addType(ftv);
|
TypeId expectedType = arena.addType(ftv);
|
||||||
|
|
||||||
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice))
|
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice))
|
||||||
{
|
{
|
||||||
unfreeze(module->interfaceTypes);
|
|
||||||
CloneState cloneState;
|
CloneState cloneState;
|
||||||
expectedType = clone(expectedType, module->interfaceTypes, cloneState);
|
expectedType = clone(expectedType, module->internalTypes, cloneState);
|
||||||
freeze(module->interfaceTypes);
|
|
||||||
reportError(TypeMismatch{expectedType, functionType}, call->location);
|
reportError(TypeMismatch{expectedType, functionType}, call->location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,7 +769,8 @@ struct TypeChecker2
|
|||||||
|
|
||||||
// leftType must have a property called indexName->index
|
// leftType must have a property called indexName->index
|
||||||
|
|
||||||
std::optional<TypeId> ty = getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
std::optional<TypeId> ty =
|
||||||
|
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
||||||
if (ty)
|
if (ty)
|
||||||
{
|
{
|
||||||
if (!isSubtype(resultType, *ty, stack.back(), ice))
|
if (!isSubtype(resultType, *ty, stack.back(), ice))
|
||||||
@ -972,18 +1150,34 @@ struct TypeChecker2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reportError(TypeErrorData&& data, const Location& location)
|
template<typename TID>
|
||||||
|
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
|
||||||
|
{
|
||||||
|
UnifierSharedState sharedState{&ice};
|
||||||
|
Unifier u{&module->internalTypes, Mode::Strict, scope, location, Covariant, sharedState};
|
||||||
|
u.anyIsTop = true;
|
||||||
|
u.tryUnify(subTy, superTy);
|
||||||
|
|
||||||
|
return std::move(u.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportError(TypeErrorData data, const Location& location)
|
||||||
{
|
{
|
||||||
module->errors.emplace_back(location, sourceModule->name, std::move(data));
|
module->errors.emplace_back(location, sourceModule->name, std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void reportError(TypeError e)
|
void reportError(TypeError e)
|
||||||
{
|
{
|
||||||
module->errors.emplace_back(std::move(e));
|
reportError(std::move(e.data), e.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> getIndexTypeFromType(
|
void reportErrors(ErrorVec errors)
|
||||||
const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors)
|
{
|
||||||
|
for (TypeError e : errors)
|
||||||
|
reportError(std::move(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors)
|
||||||
{
|
{
|
||||||
return Luau::getIndexTypeFromType(scope, module->errors, &module->internalTypes, type, prop, location, addErrors, ice);
|
return Luau::getIndexTypeFromType(scope, module->errors, &module->internalTypes, type, prop, location, addErrors, ice);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
|||||||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false)
|
LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||||
@ -45,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -2343,7 +2343,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
|||||||
{
|
{
|
||||||
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
|
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
|
||||||
expectedResultTypes.push_back(prop->second.type);
|
expectedResultTypes.push_back(prop->second.type);
|
||||||
else if (FFlag::LuauExpectedTableUnionIndexerType && ttv->indexer && maybeString(ttv->indexer->indexType))
|
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
|
||||||
expectedResultTypes.push_back(ttv->indexer->indexResultType);
|
expectedResultTypes.push_back(ttv->indexer->indexResultType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2498,6 +2498,12 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op)
|
|||||||
|
|
||||||
TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes)
|
TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauUnionOfTypesFollow)
|
||||||
|
{
|
||||||
|
a = follow(a);
|
||||||
|
b = follow(b);
|
||||||
|
}
|
||||||
|
|
||||||
if (unifyFreeTypes && (get<FreeTypeVar>(a) || get<FreeTypeVar>(b)))
|
if (unifyFreeTypes && (get<FreeTypeVar>(a) || get<FreeTypeVar>(b)))
|
||||||
{
|
{
|
||||||
if (unify(b, a, scope, location))
|
if (unify(b, a, scope, location))
|
||||||
@ -3659,33 +3665,6 @@ WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the minimum number of arguments the argument list can accept.
|
|
||||||
static size_t getMinParameterCount(TxnLog* log, TypePackId tp)
|
|
||||||
{
|
|
||||||
size_t minCount = 0;
|
|
||||||
size_t optionalCount = 0;
|
|
||||||
|
|
||||||
auto it = begin(tp, log);
|
|
||||||
auto endIter = end(tp);
|
|
||||||
|
|
||||||
while (it != endIter)
|
|
||||||
{
|
|
||||||
TypeId ty = *it;
|
|
||||||
if (isOptional(ty))
|
|
||||||
++optionalCount;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
minCount += optionalCount;
|
|
||||||
optionalCount = 0;
|
|
||||||
minCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
return minCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TypeChecker::checkArgumentList(
|
void TypeChecker::checkArgumentList(
|
||||||
const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations)
|
const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations)
|
||||||
{
|
{
|
||||||
@ -3705,7 +3684,7 @@ void TypeChecker::checkArgumentList(
|
|||||||
if (!argLocations.empty())
|
if (!argLocations.empty())
|
||||||
location = {state.location.begin, argLocations.back().end};
|
location = {state.location.begin, argLocations.back().end};
|
||||||
|
|
||||||
size_t minParams = getMinParameterCount(&state.log, paramPack);
|
size_t minParams = getParameterExtents(&state.log, paramPack).first;
|
||||||
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
|
state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3804,7 +3783,7 @@ void TypeChecker::checkArgumentList(
|
|||||||
} // ok
|
} // ok
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size_t minParams = getMinParameterCount(&state.log, paramPack);
|
size_t minParams = getParameterExtents(&state.log, paramPack).first;
|
||||||
|
|
||||||
std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
|
std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
|
||||||
bool isVariadic = tail && Luau::isVariadic(*tail);
|
bool isVariadic = tail && Luau::isVariadic(*tail);
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
BlockedTypePack::BlockedTypePack()
|
||||||
|
: index(++nextIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BlockedTypePack::nextIndex = 0;
|
||||||
|
|
||||||
TypePackVar::TypePackVar(const TypePackVariant& tp)
|
TypePackVar::TypePackVar(const TypePackVariant& tp)
|
||||||
: ty(tp)
|
: ty(tp)
|
||||||
{
|
{
|
||||||
|
@ -84,9 +84,8 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> getIndexTypeFromType(
|
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
|
||||||
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors,
|
const Location& location, bool addErrors, InternalErrorReporter& handle)
|
||||||
InternalErrorReporter& handle)
|
|
||||||
{
|
{
|
||||||
type = follow(type);
|
type = follow(type);
|
||||||
|
|
||||||
@ -190,4 +189,33 @@ std::optional<TypeId> getIndexTypeFromType(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp)
|
||||||
|
{
|
||||||
|
size_t minCount = 0;
|
||||||
|
size_t optionalCount = 0;
|
||||||
|
|
||||||
|
auto it = begin(tp, log);
|
||||||
|
auto endIter = end(tp);
|
||||||
|
|
||||||
|
while (it != endIter)
|
||||||
|
{
|
||||||
|
TypeId ty = *it;
|
||||||
|
if (isOptional(ty))
|
||||||
|
++optionalCount;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
minCount += optionalCount;
|
||||||
|
optionalCount = 0;
|
||||||
|
minCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.tail())
|
||||||
|
return {minCount, std::nullopt};
|
||||||
|
else
|
||||||
|
return {minCount, minCount + optionalCount};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -24,9 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDeduceFindMatchReturnTypes, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
|
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -446,8 +444,10 @@ BlockedTypeVar::BlockedTypeVar()
|
|||||||
|
|
||||||
int BlockedTypeVar::nextIndex = 0;
|
int BlockedTypeVar::nextIndex = 0;
|
||||||
|
|
||||||
PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
PendingExpansionTypeVar::PendingExpansionTypeVar(
|
||||||
: fn(fn)
|
std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||||
|
: prefix(prefix)
|
||||||
|
, name(name)
|
||||||
, typeArguments(typeArguments)
|
, typeArguments(typeArguments)
|
||||||
, packArguments(packArguments)
|
, packArguments(packArguments)
|
||||||
, index(++nextIndex)
|
, index(++nextIndex)
|
||||||
@ -787,8 +787,8 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||||||
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||||
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
||||||
|
|
||||||
const TypeId matchFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}),
|
const TypeId matchFunc = arena->addType(
|
||||||
arena->addTypePack(TypePackVar{VariadicTypePack{FFlag::LuauDeduceFindMatchReturnTypes ? stringType : optionalString}})});
|
FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
|
||||||
attachMagicFunction(matchFunc, magicFunctionMatch);
|
attachMagicFunction(matchFunc, magicFunctionMatch);
|
||||||
|
|
||||||
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
||||||
@ -1221,9 +1221,6 @@ static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const ch
|
|||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauDeduceGmatchReturnTypes)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto [paramPack, _predicates] = withPredicate;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
const auto& [params, tail] = flatten(paramPack);
|
const auto& [params, tail] = flatten(paramPack);
|
||||||
|
|
||||||
@ -1256,9 +1253,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
|||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauDeduceFindMatchReturnTypes)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto [paramPack, _predicates] = withPredicate;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
const auto& [params, tail] = flatten(paramPack);
|
const auto& [params, tail] = flatten(paramPack);
|
||||||
|
|
||||||
@ -1295,9 +1289,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
|||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauDeduceFindMatchReturnTypes)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto [paramPack, _predicates] = withPredicate;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
const auto& [params, tail] = flatten(paramPack);
|
const auto& [params, tail] = flatten(paramPack);
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@ void* pagedAllocate(size_t size)
|
|||||||
{
|
{
|
||||||
// By default we use operator new/delete instead of malloc/free so that they can be overridden externally
|
// By default we use operator new/delete instead of malloc/free so that they can be overridden externally
|
||||||
if (!FFlag::DebugLuauFreezeArena)
|
if (!FFlag::DebugLuauFreezeArena)
|
||||||
|
{
|
||||||
return ::operator new(size, std::nothrow);
|
return ::operator new(size, std::nothrow);
|
||||||
|
}
|
||||||
|
|
||||||
// On Windows, VirtualAlloc results in 64K granularity allocations; we allocate in chunks of ~32K so aligned_malloc is a little more efficient
|
// On Windows, VirtualAlloc results in 64K granularity allocations; we allocate in chunks of ~32K so aligned_malloc is a little more efficient
|
||||||
// On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit.
|
// On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit.
|
||||||
|
@ -13,7 +13,8 @@ Free::Free(TypeLevel level)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Free::Free(Scope* scope)
|
Free::Free(Scope* scope)
|
||||||
: scope(scope)
|
: index(++nextIndex)
|
||||||
|
, scope(scope)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,8 @@ static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMat
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
||||||
|
TxnLog* parentLog)
|
||||||
: types(types)
|
: types(types)
|
||||||
, mode(mode)
|
, mode(mode)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
@ -492,9 +493,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
|
|
||||||
if (log.get<ConstrainedTypeVar>(subTy))
|
if (log.get<ConstrainedTypeVar>(subTy))
|
||||||
tryUnifyWithConstrainedSubTypeVar(subTy, superTy);
|
tryUnifyWithConstrainedSubTypeVar(subTy, superTy);
|
||||||
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy))
|
else if (const UnionTypeVar* subUnion = log.getMutable<UnionTypeVar>(subTy))
|
||||||
{
|
{
|
||||||
tryUnifyUnionWithType(subTy, uv, superTy);
|
tryUnifyUnionWithType(subTy, subUnion, superTy);
|
||||||
}
|
}
|
||||||
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
|
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
|
||||||
{
|
{
|
||||||
@ -555,14 +556,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
log.popSeen(superTy, subTy);
|
log.popSeen(superTy, subTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId superTy)
|
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion, TypeId superTy)
|
||||||
{
|
{
|
||||||
// A | B <: T if A <: T and B <: T
|
// A | B <: T if A <: T and B <: T
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
std::optional<TypeError> unificationTooComplex;
|
std::optional<TypeError> unificationTooComplex;
|
||||||
std::optional<TypeError> firstFailedOption;
|
std::optional<TypeError> firstFailedOption;
|
||||||
|
|
||||||
for (TypeId type : uv->options)
|
for (TypeId type : subUnion->options)
|
||||||
{
|
{
|
||||||
Unifier innerState = makeChildUnifier();
|
Unifier innerState = makeChildUnifier();
|
||||||
innerState.tryUnify_(type, superTy);
|
innerState.tryUnify_(type, superTy);
|
||||||
@ -608,9 +609,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
|
if (auto superUnion = log.getMutable<UnionTypeVar>(superTy))
|
||||||
{
|
{
|
||||||
for (TypeId ty : utv)
|
for (TypeId ty : superUnion)
|
||||||
tryBind(ty);
|
tryBind(ty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1276,6 +1276,16 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
AstName getIdentifier(AstExpr*);
|
AstName getIdentifier(AstExpr*);
|
||||||
|
Location getLocation(const AstTypeList& typeList);
|
||||||
|
|
||||||
|
template<typename T> // AstNode, AstExpr, AstLocal, etc
|
||||||
|
Location getLocation(AstArray<T*> array)
|
||||||
|
{
|
||||||
|
if (0 == array.size)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return Location{array.data[0]->location.begin, array.data[array.size - 1]->location.end};
|
||||||
|
}
|
||||||
|
|
||||||
#undef LUAU_RTTI
|
#undef LUAU_RTTI
|
||||||
|
|
||||||
|
@ -304,6 +304,12 @@ private:
|
|||||||
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||||
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||||
LUAU_PRINTF_ATTR(5, 6);
|
LUAU_PRINTF_ATTR(5, 6);
|
||||||
|
// `parseErrorLocation` is associated with the parser error
|
||||||
|
// `astErrorLocation` is associated with the AstTypeError created
|
||||||
|
// It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely
|
||||||
|
// define the location (possibly of zero size) where a type annotation is expected.
|
||||||
|
AstTypeError* reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||||
|
LUAU_PRINTF_ATTR(4, 5);
|
||||||
|
|
||||||
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
||||||
void reportAmbiguousCallError();
|
void reportAmbiguousCallError();
|
||||||
|
@ -952,4 +952,17 @@ AstName getIdentifier(AstExpr* node)
|
|||||||
return AstName();
|
return AstName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Location getLocation(const AstTypeList& typeList)
|
||||||
|
{
|
||||||
|
Location result;
|
||||||
|
if (typeList.types.size)
|
||||||
|
{
|
||||||
|
result = Location{typeList.types.data[0]->location, typeList.types.data[typeList.types.size - 1]->location};
|
||||||
|
}
|
||||||
|
if (typeList.tailType)
|
||||||
|
result.end = typeList.tailType->location.end;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -91,18 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz
|
|||||||
, length(unsigned(size))
|
, length(unsigned(size))
|
||||||
, data(data)
|
, data(data)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(
|
LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd ||
|
||||||
type == RawString
|
type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment);
|
||||||
|| type == QuotedString
|
|
||||||
|| type == InterpStringBegin
|
|
||||||
|| type == InterpStringMid
|
|
||||||
|| type == InterpStringEnd
|
|
||||||
|| type == InterpStringSimple
|
|
||||||
|| type == BrokenInterpDoubleBrace
|
|
||||||
|| type == Number
|
|
||||||
|| type == Comment
|
|
||||||
|| type == BlockComment
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Lexeme::Lexeme(const Location& location, Type type, const char* name)
|
Lexeme::Lexeme(const Location& location, Type type, const char* name)
|
||||||
@ -644,7 +634,8 @@ Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatT
|
|||||||
|
|
||||||
if (peekch(1) == '{')
|
if (peekch(1) == '{')
|
||||||
{
|
{
|
||||||
Lexeme brokenDoubleBrace = Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset);
|
Lexeme brokenDoubleBrace =
|
||||||
|
Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset);
|
||||||
consume();
|
consume();
|
||||||
consume();
|
consume();
|
||||||
return brokenDoubleBrace;
|
return brokenDoubleBrace;
|
||||||
|
@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
|
|||||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false)
|
||||||
|
|
||||||
bool lua_telemetry_parsed_out_of_range_bin_integer = 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_out_of_range_hex_integer = false;
|
||||||
@ -1564,44 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
incrementRecursionCounter("type annotation");
|
||||||
|
|
||||||
Location begin = lexer.current().location;
|
Location start = lexer.current().location;
|
||||||
|
|
||||||
if (lexer.current().type == Lexeme::ReservedNil)
|
if (lexer.current().type == Lexeme::ReservedNil)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}};
|
return {allocator.alloc<AstTypeReference>(start, std::nullopt, nameNil), {}};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedTrue)
|
else if (lexer.current().type == Lexeme::ReservedTrue)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeSingletonBool>(begin, true)};
|
return {allocator.alloc<AstTypeSingletonBool>(start, true)};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedFalse)
|
else if (lexer.current().type == Lexeme::ReservedFalse)
|
||||||
{
|
{
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {allocator.alloc<AstTypeSingletonBool>(begin, false)};
|
return {allocator.alloc<AstTypeSingletonBool>(start, false)};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
|
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
|
||||||
{
|
{
|
||||||
if (std::optional<AstArray<char>> value = parseCharArray())
|
if (std::optional<AstArray<char>> value = parseCharArray())
|
||||||
{
|
{
|
||||||
AstArray<char> svalue = *value;
|
AstArray<char> svalue = *value;
|
||||||
return {allocator.alloc<AstTypeSingletonString>(begin, svalue)};
|
return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
|
else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
|
||||||
{
|
{
|
||||||
parseInterpString();
|
parseInterpString();
|
||||||
|
|
||||||
return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::BrokenString)
|
else if (lexer.current().type == Lexeme::BrokenString)
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")};
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::Name)
|
else if (lexer.current().type == Lexeme::Name)
|
||||||
{
|
{
|
||||||
@ -1632,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||||||
|
|
||||||
expectMatchAndConsume(')', typeofBegin);
|
expectMatchAndConsume(')', typeofBegin);
|
||||||
|
|
||||||
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}};
|
return {allocator.alloc<AstTypeTypeof>(Location(start, end), expr), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasParameters = false;
|
bool hasParameters = false;
|
||||||
@ -1646,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||||||
|
|
||||||
Location end = lexer.previousLocation();
|
Location end = lexer.previousLocation();
|
||||||
|
|
||||||
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}};
|
return {allocator.alloc<AstTypeReference>(Location(start, end), prefix, name.name, hasParameters, parameters), {}};
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == '{')
|
else if (lexer.current().type == '{')
|
||||||
{
|
{
|
||||||
@ -1658,23 +1658,35 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::ReservedFunction)
|
else if (lexer.current().type == Lexeme::ReservedFunction)
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
|
||||||
|
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
|
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false,
|
||||||
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
||||||
"...any'"),
|
"...any'"),
|
||||||
{}};
|
{}};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Location location = lexer.current().location;
|
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||||
|
{
|
||||||
|
// For a missing type annotation, capture 'space' between last token and the next one
|
||||||
|
Location astErrorlocation(lexer.previousLocation().end, start.begin);
|
||||||
|
// The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message).
|
||||||
|
// Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau.
|
||||||
|
Location parseErrorLocation(lexer.previousLocation().end, start.end);
|
||||||
|
return {
|
||||||
|
reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()),
|
||||||
|
{}};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Location location = lexer.current().location;
|
||||||
|
|
||||||
// For a missing type annotation, capture 'space' between last token and the next one
|
// For a missing type annotation, capture 'space' between last token and the next one
|
||||||
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
|
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
|
||||||
|
|
||||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2245,7 +2257,8 @@ AstExpr* Parser::parseSimpleExpr()
|
|||||||
{
|
{
|
||||||
return parseNumber();
|
return parseNumber();
|
||||||
}
|
}
|
||||||
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
|
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString ||
|
||||||
|
(FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
|
||||||
{
|
{
|
||||||
return parseString();
|
return parseString();
|
||||||
}
|
}
|
||||||
@ -2653,7 +2666,8 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
|
|||||||
|
|
||||||
std::optional<AstArray<char>> Parser::parseCharArray()
|
std::optional<AstArray<char>> Parser::parseCharArray()
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::InterpStringSimple);
|
LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString ||
|
||||||
|
lexer.current().type == Lexeme::InterpStringSimple);
|
||||||
|
|
||||||
scratchData.assign(lexer.current().data, lexer.current().length);
|
scratchData.assign(lexer.current().data, lexer.current().length);
|
||||||
|
|
||||||
@ -2691,14 +2705,11 @@ AstExpr* Parser::parseInterpString()
|
|||||||
|
|
||||||
Location startLocation = lexer.current().location;
|
Location startLocation = lexer.current().location;
|
||||||
|
|
||||||
do {
|
do
|
||||||
|
{
|
||||||
Lexeme currentLexeme = lexer.current();
|
Lexeme currentLexeme = lexer.current();
|
||||||
LUAU_ASSERT(
|
LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
|
||||||
currentLexeme.type == Lexeme::InterpStringBegin
|
currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
|
||||||
|| currentLexeme.type == Lexeme::InterpStringMid
|
|
||||||
|| currentLexeme.type == Lexeme::InterpStringEnd
|
|
||||||
|| currentLexeme.type == Lexeme::InterpStringSimple
|
|
||||||
);
|
|
||||||
|
|
||||||
Location location = currentLexeme.location;
|
Location location = currentLexeme.location;
|
||||||
|
|
||||||
@ -2973,8 +2984,7 @@ bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begi
|
|||||||
{
|
{
|
||||||
// If the token matches on a different line and a different column, it suggests misleading indentation
|
// If the token matches on a different line and a different column, it suggests misleading indentation
|
||||||
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
|
// This can be used to pinpoint the problem location for a possible future *actual* mismatch
|
||||||
if (lexer.current().location.begin.line != begin.position.line &&
|
if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column &&
|
||||||
lexer.current().location.begin.column != begin.position.column &&
|
|
||||||
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
|
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
|
||||||
{
|
{
|
||||||
endMismatchSuspect = begin;
|
endMismatchSuspect = begin;
|
||||||
@ -3108,6 +3118,13 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray<A
|
|||||||
|
|
||||||
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||||
|
{
|
||||||
|
// Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled
|
||||||
|
// Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true.
|
||||||
|
LUAU_ASSERT(!isMissing);
|
||||||
|
}
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
report(location, format, args);
|
report(location, format, args);
|
||||||
@ -3116,6 +3133,18 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
|
|||||||
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
|
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
report(parseErrorLocation, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return allocator.alloc<AstTypeError>(astErrorLocation, AstArray<AstType*>{}, true, unsigned(parseErrors.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
void Parser::nextLexeme()
|
void Parser::nextLexeme()
|
||||||
{
|
{
|
||||||
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;
|
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;
|
||||||
|
@ -414,7 +414,7 @@ enum LuauBytecodeTag
|
|||||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
// 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_MIN = 2,
|
||||||
LBC_VERSION_MAX = 3,
|
LBC_VERSION_MAX = 3,
|
||||||
LBC_VERSION_TARGET = 2,
|
LBC_VERSION_TARGET = 3,
|
||||||
// Types of constant table entries
|
// Types of constant table entries
|
||||||
LBC_CONSTANT_NIL = 0,
|
LBC_CONSTANT_NIL = 0,
|
||||||
LBC_CONSTANT_BOOLEAN,
|
LBC_CONSTANT_BOOLEAN,
|
||||||
|
@ -13,7 +13,8 @@ inline bool isFlagExperimental(const char* flag)
|
|||||||
static const char* kList[] = {
|
static const char* kList[] = {
|
||||||
"LuauLowerBoundsCalculation",
|
"LuauLowerBoundsCalculation",
|
||||||
"LuauInterpolatedStringBaseSupport",
|
"LuauInterpolatedStringBaseSupport",
|
||||||
nullptr, // makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
|
nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* item : kList)
|
for (const char* item : kList)
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -1079,9 +1077,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
|
|||||||
|
|
||||||
uint8_t BytecodeBuilder::getVersion()
|
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
|
// 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;
|
return LBC_VERSION_TARGET;
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
|||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -406,47 +400,29 @@ struct Compiler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void compileExprFastcallN(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
|
void compileExprFastcallN(
|
||||||
|
AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!expr->self);
|
LUAU_ASSERT(!expr->self);
|
||||||
LUAU_ASSERT(expr->args.size >= 1);
|
LUAU_ASSERT(expr->args.size >= 1);
|
||||||
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
|
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
|
||||||
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
|
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
|
||||||
|
|
||||||
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2;
|
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
|
||||||
|
|
||||||
if (FFlag::LuauCompileExtractK)
|
|
||||||
{
|
|
||||||
opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t args[3] = {};
|
uint32_t args[3] = {};
|
||||||
|
|
||||||
for (size_t i = 0; i < expr->args.size; ++i)
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauCompileExtractK)
|
if (i > 0 && opc == LOP_FASTCALL2K)
|
||||||
{
|
{
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
int32_t cid = getConstantIndex(expr->args.data[i]);
|
||||||
{
|
if (cid < 0)
|
||||||
int32_t cid = getConstantIndex(expr->args.data[i]);
|
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||||
if (cid < 0)
|
|
||||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
|
||||||
|
|
||||||
args[i] = cid;
|
args[i] = cid;
|
||||||
continue; // TODO: remove this and change if below to else if
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (i > 0)
|
else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
||||||
{
|
|
||||||
if (int32_t cid = getConstantIndex(expr->args.data[i]); cid >= 0)
|
|
||||||
{
|
|
||||||
opc = LOP_FASTCALL2K;
|
|
||||||
args[i] = cid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
|
||||||
{
|
{
|
||||||
args[i] = uint8_t(reg);
|
args[i] = uint8_t(reg);
|
||||||
}
|
}
|
||||||
@ -468,24 +444,10 @@ struct Compiler
|
|||||||
// these FASTCALL variants.
|
// these FASTCALL variants.
|
||||||
for (size_t i = 0; i < expr->args.size; ++i)
|
for (size_t i = 0; i < expr->args.size; ++i)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauCompileExtractK)
|
if (i > 0 && opc == LOP_FASTCALL2K)
|
||||||
{
|
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
else if (args[i] != regs + 1 + i)
|
||||||
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
||||||
else if (args[i] != regs + 1 + i)
|
|
||||||
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i > 0 && opc == LOP_FASTCALL2K)
|
|
||||||
{
|
|
||||||
emitLoadK(uint8_t(regs + 1 + i), args[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[i] != regs + 1 + i)
|
|
||||||
bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
|
// note, these instructions are normally not executed and are used as a fallback for FASTCALL
|
||||||
@ -758,7 +720,7 @@ struct Compiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin
|
// Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin
|
||||||
if (FFlag::LuauCompileExtractK && bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
|
if (bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2]))
|
||||||
{
|
{
|
||||||
Constant fc = getConstant(expr->args.data[1]);
|
Constant fc = getConstant(expr->args.data[1]);
|
||||||
Constant wc = getConstant(expr->args.data[2]);
|
Constant wc = getConstant(expr->args.data[2]);
|
||||||
@ -1080,102 +1042,64 @@ struct Compiler
|
|||||||
std::swap(left, right);
|
std::swap(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauCompileXEQ)
|
uint8_t rl = compileExprAuto(left, rs);
|
||||||
|
|
||||||
|
if (isEq && operandIsConstant)
|
||||||
{
|
{
|
||||||
uint8_t rl = compileExprAuto(left, rs);
|
const Constant* cv = constants.find(right);
|
||||||
|
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
|
||||||
|
|
||||||
if (isEq && operandIsConstant)
|
LuauOpcode opc = LOP_NOP;
|
||||||
|
int32_t cid = -1;
|
||||||
|
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
|
||||||
|
|
||||||
|
switch (cv->type)
|
||||||
{
|
{
|
||||||
const Constant* cv = constants.find(right);
|
case Constant::Type_Nil:
|
||||||
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown);
|
opc = LOP_JUMPXEQKNIL;
|
||||||
|
cid = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
LuauOpcode opc = LOP_NOP;
|
case Constant::Type_Boolean:
|
||||||
int32_t cid = -1;
|
opc = LOP_JUMPXEQKB;
|
||||||
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0;
|
cid = cv->valueBoolean;
|
||||||
|
break;
|
||||||
|
|
||||||
switch (cv->type)
|
case Constant::Type_Number:
|
||||||
{
|
opc = LOP_JUMPXEQKN;
|
||||||
case Constant::Type_Nil:
|
cid = getConstantIndex(right);
|
||||||
opc = LOP_JUMPXEQKNIL;
|
break;
|
||||||
cid = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Constant::Type_Boolean:
|
case Constant::Type_String:
|
||||||
opc = LOP_JUMPXEQKB;
|
opc = LOP_JUMPXEQKS;
|
||||||
cid = cv->valueBoolean;
|
cid = getConstantIndex(right);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constant::Type_Number:
|
default:
|
||||||
opc = LOP_JUMPXEQKN;
|
LUAU_ASSERT(!"Unexpected constant type");
|
||||||
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);
|
if (cid < 0)
|
||||||
|
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||||
|
|
||||||
size_t jumpLabel = bytecode.emitLabel();
|
size_t jumpLabel = bytecode.emitLabel();
|
||||||
|
|
||||||
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
|
bytecode.emitAD(opc, rl, 0);
|
||||||
{
|
bytecode.emitAux(cid | flip);
|
||||||
bytecode.emitAD(opc, rr, 0);
|
|
||||||
bytecode.emitAux(rl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bytecode.emitAD(opc, rl, 0);
|
|
||||||
bytecode.emitAux(rr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jumpLabel;
|
return jumpLabel;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
|
LuauOpcode opc = getJumpOpCompare(expr->op, not_);
|
||||||
|
|
||||||
uint8_t rl = compileExprAuto(left, rs);
|
uint8_t rr = compileExprAuto(right, 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();
|
size_t jumpLabel = bytecode.emitLabel();
|
||||||
|
|
||||||
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
|
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
|
||||||
{
|
{
|
||||||
bytecode.emitAD(opc, uint8_t(rr), 0);
|
bytecode.emitAD(opc, rr, 0);
|
||||||
bytecode.emitAux(rl);
|
bytecode.emitAux(rl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2979,62 +2903,6 @@ struct Compiler
|
|||||||
loops.pop_back();
|
loops.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolveAssignConflicts(AstStat* stat, std::vector<LValue>& vars)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauCompileOptimalAssignment);
|
|
||||||
|
|
||||||
// regsUsed[i] is true if we have assigned the register during earlier assignments
|
|
||||||
// regsRemap[i] is set to the register where the original (pre-assignment) copy was made
|
|
||||||
// note: regsRemap is uninitialized intentionally to speed small assignments up; regsRemap[i] is valid iff regsUsed[i]
|
|
||||||
std::bitset<256> regsUsed;
|
|
||||||
uint8_t regsRemap[256];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < vars.size(); ++i)
|
|
||||||
{
|
|
||||||
LValue& li = vars[i];
|
|
||||||
|
|
||||||
if (li.kind == LValue::Kind_Local)
|
|
||||||
{
|
|
||||||
if (!regsUsed[li.reg])
|
|
||||||
{
|
|
||||||
regsUsed[li.reg] = true;
|
|
||||||
regsRemap[li.reg] = li.reg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (li.kind == LValue::Kind_IndexName || li.kind == LValue::Kind_IndexNumber || li.kind == LValue::Kind_IndexExpr)
|
|
||||||
{
|
|
||||||
// we're looking for assignments before this one that invalidate any of the registers involved
|
|
||||||
if (regsUsed[li.reg])
|
|
||||||
{
|
|
||||||
// the register may have been evacuated previously, but if it wasn't - move it now
|
|
||||||
if (regsRemap[li.reg] == li.reg)
|
|
||||||
{
|
|
||||||
uint8_t reg = allocReg(stat, 1);
|
|
||||||
bytecode.emitABC(LOP_MOVE, reg, li.reg, 0);
|
|
||||||
|
|
||||||
regsRemap[li.reg] = reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.reg = regsRemap[li.reg];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (li.kind == LValue::Kind_IndexExpr && regsUsed[li.index])
|
|
||||||
{
|
|
||||||
// the register may have been evacuated previously, but if it wasn't - move it now
|
|
||||||
if (regsRemap[li.index] == li.index)
|
|
||||||
{
|
|
||||||
uint8_t reg = allocReg(stat, 1);
|
|
||||||
bytecode.emitABC(LOP_MOVE, reg, li.index, 0);
|
|
||||||
|
|
||||||
regsRemap[li.index] = reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.index = regsRemap[li.index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Assignment
|
struct Assignment
|
||||||
{
|
{
|
||||||
LValue lvalue;
|
LValue lvalue;
|
||||||
@ -3146,111 +3014,82 @@ struct Compiler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauCompileOptimalAssignment)
|
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
||||||
|
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
|
||||||
|
std::vector<Assignment> vars(stat->vars.size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||||
|
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
|
||||||
|
|
||||||
|
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
|
||||||
|
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
|
||||||
|
resolveAssignConflicts(stat, vars, stat->values);
|
||||||
|
|
||||||
|
// compute rhs into (mostly) fresh registers
|
||||||
|
// note that when the lhs assigment is a local, we evaluate directly into that register
|
||||||
|
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
|
||||||
|
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
|
||||||
|
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
|
||||||
{
|
{
|
||||||
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
AstExpr* value = stat->values.data[i];
|
||||||
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
|
|
||||||
std::vector<Assignment> vars(stat->vars.size);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
|
||||||
vars[i].lvalue = compileLValue(stat->vars.data[i], rs);
|
|
||||||
|
|
||||||
// perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary
|
|
||||||
// register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass
|
|
||||||
resolveAssignConflicts(stat, vars, stat->values);
|
|
||||||
|
|
||||||
// compute rhs into (mostly) fresh registers
|
|
||||||
// note that when the lhs assigment is a local, we evaluate directly into that register
|
|
||||||
// this is possible because resolveAssignConflicts renamed conflicting locals into temporaries
|
|
||||||
// after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned
|
|
||||||
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
|
|
||||||
{
|
{
|
||||||
AstExpr* value = stat->values.data[i];
|
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
|
||||||
|
// note, this also handles trailing nils
|
||||||
|
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
|
||||||
|
uint8_t temp = allocReg(stat, rest);
|
||||||
|
|
||||||
if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
|
compileExprTempN(value, temp, rest, /* targetTop= */ true);
|
||||||
|
|
||||||
|
for (size_t j = i; j < stat->vars.size; ++j)
|
||||||
|
vars[j].valueReg = uint8_t(temp + (j - i));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assignment& var = vars[i];
|
||||||
|
|
||||||
|
// if target is a local, use compileExpr directly to target
|
||||||
|
if (var.lvalue.kind == LValue::Kind_Local)
|
||||||
{
|
{
|
||||||
// allocate a consecutive range of regs for all remaining vars and compute everything into temps
|
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
|
||||||
// note, this also handles trailing nils
|
|
||||||
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
|
|
||||||
uint8_t temp = allocReg(stat, rest);
|
|
||||||
|
|
||||||
compileExprTempN(value, temp, rest, /* targetTop= */ true);
|
compileExpr(stat->values.data[i], var.valueReg);
|
||||||
|
|
||||||
for (size_t j = i; j < stat->vars.size; ++j)
|
|
||||||
vars[j].valueReg = uint8_t(temp + (j - i));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assignment& var = vars[i];
|
var.valueReg = compileExprAuto(stat->values.data[i], rs);
|
||||||
|
|
||||||
// if target is a local, use compileExpr directly to target
|
|
||||||
if (var.lvalue.kind == LValue::Kind_Local)
|
|
||||||
{
|
|
||||||
var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
|
|
||||||
|
|
||||||
compileExpr(stat->values.data[i], var.valueReg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var.valueReg = compileExprAuto(stat->values.data[i], rs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute expressions with side effects for lulz
|
|
||||||
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
|
|
||||||
{
|
|
||||||
RegScope rsi(this);
|
|
||||||
compileExprAuto(stat->values.data[i], rsi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
|
|
||||||
// separate pass to avoid conflicts
|
|
||||||
for (const Assignment& var : vars)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(var.valueReg != kInvalidReg);
|
|
||||||
|
|
||||||
if (var.lvalue.kind != LValue::Kind_Local)
|
|
||||||
{
|
|
||||||
setDebugLine(var.lvalue.location);
|
|
||||||
compileAssign(var.lvalue, var.valueReg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
|
|
||||||
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
|
|
||||||
for (const Assignment& var : vars)
|
|
||||||
{
|
|
||||||
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
|
|
||||||
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// compute expressions with side effects for lulz
|
||||||
|
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
|
||||||
{
|
{
|
||||||
// compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the
|
RegScope rsi(this);
|
||||||
// left hand side for example, in "a[expr] = foo" expr will get evaluated here
|
compileExprAuto(stat->values.data[i], rsi);
|
||||||
std::vector<LValue> vars(stat->vars.size);
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
// almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
|
||||||
vars[i] = compileLValue(stat->vars.data[i], rs);
|
// separate pass to avoid conflicts
|
||||||
|
for (const Assignment& var : vars)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(var.valueReg != kInvalidReg);
|
||||||
|
|
||||||
// perform conflict resolution: if any lvalue refers to a local reg that will be reassigned before that, we save the local variable in a
|
if (var.lvalue.kind != LValue::Kind_Local)
|
||||||
// temporary reg
|
|
||||||
resolveAssignConflicts(stat, vars);
|
|
||||||
|
|
||||||
// compute values into temporaries
|
|
||||||
uint8_t regs = allocReg(stat, unsigned(stat->vars.size));
|
|
||||||
|
|
||||||
compileExprListTemp(stat->values, regs, uint8_t(stat->vars.size), /* targetTop= */ true);
|
|
||||||
|
|
||||||
// assign variables that have associated values; note that if we have fewer values than variables, we'll assign nil because
|
|
||||||
// compileExprListTemp will generate nils
|
|
||||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
|
||||||
{
|
{
|
||||||
setDebugLine(stat->vars.data[i]);
|
setDebugLine(var.lvalue.location);
|
||||||
compileAssign(vars[i], uint8_t(regs + i));
|
compileAssign(var.lvalue, var.valueReg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR
|
||||||
|
// local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries
|
||||||
|
for (const Assignment& var : vars)
|
||||||
|
{
|
||||||
|
if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg)
|
||||||
|
bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
|
void compileStatCompoundAssign(AstStatCompoundAssign* stat)
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false)
|
|
||||||
|
|
||||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||||
@ -602,7 +600,7 @@ static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkI
|
|||||||
|
|
||||||
static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
{
|
{
|
||||||
if (nparams >= (3 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
|
||||||
{
|
{
|
||||||
double a1 = nvalue(arg0);
|
double a1 = nvalue(arg0);
|
||||||
double a2 = nvalue(args);
|
double a2 = nvalue(args);
|
||||||
@ -693,7 +691,7 @@ static int luauF_lshift(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
|||||||
|
|
||||||
static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
{
|
{
|
||||||
if (nparams >= (4 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
||||||
{
|
{
|
||||||
double a1 = nvalue(arg0);
|
double a1 = nvalue(arg0);
|
||||||
double a2 = nvalue(args);
|
double a2 = nvalue(args);
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
|
|
||||||
|
|
||||||
// macro to `unsign' a character
|
// macro to `unsign' a character
|
||||||
#define uchar(c) ((unsigned char)(c))
|
#define uchar(c) ((unsigned char)(c))
|
||||||
|
|
||||||
@ -1036,9 +1034,6 @@ static int str_format(lua_State* L)
|
|||||||
}
|
}
|
||||||
case '*':
|
case '*':
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauTostringFormatSpecifier)
|
|
||||||
luaL_error(L, "invalid option '%%*' to 'format'");
|
|
||||||
|
|
||||||
if (formatItemSize != 1)
|
if (formatItemSize != 1)
|
||||||
luaL_error(L, "'%%*' does not take a form");
|
luaL_error(L, "'%%*' does not take a form");
|
||||||
|
|
||||||
|
@ -3031,7 +3031,16 @@ static void luau_execute(lua_State* L)
|
|||||||
TValue* kv = VM_KV(aux & 0xffffff);
|
TValue* kv = VM_KV(aux & 0xffffff);
|
||||||
LUAU_ASSERT(ttisnumber(kv));
|
LUAU_ASSERT(ttisnumber(kv));
|
||||||
|
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
// On several ARM chips (Apple M1/M2, Neoverse N1), comparing the result of a floating-point comparison is expensive, and a branch
|
||||||
|
// is much cheaper; on some 32-bit ARM chips (Cortex A53) the performance is about the same so we prefer less branchy variant there
|
||||||
|
if (aux >> 31)
|
||||||
|
pc += !(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
else
|
||||||
|
pc += (ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
#else
|
||||||
pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1;
|
pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1;
|
||||||
|
#endif
|
||||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
using namespace Luau::CodeGen;
|
using namespace Luau::CodeGen;
|
||||||
@ -22,7 +21,7 @@ std::string bytecodeAsArray(const std::vector<uint8_t>& bytecode)
|
|||||||
class AssemblyBuilderX64Fixture
|
class AssemblyBuilderX64Fixture
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void check(std::function<void(AssemblyBuilderX64& build)> f, std::vector<uint8_t> code, std::vector<uint8_t> data = {})
|
void check(void (*f)(AssemblyBuilderX64& build), std::vector<uint8_t> code, std::vector<uint8_t> data = {})
|
||||||
{
|
{
|
||||||
AssemblyBuilderX64 build(/* logText= */ false);
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
|
||||||
|
@ -1241,8 +1241,7 @@ TEST_CASE("InterpStringZeroCost")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
CHECK_EQ(
|
CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
|
||||||
"\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
|
|
||||||
R"(
|
R"(
|
||||||
LOADK R1 K0
|
LOADK R1 K0
|
||||||
LOADK R3 K1
|
LOADK R3 K1
|
||||||
@ -1250,16 +1249,14 @@ NAMECALL R1 R1 K2
|
|||||||
CALL R1 2 1
|
CALL R1 2 1
|
||||||
MOVE R0 R1
|
MOVE R0 R1
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)"
|
)");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("InterpStringRegisterCleanup")
|
TEST_CASE("InterpStringRegisterCleanup")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
CHECK_EQ(
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
"\n" + compileFunction0(R"(
|
|
||||||
local a, b, c = nil, "um", "uh oh"
|
local a, b, c = nil, "um", "uh oh"
|
||||||
a = `foo{"bar"}`
|
a = `foo{"bar"}`
|
||||||
print(a)
|
print(a)
|
||||||
@ -1278,8 +1275,7 @@ GETIMPORT R3 6
|
|||||||
MOVE R4 R0
|
MOVE R4 R0
|
||||||
CALL R3 1 0
|
CALL R3 1 0
|
||||||
RETURN R0 0
|
RETURN R0 0
|
||||||
)"
|
)");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ConstantFoldArith")
|
TEST_CASE("ConstantFoldArith")
|
||||||
@ -2488,8 +2484,6 @@ end
|
|||||||
|
|
||||||
TEST_CASE("DebugLineInfoRepeatUntil")
|
TEST_CASE("DebugLineInfoRepeatUntil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileXEQ", true);
|
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction0Coverage(R"(
|
CHECK_EQ("\n" + compileFunction0Coverage(R"(
|
||||||
local f = 0
|
local f = 0
|
||||||
repeat
|
repeat
|
||||||
@ -2834,8 +2828,6 @@ RETURN R0 0
|
|||||||
|
|
||||||
TEST_CASE("AssignmentConflict")
|
TEST_CASE("AssignmentConflict")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
|
|
||||||
|
|
||||||
// assignments are left to right
|
// assignments are left to right
|
||||||
CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"(
|
CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"(
|
||||||
LOADNIL R0
|
LOADNIL R0
|
||||||
@ -3610,8 +3602,6 @@ RETURN R0 1
|
|||||||
|
|
||||||
TEST_CASE("ConstantJumpCompare")
|
TEST_CASE("ConstantJumpCompare")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileXEQ", true);
|
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction0(R"(
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
local obj = ...
|
local obj = ...
|
||||||
local b = obj == 1
|
local b = obj == 1
|
||||||
@ -6210,8 +6200,6 @@ L4: RETURN R0 -1
|
|||||||
|
|
||||||
TEST_CASE("BuiltinFoldingMultret")
|
TEST_CASE("BuiltinFoldingMultret")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileXEQ", true);
|
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000
|
local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000
|
||||||
local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000
|
local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000
|
||||||
@ -6350,8 +6338,6 @@ RETURN R2 1
|
|||||||
|
|
||||||
TEST_CASE("MultipleAssignments")
|
TEST_CASE("MultipleAssignments")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
|
|
||||||
|
|
||||||
// order of assignments is left to right
|
// order of assignments is left to right
|
||||||
CHECK_EQ("\n" + compileFunction0(R"(
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
local a, b
|
local a, b
|
||||||
@ -6574,15 +6560,14 @@ RETURN R0 0
|
|||||||
|
|
||||||
TEST_CASE("BuiltinExtractK")
|
TEST_CASE("BuiltinExtractK")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauCompileExtractK", true);
|
|
||||||
|
|
||||||
// below, K0 refers to a packed f+w constant for bit32.extractk builtin
|
// below, K0 refers to a packed f+w constant for bit32.extractk builtin
|
||||||
// K1 and K2 refer to 1 and 3 and are only used during fallback path
|
// K1 and K2 refer to 1 and 3 and are only used during fallback path
|
||||||
CHECK_EQ("\n" + compileFunction0(R"(
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
local v = ...
|
local v = ...
|
||||||
|
|
||||||
return bit32.extract(v, 1, 3)
|
return bit32.extract(v, 1, 3)
|
||||||
)"), R"(
|
)"),
|
||||||
|
R"(
|
||||||
GETVARARGS R0 1
|
GETVARARGS R0 1
|
||||||
FASTCALL2K 59 R0 K0 L0
|
FASTCALL2K 59 R0 K0 L0
|
||||||
MOVE R2 R0
|
MOVE R2 R0
|
||||||
|
@ -289,15 +289,12 @@ TEST_CASE("Clear")
|
|||||||
|
|
||||||
TEST_CASE("Strings")
|
TEST_CASE("Strings")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauTostringFormatSpecifier", true};
|
|
||||||
|
|
||||||
runConformance("strings.lua");
|
runConformance("strings.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("StringInterp")
|
TEST_CASE("StringInterp")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true};
|
ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true};
|
||||||
ScopedFastFlag sffTostringFormat{"LuauTostringFormatSpecifier", true};
|
|
||||||
|
|
||||||
runConformance("stringinterp.lua");
|
runConformance("stringinterp.lua");
|
||||||
}
|
}
|
||||||
@ -725,13 +722,16 @@ TEST_CASE("NewUserdataOverflow")
|
|||||||
StateRef globalState(luaL_newstate(), lua_close);
|
StateRef globalState(luaL_newstate(), lua_close);
|
||||||
lua_State* L = globalState.get();
|
lua_State* L = globalState.get();
|
||||||
|
|
||||||
lua_pushcfunction(L, [](lua_State* L1) {
|
lua_pushcfunction(
|
||||||
// The following userdata request might cause an overflow.
|
L,
|
||||||
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d){});
|
[](lua_State* L1) {
|
||||||
// The overflow might segfault in the following call.
|
// The following userdata request might cause an overflow.
|
||||||
lua_getmetatable(L1, -1);
|
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {});
|
||||||
return 0;
|
// The overflow might segfault in the following call.
|
||||||
}, nullptr);
|
lua_getmetatable(L1, -1);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
|
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
|
||||||
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);
|
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);
|
||||||
|
@ -57,13 +57,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "nil_primitive")
|
|||||||
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
REQUIRE(5 <= constraints.size());
|
REQUIRE(4 <= constraints.size());
|
||||||
|
|
||||||
CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts));
|
CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts));
|
||||||
CHECK("*blocked-2* ~ inst *blocked-1*" == toString(*constraints[1], opts));
|
CHECK("call *blocked-1* with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
|
||||||
CHECK("() -> (b...) <: *blocked-2*" == toString(*constraints[2], opts));
|
CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
|
||||||
CHECK("b... <: c" == toString(*constraints[3], opts));
|
CHECK("nil <: a..." == toString(*constraints[3], opts));
|
||||||
CHECK("nil <: a..." == toString(*constraints[4], opts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
||||||
@ -76,13 +75,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
|||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
||||||
|
|
||||||
REQUIRE(4 == constraints.size());
|
REQUIRE(3 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("string <: a" == toString(*constraints[0], opts));
|
CHECK("string <: a" == toString(*constraints[0], opts));
|
||||||
CHECK("*blocked-1* ~ inst a" == toString(*constraints[1], opts));
|
CHECK("call a with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
|
||||||
CHECK("(string) -> (b...) <: *blocked-1*" == toString(*constraints[2], opts));
|
CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
|
||||||
CHECK("b... <: c" == toString(*constraints[3], opts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
||||||
@ -114,13 +112,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
|
|||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
auto constraints = collectConstraints(NotNull(cgb.rootScope));
|
||||||
|
|
||||||
REQUIRE(4 == constraints.size());
|
REQUIRE(3 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
|
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
|
||||||
CHECK("*blocked-2* ~ inst (a) -> (b...)" == toString(*constraints[1], opts));
|
CHECK("call (a) -> (b...) with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
|
||||||
CHECK("(a) -> (c...) <: *blocked-2*" == toString(*constraints[2], opts));
|
CHECK("*blocked-tp-1* <: b..." == toString(*constraints[2], opts));
|
||||||
CHECK("c... <: b..." == toString(*constraints[3], opts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -28,7 +28,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
|
|||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
|
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
|
||||||
|
|
||||||
ConstraintSolver cs{&arena, rootScope};
|
NullModuleResolver resolver;
|
||||||
|
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
|
||||||
|
|
||||||
cs.run();
|
cs.run();
|
||||||
|
|
||||||
@ -48,7 +49,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
|||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
|
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
|
||||||
|
|
||||||
ConstraintSolver cs{&arena, rootScope};
|
NullModuleResolver resolver;
|
||||||
|
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
|
||||||
|
|
||||||
cs.run();
|
cs.run();
|
||||||
|
|
||||||
@ -57,7 +59,6 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
|||||||
CHECK("<a>(a) -> a" == toString(idType));
|
CHECK("<a>(a) -> a" == toString(idType));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 1
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
||||||
{
|
{
|
||||||
AstStatBlock* block = parse(R"(
|
AstStatBlock* block = parse(R"(
|
||||||
@ -77,7 +78,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
|||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
|
|
||||||
ConstraintSolver cs{&arena, rootScope};
|
NullModuleResolver resolver;
|
||||||
|
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
|
||||||
|
|
||||||
cs.run();
|
cs.run();
|
||||||
|
|
||||||
@ -85,6 +87,5 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
|||||||
|
|
||||||
CHECK("<a>(a) -> number" == toString(idType, opts));
|
CHECK("<a>(a) -> number" == toString(idType, opts));
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
|
||||||
#include "Luau/AstQuery.h"
|
#include "Luau/AstQuery.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/TypeAttach.h"
|
#include "Luau/TypeAttach.h"
|
||||||
@ -444,10 +446,11 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
|||||||
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||||
: Fixture()
|
: Fixture()
|
||||||
, mainModule(new Module)
|
, mainModule(new Module)
|
||||||
, cgb(mainModuleName, mainModule, &arena, NotNull(&ice), frontend.getGlobalScope())
|
, cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), NotNull(&ice), frontend.getGlobalScope())
|
||||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||||
{
|
{
|
||||||
BlockedTypeVar::nextIndex = 0;
|
BlockedTypeVar::nextIndex = 0;
|
||||||
|
BlockedTypePack::nextIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleName fromString(std::string_view name)
|
ModuleName fromString(std::string_view name)
|
||||||
|
@ -132,6 +132,7 @@ struct Fixture
|
|||||||
|
|
||||||
TestFileResolver fileResolver;
|
TestFileResolver fileResolver;
|
||||||
TestConfigResolver configResolver;
|
TestConfigResolver configResolver;
|
||||||
|
NullModuleResolver moduleResolver;
|
||||||
std::unique_ptr<SourceModule> sourceModule;
|
std::unique_ptr<SourceModule> sourceModule;
|
||||||
Frontend frontend;
|
Frontend frontend;
|
||||||
InternalErrorReporter ice;
|
InternalErrorReporter ice;
|
||||||
@ -244,7 +245,7 @@ struct FindNthOccurenceOf : public AstVisitor
|
|||||||
* 2. Luau::query<AstExprBinary>(Luau::query<AstStatFunction>(block))
|
* 2. Luau::query<AstExprBinary>(Luau::query<AstStatFunction>(block))
|
||||||
* 3. Luau::query<AstExprBinary>(block, {nth<AstExprBinary>(2)})
|
* 3. Luau::query<AstExprBinary>(block, {nth<AstExprBinary>(2)})
|
||||||
*/
|
*/
|
||||||
template<typename T, size_t N = 1>
|
template<typename T, int N = 1>
|
||||||
T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
|
T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
|
||||||
{
|
{
|
||||||
static_assert(std::is_base_of_v<AstNode, T>, "T must be a derived class of AstNode");
|
static_assert(std::is_base_of_v<AstNode, T>, "T must be a derived class of AstNode");
|
||||||
|
@ -94,7 +94,7 @@ TEST_CASE("write_optional")
|
|||||||
write(emitter, std::optional<bool>{true});
|
write(emitter, std::optional<bool>{true});
|
||||||
emitter.writeComma();
|
emitter.writeComma();
|
||||||
write(emitter, std::nullopt);
|
write(emitter, std::nullopt);
|
||||||
|
|
||||||
CHECK(emitter.str() == "true,null");
|
CHECK(emitter.str() == "true,null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1181,7 +1181,7 @@ s:match("[]")
|
|||||||
nons:match("[]")
|
nons:match("[]")
|
||||||
)~");
|
)~");
|
||||||
|
|
||||||
CHECK_EQ(result.warnings.size(), 2);
|
REQUIRE_EQ(result.warnings.size(), 2);
|
||||||
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
||||||
CHECK_EQ(result.warnings[0].location.begin.line, 3);
|
CHECK_EQ(result.warnings[0].location.begin.line, 3);
|
||||||
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
|
||||||
@ -1746,6 +1746,7 @@ local _ = not a == b
|
|||||||
local _ = not a ~= b
|
local _ = not a ~= b
|
||||||
local _ = not a <= b
|
local _ = not a <= b
|
||||||
local _ = a <= b == 0
|
local _ = a <= b == 0
|
||||||
|
local _ = a <= b <= 0
|
||||||
|
|
||||||
local _ = not a == not b -- weird but ok
|
local _ = not a == not b -- weird but ok
|
||||||
|
|
||||||
@ -1760,11 +1761,12 @@ local _ = (a <= b) == 0
|
|||||||
local _ = a <= (b == 0)
|
local _ = a <= (b == 0)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
REQUIRE_EQ(result.warnings.size(), 4);
|
REQUIRE_EQ(result.warnings.size(), 5);
|
||||||
CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence");
|
CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or add parentheses to silence");
|
||||||
CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or wrap one of the expressions in parentheses to silence");
|
CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or add parentheses to silence");
|
||||||
CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; wrap one of the expressions in parentheses to silence");
|
CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; add parentheses to silence");
|
||||||
CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; wrap one of the expressions in parentheses to silence");
|
CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; add parentheses to silence");
|
||||||
|
CHECK_EQ(result.warnings[4].text, "X <= Y <= Z is equivalent to (X <= Y) <= Z; did you mean X <= Y and Y <= Z?");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -943,8 +943,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
|
|||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
|
||||||
|
|
||||||
auto columnOfEndBraceError = [this](const char* code)
|
auto columnOfEndBraceError = [this](const char* code) {
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
parse(code);
|
parse(code);
|
||||||
@ -1737,6 +1736,48 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation")
|
|||||||
matchParseError("local a : 2 = 2", "Expected type, got '2'");
|
matchParseError("local a : 2 = 2", "Expected type, got '2'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "parse_error_missing_type_annotation")
|
||||||
|
{
|
||||||
|
ScopedFastFlag LuauTypeAnnotationLocationChange{"LuauTypeAnnotationLocationChange", true};
|
||||||
|
|
||||||
|
{
|
||||||
|
ParseResult result = tryParse("local x:");
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
Position begin = result.errors[0].getLocation().begin;
|
||||||
|
Position end = result.errors[0].getLocation().end;
|
||||||
|
CHECK(begin.line == end.line);
|
||||||
|
int width = end.column - begin.column;
|
||||||
|
CHECK(width == 0);
|
||||||
|
CHECK(result.errors[0].getMessage() == "Expected type, got <eof>");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ParseResult result = tryParse(R"(
|
||||||
|
local x:=42
|
||||||
|
)");
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
Position begin = result.errors[0].getLocation().begin;
|
||||||
|
Position end = result.errors[0].getLocation().end;
|
||||||
|
CHECK(begin.line == end.line);
|
||||||
|
int width = end.column - begin.column;
|
||||||
|
CHECK(width == 1); // Length of `=`
|
||||||
|
CHECK(result.errors[0].getMessage() == "Expected type, got '='");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ParseResult result = tryParse(R"(
|
||||||
|
function func():end
|
||||||
|
)");
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
Position begin = result.errors[0].getLocation().begin;
|
||||||
|
Position end = result.errors[0].getLocation().end;
|
||||||
|
CHECK(begin.line == end.line);
|
||||||
|
int width = end.column - begin.column;
|
||||||
|
CHECK(width == 3); // Length of `end`
|
||||||
|
CHECK(result.errors[0].getMessage() == "Expected type, got 'end'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "parse_declarations")
|
TEST_CASE_FIXTURE(Fixture, "parse_declarations")
|
||||||
{
|
{
|
||||||
AstStatBlock* stat = parseEx(R"(
|
AstStatBlock* stat = parseEx(R"(
|
||||||
|
@ -346,4 +346,17 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_any_with_other_options")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "union_of_types_regression_test")
|
||||||
|
{
|
||||||
|
ScopedFastFlag LuauUnionOfTypesFollow{"LuauUnionOfTypesFollow", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
local stat
|
||||||
|
stat = stat and tonumber(stat) or stat
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -1061,7 +1061,6 @@ end
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
|
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
|
||||||
)END");
|
)END");
|
||||||
@ -1075,7 +1074,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
|
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
|
||||||
)END");
|
)END");
|
||||||
@ -1089,7 +1087,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
|
local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
|
||||||
)END");
|
)END");
|
||||||
@ -1107,7 +1104,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
|
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
|
||||||
)END");
|
)END");
|
||||||
@ -1127,7 +1123,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
|
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
|
||||||
)END");
|
)END");
|
||||||
@ -1146,7 +1141,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b = string.gmatch("[[[", "()([[])")()
|
local a, b = string.gmatch("[[[", "()([[])")()
|
||||||
)END");
|
)END");
|
||||||
@ -1196,7 +1190,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallbac
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c = string.match("This is a string", "(.()(%a+))")
|
local a, b, c = string.match("This is a string", "(.()(%a+))")
|
||||||
)END");
|
)END");
|
||||||
@ -1210,7 +1203,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number")
|
local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number")
|
||||||
)END");
|
)END");
|
||||||
@ -1229,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))")
|
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))")
|
||||||
)END");
|
)END");
|
||||||
@ -1245,7 +1236,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number")
|
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number")
|
||||||
)END");
|
)END");
|
||||||
@ -1266,7 +1256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool")
|
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool")
|
||||||
)END");
|
)END");
|
||||||
@ -1287,7 +1276,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
|
|
||||||
CheckResult result = check(R"END(
|
CheckResult result = check(R"END(
|
||||||
local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true)
|
local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true)
|
||||||
)END");
|
)END");
|
||||||
|
@ -133,6 +133,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
|
|||||||
TableTypeVar* ttv = getMutable<TableTypeVar>(*r);
|
TableTypeVar* ttv = getMutable<TableTypeVar>(*r);
|
||||||
REQUIRE(ttv);
|
REQUIRE(ttv);
|
||||||
|
|
||||||
|
REQUIRE(ttv->props.count("f"));
|
||||||
TypeId k = ttv->props["f"].type;
|
TypeId k = ttv->props["f"].type;
|
||||||
REQUIRE(k);
|
REQUIRE(k);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferLoops");
|
TEST_SUITE_BEGIN("TypeInferLoops");
|
||||||
|
|
||||||
@ -109,6 +110,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators_dcr")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function no_iter() end
|
||||||
|
for key in no_iter() do end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -141,7 +154,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
|
|||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
CHECK_EQ(2, result.errors.size());
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
|
||||||
TypeId p = requireType("p");
|
TypeId p = requireType("p");
|
||||||
if (FFlag::LuauSpecialTypesAsterisked)
|
if (FFlag::LuauSpecialTypesAsterisked)
|
||||||
@ -232,6 +245,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args
|
|||||||
CHECK_EQ(0, acm->actual);
|
CHECK_EQ(0, acm->actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_incompatible_args_to_iterator")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function my_iter(state: string, index: number)
|
||||||
|
return state, index
|
||||||
|
end
|
||||||
|
|
||||||
|
local my_state = {}
|
||||||
|
local first_index = "first"
|
||||||
|
|
||||||
|
-- Type errors here. my_state and first_index cannot be passed to my_iter
|
||||||
|
for a, b in my_iter, my_state, first_index do
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
|
||||||
|
CHECK(get<TypeMismatch>(result.errors[1]));
|
||||||
|
CHECK(Location{{9, 29}, {9, 37}} == result.errors[0].location);
|
||||||
|
|
||||||
|
CHECK(get<TypeMismatch>(result.errors[1]));
|
||||||
|
CHECK(Location{{9, 39}, {9, 50}} == result.errors[1].location);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator")
|
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -503,7 +540,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
|
|||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ(*typeChecker.numberType, *requireType("key"));
|
CHECK_EQ(*typeChecker.numberType, *requireType("key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,35 @@ LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
|||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferModules");
|
TEST_SUITE_BEGIN("TypeInferModules");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_require_basic")
|
||||||
|
{
|
||||||
|
fileResolver.source["game/A"] = R"(
|
||||||
|
--!strict
|
||||||
|
return {
|
||||||
|
a = 1,
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
fileResolver.source["game/B"] = R"(
|
||||||
|
--!strict
|
||||||
|
local A = require(game.A)
|
||||||
|
|
||||||
|
local b = A.a
|
||||||
|
)";
|
||||||
|
|
||||||
|
CheckResult aResult = frontend.check("game/A");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(aResult);
|
||||||
|
|
||||||
|
CheckResult bResult = frontend.check("game/B");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(bResult);
|
||||||
|
|
||||||
|
ModulePtr b = frontend.moduleResolver.modules["game/B"];
|
||||||
|
REQUIRE(b != nullptr);
|
||||||
|
std::optional<TypeId> bType = requireType(b, "b");
|
||||||
|
REQUIRE(bType);
|
||||||
|
CHECK(toString(*bType) == "number");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "require")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "require")
|
||||||
{
|
{
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes)
|
|
||||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
@ -61,8 +60,8 @@ TEST_CASE_FIXTURE(Fixture, "string_method")
|
|||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local p = ("tacos"):len()
|
local p = ("tacos"):len()
|
||||||
)");
|
)");
|
||||||
CHECK_EQ(0, result.errors.size());
|
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ(*requireType("p"), *typeChecker.numberType);
|
CHECK_EQ(*requireType("p"), *typeChecker.numberType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +72,8 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
|
|||||||
local l = s.lower
|
local l = s.lower
|
||||||
local p = l(s)
|
local p = l(s)
|
||||||
)");
|
)");
|
||||||
CHECK_EQ(0, result.errors.size());
|
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ(*requireType("p"), *typeChecker.stringType);
|
CHECK_EQ(*requireType("p"), *typeChecker.stringType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,12 +83,9 @@ TEST_CASE_FIXTURE(Fixture, "string_function_other")
|
|||||||
local s:string
|
local s:string
|
||||||
local p = s:match("foo")
|
local p = s:match("foo")
|
||||||
)");
|
)");
|
||||||
CHECK_EQ(0, result.errors.size());
|
|
||||||
|
|
||||||
if (FFlag::LuauDeduceFindMatchReturnTypes)
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
CHECK_EQ(toString(requireType("p")), "string");
|
CHECK_EQ(toString(requireType("p")), "string");
|
||||||
else
|
|
||||||
CHECK_EQ(toString(requireType("p")), "string?");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
|
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
|
||||||
|
@ -62,7 +62,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_returns_what_f_returns")
|
|||||||
local a:boolean,b:number,c:string=xpcall(function(): (number,string)return 1,'foo'end,function(): (string,number)return'foo',1 end)
|
local a:boolean,b:number,c:string=xpcall(function(): (number,string)return 1,'foo'end,function(): (string,number)return'foo',1 end)
|
||||||
)";
|
)";
|
||||||
|
|
||||||
CHECK_EQ(expected, decorateWithTypes(code));
|
CheckResult result = check(code);
|
||||||
|
|
||||||
|
CHECK("boolean" == toString(requireType("a")));
|
||||||
|
CHECK("number" == toString(requireType("b")));
|
||||||
|
CHECK("string" == toString(requireType("c")));
|
||||||
|
|
||||||
|
CHECK(expected == decorateWithTypes(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We had a bug where if you have two type packs that looks like:
|
// We had a bug where if you have two type packs that looks like:
|
||||||
@ -609,4 +615,16 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|||||||
CHECK("b?" == toString(option2, opts)); // This should not hold.
|
CHECK("b?" == toString(option2, opts)); // This should not hold.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function no_iter() end
|
||||||
|
for key in no_iter() do end -- This should not be ok
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -2994,8 +2994,6 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union")
|
TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExpectedTableUnionIndexerType{"LuauExpectedTableUnionIndexerType", true};
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}} = {a = {2, 's'}})"));
|
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}} = {a = {2, 's'}})"));
|
||||||
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}}? = {a = {2, 's'}})"));
|
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}}? = {a = {2, 's'}})"));
|
||||||
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})"));
|
LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})"));
|
||||||
|
@ -111,6 +111,11 @@ assert((function() local a = nil a = a and 2 return a end)() == nil)
|
|||||||
assert((function() local a = 1 a = a or 2 return a end)() == 1)
|
assert((function() local a = 1 a = a or 2 return a end)() == 1)
|
||||||
assert((function() local a = nil a = a or 2 return a end)() == 2)
|
assert((function() local a = nil a = a or 2 return a end)() == 2)
|
||||||
|
|
||||||
|
assert((function() local a a = 1 local b = 2 b = a and b return b end)() == 2)
|
||||||
|
assert((function() local a a = nil local b = 2 b = a and b return b end)() == nil)
|
||||||
|
assert((function() local a a = 1 local b = 2 b = a or b return b end)() == 1)
|
||||||
|
assert((function() local a a = nil local b = 2 b = a or b return b end)() == 2)
|
||||||
|
|
||||||
-- binary arithmetics coerces strings to numbers (sadly)
|
-- binary arithmetics coerces strings to numbers (sadly)
|
||||||
assert(1 + "2" == 3)
|
assert(1 + "2" == 3)
|
||||||
assert(2 * "0xa" == 20)
|
assert(2 * "0xa" == 20)
|
||||||
|
@ -369,21 +369,31 @@ assert(not a and b:match('[^ ]+') == "short:1:")
|
|||||||
local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
|
local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
|
||||||
assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:")
|
assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:")
|
||||||
|
|
||||||
-- arith errors
|
|
||||||
function ecall(fn, ...)
|
function ecall(fn, ...)
|
||||||
local ok, err = pcall(fn, ...)
|
local ok, err = pcall(fn, ...)
|
||||||
assert(not ok)
|
assert(not ok)
|
||||||
return err:sub(err:find(": ") + 2, #err)
|
return err:sub((err:find(": ") or -1) + 2, #err)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- arith errors
|
||||||
assert(ecall(function() return nil + 5 end) == "attempt to perform arithmetic (add) on nil and number")
|
assert(ecall(function() return nil + 5 end) == "attempt to perform arithmetic (add) on nil and number")
|
||||||
assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic (add) on string")
|
assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic (add) on string")
|
||||||
assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design)
|
assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design)
|
||||||
assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number")
|
assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number")
|
||||||
|
|
||||||
|
-- table errors
|
||||||
assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil")
|
assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil")
|
||||||
assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN")
|
assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN")
|
||||||
|
|
||||||
|
assert(ecall(function() local t = {} rawset(t, nil, 2) end) == "table index is nil")
|
||||||
|
assert(ecall(function() local t = {} rawset(t, 0/0, 2) end) == "table index is NaN")
|
||||||
|
|
||||||
|
assert(ecall(function() local t = {} t[nil] = nil end) == "table index is nil")
|
||||||
|
assert(ecall(function() local t = {} t[0/0] = nil end) == "table index is NaN")
|
||||||
|
|
||||||
|
assert(ecall(function() local t = {} rawset(t, nil, nil) end) == "table index is nil")
|
||||||
|
assert(ecall(function() local t = {} rawset(t, 0/0, nil) end) == "table index is NaN")
|
||||||
|
|
||||||
-- for loop type errors
|
-- for loop type errors
|
||||||
assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)")
|
assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)")
|
||||||
assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)")
|
assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)")
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
AnnotationTests.builtin_types_are_not_exported
|
AnnotationTests.builtin_types_are_not_exported
|
||||||
AnnotationTests.cannot_use_nonexported_type
|
|
||||||
AnnotationTests.cloned_interface_maintains_pointers_between_definitions
|
|
||||||
AnnotationTests.duplicate_type_param_name
|
AnnotationTests.duplicate_type_param_name
|
||||||
AnnotationTests.for_loop_counter_annotation_is_checked
|
AnnotationTests.for_loop_counter_annotation_is_checked
|
||||||
AnnotationTests.generic_aliases_are_cloned_properly
|
AnnotationTests.generic_aliases_are_cloned_properly
|
||||||
AnnotationTests.interface_types_belong_to_interface_arena
|
AnnotationTests.instantiation_clone_has_to_follow
|
||||||
AnnotationTests.luau_ice_triggers_an_ice
|
AnnotationTests.luau_ice_triggers_an_ice
|
||||||
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag
|
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag
|
||||||
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler
|
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler
|
||||||
@ -26,6 +24,7 @@ AutocompleteTest.autocomplete_first_function_arg_expected_type
|
|||||||
AutocompleteTest.autocomplete_for_in_middle_keywords
|
AutocompleteTest.autocomplete_for_in_middle_keywords
|
||||||
AutocompleteTest.autocomplete_for_middle_keywords
|
AutocompleteTest.autocomplete_for_middle_keywords
|
||||||
AutocompleteTest.autocomplete_if_middle_keywords
|
AutocompleteTest.autocomplete_if_middle_keywords
|
||||||
|
AutocompleteTest.autocomplete_interpolated_string
|
||||||
AutocompleteTest.autocomplete_on_string_singletons
|
AutocompleteTest.autocomplete_on_string_singletons
|
||||||
AutocompleteTest.autocomplete_oop_implicit_self
|
AutocompleteTest.autocomplete_oop_implicit_self
|
||||||
AutocompleteTest.autocomplete_repeat_middle_keyword
|
AutocompleteTest.autocomplete_repeat_middle_keyword
|
||||||
@ -56,14 +55,12 @@ AutocompleteTest.global_functions_are_not_scoped_lexically
|
|||||||
AutocompleteTest.globals_are_order_independent
|
AutocompleteTest.globals_are_order_independent
|
||||||
AutocompleteTest.if_then_else_elseif_completions
|
AutocompleteTest.if_then_else_elseif_completions
|
||||||
AutocompleteTest.keyword_methods
|
AutocompleteTest.keyword_methods
|
||||||
AutocompleteTest.keyword_types
|
|
||||||
AutocompleteTest.library_non_self_calls_are_fine
|
AutocompleteTest.library_non_self_calls_are_fine
|
||||||
AutocompleteTest.library_self_calls_are_invalid
|
AutocompleteTest.library_self_calls_are_invalid
|
||||||
AutocompleteTest.local_function
|
AutocompleteTest.local_function
|
||||||
AutocompleteTest.local_function_params
|
AutocompleteTest.local_function_params
|
||||||
AutocompleteTest.local_functions_fall_out_of_scope
|
AutocompleteTest.local_functions_fall_out_of_scope
|
||||||
AutocompleteTest.method_call_inside_function_body
|
AutocompleteTest.method_call_inside_function_body
|
||||||
AutocompleteTest.module_type_members
|
|
||||||
AutocompleteTest.nested_member_completions
|
AutocompleteTest.nested_member_completions
|
||||||
AutocompleteTest.nested_recursive_function
|
AutocompleteTest.nested_recursive_function
|
||||||
AutocompleteTest.no_function_name_suggestions
|
AutocompleteTest.no_function_name_suggestions
|
||||||
@ -78,7 +75,6 @@ AutocompleteTest.return_types
|
|||||||
AutocompleteTest.sometimes_the_metatable_is_an_error
|
AutocompleteTest.sometimes_the_metatable_is_an_error
|
||||||
AutocompleteTest.source_module_preservation_and_invalidation
|
AutocompleteTest.source_module_preservation_and_invalidation
|
||||||
AutocompleteTest.statement_between_two_statements
|
AutocompleteTest.statement_between_two_statements
|
||||||
AutocompleteTest.stop_at_first_stat_when_recommending_keywords
|
|
||||||
AutocompleteTest.string_prim_non_self_calls_are_avoided
|
AutocompleteTest.string_prim_non_self_calls_are_avoided
|
||||||
AutocompleteTest.string_prim_self_calls_are_fine
|
AutocompleteTest.string_prim_self_calls_are_fine
|
||||||
AutocompleteTest.suggest_external_module_type
|
AutocompleteTest.suggest_external_module_type
|
||||||
@ -155,6 +151,7 @@ BuiltinTests.string_format_tostring_specifier
|
|||||||
BuiltinTests.string_format_tostring_specifier_type_constraint
|
BuiltinTests.string_format_tostring_specifier_type_constraint
|
||||||
BuiltinTests.string_format_use_correct_argument
|
BuiltinTests.string_format_use_correct_argument
|
||||||
BuiltinTests.string_format_use_correct_argument2
|
BuiltinTests.string_format_use_correct_argument2
|
||||||
|
BuiltinTests.string_format_use_correct_argument3
|
||||||
BuiltinTests.string_lib_self_noself
|
BuiltinTests.string_lib_self_noself
|
||||||
BuiltinTests.table_concat_returns_string
|
BuiltinTests.table_concat_returns_string
|
||||||
BuiltinTests.table_dot_remove_optionally_returns_generic
|
BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||||
@ -168,31 +165,21 @@ BuiltinTests.tonumber_returns_optional_number_type
|
|||||||
BuiltinTests.tonumber_returns_optional_number_type2
|
BuiltinTests.tonumber_returns_optional_number_type2
|
||||||
DefinitionTests.declaring_generic_functions
|
DefinitionTests.declaring_generic_functions
|
||||||
DefinitionTests.definition_file_classes
|
DefinitionTests.definition_file_classes
|
||||||
DefinitionTests.definition_file_loading
|
|
||||||
DefinitionTests.definitions_documentation_symbols
|
|
||||||
DefinitionTests.documentation_symbols_dont_attach_to_persistent_types
|
|
||||||
DefinitionTests.single_class_type_identity_in_global_types
|
|
||||||
FrontendTest.ast_node_at_position
|
FrontendTest.ast_node_at_position
|
||||||
FrontendTest.automatically_check_dependent_scripts
|
FrontendTest.automatically_check_dependent_scripts
|
||||||
FrontendTest.check_without_builtin_next
|
|
||||||
FrontendTest.dont_reparse_clean_file_when_linting
|
FrontendTest.dont_reparse_clean_file_when_linting
|
||||||
FrontendTest.environments
|
FrontendTest.environments
|
||||||
FrontendTest.imported_table_modification_2
|
FrontendTest.imported_table_modification_2
|
||||||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||||
FrontendTest.no_use_after_free_with_type_fun_instantiation
|
|
||||||
FrontendTest.nocheck_cycle_used_by_checked
|
FrontendTest.nocheck_cycle_used_by_checked
|
||||||
FrontendTest.nocheck_modules_are_typed
|
|
||||||
FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error
|
FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error
|
||||||
FrontendTest.recheck_if_dependent_script_is_dirty
|
FrontendTest.recheck_if_dependent_script_is_dirty
|
||||||
FrontendTest.reexport_cyclic_type
|
FrontendTest.reexport_cyclic_type
|
||||||
FrontendTest.reexport_type_alias
|
|
||||||
FrontendTest.report_require_to_nonexistent_file
|
|
||||||
FrontendTest.report_syntax_error_in_required_file
|
FrontendTest.report_syntax_error_in_required_file
|
||||||
FrontendTest.trace_requires_in_nonstrict_mode
|
FrontendTest.trace_requires_in_nonstrict_mode
|
||||||
GenericsTests.apply_type_function_nested_generics1
|
GenericsTests.apply_type_function_nested_generics1
|
||||||
GenericsTests.apply_type_function_nested_generics2
|
GenericsTests.apply_type_function_nested_generics2
|
||||||
GenericsTests.better_mismatch_error_messages
|
GenericsTests.better_mismatch_error_messages
|
||||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
|
||||||
GenericsTests.calling_self_generic_methods
|
GenericsTests.calling_self_generic_methods
|
||||||
GenericsTests.check_generic_typepack_function
|
GenericsTests.check_generic_typepack_function
|
||||||
GenericsTests.check_mutual_generic_functions
|
GenericsTests.check_mutual_generic_functions
|
||||||
@ -208,7 +195,6 @@ GenericsTests.factories_of_generics
|
|||||||
GenericsTests.generic_argument_count_too_few
|
GenericsTests.generic_argument_count_too_few
|
||||||
GenericsTests.generic_argument_count_too_many
|
GenericsTests.generic_argument_count_too_many
|
||||||
GenericsTests.generic_factories
|
GenericsTests.generic_factories
|
||||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
|
||||||
GenericsTests.generic_functions_in_types
|
GenericsTests.generic_functions_in_types
|
||||||
GenericsTests.generic_functions_should_be_memory_safe
|
GenericsTests.generic_functions_should_be_memory_safe
|
||||||
GenericsTests.generic_table_method
|
GenericsTests.generic_table_method
|
||||||
@ -245,7 +231,6 @@ IntersectionTypes.no_stack_overflow_from_flattenintersection
|
|||||||
IntersectionTypes.overload_is_not_a_function
|
IntersectionTypes.overload_is_not_a_function
|
||||||
IntersectionTypes.select_correct_union_fn
|
IntersectionTypes.select_correct_union_fn
|
||||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
||||||
IntersectionTypes.table_intersection_write
|
|
||||||
IntersectionTypes.table_intersection_write_sealed
|
IntersectionTypes.table_intersection_write_sealed
|
||||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
@ -255,7 +240,6 @@ Linter.TableOperations
|
|||||||
ModuleTests.clone_self_property
|
ModuleTests.clone_self_property
|
||||||
ModuleTests.deepClone_cyclic_table
|
ModuleTests.deepClone_cyclic_table
|
||||||
ModuleTests.do_not_clone_reexports
|
ModuleTests.do_not_clone_reexports
|
||||||
ModuleTests.do_not_clone_types_of_reexported_values
|
|
||||||
NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything
|
NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything
|
||||||
NonstrictModeTests.for_in_iterator_variables_are_any
|
NonstrictModeTests.for_in_iterator_variables_are_any
|
||||||
NonstrictModeTests.function_parameters_are_any
|
NonstrictModeTests.function_parameters_are_any
|
||||||
@ -319,6 +303,7 @@ ProvisionalTests.typeguard_inference_incomplete
|
|||||||
ProvisionalTests.weird_fail_to_unify_type_pack
|
ProvisionalTests.weird_fail_to_unify_type_pack
|
||||||
ProvisionalTests.weirditer_should_not_loop_forever
|
ProvisionalTests.weirditer_should_not_loop_forever
|
||||||
ProvisionalTests.while_body_are_also_refined
|
ProvisionalTests.while_body_are_also_refined
|
||||||
|
ProvisionalTests.xpcall_returns_what_f_returns
|
||||||
RefinementTest.and_constraint
|
RefinementTest.and_constraint
|
||||||
RefinementTest.and_or_peephole_refinement
|
RefinementTest.and_or_peephole_refinement
|
||||||
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||||
@ -358,6 +343,7 @@ RefinementTest.not_and_constraint
|
|||||||
RefinementTest.not_t_or_some_prop_of_t
|
RefinementTest.not_t_or_some_prop_of_t
|
||||||
RefinementTest.or_predicate_with_truthy_predicates
|
RefinementTest.or_predicate_with_truthy_predicates
|
||||||
RefinementTest.parenthesized_expressions_are_followed_through
|
RefinementTest.parenthesized_expressions_are_followed_through
|
||||||
|
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
|
||||||
RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string
|
RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string
|
||||||
RefinementTest.refine_unknowns
|
RefinementTest.refine_unknowns
|
||||||
RefinementTest.string_not_equal_to_string_or_nil
|
RefinementTest.string_not_equal_to_string_or_nil
|
||||||
@ -394,7 +380,6 @@ TableTests.augment_table
|
|||||||
TableTests.builtin_table_names
|
TableTests.builtin_table_names
|
||||||
TableTests.call_method
|
TableTests.call_method
|
||||||
TableTests.cannot_augment_sealed_table
|
TableTests.cannot_augment_sealed_table
|
||||||
TableTests.cannot_call_tables
|
|
||||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||||
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||||
@ -409,7 +394,6 @@ TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail
|
|||||||
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
||||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||||
TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back
|
|
||||||
TableTests.dont_leak_free_table_props
|
TableTests.dont_leak_free_table_props
|
||||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||||
TableTests.dont_suggest_exact_match_keys
|
TableTests.dont_suggest_exact_match_keys
|
||||||
@ -448,6 +432,7 @@ TableTests.length_operator_intersection
|
|||||||
TableTests.length_operator_non_table_union
|
TableTests.length_operator_non_table_union
|
||||||
TableTests.length_operator_union
|
TableTests.length_operator_union
|
||||||
TableTests.length_operator_union_errors
|
TableTests.length_operator_union_errors
|
||||||
|
TableTests.less_exponential_blowup_please
|
||||||
TableTests.meta_add
|
TableTests.meta_add
|
||||||
TableTests.meta_add_both_ways
|
TableTests.meta_add_both_ways
|
||||||
TableTests.meta_add_inferred
|
TableTests.meta_add_inferred
|
||||||
@ -470,7 +455,6 @@ TableTests.quantify_even_that_table_was_never_exported_at_all
|
|||||||
TableTests.quantify_metatables_of_metatables_of_table
|
TableTests.quantify_metatables_of_metatables_of_table
|
||||||
TableTests.quantifying_a_bound_var_works
|
TableTests.quantifying_a_bound_var_works
|
||||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||||
TableTests.recursive_metatable_type_call
|
|
||||||
TableTests.result_is_always_any_if_lhs_is_any
|
TableTests.result_is_always_any_if_lhs_is_any
|
||||||
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
|
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
|
||||||
TableTests.right_table_missing_key
|
TableTests.right_table_missing_key
|
||||||
@ -480,7 +464,6 @@ TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
|||||||
TableTests.shared_selfs
|
TableTests.shared_selfs
|
||||||
TableTests.shared_selfs_from_free_param
|
TableTests.shared_selfs_from_free_param
|
||||||
TableTests.shared_selfs_through_metatables
|
TableTests.shared_selfs_through_metatables
|
||||||
TableTests.table_function_check_use_after_free
|
|
||||||
TableTests.table_indexing_error_location
|
TableTests.table_indexing_error_location
|
||||||
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
|
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
|
||||||
TableTests.table_insert_should_cope_with_optional_properties_in_strict
|
TableTests.table_insert_should_cope_with_optional_properties_in_strict
|
||||||
@ -525,13 +508,10 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
|||||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||||
TryUnifyTests.variadics_should_use_reversed_properly
|
TryUnifyTests.variadics_should_use_reversed_properly
|
||||||
TypeAliases.cli_38393_recursive_intersection_oom
|
TypeAliases.cli_38393_recursive_intersection_oom
|
||||||
TypeAliases.corecursive_types_generic
|
|
||||||
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
|
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
|
||||||
TypeAliases.general_require_multi_assign
|
|
||||||
TypeAliases.generic_param_remap
|
TypeAliases.generic_param_remap
|
||||||
TypeAliases.mismatched_generic_pack_type_param
|
TypeAliases.mismatched_generic_pack_type_param
|
||||||
TypeAliases.mismatched_generic_type_param
|
TypeAliases.mismatched_generic_type_param
|
||||||
TypeAliases.mutually_recursive_types_errors
|
|
||||||
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
||||||
TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
||||||
TypeAliases.mutually_recursive_types_swapsies_not_ok
|
TypeAliases.mutually_recursive_types_swapsies_not_ok
|
||||||
@ -543,7 +523,6 @@ TypeAliases.type_alias_fwd_declaration_is_precise
|
|||||||
TypeAliases.type_alias_local_mutation
|
TypeAliases.type_alias_local_mutation
|
||||||
TypeAliases.type_alias_local_rename
|
TypeAliases.type_alias_local_rename
|
||||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||||
TypeAliases.type_alias_of_an_imported_recursive_type
|
|
||||||
TypeInfer.checking_should_not_ice
|
TypeInfer.checking_should_not_ice
|
||||||
TypeInfer.cyclic_follow
|
TypeInfer.cyclic_follow
|
||||||
TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table
|
TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table
|
||||||
@ -559,20 +538,21 @@ TypeInfer.tc_if_else_expressions_expected_type_1
|
|||||||
TypeInfer.tc_if_else_expressions_expected_type_2
|
TypeInfer.tc_if_else_expressions_expected_type_2
|
||||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||||
TypeInfer.tc_if_else_expressions_type_union
|
TypeInfer.tc_if_else_expressions_type_union
|
||||||
|
TypeInfer.tc_interpolated_string_basic
|
||||||
|
TypeInfer.tc_interpolated_string_constant_type
|
||||||
|
TypeInfer.tc_interpolated_string_with_invalid_expression
|
||||||
TypeInfer.type_infer_recursion_limit_no_ice
|
TypeInfer.type_infer_recursion_limit_no_ice
|
||||||
TypeInfer.warn_on_lowercase_parent_property
|
|
||||||
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
|
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
|
||||||
TypeInferAnyError.can_get_length_of_any
|
TypeInferAnyError.can_get_length_of_any
|
||||||
TypeInferAnyError.for_in_loop_iterator_is_any
|
|
||||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||||
TypeInferAnyError.for_in_loop_iterator_is_error
|
|
||||||
TypeInferAnyError.for_in_loop_iterator_is_error2
|
|
||||||
TypeInferAnyError.for_in_loop_iterator_returns_any
|
TypeInferAnyError.for_in_loop_iterator_returns_any
|
||||||
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
|
||||||
TypeInferAnyError.length_of_error_type_does_not_produce_an_error
|
TypeInferAnyError.length_of_error_type_does_not_produce_an_error
|
||||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||||
|
TypeInferAnyError.union_of_types_regression_test
|
||||||
TypeInferClasses.call_base_method
|
TypeInferClasses.call_base_method
|
||||||
TypeInferClasses.call_instance_method
|
TypeInferClasses.call_instance_method
|
||||||
|
TypeInferClasses.can_assign_to_prop_of_base_class_using_string
|
||||||
|
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||||
TypeInferClasses.classes_can_have_overloaded_operators
|
TypeInferClasses.classes_can_have_overloaded_operators
|
||||||
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
|
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
|
||||||
@ -582,10 +562,13 @@ TypeInferClasses.higher_order_function_return_type_is_not_contravariant
|
|||||||
TypeInferClasses.higher_order_function_return_values_are_covariant
|
TypeInferClasses.higher_order_function_return_values_are_covariant
|
||||||
TypeInferClasses.optional_class_field_access_error
|
TypeInferClasses.optional_class_field_access_error
|
||||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||||
|
TypeInferClasses.table_indexers_are_invariant
|
||||||
|
TypeInferClasses.table_properties_are_invariant
|
||||||
TypeInferClasses.warn_when_prop_almost_matches
|
TypeInferClasses.warn_when_prop_almost_matches
|
||||||
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||||
TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments
|
TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments
|
||||||
TypeInferFunctions.another_recursive_local_function
|
TypeInferFunctions.another_recursive_local_function
|
||||||
|
TypeInferFunctions.call_o_with_another_argument_after_foo_was_quantified
|
||||||
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
||||||
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
|
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
|
||||||
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
|
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
|
||||||
@ -634,43 +617,23 @@ TypeInferFunctions.too_many_arguments
|
|||||||
TypeInferFunctions.too_many_return_values
|
TypeInferFunctions.too_many_return_values
|
||||||
TypeInferFunctions.vararg_function_is_quantified
|
TypeInferFunctions.vararg_function_is_quantified
|
||||||
TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size
|
TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size
|
||||||
TypeInferLoops.for_in_loop
|
|
||||||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||||
TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given
|
|
||||||
TypeInferLoops.for_in_loop_on_error
|
|
||||||
TypeInferLoops.for_in_loop_on_non_function
|
|
||||||
TypeInferLoops.for_in_loop_should_fail_with_non_function_iterator
|
|
||||||
TypeInferLoops.for_in_loop_where_iteratee_is_free
|
|
||||||
TypeInferLoops.for_in_loop_with_custom_iterator
|
TypeInferLoops.for_in_loop_with_custom_iterator
|
||||||
TypeInferLoops.for_in_loop_with_next
|
TypeInferLoops.for_in_loop_with_next
|
||||||
TypeInferLoops.for_in_with_a_custom_iterator_should_type_check
|
|
||||||
TypeInferLoops.for_in_with_an_iterator_of_type_any
|
|
||||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||||
TypeInferLoops.fuzz_fail_missing_instantitation_follow
|
|
||||||
TypeInferLoops.ipairs_produces_integral_indices
|
|
||||||
TypeInferLoops.loop_iter_basic
|
|
||||||
TypeInferLoops.loop_iter_iter_metamethod
|
TypeInferLoops.loop_iter_iter_metamethod
|
||||||
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
||||||
TypeInferLoops.loop_iter_no_indexer_strict
|
|
||||||
TypeInferLoops.loop_iter_trailing_nil
|
TypeInferLoops.loop_iter_trailing_nil
|
||||||
TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
||||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
|
||||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||||
TypeInferModules.do_not_modify_imported_types
|
TypeInferModules.custom_require_global
|
||||||
TypeInferModules.do_not_modify_imported_types_2
|
|
||||||
TypeInferModules.do_not_modify_imported_types_3
|
|
||||||
TypeInferModules.general_require_call_expression
|
|
||||||
TypeInferModules.general_require_type_mismatch
|
TypeInferModules.general_require_type_mismatch
|
||||||
TypeInferModules.module_type_conflict
|
TypeInferModules.module_type_conflict
|
||||||
TypeInferModules.module_type_conflict_instantiated
|
TypeInferModules.module_type_conflict_instantiated
|
||||||
TypeInferModules.require
|
|
||||||
TypeInferModules.require_a_variadic_function
|
TypeInferModules.require_a_variadic_function
|
||||||
TypeInferModules.require_failed_module
|
|
||||||
TypeInferModules.require_module_that_does_not_export
|
|
||||||
TypeInferModules.require_types
|
TypeInferModules.require_types
|
||||||
TypeInferModules.type_error_of_unknown_qualified_type
|
TypeInferModules.type_error_of_unknown_qualified_type
|
||||||
TypeInferModules.warn_if_you_try_to_require_a_non_modulescript
|
|
||||||
TypeInferOOP.CheckMethodsOfSealed
|
TypeInferOOP.CheckMethodsOfSealed
|
||||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
|
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
|
||||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||||
@ -725,7 +688,6 @@ TypeInferOperators.typecheck_unary_minus_error
|
|||||||
TypeInferOperators.unary_not_is_boolean
|
TypeInferOperators.unary_not_is_boolean
|
||||||
TypeInferOperators.unknown_type_in_comparison
|
TypeInferOperators.unknown_type_in_comparison
|
||||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||||
TypeInferPrimitives.cannot_call_primitives
|
|
||||||
TypeInferPrimitives.CheckMethodsOfNumber
|
TypeInferPrimitives.CheckMethodsOfNumber
|
||||||
TypeInferPrimitives.string_function_other
|
TypeInferPrimitives.string_function_other
|
||||||
TypeInferPrimitives.string_index
|
TypeInferPrimitives.string_index
|
||||||
@ -768,7 +730,6 @@ TypePackTests.type_alias_type_packs_nested
|
|||||||
TypePackTests.type_pack_hidden_free_tail_infinite_growth
|
TypePackTests.type_pack_hidden_free_tail_infinite_growth
|
||||||
TypePackTests.type_pack_type_parameters
|
TypePackTests.type_pack_type_parameters
|
||||||
TypePackTests.varargs_inference_through_multiple_scopes
|
TypePackTests.varargs_inference_through_multiple_scopes
|
||||||
TypePackTests.variadic_argument_tail
|
|
||||||
TypePackTests.variadic_pack_syntax
|
TypePackTests.variadic_pack_syntax
|
||||||
TypePackTests.variadic_packs
|
TypePackTests.variadic_packs
|
||||||
TypeSingletons.bool_singleton_subtype
|
TypeSingletons.bool_singleton_subtype
|
||||||
@ -793,7 +754,6 @@ TypeSingletons.string_singletons_escape_chars
|
|||||||
TypeSingletons.string_singletons_mismatch
|
TypeSingletons.string_singletons_mismatch
|
||||||
TypeSingletons.table_insert_with_a_singleton_argument
|
TypeSingletons.table_insert_with_a_singleton_argument
|
||||||
TypeSingletons.table_properties_type_error_escapes
|
TypeSingletons.table_properties_type_error_escapes
|
||||||
TypeSingletons.tagged_unions_immutable_tag
|
|
||||||
TypeSingletons.tagged_unions_using_singletons
|
TypeSingletons.tagged_unions_using_singletons
|
||||||
TypeSingletons.taking_the_length_of_string_singleton
|
TypeSingletons.taking_the_length_of_string_singleton
|
||||||
TypeSingletons.taking_the_length_of_union_of_string_singleton
|
TypeSingletons.taking_the_length_of_union_of_string_singleton
|
||||||
|
@ -84,12 +84,16 @@ def main():
|
|||||||
|
|
||||||
failList = loadFailList()
|
failList = loadFailList()
|
||||||
|
|
||||||
|
commandLine = [
|
||||||
|
args.path,
|
||||||
|
"--reporters=xml",
|
||||||
|
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
||||||
|
]
|
||||||
|
|
||||||
|
print('>', ' '.join(commandLine), file=sys.stderr)
|
||||||
|
|
||||||
p = sp.Popen(
|
p = sp.Popen(
|
||||||
[
|
commandLine,
|
||||||
args.path,
|
|
||||||
"--reporters=xml",
|
|
||||||
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
|
|
||||||
],
|
|
||||||
stdout=sp.PIPE,
|
stdout=sp.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,7 +126,7 @@ def main():
|
|||||||
with open(FAIL_LIST_PATH, "w", newline="\n") as f:
|
with open(FAIL_LIST_PATH, "w", newline="\n") as f:
|
||||||
for name in newFailList:
|
for name in newFailList:
|
||||||
print(name, file=f)
|
print(name, file=f)
|
||||||
print("Updated faillist.txt")
|
print("Updated faillist.txt", file=sys.stderr)
|
||||||
|
|
||||||
if handler.numSkippedTests > 0:
|
if handler.numSkippedTests > 0:
|
||||||
print(
|
print(
|
||||||
|
Loading…
Reference in New Issue
Block a user