Sync to upstream/release/547 (#690)

- Type aliases can no longer override primitive types; attempts to do
that will result in a type error
- Fix misleading type error messages for mismatches in expression list
length during assignment
- Fix incorrect type name display in certain cases
- setmetatable/getmetatable are now ~2x faster
- tools/perfstat.py can be used to display statistics about profiles
captured via --profile switch
This commit is contained in:
Arseny Kapoulkine 2022-09-29 15:23:10 -07:00 committed by GitHub
parent 1acd66c97d
commit 944e8375aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 1660 additions and 702 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 != &currentModule->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 == &currentModule->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 != &currentModule->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 == &currentModule->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, &currentModule->internalTypes, level, retPack); promoteTypeLevels(log, &currentModule->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, &currentModule->internalTypes, scope->level}; Instantiation instantiation{log, &currentModule->internalTypes, scope->level, /*scope*/ nullptr};
if (FFlag::LuauAutocompleteDynamicLimits && instantiationChildLimit) if (FFlag::LuauAutocompleteDynamicLimits && instantiationChildLimit)
instantiation.childLimit = *instantiationChildLimit; instantiation.childLimit = *instantiationChildLimit;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,6 +57,7 @@ const char* const luaT_eventname[] = {
"__le", "__le",
"__concat", "__concat",
"__type", "__type",
"__metatable",
}; };
// clang-format on // clang-format on

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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()}")

View File

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