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
{
void registerBuiltinTypes(TypeChecker& typeChecker);
void registerBuiltinTypes(Frontend& frontend);
void registerBuiltinGlobals(TypeChecker& typeChecker);
void registerBuiltinGlobals(Frontend& frontend);
TypeId makeUnion(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
{
Config()
{
enabledLint.setDefaults();
}
Config();
Mode mode = Mode::NoCheck;
Mode mode;
ParseOptions parseOptions;

View file

@ -94,8 +94,9 @@ struct FunctionCallConstraint
{
std::vector<NotNull<const struct Constraint>> innerConstraints;
TypeId fn;
TypePackId argsPack;
TypePackId result;
class AstExprCall* astFragment;
class AstExprCall* callSite;
};
// 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, AstStatDeclareClass* declareClass);
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, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});

View file

@ -104,6 +104,7 @@ struct ConstraintSolver
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do
// also handles __iter metamethod
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
// for a, ... in next_function, t, ... do

View file

@ -81,7 +81,7 @@ struct OnlyTablesCanHaveMethods
struct DuplicateTypeDefinition
{
Name name;
Location previousLocation;
std::optional<Location> previousLocation;
bool operator==(const DuplicateTypeDefinition& rhs) const;
};
@ -91,7 +91,8 @@ struct CountMismatch
enum Context
{
Arg,
Result,
FunctionResult,
ExprListResult,
Return,
};
size_t expected;

View file

@ -157,7 +157,8 @@ struct Frontend
ScopePtr getGlobalScope();
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);
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, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
ScopePtr globalScope;
SingletonTypes singletonTypes_;
public:
SingletonTypes singletonTypes_;
const NotNull<SingletonTypes> singletonTypes;
FileResolver* fileResolver;
@ -186,13 +186,15 @@ public:
FrontendOptions options;
InternalErrorReporter iceHandler;
TypeArena globalTypes;
TypeArena arenaForAutocomplete;
std::unordered_map<ModuleName, SourceNode> sourceNodes;
std::unordered_map<ModuleName, SourceModule> sourceModules;
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
Stats stats = {};
private:
ScopePtr globalScope;
};
} // namespace Luau

View file

@ -14,16 +14,18 @@ struct TxnLog;
// A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution
{
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
ReplaceGenerics(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, scope(scope)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level;
Scope* scope;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
bool ignoreChildren(TypeId ty) override;
@ -36,13 +38,15 @@ struct ReplaceGenerics : Substitution
// A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution
{
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope)
: Substitution(log, arena)
, level(level)
, scope(scope)
{
}
TypeLevel level;
Scope* scope;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) 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> privateTypeBindings;
std::unordered_map<Name, Location> typeAliasLocations;
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<TypeFun> lookupType(const Name& name);
@ -61,7 +63,7 @@ struct Scope
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)
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;
@ -73,4 +75,13 @@ struct Scope
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

View file

@ -186,6 +186,16 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called.
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
// indexer.
//

View file

@ -30,6 +30,7 @@ struct TypeArena
TypeId freshType(TypeLevel level);
TypeId freshType(Scope* scope);
TypeId freshType(Scope* scope, TypeLevel level);
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, 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, 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 AstStatDeclareFunction& declaredFunction);
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
@ -392,8 +394,12 @@ private:
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
};
using PrintLineProc = void(*)(const std::string&);
extern PrintLineProc luauPrintLine;
// Unit test hook
void setPrintLine(void (*pl)(const std::string& s));
void setPrintLine(PrintLineProc pl);
void resetPrintLine();
} // 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.
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

View file

@ -27,6 +27,7 @@ namespace Luau
struct TypeArena;
struct Scope;
using ScopePtr = std::shared_ptr<Scope>;
/**
* There are three kinds of type variables:
@ -264,7 +265,15 @@ struct WithPredicate
using MagicFunction = std::function<std::optional<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
{
@ -277,10 +286,14 @@ struct FunctionTypeVar
// Local monomorphic function
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
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
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;
Scope* scope = nullptr;
@ -345,8 +358,9 @@ struct TableTypeVar
using Props = std::map<Name, Property>;
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, Scope* scope, TableState state);
Props props;
std::optional<TableIndexer> indexer;

View file

@ -85,6 +85,7 @@ struct Free
{
explicit Free(TypeLevel level);
explicit Free(Scope* scope);
explicit Free(Scope* scope, TypeLevel level);
int index;
TypeLevel level;

View file

@ -60,6 +60,7 @@ struct Unifier
Location location;
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 useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
CountMismatch::Context ctx = CountMismatch::Arg;
UnifierSharedState& sharedState;
@ -140,6 +141,6 @@ private:
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

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)
{
LUAU_ASSERT(!ancestry.empty());
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
AstNode* node = ancestry.rbegin()[0];
if (node->is<AstExprIndexName>())
@ -1254,16 +1252,16 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
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 correctForFalse = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->falseType);
TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
result["true"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForTrue};
result["false"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForFalse};
result["nil"] = {AutocompleteEntryKind::Keyword, typeChecker.nilType, false, false, correctForNil};
result["true"] = {AutocompleteEntryKind::Keyword, singletonTypes->booleanType, false, false, correctForTrue};
result["false"] = {AutocompleteEntryKind::Keyword, singletonTypes->booleanType, false, false, correctForFalse};
result["nil"] = {AutocompleteEntryKind::Keyword, singletonTypes->nilType, false, false, correctForNil};
result["not"] = {AutocompleteEntryKind::Keyword};
result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction};
@ -1274,11 +1272,11 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
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)
{
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};
}
@ -1385,13 +1383,13 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt;
}
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, const TypeChecker& typeChecker,
TypeArena* typeArena, Position position, StringCompletionCallback callback)
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes,
Scope* globalScope, Position position, StringCompletionCallback callback)
{
if (isWithinComment(sourceModule, position))
return {};
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
TypeArena typeArena;
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
LUAU_ASSERT(!ancestry.empty());
@ -1419,11 +1417,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
return {autocompleteProps(
*module, typeArena, singletonTypes, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
return {autocompleteProps(*module, &typeArena, singletonTypes, globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
ancestry, AutocompleteContext::Property};
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>())
{
@ -1441,7 +1438,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
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
return {};
}
@ -1455,7 +1452,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (statFor->from->location.containsClosed(position) || statFor->to->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 {};
}
@ -1485,7 +1482,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
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)
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};
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)
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>())
{
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))
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)))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
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)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
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))
{
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
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 (!key)
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position, result);
autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position, result);
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 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>())
{
@ -1604,12 +1601,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
}
if (node->is<AstExprConstantNumber>())
{
return {};
}
if (node->asExpr())
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
else if (node->asStat())
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
@ -1628,15 +1623,15 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
if (!sourceModule)
return {};
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
if (!module)
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;
}

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
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Ast.h"
#include "Luau/Frontend.h"
#include "Luau/Symbol.h"
#include "Luau/Common.h"
#include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
/** 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(
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)
{
@ -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.typePacks.isFrozen());
@ -303,6 +324,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
attachDcrMagicFunction(getGlobalBinding(typeChecker, "select"), dcrMagicFunctionSelect);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table")))
{
@ -317,12 +339,13 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
}
void registerBuiltinTypes(Frontend& frontend)
void registerBuiltinGlobals(Frontend& frontend)
{
LUAU_ASSERT(!frontend.globalTypes.typeVars.isFrozen());
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
TypeId nilType = frontend.typeChecker.nilType;
if (FFlag::LuauReportShadowedTypeAlias)
registerBuiltinTypes(frontend);
TypeArena& arena = frontend.globalTypes;
NotNull<SingletonTypes> singletonTypes = frontend.singletonTypes;
@ -352,7 +375,7 @@ void registerBuiltinTypes(Frontend& frontend)
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
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)
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, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(frontend, "select"), magicFunctionSelect);
attachDcrMagicFunction(getGlobalBinding(frontend, "select"), dcrMagicFunctionSelect);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(frontend, "table")))
{
@ -408,7 +432,6 @@ void registerBuiltinTypes(Frontend& frontend)
attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire);
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
@ -450,6 +473,50 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
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(
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;
}
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;
}
if (!checkRequirePathDcr(solver, expr->args.data[0]))
if (!checkRequirePathDcr(context.solver, context.callSite->args.data[0]))
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);
TypePackId moduleResult = solver->arena->addTypePack({moduleType});
asMutable(result)->ty.emplace<BoundTypePack>(moduleResult);
TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location);
TypePackId moduleResult = context.solver->arena->addTypePack({moduleType});
asMutable(context.result)->ty.emplace<BoundTypePack>(moduleResult);
return true;
}

View file

@ -220,6 +220,9 @@ void TypeCloner::operator()(const SingletonTypeVar& 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});
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
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))
{
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.genericPacks = ftv->genericPacks;
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))
{
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.name = ttv->name;
clone.syntheticName = ttv->syntheticName;

View file

@ -4,15 +4,18 @@
#include "Luau/Lexer.h"
#include "Luau/StringUtils.h"
namespace
LUAU_FASTFLAGVARIABLE(LuauEnableNonstrictByDefaultForLuauConfig, false)
namespace Luau
{
using Error = std::optional<std::string>;
}
namespace Luau
Config::Config()
: mode(FFlag::LuauEnableNonstrictByDefaultForLuauConfig ? Mode::Nonstrict : Mode::NoCheck)
{
enabledLint.setDefaults();
}
static Error parseBoolean(bool& result, const std::string& value)
{

View file

@ -11,6 +11,7 @@
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
#include "Luau/Scope.h"
@ -218,6 +219,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>())
visit(scope, s);
else if (auto s = stat->as<AstStatError>())
visit(scope, s);
else
LUAU_ASSERT(0);
}
@ -454,8 +457,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
TypeId containingTableType = check(scope, indexName->expr);
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)};
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));
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);
ctv->metatable = metaTy;
@ -715,7 +720,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
TypePackId paramPack = resolveTypePack(funScope, global->params);
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);
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};
}
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)
{
std::vector<TypeId> head;
@ -745,7 +758,9 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
}
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);
}
}
@ -803,7 +818,8 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
// TODO: How do expectedTypes play into this? Do they?
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);
scope->unqueuedConstraints.push_back(
@ -834,6 +850,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
FunctionCallConstraint{
{ic, sc},
fnType,
argPack,
rets,
call,
});
@ -968,6 +985,9 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::
else if (auto err = expr->as<AstExprError>())
{
// Open question: Should we traverse into this?
for (AstExpr* subExpr : err->expressions)
check(scope, subExpr);
result = singletonTypes->errorRecoveryType();
}
else
@ -988,7 +1008,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
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));
@ -1005,7 +1025,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
TypeId result = freshType(scope);
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});
@ -1094,6 +1115,9 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr,
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(ttv);
ttv->state = TableState::Unsealed;
ttv->scope = scope.get();
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
if (!ttv->indexer)
{
@ -1195,7 +1219,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
}
else
{
bodyScope = childScope(fn->body, parent);
bodyScope = childScope(fn, parent);
returnType = freshTypePack(bodyScope);
bodyScope->returnType = returnType;
@ -1260,7 +1284,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// TODO: Vararg annotation.
// 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.generics = std::move(genericTypes);
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 (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);
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>())
{
@ -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
// how to quantify/instantiate it.
FunctionTypeVar ftv{argTypes, returnTypes};
FunctionTypeVar ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
// This replicates the behavior of the appropriate FunctionTypeVar
// constructors.

View file

@ -8,9 +8,11 @@
#include "Luau/ModuleResolver.h"
#include "Luau/Quantify.h"
#include "Luau/ToString.h"
#include "Luau/TypeVar.h"
#include "Luau/Unifier.h"
#include "Luau/DcrLogger.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/TypeUtils.h"
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
@ -439,6 +441,7 @@ bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const
return block(c.superPack, constraint);
unify(c.subPack, c.superPack, constraint->scope);
return true;
}
@ -465,7 +468,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
if (isBlocked(c.superType))
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);
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
@ -909,7 +912,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
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)
@ -1087,6 +1090,63 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
else
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))
{
TypeId metaTy = follow(iteratorMetatable->metatable);
@ -1124,7 +1184,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
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);
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};
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.useScopes = true;
u.tryUnify(subType, superType);
@ -1319,6 +1380,7 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
{
UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.useScopes = true;
u.tryUnify(subPack, superPack);

View file

@ -169,7 +169,10 @@ struct ErrorConverter
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
@ -183,11 +186,14 @@ struct ErrorConverter
case CountMismatch::Return:
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
actualVerb + " returned here";
case CountMismatch::Result:
case CountMismatch::FunctionResult:
// It is alright if right hand side produces more values than the
// 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) +
" are required here";
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
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:
if (!e.function.empty())
return "Argument count mismatch. Function '" + e.function + "' " +

View file

@ -400,6 +400,7 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, singletonTypes, &iceHandler)
, configResolver(configResolver)
, options(options)
, globalScope(typeChecker.globalScope)
{
}
@ -505,7 +506,10 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
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;
double duration = getTimestamp() - timestamp;
@ -837,7 +841,8 @@ ScopePtr Frontend::getGlobalScope()
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>();
@ -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);
result->errors = std::move(cgb.errors);

View file

@ -44,7 +44,7 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
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.dcrMagicFunction = ftv->dcrMagicFunction;
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,
// 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?
// We don't have access to the error-reporting machinery
@ -114,12 +114,12 @@ TypeId ReplaceGenerics::clean(TypeId ty)
LUAU_ASSERT(isDirty(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;
return addType(std::move(clone));
}
else
return addType(FreeTypeVar{level});
return addType(FreeTypeVar{scope, level});
}
TypePackId ReplaceGenerics::clean(TypePackId tp)

View file

@ -15,19 +15,6 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
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
{
TypeLevel level;
@ -43,12 +30,6 @@ struct Quantifier final : TypeVarOnceVisitor
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
}
explicit Quantifier(Scope* scope)
: scope(scope)
{
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
}
/// @return true if outer encloses inner
bool subsumes(Scope* outer, Scope* inner)
{
@ -66,13 +47,10 @@ struct Quantifier final : TypeVarOnceVisitor
{
seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
if (!level.subsumes(ftv.level))
return false;
if (FFlag::DebugLuauDeferredConstraintResolution)
*asMutable(ty) = GenericTypeVar{scope};
else
*asMutable(ty) = GenericTypeVar{level};
*asMutable(ty) = GenericTypeVar{level};
generics.push_back(ty);
@ -85,7 +63,7 @@ struct Quantifier final : TypeVarOnceVisitor
seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level))
if (!level.subsumes(ctv->level))
return false;
std::vector<TypeId> opts = std::move(ctv->parts);
@ -113,7 +91,7 @@ struct Quantifier final : TypeVarOnceVisitor
if (ttv.state == TableState::Free)
seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
if (!level.subsumes(ttv.level))
{
if (ttv.state == TableState::Unsealed)
seenMutableType = true;
@ -137,7 +115,7 @@ struct Quantifier final : TypeVarOnceVisitor
{
seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
if (!level.subsumes(ftp.level))
return false;
*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
{
Scope* scope;
@ -253,7 +217,7 @@ struct PureQuantifier : Substitution
{
if (auto ftv = get<FreeTypeVar>(ty))
{
TypeId result = arena->addType(GenericTypeVar{});
TypeId result = arena->addType(GenericTypeVar{scope});
insertedGenerics.push_back(result);
return result;
}
@ -264,7 +228,8 @@ struct PureQuantifier : Substitution
LUAU_ASSERT(resultTable);
*resultTable = *ttv;
resultTable->scope = nullptr;
resultTable->level = TypeLevel{};
resultTable->scope = scope;
resultTable->state = TableState::Generic;
return result;
@ -306,6 +271,7 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
LUAU_ASSERT(ftv);
ftv->scope = scope;
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->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty();

View file

@ -21,6 +21,12 @@ Scope::Scope(const ScopePtr& parent, int 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)
{
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)
{
@ -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

View file

@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
/*
* Prefix generic typenames with gen-
@ -632,6 +633,10 @@ struct TypeVarStringifier
state.emit("{");
stringify(ttv.indexer->indexResultType);
state.emit("}");
if (FFlag::LuauUnseeArrayTtv)
state.unsee(&ttv);
return;
}

View file

@ -289,6 +289,45 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
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)
{
LUAU_ASSERT(get<TableTypeVar>(ty));

View file

@ -40,6 +40,15 @@ TypeId TypeArena::freshType(Scope* scope)
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 allocated = typePacks.allocate(FreeTypePack{scope});

View file

@ -17,10 +17,16 @@
#include <algorithm>
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
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.
* TypeChecker2 uses this to maintain knowledge about which scope encloses every
* given AstNode.
@ -114,6 +120,19 @@ struct TypeChecker2
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);
LUAU_ASSERT(ty);
return follow(*ty);
@ -284,50 +303,49 @@ struct TypeChecker2
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);
auto it = begin(valueTypes);
for (size_t j = i; j < local->vars.size; ++j)
{
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;
}
TypeId varType = lookupAnnotation(var->annotation);
TypeId valueType = value ? lookupType(value) : nullptr;
if (valueType && !isSubtype(varType, valueType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
reportError(TypeMismatch{varType, valueType}, value->location);
}
}
else
{
TypeId valueType = lookupType(value);
AstLocal* var = local->vars.data[i];
LUAU_ASSERT(value);
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 (!isSubtype(varType, valueType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
if (it == end(valueTypes))
{
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);
}
// "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)
{
for (AstLocal* local : forInStatement->vars)
@ -426,7 +400,7 @@ struct TypeChecker2
TypePackId iteratorPack = arena.addTypePack(valueTypes, iteratorTail);
// ... 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())
{
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]);
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
* * 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 (iteratorTypes.size() < 1 || iteratorTypes.size() > 3)
reportError(GenericError{"for..in loops must be passed (next, [table[, state]])"}, getLocation(forInStatement->values));
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
std::vector<TypeId> expectedVariableTypes = flatten(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]));
}
checkFunction(nextFn, iteratorTypes, false);
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(iteratorTy))
{
@ -519,6 +508,62 @@ struct TypeChecker2
{
// 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
{
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
@ -730,7 +775,7 @@ struct TypeChecker2
visit(arg);
TypeArena arena;
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}};
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, stack.back()};
TypePackId expectedRetType = lookupPack(call);
TypeId functionType = lookupType(call->func);

View file

@ -47,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
namespace Luau
{
@ -66,9 +68,7 @@ static void defaultLuauPrintLine(const std::string& s)
printf("%s\n", s.c_str());
}
using PrintLineProc = decltype(&defaultLuauPrintLine);
static PrintLineProc luauPrintLine = &defaultLuauPrintLine;
PrintLineProc luauPrintLine = &defaultLuauPrintLine;
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->exportedTypeBindings["any"] = TypeFun{{}, anyType};
globalScope->exportedTypeBindings["nil"] = TypeFun{{}, nilType};
globalScope->exportedTypeBindings["number"] = TypeFun{{}, numberType};
globalScope->exportedTypeBindings["string"] = TypeFun{{}, stringType};
globalScope->exportedTypeBindings["boolean"] = TypeFun{{}, booleanType};
globalScope->exportedTypeBindings["thread"] = TypeFun{{}, threadType};
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, anyType});
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, nilType});
globalScope->addBuiltinTypeBinding("number", TypeFun{{}, numberType});
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, stringType});
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, booleanType});
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, threadType});
if (FFlag::LuauUnknownAndNeverType)
{
globalScope->exportedTypeBindings["unknown"] = TypeFun{{}, unknownType};
globalScope->exportedTypeBindings["never"] = TypeFun{{}, neverType};
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, neverType});
}
}
@ -534,7 +534,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
{
if (const auto& typealias = stat->as<AstStatTypeAlias>())
{
check(scope, *typealias, subLevel, true);
prototype(scope, *typealias, subLevel);
++subLevel;
}
}
@ -698,6 +698,10 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
Name name = typealias->name.value;
if (FFlag::LuauReportShadowedTypeAlias && duplicateTypeAliases.contains({typealias->exported, name}))
continue;
TypeId type = bindings[name].type;
if (get<FreeTypeVar>(follow(type)))
{
@ -1109,8 +1113,23 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
TypePackId valuePack =
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);
state.ctx = CountMismatch::Result;
state.ctx = ctx;
state.tryUnify(valuePack, variablePack);
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};
}
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;
// 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;
if (forwardDeclare)
{
if (binding)
{
Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
// 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;
// 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)};
duplicateTypeAliases.insert({typealias.exported, name});
}
else if (FFlag::LuauReportShadowedTypeAlias)
{
if (globalScope->builtinTypeNames.contains(name))
{
reportError(typealias.location, DuplicateTypeDefinition{name});
duplicateTypeAliases.insert({typealias.exported, name});
}
else
@ -1520,100 +1657,20 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
}
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);
aliasScope->level = scope->level.incr();
aliasScope->level.subLevel = subLevel;
for (auto param : binding->typeParams)
{
auto generic = get<GenericTypeVar>(param.ty);
LUAU_ASSERT(generic);
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, param.ty};
}
auto [generics, genericPacks] =
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
for (auto param : binding->typePackParams)
{
auto generic = get<GenericTypePack>(param.tp);
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = param.tp;
}
TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
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{});
}
scope->typeAliasLocations[name] = typealias.location;
}
}
@ -4152,7 +4209,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
TypePackId adjustedArgPack = addTypePack(TypePack{std::move(adjustedArgTypes), it.tail()});
TxnLog log;
promoteTypeLevels(log, &currentModule->internalTypes, level, retPack);
promoteTypeLevels(log, &currentModule->internalTypes, level, /*scope*/ nullptr, /*useScope*/ false, retPack);
log.commit();
*asMutable(fn) = FunctionTypeVar{level, adjustedArgPack, retPack};
@ -4712,7 +4769,7 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
if (ftv && ftv->hasNoGenerics)
return ty;
Instantiation instantiation{log, &currentModule->internalTypes, scope->level};
Instantiation instantiation{log, &currentModule->internalTypes, scope->level, /*scope*/ nullptr};
if (FFlag::LuauAutocompleteDynamicLimits && 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};
}
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

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,
std::optional<FunctionDefinition> defn, bool hasSelf)
: 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)
, 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
// 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;
Generic::Generic()

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(LuauCallUnifyPackTails)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace Luau
{
@ -33,10 +34,15 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
const TypeArena* typeArena = nullptr;
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)
, typeArena(typeArena)
, minLevel(minLevel)
, outerScope(outerScope)
, useScopes(useScopes)
{
}
@ -44,9 +50,18 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
void promote(TID ty, T* 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
if (ty->owningArena != typeArena)
return;
PromoteTypeLevels ptl{log, typeArena, minLevel};
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes};
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
if (tp->owningArena != typeArena)
return;
PromoteTypeLevels ptl{log, typeArena, minLevel};
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes};
ptl.traverse(tp);
}
@ -318,6 +333,16 @@ static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMat
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,
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
: types(types)
@ -375,7 +400,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
auto superFree = log.getMutable<FreeTypeVar>(superTy);
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))
log.replace(subTy, BoundTypeVar(superTy));
@ -386,7 +411,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
if (!occursCheck(superTy, subTy))
{
if (superFree->level.subsumes(subFree->level))
if (subsumes(useScopes, superFree, subFree))
{
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.
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
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))
{
promoteTypeLevels(log, types, superFree->level, subTy);
promoteTypeLevels(log, types, superFree->level, superFree->scope, useScopes, subTy);
Widen widen{types, singletonTypes};
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.
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
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))
{
promoteTypeLevels(log, types, subFree->level, superTy);
promoteTypeLevels(log, types, subFree->level, subFree->scope, useScopes, superTy);
log.replace(subTy, BoundTypeVar(superTy));
}
@ -855,6 +880,7 @@ struct WeirdIter
size_t index;
bool growing;
TypeLevel level;
Scope* scope = nullptr;
WeirdIter(TypePackId packId, TxnLog& log)
: packId(packId)
@ -915,6 +941,7 @@ struct WeirdIter
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
level = log.getMutable<Unifiable::Free>(packId)->level;
scope = log.getMutable<Unifiable::Free>(packId)->scope;
log.replace(packId, BoundTypePack(newTail));
packId = 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 subIter = WeirdIter(subTp, log);
auto mkFreshType = [this](TypeLevel level) {
return types->freshType(level);
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
return types->freshType(scope, level);
};
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)
{
subIter.pushType(mkFreshType(subIter.level));
subIter.pushType(mkFreshType(subIter.scope, subIter.level));
}
if (subIter.good() && superIter.growing)
{
superIter.pushType(mkFreshType(superIter.level));
superIter.pushType(mkFreshType(superIter.scope, superIter.level));
}
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.
size_t expectedSize = size(superTp);
size_t actualSize = size(subTp);
if (ctx == CountMismatch::Result)
if (ctx == CountMismatch::FunctionResult || ctx == CountMismatch::ExprListResult)
std::swap(expectedSize, actualSize);
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())
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
innerState.ctx = CountMismatch::Result;
innerState.ctx = CountMismatch::FunctionResult;
innerState.tryUnify_(subFunction->retTypes, superFunction->retTypes);
if (!reported)
@ -1295,7 +1322,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
ctx = CountMismatch::Arg;
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
ctx = CountMismatch::Result;
ctx = CountMismatch::FunctionResult;
tryUnify_(subFunction->retTypes, superFunction->retTypes);
}
@ -1693,8 +1720,45 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
{
case TableState::Free:
{
tryUnify_(subTy, superMetatable->table);
log.bindTable(subTy, superTy);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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;
}

View file

@ -270,7 +270,7 @@ int main(int argc, char** argv)
CliConfigResolver configResolver(mode);
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
Luau::registerBuiltinTypes(frontend.typeChecker);
Luau::registerBuiltinGlobals(frontend.typeChecker);
Luau::freeze(frontend.typeChecker.globalTypes);
#ifdef CALLGRIND

View file

@ -720,7 +720,7 @@ const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure
int i;
for (i = nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
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
int i;
for (i = nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
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));
setobjs2s(L, ra, base + b);
setobj2s(L, ra, base + b);
VM_PROTECT(luaC_checkGC(L));
return pc;
}
@ -2003,9 +2003,9 @@ const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Clo
else
{
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
setobjs2s(L, ra + 3 + 2, ra + 2);
setobjs2s(L, ra + 3 + 1, ra + 1);
setobjs2s(L, ra + 3, ra);
setobj2s(L, ra + 3 + 2, ra + 2);
setobj2s(L, ra + 3 + 1, ra + 1);
setobj2s(L, ra + 3, ra);
L->top = ra + 3 + 3; // func + 2 args (state and index)
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));
// 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
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
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;
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));
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++)
setnilvalue(ra + j);
return pc;
@ -2183,7 +2183,7 @@ const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc,
for (int i = 0; i < numparams; ++i)
{
setobjs2s(L, base + i, fixed + i);
setobj2s(L, base + i, fixed + i);
setnilvalue(fixed + i);
}

View file

@ -517,6 +517,10 @@ enum LuauBuiltinFunction
// bit32.extract(_, k, k)
LBF_BIT32_EXTRACTK,
// get/setmetatable
LBF_GETMETATABLE,
LBF_SETMETATABLE,
};
// Capture type, used in LOP_CAPTURE

View file

@ -4,6 +4,8 @@
#include "Luau/Bytecode.h"
#include "Luau/Compiler.h"
LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinMT, false)
namespace Luau
{
namespace Compile
@ -64,6 +66,14 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
if (builtin.isGlobal("select"))
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.method == "abs")

View file

@ -235,7 +235,7 @@ void lua_remove(lua_State* L, int idx)
StkId p = index2addr(L, idx);
api_checkvalidindex(L, p);
while (++p < L->top)
setobjs2s(L, p - 1, p);
setobj2s(L, p - 1, p);
L->top--;
return;
}
@ -246,8 +246,8 @@ void lua_insert(lua_State* L, int idx)
StkId p = index2addr(L, idx);
api_checkvalidindex(L, p);
for (StkId q = L->top; q > p; q--)
setobjs2s(L, q, q - 1);
setobjs2s(L, p, L->top);
setobj2s(L, q, q - 1);
setobj2s(L, p, L->top);
return;
}
@ -614,7 +614,7 @@ void lua_pushlstring(lua_State* L, const char* s, size_t len)
{
luaC_checkGC(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);
return;
}
@ -1269,7 +1269,7 @@ void lua_concat(lua_State* L, int n)
else if (n == 0)
{ // push empty string
luaC_threadbarrier(L);
setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
setsvalue(L, L->top, luaS_newlstr(L, "", 0));
api_incr_top(L);
}
// 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);
}
setsvalue2s(L, L->top + boxloc, newStorage);
setsvalue(L, L->top + boxloc, newStorage);
B->p = newStorage->data + (B->p - base);
B->end = newStorage->data + nextsize;
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 (B->p == B->end)
{
setsvalue2s(L, L->top - 1, luaS_buffinish(L, storage));
setsvalue(L, L->top - 1, luaS_buffinish(L, storage));
}
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

View file

@ -789,7 +789,7 @@ static int luauF_type(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
int tt = ttype(arg0);
TString* ttname = L->global->ttname[tt];
setsvalue2s(L, res, ttname);
setsvalue(L, res, ttname);
return 1;
}
@ -861,7 +861,7 @@ static int luauF_char(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
buffer[nparams] = 0;
setsvalue2s(L, res, luaS_newlstr(L, buffer, nparams));
setsvalue(L, res, luaS_newlstr(L, buffer, nparams));
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);
setsvalue2s(L, res, ttname);
setsvalue(L, res, ttname);
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))
{
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;
}
}
@ -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)))
return -1;
if (hvalue(arg0)->readonly)
Table* t = hvalue(arg0);
if (t->readonly)
return -1;
setobj2s(L, res, arg0);
setobj2t(L, luaH_set(L, hvalue(arg0), args), args + 1);
luaC_barriert(L, hvalue(arg0), args + 1);
setobj2t(L, luaH_set(L, t, args), args + 1);
luaC_barriert(L, t, args + 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 (hvalue(arg0)->readonly)
Table* t = hvalue(arg0);
if (t->readonly)
return -1;
int pos = luaH_getn(hvalue(arg0)) + 1;
setobj2t(L, luaH_setnum(L, hvalue(arg0), pos), args);
luaC_barriert(L, hvalue(arg0), args);
int pos = luaH_getn(t) + 1;
setobj2t(L, luaH_setnum(L, t, pos), args);
luaC_barriert(L, t, args);
return 0;
}
@ -1193,6 +1195,60 @@ static int luauF_extractk(lua_State* L, StkId res, TValue* arg0, int nresults, S
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] = {
NULL,
luauF_assert,
@ -1268,4 +1324,7 @@ luau_FastFunction luauF_table[256] = {
luauF_rawlen,
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);
const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL;
if (var)
setobjs2s(L, ci->base + var->reg, L->top - 1);
setobj2s(L, ci->base + var->reg, L->top - 1);
L->top--; // pop value
const char* name = var ? getstr(var->varname) : NULL;
return name;

View file

@ -263,18 +263,18 @@ static void seterrorobj(lua_State* L, int errcode, StkId oldtop)
{
case LUA_ERRMEM:
{
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen
setsvalue(L, oldtop, luaS_newliteral(L, LUA_MEMERRMSG)); // can not fail because string is pinned in luaopen
break;
}
case LUA_ERRERR:
{
setsvalue2s(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen
setsvalue(L, oldtop, luaS_newliteral(L, LUA_ERRERRMSG)); // can not fail because string is pinned in luaopen
break;
}
case LUA_ERRSYNTAX:
case LUA_ERRRUN:
{
setobjs2s(L, oldtop, L->top - 1); // error message on current top
setobj2s(L, oldtop, L->top - 1); // error message on current top
break;
}
}
@ -419,7 +419,7 @@ static void resume_handle(lua_State* L, void* ud)
static int resume_error(lua_State* L, const char* msg)
{
L->top = L->ci->base;
setsvalue2s(L, L->top, luaS_new(L, msg));
setsvalue(L, L->top, luaS_new(L, msg));
incr_top(L);
return LUA_ERRRUN;
}
@ -525,8 +525,8 @@ static void callerrfunc(lua_State* L, void* ud)
{
StkId errfunc = cast_to(StkId, ud);
setobjs2s(L, L->top, L->top - 1);
setobjs2s(L, L->top - 1, errfunc);
setobj2s(L, L->top, L->top - 1);
setobj2s(L, L->top - 1, errfunc);
incr_top(L);
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`.
*/
LUAU_FASTFLAGVARIABLE(LuauFasterSweep, false)
#define GC_SWEEPPAGESTEPCOST 16
#define GC_INTERRUPT(state) \
@ -836,28 +834,6 @@ static size_t atomic(lua_State* L)
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
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);
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);
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
int newwhite = luaC_white(g);
for (char* pos = start; pos != end; pos += blockSize)
// is the object alive?
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
{
GCObject* gco = (GCObject*)pos;
// skip memory blocks that are already freed
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;
}
LUAU_ASSERT(!isdead(g, gco));
// make it white (for next cycle)
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
}
}
else
{
for (char* pos = start; pos != end; pos += blockSize)
else
{
GCObject* gco = (GCObject*)pos;
LUAU_ASSERT(isdead(g, gco));
freeobj(L, gco, page);
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
// 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;
}
// 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)
{
// 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)
}
else
{
sweepgco(L, NULL, obj2gco(g->mainthread));
}
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
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];
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);
return svalue(L->top - 1);
}

View file

@ -200,20 +200,14 @@ typedef struct lua_TValue
** different types of sets, according to destination
*/
// from stack to (same) stack
#define setobjs2s setobj
// to stack (not from same stack)
// to stack
#define setobj2s setobj
#define setsvalue2s setsvalue
#define sethvalue2s sethvalue
#define setptvalue2s setptvalue
// from table to same table
// from table to same table (no barrier)
#define setobjt2t setobj
// to table
// to table (needs barrier)
#define setobj2t setobj
// to new object
// to new object (no barrier)
#define setobj2n setobj
#define setsvalue2n setsvalue
#define setttype(obj, tt) (ttype(obj) = (tt))

View file

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

View file

@ -36,6 +36,7 @@ typedef enum
TM_LE,
TM_CONCAT,
TM_TYPE,
TM_METATABLE,
TM_N // number of elements in the enum
} TMS;

View file

@ -985,7 +985,7 @@ reentry:
int i;
for (i = nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
setnilvalue(res++);
@ -1022,7 +1022,7 @@ reentry:
// note: in MULTRET context nresults starts as -1 so i != 0 condition never activates intentionally
int i;
for (i = nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
setnilvalue(res++);
@ -1945,7 +1945,7 @@ reentry:
StkId ra = VM_REG(LUAU_INSN_A(insn));
setobjs2s(L, ra, base + b);
setobj2s(L, ra, base + b);
VM_PROTECT(luaC_checkGC(L));
VM_NEXT();
}
@ -2281,9 +2281,9 @@ reentry:
else
{
// note: it's safe to push arguments past top for complicated reasons (see top of the file)
setobjs2s(L, ra + 3 + 2, ra + 2);
setobjs2s(L, ra + 3 + 1, ra + 1);
setobjs2s(L, ra + 3, ra);
setobj2s(L, ra + 3 + 2, ra + 2);
setobj2s(L, ra + 3 + 1, ra + 1);
setobj2s(L, ra + 3, ra);
L->top = ra + 3 + 3; // func + 2 args (state and index)
LUAU_ASSERT(L->top <= L->stack_last);
@ -2295,7 +2295,7 @@ reentry:
ra = VM_REG(LUAU_INSN_A(insn));
// 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
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
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;
VM_NEXT();
@ -2382,7 +2382,7 @@ reentry:
StkId ra = VM_REG(LUAU_INSN_A(insn));
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++)
setnilvalue(ra + j);
VM_NEXT();
@ -2461,7 +2461,7 @@ reentry:
for (int i = 0; i < numparams; ++i)
{
setobjs2s(L, base + i, fixed + i);
setobj2s(L, base + i, fixed + i);
setnilvalue(fixed + i);
}
@ -2878,7 +2878,7 @@ int luau_precall(lua_State* L, StkId func, int nresults)
int i;
for (i = nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
setnilvalue(res++);
@ -2906,7 +2906,7 @@ void luau_poscall(lua_State* L, StkId first)
int i;
for (i = ci->nresults; i != 0 && vali < valend; i--)
setobjs2s(L, res++, vali++);
setobj2s(L, res++, vali++);
while (i-- > 0)
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:
{
TString* v = readString(strings, data, size, offset);
setsvalue2n(L, &p->k[j], v);
setsvalue(L, &p->k[j], v);
break;
}

View file

@ -38,7 +38,7 @@ int luaV_tostring(lua_State* L, StkId obj)
double n = nvalue(obj);
char* e = luai_num2str(s, n);
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;
}
}
@ -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);
res = restorestack(L, result);
L->top--;
setobjs2s(L, res, L->top);
setobj2s(L, res, L->top);
return res;
}
@ -350,11 +350,11 @@ void luaV_concat(lua_State* L, int total, int last)
if (tl < LUA_BUFFERSIZE)
{
setsvalue2s(L, top - n, luaS_newlstr(L, buffer, tl));
setsvalue(L, top - n, luaS_newlstr(L, buffer, tl));
}
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
@ -582,7 +582,7 @@ LUAU_NOINLINE void luaV_tryfuncTM(lua_State* L, StkId func)
if (!ttisfunction(tm))
luaG_typeerror(L, func, "call");
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
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::InternalErrorReporter iceHandler;
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
static int once = (Luau::registerBuiltinTypes(sharedEnv), 1);
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
(void)once;
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
(void)once2;

View file

@ -96,7 +96,7 @@ int registerTypes(Luau::TypeChecker& env)
using namespace Luau;
using std::nullopt;
Luau::registerBuiltinTypes(env);
Luau::registerBuiltinGlobals(env);
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::InternalErrorReporter iceHandler;
static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler);
static int once = (Luau::registerBuiltinTypes(sharedEnv), 1);
static int once = (Luau::registerBuiltinGlobals(sharedEnv), 1);
(void)once;
static int once2 = (Luau::freeze(sharedEnv.globalTypes), 1);
(void)once2;

View file

@ -798,6 +798,8 @@ RETURN R0 1
TEST_CASE("TableSizePredictionSetMetatable")
{
ScopedFastFlag sff("LuauCompileBuiltinMT", true);
CHECK_EQ("\n" + compileFunction0(R"(
local t = setmetatable({}, nil)
t.field1 = 1
@ -805,14 +807,15 @@ t.field2 = 2
return t
)"),
R"(
GETIMPORT R0 1
NEWTABLE R1 2 0
LOADNIL R2
FASTCALL2K 61 R1 K0 L0
LOADK R2 K0
GETIMPORT R0 2
CALL R0 2 1
LOADN R1 1
SETTABLEKS R1 R0 K2
LOADN R1 2
L0: LOADN R1 1
SETTABLEKS R1 R0 K3
LOADN R1 2
SETTABLEKS R1 R0 K4
RETURN R0 1
)");
}

View file

@ -499,7 +499,7 @@ TEST_CASE("Types")
Luau::SingletonTypes singletonTypes;
Luau::TypeChecker env(&moduleResolver, Luau::NotNull{&singletonTypes}, &iceHandler);
Luau::registerBuiltinTypes(env);
Luau::registerBuiltinGlobals(env);
Luau::freeze(env.globalTypes);
lua_newtable(L);

View file

@ -8,7 +8,6 @@
#include "Luau/TypeVar.h"
#include "Luau/TypeAttach.h"
#include "Luau/Transpiler.h"
#include "Luau/BuiltinDefinitions.h"
#include "doctest.h"
@ -20,6 +19,8 @@
static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
namespace Luau
{
@ -97,6 +98,8 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete)
configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
configResolver.defaultConfig.parseOptions.captureComments = true;
registerBuiltinTypes(frontend);
Luau::freeze(frontend.typeChecker.globalTypes);
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
@ -435,9 +438,9 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
Luau::unfreeze(frontend.typeChecker.globalTypes);
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
registerBuiltinTypes(frontend);
registerBuiltinGlobals(frontend);
if (prepareAutocomplete)
registerBuiltinTypes(frontend.typeCheckerForAutocomplete);
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete);
registerTestTypes();
Luau::freeze(frontend.typeChecker.globalTypes);

View file

@ -1070,12 +1070,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
fileResolver.source["Module/A"] = R"(
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> = (
eventName: T,
args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]]
) -> unknown
) -> MyAny
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));
}
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();

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]);
REQUIRE(dtd);
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")
@ -868,4 +869,40 @@ TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok")
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();

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")
{
// Luau::resetPrintLine();
static std::vector<std::string> output;
output.clear();
Luau::setPrintLine([](const std::string& s) {
output.push_back(s);
});
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
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);
REQUIRE(1 == output.size());
}
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(LuauSpecialTypesAsterisked);
LUAU_FASTFLAG(LuauStringFormatArgumentErrorFix)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("BuiltinTests");
@ -596,6 +597,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall")
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")
{
CheckResult result = check(R"(
@ -679,10 +689,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("any", toString(requireType("foo")));
CHECK_EQ("any", toString(requireType("bar")));
CHECK_EQ("any", toString(requireType("baz")));
CHECK_EQ("any", toString(requireType("quux")));
if (FFlag::DebugLuauDeferredConstraintResolution && FFlag::LuauSpecialTypesAsterisked)
{
CHECK_EQ("string", toString(requireType("foo")));
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")
@ -698,10 +718,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_strin
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("any", toString(requireType("foo")));
CHECK_EQ("any", toString(requireType("bar")));
CHECK_EQ("any", toString(requireType("baz")));
CHECK_EQ("any", toString(requireType("quux")));
if (FFlag::DebugLuauDeferredConstraintResolution && FFlag::LuauSpecialTypesAsterisked)
{
CHECK_EQ("string", toString(requireType("foo")));
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")
@ -1099,7 +1129,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::Result);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
CHECK_EQ(acm->expected, 1);
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]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::Result);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
CHECK_EQ(acm->expected, 3);
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]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::Result);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
CHECK_EQ(acm->expected, 2);
CHECK_EQ(acm->actual, 3);
@ -1288,7 +1318,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::Result);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
CHECK_EQ(acm->expected, 2);
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")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
--!strict
@ -851,7 +853,49 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
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->actual, 2);
}
@ -1271,7 +1315,7 @@ local b: B = a
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)'
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")

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")
{
CheckResult result = check(R"(
@ -584,11 +594,48 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict")
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"(
local t = {}
setmetatable(t, { __iter = function(o) return next, o.children end })
local t = setmetatable({}, { __iter = function(o) return next, nil 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
end
)");
@ -596,4 +643,26 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_iter_metamethod")
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();

View file

@ -439,13 +439,15 @@ TEST_CASE_FIXTURE(Fixture, "normalization_fails_on_certain_kinds_of_cyclic_table
// Belongs in TypeInfer.builtins.test.cpp.
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
local function f(): () end
local ok, res = pcall(f)
)");
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);
// CHECK_EQ("boolean", toString(requireType("ok")));
// 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")));
}
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")
{
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")));
}
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();

View file

@ -271,4 +271,40 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_pack_owner")
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();

View file

@ -24,6 +24,9 @@ assert(getmetatable(nil) == nil)
a={}; setmetatable(a, {__metatable = "xuxu",
__tostring=function(x) return x.name end})
assert(getmetatable(a) == "xuxu")
ud=newproxy(true); getmetatable(ud).__metatable = "xuxu"
assert(getmetatable(ud) == "xuxu")
local res,err = pcall(tostring, a)
assert(not res and err == "'__tostring' must return a string")
-- cannot change a protected metatable

View file

@ -1,101 +1,47 @@
AnnotationTests.builtin_types_are_not_exported
AnnotationTests.corecursive_types_error_on_tight_loop
AnnotationTests.duplicate_type_param_name
AnnotationTests.for_loop_counter_annotation_is_checked
AnnotationTests.generic_aliases_are_cloned_properly
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_union_typevar
AnnotationTests.too_many_type_params
AnnotationTests.two_type_params
AnnotationTests.use_type_required_from_another_file
AstQuery.last_argument_function_call_type
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_for_in_middle_keywords
AutocompleteTest.autocomplete_for_middle_keywords
AutocompleteTest.autocomplete_if_middle_keywords
AutocompleteTest.autocomplete_interpolated_string
AutocompleteTest.autocomplete_on_string_singletons
AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_repeat_middle_keyword
AutocompleteTest.autocomplete_string_singleton_equality
AutocompleteTest.autocomplete_string_singleton_escape
AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.autocomplete_while_middle_keywords
AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic
AutocompleteTest.bias_toward_inner_scope
AutocompleteTest.cyclic_table
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.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.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_2
AutocompleteTest.no_incompatible_self_calls_on_class
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
AutocompleteTest.recursive_function
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.suggest_table_keys
AutocompleteTest.type_correct_argument_type_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_self
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
AutocompleteTest.type_correct_expected_return_type_suggestion
AutocompleteTest.type_correct_full_type_suggestion
AutocompleteTest.type_correct_function_no_parenthesis
AutocompleteTest.type_correct_function_return_types
AutocompleteTest.type_correct_function_type_suggestion
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_in_argument
AutocompleteTest.type_correct_suggestion_in_table
AutocompleteTest.unsealed_table
AutocompleteTest.unsealed_table_2
AutocompleteTest.user_defined_local_functions_in_own_definition
BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types
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_pack
BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.class_definition_overload_metamethods
DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
FrontendTest.ast_node_at_position
FrontendTest.automatically_check_dependent_scripts
FrontendTest.environments
FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error
FrontendTest.recheck_if_dependent_script_is_dirty
FrontendTest.reexport_cyclic_type
FrontendTest.report_syntax_error_in_required_file
FrontendTest.reexport_type_alias
FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2
@ -212,13 +157,16 @@ IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect
isSubtype.intersection_of_tables
isSubtype.table_with_table_prop
ModuleTests.any_persistance_does_not_leak
ModuleTests.clone_self_property
ModuleTests.deepClone_cyclic_table
ModuleTests.do_not_clone_reexports
NonstrictModeTests.for_in_iterator_variables_are_any
NonstrictModeTests.function_parameters_are_any
NonstrictModeTests.inconsistent_module_return_types_are_ok
NonstrictModeTests.inconsistent_return_types_are_ok
NonstrictModeTests.infer_nullary_function
NonstrictModeTests.infer_the_maximum_number_of_values_the_function_could_return
NonstrictModeTests.inline_table_props_are_also_any
NonstrictModeTests.local_tables_are_not_any
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_table
RefinementTest.typeguard_not_to_be_string
RefinementTest.typeguard_only_look_up_types_from_global_scope
RefinementTest.what_nonsensical_condition
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part
@ -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.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back
TableTests.dont_leak_free_table_props
TableTests.dont_quantify_table_that_belongs_to_outer_scope
TableTests.dont_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_multiple_like_keys
TableTests.function_calls_produces_sealed_table_given_unsealed_table
TableTests.generalize_table_argument
TableTests.getmetatable_returns_pointer_to_metatable
TableTests.give_up_after_one_metatable_index_look_up
TableTests.hide_table_error_properties
TableTests.indexer_fn
TableTests.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexer_table
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
TableTests.inequality_operators_imply_exactly_matching_types
TableTests.infer_array_2
@ -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_2
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
TableTests.persistent_sealed_table_is_immutable
TableTests.prop_access_on_key_whose_types_mismatches
TableTests.property_lookup_through_tabletypevar_metatable
TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
TableTests.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.toStringDetailed2
ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack
ToString.toStringNamedFunction_hide_self_param
ToString.toStringNamedFunction_hide_type_params
ToString.toStringNamedFunction_id
ToString.toStringNamedFunction_include_self_param
ToString.toStringNamedFunction_map
ToString.toStringNamedFunction_variadics
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_swapsies_not_ok
TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases
TypeAliases.stringify_optional_parameterized_alias
TypeAliases.stringify_type_alias_of_recursive_template_table_type
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_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeAliases.type_alias_of_an_imported_recursive_type
TypeInfer.checking_should_not_ice
TypeInfer.dont_report_type_errors_within_an_AstExprError
TypeInfer.dont_report_type_errors_within_an_AstStatError
TypeInfer.globals
TypeInfer.globals2
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.tc_after_error_recovery_no_replacement_name_in_error
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.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.calling_function_with_anytypepack_doesnt_leak_free_types
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_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_does_not_return_enough_values
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
TypeInferFunctions.ignored_return_values
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
TypeInferFunctions.improved_function_arg_mismatch_errors
TypeInferFunctions.inconsistent_higher_order_function
TypeInferFunctions.inconsistent_return_types
TypeInferFunctions.infer_anonymous_function_arguments
TypeInferFunctions.infer_return_type_from_selected_overload
TypeInferFunctions.infer_return_value_type
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_only_alternative_overloads_that_match_argument_count
TypeInferFunctions.no_lossy_function_type
TypeInferFunctions.occurs_check_failure_in_function_return_type
TypeInferFunctions.quantify_constrained_types
TypeInferFunctions.record_matching_overload
TypeInferFunctions.report_exiting_without_return_nonstrict
@ -521,7 +479,13 @@ TypeInferFunctions.too_few_arguments_variadic_generic
TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_return_values
TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function
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.loop_iter_no_indexer_nonstrict
TypeInferLoops.loop_iter_trailing_nil
@ -529,6 +493,9 @@ TypeInferLoops.loop_typecheck_crash_on_empty_optional
TypeInferLoops.unreachable_code_after_infinite_loop
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
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.module_type_conflict
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_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.methods_are_topologically_sorted
TypeInferOperators.and_adds_boolean
TypeInferOperators.and_adds_boolean_no_superfluous_union
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.or_joins_types
TypeInferOperators.or_joins_types_with_no_extras
TypeInferOperators.primitive_arith_no_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.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_sorta_never
TypeInferUnknownNever.math_operators_and_never
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
TypeInferUnknownNever.unary_minus_of_never
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 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.
def templateParams(s):
depth = 0
start = s.find('<') + 1
start = s.find("<") + 1
result = []
for i, c in enumerate(s[start:], start):
if c == '<':
if c == "<":
depth += 1
elif c == '>':
elif c == ">":
if depth == 0:
result.append(s[start: i].strip())
result.append(s[start:i].strip())
break
depth -= 1
elif c == ',' and depth == 0:
result.append(s[start: i].strip())
elif c == "," and depth == 0:
result.append(s[start:i].strip())
start = i + 1
return result
def getType(target, typeName):
stars = 0
typeName = typeName.strip()
while typeName.endswith('*'):
while typeName.endswith("*"):
stars += 1
typeName = typeName[:-1]
if typeName.startswith('const '):
if typeName.startswith("const "):
typeName = typeName[6:]
ty = target.FindFirstType(typeName.strip())
@ -36,13 +37,10 @@ def getType(target, typeName):
return ty
def luau_variant_summary(valobj, internal_dict, options):
type_id = valobj.GetChildMemberWithName("typeId").GetValueAsUnsigned()
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() + "]"
return valobj.GetChildMemberWithName("type").GetSummary()[1:-1]
class LuauVariantSyntheticChildrenProvider:
node_names = ["type", "value"]
@ -74,26 +72,42 @@ class LuauVariantSyntheticChildrenProvider:
if node == "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:
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<unknown type>\"")
return self.valobj.CreateValueFromExpression(
node, '(const char*)"<unknown type>"'
)
elif node == "value":
if self.stored_value 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:
return self.valobj.CreateValueExpression(node, "(const char*)\"<unknown type>\"")
return self.valobj.CreateValueExpression(
node, '(const char*)"<unknown type>"'
)
else:
return self.valobj.CreateValueFromExpression(node, "(const char*)\"<no stored value>\"")
return self.valobj.CreateValueFromExpression(
node, '(const char*)"<no stored value>"'
)
else:
return None
def update(self):
self.type_index = self.valobj.GetChildMemberWithName("typeId").GetValueAsSigned()
self.type_params = templateParams(self.valobj.GetType().GetCanonicalType().GetName())
self.type_index = self.valobj.GetChildMemberWithName(
"typeId"
).GetValueAsSigned()
self.type_params = templateParams(
self.valobj.GetType().GetCanonicalType().GetName()
)
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:
storage = self.valobj.GetChildMemberWithName("storage")
@ -105,3 +119,97 @@ class LuauVariantSyntheticChildrenProvider:
self.stored_value = None
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
# The result of analysis is a .svg file which can be viewed in a browser
import sys
import svg
import argparse
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":
if self.currentTest:
passed = 0 == safeParseInt(attrs["failures"])
passed = attrs["test_case_success"] == "true"
dottedName = ".".join(self.currentTest)