mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 22:35:43 +08:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
ebf252f325
@ -8,9 +8,11 @@
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
void registerBuiltinTypes(TypeChecker& typeChecker);
|
|
||||||
void registerBuiltinTypes(Frontend& frontend);
|
void registerBuiltinTypes(Frontend& frontend);
|
||||||
|
|
||||||
|
void registerBuiltinGlobals(TypeChecker& typeChecker);
|
||||||
|
void registerBuiltinGlobals(Frontend& frontend);
|
||||||
|
|
||||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||||
|
|
||||||
|
@ -17,12 +17,9 @@ constexpr const char* kConfigName = ".luaurc";
|
|||||||
|
|
||||||
struct Config
|
struct Config
|
||||||
{
|
{
|
||||||
Config()
|
Config();
|
||||||
{
|
|
||||||
enabledLint.setDefaults();
|
|
||||||
}
|
|
||||||
|
|
||||||
Mode mode = Mode::NoCheck;
|
Mode mode;
|
||||||
|
|
||||||
ParseOptions parseOptions;
|
ParseOptions parseOptions;
|
||||||
|
|
||||||
|
@ -94,8 +94,9 @@ struct FunctionCallConstraint
|
|||||||
{
|
{
|
||||||
std::vector<NotNull<const struct Constraint>> innerConstraints;
|
std::vector<NotNull<const struct Constraint>> innerConstraints;
|
||||||
TypeId fn;
|
TypeId fn;
|
||||||
|
TypePackId argsPack;
|
||||||
TypePackId result;
|
TypePackId result;
|
||||||
class AstExprCall* astFragment;
|
class AstExprCall* callSite;
|
||||||
};
|
};
|
||||||
|
|
||||||
// result ~ prim ExpectedType SomeSingletonType MultitonType
|
// result ~ prim ExpectedType SomeSingletonType MultitonType
|
||||||
|
@ -124,6 +124,7 @@ struct ConstraintGraphBuilder
|
|||||||
void visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
|
void visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
|
||||||
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);
|
||||||
|
void visit(const ScopePtr& scope, AstStatError* error);
|
||||||
|
|
||||||
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {});
|
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {});
|
||||||
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});
|
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});
|
||||||
|
@ -104,6 +104,7 @@ struct ConstraintSolver
|
|||||||
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
// for a, ... in some_table do
|
// for a, ... in some_table do
|
||||||
|
// also handles __iter metamethod
|
||||||
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
// for a, ... in next_function, t, ... do
|
// for a, ... in next_function, t, ... do
|
||||||
|
@ -81,7 +81,7 @@ struct OnlyTablesCanHaveMethods
|
|||||||
struct DuplicateTypeDefinition
|
struct DuplicateTypeDefinition
|
||||||
{
|
{
|
||||||
Name name;
|
Name name;
|
||||||
Location previousLocation;
|
std::optional<Location> previousLocation;
|
||||||
|
|
||||||
bool operator==(const DuplicateTypeDefinition& rhs) const;
|
bool operator==(const DuplicateTypeDefinition& rhs) const;
|
||||||
};
|
};
|
||||||
@ -91,7 +91,8 @@ struct CountMismatch
|
|||||||
enum Context
|
enum Context
|
||||||
{
|
{
|
||||||
Arg,
|
Arg,
|
||||||
Result,
|
FunctionResult,
|
||||||
|
ExprListResult,
|
||||||
Return,
|
Return,
|
||||||
};
|
};
|
||||||
size_t expected;
|
size_t expected;
|
||||||
|
@ -157,7 +157,8 @@ struct Frontend
|
|||||||
ScopePtr getGlobalScope();
|
ScopePtr getGlobalScope();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles);
|
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles,
|
||||||
|
bool forAutocomplete = false);
|
||||||
|
|
||||||
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);
|
||||||
@ -171,10 +172,9 @@ private:
|
|||||||
std::unordered_map<std::string, ScopePtr> environments;
|
std::unordered_map<std::string, ScopePtr> environments;
|
||||||
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
|
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
|
||||||
|
|
||||||
ScopePtr globalScope;
|
SingletonTypes singletonTypes_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SingletonTypes singletonTypes_;
|
|
||||||
const NotNull<SingletonTypes> singletonTypes;
|
const NotNull<SingletonTypes> singletonTypes;
|
||||||
|
|
||||||
FileResolver* fileResolver;
|
FileResolver* fileResolver;
|
||||||
@ -186,13 +186,15 @@ public:
|
|||||||
FrontendOptions options;
|
FrontendOptions options;
|
||||||
InternalErrorReporter iceHandler;
|
InternalErrorReporter iceHandler;
|
||||||
TypeArena globalTypes;
|
TypeArena globalTypes;
|
||||||
TypeArena arenaForAutocomplete;
|
|
||||||
|
|
||||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||||
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
|
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
|
||||||
|
|
||||||
Stats stats = {};
|
Stats stats = {};
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScopePtr globalScope;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -14,16 +14,18 @@ struct TxnLog;
|
|||||||
// A substitution which replaces generic types in a given set by free types.
|
// A substitution which replaces generic types in a given set by free types.
|
||||||
struct ReplaceGenerics : Substitution
|
struct ReplaceGenerics : Substitution
|
||||||
{
|
{
|
||||||
ReplaceGenerics(
|
ReplaceGenerics(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
||||||
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
|
const std::vector<TypePackId>& genericPacks)
|
||||||
: Substitution(log, arena)
|
: Substitution(log, arena)
|
||||||
, level(level)
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
, generics(generics)
|
, generics(generics)
|
||||||
, genericPacks(genericPacks)
|
, genericPacks(genericPacks)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
Scope* scope;
|
||||||
std::vector<TypeId> generics;
|
std::vector<TypeId> generics;
|
||||||
std::vector<TypePackId> genericPacks;
|
std::vector<TypePackId> genericPacks;
|
||||||
bool ignoreChildren(TypeId ty) override;
|
bool ignoreChildren(TypeId ty) override;
|
||||||
@ -36,13 +38,15 @@ struct ReplaceGenerics : Substitution
|
|||||||
// A substitution which replaces generic functions by monomorphic functions
|
// A substitution which replaces generic functions by monomorphic functions
|
||||||
struct Instantiation : Substitution
|
struct Instantiation : Substitution
|
||||||
{
|
{
|
||||||
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
|
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope)
|
||||||
: Substitution(log, arena)
|
: Substitution(log, arena)
|
||||||
, level(level)
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
Scope* scope;
|
||||||
bool ignoreChildren(TypeId ty) override;
|
bool ignoreChildren(TypeId ty) override;
|
||||||
bool isDirty(TypeId ty) override;
|
bool isDirty(TypeId ty) override;
|
||||||
bool isDirty(TypePackId tp) override;
|
bool isDirty(TypePackId tp) override;
|
||||||
|
@ -49,9 +49,11 @@ struct Scope
|
|||||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||||
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
||||||
std::unordered_map<Name, Location> typeAliasLocations;
|
std::unordered_map<Name, Location> typeAliasLocations;
|
||||||
|
|
||||||
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
|
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
|
||||||
|
|
||||||
|
DenseHashSet<Name> builtinTypeNames{""};
|
||||||
|
void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun);
|
||||||
|
|
||||||
std::optional<TypeId> lookup(Symbol sym);
|
std::optional<TypeId> lookup(Symbol sym);
|
||||||
|
|
||||||
std::optional<TypeFun> lookupType(const Name& name);
|
std::optional<TypeFun> lookupType(const Name& name);
|
||||||
@ -61,7 +63,7 @@ struct Scope
|
|||||||
std::optional<TypePackId> lookupPack(const Name& name);
|
std::optional<TypePackId> lookupPack(const Name& name);
|
||||||
|
|
||||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true);
|
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||||
|
|
||||||
RefinementMap refinements;
|
RefinementMap refinements;
|
||||||
|
|
||||||
@ -73,4 +75,13 @@ struct Scope
|
|||||||
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
|
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Returns true iff the left scope encloses the right scope. A Scope* equal to
|
||||||
|
// nullptr is considered to be the outermost-possible scope.
|
||||||
|
bool subsumesStrict(Scope* left, Scope* right);
|
||||||
|
|
||||||
|
// Returns true if the left scope encloses the right scope, or if they are the
|
||||||
|
// same scope. As in subsumesStrict(), nullptr is considered to be the
|
||||||
|
// outermost-possible scope.
|
||||||
|
bool subsumes(Scope* left, Scope* right);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -186,6 +186,16 @@ struct TxnLog
|
|||||||
// The pointer returned lives until `commit` or `clear` is called.
|
// The pointer returned lives until `commit` or `clear` is called.
|
||||||
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
||||||
|
|
||||||
|
// Queues the replacement of a type's scope with the provided scope.
|
||||||
|
//
|
||||||
|
// The pointer returned lives until `commit` or `clear` is called.
|
||||||
|
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
|
||||||
|
|
||||||
|
// Queues the replacement of a type pack's scope with the provided scope.
|
||||||
|
//
|
||||||
|
// The pointer returned lives until `commit` or `clear` is called.
|
||||||
|
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
|
||||||
|
|
||||||
// Queues a replacement of a table type with another table type with a new
|
// Queues a replacement of a table type with another table type with a new
|
||||||
// indexer.
|
// indexer.
|
||||||
//
|
//
|
||||||
|
@ -30,6 +30,7 @@ struct TypeArena
|
|||||||
|
|
||||||
TypeId freshType(TypeLevel level);
|
TypeId freshType(TypeLevel level);
|
||||||
TypeId freshType(Scope* scope);
|
TypeId freshType(Scope* scope);
|
||||||
|
TypeId freshType(Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
TypePackId freshTypePack(Scope* scope);
|
TypePackId freshTypePack(Scope* scope);
|
||||||
|
|
||||||
|
@ -80,10 +80,12 @@ struct TypeChecker
|
|||||||
void check(const ScopePtr& scope, const AstStatForIn& forin);
|
void check(const ScopePtr& scope, const AstStatForIn& forin);
|
||||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function);
|
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function);
|
||||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
||||||
void check(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0, bool forwardDeclare = false);
|
void check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
|
||||||
void check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
void check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||||
void check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
void check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||||
|
|
||||||
|
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
|
||||||
|
|
||||||
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||||
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
||||||
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
||||||
@ -392,8 +394,12 @@ private:
|
|||||||
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PrintLineProc = void(*)(const std::string&);
|
||||||
|
|
||||||
|
extern PrintLineProc luauPrintLine;
|
||||||
|
|
||||||
// Unit test hook
|
// Unit test hook
|
||||||
void setPrintLine(void (*pl)(const std::string& s));
|
void setPrintLine(PrintLineProc pl);
|
||||||
void resetPrintLine();
|
void resetPrintLine();
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -25,4 +25,8 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
|
|||||||
// Returns the minimum and maximum number of types the argument list can accept.
|
// 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, bool includeHiddenVariadics = false);
|
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
|
||||||
|
|
||||||
|
// "Render" a type pack out to an array of a given length. Expands variadics and
|
||||||
|
// various other things to get there.
|
||||||
|
std::vector<TypeId> flatten(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, TypePackId pack, size_t length);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -27,6 +27,7 @@ namespace Luau
|
|||||||
|
|
||||||
struct TypeArena;
|
struct TypeArena;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
|
using ScopePtr = std::shared_ptr<Scope>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are three kinds of type variables:
|
* There are three kinds of type variables:
|
||||||
@ -264,7 +265,15 @@ 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 MagicFunctionCallContext
|
||||||
|
{
|
||||||
|
NotNull<struct ConstraintSolver> solver;
|
||||||
|
const class AstExprCall* callSite;
|
||||||
|
TypePackId arguments;
|
||||||
|
TypePackId result;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DcrMagicFunction = std::function<bool(MagicFunctionCallContext)>;
|
||||||
|
|
||||||
struct FunctionTypeVar
|
struct FunctionTypeVar
|
||||||
{
|
{
|
||||||
@ -277,10 +286,14 @@ struct FunctionTypeVar
|
|||||||
|
|
||||||
// Local monomorphic function
|
// Local monomorphic function
|
||||||
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
FunctionTypeVar(
|
||||||
|
TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
// Local polymorphic function
|
// Local polymorphic function
|
||||||
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
FunctionTypeVar(TypeLevel level, Scope* scope, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes,
|
||||||
|
TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
Scope* scope = nullptr;
|
Scope* scope = nullptr;
|
||||||
@ -345,8 +358,9 @@ struct TableTypeVar
|
|||||||
using Props = std::map<Name, Property>;
|
using Props = std::map<Name, Property>;
|
||||||
|
|
||||||
TableTypeVar() = default;
|
TableTypeVar() = default;
|
||||||
explicit TableTypeVar(TableState state, TypeLevel level);
|
explicit TableTypeVar(TableState state, TypeLevel level, Scope* scope = nullptr);
|
||||||
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state);
|
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state);
|
||||||
|
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, Scope* scope, TableState state);
|
||||||
|
|
||||||
Props props;
|
Props props;
|
||||||
std::optional<TableIndexer> indexer;
|
std::optional<TableIndexer> indexer;
|
||||||
|
@ -85,6 +85,7 @@ struct Free
|
|||||||
{
|
{
|
||||||
explicit Free(TypeLevel level);
|
explicit Free(TypeLevel level);
|
||||||
explicit Free(Scope* scope);
|
explicit Free(Scope* scope);
|
||||||
|
explicit Free(Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
int index;
|
int index;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
@ -60,6 +60,7 @@ struct Unifier
|
|||||||
Location location;
|
Location location;
|
||||||
Variance variance = Covariant;
|
Variance variance = Covariant;
|
||||||
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
|
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
|
||||||
|
bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
|
||||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||||
|
|
||||||
UnifierSharedState& sharedState;
|
UnifierSharedState& sharedState;
|
||||||
@ -140,6 +141,6 @@ private:
|
|||||||
std::optional<int> firstPackErrorPos;
|
std::optional<int> firstPackErrorPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, TypePackId tp);
|
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -1208,13 +1208,11 @@ static bool autocompleteIfElseExpression(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
|
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<SingletonTypes> singletonTypes,
|
||||||
TypeArena* typeArena, 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());
|
||||||
|
|
||||||
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
|
|
||||||
|
|
||||||
AstNode* node = ancestry.rbegin()[0];
|
AstNode* node = ancestry.rbegin()[0];
|
||||||
|
|
||||||
if (node->is<AstExprIndexName>())
|
if (node->is<AstExprIndexName>())
|
||||||
@ -1254,16 +1252,16 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
|
|||||||
scope = scope->parent;
|
scope = scope->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, typeChecker.nilType);
|
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->nilType);
|
||||||
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->trueType);
|
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->trueType);
|
||||||
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->falseType);
|
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->falseType);
|
||||||
TypeCorrectKind correctForFunction =
|
TypeCorrectKind correctForFunction =
|
||||||
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||||
|
|
||||||
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
|
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
|
||||||
result["true"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForTrue};
|
result["true"] = {AutocompleteEntryKind::Keyword, singletonTypes->booleanType, false, false, correctForTrue};
|
||||||
result["false"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForFalse};
|
result["false"] = {AutocompleteEntryKind::Keyword, singletonTypes->booleanType, false, false, correctForFalse};
|
||||||
result["nil"] = {AutocompleteEntryKind::Keyword, typeChecker.nilType, false, false, correctForNil};
|
result["nil"] = {AutocompleteEntryKind::Keyword, singletonTypes->nilType, false, false, correctForNil};
|
||||||
result["not"] = {AutocompleteEntryKind::Keyword};
|
result["not"] = {AutocompleteEntryKind::Keyword};
|
||||||
result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction};
|
result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction};
|
||||||
|
|
||||||
@ -1274,11 +1272,11 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
|
|||||||
return AutocompleteContext::Expression;
|
return AutocompleteContext::Expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
|
static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<SingletonTypes> singletonTypes,
|
||||||
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position)
|
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position)
|
||||||
{
|
{
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
AutocompleteContext context = autocompleteExpression(sourceModule, module, typeChecker, typeArena, ancestry, position, result);
|
AutocompleteContext context = autocompleteExpression(sourceModule, module, singletonTypes, typeArena, ancestry, position, result);
|
||||||
return {result, ancestry, context};
|
return {result, ancestry, context};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1385,13 +1383,13 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, const TypeChecker& typeChecker,
|
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes,
|
||||||
TypeArena* typeArena, Position position, StringCompletionCallback callback)
|
Scope* globalScope, Position position, StringCompletionCallback callback)
|
||||||
{
|
{
|
||||||
if (isWithinComment(sourceModule, position))
|
if (isWithinComment(sourceModule, position))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
|
TypeArena typeArena;
|
||||||
|
|
||||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
||||||
LUAU_ASSERT(!ancestry.empty());
|
LUAU_ASSERT(!ancestry.empty());
|
||||||
@ -1419,11 +1417,10 @@ 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 {autocompleteProps(
|
return {autocompleteProps(*module, &typeArena, singletonTypes, globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
|
||||||
*module, typeArena, singletonTypes, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
|
|
||||||
ancestry, AutocompleteContext::Property};
|
ancestry, AutocompleteContext::Property};
|
||||||
else
|
else
|
||||||
return {autocompleteProps(*module, typeArena, singletonTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
return {autocompleteProps(*module, &typeArena, singletonTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||||
}
|
}
|
||||||
else if (auto typeReference = node->as<AstTypeReference>())
|
else if (auto typeReference = node->as<AstTypeReference>())
|
||||||
{
|
{
|
||||||
@ -1441,7 +1438,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
||||||
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
|
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
|
||||||
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
|
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
else
|
else
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1455,7 +1452,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
|
|
||||||
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||||
(statFor->step && statFor->step->location.containsClosed(position)))
|
(statFor->step && statFor->step->location.containsClosed(position)))
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1485,7 +1482,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
||||||
|
|
||||||
if (lastExpr->location.containsClosed(position))
|
if (lastExpr->location.containsClosed(position))
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
|
|
||||||
if (position > lastExpr->location.end)
|
if (position > lastExpr->location.end)
|
||||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||||
@ -1509,7 +1506,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||||
|
|
||||||
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
|
|
||||||
if (statWhile->hasDo && position > statWhile->doLocation.end)
|
if (statWhile->hasDo && position > statWhile->doLocation.end)
|
||||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||||
@ -1526,7 +1523,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
||||||
{
|
{
|
||||||
if (statIf->condition->is<AstExprError>())
|
if (statIf->condition->is<AstExprError>())
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
|
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
|
||||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||||
}
|
}
|
||||||
@ -1534,7 +1531,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
|
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
|
||||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||||
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
||||||
@ -1546,7 +1543,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
{
|
{
|
||||||
if (auto it = module->astExpectedTypes.find(exprTable))
|
if (auto it = module->astExpectedTypes.find(exprTable))
|
||||||
{
|
{
|
||||||
auto result = autocompleteProps(*module, typeArena, singletonTypes, *it, PropIndexType::Key, ancestry);
|
auto result = autocompleteProps(*module, &typeArena, singletonTypes, *it, PropIndexType::Key, ancestry);
|
||||||
|
|
||||||
// Remove keys that are already completed
|
// Remove keys that are already completed
|
||||||
for (const auto& item : exprTable->items)
|
for (const auto& item : exprTable->items)
|
||||||
@ -1560,7 +1557,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
|
|
||||||
// If we know for sure that a key is being written, do not offer general expression suggestions
|
// If we know for sure that a key is being written, do not offer general expression suggestions
|
||||||
if (!key)
|
if (!key)
|
||||||
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position, result);
|
autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position, result);
|
||||||
|
|
||||||
return {result, ancestry, AutocompleteContext::Property};
|
return {result, ancestry, AutocompleteContext::Property};
|
||||||
}
|
}
|
||||||
@ -1588,7 +1585,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
|
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
|
||||||
{
|
{
|
||||||
if (auto it = module->astTypes.find(idxExpr->expr))
|
if (auto it = module->astTypes.find(idxExpr->expr))
|
||||||
autocompleteProps(*module, typeArena, singletonTypes, follow(*it), PropIndexType::Point, ancestry, result);
|
autocompleteProps(*module, &typeArena, singletonTypes, follow(*it), PropIndexType::Point, ancestry, result);
|
||||||
}
|
}
|
||||||
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
|
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
|
||||||
{
|
{
|
||||||
@ -1604,12 +1601,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node->is<AstExprConstantNumber>())
|
if (node->is<AstExprConstantNumber>())
|
||||||
{
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
|
|
||||||
if (node->asExpr())
|
if (node->asExpr())
|
||||||
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
|
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||||
else if (node->asStat())
|
else if (node->asStat())
|
||||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||||
|
|
||||||
@ -1628,15 +1623,15 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
|||||||
if (!sourceModule)
|
if (!sourceModule)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
|
|
||||||
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
||||||
|
|
||||||
if (!module)
|
if (!module)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
AutocompleteResult autocompleteResult = autocomplete(*sourceModule, module, typeChecker, &frontend.arenaForAutocomplete, position, callback);
|
NotNull<SingletonTypes> singletonTypes = frontend.singletonTypes;
|
||||||
|
Scope* globalScope = frontend.typeCheckerForAutocomplete.globalScope.get();
|
||||||
|
|
||||||
frontend.arenaForAutocomplete.clear();
|
AutocompleteResult autocompleteResult = autocomplete(*sourceModule, module, singletonTypes, globalScope, position, callback);
|
||||||
|
|
||||||
return autocompleteResult;
|
return autocompleteResult;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
// 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/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
#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 "Luau/ConstraintSolver.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
#include "Luau/TypePack.h"
|
||||||
|
#include "Luau/TypeVar.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
|
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
|
||||||
|
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
|
||||||
|
|
||||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||||
*
|
*
|
||||||
@ -34,7 +38,9 @@ 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);
|
|
||||||
|
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
|
||||||
|
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
|
||||||
|
|
||||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||||
{
|
{
|
||||||
@ -226,7 +232,22 @@ void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerBuiltinTypes(TypeChecker& typeChecker)
|
void registerBuiltinTypes(Frontend& frontend)
|
||||||
|
{
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("any", TypeFun{{}, frontend.singletonTypes->anyType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("nil", TypeFun{{}, frontend.singletonTypes->nilType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("number", TypeFun{{}, frontend.singletonTypes->numberType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("string", TypeFun{{}, frontend.singletonTypes->stringType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("boolean", TypeFun{{}, frontend.singletonTypes->booleanType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("thread", TypeFun{{}, frontend.singletonTypes->threadType});
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
{
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("unknown", TypeFun{{}, frontend.singletonTypes->unknownType});
|
||||||
|
frontend.getGlobalScope()->addBuiltinTypeBinding("never", TypeFun{{}, frontend.singletonTypes->neverType});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
|
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
|
||||||
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
|
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
|
||||||
@ -303,6 +324,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||||||
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
||||||
|
attachDcrMagicFunction(getGlobalBinding(typeChecker, "select"), dcrMagicFunctionSelect);
|
||||||
|
|
||||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table")))
|
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table")))
|
||||||
{
|
{
|
||||||
@ -317,12 +339,13 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
|||||||
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
|
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerBuiltinTypes(Frontend& frontend)
|
void registerBuiltinGlobals(Frontend& frontend)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!frontend.globalTypes.typeVars.isFrozen());
|
LUAU_ASSERT(!frontend.globalTypes.typeVars.isFrozen());
|
||||||
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
|
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
|
||||||
|
|
||||||
TypeId nilType = frontend.typeChecker.nilType;
|
if (FFlag::LuauReportShadowedTypeAlias)
|
||||||
|
registerBuiltinTypes(frontend);
|
||||||
|
|
||||||
TypeArena& arena = frontend.globalTypes;
|
TypeArena& arena = frontend.globalTypes;
|
||||||
NotNull<SingletonTypes> singletonTypes = frontend.singletonTypes;
|
NotNull<SingletonTypes> singletonTypes = frontend.singletonTypes;
|
||||||
@ -352,7 +375,7 @@ void registerBuiltinTypes(Frontend& frontend)
|
|||||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||||
|
|
||||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.singletonTypes->nilType}});
|
||||||
|
|
||||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||||
addGlobalBinding(frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
addGlobalBinding(frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||||
@ -394,6 +417,7 @@ void registerBuiltinTypes(Frontend& frontend)
|
|||||||
attachMagicFunction(getGlobalBinding(frontend, "assert"), magicFunctionAssert);
|
attachMagicFunction(getGlobalBinding(frontend, "assert"), magicFunctionAssert);
|
||||||
attachMagicFunction(getGlobalBinding(frontend, "setmetatable"), magicFunctionSetMetaTable);
|
attachMagicFunction(getGlobalBinding(frontend, "setmetatable"), magicFunctionSetMetaTable);
|
||||||
attachMagicFunction(getGlobalBinding(frontend, "select"), magicFunctionSelect);
|
attachMagicFunction(getGlobalBinding(frontend, "select"), magicFunctionSelect);
|
||||||
|
attachDcrMagicFunction(getGlobalBinding(frontend, "select"), dcrMagicFunctionSelect);
|
||||||
|
|
||||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(frontend, "table")))
|
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(frontend, "table")))
|
||||||
{
|
{
|
||||||
@ -408,7 +432,6 @@ void registerBuiltinTypes(Frontend& frontend)
|
|||||||
attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire);
|
attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
@ -450,6 +473,50 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
||||||
|
{
|
||||||
|
if (context.callSite->args.size <= 0)
|
||||||
|
{
|
||||||
|
context.solver->reportError(TypeError{context.callSite->location, GenericError{"select should take 1 or more arguments"}});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstExpr* arg1 = context.callSite->args.data[0];
|
||||||
|
|
||||||
|
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
|
||||||
|
{
|
||||||
|
const auto& [v, tail] = flatten(context.arguments);
|
||||||
|
|
||||||
|
int offset = int(num->value);
|
||||||
|
if (offset > 0)
|
||||||
|
{
|
||||||
|
if (size_t(offset) < v.size())
|
||||||
|
{
|
||||||
|
std::vector<TypeId> res(v.begin() + offset, v.end());
|
||||||
|
TypePackId resTypePack = context.solver->arena->addTypePack({std::move(res), tail});
|
||||||
|
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
|
||||||
|
}
|
||||||
|
else if (tail)
|
||||||
|
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
||||||
|
{
|
||||||
|
if (str->value.size == 1 && str->value.data[0] == '#') {
|
||||||
|
TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->singletonTypes->numberType});
|
||||||
|
asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
@ -675,22 +742,22 @@ static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
|
|||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr)
|
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context)
|
||||||
{
|
{
|
||||||
if (expr->args.size != 1)
|
if (context.callSite->args.size != 1)
|
||||||
{
|
{
|
||||||
solver->reportError(GenericError{"require takes 1 argument"}, expr->location);
|
context.solver->reportError(GenericError{"require takes 1 argument"}, context.callSite->location);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkRequirePathDcr(solver, expr->args.data[0]))
|
if (!checkRequirePathDcr(context.solver, context.callSite->args.data[0]))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (auto moduleInfo = solver->moduleResolver->resolveModuleInfo(solver->currentModuleName, *expr))
|
if (auto moduleInfo = context.solver->moduleResolver->resolveModuleInfo(context.solver->currentModuleName, *context.callSite))
|
||||||
{
|
{
|
||||||
TypeId moduleType = solver->resolveModule(*moduleInfo, expr->location);
|
TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location);
|
||||||
TypePackId moduleResult = solver->arena->addTypePack({moduleType});
|
TypePackId moduleResult = context.solver->arena->addTypePack({moduleType});
|
||||||
asMutable(result)->ty.emplace<BoundTypePack>(moduleResult);
|
asMutable(context.result)->ty.emplace<BoundTypePack>(moduleResult);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -220,6 +220,9 @@ void TypeCloner::operator()(const SingletonTypeVar& t)
|
|||||||
|
|
||||||
void TypeCloner::operator()(const FunctionTypeVar& t)
|
void TypeCloner::operator()(const FunctionTypeVar& t)
|
||||||
{
|
{
|
||||||
|
// FISHY: We always erase the scope when we clone things. clone() was
|
||||||
|
// originally written so that we could copy a module's type surface into an
|
||||||
|
// export arena. This probably dates to that.
|
||||||
TypeId result = dest.addType(FunctionTypeVar{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf});
|
TypeId result = dest.addType(FunctionTypeVar{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf});
|
||||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
|
||||||
LUAU_ASSERT(ftv != nullptr);
|
LUAU_ASSERT(ftv != nullptr);
|
||||||
@ -436,7 +439,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
|
|||||||
|
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.generics = ftv->generics;
|
clone.generics = ftv->generics;
|
||||||
clone.genericPacks = ftv->genericPacks;
|
clone.genericPacks = ftv->genericPacks;
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
@ -448,7 +451,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl
|
|||||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!ttv->boundTo);
|
LUAU_ASSERT(!ttv->boundTo);
|
||||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->scope, ttv->state};
|
||||||
clone.definitionModuleName = ttv->definitionModuleName;
|
clone.definitionModuleName = ttv->definitionModuleName;
|
||||||
clone.name = ttv->name;
|
clone.name = ttv->name;
|
||||||
clone.syntheticName = ttv->syntheticName;
|
clone.syntheticName = ttv->syntheticName;
|
||||||
|
@ -4,15 +4,18 @@
|
|||||||
#include "Luau/Lexer.h"
|
#include "Luau/Lexer.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
namespace
|
LUAU_FASTFLAGVARIABLE(LuauEnableNonstrictByDefaultForLuauConfig, false)
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
using Error = std::optional<std::string>;
|
using Error = std::optional<std::string>;
|
||||||
|
|
||||||
}
|
Config::Config()
|
||||||
|
: mode(FFlag::LuauEnableNonstrictByDefaultForLuauConfig ? Mode::Nonstrict : Mode::NoCheck)
|
||||||
namespace Luau
|
|
||||||
{
|
{
|
||||||
|
enabledLint.setDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
static Error parseBoolean(bool& result, const std::string& value)
|
static Error parseBoolean(bool& result, const std::string& value)
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||||
|
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||||
|
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
|
|
||||||
@ -218,6 +219,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
|||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
else if (auto s = stat->as<AstStatDeclareFunction>())
|
else if (auto s = stat->as<AstStatDeclareFunction>())
|
||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
|
else if (auto s = stat->as<AstStatError>())
|
||||||
|
visit(scope, s);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(0);
|
LUAU_ASSERT(0);
|
||||||
}
|
}
|
||||||
@ -454,8 +457,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||||||
TypeId containingTableType = check(scope, indexName->expr);
|
TypeId containingTableType = check(scope, indexName->expr);
|
||||||
|
|
||||||
functionType = arena->addType(BlockedTypeVar{});
|
functionType = arena->addType(BlockedTypeVar{});
|
||||||
TypeId prospectiveTableType =
|
|
||||||
arena->addType(TableTypeVar{}); // TODO look into stack utilization. This is probably ok because it scales with AST depth.
|
// TODO look into stack utilization. This is probably ok because it scales with AST depth.
|
||||||
|
TypeId prospectiveTableType = arena->addType(TableTypeVar{TableState::Unsealed, TypeLevel{}, scope.get()});
|
||||||
|
|
||||||
NotNull<TableTypeVar> prospectiveTable{getMutable<TableTypeVar>(prospectiveTableType)};
|
NotNull<TableTypeVar> prospectiveTable{getMutable<TableTypeVar>(prospectiveTableType)};
|
||||||
|
|
||||||
Property& prop = prospectiveTable->props[indexName->index.value];
|
Property& prop = prospectiveTable->props[indexName->index.value];
|
||||||
@ -619,7 +624,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* d
|
|||||||
TypeId classTy = arena->addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, moduleName));
|
TypeId classTy = arena->addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, moduleName));
|
||||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||||
|
|
||||||
TypeId metaTy = arena->addType(TableTypeVar{TableState::Sealed, scope->level});
|
TypeId metaTy = arena->addType(TableTypeVar{TableState::Sealed, scope->level, scope.get()});
|
||||||
TableTypeVar* metatable = getMutable<TableTypeVar>(metaTy);
|
TableTypeVar* metatable = getMutable<TableTypeVar>(metaTy);
|
||||||
|
|
||||||
ctv->metatable = metaTy;
|
ctv->metatable = metaTy;
|
||||||
@ -715,7 +720,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
|
|||||||
|
|
||||||
TypePackId paramPack = resolveTypePack(funScope, global->params);
|
TypePackId paramPack = resolveTypePack(funScope, global->params);
|
||||||
TypePackId retPack = resolveTypePack(funScope, global->retTypes);
|
TypePackId retPack = resolveTypePack(funScope, global->retTypes);
|
||||||
TypeId fnType = arena->addType(FunctionTypeVar{funScope->level, std::move(genericTys), std::move(genericTps), paramPack, retPack});
|
TypeId fnType = arena->addType(FunctionTypeVar{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack});
|
||||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(fnType);
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(fnType);
|
||||||
|
|
||||||
ftv->argNames.reserve(global->paramNames.size);
|
ftv->argNames.reserve(global->paramNames.size);
|
||||||
@ -728,6 +733,14 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
|
|||||||
scope->bindings[global->name] = Binding{fnType, global->location};
|
scope->bindings[global->name] = Binding{fnType, global->location};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error)
|
||||||
|
{
|
||||||
|
for (AstStat* stat : error->statements)
|
||||||
|
visit(scope, stat);
|
||||||
|
for (AstExpr* expr : error->expressions)
|
||||||
|
check(scope, expr);
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes)
|
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes)
|
||||||
{
|
{
|
||||||
std::vector<TypeId> head;
|
std::vector<TypeId> head;
|
||||||
@ -745,7 +758,9 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<TypeId> expectedTailTypes{begin(expectedTypes) + i, end(expectedTypes)};
|
std::vector<TypeId> expectedTailTypes;
|
||||||
|
if (i < expectedTypes.size())
|
||||||
|
expectedTailTypes.assign(begin(expectedTypes) + i, end(expectedTypes));
|
||||||
tail = checkPack(scope, expr, expectedTailTypes);
|
tail = checkPack(scope, expr, expectedTailTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -803,7 +818,8 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
|
|||||||
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
||||||
// TODO: How do expectedTypes play into this? Do they?
|
// TODO: How do expectedTypes play into this? Do they?
|
||||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||||
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
TypePackId argPack = arena->addTypePack(TypePack{args, {}});
|
||||||
|
FunctionTypeVar ftv(TypeLevel{}, scope.get(), argPack, rets);
|
||||||
TypeId inferredFnType = arena->addType(ftv);
|
TypeId inferredFnType = arena->addType(ftv);
|
||||||
|
|
||||||
scope->unqueuedConstraints.push_back(
|
scope->unqueuedConstraints.push_back(
|
||||||
@ -834,6 +850,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
|
|||||||
FunctionCallConstraint{
|
FunctionCallConstraint{
|
||||||
{ic, sc},
|
{ic, sc},
|
||||||
fnType,
|
fnType,
|
||||||
|
argPack,
|
||||||
rets,
|
rets,
|
||||||
call,
|
call,
|
||||||
});
|
});
|
||||||
@ -968,6 +985,9 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::
|
|||||||
else if (auto err = expr->as<AstExprError>())
|
else if (auto err = expr->as<AstExprError>())
|
||||||
{
|
{
|
||||||
// Open question: Should we traverse into this?
|
// Open question: Should we traverse into this?
|
||||||
|
for (AstExpr* subExpr : err->expressions)
|
||||||
|
check(scope, subExpr);
|
||||||
|
|
||||||
result = singletonTypes->errorRecoveryType();
|
result = singletonTypes->errorRecoveryType();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -988,7 +1008,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
|
|||||||
|
|
||||||
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
||||||
const std::optional<TableIndexer> indexer;
|
const std::optional<TableIndexer> indexer;
|
||||||
TableTypeVar ttv{std::move(props), indexer, TypeLevel{}, TableState::Free};
|
TableTypeVar ttv{std::move(props), indexer, TypeLevel{}, scope.get(), TableState::Free};
|
||||||
|
|
||||||
TypeId expectedTableType = arena->addType(std::move(ttv));
|
TypeId expectedTableType = arena->addType(std::move(ttv));
|
||||||
|
|
||||||
@ -1005,7 +1025,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
|
|||||||
TypeId result = freshType(scope);
|
TypeId result = freshType(scope);
|
||||||
|
|
||||||
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{}, scope.get(), TableState::Free});
|
||||||
|
|
||||||
addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
|
addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
|
||||||
|
|
||||||
@ -1094,6 +1115,9 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr,
|
|||||||
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
||||||
LUAU_ASSERT(ttv);
|
LUAU_ASSERT(ttv);
|
||||||
|
|
||||||
|
ttv->state = TableState::Unsealed;
|
||||||
|
ttv->scope = scope.get();
|
||||||
|
|
||||||
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
||||||
if (!ttv->indexer)
|
if (!ttv->indexer)
|
||||||
{
|
{
|
||||||
@ -1195,7 +1219,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bodyScope = childScope(fn->body, parent);
|
bodyScope = childScope(fn, parent);
|
||||||
|
|
||||||
returnType = freshTypePack(bodyScope);
|
returnType = freshTypePack(bodyScope);
|
||||||
bodyScope->returnType = returnType;
|
bodyScope->returnType = returnType;
|
||||||
@ -1260,7 +1284,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||||||
// TODO: Vararg annotation.
|
// TODO: Vararg annotation.
|
||||||
// TODO: Preserve argument names in the function's type.
|
// TODO: Preserve argument names in the function's type.
|
||||||
|
|
||||||
FunctionTypeVar actualFunction{arena->addTypePack(argTypes, varargPack), returnType};
|
FunctionTypeVar actualFunction{TypeLevel{}, parent.get(), arena->addTypePack(argTypes, varargPack), returnType};
|
||||||
actualFunction.hasNoGenerics = !hasGenerics;
|
actualFunction.hasNoGenerics = !hasGenerics;
|
||||||
actualFunction.generics = std::move(genericTypes);
|
actualFunction.generics = std::move(genericTypes);
|
||||||
actualFunction.genericPacks = std::move(genericTypePacks);
|
actualFunction.genericPacks = std::move(genericTypePacks);
|
||||||
@ -1297,6 +1321,22 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
|
|
||||||
if (auto ref = ty->as<AstTypeReference>())
|
if (auto ref = ty->as<AstTypeReference>())
|
||||||
{
|
{
|
||||||
|
if (FFlag::DebugLuauMagicTypes)
|
||||||
|
{
|
||||||
|
if (ref->name == "_luau_ice")
|
||||||
|
ice->ice("_luau_ice encountered", ty->location);
|
||||||
|
else if (ref->name == "_luau_print")
|
||||||
|
{
|
||||||
|
if (ref->parameters.size != 1 || !ref->parameters.data[0].type)
|
||||||
|
{
|
||||||
|
reportError(ty->location, GenericError{"_luau_print requires one generic parameter"});
|
||||||
|
return singletonTypes->errorRecoveryType();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return resolveType(scope, ref->parameters.data[0].type, topLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
|
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
|
||||||
|
|
||||||
if (alias.has_value() || ref->prefix.has_value())
|
if (alias.has_value() || ref->prefix.has_value())
|
||||||
@ -1369,7 +1409,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result = arena->addType(TableTypeVar{props, indexer, scope->level, TableState::Sealed});
|
result = arena->addType(TableTypeVar{props, indexer, scope->level, scope.get(), TableState::Sealed});
|
||||||
}
|
}
|
||||||
else if (auto fn = ty->as<AstTypeFunction>())
|
else if (auto fn = ty->as<AstTypeFunction>())
|
||||||
{
|
{
|
||||||
@ -1414,7 +1454,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||||||
|
|
||||||
// TODO: FunctionTypeVar needs a pointer to the scope so that we know
|
// TODO: FunctionTypeVar needs a pointer to the scope so that we know
|
||||||
// how to quantify/instantiate it.
|
// how to quantify/instantiate it.
|
||||||
FunctionTypeVar ftv{argTypes, returnTypes};
|
FunctionTypeVar ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||||
|
|
||||||
// This replicates the behavior of the appropriate FunctionTypeVar
|
// This replicates the behavior of the appropriate FunctionTypeVar
|
||||||
// constructors.
|
// constructors.
|
||||||
|
@ -8,9 +8,11 @@
|
|||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/Quantify.h"
|
#include "Luau/Quantify.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
#include "Luau/DcrLogger.h"
|
#include "Luau/DcrLogger.h"
|
||||||
#include "Luau/VisitTypeVar.h"
|
#include "Luau/VisitTypeVar.h"
|
||||||
|
#include "Luau/TypeUtils.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||||
@ -439,6 +441,7 @@ bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const
|
|||||||
return block(c.superPack, constraint);
|
return block(c.superPack, constraint);
|
||||||
|
|
||||||
unify(c.subPack, c.superPack, constraint->scope);
|
unify(c.subPack, c.superPack, constraint->scope);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,7 +468,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
|||||||
if (isBlocked(c.superType))
|
if (isBlocked(c.superType))
|
||||||
return block(c.superType, constraint);
|
return block(c.superType, constraint);
|
||||||
|
|
||||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
|
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||||
|
|
||||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||||
@ -909,7 +912,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||||||
|
|
||||||
if (ftv && ftv->dcrMagicFunction != nullptr)
|
if (ftv && ftv->dcrMagicFunction != nullptr)
|
||||||
{
|
{
|
||||||
usedMagic = ftv->dcrMagicFunction(NotNull(this), result, c.astFragment);
|
usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull(this), c.callSite, c.argsPack, result});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usedMagic)
|
if (usedMagic)
|
||||||
@ -1087,6 +1090,63 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||||||
else
|
else
|
||||||
errorify(c.variables);
|
errorify(c.variables);
|
||||||
}
|
}
|
||||||
|
else if (std::optional<TypeId> iterFn = findMetatableEntry(singletonTypes, errors, iteratorTy, "__iter", Location{}))
|
||||||
|
{
|
||||||
|
if (isBlocked(*iterFn))
|
||||||
|
{
|
||||||
|
return block(*iterFn, constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
Instantiation instantiation(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||||
|
|
||||||
|
if (std::optional<TypeId> instantiatedIterFn = instantiation.substitute(*iterFn))
|
||||||
|
{
|
||||||
|
if (auto iterFtv = get<FunctionTypeVar>(*instantiatedIterFn))
|
||||||
|
{
|
||||||
|
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
||||||
|
unify(iterFtv->argTypes, expectedIterArgs, constraint->scope);
|
||||||
|
|
||||||
|
std::vector<TypeId> iterRets = flatten(*arena, singletonTypes, iterFtv->retTypes, 2);
|
||||||
|
|
||||||
|
if (iterRets.size() < 1)
|
||||||
|
{
|
||||||
|
// We've done what we can; this will get reported as an
|
||||||
|
// error by the type checker.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId nextFn = iterRets[0];
|
||||||
|
TypeId table = iterRets.size() == 2 ? iterRets[1] : arena->freshType(constraint->scope);
|
||||||
|
|
||||||
|
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
||||||
|
{
|
||||||
|
const TypeId firstIndex = arena->freshType(constraint->scope);
|
||||||
|
|
||||||
|
// nextTy : (iteratorTy, indexTy?) -> (indexTy, valueTailTy...)
|
||||||
|
const TypePackId nextArgPack = arena->addTypePack({table, arena->addType(UnionTypeVar{{firstIndex, singletonTypes->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(*instantiatedNextFn, expectedNextTy, constraint->scope);
|
||||||
|
|
||||||
|
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(UnificationTooComplex{}, constraint->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Support __call and function overloads (what does an overload even mean for this?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(UnificationTooComplex{}, constraint->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (auto iteratorMetatable = get<MetatableTypeVar>(iteratorTy))
|
else if (auto iteratorMetatable = get<MetatableTypeVar>(iteratorTy))
|
||||||
{
|
{
|
||||||
TypeId metaTy = follow(iteratorMetatable->metatable);
|
TypeId metaTy = follow(iteratorMetatable->metatable);
|
||||||
@ -1124,7 +1184,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
|||||||
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
|
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
|
||||||
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
||||||
|
|
||||||
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{nextArgPack, nextRetPack});
|
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
|
||||||
unify(nextTy, expectedNextTy, constraint->scope);
|
unify(nextTy, expectedNextTy, constraint->scope);
|
||||||
|
|
||||||
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
||||||
@ -1297,6 +1357,7 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
|
|||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
UnifierSharedState sharedState{&iceReporter};
|
||||||
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
||||||
|
u.useScopes = true;
|
||||||
|
|
||||||
u.tryUnify(subType, superType);
|
u.tryUnify(subType, superType);
|
||||||
|
|
||||||
@ -1319,6 +1380,7 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
|
|||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
UnifierSharedState sharedState{&iceReporter};
|
||||||
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
|
||||||
|
u.useScopes = true;
|
||||||
|
|
||||||
u.tryUnify(subPack, superPack);
|
u.tryUnify(subPack, superPack);
|
||||||
|
|
||||||
|
@ -169,7 +169,10 @@ struct ErrorConverter
|
|||||||
|
|
||||||
std::string operator()(const Luau::DuplicateTypeDefinition& e) const
|
std::string operator()(const Luau::DuplicateTypeDefinition& e) const
|
||||||
{
|
{
|
||||||
return "Redefinition of type '" + e.name + "', previously defined at line " + std::to_string(e.previousLocation.begin.line + 1);
|
std::string s = "Redefinition of type '" + e.name + "'";
|
||||||
|
if (e.previousLocation)
|
||||||
|
s += ", previously defined at line " + std::to_string(e.previousLocation->begin.line + 1);
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const Luau::CountMismatch& e) const
|
std::string operator()(const Luau::CountMismatch& e) const
|
||||||
@ -183,11 +186,14 @@ struct ErrorConverter
|
|||||||
case CountMismatch::Return:
|
case CountMismatch::Return:
|
||||||
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
|
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
|
||||||
actualVerb + " returned here";
|
actualVerb + " returned here";
|
||||||
case CountMismatch::Result:
|
case CountMismatch::FunctionResult:
|
||||||
// It is alright if right hand side produces more values than the
|
// It is alright if right hand side produces more values than the
|
||||||
// left hand side accepts. In this context consider only the opposite case.
|
// left hand side accepts. In this context consider only the opposite case.
|
||||||
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
|
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
|
||||||
" are required here";
|
actualVerb + " required here";
|
||||||
|
case CountMismatch::ExprListResult:
|
||||||
|
return "Expression list has " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
|
||||||
|
actualVerb + " required here";
|
||||||
case CountMismatch::Arg:
|
case CountMismatch::Arg:
|
||||||
if (!e.function.empty())
|
if (!e.function.empty())
|
||||||
return "Argument count mismatch. Function '" + e.function + "' " +
|
return "Argument count mismatch. Function '" + e.function + "' " +
|
||||||
|
@ -400,6 +400,7 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
|
|||||||
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, singletonTypes, &iceHandler)
|
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, singletonTypes, &iceHandler)
|
||||||
, configResolver(configResolver)
|
, configResolver(configResolver)
|
||||||
, options(options)
|
, options(options)
|
||||||
|
, globalScope(typeChecker.globalScope)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +506,10 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||||||
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
|
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
|
ModulePtr moduleForAutocomplete = FFlag::DebugLuauDeferredConstraintResolution
|
||||||
|
? check(sourceModule, mode, environmentScope, requireCycles, /*forAutocomplete*/ true)
|
||||||
|
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
|
||||||
|
|
||||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||||
|
|
||||||
double duration = getTimestamp() - timestamp;
|
double duration = getTimestamp() - timestamp;
|
||||||
@ -837,7 +841,8 @@ ScopePtr Frontend::getGlobalScope()
|
|||||||
return globalScope;
|
return globalScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles)
|
ModulePtr Frontend::check(
|
||||||
|
const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles, bool forAutocomplete)
|
||||||
{
|
{
|
||||||
ModulePtr result = std::make_shared<Module>();
|
ModulePtr result = std::make_shared<Module>();
|
||||||
|
|
||||||
@ -852,7 +857,11 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), singletonTypes, NotNull(&iceHandler), getGlobalScope(), logger.get()};
|
const NotNull<ModuleResolver> mr{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver};
|
||||||
|
const ScopePtr& globalScope{forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope};
|
||||||
|
|
||||||
|
ConstraintGraphBuilder cgb{
|
||||||
|
sourceModule.name, result, &result->internalTypes, mr, singletonTypes, NotNull(&iceHandler), globalScope, logger.get()};
|
||||||
cgb.visit(sourceModule.root);
|
cgb.visit(sourceModule.root);
|
||||||
result->errors = std::move(cgb.errors);
|
result->errors = std::move(cgb.errors);
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ TypeId Instantiation::clean(TypeId ty)
|
|||||||
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
|
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
|
|
||||||
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
FunctionTypeVar clone = FunctionTypeVar{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
clone.dcrMagicFunction = ftv->dcrMagicFunction;
|
clone.dcrMagicFunction = ftv->dcrMagicFunction;
|
||||||
clone.tags = ftv->tags;
|
clone.tags = ftv->tags;
|
||||||
@ -53,7 +53,7 @@ TypeId Instantiation::clean(TypeId ty)
|
|||||||
|
|
||||||
// Annoyingly, we have to do this even if there are no generics,
|
// Annoyingly, we have to do this even if there are no generics,
|
||||||
// to replace any generic tables.
|
// to replace any generic tables.
|
||||||
ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
|
ReplaceGenerics replaceGenerics{log, arena, level, scope, ftv->generics, ftv->genericPacks};
|
||||||
|
|
||||||
// TODO: What to do if this returns nullopt?
|
// TODO: What to do if this returns nullopt?
|
||||||
// We don't have access to the error-reporting machinery
|
// We don't have access to the error-reporting machinery
|
||||||
@ -114,12 +114,12 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
|||||||
LUAU_ASSERT(isDirty(ty));
|
LUAU_ASSERT(isDirty(ty));
|
||||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
|
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, scope, TableState::Free};
|
||||||
clone.definitionModuleName = ttv->definitionModuleName;
|
clone.definitionModuleName = ttv->definitionModuleName;
|
||||||
return addType(std::move(clone));
|
return addType(std::move(clone));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return addType(FreeTypeVar{level});
|
return addType(FreeTypeVar{scope, level});
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId ReplaceGenerics::clean(TypePackId tp)
|
TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||||
|
@ -15,19 +15,6 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
|||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
/// @return true if outer encloses inner
|
|
||||||
static bool subsumes(Scope* outer, Scope* inner)
|
|
||||||
{
|
|
||||||
while (inner)
|
|
||||||
{
|
|
||||||
if (inner == outer)
|
|
||||||
return true;
|
|
||||||
inner = inner->parent.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Quantifier final : TypeVarOnceVisitor
|
struct Quantifier final : TypeVarOnceVisitor
|
||||||
{
|
{
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
@ -43,12 +30,6 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit Quantifier(Scope* scope)
|
|
||||||
: scope(scope)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return true if outer encloses inner
|
/// @return true if outer encloses inner
|
||||||
bool subsumes(Scope* outer, Scope* inner)
|
bool subsumes(Scope* outer, Scope* inner)
|
||||||
{
|
{
|
||||||
@ -66,13 +47,10 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
{
|
{
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
|
if (!level.subsumes(ftv.level))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
*asMutable(ty) = GenericTypeVar{level};
|
||||||
*asMutable(ty) = GenericTypeVar{scope};
|
|
||||||
else
|
|
||||||
*asMutable(ty) = GenericTypeVar{level};
|
|
||||||
|
|
||||||
generics.push_back(ty);
|
generics.push_back(ty);
|
||||||
|
|
||||||
@ -85,7 +63,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
|
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level))
|
if (!level.subsumes(ctv->level))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::vector<TypeId> opts = std::move(ctv->parts);
|
std::vector<TypeId> opts = std::move(ctv->parts);
|
||||||
@ -113,7 +91,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
if (ttv.state == TableState::Free)
|
if (ttv.state == TableState::Free)
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
|
if (!level.subsumes(ttv.level))
|
||||||
{
|
{
|
||||||
if (ttv.state == TableState::Unsealed)
|
if (ttv.state == TableState::Unsealed)
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
@ -137,7 +115,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||||||
{
|
{
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
|
if (!level.subsumes(ftp.level))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
*asMutable(tp) = GenericTypePack{level};
|
*asMutable(tp) = GenericTypePack{level};
|
||||||
@ -197,20 +175,6 @@ void quantify(TypeId ty, TypeLevel level)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void quantify(TypeId ty, Scope* scope)
|
|
||||||
{
|
|
||||||
Quantifier q{scope};
|
|
||||||
q.traverse(ty);
|
|
||||||
|
|
||||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
|
||||||
LUAU_ASSERT(ftv);
|
|
||||||
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
|
|
||||||
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
|
||||||
|
|
||||||
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
|
|
||||||
ftv->hasNoGenerics = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PureQuantifier : Substitution
|
struct PureQuantifier : Substitution
|
||||||
{
|
{
|
||||||
Scope* scope;
|
Scope* scope;
|
||||||
@ -253,7 +217,7 @@ struct PureQuantifier : Substitution
|
|||||||
{
|
{
|
||||||
if (auto ftv = get<FreeTypeVar>(ty))
|
if (auto ftv = get<FreeTypeVar>(ty))
|
||||||
{
|
{
|
||||||
TypeId result = arena->addType(GenericTypeVar{});
|
TypeId result = arena->addType(GenericTypeVar{scope});
|
||||||
insertedGenerics.push_back(result);
|
insertedGenerics.push_back(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -264,7 +228,8 @@ struct PureQuantifier : Substitution
|
|||||||
LUAU_ASSERT(resultTable);
|
LUAU_ASSERT(resultTable);
|
||||||
|
|
||||||
*resultTable = *ttv;
|
*resultTable = *ttv;
|
||||||
resultTable->scope = nullptr;
|
resultTable->level = TypeLevel{};
|
||||||
|
resultTable->scope = scope;
|
||||||
resultTable->state = TableState::Generic;
|
resultTable->state = TableState::Generic;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -306,6 +271,7 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
|||||||
|
|
||||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
|
ftv->scope = scope;
|
||||||
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
|
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
|
||||||
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
|
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
|
||||||
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty();
|
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty();
|
||||||
|
@ -21,6 +21,12 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
|
|||||||
level.subLevel = subLevel;
|
level.subLevel = subLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scope::addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun)
|
||||||
|
{
|
||||||
|
exportedTypeBindings[name] = tyFun;
|
||||||
|
builtinTypeNames.insert(name);
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<TypeFun> Scope::lookupType(const Name& name)
|
std::optional<TypeFun> Scope::lookupType(const Name& name)
|
||||||
{
|
{
|
||||||
const Scope* scope = this;
|
const Scope* scope = this;
|
||||||
@ -82,9 +88,9 @@ std::optional<TypePackId> Scope::lookupPack(const Name& name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bool traverseScopeChain)
|
std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bool traverseScopeChain) const
|
||||||
{
|
{
|
||||||
Scope* scope = this;
|
const Scope* scope = this;
|
||||||
|
|
||||||
while (scope)
|
while (scope)
|
||||||
{
|
{
|
||||||
@ -122,4 +128,22 @@ std::optional<TypeId> Scope::lookup(Symbol sym)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool subsumesStrict(Scope* left, Scope* right)
|
||||||
|
{
|
||||||
|
while (right)
|
||||||
|
{
|
||||||
|
if (right->parent.get() == left)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
right = right->parent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool subsumes(Scope* left, Scope* right)
|
||||||
|
{
|
||||||
|
return left == right || subsumesStrict(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
|||||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
|
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
|
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prefix generic typenames with gen-
|
* Prefix generic typenames with gen-
|
||||||
@ -632,6 +633,10 @@ struct TypeVarStringifier
|
|||||||
state.emit("{");
|
state.emit("{");
|
||||||
stringify(ttv.indexer->indexResultType);
|
stringify(ttv.indexer->indexResultType);
|
||||||
state.emit("}");
|
state.emit("}");
|
||||||
|
|
||||||
|
if (FFlag::LuauUnseeArrayTtv)
|
||||||
|
state.unsee(&ttv);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +289,45 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
|
|||||||
return newTp;
|
return newTp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty) || get<ConstrainedTypeVar>(ty));
|
||||||
|
|
||||||
|
PendingType* newTy = queue(ty);
|
||||||
|
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
|
||||||
|
{
|
||||||
|
ftv->scope = newScope;
|
||||||
|
}
|
||||||
|
else if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
||||||
|
ttv->scope = newScope;
|
||||||
|
}
|
||||||
|
else if (FunctionTypeVar* ftv = Luau::getMutable<FunctionTypeVar>(newTy))
|
||||||
|
{
|
||||||
|
ftv->scope = newScope;
|
||||||
|
}
|
||||||
|
else if (ConstrainedTypeVar* ctv = Luau::getMutable<ConstrainedTypeVar>(newTy))
|
||||||
|
{
|
||||||
|
ctv->scope = newScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTy;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(get<FreeTypePack>(tp));
|
||||||
|
|
||||||
|
PendingTypePack* newTp = queue(tp);
|
||||||
|
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
||||||
|
{
|
||||||
|
ftp->scope = newScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTp;
|
||||||
|
}
|
||||||
|
|
||||||
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(get<TableTypeVar>(ty));
|
LUAU_ASSERT(get<TableTypeVar>(ty));
|
||||||
|
@ -40,6 +40,15 @@ TypeId TypeArena::freshType(Scope* scope)
|
|||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::freshType(Scope* scope, TypeLevel level)
|
||||||
|
{
|
||||||
|
TypeId allocated = typeVars.allocate(FreeTypeVar{scope, level});
|
||||||
|
|
||||||
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId TypeArena::freshTypePack(Scope* scope)
|
TypePackId TypeArena::freshTypePack(Scope* scope)
|
||||||
{
|
{
|
||||||
TypePackId allocated = typePacks.allocate(FreeTypePack{scope});
|
TypePackId allocated = typePacks.allocate(FreeTypePack{scope});
|
||||||
|
@ -17,10 +17,16 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||||
|
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// TypeInfer.h
|
||||||
|
// TODO move these
|
||||||
|
using PrintLineProc = void(*)(const std::string&);
|
||||||
|
extern PrintLineProc luauPrintLine;
|
||||||
|
|
||||||
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance.
|
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance.
|
||||||
* TypeChecker2 uses this to maintain knowledge about which scope encloses every
|
* TypeChecker2 uses this to maintain knowledge about which scope encloses every
|
||||||
* given AstNode.
|
* given AstNode.
|
||||||
@ -114,6 +120,19 @@ struct TypeChecker2
|
|||||||
|
|
||||||
TypeId lookupAnnotation(AstType* annotation)
|
TypeId lookupAnnotation(AstType* annotation)
|
||||||
{
|
{
|
||||||
|
if (FFlag::DebugLuauMagicTypes)
|
||||||
|
{
|
||||||
|
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->name == "_luau_print" && ref->parameters.size > 0)
|
||||||
|
{
|
||||||
|
if (auto ann = ref->parameters.data[0].type)
|
||||||
|
{
|
||||||
|
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type);
|
||||||
|
luauPrintLine(format("_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str()));
|
||||||
|
return follow(argTy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypeId* ty = module->astResolvedTypes.find(annotation);
|
TypeId* ty = module->astResolvedTypes.find(annotation);
|
||||||
LUAU_ASSERT(ty);
|
LUAU_ASSERT(ty);
|
||||||
return follow(*ty);
|
return follow(*ty);
|
||||||
@ -284,50 +303,49 @@ struct TypeChecker2
|
|||||||
|
|
||||||
void visit(AstStatLocal* local)
|
void visit(AstStatLocal* local)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < local->values.size; ++i)
|
size_t count = std::max(local->values.size, local->vars.size);
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
AstExpr* value = local->values.data[i];
|
AstExpr* value = i < local->values.size ? local->values.data[i] : nullptr;
|
||||||
|
|
||||||
visit(value);
|
if (value)
|
||||||
|
visit(value);
|
||||||
|
|
||||||
if (i == local->values.size - 1)
|
if (i != local->values.size - 1)
|
||||||
{
|
{
|
||||||
if (i < local->values.size)
|
AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr;
|
||||||
|
|
||||||
|
if (var && var->annotation)
|
||||||
{
|
{
|
||||||
TypePackId valueTypes = lookupPack(value);
|
TypeId varType = lookupAnnotation(var->annotation);
|
||||||
auto it = begin(valueTypes);
|
TypeId valueType = value ? lookupType(value) : nullptr;
|
||||||
for (size_t j = i; j < local->vars.size; ++j)
|
if (valueType && !isSubtype(varType, valueType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
|
||||||
{
|
reportError(TypeMismatch{varType, valueType}, value->location);
|
||||||
if (it == end(valueTypes))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AstLocal* var = local->vars.data[i];
|
|
||||||
if (var->annotation)
|
|
||||||
{
|
|
||||||
TypeId varType = lookupAnnotation(var->annotation);
|
|
||||||
ErrorVec errors = tryUnify(stack.back(), value->location, *it, varType);
|
|
||||||
if (!errors.empty())
|
|
||||||
reportErrors(std::move(errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId valueType = lookupType(value);
|
LUAU_ASSERT(value);
|
||||||
AstLocal* var = local->vars.data[i];
|
|
||||||
|
|
||||||
if (var->annotation)
|
TypePackId valueTypes = lookupPack(value);
|
||||||
|
auto it = begin(valueTypes);
|
||||||
|
for (size_t j = i; j < local->vars.size; ++j)
|
||||||
{
|
{
|
||||||
TypeId varType = lookupAnnotation(var->annotation);
|
if (it == end(valueTypes))
|
||||||
if (!isSubtype(varType, valueType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
|
|
||||||
{
|
{
|
||||||
reportError(TypeMismatch{varType, valueType}, value->location);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstLocal* var = local->vars.data[i];
|
||||||
|
if (var->annotation)
|
||||||
|
{
|
||||||
|
TypeId varType = lookupAnnotation(var->annotation);
|
||||||
|
ErrorVec errors = tryUnify(stack.back(), value->location, *it, varType);
|
||||||
|
if (!errors.empty())
|
||||||
|
reportErrors(std::move(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,50 +363,6 @@ 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.
|
|
||||||
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(singletonTypes->errorRecoveryType());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void visit(AstStatForIn* forInStatement)
|
void visit(AstStatForIn* forInStatement)
|
||||||
{
|
{
|
||||||
for (AstLocal* local : forInStatement->vars)
|
for (AstLocal* local : forInStatement->vars)
|
||||||
@ -426,7 +400,7 @@ struct TypeChecker2
|
|||||||
TypePackId iteratorPack = arena.addTypePack(valueTypes, iteratorTail);
|
TypePackId iteratorPack = arena.addTypePack(valueTypes, iteratorTail);
|
||||||
|
|
||||||
// ... and then expand it out to 3 values (if possible)
|
// ... and then expand it out to 3 values (if possible)
|
||||||
const std::vector<TypeId> iteratorTypes = flatten(arena, iteratorPack, 3);
|
const std::vector<TypeId> iteratorTypes = flatten(arena, singletonTypes, iteratorPack, 3);
|
||||||
if (iteratorTypes.empty())
|
if (iteratorTypes.empty())
|
||||||
{
|
{
|
||||||
reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values));
|
reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values));
|
||||||
@ -434,6 +408,72 @@ struct TypeChecker2
|
|||||||
}
|
}
|
||||||
TypeId iteratorTy = follow(iteratorTypes[0]);
|
TypeId iteratorTy = follow(iteratorTypes[0]);
|
||||||
|
|
||||||
|
auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes](const FunctionTypeVar* iterFtv, std::vector<TypeId> iterTys, bool isMm)
|
||||||
|
{
|
||||||
|
if (iterTys.size() < 1 || iterTys.size() > 3)
|
||||||
|
{
|
||||||
|
if (isMm)
|
||||||
|
reportError(GenericError{"__iter metamethod must return (next[, table[, state]])"}, getLocation(forInStatement->values));
|
||||||
|
else
|
||||||
|
reportError(GenericError{"for..in loops must be passed (next[, table[, state]])"}, getLocation(forInStatement->values));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
|
||||||
|
std::vector<TypeId> expectedVariableTypes = flatten(arena, singletonTypes, iterFtv->retTypes, variableTypes.size());
|
||||||
|
if (expectedVariableTypes.size() < variableTypes.size())
|
||||||
|
{
|
||||||
|
if (isMm)
|
||||||
|
reportError(GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values));
|
||||||
|
else
|
||||||
|
reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), iterFtv->argTypes, /*includeHiddenVariadics*/ true);
|
||||||
|
|
||||||
|
if (minCount > 2)
|
||||||
|
reportError(CountMismatch{2, std::nullopt, minCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
if (maxCount && *maxCount < 2)
|
||||||
|
reportError(CountMismatch{2, std::nullopt, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
|
||||||
|
const std::vector<TypeId> flattenedArgTypes = flatten(arena, singletonTypes, iterFtv->argTypes, 2);
|
||||||
|
size_t firstIterationArgCount = iterTys.empty() ? 0 : iterTys.size() - 1;
|
||||||
|
size_t actualArgCount = expectedVariableTypes.size();
|
||||||
|
|
||||||
|
if (firstIterationArgCount < minCount)
|
||||||
|
reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
else if (actualArgCount < minCount)
|
||||||
|
reportError(CountMismatch{2, std::nullopt, actualArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||||
|
|
||||||
|
if (iterTys.size() >= 2 && flattenedArgTypes.size() > 0)
|
||||||
|
{
|
||||||
|
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[1], flattenedArgTypes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iterTys.size() == 3 && flattenedArgTypes.size() > 1)
|
||||||
|
{
|
||||||
|
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[2], flattenedArgTypes[1]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the first iterator argument is a function
|
* If the first iterator argument is a function
|
||||||
* * There must be 1 to 3 iterator arguments. Name them (nextTy,
|
* * There must be 1 to 3 iterator arguments. Name them (nextTy,
|
||||||
@ -451,58 +491,7 @@ struct TypeChecker2
|
|||||||
*/
|
*/
|
||||||
if (const FunctionTypeVar* nextFn = get<FunctionTypeVar>(iteratorTy))
|
if (const FunctionTypeVar* nextFn = get<FunctionTypeVar>(iteratorTy))
|
||||||
{
|
{
|
||||||
if (iteratorTypes.size() < 1 || iteratorTypes.size() > 3)
|
checkFunction(nextFn, iteratorTypes, false);
|
||||||
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(arena, 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, /*includeHiddenVariadics*/ true);
|
|
||||||
|
|
||||||
if (minCount > 2)
|
|
||||||
reportError(CountMismatch{2, std::nullopt, minCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
|
||||||
if (maxCount && *maxCount < 2)
|
|
||||||
reportError(CountMismatch{2, std::nullopt, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
|
||||||
|
|
||||||
const std::vector<TypeId> flattenedArgTypes = flatten(arena, 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, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
|
||||||
else if (actualArgCount < minCount)
|
|
||||||
reportError(CountMismatch{2, std::nullopt, 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))
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(iteratorTy))
|
||||||
{
|
{
|
||||||
@ -519,6 +508,62 @@ struct TypeChecker2
|
|||||||
{
|
{
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
else if (std::optional<TypeId> iterMmTy = findMetatableEntry(singletonTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location))
|
||||||
|
{
|
||||||
|
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, scope};
|
||||||
|
|
||||||
|
if (std::optional<TypeId> instantiatedIterMmTy = instantiation.substitute(*iterMmTy))
|
||||||
|
{
|
||||||
|
if (const FunctionTypeVar* iterMmFtv = get<FunctionTypeVar>(*instantiatedIterMmTy))
|
||||||
|
{
|
||||||
|
TypePackId argPack = arena.addTypePack({iteratorTy});
|
||||||
|
reportErrors(tryUnify(scope, forInStatement->values.data[0]->location, argPack, iterMmFtv->argTypes));
|
||||||
|
|
||||||
|
std::vector<TypeId> mmIteratorTypes = flatten(arena, singletonTypes, iterMmFtv->retTypes, 3);
|
||||||
|
|
||||||
|
if (mmIteratorTypes.size() == 0)
|
||||||
|
{
|
||||||
|
reportError(GenericError{"__iter must return at least one value"}, forInStatement->values.data[0]->location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId nextFn = follow(mmIteratorTypes[0]);
|
||||||
|
|
||||||
|
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
||||||
|
{
|
||||||
|
std::vector<TypeId> instantiatedIteratorTypes = mmIteratorTypes;
|
||||||
|
instantiatedIteratorTypes[0] = *instantiatedNextFn;
|
||||||
|
|
||||||
|
if (const FunctionTypeVar* nextFtv = get<FunctionTypeVar>(*instantiatedNextFn))
|
||||||
|
{
|
||||||
|
checkFunction(nextFtv, instantiatedIteratorTypes, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(CannotCallNonFunction{*instantiatedNextFn}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(UnificationTooComplex{}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: This will not tell the user that this is because the
|
||||||
|
// metamethod isn't callable. This is not ideal, and we should
|
||||||
|
// improve this error message.
|
||||||
|
|
||||||
|
// TODO: This will also not handle intersections of functions or
|
||||||
|
// callable tables (which are supported by the runtime).
|
||||||
|
reportError(CannotCallNonFunction{*iterMmTy}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(UnificationTooComplex{}, forInStatement->values.data[0]->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
|
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
|
||||||
@ -730,7 +775,7 @@ struct TypeChecker2
|
|||||||
visit(arg);
|
visit(arg);
|
||||||
|
|
||||||
TypeArena arena;
|
TypeArena arena;
|
||||||
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}};
|
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, stack.back()};
|
||||||
|
|
||||||
TypePackId expectedRetType = lookupPack(call);
|
TypePackId expectedRetType = lookupPack(call);
|
||||||
TypeId functionType = lookupType(call->func);
|
TypeId functionType = lookupType(call->func);
|
||||||
|
@ -47,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
|
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -66,9 +68,7 @@ static void defaultLuauPrintLine(const std::string& s)
|
|||||||
printf("%s\n", s.c_str());
|
printf("%s\n", s.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
using PrintLineProc = decltype(&defaultLuauPrintLine);
|
PrintLineProc luauPrintLine = &defaultLuauPrintLine;
|
||||||
|
|
||||||
static PrintLineProc luauPrintLine = &defaultLuauPrintLine;
|
|
||||||
|
|
||||||
void setPrintLine(PrintLineProc pl)
|
void setPrintLine(PrintLineProc pl)
|
||||||
{
|
{
|
||||||
@ -270,16 +270,16 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<SingletonTypes> singl
|
|||||||
{
|
{
|
||||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||||
|
|
||||||
globalScope->exportedTypeBindings["any"] = TypeFun{{}, anyType};
|
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, anyType});
|
||||||
globalScope->exportedTypeBindings["nil"] = TypeFun{{}, nilType};
|
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, nilType});
|
||||||
globalScope->exportedTypeBindings["number"] = TypeFun{{}, numberType};
|
globalScope->addBuiltinTypeBinding("number", TypeFun{{}, numberType});
|
||||||
globalScope->exportedTypeBindings["string"] = TypeFun{{}, stringType};
|
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, stringType});
|
||||||
globalScope->exportedTypeBindings["boolean"] = TypeFun{{}, booleanType};
|
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, booleanType});
|
||||||
globalScope->exportedTypeBindings["thread"] = TypeFun{{}, threadType};
|
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, threadType});
|
||||||
if (FFlag::LuauUnknownAndNeverType)
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
{
|
{
|
||||||
globalScope->exportedTypeBindings["unknown"] = TypeFun{{}, unknownType};
|
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, unknownType});
|
||||||
globalScope->exportedTypeBindings["never"] = TypeFun{{}, neverType};
|
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, neverType});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,7 +534,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
|||||||
{
|
{
|
||||||
if (const auto& typealias = stat->as<AstStatTypeAlias>())
|
if (const auto& typealias = stat->as<AstStatTypeAlias>())
|
||||||
{
|
{
|
||||||
check(scope, *typealias, subLevel, true);
|
prototype(scope, *typealias, subLevel);
|
||||||
++subLevel;
|
++subLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -698,6 +698,10 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
|
|||||||
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||||
|
|
||||||
Name name = typealias->name.value;
|
Name name = typealias->name.value;
|
||||||
|
|
||||||
|
if (FFlag::LuauReportShadowedTypeAlias && duplicateTypeAliases.contains({typealias->exported, name}))
|
||||||
|
continue;
|
||||||
|
|
||||||
TypeId type = bindings[name].type;
|
TypeId type = bindings[name].type;
|
||||||
if (get<FreeTypeVar>(follow(type)))
|
if (get<FreeTypeVar>(follow(type)))
|
||||||
{
|
{
|
||||||
@ -1109,8 +1113,23 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
|
|||||||
TypePackId valuePack =
|
TypePackId valuePack =
|
||||||
checkExprList(scope, local.location, local.values, /* substituteFreeForNil= */ true, instantiateGenerics, expectedTypes).type;
|
checkExprList(scope, local.location, local.values, /* substituteFreeForNil= */ true, instantiateGenerics, expectedTypes).type;
|
||||||
|
|
||||||
|
// If the expression list only contains one expression and it's a function call or is otherwise within parentheses, use FunctionResult.
|
||||||
|
// Otherwise, we'll want to use ExprListResult to make the error messaging more general.
|
||||||
|
CountMismatch::Context ctx = FFlag::LuauBetterMessagingOnCountMismatch ? CountMismatch::ExprListResult : CountMismatch::FunctionResult;
|
||||||
|
if (FFlag::LuauBetterMessagingOnCountMismatch)
|
||||||
|
{
|
||||||
|
if (local.values.size == 1)
|
||||||
|
{
|
||||||
|
AstExpr* e = local.values.data[0];
|
||||||
|
while (auto group = e->as<AstExprGroup>())
|
||||||
|
e = group->expr;
|
||||||
|
if (e->is<AstExprCall>())
|
||||||
|
ctx = CountMismatch::FunctionResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Unifier state = mkUnifier(scope, local.location);
|
Unifier state = mkUnifier(scope, local.location);
|
||||||
state.ctx = CountMismatch::Result;
|
state.ctx = ctx;
|
||||||
state.tryUnify(valuePack, variablePack);
|
state.tryUnify(valuePack, variablePack);
|
||||||
reportErrors(state.errors);
|
reportErrors(state.errors);
|
||||||
|
|
||||||
@ -1472,10 +1491,8 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
|||||||
scope->bindings[function.name] = {quantify(funScope, ty, function.name->location), function.name->location};
|
scope->bindings[function.name] = {quantify(funScope, ty, function.name->location), function.name->location};
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel, bool forwardDeclare)
|
void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias)
|
||||||
{
|
{
|
||||||
// This function should be called at most twice for each type alias.
|
|
||||||
// Once with forwardDeclare, and once without.
|
|
||||||
Name name = typealias.name.value;
|
Name name = typealias.name.value;
|
||||||
|
|
||||||
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
||||||
@ -1490,14 +1507,134 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||||||
|
|
||||||
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||||
|
|
||||||
if (forwardDeclare)
|
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
|
||||||
{
|
// interesting.
|
||||||
if (binding)
|
if (duplicateTypeAliases.find({typealias.exported, name}))
|
||||||
{
|
return;
|
||||||
Location location = scope->typeAliasLocations[name];
|
|
||||||
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
|
||||||
|
|
||||||
|
// By now this alias must have been `prototype()`d first.
|
||||||
|
if (!binding)
|
||||||
|
ice("Not predeclared");
|
||||||
|
|
||||||
|
ScopePtr aliasScope = childScope(scope, typealias.location);
|
||||||
|
aliasScope->level = scope->level.incr();
|
||||||
|
|
||||||
|
for (auto param : binding->typeParams)
|
||||||
|
{
|
||||||
|
auto generic = get<GenericTypeVar>(param.ty);
|
||||||
|
LUAU_ASSERT(generic);
|
||||||
|
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, param.ty};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto param : binding->typePackParams)
|
||||||
|
{
|
||||||
|
auto generic = get<GenericTypePack>(param.tp);
|
||||||
|
LUAU_ASSERT(generic);
|
||||||
|
aliasScope->privateTypePackBindings[generic->name] = param.tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ty = resolveType(aliasScope, *typealias.type);
|
||||||
|
if (auto ttv = getMutable<TableTypeVar>(follow(ty)))
|
||||||
|
{
|
||||||
|
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy
|
||||||
|
// Additionally, we can't modify types that come from other modules
|
||||||
|
if (ttv->name || follow(ty)->owningArena != ¤tModule->internalTypes)
|
||||||
|
{
|
||||||
|
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
|
||||||
|
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
|
||||||
|
return itp == tp.ty;
|
||||||
|
});
|
||||||
|
bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(), binding->typePackParams.begin(),
|
||||||
|
binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
|
||||||
|
return itpp == tpp.tp;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy can be skipped if this is an identical alias
|
||||||
|
if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
|
||||||
|
{
|
||||||
|
// This is a shallow clone, original recursive links to self are not updated
|
||||||
|
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||||
|
clone.definitionModuleName = ttv->definitionModuleName;
|
||||||
|
clone.name = name;
|
||||||
|
|
||||||
|
for (auto param : binding->typeParams)
|
||||||
|
clone.instantiatedTypeParams.push_back(param.ty);
|
||||||
|
|
||||||
|
for (auto param : binding->typePackParams)
|
||||||
|
clone.instantiatedTypePackParams.push_back(param.tp);
|
||||||
|
|
||||||
|
bool isNormal = ty->normal;
|
||||||
|
ty = addType(std::move(clone));
|
||||||
|
|
||||||
|
if (FFlag::LuauLowerBoundsCalculation)
|
||||||
|
asMutable(ty)->normal = isNormal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ttv->name = name;
|
||||||
|
|
||||||
|
ttv->instantiatedTypeParams.clear();
|
||||||
|
for (auto param : binding->typeParams)
|
||||||
|
ttv->instantiatedTypeParams.push_back(param.ty);
|
||||||
|
|
||||||
|
ttv->instantiatedTypePackParams.clear();
|
||||||
|
for (auto param : binding->typePackParams)
|
||||||
|
ttv->instantiatedTypePackParams.push_back(param.tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||||
|
{
|
||||||
|
// We can't modify types that come from other modules
|
||||||
|
if (follow(ty)->owningArena == ¤tModule->internalTypes)
|
||||||
|
mtv->syntheticName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId& bindingType = bindingsMap[name].type;
|
||||||
|
|
||||||
|
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||||
|
bindingType = ty;
|
||||||
|
|
||||||
|
if (FFlag::LuauLowerBoundsCalculation)
|
||||||
|
{
|
||||||
|
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
|
||||||
|
bindingType = t;
|
||||||
|
if (!ok)
|
||||||
|
reportError(typealias.location, NormalizationTooComplex{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel)
|
||||||
|
{
|
||||||
|
Name name = typealias.name.value;
|
||||||
|
|
||||||
|
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
||||||
|
if (name == kParseNameError)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::optional<TypeFun> binding;
|
||||||
|
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
|
||||||
|
binding = it->second;
|
||||||
|
else if (auto it = scope->privateTypeBindings.find(name); it != scope->privateTypeBindings.end())
|
||||||
|
binding = it->second;
|
||||||
|
|
||||||
|
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||||
|
|
||||||
|
if (binding)
|
||||||
|
{
|
||||||
|
Location location = scope->typeAliasLocations[name];
|
||||||
|
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
||||||
|
|
||||||
|
if (!FFlag::LuauReportShadowedTypeAlias)
|
||||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||||
|
|
||||||
|
duplicateTypeAliases.insert({typealias.exported, name});
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauReportShadowedTypeAlias)
|
||||||
|
{
|
||||||
|
if (globalScope->builtinTypeNames.contains(name))
|
||||||
|
{
|
||||||
|
reportError(typealias.location, DuplicateTypeDefinition{name});
|
||||||
duplicateTypeAliases.insert({typealias.exported, name});
|
duplicateTypeAliases.insert({typealias.exported, name});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1520,100 +1657,20 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
|
|
||||||
// interesting.
|
|
||||||
if (duplicateTypeAliases.find({typealias.exported, name}))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!binding)
|
|
||||||
ice("Not predeclared");
|
|
||||||
|
|
||||||
ScopePtr aliasScope = childScope(scope, typealias.location);
|
ScopePtr aliasScope = childScope(scope, typealias.location);
|
||||||
aliasScope->level = scope->level.incr();
|
aliasScope->level = scope->level.incr();
|
||||||
|
aliasScope->level.subLevel = subLevel;
|
||||||
|
|
||||||
for (auto param : binding->typeParams)
|
auto [generics, genericPacks] =
|
||||||
{
|
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
|
||||||
auto generic = get<GenericTypeVar>(param.ty);
|
|
||||||
LUAU_ASSERT(generic);
|
|
||||||
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, param.ty};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto param : binding->typePackParams)
|
TypeId ty = freshType(aliasScope);
|
||||||
{
|
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
|
||||||
auto generic = get<GenericTypePack>(param.tp);
|
LUAU_ASSERT(ftv);
|
||||||
LUAU_ASSERT(generic);
|
ftv->forwardedTypeAlias = true;
|
||||||
aliasScope->privateTypePackBindings[generic->name] = param.tp;
|
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||||
}
|
|
||||||
|
|
||||||
TypeId ty = resolveType(aliasScope, *typealias.type);
|
scope->typeAliasLocations[name] = typealias.location;
|
||||||
if (auto ttv = getMutable<TableTypeVar>(follow(ty)))
|
|
||||||
{
|
|
||||||
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy
|
|
||||||
// Additionally, we can't modify types that come from other modules
|
|
||||||
if (ttv->name || follow(ty)->owningArena != ¤tModule->internalTypes)
|
|
||||||
{
|
|
||||||
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
|
|
||||||
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
|
|
||||||
return itp == tp.ty;
|
|
||||||
});
|
|
||||||
bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(),
|
|
||||||
binding->typePackParams.begin(), binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
|
|
||||||
return itpp == tpp.tp;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy can be skipped if this is an identical alias
|
|
||||||
if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
|
|
||||||
{
|
|
||||||
// This is a shallow clone, original recursive links to self are not updated
|
|
||||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
|
||||||
clone.definitionModuleName = ttv->definitionModuleName;
|
|
||||||
clone.name = name;
|
|
||||||
|
|
||||||
for (auto param : binding->typeParams)
|
|
||||||
clone.instantiatedTypeParams.push_back(param.ty);
|
|
||||||
|
|
||||||
for (auto param : binding->typePackParams)
|
|
||||||
clone.instantiatedTypePackParams.push_back(param.tp);
|
|
||||||
|
|
||||||
bool isNormal = ty->normal;
|
|
||||||
ty = addType(std::move(clone));
|
|
||||||
|
|
||||||
if (FFlag::LuauLowerBoundsCalculation)
|
|
||||||
asMutable(ty)->normal = isNormal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ttv->name = name;
|
|
||||||
|
|
||||||
ttv->instantiatedTypeParams.clear();
|
|
||||||
for (auto param : binding->typeParams)
|
|
||||||
ttv->instantiatedTypeParams.push_back(param.ty);
|
|
||||||
|
|
||||||
ttv->instantiatedTypePackParams.clear();
|
|
||||||
for (auto param : binding->typePackParams)
|
|
||||||
ttv->instantiatedTypePackParams.push_back(param.tp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
|
||||||
{
|
|
||||||
// We can't modify types that come from other modules
|
|
||||||
if (follow(ty)->owningArena == ¤tModule->internalTypes)
|
|
||||||
mtv->syntheticName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeId& bindingType = bindingsMap[name].type;
|
|
||||||
|
|
||||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
|
||||||
bindingType = ty;
|
|
||||||
|
|
||||||
if (FFlag::LuauLowerBoundsCalculation)
|
|
||||||
{
|
|
||||||
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
|
|
||||||
bindingType = t;
|
|
||||||
if (!ok)
|
|
||||||
reportError(typealias.location, NormalizationTooComplex{});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4152,7 +4209,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
|
|||||||
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
|
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
|
||||||
|
|
||||||
TxnLog log;
|
TxnLog log;
|
||||||
promoteTypeLevels(log, ¤tModule->internalTypes, level, retPack);
|
promoteTypeLevels(log, ¤tModule->internalTypes, level, /*scope*/ nullptr, /*useScope*/ false, retPack);
|
||||||
log.commit();
|
log.commit();
|
||||||
|
|
||||||
*asMutable(fn) = FunctionTypeVar{level, adjustedArgPack, retPack};
|
*asMutable(fn) = FunctionTypeVar{level, adjustedArgPack, retPack};
|
||||||
@ -4712,7 +4769,7 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
|
|||||||
if (ftv && ftv->hasNoGenerics)
|
if (ftv && ftv->hasNoGenerics)
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level};
|
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level, /*scope*/ nullptr};
|
||||||
|
|
||||||
if (FFlag::LuauAutocompleteDynamicLimits && instantiationChildLimit)
|
if (FFlag::LuauAutocompleteDynamicLimits && instantiationChildLimit)
|
||||||
instantiation.childLimit = *instantiationChildLimit;
|
instantiation.childLimit = *instantiationChildLimit;
|
||||||
|
@ -224,4 +224,46 @@ std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log,
|
|||||||
return {minCount, minCount + optionalCount};
|
return {minCount, minCount + optionalCount};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<TypeId> flatten(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, 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(singletonTypes->errorRecoveryType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -474,6 +474,17 @@ FunctionTypeVar::FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FunctionTypeVar::FunctionTypeVar(
|
||||||
|
TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
|
: level(level)
|
||||||
|
, scope(scope)
|
||||||
|
, argTypes(argTypes)
|
||||||
|
, retTypes(retTypes)
|
||||||
|
, definition(std::move(defn))
|
||||||
|
, hasSelf(hasSelf)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
FunctionTypeVar::FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
FunctionTypeVar::FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn, bool hasSelf)
|
std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
: generics(generics)
|
: generics(generics)
|
||||||
@ -497,9 +508,23 @@ FunctionTypeVar::FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics,
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TableTypeVar::TableTypeVar(TableState state, TypeLevel level)
|
FunctionTypeVar::FunctionTypeVar(TypeLevel level, Scope* scope, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks,
|
||||||
|
TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
|
: level(level)
|
||||||
|
, scope(scope)
|
||||||
|
, generics(generics)
|
||||||
|
, genericPacks(genericPacks)
|
||||||
|
, argTypes(argTypes)
|
||||||
|
, retTypes(retTypes)
|
||||||
|
, definition(std::move(defn))
|
||||||
|
, hasSelf(hasSelf)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TableTypeVar::TableTypeVar(TableState state, TypeLevel level, Scope* scope)
|
||||||
: state(state)
|
: state(state)
|
||||||
, level(level)
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,6 +536,15 @@ TableTypeVar::TableTypeVar(const Props& props, const std::optional<TableIndexer>
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableTypeVar::TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, Scope* scope, TableState state)
|
||||||
|
: props(props)
|
||||||
|
, indexer(indexer)
|
||||||
|
, state(state)
|
||||||
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// Test TypeVars for equivalence
|
// Test TypeVars for equivalence
|
||||||
// More complex than we'd like because TypeVars can self-reference.
|
// More complex than we'd like because TypeVars can self-reference.
|
||||||
|
|
||||||
|
@ -18,6 +18,13 @@ Free::Free(Scope* scope)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Free::Free(Scope* scope, TypeLevel level)
|
||||||
|
: index(++nextIndex)
|
||||||
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
int Free::nextIndex = 0;
|
int Free::nextIndex = 0;
|
||||||
|
|
||||||
Generic::Generic()
|
Generic::Generic()
|
||||||
|
@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
|||||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||||
LUAU_FASTFLAG(LuauCallUnifyPackTails)
|
LUAU_FASTFLAG(LuauCallUnifyPackTails)
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -33,10 +34,15 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||||||
const TypeArena* typeArena = nullptr;
|
const TypeArena* typeArena = nullptr;
|
||||||
TypeLevel minLevel;
|
TypeLevel minLevel;
|
||||||
|
|
||||||
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
|
Scope* outerScope = nullptr;
|
||||||
|
bool useScopes;
|
||||||
|
|
||||||
|
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes)
|
||||||
: log(log)
|
: log(log)
|
||||||
, typeArena(typeArena)
|
, typeArena(typeArena)
|
||||||
, minLevel(minLevel)
|
, minLevel(minLevel)
|
||||||
|
, outerScope(outerScope)
|
||||||
|
, useScopes(useScopes)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +50,18 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||||||
void promote(TID ty, T* t)
|
void promote(TID ty, T* t)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(t);
|
LUAU_ASSERT(t);
|
||||||
if (minLevel.subsumesStrict(t->level))
|
|
||||||
|
if (useScopes)
|
||||||
{
|
{
|
||||||
log.changeLevel(ty, minLevel);
|
if (subsumesStrict(outerScope, t->scope))
|
||||||
|
log.changeScope(ty, NotNull{outerScope});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (minLevel.subsumesStrict(t->level))
|
||||||
|
{
|
||||||
|
log.changeLevel(ty, minLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,23 +138,23 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
|
static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes, TypeId ty)
|
||||||
{
|
{
|
||||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||||
if (ty->owningArena != typeArena)
|
if (ty->owningArena != typeArena)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes};
|
||||||
ptl.traverse(ty);
|
ptl.traverse(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
|
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes, TypePackId tp)
|
||||||
{
|
{
|
||||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||||
if (tp->owningArena != typeArena)
|
if (tp->owningArena != typeArena)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes};
|
||||||
ptl.traverse(tp);
|
ptl.traverse(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +333,16 @@ static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMat
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Inline and clip with FFlag::DebugLuauDeferredConstraintResolution
|
||||||
|
template<typename TY_A, typename TY_B>
|
||||||
|
static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
|
||||||
|
{
|
||||||
|
if (useScopes)
|
||||||
|
return subsumes(left->scope, right->scope);
|
||||||
|
else
|
||||||
|
return left->level.subsumes(right->level);
|
||||||
|
}
|
||||||
|
|
||||||
Unifier::Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location,
|
Unifier::Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location,
|
||||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
||||||
: types(types)
|
: types(types)
|
||||||
@ -375,7 +400,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
auto superFree = log.getMutable<FreeTypeVar>(superTy);
|
auto superFree = log.getMutable<FreeTypeVar>(superTy);
|
||||||
auto subFree = log.getMutable<FreeTypeVar>(subTy);
|
auto subFree = log.getMutable<FreeTypeVar>(subTy);
|
||||||
|
|
||||||
if (superFree && subFree && superFree->level.subsumes(subFree->level))
|
if (superFree && subFree && subsumes(useScopes, superFree, subFree))
|
||||||
{
|
{
|
||||||
if (!occursCheck(subTy, superTy))
|
if (!occursCheck(subTy, superTy))
|
||||||
log.replace(subTy, BoundTypeVar(superTy));
|
log.replace(subTy, BoundTypeVar(superTy));
|
||||||
@ -386,7 +411,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
{
|
{
|
||||||
if (!occursCheck(superTy, subTy))
|
if (!occursCheck(superTy, subTy))
|
||||||
{
|
{
|
||||||
if (superFree->level.subsumes(subFree->level))
|
if (subsumes(useScopes, superFree, subFree))
|
||||||
{
|
{
|
||||||
log.changeLevel(subTy, superFree->level);
|
log.changeLevel(subTy, superFree->level);
|
||||||
}
|
}
|
||||||
@ -400,7 +425,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
{
|
{
|
||||||
// Unification can't change the level of a generic.
|
// Unification can't change the level of a generic.
|
||||||
auto subGeneric = log.getMutable<GenericTypeVar>(subTy);
|
auto subGeneric = log.getMutable<GenericTypeVar>(subTy);
|
||||||
if (subGeneric && !subGeneric->level.subsumes(superFree->level))
|
if (subGeneric && !subsumes(useScopes, subGeneric, superFree))
|
||||||
{
|
{
|
||||||
// TODO: a more informative error message? CLI-39912
|
// TODO: a more informative error message? CLI-39912
|
||||||
reportError(TypeError{location, GenericError{"Generic subtype escaping scope"}});
|
reportError(TypeError{location, GenericError{"Generic subtype escaping scope"}});
|
||||||
@ -409,7 +434,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
|
|
||||||
if (!occursCheck(superTy, subTy))
|
if (!occursCheck(superTy, subTy))
|
||||||
{
|
{
|
||||||
promoteTypeLevels(log, types, superFree->level, subTy);
|
promoteTypeLevels(log, types, superFree->level, superFree->scope, useScopes, subTy);
|
||||||
|
|
||||||
Widen widen{types, singletonTypes};
|
Widen widen{types, singletonTypes};
|
||||||
log.replace(superTy, BoundTypeVar(widen(subTy)));
|
log.replace(superTy, BoundTypeVar(widen(subTy)));
|
||||||
@ -429,7 +454,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
|
|
||||||
// Unification can't change the level of a generic.
|
// Unification can't change the level of a generic.
|
||||||
auto superGeneric = log.getMutable<GenericTypeVar>(superTy);
|
auto superGeneric = log.getMutable<GenericTypeVar>(superTy);
|
||||||
if (superGeneric && !superGeneric->level.subsumes(subFree->level))
|
if (superGeneric && !subsumes(useScopes, superGeneric, subFree))
|
||||||
{
|
{
|
||||||
// TODO: a more informative error message? CLI-39912
|
// TODO: a more informative error message? CLI-39912
|
||||||
reportError(TypeError{location, GenericError{"Generic supertype escaping scope"}});
|
reportError(TypeError{location, GenericError{"Generic supertype escaping scope"}});
|
||||||
@ -438,7 +463,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||||||
|
|
||||||
if (!occursCheck(subTy, superTy))
|
if (!occursCheck(subTy, superTy))
|
||||||
{
|
{
|
||||||
promoteTypeLevels(log, types, subFree->level, superTy);
|
promoteTypeLevels(log, types, subFree->level, subFree->scope, useScopes, superTy);
|
||||||
log.replace(subTy, BoundTypeVar(superTy));
|
log.replace(subTy, BoundTypeVar(superTy));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -855,6 +880,7 @@ struct WeirdIter
|
|||||||
size_t index;
|
size_t index;
|
||||||
bool growing;
|
bool growing;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
Scope* scope = nullptr;
|
||||||
|
|
||||||
WeirdIter(TypePackId packId, TxnLog& log)
|
WeirdIter(TypePackId packId, TxnLog& log)
|
||||||
: packId(packId)
|
: packId(packId)
|
||||||
@ -915,6 +941,7 @@ struct WeirdIter
|
|||||||
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
|
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
|
||||||
|
|
||||||
level = log.getMutable<Unifiable::Free>(packId)->level;
|
level = log.getMutable<Unifiable::Free>(packId)->level;
|
||||||
|
scope = log.getMutable<Unifiable::Free>(packId)->scope;
|
||||||
log.replace(packId, BoundTypePack(newTail));
|
log.replace(packId, BoundTypePack(newTail));
|
||||||
packId = newTail;
|
packId = newTail;
|
||||||
pack = log.getMutable<TypePack>(newTail);
|
pack = log.getMutable<TypePack>(newTail);
|
||||||
@ -1055,8 +1082,8 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
auto superIter = WeirdIter(superTp, log);
|
auto superIter = WeirdIter(superTp, log);
|
||||||
auto subIter = WeirdIter(subTp, log);
|
auto subIter = WeirdIter(subTp, log);
|
||||||
|
|
||||||
auto mkFreshType = [this](TypeLevel level) {
|
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
|
||||||
return types->freshType(level);
|
return types->freshType(scope, level);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
||||||
@ -1072,12 +1099,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
|
|
||||||
if (superIter.good() && subIter.growing)
|
if (superIter.good() && subIter.growing)
|
||||||
{
|
{
|
||||||
subIter.pushType(mkFreshType(subIter.level));
|
subIter.pushType(mkFreshType(subIter.scope, subIter.level));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subIter.good() && superIter.growing)
|
if (subIter.good() && superIter.growing)
|
||||||
{
|
{
|
||||||
superIter.pushType(mkFreshType(superIter.level));
|
superIter.pushType(mkFreshType(superIter.scope, superIter.level));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (superIter.good() && subIter.good())
|
if (superIter.good() && subIter.good())
|
||||||
@ -1158,7 +1185,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||||||
// these to produce the expected error message.
|
// these to produce the expected error message.
|
||||||
size_t expectedSize = size(superTp);
|
size_t expectedSize = size(superTp);
|
||||||
size_t actualSize = size(subTp);
|
size_t actualSize = size(subTp);
|
||||||
if (ctx == CountMismatch::Result)
|
if (ctx == CountMismatch::FunctionResult || ctx == CountMismatch::ExprListResult)
|
||||||
std::swap(expectedSize, actualSize);
|
std::swap(expectedSize, actualSize);
|
||||||
reportError(TypeError{location, CountMismatch{expectedSize, std::nullopt, actualSize, ctx}});
|
reportError(TypeError{location, CountMismatch{expectedSize, std::nullopt, actualSize, ctx}});
|
||||||
|
|
||||||
@ -1271,7 +1298,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||||||
else if (!innerState.errors.empty())
|
else if (!innerState.errors.empty())
|
||||||
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
||||||
|
|
||||||
innerState.ctx = CountMismatch::Result;
|
innerState.ctx = CountMismatch::FunctionResult;
|
||||||
innerState.tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
innerState.tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
||||||
|
|
||||||
if (!reported)
|
if (!reported)
|
||||||
@ -1295,7 +1322,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||||||
ctx = CountMismatch::Arg;
|
ctx = CountMismatch::Arg;
|
||||||
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
||||||
|
|
||||||
ctx = CountMismatch::Result;
|
ctx = CountMismatch::FunctionResult;
|
||||||
tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1693,8 +1720,45 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
|||||||
{
|
{
|
||||||
case TableState::Free:
|
case TableState::Free:
|
||||||
{
|
{
|
||||||
tryUnify_(subTy, superMetatable->table);
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
log.bindTable(subTy, superTy);
|
{
|
||||||
|
Unifier innerState = makeChildUnifier();
|
||||||
|
bool missingProperty = false;
|
||||||
|
|
||||||
|
for (const auto& [propName, prop] : subTable->props)
|
||||||
|
{
|
||||||
|
if (std::optional<TypeId> mtPropTy = findTablePropertyRespectingMeta(superTy, propName))
|
||||||
|
{
|
||||||
|
innerState.tryUnify(prop.type, *mtPropTy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(mismatchError);
|
||||||
|
missingProperty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const TableTypeVar* superTable = log.get<TableTypeVar>(log.follow(superMetatable->table)))
|
||||||
|
{
|
||||||
|
// TODO: Unify indexers.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||||
|
reportError(*e);
|
||||||
|
else if (!innerState.errors.empty())
|
||||||
|
reportError(TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}});
|
||||||
|
else if (!missingProperty)
|
||||||
|
{
|
||||||
|
log.concat(std::move(innerState.log));
|
||||||
|
log.bindTable(subTy, superTy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tryUnify_(subTy, superMetatable->table);
|
||||||
|
log.bindTable(subTy, superTy);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ int main(int argc, char** argv)
|
|||||||
CliConfigResolver configResolver(mode);
|
CliConfigResolver configResolver(mode);
|
||||||
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
||||||
|
|
||||||
Luau::registerBuiltinTypes(frontend.typeChecker);
|
Luau::registerBuiltinGlobals(frontend.typeChecker);
|
||||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||||
|
|
||||||
#ifdef CALLGRIND
|
#ifdef CALLGRIND
|
||||||
|
@ -720,7 +720,7 @@ const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure
|
|||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = nresults; i != 0 && vali < valend; i--)
|
for (i = nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
@ -756,7 +756,7 @@ const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, Closu
|
|||||||
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
||||||
int i;
|
int i;
|
||||||
for (i = nresults; i != 0 && vali < valend; i--)
|
for (i = nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
@ -1667,7 +1667,7 @@ const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, Closu
|
|||||||
|
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
setobjs2s(L, ra, base + b);
|
setobj2s(L, ra, base + b);
|
||||||
VM_PROTECT(luaC_checkGC(L));
|
VM_PROTECT(luaC_checkGC(L));
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
@ -2003,9 +2003,9 @@ const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Clo
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
|
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
|
||||||
setobjs2s(L, ra + 3 + 2, ra + 2);
|
setobj2s(L, ra + 3 + 2, ra + 2);
|
||||||
setobjs2s(L, ra + 3 + 1, ra + 1);
|
setobj2s(L, ra + 3 + 1, ra + 1);
|
||||||
setobjs2s(L, ra + 3, ra);
|
setobj2s(L, ra + 3, ra);
|
||||||
|
|
||||||
L->top = ra + 3 + 3; // func + 2 args (state and index)
|
L->top = ra + 3 + 3; // func + 2 args (state and index)
|
||||||
LUAU_ASSERT(L->top <= L->stack_last);
|
LUAU_ASSERT(L->top <= L->stack_last);
|
||||||
@ -2017,7 +2017,7 @@ const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Clo
|
|||||||
ra = VM_REG(LUAU_INSN_A(insn));
|
ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
// copy first variable back into the iteration index
|
// copy first variable back into the iteration index
|
||||||
setobjs2s(L, ra + 2, ra + 3);
|
setobj2s(L, ra + 2, ra + 3);
|
||||||
|
|
||||||
// note that we need to increment pc by 1 to exit the loop since we need to skip over aux
|
// note that we need to increment pc by 1 to exit the loop since we need to skip over aux
|
||||||
pc += ttisnil(ra + 3) ? 1 : LUAU_INSN_D(insn);
|
pc += ttisnil(ra + 3) ? 1 : LUAU_INSN_D(insn);
|
||||||
@ -2094,7 +2094,7 @@ const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, C
|
|||||||
StkId ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
StkId ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
||||||
|
|
||||||
for (int j = 0; j < n; j++)
|
for (int j = 0; j < n; j++)
|
||||||
setobjs2s(L, ra + j, base - n + j);
|
setobj2s(L, ra + j, base - n + j);
|
||||||
|
|
||||||
L->top = ra + n;
|
L->top = ra + n;
|
||||||
return pc;
|
return pc;
|
||||||
@ -2104,7 +2104,7 @@ const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, C
|
|||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
for (int j = 0; j < b && j < n; j++)
|
for (int j = 0; j < b && j < n; j++)
|
||||||
setobjs2s(L, ra + j, base - n + j);
|
setobj2s(L, ra + j, base - n + j);
|
||||||
for (int j = n; j < b; j++)
|
for (int j = n; j < b; j++)
|
||||||
setnilvalue(ra + j);
|
setnilvalue(ra + j);
|
||||||
return pc;
|
return pc;
|
||||||
@ -2183,7 +2183,7 @@ const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc,
|
|||||||
|
|
||||||
for (int i = 0; i < numparams; ++i)
|
for (int i = 0; i < numparams; ++i)
|
||||||
{
|
{
|
||||||
setobjs2s(L, base + i, fixed + i);
|
setobj2s(L, base + i, fixed + i);
|
||||||
setnilvalue(fixed + i);
|
setnilvalue(fixed + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,6 +517,10 @@ enum LuauBuiltinFunction
|
|||||||
|
|
||||||
// bit32.extract(_, k, k)
|
// bit32.extract(_, k, k)
|
||||||
LBF_BIT32_EXTRACTK,
|
LBF_BIT32_EXTRACTK,
|
||||||
|
|
||||||
|
// get/setmetatable
|
||||||
|
LBF_GETMETATABLE,
|
||||||
|
LBF_SETMETATABLE,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture type, used in LOP_CAPTURE
|
// Capture type, used in LOP_CAPTURE
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#include "Luau/Bytecode.h"
|
#include "Luau/Bytecode.h"
|
||||||
#include "Luau/Compiler.h"
|
#include "Luau/Compiler.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinMT, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace Compile
|
namespace Compile
|
||||||
@ -64,6 +66,14 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
|
|||||||
if (builtin.isGlobal("select"))
|
if (builtin.isGlobal("select"))
|
||||||
return LBF_SELECT_VARARG;
|
return LBF_SELECT_VARARG;
|
||||||
|
|
||||||
|
if (FFlag::LuauCompileBuiltinMT)
|
||||||
|
{
|
||||||
|
if (builtin.isGlobal("getmetatable"))
|
||||||
|
return LBF_GETMETATABLE;
|
||||||
|
if (builtin.isGlobal("setmetatable"))
|
||||||
|
return LBF_SETMETATABLE;
|
||||||
|
}
|
||||||
|
|
||||||
if (builtin.object == "math")
|
if (builtin.object == "math")
|
||||||
{
|
{
|
||||||
if (builtin.method == "abs")
|
if (builtin.method == "abs")
|
||||||
|
@ -235,7 +235,7 @@ void lua_remove(lua_State* L, int idx)
|
|||||||
StkId p = index2addr(L, idx);
|
StkId p = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, p);
|
api_checkvalidindex(L, p);
|
||||||
while (++p < L->top)
|
while (++p < L->top)
|
||||||
setobjs2s(L, p - 1, p);
|
setobj2s(L, p - 1, p);
|
||||||
L->top--;
|
L->top--;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -246,8 +246,8 @@ void lua_insert(lua_State* L, int idx)
|
|||||||
StkId p = index2addr(L, idx);
|
StkId p = index2addr(L, idx);
|
||||||
api_checkvalidindex(L, p);
|
api_checkvalidindex(L, p);
|
||||||
for (StkId q = L->top; q > p; q--)
|
for (StkId q = L->top; q > p; q--)
|
||||||
setobjs2s(L, q, q - 1);
|
setobj2s(L, q, q - 1);
|
||||||
setobjs2s(L, p, L->top);
|
setobj2s(L, p, L->top);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +614,7 @@ void lua_pushlstring(lua_State* L, const char* s, size_t len)
|
|||||||
{
|
{
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
luaC_threadbarrier(L);
|
luaC_threadbarrier(L);
|
||||||
setsvalue2s(L, L->top, luaS_newlstr(L, s, len));
|
setsvalue(L, L->top, luaS_newlstr(L, s, len));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1269,7 +1269,7 @@ void lua_concat(lua_State* L, int n)
|
|||||||
else if (n == 0)
|
else if (n == 0)
|
||||||
{ // push empty string
|
{ // push empty string
|
||||||
luaC_threadbarrier(L);
|
luaC_threadbarrier(L);
|
||||||
setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
|
setsvalue(L, L->top, luaS_newlstr(L, "", 0));
|
||||||
api_incr_top(L);
|
api_incr_top(L);
|
||||||
}
|
}
|
||||||
// else n == 1; nothing to do
|
// else n == 1; nothing to do
|
||||||
|
@ -400,7 +400,7 @@ char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
|
|||||||
lua_insert(L, boxloc);
|
lua_insert(L, boxloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
setsvalue2s(L, L->top + boxloc, newStorage);
|
setsvalue(L, L->top + boxloc, newStorage);
|
||||||
B->p = newStorage->data + (B->p - base);
|
B->p = newStorage->data + (B->p - base);
|
||||||
B->end = newStorage->data + nextsize;
|
B->end = newStorage->data + nextsize;
|
||||||
B->storage = newStorage;
|
B->storage = newStorage;
|
||||||
@ -451,11 +451,11 @@ void luaL_pushresult(luaL_Buffer* B)
|
|||||||
// if we finished just at the end of the string buffer, we can convert it to a mutable stirng without a copy
|
// if we finished just at the end of the string buffer, we can convert it to a mutable stirng without a copy
|
||||||
if (B->p == B->end)
|
if (B->p == B->end)
|
||||||
{
|
{
|
||||||
setsvalue2s(L, L->top - 1, luaS_buffinish(L, storage));
|
setsvalue(L, L->top - 1, luaS_buffinish(L, storage));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setsvalue2s(L, L->top - 1, luaS_newlstr(L, storage->data, B->p - storage->data));
|
setsvalue(L, L->top - 1, luaS_newlstr(L, storage->data, B->p - storage->data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -789,7 +789,7 @@ static int luauF_type(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||||||
int tt = ttype(arg0);
|
int tt = ttype(arg0);
|
||||||
TString* ttname = L->global->ttname[tt];
|
TString* ttname = L->global->ttname[tt];
|
||||||
|
|
||||||
setsvalue2s(L, res, ttname);
|
setsvalue(L, res, ttname);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -861,7 +861,7 @@ static int luauF_char(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||||||
|
|
||||||
buffer[nparams] = 0;
|
buffer[nparams] = 0;
|
||||||
|
|
||||||
setsvalue2s(L, res, luaS_newlstr(L, buffer, nparams));
|
setsvalue(L, res, luaS_newlstr(L, buffer, nparams));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,7 +887,7 @@ static int luauF_typeof(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
|||||||
{
|
{
|
||||||
const TString* ttname = luaT_objtypenamestr(L, arg0);
|
const TString* ttname = luaT_objtypenamestr(L, arg0);
|
||||||
|
|
||||||
setsvalue2s(L, res, ttname);
|
setsvalue(L, res, ttname);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,7 +904,7 @@ static int luauF_sub(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||||||
|
|
||||||
if (i >= 1 && j >= i && unsigned(j - 1) < unsigned(ts->len))
|
if (i >= 1 && j >= i && unsigned(j - 1) < unsigned(ts->len))
|
||||||
{
|
{
|
||||||
setsvalue2s(L, res, luaS_newlstr(L, getstr(ts) + (i - 1), j - i + 1));
|
setsvalue(L, res, luaS_newlstr(L, getstr(ts) + (i - 1), j - i + 1));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -993,12 +993,13 @@ static int luauF_rawset(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
|
|||||||
else if (ttisvector(key) && luai_vecisnan(vvalue(key)))
|
else if (ttisvector(key) && luai_vecisnan(vvalue(key)))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (hvalue(arg0)->readonly)
|
Table* t = hvalue(arg0);
|
||||||
|
if (t->readonly)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
setobj2s(L, res, arg0);
|
setobj2s(L, res, arg0);
|
||||||
setobj2t(L, luaH_set(L, hvalue(arg0), args), args + 1);
|
setobj2t(L, luaH_set(L, t, args), args + 1);
|
||||||
luaC_barriert(L, hvalue(arg0), args + 1);
|
luaC_barriert(L, t, args + 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,12 +1010,13 @@ static int luauF_tinsert(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
|||||||
{
|
{
|
||||||
if (nparams == 2 && nresults <= 0 && ttistable(arg0))
|
if (nparams == 2 && nresults <= 0 && ttistable(arg0))
|
||||||
{
|
{
|
||||||
if (hvalue(arg0)->readonly)
|
Table* t = hvalue(arg0);
|
||||||
|
if (t->readonly)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int pos = luaH_getn(hvalue(arg0)) + 1;
|
int pos = luaH_getn(t) + 1;
|
||||||
setobj2t(L, luaH_setnum(L, hvalue(arg0), pos), args);
|
setobj2t(L, luaH_setnum(L, t, pos), args);
|
||||||
luaC_barriert(L, hvalue(arg0), args);
|
luaC_barriert(L, t, args);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1193,6 +1195,60 @@ static int luauF_extractk(lua_State* L, StkId res, TValue* arg0, int nresults, S
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int luauF_getmetatable(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
|
{
|
||||||
|
if (nparams >= 1 && nresults <= 1)
|
||||||
|
{
|
||||||
|
Table* mt = NULL;
|
||||||
|
if (ttistable(arg0))
|
||||||
|
mt = hvalue(arg0)->metatable;
|
||||||
|
else if (ttisuserdata(arg0))
|
||||||
|
mt = uvalue(arg0)->metatable;
|
||||||
|
else
|
||||||
|
mt = L->global->mt[ttype(arg0)];
|
||||||
|
|
||||||
|
const TValue* mtv = mt ? luaH_getstr(mt, L->global->tmname[TM_METATABLE]) : luaO_nilobject;
|
||||||
|
if (!ttisnil(mtv))
|
||||||
|
{
|
||||||
|
setobj2s(L, res, mtv);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mt)
|
||||||
|
{
|
||||||
|
sethvalue(L, res, mt);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setnilvalue(res);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||||
|
{
|
||||||
|
// note: setmetatable(_, nil) is rare so we use fallback for it to optimize the fast path
|
||||||
|
if (nparams >= 2 && nresults <= 1 && ttistable(arg0) && ttistable(args))
|
||||||
|
{
|
||||||
|
Table* t = hvalue(arg0);
|
||||||
|
if (t->readonly || t->metatable != NULL)
|
||||||
|
return -1; // note: overwriting non-null metatable is very rare but it requires __metatable check
|
||||||
|
|
||||||
|
Table* mt = hvalue(args);
|
||||||
|
t->metatable = mt;
|
||||||
|
luaC_objbarrier(L, t, mt);
|
||||||
|
|
||||||
|
sethvalue(L, res, t);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
luau_FastFunction luauF_table[256] = {
|
luau_FastFunction luauF_table[256] = {
|
||||||
NULL,
|
NULL,
|
||||||
luauF_assert,
|
luauF_assert,
|
||||||
@ -1268,4 +1324,7 @@ luau_FastFunction luauF_table[256] = {
|
|||||||
luauF_rawlen,
|
luauF_rawlen,
|
||||||
|
|
||||||
luauF_extractk,
|
luauF_extractk,
|
||||||
|
|
||||||
|
luauF_getmetatable,
|
||||||
|
luauF_setmetatable,
|
||||||
};
|
};
|
||||||
|
@ -83,7 +83,7 @@ const char* lua_setlocal(lua_State* L, int level, int n)
|
|||||||
Proto* fp = getluaproto(ci);
|
Proto* fp = getluaproto(ci);
|
||||||
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
|
||||||
if (var)
|
if (var)
|
||||||
setobjs2s(L, ci->base + var->reg, L->top - 1);
|
setobj2s(L, ci->base + var->reg, L->top - 1);
|
||||||
L->top--; // pop value
|
L->top--; // pop value
|
||||||
const char* name = var ? getstr(var->varname) : NULL;
|
const char* name = var ? getstr(var->varname) : NULL;
|
||||||
return name;
|
return name;
|
||||||
|
@ -263,18 +263,18 @@ static void seterrorobj(lua_State* L, int errcode, StkId oldtop)
|
|||||||
{
|
{
|
||||||
case LUA_ERRMEM:
|
case LUA_ERRMEM:
|
||||||
{
|
{
|
||||||
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen
|
setsvalue(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_ERRERR:
|
case LUA_ERRERR:
|
||||||
{
|
{
|
||||||
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen
|
setsvalue(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_ERRSYNTAX:
|
case LUA_ERRSYNTAX:
|
||||||
case LUA_ERRRUN:
|
case LUA_ERRRUN:
|
||||||
{
|
{
|
||||||
setobjs2s(L, oldtop, L->top - 1); // error message on current top
|
setobj2s(L, oldtop, L->top - 1); // error message on current top
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,7 +419,7 @@ static void resume_handle(lua_State* L, void* ud)
|
|||||||
static int resume_error(lua_State* L, const char* msg)
|
static int resume_error(lua_State* L, const char* msg)
|
||||||
{
|
{
|
||||||
L->top = L->ci->base;
|
L->top = L->ci->base;
|
||||||
setsvalue2s(L, L->top, luaS_new(L, msg));
|
setsvalue(L, L->top, luaS_new(L, msg));
|
||||||
incr_top(L);
|
incr_top(L);
|
||||||
return LUA_ERRRUN;
|
return LUA_ERRRUN;
|
||||||
}
|
}
|
||||||
@ -525,8 +525,8 @@ static void callerrfunc(lua_State* L, void* ud)
|
|||||||
{
|
{
|
||||||
StkId errfunc = cast_to(StkId, ud);
|
StkId errfunc = cast_to(StkId, ud);
|
||||||
|
|
||||||
setobjs2s(L, L->top, L->top - 1);
|
setobj2s(L, L->top, L->top - 1);
|
||||||
setobjs2s(L, L->top - 1, errfunc);
|
setobj2s(L, L->top - 1, errfunc);
|
||||||
incr_top(L);
|
incr_top(L);
|
||||||
luaD_call(L, L->top - 2, 1);
|
luaD_call(L, L->top - 2, 1);
|
||||||
}
|
}
|
||||||
|
103
VM/src/lgc.cpp
103
VM/src/lgc.cpp
@ -118,8 +118,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterThreadMark, false)
|
|||||||
* slot - upvalues like this are identified since they don't have `markedopen` bit set during thread traversal and closed in `clearupvals`.
|
* slot - upvalues like this are identified since they don't have `markedopen` bit set during thread traversal and closed in `clearupvals`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFasterSweep, false)
|
|
||||||
|
|
||||||
#define GC_SWEEPPAGESTEPCOST 16
|
#define GC_SWEEPPAGESTEPCOST 16
|
||||||
|
|
||||||
#define GC_INTERRUPT(state) \
|
#define GC_INTERRUPT(state) \
|
||||||
@ -836,28 +834,6 @@ static size_t atomic(lua_State* L)
|
|||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!FFlag::LuauFasterSweep);
|
|
||||||
global_State* g = L->global;
|
|
||||||
|
|
||||||
int deadmask = otherwhite(g);
|
|
||||||
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
|
|
||||||
|
|
||||||
int alive = (gco->gch.marked ^ WHITEBITS) & deadmask;
|
|
||||||
|
|
||||||
if (alive)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!isdead(g, gco));
|
|
||||||
makewhite(g, gco); // make it white (for next cycle)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_ASSERT(isdead(g, gco));
|
|
||||||
freeobj(L, gco, page);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a version of generic luaM_visitpage specialized for the main sweep stage
|
// a version of generic luaM_visitpage specialized for the main sweep stage
|
||||||
static int sweepgcopage(lua_State* L, lua_Page* page)
|
static int sweepgcopage(lua_State* L, lua_Page* page)
|
||||||
{
|
{
|
||||||
@ -869,58 +845,36 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
|
|||||||
|
|
||||||
LUAU_ASSERT(busyBlocks > 0);
|
LUAU_ASSERT(busyBlocks > 0);
|
||||||
|
|
||||||
if (FFlag::LuauFasterSweep)
|
global_State* g = L->global;
|
||||||
|
|
||||||
|
int deadmask = otherwhite(g);
|
||||||
|
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
|
||||||
|
|
||||||
|
int newwhite = luaC_white(g);
|
||||||
|
|
||||||
|
for (char* pos = start; pos != end; pos += blockSize)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
GCObject* gco = (GCObject*)pos;
|
||||||
|
|
||||||
int deadmask = otherwhite(g);
|
// skip memory blocks that are already freed
|
||||||
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
|
if (gco->gch.tt == LUA_TNIL)
|
||||||
|
continue;
|
||||||
|
|
||||||
int newwhite = luaC_white(g);
|
// is the object alive?
|
||||||
|
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
|
||||||
for (char* pos = start; pos != end; pos += blockSize)
|
|
||||||
{
|
{
|
||||||
GCObject* gco = (GCObject*)pos;
|
LUAU_ASSERT(!isdead(g, gco));
|
||||||
|
// make it white (for next cycle)
|
||||||
// skip memory blocks that are already freed
|
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
|
||||||
if (gco->gch.tt == LUA_TNIL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// is the object alive?
|
|
||||||
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!isdead(g, gco));
|
|
||||||
// make it white (for next cycle)
|
|
||||||
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(isdead(g, gco));
|
|
||||||
freeobj(L, gco, page);
|
|
||||||
|
|
||||||
// if the last block was removed, page would be removed as well
|
|
||||||
if (--busyBlocks == 0)
|
|
||||||
return int(pos - start) / blockSize + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
|
||||||
{
|
|
||||||
for (char* pos = start; pos != end; pos += blockSize)
|
|
||||||
{
|
{
|
||||||
GCObject* gco = (GCObject*)pos;
|
LUAU_ASSERT(isdead(g, gco));
|
||||||
|
freeobj(L, gco, page);
|
||||||
|
|
||||||
// skip memory blocks that are already freed
|
// if the last block was removed, page would be removed as well
|
||||||
if (gco->gch.tt == LUA_TNIL)
|
if (--busyBlocks == 0)
|
||||||
continue;
|
return int(pos - start) / blockSize + 1;
|
||||||
|
|
||||||
// when true is returned it means that the element was deleted
|
|
||||||
if (sweepgco(L, page, gco))
|
|
||||||
{
|
|
||||||
// if the last block was removed, page would be removed as well
|
|
||||||
if (--busyBlocks == 0)
|
|
||||||
return int(pos - start) / blockSize + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,15 +963,8 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||||||
if (g->sweepgcopage == NULL)
|
if (g->sweepgcopage == NULL)
|
||||||
{
|
{
|
||||||
// don't forget to visit main thread, it's the only object not allocated in GCO pages
|
// don't forget to visit main thread, it's the only object not allocated in GCO pages
|
||||||
if (FFlag::LuauFasterSweep)
|
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
||||||
{
|
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
|
||||||
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
|
|
||||||
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sweepgco(L, NULL, obj2gco(g->mainthread));
|
|
||||||
}
|
|
||||||
|
|
||||||
shrinkbuffers(L);
|
shrinkbuffers(L);
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ const char* luaO_pushvfstring(lua_State* L, const char* fmt, va_list argp)
|
|||||||
char result[LUA_BUFFERSIZE];
|
char result[LUA_BUFFERSIZE];
|
||||||
vsnprintf(result, sizeof(result), fmt, argp);
|
vsnprintf(result, sizeof(result), fmt, argp);
|
||||||
|
|
||||||
setsvalue2s(L, L->top, luaS_new(L, result));
|
setsvalue(L, L->top, luaS_new(L, result));
|
||||||
incr_top(L);
|
incr_top(L);
|
||||||
return svalue(L->top - 1);
|
return svalue(L->top - 1);
|
||||||
}
|
}
|
||||||
|
@ -200,20 +200,14 @@ typedef struct lua_TValue
|
|||||||
** different types of sets, according to destination
|
** different types of sets, according to destination
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// from stack to (same) stack
|
// to stack
|
||||||
#define setobjs2s setobj
|
|
||||||
// to stack (not from same stack)
|
|
||||||
#define setobj2s setobj
|
#define setobj2s setobj
|
||||||
#define setsvalue2s setsvalue
|
// from table to same table (no barrier)
|
||||||
#define sethvalue2s sethvalue
|
|
||||||
#define setptvalue2s setptvalue
|
|
||||||
// from table to same table
|
|
||||||
#define setobjt2t setobj
|
#define setobjt2t setobj
|
||||||
// to table
|
// to table (needs barrier)
|
||||||
#define setobj2t setobj
|
#define setobj2t setobj
|
||||||
// to new object
|
// to new object (no barrier)
|
||||||
#define setobj2n setobj
|
#define setobj2n setobj
|
||||||
#define setsvalue2n setsvalue
|
|
||||||
|
|
||||||
#define setttype(obj, tt) (ttype(obj) = (tt))
|
#define setttype(obj, tt) (ttype(obj) = (tt))
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ const char* const luaT_eventname[] = {
|
|||||||
"__le",
|
"__le",
|
||||||
"__concat",
|
"__concat",
|
||||||
"__type",
|
"__type",
|
||||||
|
"__metatable",
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ typedef enum
|
|||||||
TM_LE,
|
TM_LE,
|
||||||
TM_CONCAT,
|
TM_CONCAT,
|
||||||
TM_TYPE,
|
TM_TYPE,
|
||||||
|
TM_METATABLE,
|
||||||
|
|
||||||
TM_N // number of elements in the enum
|
TM_N // number of elements in the enum
|
||||||
} TMS;
|
} TMS;
|
||||||
|
@ -985,7 +985,7 @@ reentry:
|
|||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = nresults; i != 0 && vali < valend; i--)
|
for (i = nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
@ -1022,7 +1022,7 @@ reentry:
|
|||||||
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
|
||||||
int i;
|
int i;
|
||||||
for (i = nresults; i != 0 && vali < valend; i--)
|
for (i = nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
@ -1945,7 +1945,7 @@ reentry:
|
|||||||
|
|
||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
setobjs2s(L, ra, base + b);
|
setobj2s(L, ra, base + b);
|
||||||
VM_PROTECT(luaC_checkGC(L));
|
VM_PROTECT(luaC_checkGC(L));
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
@ -2281,9 +2281,9 @@ reentry:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
|
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
|
||||||
setobjs2s(L, ra + 3 + 2, ra + 2);
|
setobj2s(L, ra + 3 + 2, ra + 2);
|
||||||
setobjs2s(L, ra + 3 + 1, ra + 1);
|
setobj2s(L, ra + 3 + 1, ra + 1);
|
||||||
setobjs2s(L, ra + 3, ra);
|
setobj2s(L, ra + 3, ra);
|
||||||
|
|
||||||
L->top = ra + 3 + 3; // func + 2 args (state and index)
|
L->top = ra + 3 + 3; // func + 2 args (state and index)
|
||||||
LUAU_ASSERT(L->top <= L->stack_last);
|
LUAU_ASSERT(L->top <= L->stack_last);
|
||||||
@ -2295,7 +2295,7 @@ reentry:
|
|||||||
ra = VM_REG(LUAU_INSN_A(insn));
|
ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
// copy first variable back into the iteration index
|
// copy first variable back into the iteration index
|
||||||
setobjs2s(L, ra + 2, ra + 3);
|
setobj2s(L, ra + 2, ra + 3);
|
||||||
|
|
||||||
// note that we need to increment pc by 1 to exit the loop since we need to skip over aux
|
// note that we need to increment pc by 1 to exit the loop since we need to skip over aux
|
||||||
pc += ttisnil(ra + 3) ? 1 : LUAU_INSN_D(insn);
|
pc += ttisnil(ra + 3) ? 1 : LUAU_INSN_D(insn);
|
||||||
@ -2372,7 +2372,7 @@ reentry:
|
|||||||
StkId ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
StkId ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
||||||
|
|
||||||
for (int j = 0; j < n; j++)
|
for (int j = 0; j < n; j++)
|
||||||
setobjs2s(L, ra + j, base - n + j);
|
setobj2s(L, ra + j, base - n + j);
|
||||||
|
|
||||||
L->top = ra + n;
|
L->top = ra + n;
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
@ -2382,7 +2382,7 @@ reentry:
|
|||||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||||
|
|
||||||
for (int j = 0; j < b && j < n; j++)
|
for (int j = 0; j < b && j < n; j++)
|
||||||
setobjs2s(L, ra + j, base - n + j);
|
setobj2s(L, ra + j, base - n + j);
|
||||||
for (int j = n; j < b; j++)
|
for (int j = n; j < b; j++)
|
||||||
setnilvalue(ra + j);
|
setnilvalue(ra + j);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
@ -2461,7 +2461,7 @@ reentry:
|
|||||||
|
|
||||||
for (int i = 0; i < numparams; ++i)
|
for (int i = 0; i < numparams; ++i)
|
||||||
{
|
{
|
||||||
setobjs2s(L, base + i, fixed + i);
|
setobj2s(L, base + i, fixed + i);
|
||||||
setnilvalue(fixed + i);
|
setnilvalue(fixed + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2878,7 +2878,7 @@ int luau_precall(lua_State* L, StkId func, int nresults)
|
|||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = nresults; i != 0 && vali < valend; i--)
|
for (i = nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
@ -2906,7 +2906,7 @@ void luau_poscall(lua_State* L, StkId first)
|
|||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = ci->nresults; i != 0 && vali < valend; i--)
|
for (i = ci->nresults; i != 0 && vali < valend; i--)
|
||||||
setobjs2s(L, res++, vali++);
|
setobj2s(L, res++, vali++);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
setnilvalue(res++);
|
setnilvalue(res++);
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||||||
case LBC_CONSTANT_STRING:
|
case LBC_CONSTANT_STRING:
|
||||||
{
|
{
|
||||||
TString* v = readString(strings, data, size, offset);
|
TString* v = readString(strings, data, size, offset);
|
||||||
setsvalue2n(L, &p->k[j], v);
|
setsvalue(L, &p->k[j], v);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ int luaV_tostring(lua_State* L, StkId obj)
|
|||||||
double n = nvalue(obj);
|
double n = nvalue(obj);
|
||||||
char* e = luai_num2str(s, n);
|
char* e = luai_num2str(s, n);
|
||||||
LUAU_ASSERT(e < s + sizeof(s));
|
LUAU_ASSERT(e < s + sizeof(s));
|
||||||
setsvalue2s(L, obj, luaS_newlstr(L, s, e - s));
|
setsvalue(L, obj, luaS_newlstr(L, s, e - s));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ static StkId callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p
|
|||||||
luaD_call(L, L->top - 3, 1);
|
luaD_call(L, L->top - 3, 1);
|
||||||
res = restorestack(L, result);
|
res = restorestack(L, result);
|
||||||
L->top--;
|
L->top--;
|
||||||
setobjs2s(L, res, L->top);
|
setobj2s(L, res, L->top);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,11 +350,11 @@ void luaV_concat(lua_State* L, int total, int last)
|
|||||||
|
|
||||||
if (tl < LUA_BUFFERSIZE)
|
if (tl < LUA_BUFFERSIZE)
|
||||||
{
|
{
|
||||||
setsvalue2s(L, top - n, luaS_newlstr(L, buffer, tl));
|
setsvalue(L, top - n, luaS_newlstr(L, buffer, tl));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setsvalue2s(L, top - n, luaS_buffinish(L, ts));
|
setsvalue(L, top - n, luaS_buffinish(L, ts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total -= n - 1; // got `n' strings to create 1 new
|
total -= n - 1; // got `n' strings to create 1 new
|
||||||
@ -582,7 +582,7 @@ LUAU_NOINLINE void luaV_tryfuncTM(lua_State* L, StkId func)
|
|||||||
if (!ttisfunction(tm))
|
if (!ttisfunction(tm))
|
||||||
luaG_typeerror(L, func, "call");
|
luaG_typeerror(L, func, "call");
|
||||||
for (StkId p = L->top; p > func; p--) // open space for metamethod
|
for (StkId p = L->top; p > func; p--) // open space for metamethod
|
||||||
setobjs2s(L, p, p - 1);
|
setobj2s(L, p, p - 1);
|
||||||
L->top++; // stack space pre-allocated by the caller
|
L->top++; // stack space pre-allocated by the caller
|
||||||
setobj2s(L, func, tm); // tag method is the new function to be called
|
setobj2s(L, func, tm); // tag method is the new function to be called
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
|||||||
static Luau::NullModuleResolver moduleResolver;
|
static Luau::NullModuleResolver moduleResolver;
|
||||||
static Luau::InternalErrorReporter iceHandler;
|
static Luau::InternalErrorReporter iceHandler;
|
||||||
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
||||||
static int once = (Luau::registerBuiltinTypes(sharedEnv), 1);
|
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
|
||||||
(void)once;
|
(void)once;
|
||||||
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
||||||
(void)once2;
|
(void)once2;
|
||||||
|
@ -96,7 +96,7 @@ int registerTypes(Luau::TypeChecker& env)
|
|||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
|
|
||||||
Luau::registerBuiltinTypes(env);
|
Luau::registerBuiltinGlobals(env);
|
||||||
|
|
||||||
TypeArena& arena = env.globalTypes;
|
TypeArena& arena = env.globalTypes;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
|
|||||||
static Luau::NullModuleResolver moduleResolver;
|
static Luau::NullModuleResolver moduleResolver;
|
||||||
static Luau::InternalErrorReporter iceHandler;
|
static Luau::InternalErrorReporter iceHandler;
|
||||||
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
|
||||||
static int once = (Luau::registerBuiltinTypes(sharedEnv), 1);
|
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
|
||||||
(void)once;
|
(void)once;
|
||||||
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
|
||||||
(void)once2;
|
(void)once2;
|
||||||
|
@ -798,6 +798,8 @@ RETURN R0 1
|
|||||||
|
|
||||||
TEST_CASE("TableSizePredictionSetMetatable")
|
TEST_CASE("TableSizePredictionSetMetatable")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff("LuauCompileBuiltinMT", true);
|
||||||
|
|
||||||
CHECK_EQ("\n" + compileFunction0(R"(
|
CHECK_EQ("\n" + compileFunction0(R"(
|
||||||
local t = setmetatable({}, nil)
|
local t = setmetatable({}, nil)
|
||||||
t.field1 = 1
|
t.field1 = 1
|
||||||
@ -805,14 +807,15 @@ t.field2 = 2
|
|||||||
return t
|
return t
|
||||||
)"),
|
)"),
|
||||||
R"(
|
R"(
|
||||||
GETIMPORT R0 1
|
|
||||||
NEWTABLE R1 2 0
|
NEWTABLE R1 2 0
|
||||||
LOADNIL R2
|
FASTCALL2K 61 R1 K0 L0
|
||||||
|
LOADK R2 K0
|
||||||
|
GETIMPORT R0 2
|
||||||
CALL R0 2 1
|
CALL R0 2 1
|
||||||
LOADN R1 1
|
L0: LOADN R1 1
|
||||||
SETTABLEKS R1 R0 K2
|
|
||||||
LOADN R1 2
|
|
||||||
SETTABLEKS R1 R0 K3
|
SETTABLEKS R1 R0 K3
|
||||||
|
LOADN R1 2
|
||||||
|
SETTABLEKS R1 R0 K4
|
||||||
RETURN R0 1
|
RETURN R0 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
@ -499,7 +499,7 @@ TEST_CASE("Types")
|
|||||||
Luau::SingletonTypes singletonTypes;
|
Luau::SingletonTypes singletonTypes;
|
||||||
Luau::TypeChecker env(&moduleResolver, Luau::NotNull{&singletonTypes}, &iceHandler);
|
Luau::TypeChecker env(&moduleResolver, Luau::NotNull{&singletonTypes}, &iceHandler);
|
||||||
|
|
||||||
Luau::registerBuiltinTypes(env);
|
Luau::registerBuiltinGlobals(env);
|
||||||
Luau::freeze(env.globalTypes);
|
Luau::freeze(env.globalTypes);
|
||||||
|
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/TypeAttach.h"
|
#include "Luau/TypeAttach.h"
|
||||||
#include "Luau/Transpiler.h"
|
#include "Luau/Transpiler.h"
|
||||||
|
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
@ -20,6 +19,8 @@
|
|||||||
static const char* mainModuleName = "MainModule";
|
static const char* mainModuleName = "MainModule";
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -97,6 +98,8 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
|||||||
configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
|
configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
|
||||||
configResolver.defaultConfig.parseOptions.captureComments = true;
|
configResolver.defaultConfig.parseOptions.captureComments = true;
|
||||||
|
|
||||||
|
registerBuiltinTypes(frontend);
|
||||||
|
|
||||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||||
|
|
||||||
@ -435,9 +438,9 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
|||||||
Luau::unfreeze(frontend.typeChecker.globalTypes);
|
Luau::unfreeze(frontend.typeChecker.globalTypes);
|
||||||
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||||
|
|
||||||
registerBuiltinTypes(frontend);
|
registerBuiltinGlobals(frontend);
|
||||||
if (prepareAutocomplete)
|
if (prepareAutocomplete)
|
||||||
registerBuiltinTypes(frontend.typeCheckerForAutocomplete);
|
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete);
|
||||||
registerTestTypes();
|
registerTestTypes();
|
||||||
|
|
||||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||||
|
@ -1070,12 +1070,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
|
|||||||
|
|
||||||
fileResolver.source["Module/A"] = R"(
|
fileResolver.source["Module/A"] = R"(
|
||||||
type KeyOfTestEvents = "test-file-start" | "test-file-success" | "test-file-failure" | "test-case-result"
|
type KeyOfTestEvents = "test-file-start" | "test-file-success" | "test-file-failure" | "test-case-result"
|
||||||
type unknown = any
|
type MyAny = any
|
||||||
|
|
||||||
export type TestFileEvent<T = KeyOfTestEvents> = (
|
export type TestFileEvent<T = KeyOfTestEvents> = (
|
||||||
eventName: T,
|
eventName: T,
|
||||||
args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]]
|
args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]]
|
||||||
) -> unknown
|
) -> MyAny
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
)";
|
)";
|
||||||
|
@ -790,4 +790,20 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
|
|||||||
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
|
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff("LuauUnseeArrayTtv", true);
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x: {string}
|
||||||
|
-- This code is constructed very specifically to use the same (by pointer
|
||||||
|
-- identity) type in the function twice.
|
||||||
|
local y: (typeof(x), typeof(x)) -> ()
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK(toString(requireType("y")) == "({string}, {string}) -> ()");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -443,7 +443,8 @@ TEST_CASE_FIXTURE(Fixture, "reported_location_is_correct_when_type_alias_are_dup
|
|||||||
auto dtd = get<DuplicateTypeDefinition>(result.errors[0]);
|
auto dtd = get<DuplicateTypeDefinition>(result.errors[0]);
|
||||||
REQUIRE(dtd);
|
REQUIRE(dtd);
|
||||||
CHECK_EQ(dtd->name, "B");
|
CHECK_EQ(dtd->name, "B");
|
||||||
CHECK_EQ(dtd->previousLocation.begin.line + 1, 3);
|
REQUIRE(dtd->previousLocation);
|
||||||
|
CHECK_EQ(dtd->previousLocation->begin.line + 1, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "stringify_optional_parameterized_alias")
|
TEST_CASE_FIXTURE(Fixture, "stringify_optional_parameterized_alias")
|
||||||
@ -868,4 +869,40 @@ TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok")
|
|||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "report_shadowed_aliases")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauReportShadowedTypeAlias", true};
|
||||||
|
|
||||||
|
// We allow a previous type alias to depend on a future type alias. That exact feature enables a confusing example, like the following snippet,
|
||||||
|
// which has the type alias FakeString point to the type alias `string` that which points to `number`.
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type MyString = string
|
||||||
|
type string = number
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK(toString(result.errors[0]) == "Redefinition of type 'string'");
|
||||||
|
|
||||||
|
std::optional<TypeId> t1 = lookupType("MyString");
|
||||||
|
REQUIRE(t1);
|
||||||
|
CHECK(isPrim(*t1, PrimitiveTypeVar::String));
|
||||||
|
|
||||||
|
std::optional<TypeId> t2 = lookupType("string");
|
||||||
|
REQUIRE(t2);
|
||||||
|
CHECK(isPrim(*t2, PrimitiveTypeVar::String));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "it_is_ok_to_shadow_user_defined_alias")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type T = number
|
||||||
|
|
||||||
|
do
|
||||||
|
type T = string
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -745,7 +745,12 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_is_not_special_without_the_flag")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
|
||||||
{
|
{
|
||||||
// Luau::resetPrintLine();
|
static std::vector<std::string> output;
|
||||||
|
output.clear();
|
||||||
|
Luau::setPrintLine([](const std::string& s) {
|
||||||
|
output.push_back(s);
|
||||||
|
});
|
||||||
|
|
||||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -753,6 +758,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
REQUIRE(1 == output.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
|
TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
|
||||||
|
@ -11,6 +11,7 @@ using namespace Luau;
|
|||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
LUAU_FASTFLAG(LuauSpecialTypesAsterisked);
|
||||||
LUAU_FASTFLAG(LuauStringFormatArgumentErrorFix)
|
LUAU_FASTFLAG(LuauStringFormatArgumentErrorFix)
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("BuiltinTests");
|
TEST_SUITE_BEGIN("BuiltinTests");
|
||||||
|
|
||||||
@ -596,6 +597,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall")
|
|||||||
CHECK_EQ("boolean", toString(requireType("c")));
|
CHECK_EQ("boolean", toString(requireType("c")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "trivial_select")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a:number = select(1, 42)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "see_thru_select")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "see_thru_select")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -679,10 +689,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail")
|
|||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("any", toString(requireType("foo")));
|
if (FFlag::DebugLuauDeferredConstraintResolution && FFlag::LuauSpecialTypesAsterisked)
|
||||||
CHECK_EQ("any", toString(requireType("bar")));
|
{
|
||||||
CHECK_EQ("any", toString(requireType("baz")));
|
CHECK_EQ("string", toString(requireType("foo")));
|
||||||
CHECK_EQ("any", toString(requireType("quux")));
|
CHECK_EQ("*error-type*", toString(requireType("bar")));
|
||||||
|
CHECK_EQ("*error-type*", toString(requireType("baz")));
|
||||||
|
CHECK_EQ("*error-type*", toString(requireType("quux")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("any", toString(requireType("foo")));
|
||||||
|
CHECK_EQ("any", toString(requireType("bar")));
|
||||||
|
CHECK_EQ("any", toString(requireType("baz")));
|
||||||
|
CHECK_EQ("any", toString(requireType("quux")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head")
|
||||||
@ -698,10 +718,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_strin
|
|||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("any", toString(requireType("foo")));
|
if (FFlag::DebugLuauDeferredConstraintResolution && FFlag::LuauSpecialTypesAsterisked)
|
||||||
CHECK_EQ("any", toString(requireType("bar")));
|
{
|
||||||
CHECK_EQ("any", toString(requireType("baz")));
|
CHECK_EQ("string", toString(requireType("foo")));
|
||||||
CHECK_EQ("any", toString(requireType("quux")));
|
CHECK_EQ("string", toString(requireType("bar")));
|
||||||
|
CHECK_EQ("*error-type*", toString(requireType("baz")));
|
||||||
|
CHECK_EQ("*error-type*", toString(requireType("quux")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("any", toString(requireType("foo")));
|
||||||
|
CHECK_EQ("any", toString(requireType("bar")));
|
||||||
|
CHECK_EQ("any", toString(requireType("baz")));
|
||||||
|
CHECK_EQ("any", toString(requireType("quux")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
||||||
@ -1099,7 +1129,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
|
|||||||
|
|
||||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
REQUIRE(acm);
|
REQUIRE(acm);
|
||||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
CHECK_EQ(acm->expected, 1);
|
CHECK_EQ(acm->expected, 1);
|
||||||
CHECK_EQ(acm->actual, 4);
|
CHECK_EQ(acm->actual, 4);
|
||||||
|
|
||||||
@ -1116,7 +1146,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens
|
|||||||
|
|
||||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
REQUIRE(acm);
|
REQUIRE(acm);
|
||||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
CHECK_EQ(acm->expected, 3);
|
CHECK_EQ(acm->expected, 3);
|
||||||
CHECK_EQ(acm->actual, 4);
|
CHECK_EQ(acm->actual, 4);
|
||||||
|
|
||||||
@ -1135,7 +1165,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno
|
|||||||
|
|
||||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
REQUIRE(acm);
|
REQUIRE(acm);
|
||||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
CHECK_EQ(acm->expected, 2);
|
CHECK_EQ(acm->expected, 2);
|
||||||
CHECK_EQ(acm->actual, 3);
|
CHECK_EQ(acm->actual, 3);
|
||||||
|
|
||||||
@ -1288,7 +1318,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
|
|||||||
|
|
||||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
REQUIRE(acm);
|
REQUIRE(acm);
|
||||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
CHECK_EQ(acm->expected, 2);
|
CHECK_EQ(acm->expected, 2);
|
||||||
CHECK_EQ(acm->actual, 4);
|
CHECK_EQ(acm->actual, 4);
|
||||||
|
|
||||||
|
@ -837,6 +837,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_lea
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
|
TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!strict
|
--!strict
|
||||||
|
|
||||||
@ -851,7 +853,49 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
|
|||||||
|
|
||||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
REQUIRE(acm);
|
REQUIRE(acm);
|
||||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
|
CHECK_EQ(acm->expected, 1);
|
||||||
|
CHECK_EQ(acm->actual, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
|
||||||
|
function f()
|
||||||
|
return 55
|
||||||
|
end
|
||||||
|
|
||||||
|
local a, b = (f())
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
|
REQUIRE(acm);
|
||||||
|
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
|
||||||
|
CHECK_EQ(acm->expected, 1);
|
||||||
|
CHECK_EQ(acm->actual, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_no_function")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
|
||||||
|
local a, b = 55
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||||
|
REQUIRE(acm);
|
||||||
|
CHECK_EQ(acm->context, CountMismatch::ExprListResult);
|
||||||
CHECK_EQ(acm->expected, 1);
|
CHECK_EQ(acm->expected, 1);
|
||||||
CHECK_EQ(acm->actual, 2);
|
CHECK_EQ(acm->actual, 2);
|
||||||
}
|
}
|
||||||
@ -1271,7 +1315,7 @@ local b: B = a
|
|||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> number' could not be converted into '(number, number) -> (number, boolean)'
|
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> number' could not be converted into '(number, number) -> (number, boolean)'
|
||||||
caused by:
|
caused by:
|
||||||
Function only returns 1 value. 2 are required here)");
|
Function only returns 1 value, but 2 are required here)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret")
|
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret")
|
||||||
|
@ -526,6 +526,16 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow")
|
|||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_generic_next")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
for k: number, v: number in next, {1, 2, 3} do
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
|
TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -584,11 +594,48 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict")
|
|||||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_iter_metamethod")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_nil")
|
||||||
{
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local t = {}
|
local t = setmetatable({}, { __iter = function(o) return next, nil end, })
|
||||||
setmetatable(t, { __iter = function(o) return next, o.children end })
|
for k: number, v: string in t do
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK(toString(result.errors[0]) == "Type 'nil' could not be converted into '{- [a]: b -}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local t = setmetatable({}, { __iter = function(o) end })
|
||||||
|
for k: number, v: string in t do
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK(result.errors[0] == TypeError{
|
||||||
|
Location{{2, 36}, {2, 37}},
|
||||||
|
GenericError{"__iter must return at least one value"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local t = setmetatable({
|
||||||
|
children = {"foo"}
|
||||||
|
}, { __iter = function(o) return next, o.children end })
|
||||||
for k: number, v: string in t do
|
for k: number, v: string in t do
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
@ -596,4 +643,26 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_iter_metamethod")
|
|||||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local t = setmetatable({
|
||||||
|
children = {"foo"}
|
||||||
|
}, { __iter = function(o) return next, o.children end })
|
||||||
|
|
||||||
|
local a, b
|
||||||
|
for k, v in t do
|
||||||
|
a = k
|
||||||
|
b = v
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK(toString(requireType("a")) == "number");
|
||||||
|
CHECK(toString(requireType("b")) == "string");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -439,13 +439,15 @@ TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_table
|
|||||||
// Belongs in TypeInfer.builtins.test.cpp.
|
// Belongs in TypeInfer.builtins.test.cpp.
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(): () end
|
local function f(): () end
|
||||||
local ok, res = pcall(f)
|
local ok, res = pcall(f)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ("Function only returns 1 value. 2 are required here", toString(result.errors[0]));
|
CHECK_EQ("Function only returns 1 value, but 2 are required here", toString(result.errors[0]));
|
||||||
// LUAU_REQUIRE_NO_ERRORS(result);
|
// LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
// CHECK_EQ("boolean", toString(requireType("ok")));
|
// CHECK_EQ("boolean", toString(requireType("ok")));
|
||||||
// CHECK_EQ("any", toString(requireType("res")));
|
// CHECK_EQ("any", toString(requireType("res")));
|
||||||
|
@ -256,28 +256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
|||||||
REQUIRE_EQ("number", toString(requireType("b")));
|
REQUIRE_EQ("number", toString(requireType("b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
type ActuallyString = string
|
|
||||||
|
|
||||||
do -- Necessary. Otherwise toposort has ActuallyString come after string type alias.
|
|
||||||
type string = number
|
|
||||||
local foo: string = 1
|
|
||||||
|
|
||||||
if type(foo) == "string" then
|
|
||||||
local bar: ActuallyString = foo
|
|
||||||
local baz: boolean = foo
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
|
|
||||||
CHECK_EQ("never", toString(requireTypeAtPosition({8, 44})));
|
|
||||||
CHECK_EQ("never", toString(requireTypeAtPosition({9, 38})));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -1159,4 +1159,38 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
|||||||
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
|
function validate(stats, hits, misses)
|
||||||
|
local checked = {}
|
||||||
|
|
||||||
|
for _,l in ipairs(hits) do
|
||||||
|
if not (stats[l] and stats[l] > 0) then
|
||||||
|
return false, string.format("expected line %d to be hit", l)
|
||||||
|
end
|
||||||
|
checked[l] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,l in ipairs(misses) do
|
||||||
|
if not (stats[l] and stats[l] == 0) then
|
||||||
|
return false, string.format("expected line %d to be missed", l)
|
||||||
|
end
|
||||||
|
checked[l] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(stats) do
|
||||||
|
if type(k) == "number" and not checked[k] then
|
||||||
|
return false, string.format("expected line %d to be absent", k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -271,4 +271,40 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_pack_owner")
|
|||||||
CHECK_EQ(a->owningArena, &arena);
|
CHECK_EQ(a->owningArena, &arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff("DebugLuauDeferredConstraintResolution", true);
|
||||||
|
|
||||||
|
TableTypeVar::Props freeProps{
|
||||||
|
{"foo", {typeChecker.numberType}},
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeId free = arena.addType(TableTypeVar{freeProps, std::nullopt, TypeLevel{}, TableState::Free});
|
||||||
|
|
||||||
|
TableTypeVar::Props indexProps{
|
||||||
|
{"foo", {typeChecker.stringType}},
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeId index = arena.addType(TableTypeVar{indexProps, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||||
|
|
||||||
|
TableTypeVar::Props mtProps{
|
||||||
|
{"__index", {index}},
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeId mt = arena.addType(TableTypeVar{mtProps, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||||
|
|
||||||
|
TypeId target = arena.addType(TableTypeVar{TableState::Unsealed, TypeLevel{}});
|
||||||
|
TypeId metatable = arena.addType(MetatableTypeVar{target, mt});
|
||||||
|
|
||||||
|
state.tryUnify(metatable, free);
|
||||||
|
state.log.commit();
|
||||||
|
|
||||||
|
REQUIRE_EQ(state.errors.size(), 1);
|
||||||
|
|
||||||
|
std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n"
|
||||||
|
"caused by:\n"
|
||||||
|
" Type 'number' could not be converted into 'string'";
|
||||||
|
CHECK_EQ(toString(state.errors[0]), expected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -24,6 +24,9 @@ assert(getmetatable(nil) == nil)
|
|||||||
a={}; setmetatable(a, {__metatable = "xuxu",
|
a={}; setmetatable(a, {__metatable = "xuxu",
|
||||||
__tostring=function(x) return x.name end})
|
__tostring=function(x) return x.name end})
|
||||||
assert(getmetatable(a) == "xuxu")
|
assert(getmetatable(a) == "xuxu")
|
||||||
|
ud=newproxy(true); getmetatable(ud).__metatable = "xuxu"
|
||||||
|
assert(getmetatable(ud) == "xuxu")
|
||||||
|
|
||||||
local res,err = pcall(tostring, a)
|
local res,err = pcall(tostring, a)
|
||||||
assert(not res and err == "'__tostring' must return a string")
|
assert(not res and err == "'__tostring' must return a string")
|
||||||
-- cannot change a protected metatable
|
-- cannot change a protected metatable
|
||||||
|
@ -1,101 +1,47 @@
|
|||||||
AnnotationTests.builtin_types_are_not_exported
|
AnnotationTests.builtin_types_are_not_exported
|
||||||
|
AnnotationTests.corecursive_types_error_on_tight_loop
|
||||||
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.instantiation_clone_has_to_follow
|
AnnotationTests.instantiation_clone_has_to_follow
|
||||||
AnnotationTests.luau_ice_triggers_an_ice
|
|
||||||
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_handler
|
|
||||||
AnnotationTests.luau_print_is_magic_if_the_flag_is_set
|
|
||||||
AnnotationTests.occurs_check_on_cyclic_intersection_typevar
|
AnnotationTests.occurs_check_on_cyclic_intersection_typevar
|
||||||
AnnotationTests.occurs_check_on_cyclic_union_typevar
|
AnnotationTests.occurs_check_on_cyclic_union_typevar
|
||||||
|
AnnotationTests.too_many_type_params
|
||||||
AnnotationTests.two_type_params
|
AnnotationTests.two_type_params
|
||||||
AnnotationTests.use_type_required_from_another_file
|
AnnotationTests.use_type_required_from_another_file
|
||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
AstQuery::getDocumentationSymbolAtPosition.overloaded_fn
|
AstQuery::getDocumentationSymbolAtPosition.overloaded_fn
|
||||||
AutocompleteTest.argument_types
|
|
||||||
AutocompleteTest.arguments_to_global_lambda
|
|
||||||
AutocompleteTest.autocomplete_boolean_singleton
|
|
||||||
AutocompleteTest.autocomplete_end_with_fn_exprs
|
|
||||||
AutocompleteTest.autocomplete_end_with_lambda
|
|
||||||
AutocompleteTest.autocomplete_first_function_arg_expected_type
|
AutocompleteTest.autocomplete_first_function_arg_expected_type
|
||||||
AutocompleteTest.autocomplete_for_in_middle_keywords
|
|
||||||
AutocompleteTest.autocomplete_for_middle_keywords
|
|
||||||
AutocompleteTest.autocomplete_if_middle_keywords
|
|
||||||
AutocompleteTest.autocomplete_interpolated_string
|
AutocompleteTest.autocomplete_interpolated_string
|
||||||
AutocompleteTest.autocomplete_on_string_singletons
|
|
||||||
AutocompleteTest.autocomplete_oop_implicit_self
|
AutocompleteTest.autocomplete_oop_implicit_self
|
||||||
AutocompleteTest.autocomplete_repeat_middle_keyword
|
AutocompleteTest.autocomplete_string_singleton_equality
|
||||||
AutocompleteTest.autocomplete_string_singleton_escape
|
AutocompleteTest.autocomplete_string_singleton_escape
|
||||||
AutocompleteTest.autocomplete_string_singletons
|
AutocompleteTest.autocomplete_string_singletons
|
||||||
AutocompleteTest.autocomplete_while_middle_keywords
|
|
||||||
AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic
|
AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic
|
||||||
AutocompleteTest.bias_toward_inner_scope
|
|
||||||
AutocompleteTest.cyclic_table
|
AutocompleteTest.cyclic_table
|
||||||
AutocompleteTest.do_compatible_self_calls
|
AutocompleteTest.do_compatible_self_calls
|
||||||
AutocompleteTest.do_not_overwrite_context_sensitive_kws
|
|
||||||
AutocompleteTest.do_not_suggest_internal_module_type
|
|
||||||
AutocompleteTest.do_wrong_compatible_self_calls
|
AutocompleteTest.do_wrong_compatible_self_calls
|
||||||
AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment
|
|
||||||
AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment_at_the_very_end_of_the_file
|
|
||||||
AutocompleteTest.dont_offer_any_suggestions_from_within_a_comment
|
|
||||||
AutocompleteTest.dont_suggest_local_before_its_definition
|
|
||||||
AutocompleteTest.function_expr_params
|
|
||||||
AutocompleteTest.function_in_assignment_has_parentheses
|
|
||||||
AutocompleteTest.function_in_assignment_has_parentheses_2
|
|
||||||
AutocompleteTest.function_parameters
|
|
||||||
AutocompleteTest.function_result_passed_to_function_has_parentheses
|
|
||||||
AutocompleteTest.generic_types
|
|
||||||
AutocompleteTest.get_suggestions_for_the_very_start_of_the_script
|
|
||||||
AutocompleteTest.global_function_params
|
|
||||||
AutocompleteTest.global_functions_are_not_scoped_lexically
|
|
||||||
AutocompleteTest.globals_are_order_independent
|
|
||||||
AutocompleteTest.if_then_else_elseif_completions
|
|
||||||
AutocompleteTest.keyword_methods
|
AutocompleteTest.keyword_methods
|
||||||
AutocompleteTest.library_non_self_calls_are_fine
|
|
||||||
AutocompleteTest.library_self_calls_are_invalid
|
|
||||||
AutocompleteTest.local_function
|
|
||||||
AutocompleteTest.local_function_params
|
|
||||||
AutocompleteTest.local_functions_fall_out_of_scope
|
|
||||||
AutocompleteTest.method_call_inside_function_body
|
|
||||||
AutocompleteTest.nested_member_completions
|
|
||||||
AutocompleteTest.nested_recursive_function
|
|
||||||
AutocompleteTest.no_function_name_suggestions
|
|
||||||
AutocompleteTest.no_incompatible_self_calls
|
AutocompleteTest.no_incompatible_self_calls
|
||||||
AutocompleteTest.no_incompatible_self_calls_2
|
AutocompleteTest.no_incompatible_self_calls_2
|
||||||
AutocompleteTest.no_incompatible_self_calls_on_class
|
|
||||||
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
|
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
|
||||||
AutocompleteTest.recursive_function
|
AutocompleteTest.suggest_table_keys
|
||||||
AutocompleteTest.recursive_function_global
|
|
||||||
AutocompleteTest.recursive_function_local
|
|
||||||
AutocompleteTest.return_types
|
|
||||||
AutocompleteTest.sometimes_the_metatable_is_an_error
|
|
||||||
AutocompleteTest.source_module_preservation_and_invalidation
|
|
||||||
AutocompleteTest.statement_between_two_statements
|
|
||||||
AutocompleteTest.string_prim_non_self_calls_are_avoided
|
|
||||||
AutocompleteTest.string_prim_self_calls_are_fine
|
|
||||||
AutocompleteTest.suggest_external_module_type
|
|
||||||
AutocompleteTest.table_intersection
|
|
||||||
AutocompleteTest.table_union
|
|
||||||
AutocompleteTest.type_correct_argument_type_suggestion
|
AutocompleteTest.type_correct_argument_type_suggestion
|
||||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||||
|
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||||
AutocompleteTest.type_correct_expected_return_type_suggestion
|
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||||
AutocompleteTest.type_correct_full_type_suggestion
|
AutocompleteTest.type_correct_full_type_suggestion
|
||||||
AutocompleteTest.type_correct_function_no_parenthesis
|
AutocompleteTest.type_correct_function_no_parenthesis
|
||||||
AutocompleteTest.type_correct_function_return_types
|
AutocompleteTest.type_correct_function_return_types
|
||||||
AutocompleteTest.type_correct_function_type_suggestion
|
AutocompleteTest.type_correct_function_type_suggestion
|
||||||
AutocompleteTest.type_correct_keywords
|
AutocompleteTest.type_correct_keywords
|
||||||
AutocompleteTest.type_correct_local_type_suggestion
|
|
||||||
AutocompleteTest.type_correct_sealed_table
|
|
||||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||||
AutocompleteTest.type_correct_suggestion_in_argument
|
AutocompleteTest.type_correct_suggestion_in_argument
|
||||||
|
AutocompleteTest.type_correct_suggestion_in_table
|
||||||
AutocompleteTest.unsealed_table
|
AutocompleteTest.unsealed_table
|
||||||
AutocompleteTest.unsealed_table_2
|
AutocompleteTest.unsealed_table_2
|
||||||
AutocompleteTest.user_defined_local_functions_in_own_definition
|
|
||||||
BuiltinTests.aliased_string_format
|
BuiltinTests.aliased_string_format
|
||||||
BuiltinTests.assert_removes_falsy_types
|
BuiltinTests.assert_removes_falsy_types
|
||||||
BuiltinTests.assert_removes_falsy_types2
|
BuiltinTests.assert_removes_falsy_types2
|
||||||
@ -149,21 +95,20 @@ BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
|||||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||||
BuiltinTests.table_pack
|
BuiltinTests.table_pack
|
||||||
BuiltinTests.table_pack_reduce
|
BuiltinTests.table_pack_reduce
|
||||||
|
BuiltinTests.table_pack_variadic
|
||||||
BuiltinTests.tonumber_returns_optional_number_type
|
BuiltinTests.tonumber_returns_optional_number_type
|
||||||
BuiltinTests.tonumber_returns_optional_number_type2
|
BuiltinTests.tonumber_returns_optional_number_type2
|
||||||
DefinitionTests.class_definition_overload_metamethods
|
DefinitionTests.class_definition_overload_metamethods
|
||||||
DefinitionTests.declaring_generic_functions
|
DefinitionTests.declaring_generic_functions
|
||||||
DefinitionTests.definition_file_classes
|
DefinitionTests.definition_file_classes
|
||||||
FrontendTest.ast_node_at_position
|
|
||||||
FrontendTest.automatically_check_dependent_scripts
|
FrontendTest.automatically_check_dependent_scripts
|
||||||
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.nocheck_cycle_used_by_checked
|
FrontendTest.nocheck_cycle_used_by_checked
|
||||||
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.report_syntax_error_in_required_file
|
FrontendTest.reexport_type_alias
|
||||||
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
|
||||||
@ -212,13 +157,16 @@ IntersectionTypes.table_intersection_write_sealed_indirect
|
|||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
isSubtype.intersection_of_tables
|
isSubtype.intersection_of_tables
|
||||||
isSubtype.table_with_table_prop
|
isSubtype.table_with_table_prop
|
||||||
|
ModuleTests.any_persistance_does_not_leak
|
||||||
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
|
||||||
NonstrictModeTests.for_in_iterator_variables_are_any
|
NonstrictModeTests.for_in_iterator_variables_are_any
|
||||||
NonstrictModeTests.function_parameters_are_any
|
NonstrictModeTests.function_parameters_are_any
|
||||||
NonstrictModeTests.inconsistent_module_return_types_are_ok
|
NonstrictModeTests.inconsistent_module_return_types_are_ok
|
||||||
|
NonstrictModeTests.inconsistent_return_types_are_ok
|
||||||
NonstrictModeTests.infer_nullary_function
|
NonstrictModeTests.infer_nullary_function
|
||||||
|
NonstrictModeTests.infer_the_maximum_number_of_values_the_function_could_return
|
||||||
NonstrictModeTests.inline_table_props_are_also_any
|
NonstrictModeTests.inline_table_props_are_also_any
|
||||||
NonstrictModeTests.local_tables_are_not_any
|
NonstrictModeTests.local_tables_are_not_any
|
||||||
NonstrictModeTests.locals_are_any_by_default
|
NonstrictModeTests.locals_are_any_by_default
|
||||||
@ -324,7 +272,6 @@ RefinementTest.typeguard_in_if_condition_position
|
|||||||
RefinementTest.typeguard_narrows_for_functions
|
RefinementTest.typeguard_narrows_for_functions
|
||||||
RefinementTest.typeguard_narrows_for_table
|
RefinementTest.typeguard_narrows_for_table
|
||||||
RefinementTest.typeguard_not_to_be_string
|
RefinementTest.typeguard_not_to_be_string
|
||||||
RefinementTest.typeguard_only_look_up_types_from_global_scope
|
|
||||||
RefinementTest.what_nonsensical_condition
|
RefinementTest.what_nonsensical_condition
|
||||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||||
RefinementTest.x_is_not_instance_or_else_not_part
|
RefinementTest.x_is_not_instance_or_else_not_part
|
||||||
@ -349,6 +296,7 @@ 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
|
||||||
@ -363,13 +311,11 @@ TableTests.found_like_key_in_table_function_call
|
|||||||
TableTests.found_like_key_in_table_property_access
|
TableTests.found_like_key_in_table_property_access
|
||||||
TableTests.found_multiple_like_keys
|
TableTests.found_multiple_like_keys
|
||||||
TableTests.function_calls_produces_sealed_table_given_unsealed_table
|
TableTests.function_calls_produces_sealed_table_given_unsealed_table
|
||||||
TableTests.generalize_table_argument
|
|
||||||
TableTests.getmetatable_returns_pointer_to_metatable
|
TableTests.getmetatable_returns_pointer_to_metatable
|
||||||
TableTests.give_up_after_one_metatable_index_look_up
|
TableTests.give_up_after_one_metatable_index_look_up
|
||||||
TableTests.hide_table_error_properties
|
TableTests.hide_table_error_properties
|
||||||
TableTests.indexer_fn
|
TableTests.indexer_fn
|
||||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||||
TableTests.indexer_table
|
|
||||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||||
TableTests.inequality_operators_imply_exactly_matching_types
|
TableTests.inequality_operators_imply_exactly_matching_types
|
||||||
TableTests.infer_array_2
|
TableTests.infer_array_2
|
||||||
@ -395,11 +341,11 @@ TableTests.open_table_unification_2
|
|||||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||||
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
|
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
|
||||||
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
|
|
||||||
TableTests.persistent_sealed_table_is_immutable
|
TableTests.persistent_sealed_table_is_immutable
|
||||||
TableTests.prop_access_on_key_whose_types_mismatches
|
TableTests.prop_access_on_key_whose_types_mismatches
|
||||||
TableTests.property_lookup_through_tabletypevar_metatable
|
TableTests.property_lookup_through_tabletypevar_metatable
|
||||||
TableTests.quantify_even_that_table_was_never_exported_at_all
|
TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||||
|
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.result_is_always_any_if_lhs_is_any
|
TableTests.result_is_always_any_if_lhs_is_any
|
||||||
@ -435,8 +381,11 @@ ToString.function_type_with_argument_names_generic
|
|||||||
ToString.no_parentheses_around_cyclic_function_type_in_union
|
ToString.no_parentheses_around_cyclic_function_type_in_union
|
||||||
ToString.toStringDetailed2
|
ToString.toStringDetailed2
|
||||||
ToString.toStringErrorPack
|
ToString.toStringErrorPack
|
||||||
|
ToString.toStringNamedFunction_generic_pack
|
||||||
|
ToString.toStringNamedFunction_hide_self_param
|
||||||
ToString.toStringNamedFunction_hide_type_params
|
ToString.toStringNamedFunction_hide_type_params
|
||||||
ToString.toStringNamedFunction_id
|
ToString.toStringNamedFunction_id
|
||||||
|
ToString.toStringNamedFunction_include_self_param
|
||||||
ToString.toStringNamedFunction_map
|
ToString.toStringNamedFunction_map
|
||||||
ToString.toStringNamedFunction_variadics
|
ToString.toStringNamedFunction_variadics
|
||||||
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
|
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
|
||||||
@ -453,6 +402,7 @@ 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
|
||||||
TypeAliases.recursive_types_restriction_not_ok
|
TypeAliases.recursive_types_restriction_not_ok
|
||||||
|
TypeAliases.report_shadowed_aliases
|
||||||
TypeAliases.stringify_optional_parameterized_alias
|
TypeAliases.stringify_optional_parameterized_alias
|
||||||
TypeAliases.stringify_type_alias_of_recursive_template_table_type
|
TypeAliases.stringify_type_alias_of_recursive_template_table_type
|
||||||
TypeAliases.stringify_type_alias_of_recursive_template_table_type2
|
TypeAliases.stringify_type_alias_of_recursive_template_table_type2
|
||||||
@ -460,11 +410,14 @@ 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.dont_report_type_errors_within_an_AstExprError
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||||
TypeInfer.globals
|
TypeInfer.globals
|
||||||
TypeInfer.globals2
|
TypeInfer.globals2
|
||||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||||
|
TypeInfer.it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict
|
||||||
TypeInfer.no_stack_overflow_from_isoptional
|
TypeInfer.no_stack_overflow_from_isoptional
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||||
@ -489,6 +442,7 @@ TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_propert
|
|||||||
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.call_o_with_another_argument_after_foo_was_quantified
|
TypeInferFunctions.call_o_with_another_argument_after_foo_was_quantified
|
||||||
|
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.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
||||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||||
@ -500,16 +454,20 @@ TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
|||||||
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
||||||
TypeInferFunctions.function_does_not_return_enough_values
|
TypeInferFunctions.function_does_not_return_enough_values
|
||||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||||
|
TypeInferFunctions.ignored_return_values
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||||
TypeInferFunctions.inconsistent_higher_order_function
|
TypeInferFunctions.inconsistent_higher_order_function
|
||||||
TypeInferFunctions.inconsistent_return_types
|
TypeInferFunctions.inconsistent_return_types
|
||||||
TypeInferFunctions.infer_anonymous_function_arguments
|
TypeInferFunctions.infer_anonymous_function_arguments
|
||||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||||
|
TypeInferFunctions.infer_return_value_type
|
||||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||||
|
TypeInferFunctions.it_is_ok_not_to_supply_enough_retvals
|
||||||
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
||||||
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
||||||
TypeInferFunctions.no_lossy_function_type
|
TypeInferFunctions.no_lossy_function_type
|
||||||
|
TypeInferFunctions.occurs_check_failure_in_function_return_type
|
||||||
TypeInferFunctions.quantify_constrained_types
|
TypeInferFunctions.quantify_constrained_types
|
||||||
TypeInferFunctions.record_matching_overload
|
TypeInferFunctions.record_matching_overload
|
||||||
TypeInferFunctions.report_exiting_without_return_nonstrict
|
TypeInferFunctions.report_exiting_without_return_nonstrict
|
||||||
@ -521,7 +479,13 @@ TypeInferFunctions.too_few_arguments_variadic_generic
|
|||||||
TypeInferFunctions.too_few_arguments_variadic_generic2
|
TypeInferFunctions.too_few_arguments_variadic_generic2
|
||||||
TypeInferFunctions.too_many_arguments
|
TypeInferFunctions.too_many_arguments
|
||||||
TypeInferFunctions.too_many_return_values
|
TypeInferFunctions.too_many_return_values
|
||||||
|
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||||
|
TypeInferFunctions.too_many_return_values_no_function
|
||||||
TypeInferFunctions.vararg_function_is_quantified
|
TypeInferFunctions.vararg_function_is_quantified
|
||||||
|
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||||
|
TypeInferLoops.for_in_loop_with_custom_iterator
|
||||||
|
TypeInferLoops.for_in_loop_with_next
|
||||||
|
TypeInferLoops.for_in_with_generic_next
|
||||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||||
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
||||||
TypeInferLoops.loop_iter_trailing_nil
|
TypeInferLoops.loop_iter_trailing_nil
|
||||||
@ -529,6 +493,9 @@ TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
|||||||
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.custom_require_global
|
TypeInferModules.custom_require_global
|
||||||
|
TypeInferModules.do_not_modify_imported_types
|
||||||
|
TypeInferModules.do_not_modify_imported_types_2
|
||||||
|
TypeInferModules.do_not_modify_imported_types_3
|
||||||
TypeInferModules.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
|
||||||
@ -539,8 +506,8 @@ 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
|
||||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
||||||
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
|
|
||||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||||
|
TypeInferOOP.methods_are_topologically_sorted
|
||||||
TypeInferOperators.and_adds_boolean
|
TypeInferOperators.and_adds_boolean
|
||||||
TypeInferOperators.and_adds_boolean_no_superfluous_union
|
TypeInferOperators.and_adds_boolean_no_superfluous_union
|
||||||
TypeInferOperators.and_binexps_dont_unify
|
TypeInferOperators.and_binexps_dont_unify
|
||||||
@ -564,6 +531,7 @@ TypeInferOperators.expected_types_through_binary_or
|
|||||||
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
|
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
|
||||||
TypeInferOperators.or_joins_types
|
TypeInferOperators.or_joins_types
|
||||||
TypeInferOperators.or_joins_types_with_no_extras
|
TypeInferOperators.or_joins_types_with_no_extras
|
||||||
|
TypeInferOperators.primitive_arith_no_metatable
|
||||||
TypeInferOperators.primitive_arith_possible_metatable
|
TypeInferOperators.primitive_arith_possible_metatable
|
||||||
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
|
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
|
||||||
TypeInferOperators.refine_and_or
|
TypeInferOperators.refine_and_or
|
||||||
@ -591,6 +559,7 @@ TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_
|
|||||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||||
TypeInferUnknownNever.math_operators_and_never
|
TypeInferUnknownNever.math_operators_and_never
|
||||||
|
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
|
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
|
||||||
TypeInferUnknownNever.unary_minus_of_never
|
TypeInferUnknownNever.unary_minus_of_never
|
||||||
TypePackTests.higher_order_function
|
TypePackTests.higher_order_function
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
|
type synthetic add -x "^Luau::detail::DenseHashTable<.*>$" -l lldb_formatters.DenseHashTableSyntheticChildrenProvider
|
||||||
|
type summary add "Luau::Symbol" -F lldb_formatters.luau_symbol_summary
|
||||||
|
|
||||||
type synthetic add -x "^Luau::Variant<.+>$" -l lldb_formatters.LuauVariantSyntheticChildrenProvider
|
type synthetic add -x "^Luau::Variant<.+>$" -l lldb_formatters.LuauVariantSyntheticChildrenProvider
|
||||||
type summary add -x "^Luau::Variant<.+>$" -l lldb_formatters.luau_variant_summary
|
type summary add -x "^Luau::Variant<.+>$" -F lldb_formatters.luau_variant_summary
|
||||||
|
|
||||||
|
type synthetic add -x "^Luau::AstArray<.+>$" -l lldb_formatters.AstArraySyntheticChildrenProvider
|
||||||
|
@ -4,30 +4,31 @@
|
|||||||
# We're forced to resort to parsing names as strings.
|
# We're forced to resort to parsing names as strings.
|
||||||
def templateParams(s):
|
def templateParams(s):
|
||||||
depth = 0
|
depth = 0
|
||||||
start = s.find('<') + 1
|
start = s.find("<") + 1
|
||||||
result = []
|
result = []
|
||||||
for i, c in enumerate(s[start:], start):
|
for i, c in enumerate(s[start:], start):
|
||||||
if c == '<':
|
if c == "<":
|
||||||
depth += 1
|
depth += 1
|
||||||
elif c == '>':
|
elif c == ">":
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
result.append(s[start: i].strip())
|
result.append(s[start:i].strip())
|
||||||
break
|
break
|
||||||
depth -= 1
|
depth -= 1
|
||||||
elif c == ',' and depth == 0:
|
elif c == "," and depth == 0:
|
||||||
result.append(s[start: i].strip())
|
result.append(s[start:i].strip())
|
||||||
start = i + 1
|
start = i + 1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def getType(target, typeName):
|
def getType(target, typeName):
|
||||||
stars = 0
|
stars = 0
|
||||||
|
|
||||||
typeName = typeName.strip()
|
typeName = typeName.strip()
|
||||||
while typeName.endswith('*'):
|
while typeName.endswith("*"):
|
||||||
stars += 1
|
stars += 1
|
||||||
typeName = typeName[:-1]
|
typeName = typeName[:-1]
|
||||||
|
|
||||||
if typeName.startswith('const '):
|
if typeName.startswith("const "):
|
||||||
typeName = typeName[6:]
|
typeName = typeName[6:]
|
||||||
|
|
||||||
ty = target.FindFirstType(typeName.strip())
|
ty = target.FindFirstType(typeName.strip())
|
||||||
@ -36,13 +37,10 @@ def getType(target, typeName):
|
|||||||
|
|
||||||
return ty
|
return ty
|
||||||
|
|
||||||
|
|
||||||
def luau_variant_summary(valobj, internal_dict, options):
|
def luau_variant_summary(valobj, internal_dict, options):
|
||||||
type_id = valobj.GetChildMemberWithName("typeId").GetValueAsUnsigned()
|
return valobj.GetChildMemberWithName("type").GetSummary()[1:-1]
|
||||||
storage = valobj.GetChildMemberWithName("storage")
|
|
||||||
params = templateParams(valobj.GetType().GetCanonicalType().GetName())
|
|
||||||
stored_type = params[type_id]
|
|
||||||
value = storage.Cast(stored_type.GetPointerType()).Dereference()
|
|
||||||
return stored_type.GetDisplayTypeName() + " [" + value.GetValue() + "]"
|
|
||||||
|
|
||||||
class LuauVariantSyntheticChildrenProvider:
|
class LuauVariantSyntheticChildrenProvider:
|
||||||
node_names = ["type", "value"]
|
node_names = ["type", "value"]
|
||||||
@ -74,26 +72,42 @@ class LuauVariantSyntheticChildrenProvider:
|
|||||||
|
|
||||||
if node == "type":
|
if node == "type":
|
||||||
if self.current_type:
|
if self.current_type:
|
||||||
return self.valobj.CreateValueFromExpression(node, f"(const char*)\"{self.current_type.GetDisplayTypeName()}\"")
|
return self.valobj.CreateValueFromExpression(
|
||||||
|
node, f'(const char*)"{self.current_type.GetDisplayTypeName()}"'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<unknown type>\"")
|
return self.valobj.CreateValueFromExpression(
|
||||||
|
node, '(const char*)"<unknown type>"'
|
||||||
|
)
|
||||||
elif node == "value":
|
elif node == "value":
|
||||||
if self.stored_value is not None:
|
if self.stored_value is not None:
|
||||||
if self.current_type is not None:
|
if self.current_type is not None:
|
||||||
return self.valobj.CreateValueFromData(node, self.stored_value.GetData(), self.current_type)
|
return self.valobj.CreateValueFromData(
|
||||||
|
node, self.stored_value.GetData(), self.current_type
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return self.valobj.CreateValueExpression(node, "(const char*)\"<unknown type>\"")
|
return self.valobj.CreateValueExpression(
|
||||||
|
node, '(const char*)"<unknown type>"'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<no stored value>\"")
|
return self.valobj.CreateValueFromExpression(
|
||||||
|
node, '(const char*)"<no stored value>"'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.type_index = self.valobj.GetChildMemberWithName("typeId").GetValueAsSigned()
|
self.type_index = self.valobj.GetChildMemberWithName(
|
||||||
self.type_params = templateParams(self.valobj.GetType().GetCanonicalType().GetName())
|
"typeId"
|
||||||
|
).GetValueAsSigned()
|
||||||
|
self.type_params = templateParams(
|
||||||
|
self.valobj.GetType().GetCanonicalType().GetName()
|
||||||
|
)
|
||||||
|
|
||||||
if len(self.type_params) > self.type_index:
|
if len(self.type_params) > self.type_index:
|
||||||
self.current_type = getType(self.valobj.GetTarget(), self.type_params[self.type_index])
|
self.current_type = getType(
|
||||||
|
self.valobj.GetTarget(), self.type_params[self.type_index]
|
||||||
|
)
|
||||||
|
|
||||||
if self.current_type:
|
if self.current_type:
|
||||||
storage = self.valobj.GetChildMemberWithName("storage")
|
storage = self.valobj.GetChildMemberWithName("storage")
|
||||||
@ -105,3 +119,97 @@ class LuauVariantSyntheticChildrenProvider:
|
|||||||
self.stored_value = None
|
self.stored_value = None
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class DenseHashTableSyntheticChildrenProvider:
|
||||||
|
def __init__(self, valobj, internal_dict):
|
||||||
|
"""this call should initialize the Python object using valobj as the variable to provide synthetic children for"""
|
||||||
|
self.valobj = valobj
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def num_children(self):
|
||||||
|
"""this call should return the number of children that you want your object to have"""
|
||||||
|
return self.capacity
|
||||||
|
|
||||||
|
def get_child_index(self, name):
|
||||||
|
"""this call should return the index of the synthetic child whose name is given as argument"""
|
||||||
|
try:
|
||||||
|
if name.startswith("[") and name.endswith("]"):
|
||||||
|
return int(name[1:-1])
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
except Exception as e:
|
||||||
|
print("get_child_index exception", e)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def get_child_at_index(self, index):
|
||||||
|
"""this call should return a new LLDB SBValue object representing the child at the index given as argument"""
|
||||||
|
try:
|
||||||
|
dataMember = self.valobj.GetChildMemberWithName("data")
|
||||||
|
|
||||||
|
data = dataMember.GetPointeeData(index)
|
||||||
|
|
||||||
|
return self.valobj.CreateValueFromData(
|
||||||
|
f"[{index}]",
|
||||||
|
data,
|
||||||
|
dataMember.Dereference().GetType(),
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("get_child_at_index error", e)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""this call should be used to update the internal state of this Python object whenever the state of the variables in LLDB changes.[1]
|
||||||
|
Also, this method is invoked before any other method in the interface."""
|
||||||
|
self.capacity = self.valobj.GetChildMemberWithName(
|
||||||
|
"capacity"
|
||||||
|
).GetValueAsUnsigned()
|
||||||
|
|
||||||
|
def has_children(self):
|
||||||
|
"""this call should return True if this object might have children, and False if this object can be guaranteed not to have children.[2]"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def luau_symbol_summary(valobj, internal_dict, options):
|
||||||
|
local = valobj.GetChildMemberWithName("local")
|
||||||
|
global_ = valobj.GetChildMemberWithName("global").GetChildMemberWithName("value")
|
||||||
|
|
||||||
|
if local.GetValueAsUnsigned() != 0:
|
||||||
|
return f'local {local.GetChildMemberWithName("name").GetChildMemberWithName("value").GetSummary()}'
|
||||||
|
elif global_.GetValueAsUnsigned() != 0:
|
||||||
|
return f"global {global_.GetSummary()}"
|
||||||
|
else:
|
||||||
|
return "???"
|
||||||
|
|
||||||
|
|
||||||
|
class AstArraySyntheticChildrenProvider:
|
||||||
|
def __init__(self, valobj, internal_dict):
|
||||||
|
self.valobj = valobj
|
||||||
|
|
||||||
|
def num_children(self):
|
||||||
|
return self.size
|
||||||
|
|
||||||
|
def get_child_index(self, name):
|
||||||
|
try:
|
||||||
|
if name.startswith("[") and name.endswith("]"):
|
||||||
|
return int(name[1:-1])
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
except Exception as e:
|
||||||
|
print("get_child_index error:", e)
|
||||||
|
|
||||||
|
def get_child_at_index(self, index):
|
||||||
|
try:
|
||||||
|
dataMember = self.valobj.GetChildMemberWithName("data")
|
||||||
|
data = dataMember.GetPointeeData(index)
|
||||||
|
return self.valobj.CreateValueFromData(
|
||||||
|
f"[{index}]", data, dataMember.Dereference().GetType()
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print("get_child_index error:", e)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.size = self.valobj.GetChildMemberWithName("size").GetValueAsUnsigned()
|
||||||
|
|
||||||
|
def has_children(self):
|
||||||
|
return True
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
# Given a profile dump, this tool generates a flame graph based on the stacks listed in the profile
|
# Given a profile dump, this tool generates a flame graph based on the stacks listed in the profile
|
||||||
# The result of analysis is a .svg file which can be viewed in a browser
|
# The result of analysis is a .svg file which can be viewed in a browser
|
||||||
|
|
||||||
import sys
|
|
||||||
import svg
|
import svg
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
65
tools/perfstat.py
Normal file
65
tools/perfstat.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
# Given a profile dump, this tool displays top functions based on the stacks listed in the profile
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
def __init__(self):
|
||||||
|
self.function = ""
|
||||||
|
self.source = ""
|
||||||
|
self.line = 0
|
||||||
|
self.hier_ticks = 0
|
||||||
|
self.self_ticks = 0
|
||||||
|
|
||||||
|
def title(self):
|
||||||
|
if self.line > 0:
|
||||||
|
return "{} ({}:{})".format(self.function, self.source, self.line)
|
||||||
|
else:
|
||||||
|
return self.function
|
||||||
|
|
||||||
|
argumentParser = argparse.ArgumentParser(description='Display summary statistics from Luau sampling profiler dumps')
|
||||||
|
argumentParser.add_argument('source_file', type=open)
|
||||||
|
argumentParser.add_argument('--limit', dest='limit', type=int, default=10, help='Display top N functions')
|
||||||
|
|
||||||
|
arguments = argumentParser.parse_args()
|
||||||
|
|
||||||
|
dump = arguments.source_file.readlines()
|
||||||
|
|
||||||
|
stats = {}
|
||||||
|
total = 0
|
||||||
|
total_gc = 0
|
||||||
|
|
||||||
|
for l in dump:
|
||||||
|
ticks, stack = l.strip().split(" ", 1)
|
||||||
|
hier = {}
|
||||||
|
|
||||||
|
for f in reversed(stack.split(";")):
|
||||||
|
source, function, line = f.split(",")
|
||||||
|
node = stats.setdefault(f, Node())
|
||||||
|
|
||||||
|
node.function = function
|
||||||
|
node.source = source
|
||||||
|
node.line = int(line) if len(line) > 0 else 0
|
||||||
|
|
||||||
|
if not node in hier:
|
||||||
|
node.hier_ticks += int(ticks)
|
||||||
|
hier[node] = True
|
||||||
|
|
||||||
|
total += int(ticks)
|
||||||
|
node.self_ticks += int(ticks)
|
||||||
|
|
||||||
|
if node.source == "GC":
|
||||||
|
total_gc += int(ticks)
|
||||||
|
|
||||||
|
if total > 0:
|
||||||
|
print(f"Runtime: {total:,} usec ({100.0 * total_gc / total:.2f}% GC)")
|
||||||
|
print()
|
||||||
|
print("Top functions (self time):")
|
||||||
|
for n in sorted(stats.values(), key=lambda node: node.self_ticks, reverse=True)[:arguments.limit]:
|
||||||
|
print(f"{n.self_ticks:12,} usec ({100.0 * n.self_ticks / total:.2f}%): {n.title()}")
|
||||||
|
print()
|
||||||
|
print("Top functions (total time):")
|
||||||
|
for n in sorted(stats.values(), key=lambda node: node.hier_ticks, reverse=True)[:arguments.limit]:
|
||||||
|
print(f"{n.hier_ticks:12,} usec ({100.0 * n.hier_ticks / total:.2f}%): {n.title()}")
|
@ -39,7 +39,7 @@ class Handler(x.ContentHandler):
|
|||||||
|
|
||||||
elif name == "OverallResultsAsserts":
|
elif name == "OverallResultsAsserts":
|
||||||
if self.currentTest:
|
if self.currentTest:
|
||||||
passed = 0 == safeParseInt(attrs["failures"])
|
passed = attrs["test_case_success"] == "true"
|
||||||
|
|
||||||
dottedName = ".".join(self.currentTest)
|
dottedName = ".".join(self.currentTest)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user