mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
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:
parent
1acd66c97d
commit
944e8375aa
77 changed files with 1660 additions and 702 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -17,12 +17,9 @@ constexpr const char* kConfigName = ".luaurc";
|
|||
|
||||
struct Config
|
||||
{
|
||||
Config()
|
||||
{
|
||||
enabledLint.setDefaults();
|
||||
}
|
||||
Config();
|
||||
|
||||
Mode mode = Mode::NoCheck;
|
||||
Mode mode;
|
||||
|
||||
ParseOptions parseOptions;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -30,6 +30,7 @@ struct TypeArena
|
|||
|
||||
TypeId freshType(TypeLevel level);
|
||||
TypeId freshType(Scope* scope);
|
||||
TypeId freshType(Scope* scope, TypeLevel level);
|
||||
|
||||
TypePackId freshTypePack(Scope* scope);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -85,6 +85,7 @@ struct Free
|
|||
{
|
||||
explicit Free(TypeLevel level);
|
||||
explicit Free(Scope* scope);
|
||||
explicit Free(Scope* scope, TypeLevel level);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 + "' " +
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 != ¤tModule->internalTypes)
|
||||
{
|
||||
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
|
||||
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
|
||||
return itp == tp.ty;
|
||||
});
|
||||
bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(), binding->typePackParams.begin(),
|
||||
binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
|
||||
return itpp == tpp.tp;
|
||||
});
|
||||
|
||||
// Copy can be skipped if this is an identical alias
|
||||
if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
|
||||
{
|
||||
// This is a shallow clone, original recursive links to self are not updated
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
clone.name = name;
|
||||
|
||||
for (auto param : binding->typeParams)
|
||||
clone.instantiatedTypeParams.push_back(param.ty);
|
||||
|
||||
for (auto param : binding->typePackParams)
|
||||
clone.instantiatedTypePackParams.push_back(param.tp);
|
||||
|
||||
bool isNormal = ty->normal;
|
||||
ty = addType(std::move(clone));
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
asMutable(ty)->normal = isNormal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ttv->name = name;
|
||||
|
||||
ttv->instantiatedTypeParams.clear();
|
||||
for (auto param : binding->typeParams)
|
||||
ttv->instantiatedTypeParams.push_back(param.ty);
|
||||
|
||||
ttv->instantiatedTypePackParams.clear();
|
||||
for (auto param : binding->typePackParams)
|
||||
ttv->instantiatedTypePackParams.push_back(param.tp);
|
||||
}
|
||||
}
|
||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||
{
|
||||
// We can't modify types that come from other modules
|
||||
if (follow(ty)->owningArena == ¤tModule->internalTypes)
|
||||
mtv->syntheticName = name;
|
||||
}
|
||||
|
||||
TypeId& bindingType = bindingsMap[name].type;
|
||||
|
||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||
bindingType = ty;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
|
||||
bindingType = t;
|
||||
if (!ok)
|
||||
reportError(typealias.location, NormalizationTooComplex{});
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel)
|
||||
{
|
||||
Name name = typealias.name.value;
|
||||
|
||||
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
||||
if (name == kParseNameError)
|
||||
return;
|
||||
|
||||
std::optional<TypeFun> binding;
|
||||
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
|
||||
binding = it->second;
|
||||
else if (auto it = scope->privateTypeBindings.find(name); it != scope->privateTypeBindings.end())
|
||||
binding = it->second;
|
||||
|
||||
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||
|
||||
if (binding)
|
||||
{
|
||||
Location location = scope->typeAliasLocations[name];
|
||||
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
|
||||
|
||||
if (!FFlag::LuauReportShadowedTypeAlias)
|
||||
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
|
||||
|
||||
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 != ¤tModule->internalTypes)
|
||||
{
|
||||
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
|
||||
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
|
||||
return itp == tp.ty;
|
||||
});
|
||||
bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(),
|
||||
binding->typePackParams.begin(), binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
|
||||
return itpp == tpp.tp;
|
||||
});
|
||||
|
||||
// Copy can be skipped if this is an identical alias
|
||||
if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
|
||||
{
|
||||
// This is a shallow clone, original recursive links to self are not updated
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
clone.name = name;
|
||||
|
||||
for (auto param : binding->typeParams)
|
||||
clone.instantiatedTypeParams.push_back(param.ty);
|
||||
|
||||
for (auto param : binding->typePackParams)
|
||||
clone.instantiatedTypePackParams.push_back(param.tp);
|
||||
|
||||
bool isNormal = ty->normal;
|
||||
ty = addType(std::move(clone));
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
asMutable(ty)->normal = isNormal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ttv->name = name;
|
||||
|
||||
ttv->instantiatedTypeParams.clear();
|
||||
for (auto param : binding->typeParams)
|
||||
ttv->instantiatedTypeParams.push_back(param.ty);
|
||||
|
||||
ttv->instantiatedTypePackParams.clear();
|
||||
for (auto param : binding->typePackParams)
|
||||
ttv->instantiatedTypePackParams.push_back(param.tp);
|
||||
}
|
||||
}
|
||||
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
|
||||
{
|
||||
// We can't modify types that come from other modules
|
||||
if (follow(ty)->owningArena == ¤tModule->internalTypes)
|
||||
mtv->syntheticName = name;
|
||||
}
|
||||
|
||||
TypeId& bindingType = bindingsMap[name].type;
|
||||
|
||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||
bindingType = ty;
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
|
||||
bindingType = t;
|
||||
if (!ok)
|
||||
reportError(typealias.location, NormalizationTooComplex{});
|
||||
}
|
||||
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, ¤tModule->internalTypes, level, retPack);
|
||||
promoteTypeLevels(log, ¤tModule->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, ¤tModule->internalTypes, scope->level};
|
||||
Instantiation instantiation{log, ¤tModule->internalTypes, scope->level, /*scope*/ nullptr};
|
||||
|
||||
if (FFlag::LuauAutocompleteDynamicLimits && instantiationChildLimit)
|
||||
instantiation.childLimit = *instantiationChildLimit;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
103
VM/src/lgc.cpp
103
VM/src/lgc.cpp
|
@ -118,8 +118,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterThreadMark, false)
|
|||
* slot - upvalues like this are identified since they don't have `markedopen` bit set during thread traversal and closed in `clearupvals`.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ const char* const luaT_eventname[] = {
|
|||
"__le",
|
||||
"__concat",
|
||||
"__type",
|
||||
"__metatable",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ typedef enum
|
|||
TM_LE,
|
||||
TM_CONCAT,
|
||||
TM_TYPE,
|
||||
TM_METATABLE,
|
||||
|
||||
TM_N // number of elements in the enum
|
||||
} TMS;
|
||||
|
|
|
@ -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++);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {}
|
||||
)";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")));
|
||||
|
|
|
@ -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"(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
65
tools/perfstat.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/python
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# Given a profile dump, this tool displays top functions based on the stacks listed in the profile
|
||||
|
||||
import argparse
|
||||
|
||||
class Node:
|
||||
def __init__(self):
|
||||
self.function = ""
|
||||
self.source = ""
|
||||
self.line = 0
|
||||
self.hier_ticks = 0
|
||||
self.self_ticks = 0
|
||||
|
||||
def title(self):
|
||||
if self.line > 0:
|
||||
return "{} ({}:{})".format(self.function, self.source, self.line)
|
||||
else:
|
||||
return self.function
|
||||
|
||||
argumentParser = argparse.ArgumentParser(description='Display summary statistics from Luau sampling profiler dumps')
|
||||
argumentParser.add_argument('source_file', type=open)
|
||||
argumentParser.add_argument('--limit', dest='limit', type=int, default=10, help='Display top N functions')
|
||||
|
||||
arguments = argumentParser.parse_args()
|
||||
|
||||
dump = arguments.source_file.readlines()
|
||||
|
||||
stats = {}
|
||||
total = 0
|
||||
total_gc = 0
|
||||
|
||||
for l in dump:
|
||||
ticks, stack = l.strip().split(" ", 1)
|
||||
hier = {}
|
||||
|
||||
for f in reversed(stack.split(";")):
|
||||
source, function, line = f.split(",")
|
||||
node = stats.setdefault(f, Node())
|
||||
|
||||
node.function = function
|
||||
node.source = source
|
||||
node.line = int(line) if len(line) > 0 else 0
|
||||
|
||||
if not node in hier:
|
||||
node.hier_ticks += int(ticks)
|
||||
hier[node] = True
|
||||
|
||||
total += int(ticks)
|
||||
node.self_ticks += int(ticks)
|
||||
|
||||
if node.source == "GC":
|
||||
total_gc += int(ticks)
|
||||
|
||||
if total > 0:
|
||||
print(f"Runtime: {total:,} usec ({100.0 * total_gc / total:.2f}% GC)")
|
||||
print()
|
||||
print("Top functions (self time):")
|
||||
for n in sorted(stats.values(), key=lambda node: node.self_ticks, reverse=True)[:arguments.limit]:
|
||||
print(f"{n.self_ticks:12,} usec ({100.0 * n.self_ticks / total:.2f}%): {n.title()}")
|
||||
print()
|
||||
print("Top functions (total time):")
|
||||
for n in sorted(stats.values(), key=lambda node: node.hier_ticks, reverse=True)[:arguments.limit]:
|
||||
print(f"{n.hier_ticks:12,} usec ({100.0 * n.hier_ticks / total:.2f}%): {n.title()}")
|
|
@ -39,7 +39,7 @@ class Handler(x.ContentHandler):
|
|||
|
||||
elif name == "OverallResultsAsserts":
|
||||
if self.currentTest:
|
||||
passed = 0 == safeParseInt(attrs["failures"])
|
||||
passed = attrs["test_case_success"] == "true"
|
||||
|
||||
dottedName = ".".join(self.currentTest)
|
||||
|
||||
|
|
Loading…
Reference in a new issue