diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index 0292dff..616367b 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -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&& types); TypeId makeIntersection(TypeArena& arena, std::vector&& types); diff --git a/Analysis/include/Luau/Config.h b/Analysis/include/Luau/Config.h index 56cdfe7..8ba4ffa 100644 --- a/Analysis/include/Luau/Config.h +++ b/Analysis/include/Luau/Config.h @@ -17,12 +17,9 @@ constexpr const char* kConfigName = ".luaurc"; struct Config { - Config() - { - enabledLint.setDefaults(); - } + Config(); - Mode mode = Mode::NoCheck; + Mode mode; ParseOptions parseOptions; diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 3ffb3fb..0e19f13 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -94,8 +94,9 @@ struct FunctionCallConstraint { std::vector> innerConstraints; TypeId fn; + TypePackId argsPack; TypePackId result; - class AstExprCall* astFragment; + class AstExprCall* callSite; }; // result ~ prim ExpectedType SomeSingletonType MultitonType diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index e7d8ad4..973c0a8 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -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 exprs, const std::vector& expectedTypes = {}); TypePackId checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector& expectedTypes = {}); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index abea51b..06f53e4 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -104,6 +104,7 @@ struct ConstraintSolver bool tryDispatch(const HasPropConstraint& c, NotNull constraint); // for a, ... in some_table do + // also handles __iter metamethod bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force); // for a, ... in next_function, t, ... do diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index eab6a21..f373586 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -81,7 +81,7 @@ struct OnlyTablesCanHaveMethods struct DuplicateTypeDefinition { Name name; - Location previousLocation; + std::optional previousLocation; bool operator==(const DuplicateTypeDefinition& rhs) const; }; @@ -91,7 +91,8 @@ struct CountMismatch enum Context { Arg, - Result, + FunctionResult, + ExprListResult, Return, }; size_t expected; diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 04c598d..5df6f4b 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -157,7 +157,8 @@ struct Frontend ScopePtr getGlobalScope(); private: - ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector requireCycles); + ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector requireCycles, + bool forAutocomplete = false); std::pair 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 environments; std::unordered_map> builtinDefinitions; - ScopePtr globalScope; + SingletonTypes singletonTypes_; public: - SingletonTypes singletonTypes_; const NotNull singletonTypes; FileResolver* fileResolver; @@ -186,13 +186,15 @@ public: FrontendOptions options; InternalErrorReporter iceHandler; TypeArena globalTypes; - TypeArena arenaForAutocomplete; std::unordered_map sourceNodes; std::unordered_map sourceModules; std::unordered_map requireTrace; Stats stats = {}; + +private: + ScopePtr globalScope; }; } // namespace Luau diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index e05ceeb..cd88d33 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -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& generics, const std::vector& genericPacks) + ReplaceGenerics(const TxnLog* log, TypeArena* arena, TypeLevel level, Scope* scope, const std::vector& generics, + const std::vector& genericPacks) : Substitution(log, arena) , level(level) + , scope(scope) , generics(generics) , genericPacks(genericPacks) { } TypeLevel level; + Scope* scope; std::vector generics; std::vector 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; diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index b7569d8..b2da7bc 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -49,9 +49,11 @@ struct Scope std::unordered_map exportedTypeBindings; std::unordered_map privateTypeBindings; std::unordered_map typeAliasLocations; - std::unordered_map> importedTypeBindings; + DenseHashSet builtinTypeNames{""}; + void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun); + std::optional lookup(Symbol sym); std::optional lookupType(const Name& name); @@ -61,7 +63,7 @@ struct Scope std::optional lookupPack(const Name& name); // WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2) - std::optional linearSearchForBinding(const std::string& name, bool traverseScopeChain = true); + std::optional linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const; RefinementMap refinements; @@ -73,4 +75,13 @@ struct Scope std::unordered_map 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 diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index 016cc92..3c3122c 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -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); + + // 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); + // Queues a replacement of a table type with another table type with a new // indexer. // diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index 1e029ae..c67f643 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -30,6 +30,7 @@ struct TypeArena TypeId freshType(TypeLevel level); TypeId freshType(Scope* scope); + TypeId freshType(Scope* scope, TypeLevel level); TypePackId freshTypePack(Scope* scope); diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index bbb9bd6..e5675eb 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -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& sorted); @@ -392,8 +394,12 @@ private: std::vector> 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 diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index efc1d88..e5a205b 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -25,4 +25,8 @@ std::optional getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro // Returns the minimum and maximum number of types the argument list can accept. std::pair> 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 flatten(TypeArena& arena, NotNull singletonTypes, TypePackId pack, size_t length); + } // namespace Luau diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index 2847d0b..1d587ff 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -27,6 +27,7 @@ namespace Luau struct TypeArena; struct Scope; +using ScopePtr = std::shared_ptr; /** * There are three kinds of type variables: @@ -264,7 +265,15 @@ struct WithPredicate using MagicFunction = std::function>( struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate)>; -using DcrMagicFunction = std::function, TypePackId, const class AstExprCall*)>; +struct MagicFunctionCallContext +{ + NotNull solver; + const class AstExprCall* callSite; + TypePackId arguments; + TypePackId result; +}; + +using DcrMagicFunction = std::function; struct FunctionTypeVar { @@ -277,10 +286,14 @@ struct FunctionTypeVar // Local monomorphic function FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); + FunctionTypeVar( + TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); // Local polymorphic function FunctionTypeVar(TypeLevel level, std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); + FunctionTypeVar(TypeLevel level, Scope* scope, std::vector generics, std::vector genericPacks, TypePackId argTypes, + TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); TypeLevel level; Scope* scope = nullptr; @@ -345,8 +358,9 @@ struct TableTypeVar using Props = std::map; TableTypeVar() = default; - explicit TableTypeVar(TableState state, TypeLevel level); + explicit TableTypeVar(TableState state, TypeLevel level, Scope* scope = nullptr); TableTypeVar(const Props& props, const std::optional& indexer, TypeLevel level, TableState state); + TableTypeVar(const Props& props, const std::optional& indexer, TypeLevel level, Scope* scope, TableState state); Props props; std::optional indexer; diff --git a/Analysis/include/Luau/Unifiable.h b/Analysis/include/Luau/Unifiable.h index e5eb419..0ea175c 100644 --- a/Analysis/include/Luau/Unifiable.h +++ b/Analysis/include/Luau/Unifiable.h @@ -85,6 +85,7 @@ struct Free { explicit Free(TypeLevel level); explicit Free(Scope* scope); + explicit Free(Scope* scope, TypeLevel level); int index; TypeLevel level; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 4d46869..26a922f 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -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 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 diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 2fc145d..1f594fe 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -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, TypeArena* typeArena, const std::vector& ancestry, Position position, AutocompleteEntryMap& result) { LUAU_ASSERT(!ancestry.empty()); - NotNull singletonTypes = typeChecker.singletonTypes; - AstNode* node = ancestry.rbegin()[0]; if (node->is()) @@ -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, TypeArena* typeArena, const std::vector& 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 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, + Scope* globalScope, Position position, StringCompletionCallback callback) { if (isWithinComment(sourceModule, position)) return {}; - NotNull singletonTypes = typeChecker.singletonTypes; + TypeArena typeArena; std::vector 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()) { @@ -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(); statIf && node->is()) { if (statIf->condition->is()) - 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(); statRepeat && statRepeat->condition->is()) - return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position); + return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position); else if (AstStatRepeat* statRepeat = extractStat(ancestry); statRepeat) return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement}; else if (AstExprTable* exprTable = parent->as(); exprTable && (node->is() || node->is())) @@ -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()) { 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()) { @@ -1604,12 +1601,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M } if (node->is()) - { 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 = frontend.singletonTypes; + Scope* globalScope = frontend.typeCheckerForAutocomplete.globalScope.get(); - frontend.arenaForAutocomplete.clear(); + AutocompleteResult autocompleteResult = autocomplete(*sourceModule, module, singletonTypes, globalScope, position, callback); return autocompleteResult; } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 8f4863d..dbe27bf 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -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 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> magicFunctionPack( static std::optional> magicFunctionRequire( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate); -static bool dcrMagicFunctionRequire(NotNull solver, TypePackId result, const AstExprCall* expr); + +static bool dcrMagicFunctionSelect(MagicFunctionCallContext context); +static bool dcrMagicFunctionRequire(MagicFunctionCallContext context); TypeId makeUnion(TypeArena& arena, std::vector&& 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(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 = 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(t: Table) -> ((Table, K?) -> (K, V), Table, 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(getGlobalBinding(frontend, "table"))) { @@ -408,7 +432,6 @@ void registerBuiltinTypes(Frontend& frontend) attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire); } - static std::optional> magicFunctionSelect( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { @@ -450,6 +473,50 @@ static std::optional> 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()) + { + const auto& [v, tail] = flatten(context.arguments); + + int offset = int(num->value); + if (offset > 0) + { + if (size_t(offset) < v.size()) + { + std::vector res(v.begin() + offset, v.end()); + TypePackId resTypePack = context.solver->arena->addTypePack({std::move(res), tail}); + asMutable(context.result)->ty.emplace(resTypePack); + } + else if (tail) + asMutable(context.result)->ty.emplace(*tail); + + return true; + } + + return false; + } + + if (AstExprConstantString* str = arg1->as()) + { + if (str->value.size == 1 && str->value.data[0] == '#') { + TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->singletonTypes->numberType}); + asMutable(context.result)->ty.emplace(numberTypePack); + return true; + } + } + + return false; +} + static std::optional> magicFunctionSetMetaTable( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { @@ -675,22 +742,22 @@ static bool checkRequirePathDcr(NotNull solver, AstExpr* expr) return good; } -static bool dcrMagicFunctionRequire(NotNull 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(moduleResult); + TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location); + TypePackId moduleResult = context.solver->arena->addTypePack({moduleType}); + asMutable(context.result)->ty.emplace(moduleResult); return true; } diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 7048d20..fd3a089 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -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(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(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(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; diff --git a/Analysis/src/Config.cpp b/Analysis/src/Config.cpp index 35a2259..00ca7b1 100644 --- a/Analysis/src/Config.cpp +++ b/Analysis/src/Config.cpp @@ -4,15 +4,18 @@ #include "Luau/Lexer.h" #include "Luau/StringUtils.h" -namespace +LUAU_FASTFLAGVARIABLE(LuauEnableNonstrictByDefaultForLuauConfig, false) + +namespace Luau { using Error = std::optional; -} - -namespace Luau +Config::Config() + : mode(FFlag::LuauEnableNonstrictByDefaultForLuauConfig ? Mode::Nonstrict : Mode::NoCheck) { + enabledLint.setDefaults(); +} static Error parseBoolean(bool& result, const std::string& value) { diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index aa1e954..169f464 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -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()) visit(scope, s); + else if (auto s = stat->as()) + 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 prospectiveTable{getMutable(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(classTy); - TypeId metaTy = arena->addType(TableTypeVar{TableState::Sealed, scope->level}); + TypeId metaTy = arena->addType(TableTypeVar{TableState::Sealed, scope->level, scope.get()}); TableTypeVar* metatable = getMutable(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(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 exprs, const std::vector& expectedTypes) { std::vector head; @@ -745,7 +758,9 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray expectedTailTypes{begin(expectedTypes) + i, end(expectedTypes)}; + std::vector 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()) { // 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 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(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()) { + 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 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()) { @@ -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. diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index b2bf773..35b8387 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -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, NotNullscope); + return true; } @@ -465,7 +468,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNullscope); std::optional instantiated = inst.substitute(c.superType); LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS @@ -909,7 +912,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulldcrMagicFunction != 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 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 instantiatedIterFn = instantiation.substitute(*iterFn)) + { + if (auto iterFtv = get(*instantiatedIterFn)) + { + TypePackId expectedIterArgs = arena->addTypePack({iteratorTy}); + unify(iterFtv->argTypes, expectedIterArgs, constraint->scope); + + std::vector 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 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(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 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, NotNullbegin.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 + "' " + diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 01e82ba..1890e08 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -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 requireCycles) +ModulePtr Frontend::check( + const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector requireCycles, bool forAutocomplete) { ModulePtr result = std::make_shared(); @@ -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 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); diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 2d1d62f..3d0cd0d 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -44,7 +44,7 @@ TypeId Instantiation::clean(TypeId ty) const FunctionTypeVar* ftv = log->getMutable(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(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) diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 7e6ff2f..e4c069b 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -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 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(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(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(*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(); diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index c129b97..9a7d360 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -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 Scope::lookupType(const Name& name) { const Scope* scope = this; @@ -82,9 +88,9 @@ std::optional Scope::lookupPack(const Name& name) } } -std::optional Scope::linearSearchForBinding(const std::string& name, bool traverseScopeChain) +std::optional Scope::linearSearchForBinding(const std::string& name, bool traverseScopeChain) const { - Scope* scope = this; + const Scope* scope = this; while (scope) { @@ -122,4 +128,22 @@ std::optional 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 diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 8ab124e..a61eb79 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -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; } diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 74d7730..06bde19 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -289,6 +289,45 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel) return newTp; } +PendingType* TxnLog::changeScope(TypeId ty, NotNull newScope) +{ + LUAU_ASSERT(get(ty) || get(ty) || get(ty) || get(ty)); + + PendingType* newTy = queue(ty); + if (FreeTypeVar* ftv = Luau::getMutable(newTy)) + { + ftv->scope = newScope; + } + else if (TableTypeVar* ttv = Luau::getMutable(newTy)) + { + LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic); + ttv->scope = newScope; + } + else if (FunctionTypeVar* ftv = Luau::getMutable(newTy)) + { + ftv->scope = newScope; + } + else if (ConstrainedTypeVar* ctv = Luau::getMutable(newTy)) + { + ctv->scope = newScope; + } + + return newTy; +} + +PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull newScope) +{ + LUAU_ASSERT(get(tp)); + + PendingTypePack* newTp = queue(tp); + if (FreeTypePack* ftp = Luau::getMutable(newTp)) + { + ftp->scope = newScope; + } + + return newTp; +} + PendingType* TxnLog::changeIndexer(TypeId ty, std::optional indexer) { LUAU_ASSERT(get(ty)); diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index abf31ae..666ab86 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -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}); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index ea06882..f98a212 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -17,10 +17,16 @@ #include 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(); 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 flatten(TypeArena& arena, TypePackId pack, size_t length) - { - std::vector 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(tail)) - LUAU_ASSERT(0); - else if (auto vtp = get(tail)) - { - while (result.size() < length) - result.push_back(vtp->ty); - } - else if (get(tail) || get(tail)) - { - while (result.size() < length) - result.push_back(arena.addType(FreeTypeVar{nullptr})); - } - else if (auto etp = get(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 iteratorTypes = flatten(arena, iteratorPack, 3); + const std::vector 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 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 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 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(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 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 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(iteratorTy)) { @@ -519,6 +508,62 @@ struct TypeChecker2 { // nothing } + else if (std::optional iterMmTy = findMetatableEntry(singletonTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location)) + { + Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, scope}; + + if (std::optional instantiatedIterMmTy = instantiation.substitute(*iterMmTy)) + { + if (const FunctionTypeVar* iterMmFtv = get(*instantiatedIterMmTy)) + { + TypePackId argPack = arena.addTypePack({iteratorTy}); + reportErrors(tryUnify(scope, forInStatement->values.data[0]->location, argPack, iterMmFtv->argTypes)); + + std::vector 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 instantiatedNextFn = instantiation.substitute(nextFn)) + { + std::vector instantiatedIteratorTypes = mmIteratorTypes; + instantiatedIteratorTypes[0] = *instantiatedNextFn; + + if (const FunctionTypeVar* nextFtv = get(*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); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index b6f20ac..b96046b 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -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 singl { globalScope = std::make_shared(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()) { - 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(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()) + e = group->expr; + if (e->is()) + 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(param.ty); + LUAU_ASSERT(generic); + aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, param.ty}; + } + + for (auto param : binding->typePackParams) + { + auto generic = get(param.tp); + LUAU_ASSERT(generic); + aliasScope->privateTypePackBindings[generic->name] = param.tp; + } + + TypeId ty = resolveType(aliasScope, *typealias.type); + if (auto ttv = getMutable(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(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 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(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(param.tp); - LUAU_ASSERT(generic); - aliasScope->privateTypePackBindings[generic->name] = param.tp; - } + TypeId ty = freshType(aliasScope); + FreeTypeVar* ftv = getMutable(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(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(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> 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; diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 6ea04ea..ca00c26 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -224,4 +224,46 @@ std::pair> getParameterExtents(const TxnLog* log, return {minCount, minCount + optionalCount}; } +std::vector flatten(TypeArena& arena, NotNull singletonTypes, TypePackId pack, size_t length) +{ + std::vector 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(tail)) + LUAU_ASSERT(0); + else if (auto vtp = get(tail)) + { + while (result.size() < length) + result.push_back(vtp->ty); + } + else if (get(tail) || get(tail)) + { + while (result.size() < length) + result.push_back(arena.addType(FreeTypeVar{nullptr})); + } + else if (auto etp = get(tail)) + { + while (result.size() < length) + result.push_back(singletonTypes->errorRecoveryType()); + } + + return result; +} + } // namespace Luau diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 3a820ea..bf6bf34 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -474,6 +474,17 @@ FunctionTypeVar::FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackI { } +FunctionTypeVar::FunctionTypeVar( + TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) + : level(level) + , scope(scope) + , argTypes(argTypes) + , retTypes(retTypes) + , definition(std::move(defn)) + , hasSelf(hasSelf) +{ +} + FunctionTypeVar::FunctionTypeVar(std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) : generics(generics) @@ -497,9 +508,23 @@ FunctionTypeVar::FunctionTypeVar(TypeLevel level, std::vector generics, { } -TableTypeVar::TableTypeVar(TableState state, TypeLevel level) +FunctionTypeVar::FunctionTypeVar(TypeLevel level, Scope* scope, std::vector generics, std::vector genericPacks, + TypePackId argTypes, TypePackId retTypes, std::optional 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 { } +TableTypeVar::TableTypeVar(const Props& props, const std::optional& 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. diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index fa76e82..a3d4540 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -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() diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 505e9e4..c13a6f8 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -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> getTableMat return std::nullopt; } +// TODO: Inline and clip with FFlag::DebugLuauDeferredConstraintResolution +template +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, Mode mode, NotNull 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(superTy); auto subFree = log.getMutable(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(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(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(newTail)); level = log.getMutable(packId)->level; + scope = log.getMutable(packId)->scope; log.replace(packId, BoundTypePack(newTail)); packId = newTail; pack = log.getMutable(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 mtPropTy = findTablePropertyRespectingMeta(superTy, propName)) + { + innerState.tryUnify(prop.type, *mtPropTy); + } + else + { + reportError(mismatchError); + missingProperty = true; + break; + } + } + + if (const TableTypeVar* superTable = log.get(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; } diff --git a/CLI/Analyze.cpp b/CLI/Analyze.cpp index cd50ef0..7e4c569 100644 --- a/CLI/Analyze.cpp +++ b/CLI/Analyze.cpp @@ -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 diff --git a/CodeGen/src/Fallbacks.cpp b/CodeGen/src/Fallbacks.cpp index 3893d34..625a9bd 100644 --- a/CodeGen/src/Fallbacks.cpp +++ b/CodeGen/src/Fallbacks.cpp @@ -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); } diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index f865243..32e5ba9 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -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 diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index 2693373..8d4640d 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -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") diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index e5ce4d5..28307eb 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -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 diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index c42e5cc..c2d07dd 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -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 diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index c16e5aa..87b6ae0 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -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, }; diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 7c181d4..e695cd2 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -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; diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index ecd2fcb..ff8105b 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -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); } diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index c2a672e..4b9fbb6 100644 --- a/VM/src/lgc.cpp +++ b/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); diff --git a/VM/src/lobject.cpp b/VM/src/lobject.cpp index b6a40bb..f5f1cd0 100644 --- a/VM/src/lobject.cpp +++ b/VM/src/lobject.cpp @@ -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); } diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 97cbfbb..5f5e7b1 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -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)) diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index cb7ba09..d753e8a 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -57,6 +57,7 @@ const char* const luaT_eventname[] = { "__le", "__concat", "__type", + "__metatable", }; // clang-format on diff --git a/VM/src/ltm.h b/VM/src/ltm.h index f20ce1b..4b1c281 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -36,6 +36,7 @@ typedef enum TM_LE, TM_CONCAT, TM_TYPE, + TM_METATABLE, TM_N // number of elements in the enum } TMS; diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 6ceed51..490358c 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -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++); diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 0ae85ab..bd40bad 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -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; } diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 35124e6..5c55515 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -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 } diff --git a/fuzz/linter.cpp b/fuzz/linter.cpp index 0bdd49f..66ca5bb 100644 --- a/fuzz/linter.cpp +++ b/fuzz/linter.cpp @@ -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; diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index f64b615..9e0a682 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -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; diff --git a/fuzz/typeck.cpp b/fuzz/typeck.cpp index 3905cc1..a6c9ae2 100644 --- a/fuzz/typeck.cpp +++ b/fuzz/typeck.cpp @@ -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; diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 9409c82..d0f7778 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -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 )"); } diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 77b3048..a6e4e6e 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -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); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 3f77978..dcc0222 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -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); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index a8a9e04..5f74931 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -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 = ( eventName: T, args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]] - ) -> unknown + ) -> MyAny return {} )"; diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index ab5d859..1339ec2 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -790,4 +790,20 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param") CHECK_EQ("foo:method(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(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 834391a..5ecc2a8 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -443,7 +443,8 @@ TEST_CASE_FIXTURE(Fixture, "reported_location_is_correct_when_type_alias_are_dup auto dtd = get(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 t1 = lookupType("MyString"); + REQUIRE(t1); + CHECK(isPrim(*t1, PrimitiveTypeVar::String)); + + std::optional 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(); diff --git a/tests/TypeInfer.annotations.test.cpp b/tests/TypeInfer.annotations.test.cpp index 5d18b33..5f2c22c 100644 --- a/tests/TypeInfer.annotations.test.cpp +++ b/tests/TypeInfer.annotations.test.cpp @@ -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 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") diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 6da3f56..037f79d 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -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(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(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(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(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); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index bde28dc..a4420b9 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -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(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(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(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") diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 85249ec..588a9a7 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -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(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 2c4c350..45740a0 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -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"))); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index d2bff9c..b6dedcb 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -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"( diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index a96c367..8ed61b4 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -1159,4 +1159,38 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint") CHECK("(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(); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 7fa0fac..3911c52 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -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(); diff --git a/tests/conformance/events.lua b/tests/conformance/events.lua index 6dcdbf0..0c6055d 100644 --- a/tests/conformance/events.lua +++ b/tests/conformance/events.lua @@ -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 diff --git a/tools/faillist.txt b/tools/faillist.txt index 825fb2f..00e0101 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -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 diff --git a/tools/lldb_formatters.lldb b/tools/lldb_formatters.lldb index f6fa6cf..4a5acd7 100644 --- a/tools/lldb_formatters.lldb +++ b/tools/lldb_formatters.lldb @@ -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 diff --git a/tools/lldb_formatters.py b/tools/lldb_formatters.py index ff610d0..19fc0f5 100644 --- a/tools/lldb_formatters.py +++ b/tools/lldb_formatters.py @@ -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*)\"\"") + return self.valobj.CreateValueFromExpression( + node, '(const char*)""' + ) 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*)\"\"") + return self.valobj.CreateValueExpression( + node, '(const char*)""' + ) else: - return self.valobj.CreateValueFromExpression(node, "(const char*)\"\"") + return self.valobj.CreateValueFromExpression( + node, '(const char*)""' + ) 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 diff --git a/tools/perfgraph.py b/tools/perfgraph.py index 7d2639d..eb6b68c 100644 --- a/tools/perfgraph.py +++ b/tools/perfgraph.py @@ -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 diff --git a/tools/perfstat.py b/tools/perfstat.py new file mode 100644 index 0000000..e5cfd11 --- /dev/null +++ b/tools/perfstat.py @@ -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()}") diff --git a/tools/test_dcr.py b/tools/test_dcr.py index db93225..1e3a501 100644 --- a/tools/test_dcr.py +++ b/tools/test_dcr.py @@ -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)