diff --git a/Analysis/include/Luau/Anyification.h b/Analysis/include/Luau/Anyification.h index ee8d6689..9dd7d8e0 100644 --- a/Analysis/include/Luau/Anyification.h +++ b/Analysis/include/Luau/Anyification.h @@ -19,6 +19,7 @@ using ScopePtr = std::shared_ptr; // A substitution which replaces free types by any struct Anyification : Substitution { + Anyification(TypeArena* arena, NotNull scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack); Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack); NotNull scope; InternalErrorReporter* iceHandler; @@ -35,4 +36,4 @@ struct Anyification : Substitution bool ignoreChildren(TypePackId ty) override; }; -} \ No newline at end of file +} // namespace Luau \ No newline at end of file diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index 07d897b2..28a4368e 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -34,6 +34,7 @@ TypeId makeFunction( // Polymorphic std::initializer_list paramTypes, std::initializer_list paramNames, std::initializer_list retTypes); void attachMagicFunction(TypeId ty, MagicFunction fn); +void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn); Property makeProperty(TypeId ty, std::optional documentationSymbol = std::nullopt); void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName); diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index e9f04e79..d5cfcf3f 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -56,6 +56,10 @@ struct UnaryConstraint TypeId resultType; }; +// let L : leftType +// let R : rightType +// in +// L op R : resultType struct BinaryConstraint { AstExprBinary::Op op; @@ -64,6 +68,14 @@ struct BinaryConstraint TypeId resultType; }; +// iteratee is iterable +// iterators is the iteration types. +struct IterableConstraint +{ + TypePackId iterator; + TypePackId variables; +}; + // name(namedType) = name struct NameConstraint { @@ -78,20 +90,31 @@ struct TypeAliasExpansionConstraint TypeId target; }; -using ConstraintV = Variant; using ConstraintPtr = std::unique_ptr; +struct FunctionCallConstraint +{ + std::vector> innerConstraints; + TypeId fn; + TypePackId result; + class AstExprCall* astFragment; +}; + +using ConstraintV = Variant; + struct Constraint { - Constraint(ConstraintV&& c, NotNull scope); + Constraint(NotNull scope, const Location& location, ConstraintV&& c); Constraint(const Constraint&) = delete; Constraint& operator=(const Constraint&) = delete; - ConstraintV c; - std::vector> dependencies; NotNull scope; + Location location; + ConstraintV c; + + std::vector> dependencies; }; inline Constraint& asMutable(const Constraint& c) diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 0e41e1ee..1cba0d33 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -9,6 +9,7 @@ #include "Luau/Ast.h" #include "Luau/Constraint.h" #include "Luau/Module.h" +#include "Luau/ModuleResolver.h" #include "Luau/NotNull.h" #include "Luau/Symbol.h" #include "Luau/TypeVar.h" @@ -51,12 +52,15 @@ struct ConstraintGraphBuilder // It is pretty uncommon for constraint generation to itself produce errors, but it can happen. std::vector errors; + // Needed to resolve modules to make 'require' import types properly. + NotNull moduleResolver; // Occasionally constraint generation needs to produce an ICE. const NotNull ice; ScopePtr globalScope; - ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull ice, const ScopePtr& globalScope); + ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull moduleResolver, + NotNull ice, const ScopePtr& globalScope); /** * Fabricates a new free type belonging to a given scope. @@ -82,7 +86,7 @@ struct ConstraintGraphBuilder * @param scope the scope to add the constraint to. * @param cv the constraint variant to add. */ - void addConstraint(const ScopePtr& scope, ConstraintV cv); + void addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv); /** * Adds a constraint to a given scope. @@ -104,6 +108,7 @@ struct ConstraintGraphBuilder void visit(const ScopePtr& scope, AstStatBlock* block); void visit(const ScopePtr& scope, AstStatLocal* local); void visit(const ScopePtr& scope, AstStatFor* for_); + void visit(const ScopePtr& scope, AstStatForIn* forIn); void visit(const ScopePtr& scope, AstStatWhile* while_); void visit(const ScopePtr& scope, AstStatRepeat* repeat); void visit(const ScopePtr& scope, AstStatLocalFunction* function); @@ -117,8 +122,6 @@ struct ConstraintGraphBuilder void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass); void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); - TypePackId checkExprList(const ScopePtr& scope, const AstArray& exprs); - TypePackId checkPack(const ScopePtr& scope, AstArray exprs); TypePackId checkPack(const ScopePtr& scope, AstExpr* expr); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index a270ec99..002aa947 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -17,6 +17,8 @@ namespace Luau // never dereference this pointer. using BlockedConstraintId = const void*; +struct ModuleResolver; + struct InstantiationSignature { TypeFun fn; @@ -42,6 +44,7 @@ struct ConstraintSolver // The entire set of constraints that the solver is trying to resolve. std::vector> constraints; NotNull rootScope; + ModuleName currentModuleName; // Constraints that the solver has generated, rather than sourcing from the // scope tree. @@ -63,9 +66,13 @@ struct ConstraintSolver // Recorded errors that take place within the solver. ErrorVec errors; + NotNull moduleResolver; + std::vector requireCycles; + ConstraintSolverLogger logger; - explicit ConstraintSolver(TypeArena* arena, NotNull rootScope); + explicit ConstraintSolver(TypeArena* arena, NotNull rootScope, ModuleName moduleName, NotNull moduleResolver, + std::vector requireCycles); /** * Attempts to dispatch all pending constraints and reach a type solution @@ -86,8 +93,17 @@ struct ConstraintSolver bool tryDispatch(const InstantiationConstraint& c, NotNull constraint, bool force); bool tryDispatch(const UnaryConstraint& c, NotNull constraint, bool force); bool tryDispatch(const BinaryConstraint& c, NotNull constraint, bool force); + bool tryDispatch(const IterableConstraint& c, NotNull constraint, bool force); bool tryDispatch(const NameConstraint& c, NotNull constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint); + bool tryDispatch(const FunctionCallConstraint& c, NotNull constraint); + + // for a, ... in some_table do + bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force); + + // for a, ... in next_function, t, ... do + bool tryDispatchIterableFunction( + TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull constraint, bool force); void block(NotNull target, NotNull constraint); /** @@ -108,6 +124,11 @@ struct ConstraintSolver */ bool isBlocked(TypeId ty); + /** + * @returns true if the TypePackId is in a blocked state. + */ + bool isBlocked(TypePackId tp); + /** * Returns whether the constraint is blocked on anything. * @param constraint the constraint to check. @@ -133,10 +154,22 @@ struct ConstraintSolver /** Pushes a new solver constraint to the solver. * @param cv the body of the constraint. **/ - void pushConstraint(ConstraintV cv, NotNull scope); + void pushConstraint(NotNull scope, const Location& location, ConstraintV cv); + + /** + * Attempts to resolve a module from its module information. Returns the + * module-level return type of the module, or the error type if one cannot + * be found. Reports errors to the solver if the module cannot be found or + * the require is illegal. + * @param module the module information to look up. + * @param location the location where the require is taking place; used for + * error locations. + **/ + TypeId resolveModule(const ModuleInfo& module, const Location& location); void reportError(TypeErrorData&& data, const Location& location); void reportError(TypeError e); + private: /** * Marks a constraint as being blocked on a type or type pack. The constraint @@ -154,6 +187,8 @@ private: * @param progressed the type or type pack pointer that has progressed. **/ void unblock_(BlockedConstraintId progressed); + + ToStringOptions opts; }; void dump(NotNull rootScope, struct ToStringOptions& opts); diff --git a/Analysis/include/Luau/ConstraintSolverLogger.h b/Analysis/include/Luau/ConstraintSolverLogger.h index 55170c42..65aa9a7e 100644 --- a/Analysis/include/Luau/ConstraintSolverLogger.h +++ b/Analysis/include/Luau/ConstraintSolverLogger.h @@ -16,7 +16,8 @@ struct ConstraintSolverLogger { std::string compileOutput(); void captureBoundarySnapshot(const Scope* rootScope, std::vector>& unsolvedConstraints); - void prepareStepSnapshot(const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints, bool force); + void prepareStepSnapshot( + const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints, bool force); void commitPreparedStepSnapshot(); private: diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index f8da3273..55612689 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -157,7 +157,7 @@ struct Frontend ScopePtr getGlobalScope(); private: - ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope); + ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector requireCycles); std::pair getSourceNode(CheckResult& checkResult, const ModuleName& name); SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions); diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index dc123335..b7569d8e 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -40,6 +40,9 @@ struct Scope std::optional varargPack; // All constraints belonging to this scope. std::vector constraints; + // Constraints belonging to this scope that are queued manually by other + // constraints. + std::vector unqueuedConstraints; TypeLevel level; diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index eabbc2be..61e07e9f 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -92,7 +92,6 @@ inline std::string toString(const Constraint& c) return toString(c, ToStringOptions{}); } - std::string toString(const TypeVar& tv, ToStringOptions& opts); std::string toString(const TypePackVar& tp, ToStringOptions& opts); diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index be36f19c..decc8c59 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -29,9 +29,10 @@ struct TypeArena TypeId addTV(TypeVar&& tv); TypeId freshType(TypeLevel level); + TypeId freshType(Scope* scope); TypePackId addTypePack(std::initializer_list types); - TypePackId addTypePack(std::vector types); + TypePackId addTypePack(std::vector types, std::optional tail = {}); TypePackId addTypePack(TypePack pack); TypePackId addTypePack(TypePackVar pack); diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index e253eddf..0b427946 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -166,7 +166,8 @@ struct TypeChecker */ bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location); bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options); - bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg); + bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, + CountMismatch::Context ctx = CountMismatch::Context::Arg); /** Attempt to unify the types. * If this fails, and the subTy type can be instantiated, do so and try unification again. diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index b17003b1..8269230b 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -15,6 +15,7 @@ struct TypeArena; struct TypePack; struct VariadicTypePack; +struct BlockedTypePack; struct TypePackVar; @@ -24,7 +25,7 @@ using TypePackId = const TypePackVar*; using FreeTypePack = Unifiable::Free; using BoundTypePack = Unifiable::Bound; using GenericTypePack = Unifiable::Generic; -using TypePackVariant = Unifiable::Variant; +using TypePackVariant = Unifiable::Variant; /* A TypePack is a rope-like string of TypeIds. We use this structure to encode * notions like packs of unknown length and packs of any length, as well as more @@ -43,6 +44,17 @@ struct VariadicTypePack bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail. }; +/** + * Analogous to a BlockedTypeVar. + */ +struct BlockedTypePack +{ + BlockedTypePack(); + size_t index; + + static size_t nextIndex; +}; + struct TypePackVar { explicit TypePackVar(const TypePackVariant& ty); diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 0aff5a7d..6c611fb2 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -11,12 +11,16 @@ namespace Luau { +struct TxnLog; + using ScopePtr = std::shared_ptr; std::optional findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location); std::optional findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location); -std::optional getIndexTypeFromType( - const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors, - InternalErrorReporter& handle); +std::optional getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, + const Location& location, bool addErrors, InternalErrorReporter& handle); + +// Returns the minimum and maximum number of types the argument list can accept. +std::pair> getParameterExtents(const TxnLog* log, TypePackId tp); } // namespace Luau diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index 35b37394..e67b3601 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -1,11 +1,13 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Ast.h" #include "Luau/DenseHash.h" #include "Luau/Predicate.h" #include "Luau/Unifiable.h" #include "Luau/Variant.h" #include "Luau/Common.h" +#include "Luau/NotNull.h" #include #include @@ -262,6 +264,8 @@ 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 FunctionTypeVar { // Global monomorphic function @@ -287,7 +291,8 @@ struct FunctionTypeVar std::vector> argNames; TypePackId retTypes; std::optional definition; - MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr. + MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr. + DcrMagicFunction dcrMagicFunction = nullptr; // can be nullptr bool hasSelf; Tags tags; bool hasNoGenerics = false; @@ -462,8 +467,9 @@ struct TypeFun */ struct PendingExpansionTypeVar { - PendingExpansionTypeVar(TypeFun fn, std::vector typeArguments, std::vector packArguments); - TypeFun fn; + PendingExpansionTypeVar(std::optional prefix, AstName name, std::vector typeArguments, std::vector packArguments); + std::optional prefix; + AstName name; std::vector typeArguments; std::vector packArguments; size_t index; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 312b0584..c7eb51a6 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -59,7 +59,8 @@ struct Unifier UnifierSharedState& sharedState; - Unifier(TypeArena* types, Mode mode, NotNull scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); + Unifier(TypeArena* types, Mode mode, NotNull scope, const Location& location, Variance variance, UnifierSharedState& sharedState, + TxnLog* parentLog = nullptr); // Test whether the two type vars unify. Never commits the result. ErrorVec canUnify(TypeId subTy, TypeId superTy); diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index 9d7fa9fe..315e5992 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -188,6 +188,10 @@ struct GenericTypeVarVisitor { return visit(tp); } + virtual bool visit(TypePackId tp, const BlockedTypePack& btp) + { + return visit(tp); + } void traverse(TypeId ty) { @@ -314,24 +318,6 @@ struct GenericTypeVarVisitor { if (visit(ty, *petv)) { - traverse(petv->fn.type); - - for (const GenericTypeDefinition& p : petv->fn.typeParams) - { - traverse(p.ty); - - if (p.defaultValue) - traverse(*p.defaultValue); - } - - for (const GenericTypePackDefinition& p : petv->fn.typePackParams) - { - traverse(p.tp); - - if (p.defaultValue) - traverse(*p.defaultValue); - } - for (TypeId a : petv->typeArguments) traverse(a); @@ -388,6 +374,9 @@ struct GenericTypeVarVisitor if (res) traverse(pack->ty); } + else if (auto btp = get(tp)) + visit(tp, *btp); + else LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"); diff --git a/Analysis/src/Anyification.cpp b/Analysis/src/Anyification.cpp index b6e58009..abcaba02 100644 --- a/Analysis/src/Anyification.cpp +++ b/Analysis/src/Anyification.cpp @@ -11,15 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) namespace Luau { -Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack) +Anyification::Anyification(TypeArena* arena, NotNull scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack) : Substitution(TxnLog::empty(), arena) - , scope(NotNull{scope.get()}) + , scope(scope) , iceHandler(iceHandler) , anyType(anyType) , anyTypePack(anyTypePack) { } +Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack) + : Anyification(arena, NotNull{scope.get()}, iceHandler, anyType, anyTypePack) +{ +} + bool Anyification::isDirty(TypeId ty) { if (ty->persistent) @@ -93,4 +98,4 @@ bool Anyification::ignoreChildren(TypePackId ty) return ty->persistent; } -} +} // namespace Luau diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 5c484899..378a1cb7 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -1215,8 +1215,8 @@ static bool autocompleteIfElseExpression( } } -static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena, - const std::vector& ancestry, Position position, AutocompleteEntryMap& result) +static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, + TypeArena* typeArena, const std::vector& ancestry, Position position, AutocompleteEntryMap& result) { LUAU_ASSERT(!ancestry.empty()); @@ -1422,8 +1422,8 @@ 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, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property}; + return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, + AutocompleteContext::Property}; else return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property}; } @@ -1522,8 +1522,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M else if (AstStatIf* statIf = node->as(); statIf && !statIf->elseLocation.has_value()) { - return { - {{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, + ancestry, AutocompleteContext::Keyword}; } else if (AstStatIf* statIf = parent->as(); statIf && node->is()) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 826179b3..e011eaa5 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -5,6 +5,7 @@ #include "Luau/Symbol.h" #include "Luau/Common.h" #include "Luau/ToString.h" +#include "Luau/ConstraintSolver.h" #include @@ -32,6 +33,8 @@ 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); + TypeId makeUnion(TypeArena& arena, std::vector&& types) { return arena.addType(UnionTypeVar{std::move(types)}); @@ -105,6 +108,14 @@ void attachMagicFunction(TypeId ty, MagicFunction fn) LUAU_ASSERT(!"Got a non functional type"); } +void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn) +{ + if (auto ftv = getMutable(ty)) + ftv->dcrMagicFunction = fn; + else + LUAU_ASSERT(!"Got a non functional type"); +} + Property makeProperty(TypeId ty, std::optional documentationSymbol) { return { @@ -263,6 +274,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker) } attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire); + attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire); } static std::optional> magicFunctionSelect( @@ -509,4 +521,49 @@ static std::optional> magicFunctionRequire( return std::nullopt; } +static bool checkRequirePathDcr(NotNull solver, AstExpr* expr) +{ + // require(foo.parent.bar) will technically work, but it depends on legacy goop that + // Luau does not and could not support without a bunch of work. It's deprecated anyway, so + // we'll warn here if we see it. + bool good = true; + AstExprIndexName* indexExpr = expr->as(); + + while (indexExpr) + { + if (indexExpr->index == "parent") + { + solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation); + good = false; + } + + indexExpr = indexExpr->expr->as(); + } + + return good; +} + +static bool dcrMagicFunctionRequire(NotNull solver, TypePackId result, const AstExprCall* expr) +{ + if (expr->args.size != 1) + { + solver->reportError(GenericError{"require takes 1 argument"}, expr->location); + return false; + } + + if (!checkRequirePathDcr(solver, expr->args.data[0])) + return false; + + if (auto moduleInfo = solver->moduleResolver->resolveModuleInfo(solver->currentModuleName, *expr)) + { + TypeId moduleType = solver->resolveModule(*moduleInfo, expr->location); + TypePackId moduleResult = solver->arena->addTypePack({moduleType}); + asMutable(result)->ty.emplace(moduleResult); + + return true; + } + + return false; +} + } // namespace Luau diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 2e04b527..7048d201 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -102,6 +102,11 @@ struct TypePackCloner defaultClone(t); } + void operator()(const BlockedTypePack& t) + { + defaultClone(t); + } + // While we are a-cloning, we can flatten out bound TypeVars and make things a bit tighter. // We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer. void operator()(const Unifiable::Bound& t) @@ -170,7 +175,7 @@ void TypeCloner::operator()(const BlockedTypeVar& t) void TypeCloner::operator()(const PendingExpansionTypeVar& t) { - TypeId res = dest.addType(PendingExpansionTypeVar{t.fn, t.typeArguments, t.packArguments}); + TypeId res = dest.addType(PendingExpansionTypeVar{t.prefix, t.name, t.typeArguments, t.packArguments}); PendingExpansionTypeVar* petv = getMutable(res); LUAU_ASSERT(petv); @@ -184,32 +189,6 @@ void TypeCloner::operator()(const PendingExpansionTypeVar& t) for (TypePackId arg : t.packArguments) packArguments.push_back(clone(arg, dest, cloneState)); - TypeFun fn; - fn.type = clone(t.fn.type, dest, cloneState); - - for (const GenericTypeDefinition& param : t.fn.typeParams) - { - TypeId ty = clone(param.ty, dest, cloneState); - std::optional defaultValue = param.defaultValue; - - if (defaultValue) - defaultValue = clone(*defaultValue, dest, cloneState); - - fn.typeParams.push_back(GenericTypeDefinition{ty, defaultValue}); - } - - for (const GenericTypePackDefinition& param : t.fn.typePackParams) - { - TypePackId tp = clone(param.tp, dest, cloneState); - std::optional defaultValue = param.defaultValue; - - if (defaultValue) - defaultValue = clone(*defaultValue, dest, cloneState); - - fn.typePackParams.push_back(GenericTypePackDefinition{tp, defaultValue}); - } - - petv->fn = std::move(fn); petv->typeArguments = std::move(typeArguments); petv->packArguments = std::move(packArguments); } @@ -461,6 +440,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl clone.generics = ftv->generics; clone.genericPacks = ftv->genericPacks; clone.magicFunction = ftv->magicFunction; + clone.dcrMagicFunction = ftv->dcrMagicFunction; clone.tags = ftv->tags; clone.argNames = ftv->argNames; result = dest.addType(std::move(clone)); @@ -502,7 +482,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysCl } else if (const PendingExpansionTypeVar* petv = get(ty)) { - PendingExpansionTypeVar clone{petv->fn, petv->typeArguments, petv->packArguments}; + PendingExpansionTypeVar clone{petv->prefix, petv->name, petv->typeArguments, petv->packArguments}; result = dest.addType(std::move(clone)); } else if (const ClassTypeVar* ctv = get(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone) diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index d272c027..3a6417dc 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -5,9 +5,10 @@ namespace Luau { -Constraint::Constraint(ConstraintV&& c, NotNull scope) - : c(std::move(c)) - , scope(scope) +Constraint::Constraint(NotNull scope, const Location& location, ConstraintV&& c) + : scope(scope) + , location(location) + , c(std::move(c)) { } diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 8f994740..9fabc528 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/Common.h" #include "Luau/Constraint.h" +#include "Luau/ModuleResolver.h" #include "Luau/RecursionCounter.h" #include "Luau/ToString.h" @@ -16,13 +17,31 @@ namespace Luau const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp -ConstraintGraphBuilder::ConstraintGraphBuilder( - const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull ice, const ScopePtr& globalScope) +static std::optional matchRequire(const AstExprCall& call) +{ + const char* require = "require"; + + if (call.args.size != 1) + return std::nullopt; + + const AstExprGlobal* funcAsGlobal = call.func->as(); + if (!funcAsGlobal || funcAsGlobal->name != require) + return std::nullopt; + + if (call.args.size != 1) + return std::nullopt; + + return call.args.data[0]; +} + +ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, + NotNull moduleResolver, NotNull ice, const ScopePtr& globalScope) : moduleName(moduleName) , module(module) , singletonTypes(getSingletonTypes()) , arena(arena) , rootScope(nullptr) + , moduleResolver(moduleResolver) , ice(ice) , globalScope(globalScope) { @@ -54,9 +73,9 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren return scope; } -void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv) +void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv) { - scope->constraints.emplace_back(new Constraint{std::move(cv), NotNull{scope.get()}}); + scope->constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)}); } void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr c) @@ -77,13 +96,6 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block) prepopulateGlobalScope(scope, block); - // TODO: We should share the global scope. - rootScope->privateTypeBindings["nil"] = TypeFun{singletonTypes.nilType}; - rootScope->privateTypeBindings["number"] = TypeFun{singletonTypes.numberType}; - rootScope->privateTypeBindings["string"] = TypeFun{singletonTypes.stringType}; - rootScope->privateTypeBindings["boolean"] = TypeFun{singletonTypes.booleanType}; - rootScope->privateTypeBindings["thread"] = TypeFun{singletonTypes.threadType}; - visitBlockWithoutChildScope(scope, block); } @@ -158,6 +170,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 if (auto s = stat->as()) visit(scope, s); else if (auto s = stat->as()) @@ -201,7 +215,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) { location = local->annotation->location; TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true); - addConstraint(scope, SubtypeConstraint{ty, annotation}); + addConstraint(scope, location, SubtypeConstraint{ty, annotation}); } varTypes.push_back(ty); @@ -225,14 +239,38 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) { std::vector tailValues{varTypes.begin() + i, varTypes.end()}; TypePackId tailPack = arena->addTypePack(std::move(tailValues)); - addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack}); + addConstraint(scope, local->location, PackSubtypeConstraint{exprPack, tailPack}); } } else { TypeId exprType = check(scope, value); if (i < varTypes.size()) - addConstraint(scope, SubtypeConstraint{varTypes[i], exprType}); + addConstraint(scope, local->location, SubtypeConstraint{varTypes[i], exprType}); + } + } + + if (local->values.size > 0) + { + // To correctly handle 'require', we need to import the exported type bindings into the variable 'namespace'. + for (size_t i = 0; i < local->values.size && i < local->vars.size; ++i) + { + const AstExprCall* call = local->values.data[i]->as(); + if (!call) + continue; + + if (auto maybeRequire = matchRequire(*call)) + { + AstExpr* require = *maybeRequire; + + if (auto moduleInfo = moduleResolver->resolveModuleInfo(moduleName, *require)) + { + const Name name{local->vars.data[i]->name.value}; + + if (ModulePtr module = moduleResolver->getModule(moduleInfo->name)) + scope->importedTypeBindings[name] = module->getModuleScope()->exportedTypeBindings; + } + } } } } @@ -244,7 +282,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) return; TypeId t = check(scope, expr); - addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType}); + addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes.numberType}); }; checkNumber(for_->from); @@ -257,6 +295,29 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) visit(forScope, for_->body); } +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn) +{ + ScopePtr loopScope = childScope(forIn, scope); + + TypePackId iterator = checkPack(scope, forIn->values); + + std::vector variableTypes; + variableTypes.reserve(forIn->vars.size); + for (AstLocal* var : forIn->vars) + { + TypeId ty = freshType(loopScope); + loopScope->bindings[var] = Binding{ty, var->location}; + variableTypes.push_back(ty); + } + + // It is always ok to provide too few variables, so we give this pack a free tail. + TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()})); + + addConstraint(loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack}); + + visit(loopScope, forIn->body); +} + void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_) { check(scope, while_->condition); @@ -284,6 +345,9 @@ void addConstraints(Constraint* constraint, NotNull scope) for (const auto& c : scope->constraints) constraint->dependencies.push_back(NotNull{c.get()}); + for (const auto& c : scope->unqueuedConstraints) + constraint->dependencies.push_back(NotNull{c.get()}); + for (NotNull childScope : scope->children) addConstraints(constraint, childScope); } @@ -308,7 +372,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* checkFunctionBody(sig.bodyScope, function->func); NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; - std::unique_ptr c = std::make_unique(GeneralizationConstraint{functionType, sig.signature}, constraintScope); + std::unique_ptr c = + std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); @@ -366,7 +431,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct prop.type = functionType; prop.location = function->name->location; - addConstraint(scope, SubtypeConstraint{containingTableType, prospectiveTableType}); + addConstraint(scope, indexName->location, SubtypeConstraint{containingTableType, prospectiveTableType}); } else if (AstExprError* err = function->name->as()) { @@ -378,7 +443,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct checkFunctionBody(sig.bodyScope, function->func); NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; - std::unique_ptr c = std::make_unique(GeneralizationConstraint{functionType, sig.signature}, constraintScope); + std::unique_ptr c = + std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); @@ -387,7 +453,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret) { TypePackId exprTypes = checkPack(scope, ret->list); - addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}); + addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType}); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) @@ -399,10 +465,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign) { - TypePackId varPackId = checkExprList(scope, assign->vars); + TypePackId varPackId = checkPack(scope, assign->vars); TypePackId valuePack = checkPack(scope, assign->values); - addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}); + addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId}); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign) @@ -435,8 +501,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) { - // TODO: Exported type aliases - auto bindingIt = scope->privateTypeBindings.find(alias->name.value); ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias); // These will be undefined if the alias was a duplicate definition, in which @@ -449,6 +513,12 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia ScopePtr resolvingScope = *defnIt; TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true); + if (alias->exported) + { + Name typeName(alias->name.value); + scope->exportedTypeBindings[typeName] = TypeFun{ty}; + } + LUAU_ASSERT(get(bindingIt->second.type)); // Rather than using a subtype constraint, we instead directly bind @@ -457,7 +527,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia // bind the free alias type to an unrelated type, causing havoc. asMutable(bindingIt->second.type)->ty.emplace(ty); - addConstraint(scope, NameConstraint{ty, alias->name.value}); + addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value}); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) @@ -615,44 +685,22 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray exprs) { - if (exprs.size == 0) - return arena->addTypePack({}); - - std::vector types; - TypePackId last = nullptr; - - for (size_t i = 0; i < exprs.size; ++i) - { - if (i < exprs.size - 1) - types.push_back(check(scope, exprs.data[i])); - else - last = checkPack(scope, exprs.data[i]); - } - - LUAU_ASSERT(last != nullptr); - - return arena->addTypePack(TypePack{std::move(types), last}); -} - -TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray& exprs) -{ - TypePackId result = arena->addTypePack({}); - TypePack* resultPack = getMutable(result); - LUAU_ASSERT(resultPack); + std::vector head; + std::optional tail; for (size_t i = 0; i < exprs.size; ++i) { AstExpr* expr = exprs.data[i]; if (i < exprs.size - 1) - resultPack->head.push_back(check(scope, expr)); + head.push_back(check(scope, expr)); else - resultPack->tail = checkPack(scope, expr); + tail = checkPack(scope, expr); } - if (resultPack->head.empty() && resultPack->tail) - return *resultPack->tail; + if (head.empty() && tail) + return *tail; else - return result; + return arena->addTypePack(TypePack{std::move(head), tail}); } TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr) @@ -683,13 +731,26 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp astOriginalCallTypes[call->func] = fnType; TypeId instantiatedType = arena->addType(BlockedTypeVar{}); - addConstraint(scope, InstantiationConstraint{instantiatedType, fnType}); - - TypePackId rets = freshTypePack(scope); + TypePackId rets = arena->addTypePack(BlockedTypePack{}); FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets); TypeId inferredFnType = arena->addType(ftv); - addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType}); + scope->unqueuedConstraints.push_back( + std::make_unique(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType})); + NotNull ic(scope->unqueuedConstraints.back().get()); + + scope->unqueuedConstraints.push_back( + std::make_unique(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType})); + NotNull sc(scope->unqueuedConstraints.back().get()); + + addConstraint(scope, call->func->location, + FunctionCallConstraint{ + {ic, sc}, + fnType, + rets, + call, + }); + result = rets; } else if (AstExprVarargs* varargs = expr->as()) @@ -805,7 +866,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in TypeId expectedTableType = arena->addType(std::move(ttv)); - addConstraint(scope, SubtypeConstraint{obj, expectedTableType}); + addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType}); return result; } @@ -820,7 +881,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in TableIndexer indexer{indexType, result}; TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free}); - addConstraint(scope, SubtypeConstraint{obj, tableType}); + addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType}); return result; } @@ -834,7 +895,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary) case AstExprUnary::Minus: { TypeId resultType = arena->addType(BlockedTypeVar{}); - addConstraint(scope, UnaryConstraint{AstExprUnary::Minus, operandType, resultType}); + addConstraint(scope, unary->location, UnaryConstraint{AstExprUnary::Minus, operandType, resultType}); return resultType; } default: @@ -853,19 +914,19 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar { case AstExprBinary::Or: { - addConstraint(scope, SubtypeConstraint{leftType, rightType}); + addConstraint(scope, binary->location, SubtypeConstraint{leftType, rightType}); return leftType; } case AstExprBinary::Add: { TypeId resultType = arena->addType(BlockedTypeVar{}); - addConstraint(scope, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType}); + addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType}); return resultType; } case AstExprBinary::Sub: { TypeId resultType = arena->addType(BlockedTypeVar{}); - addConstraint(scope, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType}); + addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType}); return resultType; } default: @@ -886,8 +947,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifEls if (ifElse->hasElse) { TypeId resultType = arena->addType(BlockedTypeVar{}); - addConstraint(scope, SubtypeConstraint{thenType, resultType}); - addConstraint(scope, SubtypeConstraint{elseType, resultType}); + addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType}); + addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType}); return resultType; } @@ -906,7 +967,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl TableTypeVar* ttv = getMutable(ty); LUAU_ASSERT(ttv); - auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) { + auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) { if (!ttv->indexer) { TypeId indexType = this->freshType(scope); @@ -914,8 +975,8 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl ttv->indexer = TableIndexer{indexType, resultType}; } - addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType}); - addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType}); + addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexType, currentIndexType}); + addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType}); }; for (const AstExprTable::Item& item : expr->items) @@ -937,13 +998,15 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl } else { - createIndexer(keyTy, itemTy); + createIndexer(item.key->location, keyTy, itemTy); } } else { TypeId numberType = singletonTypes.numberType; - createIndexer(numberType, itemTy); + // FIXME? The location isn't quite right here. Not sure what is + // right. + createIndexer(item.value->location, numberType, itemTy); } } @@ -1008,7 +1071,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS if (fn->returnAnnotation) { TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); - addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType}); + addConstraint(signatureScope, getLocation(*fn->returnAnnotation), PackSubtypeConstraint{returnType, annotatedRetType}); } std::vector argTypes; @@ -1022,7 +1085,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS if (local->annotation) { TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true); - addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation}); + addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, argAnnotation}); } } @@ -1056,7 +1119,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun if (nullptr != getFallthrough(fn->body)) { TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever - addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty}); + addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty}); } } @@ -1066,16 +1129,13 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b if (auto ref = ty->as()) { - // TODO: Support imported types w/ require tracing. - LUAU_ASSERT(!ref->prefix); - std::optional alias = scope->lookupType(ref->name.value); - if (alias.has_value()) + if (alias.has_value() || ref->prefix.has_value()) { // If the alias is not generic, we don't need to set up a blocked // type and an instantiation constraint. - if (alias->typeParams.empty() && alias->typePackParams.empty()) + if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty()) { result = alias->type; } @@ -1104,11 +1164,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b } } - result = arena->addType(PendingExpansionTypeVar{*alias, parameters, packParameters}); + result = arena->addType(PendingExpansionTypeVar{ref->prefix, ref->name, parameters, packParameters}); if (topLevel) { - addConstraint(scope, TypeAliasExpansionConstraint{ /* target */ result }); + addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result}); } } } @@ -1141,8 +1201,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b }; } - // TODO: Remove TypeLevel{} here, we don't need it. - result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed}); + result = arena->addType(TableTypeVar{props, indexer, scope->level, TableState::Sealed}); } else if (auto fn = ty->as()) { @@ -1363,7 +1422,7 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat TypePack onePack{{typeResult}, freshTypePack(scope)}; TypePackId oneTypePack = arena->addTypePack(std::move(onePack)); - addConstraint(scope, PackSubtypeConstraint{tp, oneTypePack}); + addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack}); return typeResult; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index b2b1d472..1088d982 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1,9 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Anyification.h" #include "Luau/ApplyTypeFunction.h" #include "Luau/ConstraintSolver.h" #include "Luau/Instantiation.h" #include "Luau/Location.h" +#include "Luau/ModuleResolver.h" #include "Luau/Quantify.h" #include "Luau/ToString.h" #include "Luau/Unifier.h" @@ -240,11 +242,17 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) } } -ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope) +ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope, ModuleName moduleName, NotNull moduleResolver, + std::vector requireCycles) : arena(arena) , constraints(collectConstraints(rootScope)) , rootScope(rootScope) + , currentModuleName(std::move(moduleName)) + , moduleResolver(moduleResolver) + , requireCycles(requireCycles) { + opts.exhaustive = true; + for (NotNull c : constraints) { unsolvedConstraints.push_back(c); @@ -261,9 +269,6 @@ void ConstraintSolver::run() if (done()) return; - ToStringOptions opts; - opts.exhaustive = true; - if (FFlag::DebugLuauLogSolver) { printf("Starting solver\n"); @@ -371,10 +376,14 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*uc, constraint, force); else if (auto bc = get(*constraint)) success = tryDispatch(*bc, constraint, force); + else if (auto ic = get(*constraint)) + success = tryDispatch(*ic, constraint, force); else if (auto nc = get(*constraint)) success = tryDispatch(*nc, constraint); else if (auto taec = get(*constraint)) success = tryDispatch(*taec, constraint); + else if (auto fcc = get(*constraint)) + success = tryDispatch(*fcc, constraint); else LUAU_ASSERT(0); @@ -400,6 +409,11 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull constraint, bool force) { + if (isBlocked(c.subPack)) + return block(c.subPack, constraint); + else if (isBlocked(c.superPack)) + return block(c.superPack, constraint); + unify(c.subPack, c.superPack, constraint->scope); return true; } @@ -512,6 +526,82 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull constraint, bool force) +{ + /* + * for .. in loops can play out in a bunch of different ways depending on + * the shape of iteratee. + * + * iteratee might be: + * * (nextFn) + * * (nextFn, table) + * * (nextFn, table, firstIndex) + * * table with a metatable and __index + * * table with a metatable and __call but no __index (if the metatable has + * both, __index takes precedence) + * * table with an indexer but no __index or __call (or no metatable) + * + * To dispatch this constraint, we need first to know enough about iteratee + * to figure out which of the above shapes we are actually working with. + * + * If `force` is true and we still do not know, we must flag a warning. Type + * families are the fix for this. + * + * Since we need to know all of this stuff about the types of the iteratee, + * we have no choice but for ConstraintSolver to also be the thing that + * applies constraints to the types of the iterators. + */ + + auto block_ = [&](auto&& t) { + if (force) + { + // If we haven't figured out the type of the iteratee by now, + // there's nothing we can do. + return true; + } + + block(t, constraint); + return false; + }; + + auto [iteratorTypes, iteratorTail] = flatten(c.iterator); + if (iteratorTail) + return block_(*iteratorTail); + + if (0 == iteratorTypes.size()) + { + Anyification anyify{ + arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()}; + std::optional anyified = anyify.substitute(c.variables); + LUAU_ASSERT(anyified); + unify(*anyified, c.variables, constraint->scope); + + return true; + } + + TypeId nextTy = follow(iteratorTypes[0]); + if (get(nextTy)) + return block_(nextTy); + + if (get(nextTy)) + { + TypeId tableTy = getSingletonTypes().nilType; + if (iteratorTypes.size() >= 2) + tableTy = iteratorTypes[1]; + + TypeId firstIndexTy = getSingletonTypes().nilType; + if (iteratorTypes.size() >= 3) + firstIndexTy = iteratorTypes[2]; + + return tryDispatchIterableFunction(nextTy, tableTy, firstIndexTy, c, constraint, force); + } + + else + return tryDispatchIterableTable(iteratorTypes[0], c, constraint, force); + + return true; +} + bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull constraint) { if (isBlocked(c.namedType)) @@ -519,7 +609,7 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNullpersistent) + if (target->persistent || target->owningArena != arena) return true; if (TableTypeVar* ttv = getMutable(target)) @@ -536,19 +626,27 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor { ConstraintSolver* solver; const InstantiationSignature& signature; + NotNull scope; bool foundInfiniteType = false; - explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature) + explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) : solver(solver) , signature(signature) + , scope(scope) { } bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override { - auto [typeArguments, packArguments] = saturateArguments(petv.fn, petv.typeArguments, petv.packArguments, solver->arena); + std::optional tf = + (petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value); - if (follow(petv.fn.type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) + if (!tf.has_value()) + return true; + + auto [typeArguments, packArguments] = saturateArguments(*tf, petv.typeArguments, petv.packArguments, solver->arena); + + if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) { foundInfiniteType = true; return false; @@ -563,17 +661,19 @@ struct InstantiationQueuer : TypeVarOnceVisitor ConstraintSolver* solver; const InstantiationSignature& signature; NotNull scope; + Location location; - explicit InstantiationQueuer(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) + explicit InstantiationQueuer(NotNull scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature) : solver(solver) , signature(signature) , scope(scope) + , location(location) { } bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override { - solver->pushConstraint(TypeAliasExpansionConstraint{ty}, scope); + solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty}); return false; } }; @@ -592,23 +692,32 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul unblock(c.target); }; - // If there are no parameters to the type function we can just use the type - // directly. - if (petv->fn.typeParams.empty() && petv->fn.typePackParams.empty()) + std::optional tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value) + : constraint->scope->lookupType(petv->name.value); + + if (!tf.has_value()) { - bindResult(petv->fn.type); + reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location); + bindResult(getSingletonTypes().errorRecoveryType()); return true; } - auto [typeArguments, packArguments] = saturateArguments(petv->fn, petv->typeArguments, petv->packArguments, arena); + // If there are no parameters to the type function we can just use the type + // directly. + if (tf->typeParams.empty() && tf->typePackParams.empty()) + { + bindResult(tf->type); + return true; + } - bool sameTypes = - std::equal(typeArguments.begin(), typeArguments.end(), petv->fn.typeParams.begin(), petv->fn.typeParams.end(), [](auto&& itp, auto&& p) { - return itp == p.ty; - }); + auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena); - bool samePacks = std::equal( - packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.typePackParams.end(), [](auto&& itp, auto&& p) { + bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) { + return itp == p.ty; + }); + + bool samePacks = + std::equal(packArguments.begin(), packArguments.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itp, auto&& p) { return itp == p.tp; }); @@ -617,12 +726,12 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // to the TypeFun's type. if (sameTypes && samePacks) { - bindResult(petv->fn.type); + bindResult(tf->type); return true; } InstantiationSignature signature{ - petv->fn, + *tf, typeArguments, packArguments, }; @@ -642,8 +751,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // https://github.com/Roblox/luau/pull/68 for the RFC responsible for this. // This is a little nicer than using a recursion limit because we can catch // the infinite expansion before actually trying to expand it. - InfiniteTypeFinder itf{this, signature}; - itf.traverse(petv->fn.type); + InfiniteTypeFinder itf{this, signature, constraint->scope}; + itf.traverse(tf->type); if (itf.foundInfiniteType) { @@ -655,15 +764,15 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul ApplyTypeFunction applyTypeFunction{arena}; for (size_t i = 0; i < typeArguments.size(); ++i) { - applyTypeFunction.typeArguments[petv->fn.typeParams[i].ty] = typeArguments[i]; + applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeArguments[i]; } for (size_t i = 0; i < packArguments.size(); ++i) { - applyTypeFunction.typePackArguments[petv->fn.typePackParams[i].tp] = packArguments[i]; + applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = packArguments[i]; } - std::optional maybeInstantiated = applyTypeFunction.substitute(petv->fn.type); + std::optional maybeInstantiated = applyTypeFunction.substitute(tf->type); // Note that ApplyTypeFunction::encounteredForwardedType is never set in // DCR, because we do not use free types for forward-declared generic // aliases. @@ -683,7 +792,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // Type function application will happily give us the exact same type if // there are e.g. generic saturatedTypeArguments that go unused. - bool needsClone = follow(petv->fn.type) == target; + bool needsClone = follow(tf->type) == target; // Only tables have the properties we're trying to set. TableTypeVar* ttv = getMutableTableType(target); @@ -722,7 +831,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // The application is not recursive, so we need to queue up application of // any child type function instantiations within the result in order for it // to be complete. - InstantiationQueuer queuer{this, signature, constraint->scope}; + InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature}; queuer.traverse(target); instantiatedAliases[signature] = target; @@ -730,6 +839,152 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul return true; } +bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull constraint) +{ + TypeId fn = follow(c.fn); + TypePackId result = follow(c.result); + + if (isBlocked(c.fn)) + { + return block(c.fn, constraint); + } + + const FunctionTypeVar* ftv = get(fn); + bool usedMagic = false; + + if (ftv && ftv->dcrMagicFunction != nullptr) + { + usedMagic = ftv->dcrMagicFunction(NotNull(this), result, c.astFragment); + } + + if (!usedMagic) + { + for (const auto& inner : c.innerConstraints) + { + unsolvedConstraints.push_back(inner); + } + + asMutable(c.result)->ty.emplace(constraint->scope); + } + + unblock(c.result); + + return true; +} + +bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force) +{ + auto block_ = [&](auto&& t) { + if (force) + { + // TODO: I believe it is the case that, if we are asked to force + // this constraint, then we can do nothing but fail. I'd like to + // find a code sample that gets here. + LUAU_ASSERT(0); + } + else + block(t, constraint); + return false; + }; + + // We may have to block here if we don't know what the iteratee type is, + // if it's a free table, if we don't know it has a metatable, and so on. + iteratorTy = follow(iteratorTy); + if (get(iteratorTy)) + return block_(iteratorTy); + + auto anyify = [&](auto ty) { + Anyification anyify{arena, constraint->scope, &iceReporter, getSingletonTypes().anyType, getSingletonTypes().anyTypePack}; + std::optional anyified = anyify.substitute(ty); + if (!anyified) + reportError(CodeTooComplex{}, constraint->location); + else + unify(*anyified, ty, constraint->scope); + }; + + auto errorify = [&](auto ty) { + Anyification anyify{ + arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()}; + std::optional errorified = anyify.substitute(ty); + if (!errorified) + reportError(CodeTooComplex{}, constraint->location); + else + unify(*errorified, ty, constraint->scope); + }; + + if (get(iteratorTy)) + { + anyify(c.variables); + return true; + } + + if (get(iteratorTy)) + { + errorify(c.variables); + return true; + } + + // Irksome: I don't think we have any way to guarantee that this table + // type never has a metatable. + + if (auto iteratorTable = get(iteratorTy)) + { + if (iteratorTable->state == TableState::Free) + return block_(iteratorTy); + + if (iteratorTable->indexer) + { + TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType}); + unify(c.variables, expectedVariablePack, constraint->scope); + } + else + errorify(c.variables); + } + else if (auto iteratorMetatable = get(iteratorTy)) + { + TypeId metaTy = follow(iteratorMetatable->metatable); + if (get(metaTy)) + return block_(metaTy); + + LUAU_ASSERT(0); + } + else + errorify(c.variables); + + return true; +} + +bool ConstraintSolver::tryDispatchIterableFunction( + TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull constraint, bool force) +{ + // We need to know whether or not this type is nil or not. + // If we don't know, block and reschedule ourselves. + firstIndexTy = follow(firstIndexTy); + if (get(firstIndexTy)) + { + if (force) + LUAU_ASSERT(0); + else + block(firstIndexTy, constraint); + return false; + } + + const TypeId firstIndex = isNil(firstIndexTy) ? arena->freshType(constraint->scope) // FIXME: Surely this should be a union (free | nil) + : firstIndexTy; + + // nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...) + const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, getSingletonTypes().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(nextTy, expectedNextTy, constraint->scope); + + pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack}); + + return true; +} + void ConstraintSolver::block_(BlockedConstraintId target, NotNull constraint) { blocked[target].push_back(constraint); @@ -741,14 +996,14 @@ void ConstraintSolver::block_(BlockedConstraintId target, NotNull target, NotNull constraint) { if (FFlag::DebugLuauLogSolver) - printf("block Constraint %s on\t%s\n", toString(*target).c_str(), toString(*constraint).c_str()); + printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str()); block_(target, constraint); } bool ConstraintSolver::block(TypeId target, NotNull constraint) { if (FFlag::DebugLuauLogSolver) - printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str()); + printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); block_(target, constraint); return false; } @@ -756,7 +1011,7 @@ bool ConstraintSolver::block(TypeId target, NotNull constraint bool ConstraintSolver::block(TypePackId target, NotNull constraint) { if (FFlag::DebugLuauLogSolver) - printf("block TypeId %s on\t%s\n", toString(target).c_str(), toString(*constraint).c_str()); + printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str()); block_(target, constraint); return false; } @@ -772,7 +1027,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed) { auto& count = blockedConstraints[unblockedConstraint]; if (FFlag::DebugLuauLogSolver) - printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint).c_str()); + printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint, opts).c_str()); // This assertion being hit indicates that `blocked` and // `blockedConstraints` desynchronized at some point. This is problematic @@ -817,6 +1072,11 @@ bool ConstraintSolver::isBlocked(TypeId ty) return nullptr != get(follow(ty)) || nullptr != get(follow(ty)); } +bool ConstraintSolver::isBlocked(TypePackId tp) +{ + return nullptr != get(follow(tp)); +} + bool ConstraintSolver::isBlocked(NotNull constraint) { auto blockedIt = blockedConstraints.find(constraint); @@ -830,6 +1090,13 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull sc u.tryUnify(subType, superType); + if (!u.errors.empty()) + { + TypeId errorType = getSingletonTypes().errorRecoveryType(); + u.tryUnify(subType, errorType); + u.tryUnify(superType, errorType); + } + const auto [changedTypes, changedPacks] = u.log.getChanges(); u.log.commit(); @@ -853,22 +1120,69 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull scope) +void ConstraintSolver::pushConstraint(NotNull scope, const Location& location, ConstraintV cv) { - std::unique_ptr c = std::make_unique(std::move(cv), scope); + std::unique_ptr c = std::make_unique(scope, location, std::move(cv)); NotNull borrow = NotNull(c.get()); solverConstraints.push_back(std::move(c)); unsolvedConstraints.push_back(borrow); } +TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location) +{ + if (info.name.empty()) + { + reportError(UnknownRequire{}, location); + return getSingletonTypes().errorRecoveryType(); + } + + std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name); + + for (const auto& [location, path] : requireCycles) + { + if (!path.empty() && path.front() == humanReadableName) + return getSingletonTypes().anyType; + } + + ModulePtr module = moduleResolver->getModule(info.name); + if (!module) + { + if (!moduleResolver->moduleExists(info.name) && !info.optional) + reportError(UnknownRequire{humanReadableName}, location); + + return getSingletonTypes().errorRecoveryType(); + } + + if (module->type != SourceCode::Type::Module) + { + reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location); + return getSingletonTypes().errorRecoveryType(); + } + + TypePackId modulePack = module->getModuleScope()->returnType; + if (get(modulePack)) + return getSingletonTypes().errorRecoveryType(); + + std::optional moduleType = first(modulePack); + if (!moduleType) + { + reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location); + return getSingletonTypes().errorRecoveryType(); + } + + return *moduleType; +} + void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location) { errors.emplace_back(location, std::move(data)); + errors.back().moduleName = currentModuleName; } void ConstraintSolver::reportError(TypeError e) { errors.emplace_back(std::move(e)); + errors.back().moduleName = currentModuleName; } } // namespace Luau diff --git a/Analysis/src/ConstraintSolverLogger.cpp b/Analysis/src/ConstraintSolverLogger.cpp index 097ceeec..5ba40521 100644 --- a/Analysis/src/ConstraintSolverLogger.cpp +++ b/Analysis/src/ConstraintSolverLogger.cpp @@ -3,6 +3,7 @@ #include "Luau/ConstraintSolverLogger.h" #include "Luau/JsonEmitter.h" +#include "Luau/ToString.h" LUAU_FASTFLAG(LuauFixNameMaps); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index c8c5d4b5..d8839f2f 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -14,6 +14,7 @@ #include "Luau/TypeChecker2.h" #include "Luau/TypeInfer.h" #include "Luau/Variant.h" +#include "Luau/BuiltinDefinitions.h" #include #include @@ -99,7 +100,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c module.root = parseResult.root; module.mode = Mode::Definition; - ModulePtr checkedModule = check(module, Mode::Definition, globalScope); + ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {}); if (checkedModule->errors.size() > 0) return LoadDefinitionFileResult{false, parseResult, checkedModule}; @@ -526,7 +527,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional requireCycles) { ModulePtr result = std::make_shared(); - ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()}; + ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), NotNull(&iceHandler), getGlobalScope()}; cgb.visit(sourceModule.root); result->errors = std::move(cgb.errors); - ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)}; + ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles}; cs.run(); for (TypeError& e : cs.errors) @@ -852,11 +853,12 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes); result->astResolvedTypes = std::move(cgb.astResolvedTypes); result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks); - - result->clonePublicInterface(iceHandler); + result->type = sourceModule.type; Luau::check(sourceModule, result.get()); + result->clonePublicInterface(iceHandler); + return result; } diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index e98ab185..2d1d62f3 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -46,6 +46,7 @@ TypeId Instantiation::clean(TypeId ty) FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; clone.magicFunction = ftv->magicFunction; + clone.dcrMagicFunction = ftv->dcrMagicFunction; clone.tags = ftv->tags; clone.argNames = ftv->argNames; TypeId result = addType(std::move(clone)); diff --git a/Analysis/src/JsonEmitter.cpp b/Analysis/src/JsonEmitter.cpp index e99619ba..9c8a7af9 100644 --- a/Analysis/src/JsonEmitter.cpp +++ b/Analysis/src/JsonEmitter.cpp @@ -11,7 +11,8 @@ namespace Luau::Json static constexpr int CHUNK_SIZE = 1024; ObjectEmitter::ObjectEmitter(NotNull emitter) - : emitter(emitter), finished(false) + : emitter(emitter) + , finished(false) { comma = emitter->pushComma(); emitter->writeRaw('{'); @@ -33,7 +34,8 @@ void ObjectEmitter::finish() } ArrayEmitter::ArrayEmitter(NotNull emitter) - : emitter(emitter), finished(false) + : emitter(emitter) + , finished(false) { comma = emitter->pushComma(); emitter->writeRaw('['); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 426ff9d6..669739a0 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -216,7 +216,8 @@ static bool similar(AstExpr* lhs, AstExpr* rhs) return false; for (size_t i = 0; i < le->strings.size; ++i) - if (le->strings.data[i].size != re->strings.data[i].size || memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0) + if (le->strings.data[i].size != re->strings.data[i].size || + memcmp(le->strings.data[i].data, re->strings.data[i].data, le->strings.data[i].size) != 0) return false; for (size_t i = 0; i < le->expressions.size; ++i) @@ -2675,13 +2676,18 @@ public: private: LintContext* context; - bool isComparison(AstExprBinary::Op op) + static bool isEquality(AstExprBinary::Op op) + { + return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq; + } + + static bool isComparison(AstExprBinary::Op op) { return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq || op == AstExprBinary::CompareLt || op == AstExprBinary::CompareLe || op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe; } - bool isNot(AstExpr* node) + static bool isNot(AstExpr* node) { AstExprUnary* expr = node->as(); @@ -2698,22 +2704,26 @@ private: { std::string op = toString(node->op); - if (node->op == AstExprBinary::CompareEq || node->op == AstExprBinary::CompareNe) + if (isEquality(node->op)) emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or wrap one of the expressions in parentheses to silence", - op.c_str(), op.c_str(), node->op == AstExprBinary::CompareEq ? "~=" : "=="); + "not X %s Y is equivalent to (not X) %s Y; consider using X %s Y, or add parentheses to silence", op.c_str(), op.c_str(), + node->op == AstExprBinary::CompareEq ? "~=" : "=="); else emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "not X %s Y is equivalent to (not X) %s Y; wrap one of the expressions in parentheses to silence", op.c_str(), op.c_str()); + "not X %s Y is equivalent to (not X) %s Y; add parentheses to silence", op.c_str(), op.c_str()); } else if (AstExprBinary* left = node->left->as(); left && isComparison(left->op)) { std::string lop = toString(left->op); std::string rop = toString(node->op); - emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, - "X %s Y %s Z is equivalent to (X %s Y) %s Z; wrap one of the expressions in parentheses to silence", lop.c_str(), rop.c_str(), - lop.c_str(), rop.c_str()); + if (isEquality(left->op) || isEquality(node->op)) + emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, + "X %s Y %s Z is equivalent to (X %s Y) %s Z; add parentheses to silence", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str()); + else + emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, + "X %s Y %s Z is equivalent to (X %s Y) %s Z; did you mean X %s Y and Y %s Z?", lop.c_str(), rop.c_str(), lop.c_str(), rop.c_str(), + lop.c_str(), rop.c_str()); } return true; diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index de796c7a..4c9e9537 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -219,8 +219,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) TypePackId returnType = moduleScope->returnType; std::optional varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack; - std::unordered_map* exportedTypeBindings = - FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings; + std::unordered_map* exportedTypeBindings = &moduleScope->exportedTypeBindings; TxnLog log; ClonePublicInterface clonePublicInterface{&log, this}; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 0beeb58c..fa12f306 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -510,7 +510,7 @@ void Substitution::foundDirty(TypeId ty) ty = log->follow(ty); if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty)) - return; + return; if (isDirty(ty)) newTypes[ty] = follow(clean(ty)); @@ -523,7 +523,7 @@ void Substitution::foundDirty(TypePackId tp) tp = log->follow(tp); if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp)) - return; + return; if (isDirty(tp)) newPacks[tp] = follow(clean(tp)); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index b4ef4384..62b7afd5 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1036,6 +1036,13 @@ struct TypePackStringifier { stringify(btv.boundTo); } + + void operator()(TypePackId, const BlockedTypePack& btp) + { + state.emit("*blocked-tp-"); + state.emit(btp.index); + state.emit("*"); + } }; void TypeVarStringifier::stringify(TypePackId tp) @@ -1099,9 +1106,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) ToStringResult result; - StringifierState state = FFlag::LuauFixNameMaps - ? StringifierState{opts, result, opts.nameMap} - : StringifierState{opts, result, opts.DEPRECATED_nameMap}; + StringifierState state = + FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap}; std::set cycles; std::set cycleTPs; @@ -1211,9 +1217,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) * 4. Print out the root of the type using the same algorithm as step 3. */ ToStringResult result; - StringifierState state = FFlag::LuauFixNameMaps - ? StringifierState{opts, result, opts.nameMap} - : StringifierState{opts, result, opts.DEPRECATED_nameMap}; + StringifierState state = + FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap}; std::set cycles; std::set cycleTPs; @@ -1302,9 +1307,8 @@ std::string toString(const TypePackVar& tp, ToStringOptions& opts) std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts) { ToStringResult result; - StringifierState state = FFlag::LuauFixNameMaps - ? StringifierState{opts, result, opts.nameMap} - : StringifierState{opts, result, opts.DEPRECATED_nameMap}; + StringifierState state = + FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap}; TypeVarStringifier tvs{state}; state.emit(funcName); @@ -1437,8 +1441,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) using T = std::decay_t; // TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps - auto tos = [](auto&& a, ToStringOptions& opts) - { + auto tos = [](auto&& a, ToStringOptions& opts) { if (FFlag::LuauFixNameMaps) return toString(a, opts); else @@ -1488,6 +1491,13 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">"; } + else if constexpr (std::is_same_v) + { + std::string iteratorStr = tos(c.iterator, opts); + std::string variableStr = tos(c.variables, opts); + + return variableStr + " ~ Iterate<" + iteratorStr + ">"; + } else if constexpr (std::is_same_v) { std::string namedStr = tos(c.namedType, opts); @@ -1498,6 +1508,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) std::string targetStr = tos(c.target, opts); return "expand " + targetStr; } + else if constexpr (std::is_same_v) + { + return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }"; + } else static_assert(always_false_v, "Non-exhaustive constraint switch"); }; diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index 0c89d130..c7980ab0 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -31,6 +31,15 @@ TypeId TypeArena::freshType(TypeLevel level) return allocated; } +TypeId TypeArena::freshType(Scope* scope) +{ + TypeId allocated = typeVars.allocate(FreeTypeVar{scope}); + + asMutable(allocated)->owningArena = this; + + return allocated; +} + TypePackId TypeArena::addTypePack(std::initializer_list types) { TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); @@ -40,9 +49,9 @@ TypePackId TypeArena::addTypePack(std::initializer_list types) return allocated; } -TypePackId TypeArena::addTypePack(std::vector types) +TypePackId TypeArena::addTypePack(std::vector types, std::optional tail) { - TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); + TypePackId allocated = typePacks.allocate(TypePack{std::move(types), tail}); asMutable(allocated)->owningArena = this; diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index f21a4fa9..84494083 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -373,6 +373,11 @@ public: return Luau::visit(*this, btp.boundTo->ty); } + AstTypePack* operator()(const BlockedTypePack& btp) const + { + return allocator->alloc(Location(), AstName("*blocked*")); + } + AstTypePack* operator()(const TypePack& tp) const { AstArray head; diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index e5813cd2..480bdf40 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -166,7 +166,8 @@ struct TypeChecker2 auto pusher = pushStack(stat); if (0) - {} + { + } else if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) @@ -239,11 +240,9 @@ struct TypeChecker2 visit(repeatStatement->condition); } - void visit(AstStatBreak*) - {} + void visit(AstStatBreak*) {} - void visit(AstStatContinue*) - {} + void visit(AstStatContinue*) {} void visit(AstStatReturn* ret) { @@ -339,6 +338,50 @@ 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. + static 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(getSingletonTypes().errorRecoveryType()); + } + + return result; + } + void visit(AstStatForIn* forInStatement) { for (AstLocal* local : forInStatement->vars) @@ -351,6 +394,128 @@ struct TypeChecker2 visit(expr); visit(forInStatement->body); + + // Rule out crazy stuff. Maybe possible if the file is not syntactically valid. + if (!forInStatement->vars.size || !forInStatement->values.size) + return; + + NotNull scope = stack.back(); + TypeArena tempArena; + + std::vector variableTypes; + for (AstLocal* var : forInStatement->vars) + { + std::optional ty = scope->lookup(var); + LUAU_ASSERT(ty); + variableTypes.emplace_back(*ty); + } + + // ugh. There's nothing in the AST to hang a whole type pack on for the + // set of iteratees, so we have to piece it back together by hand. + std::vector valueTypes; + for (size_t i = 0; i < forInStatement->values.size - 1; ++i) + valueTypes.emplace_back(lookupType(forInStatement->values.data[i])); + TypePackId iteratorTail = lookupPack(forInStatement->values.data[forInStatement->values.size - 1]); + TypePackId iteratorPack = tempArena.addTypePack(valueTypes, iteratorTail); + + // ... and then expand it out to 3 values (if possible) + const std::vector iteratorTypes = flatten(tempArena, iteratorPack, 3); + if (iteratorTypes.empty()) + { + reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values)); + return; + } + TypeId iteratorTy = follow(iteratorTypes[0]); + + /* + * If the first iterator argument is a function + * * There must be 1 to 3 iterator arguments. Name them (nextTy, + * arrayTy, startIndexTy) + * * The return type of nextTy() must correspond to the variables' + * types and counts. HOWEVER the first iterator will never be nil. + * * The first return value of nextTy must be compatible with + * startIndexTy. + * * The first argument to nextTy() must be compatible with arrayTy if + * present. nil if not. + * * The second argument to nextTy() must be compatible with + * startIndexTy if it is present. Else, it must be compatible with + * nil. + * * nextTy() must be callable with only 2 arguments. + */ + 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(tempArena, 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); + + if (minCount > 2) + reportError(CountMismatch{2, minCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location); + if (maxCount && *maxCount < 2) + reportError(CountMismatch{2, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location); + + const std::vector flattenedArgTypes = flatten(tempArena, 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, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location); + else if (actualArgCount < minCount) + reportError(CountMismatch{2, actualArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location); + + if (iteratorTypes.size() >= 2 && flattenedArgTypes.size() > 0) + { + size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0; + reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[1], flattenedArgTypes[0])); + } + + if (iteratorTypes.size() == 3 && flattenedArgTypes.size() > 1) + { + size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0; + reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iteratorTypes[2], flattenedArgTypes[1])); + } + } + else if (const TableTypeVar* ttv = get(iteratorTy)) + { + if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer) + { + reportErrors(tryUnify(scope, forInStatement->vars.data[0]->location, variableTypes[0], ttv->indexer->indexType)); + if (variableTypes.size() == 2) + reportErrors(tryUnify(scope, forInStatement->vars.data[1]->location, variableTypes[1], ttv->indexer->indexResultType)); + } + else + reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location); + } + else if (get(iteratorTy) || get(iteratorTy)) + { + // nothing + } + else + { + reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location); + } } void visit(AstStatAssign* assign) @@ -456,7 +621,8 @@ struct TypeChecker2 auto StackPusher = pushStack(expr); if (0) - {} + { + } else if (auto e = expr->as()) return visit(e); else if (auto e = expr->as()) @@ -561,9 +727,21 @@ struct TypeChecker2 TypePackId expectedRetType = lookupPack(call); TypeId functionType = lookupType(call->func); - TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr); LUAU_ASSERT(functionType); + if (get(functionType) || get(functionType)) + return; + + // TODO: Lots of other types are callable: intersections of functions + // and things with the __call metamethod. + if (!get(functionType)) + { + reportError(CannotCallNonFunction{functionType}, call->func->location); + return; + } + + TypeId instantiatedFunctionType = follow(instantiation.substitute(functionType).value_or(nullptr)); + TypePack args; for (AstExpr* arg : call->args) { @@ -575,12 +753,11 @@ struct TypeChecker2 TypePackId argsTp = arena.addTypePack(args); FunctionTypeVar ftv{argsTp, expectedRetType}; TypeId expectedType = arena.addType(ftv); + if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice)) { - unfreeze(module->interfaceTypes); CloneState cloneState; - expectedType = clone(expectedType, module->interfaceTypes, cloneState); - freeze(module->interfaceTypes); + expectedType = clone(expectedType, module->internalTypes, cloneState); reportError(TypeMismatch{expectedType, functionType}, call->location); } } @@ -592,7 +769,8 @@ struct TypeChecker2 // leftType must have a property called indexName->index - std::optional ty = getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true); + std::optional ty = + getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true); if (ty) { if (!isSubtype(resultType, *ty, stack.back(), ice)) @@ -972,18 +1150,34 @@ struct TypeChecker2 } } - void reportError(TypeErrorData&& data, const Location& location) + template + ErrorVec tryUnify(NotNull scope, const Location& location, TID subTy, TID superTy) + { + UnifierSharedState sharedState{&ice}; + Unifier u{&module->internalTypes, Mode::Strict, scope, location, Covariant, sharedState}; + u.anyIsTop = true; + u.tryUnify(subTy, superTy); + + return std::move(u.errors); + } + + void reportError(TypeErrorData data, const Location& location) { module->errors.emplace_back(location, sourceModule->name, std::move(data)); } void reportError(TypeError e) { - module->errors.emplace_back(std::move(e)); + reportError(std::move(e.data), e.location); } - std::optional getIndexTypeFromType( - const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors) + void reportErrors(ErrorVec errors) + { + for (TypeError e : errors) + reportError(std::move(e)); + } + + std::optional getIndexTypeFromType(const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors) { return Luau::getIndexTypeFromType(scope, module->errors, &module->internalTypes, type, prop, location, addErrors, ice); } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index c9340945..2bda2804 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) -LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false) LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) @@ -45,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false) LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false) +LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false) namespace Luau { @@ -2343,7 +2343,7 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp { if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end()) expectedResultTypes.push_back(prop->second.type); - else if (FFlag::LuauExpectedTableUnionIndexerType && ttv->indexer && maybeString(ttv->indexer->indexType)) + else if (ttv->indexer && maybeString(ttv->indexer->indexType)) expectedResultTypes.push_back(ttv->indexer->indexResultType); } } @@ -2498,6 +2498,12 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op) TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes) { + if (FFlag::LuauUnionOfTypesFollow) + { + a = follow(a); + b = follow(b); + } + if (unifyFreeTypes && (get(a) || get(b))) { if (unify(b, a, scope, location)) @@ -3659,33 +3665,6 @@ WithPredicate TypeChecker::checkExprPackHelper(const ScopePtr& scope } } -// Returns the minimum number of arguments the argument list can accept. -static size_t getMinParameterCount(TxnLog* log, TypePackId tp) -{ - size_t minCount = 0; - size_t optionalCount = 0; - - auto it = begin(tp, log); - auto endIter = end(tp); - - while (it != endIter) - { - TypeId ty = *it; - if (isOptional(ty)) - ++optionalCount; - else - { - minCount += optionalCount; - optionalCount = 0; - minCount++; - } - - ++it; - } - - return minCount; -} - void TypeChecker::checkArgumentList( const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector& argLocations) { @@ -3705,7 +3684,7 @@ void TypeChecker::checkArgumentList( if (!argLocations.empty()) location = {state.location.begin, argLocations.back().end}; - size_t minParams = getMinParameterCount(&state.log, paramPack); + size_t minParams = getParameterExtents(&state.log, paramPack).first; state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}}); }; @@ -3804,7 +3783,7 @@ void TypeChecker::checkArgumentList( } // ok else { - size_t minParams = getMinParameterCount(&state.log, paramPack); + size_t minParams = getParameterExtents(&state.log, paramPack).first; std::optional tail = flatten(paramPack, state.log).second; bool isVariadic = tail && Luau::isVariadic(*tail); diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index d4544483..2fa9413a 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -8,6 +8,13 @@ namespace Luau { +BlockedTypePack::BlockedTypePack() + : index(++nextIndex) +{ +} + +size_t BlockedTypePack::nextIndex = 0; + TypePackVar::TypePackVar(const TypePackVariant& tp) : ty(tp) { diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 60bca0a3..56fccecc 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -84,9 +84,8 @@ std::optional findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t return std::nullopt; } -std::optional getIndexTypeFromType( - const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors, - InternalErrorReporter& handle) +std::optional getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, + const Location& location, bool addErrors, InternalErrorReporter& handle) { type = follow(type); @@ -190,4 +189,33 @@ std::optional getIndexTypeFromType( return std::nullopt; } +std::pair> getParameterExtents(const TxnLog* log, TypePackId tp) +{ + size_t minCount = 0; + size_t optionalCount = 0; + + auto it = begin(tp, log); + auto endIter = end(tp); + + while (it != endIter) + { + TypeId ty = *it; + if (isOptional(ty)) + ++optionalCount; + else + { + minCount += optionalCount; + optionalCount = 0; + minCount++; + } + + ++it; + } + + if (it.tail()) + return {minCount, std::nullopt}; + else + return {minCount, minCount + optionalCount}; +} + } // namespace Luau diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 8974f8c7..4abee0f6 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -24,9 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauUnknownAndNeverType) -LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false) LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) -LUAU_FASTFLAGVARIABLE(LuauDeduceFindMatchReturnTypes, false) LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false) namespace Luau @@ -446,8 +444,10 @@ BlockedTypeVar::BlockedTypeVar() int BlockedTypeVar::nextIndex = 0; -PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector typeArguments, std::vector packArguments) - : fn(fn) +PendingExpansionTypeVar::PendingExpansionTypeVar( + std::optional prefix, AstName name, std::vector typeArguments, std::vector packArguments) + : prefix(prefix) + , name(name) , typeArguments(typeArguments) , packArguments(packArguments) , index(++nextIndex) @@ -787,8 +787,8 @@ TypeId SingletonTypes::makeStringMetatable() makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})}); attachMagicFunction(gmatchFunc, magicFunctionGmatch); - const TypeId matchFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), - arena->addTypePack(TypePackVar{VariadicTypePack{FFlag::LuauDeduceFindMatchReturnTypes ? stringType : optionalString}})}); + const TypeId matchFunc = arena->addType( + FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})}); attachMagicFunction(matchFunc, magicFunctionMatch); const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), @@ -1221,9 +1221,6 @@ static std::vector parsePatternString(TypeChecker& typechecker, const ch static std::optional> magicFunctionGmatch( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { - if (!FFlag::LuauDeduceGmatchReturnTypes) - return std::nullopt; - auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); @@ -1256,9 +1253,6 @@ static std::optional> magicFunctionGmatch( static std::optional> magicFunctionMatch( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { - if (!FFlag::LuauDeduceFindMatchReturnTypes) - return std::nullopt; - auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); @@ -1295,9 +1289,6 @@ static std::optional> magicFunctionMatch( static std::optional> magicFunctionFind( TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate withPredicate) { - if (!FFlag::LuauDeduceFindMatchReturnTypes) - return std::nullopt; - auto [paramPack, _predicates] = withPredicate; const auto& [params, tail] = flatten(paramPack); diff --git a/Analysis/src/TypedAllocator.cpp b/Analysis/src/TypedAllocator.cpp index 9ce8c3dc..c95c8eae 100644 --- a/Analysis/src/TypedAllocator.cpp +++ b/Analysis/src/TypedAllocator.cpp @@ -36,7 +36,9 @@ void* pagedAllocate(size_t size) { // By default we use operator new/delete instead of malloc/free so that they can be overridden externally if (!FFlag::DebugLuauFreezeArena) + { return ::operator new(size, std::nothrow); + } // On Windows, VirtualAlloc results in 64K granularity allocations; we allocate in chunks of ~32K so aligned_malloc is a little more efficient // On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit. diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index 63d8647d..fa76e820 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -13,7 +13,8 @@ Free::Free(TypeLevel level) } Free::Free(Scope* scope) - : scope(scope) + : index(++nextIndex) + , scope(scope) { } diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index b5f58c83..b135cd0c 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -317,7 +317,8 @@ static std::optional> getTableMat return std::nullopt; } -Unifier::Unifier(TypeArena* types, Mode mode, NotNull scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) +Unifier::Unifier(TypeArena* types, Mode mode, NotNull scope, const Location& location, Variance variance, UnifierSharedState& sharedState, + TxnLog* parentLog) : types(types) , mode(mode) , scope(scope) @@ -492,9 +493,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool if (log.get(subTy)) tryUnifyWithConstrainedSubTypeVar(subTy, superTy); - else if (const UnionTypeVar* uv = log.getMutable(subTy)) + else if (const UnionTypeVar* subUnion = log.getMutable(subTy)) { - tryUnifyUnionWithType(subTy, uv, superTy); + tryUnifyUnionWithType(subTy, subUnion, superTy); } else if (const UnionTypeVar* uv = log.getMutable(superTy)) { @@ -555,14 +556,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool log.popSeen(superTy, subTy); } -void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId superTy) +void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion, TypeId superTy) { // A | B <: T if A <: T and B <: T bool failed = false; std::optional unificationTooComplex; std::optional firstFailedOption; - for (TypeId type : uv->options) + for (TypeId type : subUnion->options) { Unifier innerState = makeChildUnifier(); innerState.tryUnify_(type, superTy); @@ -608,9 +609,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId } }; - if (auto utv = log.getMutable(superTy)) + if (auto superUnion = log.getMutable(superTy)) { - for (TypeId ty : utv) + for (TypeId ty : superUnion) tryBind(ty); } else diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 612283fb..07051163 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -1276,6 +1276,16 @@ public: }; AstName getIdentifier(AstExpr*); +Location getLocation(const AstTypeList& typeList); + +template // AstNode, AstExpr, AstLocal, etc +Location getLocation(AstArray array) +{ + if (0 == array.size) + return {}; + + return Location{array.data[0]->location.begin, array.data[array.size - 1]->location.end}; +} #undef LUAU_RTTI diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 956fcf64..848d7117 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -304,6 +304,12 @@ private: AstExprError* reportExprError(const Location& location, const AstArray& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5); AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray& types, bool isMissing, const char* format, ...) LUAU_PRINTF_ATTR(5, 6); + // `parseErrorLocation` is associated with the parser error + // `astErrorLocation` is associated with the AstTypeError created + // It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely + // define the location (possibly of zero size) where a type annotation is expected. + AstTypeError* reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...) + LUAU_PRINTF_ATTR(4, 5); AstExpr* reportFunctionArgsError(AstExpr* func, bool self); void reportAmbiguousCallError(); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 8291a5b1..cbed8bae 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -952,4 +952,17 @@ AstName getIdentifier(AstExpr* node) return AstName(); } +Location getLocation(const AstTypeList& typeList) +{ + Location result; + if (typeList.types.size) + { + result = Location{typeList.types.data[0]->location, typeList.types.data[typeList.types.size - 1]->location}; + } + if (typeList.tailType) + result.end = typeList.tailType->location.end; + + return result; +} + } // namespace Luau diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index b4db8bdf..d93f2ccb 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -91,18 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz , length(unsigned(size)) , data(data) { - LUAU_ASSERT( - type == RawString - || type == QuotedString - || type == InterpStringBegin - || type == InterpStringMid - || type == InterpStringEnd - || type == InterpStringSimple - || type == BrokenInterpDoubleBrace - || type == Number - || type == Comment - || type == BlockComment - ); + LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd || + type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment); } Lexeme::Lexeme(const Location& location, Type type, const char* name) @@ -644,7 +634,8 @@ Lexeme Lexer::readInterpolatedStringSection(Position start, Lexeme::Type formatT if (peekch(1) == '{') { - Lexeme brokenDoubleBrace = Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset); + Lexeme brokenDoubleBrace = + Lexeme(Location(start, position()), Lexeme::BrokenInterpDoubleBrace, &buffer[startOffset], offset - startOffset); consume(); consume(); return brokenDoubleBrace; diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index b6de27da..0914054f 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false) +LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false) bool lua_telemetry_parsed_out_of_range_bin_integer = false; bool lua_telemetry_parsed_out_of_range_hex_integer = false; @@ -1564,44 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) { incrementRecursionCounter("type annotation"); - Location begin = lexer.current().location; + Location start = lexer.current().location; if (lexer.current().type == Lexeme::ReservedNil) { nextLexeme(); - return {allocator.alloc(begin, std::nullopt, nameNil), {}}; + return {allocator.alloc(start, std::nullopt, nameNil), {}}; } else if (lexer.current().type == Lexeme::ReservedTrue) { nextLexeme(); - return {allocator.alloc(begin, true)}; + return {allocator.alloc(start, true)}; } else if (lexer.current().type == Lexeme::ReservedFalse) { nextLexeme(); - return {allocator.alloc(begin, false)}; + return {allocator.alloc(start, false)}; } else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString) { if (std::optional> value = parseCharArray()) { AstArray svalue = *value; - return {allocator.alloc(begin, svalue)}; + return {allocator.alloc(start, svalue)}; } else - return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")}; + return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")}; } else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple) { parseInterpString(); - return {reportTypeAnnotationError(begin, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")}; + return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")}; } else if (lexer.current().type == Lexeme::BrokenString) { - Location location = lexer.current().location; nextLexeme(); - return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")}; + return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")}; } else if (lexer.current().type == Lexeme::Name) { @@ -1632,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) expectMatchAndConsume(')', typeofBegin); - return {allocator.alloc(Location(begin, end), expr), {}}; + return {allocator.alloc(Location(start, end), expr), {}}; } bool hasParameters = false; @@ -1646,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) Location end = lexer.previousLocation(); - return {allocator.alloc(Location(begin, end), prefix, name.name, hasParameters, parameters), {}}; + return {allocator.alloc(Location(start, end), prefix, name.name, hasParameters, parameters), {}}; } else if (lexer.current().type == '{') { @@ -1658,23 +1658,35 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack) } else if (lexer.current().type == Lexeme::ReservedFunction) { - Location location = lexer.current().location; - nextLexeme(); - return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, + return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> " "...any'"), {}}; } else { - Location location = lexer.current().location; + if (FFlag::LuauTypeAnnotationLocationChange) + { + // For a missing type annotation, capture 'space' between last token and the next one + Location astErrorlocation(lexer.previousLocation().end, start.begin); + // The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message). + // Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau. + Location parseErrorLocation(lexer.previousLocation().end, start.end); + return { + reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()), + {}}; + } + else + { + Location location = lexer.current().location; - // For a missing type annotation, capture 'space' between last token and the next one - location = Location(lexer.previousLocation().end, lexer.current().location.begin); + // For a missing type annotation, capture 'space' between last token and the next one + location = Location(lexer.previousLocation().end, lexer.current().location.begin); - return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}}; + return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}}; + } } } @@ -2245,7 +2257,8 @@ AstExpr* Parser::parseSimpleExpr() { return parseNumber(); } - else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple)) + else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || + (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple)) { return parseString(); } @@ -2653,7 +2666,8 @@ AstArray Parser::parseTypeParams() std::optional> Parser::parseCharArray() { - LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::InterpStringSimple); + LUAU_ASSERT(lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::RawString || + lexer.current().type == Lexeme::InterpStringSimple); scratchData.assign(lexer.current().data, lexer.current().length); @@ -2691,14 +2705,11 @@ AstExpr* Parser::parseInterpString() Location startLocation = lexer.current().location; - do { + do + { Lexeme currentLexeme = lexer.current(); - LUAU_ASSERT( - currentLexeme.type == Lexeme::InterpStringBegin - || currentLexeme.type == Lexeme::InterpStringMid - || currentLexeme.type == Lexeme::InterpStringEnd - || currentLexeme.type == Lexeme::InterpStringSimple - ); + LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid || + currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple); Location location = currentLexeme.location; @@ -2973,8 +2984,7 @@ bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begi { // If the token matches on a different line and a different column, it suggests misleading indentation // This can be used to pinpoint the problem location for a possible future *actual* mismatch - if (lexer.current().location.begin.line != begin.position.line && - lexer.current().location.begin.column != begin.position.column && + if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column && endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects { endMismatchSuspect = begin; @@ -3108,6 +3118,13 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray& types, bool isMissing, const char* format, ...) { + if (FFlag::LuauTypeAnnotationLocationChange) + { + // Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled + // Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true. + LUAU_ASSERT(!isMissing); + } + va_list args; va_start(args, format); report(location, format, args); @@ -3116,6 +3133,18 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const return allocator.alloc(location, types, isMissing, unsigned(parseErrors.size() - 1)); } +AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...) +{ + LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange); + + va_list args; + va_start(args, format); + report(parseErrorLocation, format, args); + va_end(args); + + return allocator.alloc(astErrorLocation, AstArray{}, true, unsigned(parseErrors.size() - 1)); +} + void Parser::nextLexeme() { Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index d6660f05..decde93f 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -414,7 +414,7 @@ enum LuauBytecodeTag // Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled LBC_VERSION_MIN = 2, LBC_VERSION_MAX = 3, - LBC_VERSION_TARGET = 2, + LBC_VERSION_TARGET = 3, // Types of constant table entries LBC_CONSTANT_NIL = 0, LBC_CONSTANT_BOOLEAN, diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index 809c78da..ce47cd9a 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -13,7 +13,8 @@ inline bool isFlagExperimental(const char* flag) static const char* kList[] = { "LuauLowerBoundsCalculation", "LuauInterpolatedStringBaseSupport", - nullptr, // makes sure we always have at least one entry + // makes sure we always have at least one entry + nullptr, }; for (const char* item : kList) diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 46ab2648..713d08cd 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -6,8 +6,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false) - namespace Luau { @@ -1079,9 +1077,6 @@ std::string BytecodeBuilder::getError(const std::string& message) uint8_t BytecodeBuilder::getVersion() { - if (FFlag::LuauCompileBytecodeV3) - return 3; - // This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags return LBC_VERSION_TARGET; } diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 4429e4cc..d44daf0c 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -25,14 +25,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false) - LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport) -LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false) - -LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false) - namespace Luau { @@ -406,47 +400,29 @@ struct Compiler } } - void compileExprFastcallN(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1) + void compileExprFastcallN( + AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop, bool multRet, uint8_t regs, int bfid, int bfK = -1) { LUAU_ASSERT(!expr->self); LUAU_ASSERT(expr->args.size >= 1); LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3)); LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0); - LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2; - - if (FFlag::LuauCompileExtractK) - { - opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2; - } + LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2; uint32_t args[3] = {}; for (size_t i = 0; i < expr->args.size; ++i) { - if (FFlag::LuauCompileExtractK) + if (i > 0 && opc == LOP_FASTCALL2K) { - if (i > 0 && opc == LOP_FASTCALL2K) - { - int32_t cid = getConstantIndex(expr->args.data[i]); - if (cid < 0) - CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); + int32_t cid = getConstantIndex(expr->args.data[i]); + if (cid < 0) + CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); - args[i] = cid; - continue; // TODO: remove this and change if below to else if - } + args[i] = cid; } - else if (i > 0) - { - if (int32_t cid = getConstantIndex(expr->args.data[i]); cid >= 0) - { - opc = LOP_FASTCALL2K; - args[i] = cid; - break; - } - } - - if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0) + else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0) { args[i] = uint8_t(reg); } @@ -468,24 +444,10 @@ struct Compiler // these FASTCALL variants. for (size_t i = 0; i < expr->args.size; ++i) { - if (FFlag::LuauCompileExtractK) - { - if (i > 0 && opc == LOP_FASTCALL2K) - emitLoadK(uint8_t(regs + 1 + i), args[i]); - else if (args[i] != regs + 1 + i) - bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0); - } - else - { - if (i > 0 && opc == LOP_FASTCALL2K) - { - emitLoadK(uint8_t(regs + 1 + i), args[i]); - break; - } - - if (args[i] != regs + 1 + i) - bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0); - } + if (i > 0 && opc == LOP_FASTCALL2K) + emitLoadK(uint8_t(regs + 1 + i), args[i]); + else if (args[i] != regs + 1 + i) + bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0); } // note, these instructions are normally not executed and are used as a fallback for FASTCALL @@ -758,7 +720,7 @@ struct Compiler } // Optimization: for bit32.extract with constant in-range f/w we compile using FASTCALL2K and a special builtin - if (FFlag::LuauCompileExtractK && bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2])) + if (bfid == LBF_BIT32_EXTRACT && expr->args.size == 3 && isConstant(expr->args.data[1]) && isConstant(expr->args.data[2])) { Constant fc = getConstant(expr->args.data[1]); Constant wc = getConstant(expr->args.data[2]); @@ -1080,102 +1042,64 @@ struct Compiler std::swap(left, right); } - if (FFlag::LuauCompileXEQ) + uint8_t rl = compileExprAuto(left, rs); + + if (isEq && operandIsConstant) { - uint8_t rl = compileExprAuto(left, rs); + const Constant* cv = constants.find(right); + LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown); - if (isEq && operandIsConstant) + LuauOpcode opc = LOP_NOP; + int32_t cid = -1; + uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0; + + switch (cv->type) { - const Constant* cv = constants.find(right); - LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown); + case Constant::Type_Nil: + opc = LOP_JUMPXEQKNIL; + cid = 0; + break; - LuauOpcode opc = LOP_NOP; - int32_t cid = -1; - uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0; + case Constant::Type_Boolean: + opc = LOP_JUMPXEQKB; + cid = cv->valueBoolean; + break; - switch (cv->type) - { - case Constant::Type_Nil: - opc = LOP_JUMPXEQKNIL; - cid = 0; - break; + case Constant::Type_Number: + opc = LOP_JUMPXEQKN; + cid = getConstantIndex(right); + break; - case Constant::Type_Boolean: - opc = LOP_JUMPXEQKB; - cid = cv->valueBoolean; - break; + case Constant::Type_String: + opc = LOP_JUMPXEQKS; + cid = getConstantIndex(right); + break; - case Constant::Type_Number: - opc = LOP_JUMPXEQKN; - cid = getConstantIndex(right); - break; - - case Constant::Type_String: - opc = LOP_JUMPXEQKS; - cid = getConstantIndex(right); - break; - - default: - LUAU_ASSERT(!"Unexpected constant type"); - } - - if (cid < 0) - CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); - - size_t jumpLabel = bytecode.emitLabel(); - - bytecode.emitAD(opc, rl, 0); - bytecode.emitAux(cid | flip); - - return jumpLabel; + default: + LUAU_ASSERT(!"Unexpected constant type"); } - else - { - LuauOpcode opc = getJumpOpCompare(expr->op, not_); - uint8_t rr = compileExprAuto(right, rs); + if (cid < 0) + CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); - size_t jumpLabel = bytecode.emitLabel(); + size_t jumpLabel = bytecode.emitLabel(); - if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) - { - bytecode.emitAD(opc, rr, 0); - bytecode.emitAux(rl); - } - else - { - bytecode.emitAD(opc, rl, 0); - bytecode.emitAux(rr); - } + bytecode.emitAD(opc, rl, 0); + bytecode.emitAux(cid | flip); - return jumpLabel; - } + return jumpLabel; } else { LuauOpcode opc = getJumpOpCompare(expr->op, not_); - uint8_t rl = compileExprAuto(left, rs); - int32_t rr = -1; - - if (isEq && operandIsConstant) - { - if (opc == LOP_JUMPIFEQ) - opc = LOP_JUMPIFEQK; - else if (opc == LOP_JUMPIFNOTEQ) - opc = LOP_JUMPIFNOTEQK; - - rr = getConstantIndex(right); - LUAU_ASSERT(rr >= 0); - } - else - rr = compileExprAuto(right, rs); + uint8_t rr = compileExprAuto(right, rs); size_t jumpLabel = bytecode.emitLabel(); if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) { - bytecode.emitAD(opc, uint8_t(rr), 0); + bytecode.emitAD(opc, rr, 0); bytecode.emitAux(rl); } else @@ -2979,62 +2903,6 @@ struct Compiler loops.pop_back(); } - void resolveAssignConflicts(AstStat* stat, std::vector& vars) - { - LUAU_ASSERT(!FFlag::LuauCompileOptimalAssignment); - - // regsUsed[i] is true if we have assigned the register during earlier assignments - // regsRemap[i] is set to the register where the original (pre-assignment) copy was made - // note: regsRemap is uninitialized intentionally to speed small assignments up; regsRemap[i] is valid iff regsUsed[i] - std::bitset<256> regsUsed; - uint8_t regsRemap[256]; - - for (size_t i = 0; i < vars.size(); ++i) - { - LValue& li = vars[i]; - - if (li.kind == LValue::Kind_Local) - { - if (!regsUsed[li.reg]) - { - regsUsed[li.reg] = true; - regsRemap[li.reg] = li.reg; - } - } - else if (li.kind == LValue::Kind_IndexName || li.kind == LValue::Kind_IndexNumber || li.kind == LValue::Kind_IndexExpr) - { - // we're looking for assignments before this one that invalidate any of the registers involved - if (regsUsed[li.reg]) - { - // the register may have been evacuated previously, but if it wasn't - move it now - if (regsRemap[li.reg] == li.reg) - { - uint8_t reg = allocReg(stat, 1); - bytecode.emitABC(LOP_MOVE, reg, li.reg, 0); - - regsRemap[li.reg] = reg; - } - - li.reg = regsRemap[li.reg]; - } - - if (li.kind == LValue::Kind_IndexExpr && regsUsed[li.index]) - { - // the register may have been evacuated previously, but if it wasn't - move it now - if (regsRemap[li.index] == li.index) - { - uint8_t reg = allocReg(stat, 1); - bytecode.emitABC(LOP_MOVE, reg, li.index, 0); - - regsRemap[li.index] = reg; - } - - li.index = regsRemap[li.index]; - } - } - } - } - struct Assignment { LValue lvalue; @@ -3146,111 +3014,82 @@ struct Compiler return; } - if (FFlag::LuauCompileOptimalAssignment) + // compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the + // left hand side - for example, in "a[expr] = foo" expr will get evaluated here + std::vector vars(stat->vars.size); + + for (size_t i = 0; i < stat->vars.size; ++i) + vars[i].lvalue = compileLValue(stat->vars.data[i], rs); + + // perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary + // register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass + resolveAssignConflicts(stat, vars, stat->values); + + // compute rhs into (mostly) fresh registers + // note that when the lhs assigment is a local, we evaluate directly into that register + // this is possible because resolveAssignConflicts renamed conflicting locals into temporaries + // after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned + for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i) { - // compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the - // left hand side - for example, in "a[expr] = foo" expr will get evaluated here - std::vector vars(stat->vars.size); + AstExpr* value = stat->values.data[i]; - for (size_t i = 0; i < stat->vars.size; ++i) - vars[i].lvalue = compileLValue(stat->vars.data[i], rs); - - // perform conflict resolution: if any expression refers to a local that is assigned before evaluating it, we assign to a temporary - // register after this, vars[i].conflictReg is set for locals that need to be assigned in the second pass - resolveAssignConflicts(stat, vars, stat->values); - - // compute rhs into (mostly) fresh registers - // note that when the lhs assigment is a local, we evaluate directly into that register - // this is possible because resolveAssignConflicts renamed conflicting locals into temporaries - // after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned - for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i) + if (i + 1 == stat->values.size && stat->vars.size > stat->values.size) { - AstExpr* value = stat->values.data[i]; + // allocate a consecutive range of regs for all remaining vars and compute everything into temps + // note, this also handles trailing nils + uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1); + uint8_t temp = allocReg(stat, rest); - if (i + 1 == stat->values.size && stat->vars.size > stat->values.size) + compileExprTempN(value, temp, rest, /* targetTop= */ true); + + for (size_t j = i; j < stat->vars.size; ++j) + vars[j].valueReg = uint8_t(temp + (j - i)); + } + else + { + Assignment& var = vars[i]; + + // if target is a local, use compileExpr directly to target + if (var.lvalue.kind == LValue::Kind_Local) { - // allocate a consecutive range of regs for all remaining vars and compute everything into temps - // note, this also handles trailing nils - uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1); - uint8_t temp = allocReg(stat, rest); + var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg; - compileExprTempN(value, temp, rest, /* targetTop= */ true); - - for (size_t j = i; j < stat->vars.size; ++j) - vars[j].valueReg = uint8_t(temp + (j - i)); + compileExpr(stat->values.data[i], var.valueReg); } else { - Assignment& var = vars[i]; - - // if target is a local, use compileExpr directly to target - if (var.lvalue.kind == LValue::Kind_Local) - { - var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg; - - compileExpr(stat->values.data[i], var.valueReg); - } - else - { - var.valueReg = compileExprAuto(stat->values.data[i], rs); - } + var.valueReg = compileExprAuto(stat->values.data[i], rs); } } - - // compute expressions with side effects for lulz - for (size_t i = stat->vars.size; i < stat->values.size; ++i) - { - RegScope rsi(this); - compileExprAuto(stat->values.data[i], rsi); - } - - // almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a - // separate pass to avoid conflicts - for (const Assignment& var : vars) - { - LUAU_ASSERT(var.valueReg != kInvalidReg); - - if (var.lvalue.kind != LValue::Kind_Local) - { - setDebugLine(var.lvalue.location); - compileAssign(var.lvalue, var.valueReg); - } - } - - // all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR - // local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries - for (const Assignment& var : vars) - { - if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg) - bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0); - } } - else + + // compute expressions with side effects for lulz + for (size_t i = stat->vars.size; i < stat->values.size; ++i) { - // compute all l-values: note that this doesn't assign anything yet but it allocates registers and computes complex expressions on the - // left hand side for example, in "a[expr] = foo" expr will get evaluated here - std::vector vars(stat->vars.size); + RegScope rsi(this); + compileExprAuto(stat->values.data[i], rsi); + } - for (size_t i = 0; i < stat->vars.size; ++i) - vars[i] = compileLValue(stat->vars.data[i], rs); + // almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a + // separate pass to avoid conflicts + for (const Assignment& var : vars) + { + LUAU_ASSERT(var.valueReg != kInvalidReg); - // perform conflict resolution: if any lvalue refers to a local reg that will be reassigned before that, we save the local variable in a - // temporary reg - resolveAssignConflicts(stat, vars); - - // compute values into temporaries - uint8_t regs = allocReg(stat, unsigned(stat->vars.size)); - - compileExprListTemp(stat->values, regs, uint8_t(stat->vars.size), /* targetTop= */ true); - - // assign variables that have associated values; note that if we have fewer values than variables, we'll assign nil because - // compileExprListTemp will generate nils - for (size_t i = 0; i < stat->vars.size; ++i) + if (var.lvalue.kind != LValue::Kind_Local) { - setDebugLine(stat->vars.data[i]); - compileAssign(vars[i], uint8_t(regs + i)); + setDebugLine(var.lvalue.location); + compileAssign(var.lvalue, var.valueReg); } } + + // all regular local writes are done by the prior loops by computing result directly into target, so this just handles conflicts OR + // local copies from temporary registers in multret context, since in that case we have to allocate consecutive temporaries + for (const Assignment& var : vars) + { + if (var.lvalue.kind == LValue::Kind_Local && var.valueReg != var.lvalue.reg) + bytecode.emitABC(LOP_MOVE, var.lvalue.reg, var.valueReg, 0); + } } void compileStatCompoundAssign(AstStatCompoundAssign* stat) diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 422c82b1..c16e5aa7 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -15,8 +15,6 @@ #include #endif -LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false) - // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path @@ -602,7 +600,7 @@ static int luauF_btest(lua_State* L, StkId res, TValue* arg0, int nresults, StkI static int luauF_extract(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= (3 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) + if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) { double a1 = nvalue(arg0); double a2 = nvalue(args); @@ -693,7 +691,7 @@ static int luauF_lshift(lua_State* L, StkId res, TValue* arg0, int nresults, Stk static int luauF_replace(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { - if (nparams >= (4 - FFlag::LuauFasterBit32NoWidth) && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) + if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) { double a1 = nvalue(arg0); double a2 = nvalue(args); diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index b3ea1094..192ea0b5 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -8,8 +8,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false); - // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -1036,9 +1034,6 @@ static int str_format(lua_State* L) } case '*': { - if (!FFlag::LuauTostringFormatSpecifier) - luaL_error(L, "invalid option '%%*' to 'format'"); - if (formatItemSize != 1) luaL_error(L, "'%%*' does not take a form"); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 7306b055..aa1da8ae 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -3031,7 +3031,16 @@ static void luau_execute(lua_State* L) TValue* kv = VM_KV(aux & 0xffffff); LUAU_ASSERT(ttisnumber(kv)); +#if defined(__aarch64__) + // On several ARM chips (Apple M1/M2, Neoverse N1), comparing the result of a floating-point comparison is expensive, and a branch + // is much cheaper; on some 32-bit ARM chips (Cortex A53) the performance is about the same so we prefer less branchy variant there + if (aux >> 31) + pc += !(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1; + else + pc += (ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1; +#else pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; +#endif LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); VM_NEXT(); } diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 3f75ebac..08f241ed 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -4,7 +4,6 @@ #include "doctest.h" -#include #include using namespace Luau::CodeGen; @@ -22,7 +21,7 @@ std::string bytecodeAsArray(const std::vector& bytecode) class AssemblyBuilderX64Fixture { public: - void check(std::function f, std::vector code, std::vector data = {}) + void check(void (*f)(AssemblyBuilderX64& build), std::vector code, std::vector data = {}) { AssemblyBuilderX64 build(/* logText= */ false); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 0a3c6507..d8520a6e 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -1241,8 +1241,7 @@ TEST_CASE("InterpStringZeroCost") { ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; - CHECK_EQ( - "\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"), + CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"), R"( LOADK R1 K0 LOADK R3 K1 @@ -1250,16 +1249,14 @@ NAMECALL R1 R1 K2 CALL R1 2 1 MOVE R0 R1 RETURN R0 0 -)" - ); +)"); } TEST_CASE("InterpStringRegisterCleanup") { ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; - CHECK_EQ( - "\n" + compileFunction0(R"( + CHECK_EQ("\n" + compileFunction0(R"( local a, b, c = nil, "um", "uh oh" a = `foo{"bar"}` print(a) @@ -1278,8 +1275,7 @@ GETIMPORT R3 6 MOVE R4 R0 CALL R3 1 0 RETURN R0 0 -)" - ); +)"); } TEST_CASE("ConstantFoldArith") @@ -2488,8 +2484,6 @@ end TEST_CASE("DebugLineInfoRepeatUntil") { - ScopedFastFlag sff("LuauCompileXEQ", true); - CHECK_EQ("\n" + compileFunction0Coverage(R"( local f = 0 repeat @@ -2834,8 +2828,6 @@ RETURN R0 0 TEST_CASE("AssignmentConflict") { - ScopedFastFlag sff("LuauCompileOptimalAssignment", true); - // assignments are left to right CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"( LOADNIL R0 @@ -3610,8 +3602,6 @@ RETURN R0 1 TEST_CASE("ConstantJumpCompare") { - ScopedFastFlag sff("LuauCompileXEQ", true); - CHECK_EQ("\n" + compileFunction0(R"( local obj = ... local b = obj == 1 @@ -6210,8 +6200,6 @@ L4: RETURN R0 -1 TEST_CASE("BuiltinFoldingMultret") { - ScopedFastFlag sff("LuauCompileXEQ", true); - CHECK_EQ("\n" + compileFunction(R"( local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000 local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000 @@ -6350,8 +6338,6 @@ RETURN R2 1 TEST_CASE("MultipleAssignments") { - ScopedFastFlag sff("LuauCompileOptimalAssignment", true); - // order of assignments is left to right CHECK_EQ("\n" + compileFunction0(R"( local a, b @@ -6574,15 +6560,14 @@ RETURN R0 0 TEST_CASE("BuiltinExtractK") { - ScopedFastFlag sff("LuauCompileExtractK", true); - // below, K0 refers to a packed f+w constant for bit32.extractk builtin // K1 and K2 refer to 1 and 3 and are only used during fallback path CHECK_EQ("\n" + compileFunction0(R"( local v = ... return bit32.extract(v, 1, 3) -)"), R"( +)"), + R"( GETVARARGS R0 1 FASTCALL2K 59 R0 K0 L0 MOVE R2 R0 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f6f5b41f..a7ffb493 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -289,15 +289,12 @@ TEST_CASE("Clear") TEST_CASE("Strings") { - ScopedFastFlag sff{"LuauTostringFormatSpecifier", true}; - runConformance("strings.lua"); } TEST_CASE("StringInterp") { ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true}; - ScopedFastFlag sffTostringFormat{"LuauTostringFormatSpecifier", true}; runConformance("stringinterp.lua"); } @@ -725,13 +722,16 @@ TEST_CASE("NewUserdataOverflow") StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); - lua_pushcfunction(L, [](lua_State* L1) { - // The following userdata request might cause an overflow. - lua_newuserdatadtor(L1, SIZE_MAX, [](void* d){}); - // The overflow might segfault in the following call. - lua_getmetatable(L1, -1); - return 0; - }, nullptr); + lua_pushcfunction( + L, + [](lua_State* L1) { + // The following userdata request might cause an overflow. + lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {}); + // The overflow might segfault in the following call. + lua_getmetatable(L1, -1); + return 0; + }, + nullptr); CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN); CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0); diff --git a/tests/ConstraintGraphBuilder.test.cpp b/tests/ConstraintGraphBuilder.test.cpp index 00c3309c..bbe29429 100644 --- a/tests/ConstraintGraphBuilder.test.cpp +++ b/tests/ConstraintGraphBuilder.test.cpp @@ -57,13 +57,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "nil_primitive") auto constraints = collectConstraints(NotNull(cgb.rootScope)); ToStringOptions opts; - REQUIRE(5 <= constraints.size()); + REQUIRE(4 <= constraints.size()); CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts)); - CHECK("*blocked-2* ~ inst *blocked-1*" == toString(*constraints[1], opts)); - CHECK("() -> (b...) <: *blocked-2*" == toString(*constraints[2], opts)); - CHECK("b... <: c" == toString(*constraints[3], opts)); - CHECK("nil <: a..." == toString(*constraints[4], opts)); + CHECK("call *blocked-1* with { result = *blocked-tp-1* }" == toString(*constraints[1], opts)); + CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts)); + CHECK("nil <: a..." == toString(*constraints[3], opts)); } TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application") @@ -76,13 +75,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application") cgb.visit(block); auto constraints = collectConstraints(NotNull(cgb.rootScope)); - REQUIRE(4 == constraints.size()); + REQUIRE(3 == constraints.size()); ToStringOptions opts; CHECK("string <: a" == toString(*constraints[0], opts)); - CHECK("*blocked-1* ~ inst a" == toString(*constraints[1], opts)); - CHECK("(string) -> (b...) <: *blocked-1*" == toString(*constraints[2], opts)); - CHECK("b... <: c" == toString(*constraints[3], opts)); + CHECK("call a with { result = *blocked-tp-1* }" == toString(*constraints[1], opts)); + CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts)); } TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition") @@ -114,13 +112,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function") cgb.visit(block); auto constraints = collectConstraints(NotNull(cgb.rootScope)); - REQUIRE(4 == constraints.size()); + REQUIRE(3 == constraints.size()); ToStringOptions opts; CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts)); - CHECK("*blocked-2* ~ inst (a) -> (b...)" == toString(*constraints[1], opts)); - CHECK("(a) -> (c...) <: *blocked-2*" == toString(*constraints[2], opts)); - CHECK("c... <: b..." == toString(*constraints[3], opts)); + CHECK("call (a) -> (b...) with { result = *blocked-tp-1* }" == toString(*constraints[1], opts)); + CHECK("*blocked-tp-1* <: b..." == toString(*constraints[2], opts)); } TEST_SUITE_END(); diff --git a/tests/ConstraintSolver.test.cpp b/tests/ConstraintSolver.test.cpp index e33f6570..2c489733 100644 --- a/tests/ConstraintSolver.test.cpp +++ b/tests/ConstraintSolver.test.cpp @@ -28,7 +28,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello") cgb.visit(block); NotNull rootScope = NotNull(cgb.rootScope); - ConstraintSolver cs{&arena, rootScope}; + NullModuleResolver resolver; + ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; cs.run(); @@ -48,7 +49,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function") cgb.visit(block); NotNull rootScope = NotNull(cgb.rootScope); - ConstraintSolver cs{&arena, rootScope}; + NullModuleResolver resolver; + ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; cs.run(); @@ -57,7 +59,6 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function") CHECK("(a) -> a" == toString(idType)); } -#if 1 TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") { AstStatBlock* block = parse(R"( @@ -77,7 +78,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") ToStringOptions opts; - ConstraintSolver cs{&arena, rootScope}; + NullModuleResolver resolver; + ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; cs.run(); @@ -85,6 +87,5 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") CHECK("(a) -> number" == toString(idType, opts)); } -#endif TEST_SUITE_END(); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 4051f851..476b7a2a 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -2,6 +2,8 @@ #include "Fixture.h" #include "Luau/AstQuery.h" +#include "Luau/ModuleResolver.h" +#include "Luau/NotNull.h" #include "Luau/Parser.h" #include "Luau/TypeVar.h" #include "Luau/TypeAttach.h" @@ -444,10 +446,11 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete) ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() : Fixture() , mainModule(new Module) - , cgb(mainModuleName, mainModule, &arena, NotNull(&ice), frontend.getGlobalScope()) + , cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), NotNull(&ice), frontend.getGlobalScope()) , forceTheFlag{"DebugLuauDeferredConstraintResolution", true} { BlockedTypeVar::nextIndex = 0; + BlockedTypePack::nextIndex = 0; } ModuleName fromString(std::string_view name) diff --git a/tests/Fixture.h b/tests/Fixture.h index 956f95d0..8923b208 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -132,6 +132,7 @@ struct Fixture TestFileResolver fileResolver; TestConfigResolver configResolver; + NullModuleResolver moduleResolver; std::unique_ptr sourceModule; Frontend frontend; InternalErrorReporter ice; @@ -244,7 +245,7 @@ struct FindNthOccurenceOf : public AstVisitor * 2. Luau::query(Luau::query(block)) * 3. Luau::query(block, {nth(2)}) */ -template +template T* query(AstNode* node, const std::vector& nths = {nth(N)}) { static_assert(std::is_base_of_v, "T must be a derived class of AstNode"); diff --git a/tests/JsonEmitter.test.cpp b/tests/JsonEmitter.test.cpp index ebe83209..ff9a5955 100644 --- a/tests/JsonEmitter.test.cpp +++ b/tests/JsonEmitter.test.cpp @@ -94,7 +94,7 @@ TEST_CASE("write_optional") write(emitter, std::optional{true}); emitter.writeComma(); write(emitter, std::nullopt); - + CHECK(emitter.str() == "true,null"); } diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index a7d09e8f..b560c89e 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1181,7 +1181,7 @@ s:match("[]") nons:match("[]") )~"); - CHECK_EQ(result.warnings.size(), 2); + REQUIRE_EQ(result.warnings.size(), 2); CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set"); CHECK_EQ(result.warnings[0].location.begin.line, 3); CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set"); @@ -1746,6 +1746,7 @@ local _ = not a == b local _ = not a ~= b local _ = not a <= b local _ = a <= b == 0 +local _ = a <= b <= 0 local _ = not a == not b -- weird but ok @@ -1760,11 +1761,12 @@ local _ = (a <= b) == 0 local _ = a <= (b == 0) )"); - REQUIRE_EQ(result.warnings.size(), 4); - CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or wrap one of the expressions in parentheses to silence"); - CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or wrap one of the expressions in parentheses to silence"); - CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; wrap one of the expressions in parentheses to silence"); - CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; wrap one of the expressions in parentheses to silence"); + REQUIRE_EQ(result.warnings.size(), 5); + CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or add parentheses to silence"); + CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or add parentheses to silence"); + CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; add parentheses to silence"); + CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; add parentheses to silence"); + CHECK_EQ(result.warnings[4].text, "X <= Y <= Z is equivalent to (X <= Y) <= Z; did you mean X <= Y and Y <= Z?"); } TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 2dd47708..44a6b4ac 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -943,8 +943,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace") { ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; - auto columnOfEndBraceError = [this](const char* code) - { + auto columnOfEndBraceError = [this](const char* code) { try { parse(code); @@ -1737,6 +1736,48 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation") matchParseError("local a : 2 = 2", "Expected type, got '2'"); } +TEST_CASE_FIXTURE(Fixture, "parse_error_missing_type_annotation") +{ + ScopedFastFlag LuauTypeAnnotationLocationChange{"LuauTypeAnnotationLocationChange", true}; + + { + ParseResult result = tryParse("local x:"); + CHECK(result.errors.size() == 1); + Position begin = result.errors[0].getLocation().begin; + Position end = result.errors[0].getLocation().end; + CHECK(begin.line == end.line); + int width = end.column - begin.column; + CHECK(width == 0); + CHECK(result.errors[0].getMessage() == "Expected type, got "); + } + + { + ParseResult result = tryParse(R"( +local x:=42 + )"); + CHECK(result.errors.size() == 1); + Position begin = result.errors[0].getLocation().begin; + Position end = result.errors[0].getLocation().end; + CHECK(begin.line == end.line); + int width = end.column - begin.column; + CHECK(width == 1); // Length of `=` + CHECK(result.errors[0].getMessage() == "Expected type, got '='"); + } + + { + ParseResult result = tryParse(R"( +function func():end + )"); + CHECK(result.errors.size() == 1); + Position begin = result.errors[0].getLocation().begin; + Position end = result.errors[0].getLocation().end; + CHECK(begin.line == end.line); + int width = end.column - begin.column; + CHECK(width == 3); // Length of `end` + CHECK(result.errors[0].getMessage() == "Expected type, got 'end'"); + } +} + TEST_CASE_FIXTURE(Fixture, "parse_declarations") { AstStatBlock* stat = parseEx(R"( diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index f4766104..8c6f2e4f 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -346,4 +346,17 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_any_with_other_options") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "union_of_types_regression_test") +{ + ScopedFastFlag LuauUnionOfTypesFollow{"LuauUnionOfTypesFollow", true}; + + CheckResult result = check(R"( +--!strict +local stat +stat = stat and tonumber(stat) or stat + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 12fb4aa2..07a04363 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -1061,7 +1061,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c = string.gmatch("This is a string", "(.()(%a+))")() )END"); @@ -1075,7 +1074,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c = ("This is a string"):gmatch("(.()(%a+))")() )END"); @@ -1089,7 +1087,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c, d = string.gmatch("T(his)() is a string", ".")() )END"); @@ -1107,7 +1104,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")() )END"); @@ -1127,7 +1123,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")() )END"); @@ -1146,7 +1141,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket") { - ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true}; CheckResult result = check(R"END( local a, b = string.gmatch("[[[", "()([[])")() )END"); @@ -1196,7 +1190,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallbac TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c = string.match("This is a string", "(.()(%a+))") )END"); @@ -1210,7 +1203,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number") )END"); @@ -1229,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local d, e, a, b, c = string.find("This is a string", "(.()(%a+))") )END"); @@ -1245,7 +1236,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number") )END"); @@ -1266,7 +1256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool") )END"); @@ -1287,7 +1276,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") { - ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true}; CheckResult result = check(R"END( local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true) )END"); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 5cc759d6..3a6f4491 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -133,6 +133,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified") TableTypeVar* ttv = getMutable(*r); REQUIRE(ttv); + REQUIRE(ttv->props.count("f")); TypeId k = ttv->props["f"].type; REQUIRE(k); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 9b10092c..85249ecd 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSpecialTypesAsterisked) +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -109,6 +110,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators_dcr") +{ + ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + function no_iter() end + for key in no_iter() do end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check") { CheckResult result = check(R"( @@ -141,7 +154,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error") end )"); - CHECK_EQ(2, result.errors.size()); + LUAU_REQUIRE_ERROR_COUNT(2, result); TypeId p = requireType("p"); if (FFlag::LuauSpecialTypesAsterisked) @@ -232,6 +245,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args CHECK_EQ(0, acm->actual); } +TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_incompatible_args_to_iterator") +{ + CheckResult result = check(R"( + function my_iter(state: string, index: number) + return state, index + end + + local my_state = {} + local first_index = "first" + + -- Type errors here. my_state and first_index cannot be passed to my_iter + for a, b in my_iter, my_state, first_index do + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, result); + + CHECK(get(result.errors[1])); + CHECK(Location{{9, 29}, {9, 37}} == result.errors[0].location); + + CHECK(get(result.errors[1])); + CHECK(Location{{9, 39}, {9, 50}} == result.errors[1].location); +} + TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator") { CheckResult result = check(R"( @@ -503,7 +540,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic") end )"); - LUAU_REQUIRE_ERROR_COUNT(0, result); + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(*typeChecker.numberType, *requireType("key")); } diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index b5f2296c..ede84f4a 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -16,6 +16,35 @@ LUAU_FASTFLAG(LuauSpecialTypesAsterisked) TEST_SUITE_BEGIN("TypeInferModules"); +TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_require_basic") +{ + fileResolver.source["game/A"] = R"( + --!strict + return { + a = 1, + } + )"; + + fileResolver.source["game/B"] = R"( + --!strict + local A = require(game.A) + + local b = A.a + )"; + + CheckResult aResult = frontend.check("game/A"); + LUAU_REQUIRE_NO_ERRORS(aResult); + + CheckResult bResult = frontend.check("game/B"); + LUAU_REQUIRE_NO_ERRORS(bResult); + + ModulePtr b = frontend.moduleResolver.modules["game/B"]; + REQUIRE(b != nullptr); + std::optional bType = requireType(b, "b"); + REQUIRE(bType); + CHECK(toString(*bType) == "number"); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "require") { fileResolver.source["game/A"] = R"( diff --git a/tests/TypeInfer.primitives.test.cpp b/tests/TypeInfer.primitives.test.cpp index a06fd749..5a6fb0e4 100644 --- a/tests/TypeInfer.primitives.test.cpp +++ b/tests/TypeInfer.primitives.test.cpp @@ -11,7 +11,6 @@ #include "doctest.h" -LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes) LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -61,8 +60,8 @@ TEST_CASE_FIXTURE(Fixture, "string_method") CheckResult result = check(R"( local p = ("tacos"):len() )"); - CHECK_EQ(0, result.errors.size()); + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(*requireType("p"), *typeChecker.numberType); } @@ -73,8 +72,8 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect") local l = s.lower local p = l(s) )"); - CHECK_EQ(0, result.errors.size()); + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(*requireType("p"), *typeChecker.stringType); } @@ -84,12 +83,9 @@ TEST_CASE_FIXTURE(Fixture, "string_function_other") local s:string local p = s:match("foo") )"); - CHECK_EQ(0, result.errors.size()); - if (FFlag::LuauDeduceFindMatchReturnTypes) - CHECK_EQ(toString(requireType("p")), "string"); - else - CHECK_EQ(toString(requireType("p")), "string?"); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ(toString(requireType("p")), "string"); } TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index c8fc7f2d..472d0ed5 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -62,7 +62,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_returns_what_f_returns") local a:boolean,b:number,c:string=xpcall(function(): (number,string)return 1,'foo'end,function(): (string,number)return'foo',1 end) )"; - CHECK_EQ(expected, decorateWithTypes(code)); + CheckResult result = check(code); + + CHECK("boolean" == toString(requireType("a"))); + CHECK("number" == toString(requireType("b"))); + CHECK("string" == toString(requireType("c"))); + + CHECK(expected == decorateWithTypes(code)); } // We had a bug where if you have two type packs that looks like: @@ -609,4 +615,16 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") CHECK("b?" == toString(option2, opts)); // This should not hold. } +TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators") +{ + ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false}; + + CheckResult result = check(R"( + function no_iter() end + for key in no_iter() do end -- This should not be ok + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 95b85c48..97c3da4f 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2994,8 +2994,6 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2") TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union") { - ScopedFastFlag luauExpectedTableUnionIndexerType{"LuauExpectedTableUnionIndexerType", true}; - LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}} = {a = {2, 's'}})")); LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}}? = {a = {2, 's'}})")); LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})")); diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index d9274751..9c19da59 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -111,6 +111,11 @@ assert((function() local a = nil a = a and 2 return a end)() == nil) assert((function() local a = 1 a = a or 2 return a end)() == 1) assert((function() local a = nil a = a or 2 return a end)() == 2) +assert((function() local a a = 1 local b = 2 b = a and b return b end)() == 2) +assert((function() local a a = nil local b = 2 b = a and b return b end)() == nil) +assert((function() local a a = 1 local b = 2 b = a or b return b end)() == 1) +assert((function() local a a = nil local b = 2 b = a or b return b end)() == 2) + -- binary arithmetics coerces strings to numbers (sadly) assert(1 + "2" == 3) assert(2 * "0xa" == 20) diff --git a/tests/conformance/errors.lua b/tests/conformance/errors.lua index b69d437b..529e9b0c 100644 --- a/tests/conformance/errors.lua +++ b/tests/conformance/errors.lua @@ -369,21 +369,31 @@ assert(not a and b:match('[^ ]+') == "short:1:") local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10)) assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:") --- arith errors function ecall(fn, ...) local ok, err = pcall(fn, ...) assert(not ok) - return err:sub(err:find(": ") + 2, #err) + return err:sub((err:find(": ") or -1) + 2, #err) end +-- arith errors assert(ecall(function() return nil + 5 end) == "attempt to perform arithmetic (add) on nil and number") assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic (add) on string") assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design) assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number") +-- table errors assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil") assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN") +assert(ecall(function() local t = {} rawset(t, nil, 2) end) == "table index is nil") +assert(ecall(function() local t = {} rawset(t, 0/0, 2) end) == "table index is NaN") + +assert(ecall(function() local t = {} t[nil] = nil end) == "table index is nil") +assert(ecall(function() local t = {} t[0/0] = nil end) == "table index is NaN") + +assert(ecall(function() local t = {} rawset(t, nil, nil) end) == "table index is nil") +assert(ecall(function() local t = {} rawset(t, 0/0, nil) end) == "table index is NaN") + -- for loop type errors assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)") assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)") diff --git a/tools/faillist.txt b/tools/faillist.txt index 54e7ac05..3de9db76 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -1,10 +1,8 @@ AnnotationTests.builtin_types_are_not_exported -AnnotationTests.cannot_use_nonexported_type -AnnotationTests.cloned_interface_maintains_pointers_between_definitions AnnotationTests.duplicate_type_param_name AnnotationTests.for_loop_counter_annotation_is_checked AnnotationTests.generic_aliases_are_cloned_properly -AnnotationTests.interface_types_belong_to_interface_arena +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 @@ -26,6 +24,7 @@ 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 @@ -56,14 +55,12 @@ AutocompleteTest.global_functions_are_not_scoped_lexically AutocompleteTest.globals_are_order_independent AutocompleteTest.if_then_else_elseif_completions AutocompleteTest.keyword_methods -AutocompleteTest.keyword_types 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.module_type_members AutocompleteTest.nested_member_completions AutocompleteTest.nested_recursive_function AutocompleteTest.no_function_name_suggestions @@ -78,7 +75,6 @@ AutocompleteTest.return_types AutocompleteTest.sometimes_the_metatable_is_an_error AutocompleteTest.source_module_preservation_and_invalidation AutocompleteTest.statement_between_two_statements -AutocompleteTest.stop_at_first_stat_when_recommending_keywords AutocompleteTest.string_prim_non_self_calls_are_avoided AutocompleteTest.string_prim_self_calls_are_fine AutocompleteTest.suggest_external_module_type @@ -155,6 +151,7 @@ BuiltinTests.string_format_tostring_specifier BuiltinTests.string_format_tostring_specifier_type_constraint BuiltinTests.string_format_use_correct_argument BuiltinTests.string_format_use_correct_argument2 +BuiltinTests.string_format_use_correct_argument3 BuiltinTests.string_lib_self_noself BuiltinTests.table_concat_returns_string BuiltinTests.table_dot_remove_optionally_returns_generic @@ -168,31 +165,21 @@ BuiltinTests.tonumber_returns_optional_number_type BuiltinTests.tonumber_returns_optional_number_type2 DefinitionTests.declaring_generic_functions DefinitionTests.definition_file_classes -DefinitionTests.definition_file_loading -DefinitionTests.definitions_documentation_symbols -DefinitionTests.documentation_symbols_dont_attach_to_persistent_types -DefinitionTests.single_class_type_identity_in_global_types FrontendTest.ast_node_at_position FrontendTest.automatically_check_dependent_scripts -FrontendTest.check_without_builtin_next FrontendTest.dont_reparse_clean_file_when_linting FrontendTest.environments FrontendTest.imported_table_modification_2 FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded -FrontendTest.no_use_after_free_with_type_fun_instantiation FrontendTest.nocheck_cycle_used_by_checked -FrontendTest.nocheck_modules_are_typed FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error FrontendTest.recheck_if_dependent_script_is_dirty FrontendTest.reexport_cyclic_type -FrontendTest.reexport_type_alias -FrontendTest.report_require_to_nonexistent_file FrontendTest.report_syntax_error_in_required_file FrontendTest.trace_requires_in_nonstrict_mode GenericsTests.apply_type_function_nested_generics1 GenericsTests.apply_type_function_nested_generics2 GenericsTests.better_mismatch_error_messages -GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.calling_self_generic_methods GenericsTests.check_generic_typepack_function GenericsTests.check_mutual_generic_functions @@ -208,7 +195,6 @@ GenericsTests.factories_of_generics GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_many GenericsTests.generic_factories -GenericsTests.generic_functions_dont_cache_type_parameters GenericsTests.generic_functions_in_types GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_table_method @@ -245,7 +231,6 @@ IntersectionTypes.no_stack_overflow_from_flattenintersection IntersectionTypes.overload_is_not_a_function IntersectionTypes.select_correct_union_fn IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions -IntersectionTypes.table_intersection_write IntersectionTypes.table_intersection_write_sealed IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect @@ -255,7 +240,6 @@ Linter.TableOperations ModuleTests.clone_self_property ModuleTests.deepClone_cyclic_table ModuleTests.do_not_clone_reexports -ModuleTests.do_not_clone_types_of_reexported_values NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything NonstrictModeTests.for_in_iterator_variables_are_any NonstrictModeTests.function_parameters_are_any @@ -319,6 +303,7 @@ ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.weird_fail_to_unify_type_pack ProvisionalTests.weirditer_should_not_loop_forever ProvisionalTests.while_body_are_also_refined +ProvisionalTests.xpcall_returns_what_f_returns RefinementTest.and_constraint RefinementTest.and_or_peephole_refinement RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string @@ -358,6 +343,7 @@ RefinementTest.not_and_constraint RefinementTest.not_t_or_some_prop_of_t RefinementTest.or_predicate_with_truthy_predicates RefinementTest.parenthesized_expressions_are_followed_through +RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string RefinementTest.refine_unknowns RefinementTest.string_not_equal_to_string_or_nil @@ -394,7 +380,6 @@ TableTests.augment_table TableTests.builtin_table_names TableTests.call_method TableTests.cannot_augment_sealed_table -TableTests.cannot_call_tables TableTests.cannot_change_type_of_unsealed_table_prop TableTests.casting_sealed_tables_with_props_into_table_with_indexer TableTests.casting_tables_with_props_into_table_with_indexer3 @@ -409,7 +394,6 @@ 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 @@ -448,6 +432,7 @@ TableTests.length_operator_intersection TableTests.length_operator_non_table_union TableTests.length_operator_union TableTests.length_operator_union_errors +TableTests.less_exponential_blowup_please TableTests.meta_add TableTests.meta_add_both_ways TableTests.meta_add_inferred @@ -470,7 +455,6 @@ 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.recursive_metatable_type_call TableTests.result_is_always_any_if_lhs_is_any TableTests.result_is_bool_for_equality_operators_if_lhs_is_any TableTests.right_table_missing_key @@ -480,7 +464,6 @@ TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type TableTests.shared_selfs TableTests.shared_selfs_from_free_param TableTests.shared_selfs_through_metatables -TableTests.table_function_check_use_after_free TableTests.table_indexing_error_location TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict TableTests.table_insert_should_cope_with_optional_properties_in_strict @@ -525,13 +508,10 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.typepack_unification_should_trim_free_tails TryUnifyTests.variadics_should_use_reversed_properly TypeAliases.cli_38393_recursive_intersection_oom -TypeAliases.corecursive_types_generic TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any -TypeAliases.general_require_multi_assign TypeAliases.generic_param_remap TypeAliases.mismatched_generic_pack_type_param TypeAliases.mismatched_generic_type_param -TypeAliases.mutually_recursive_types_errors TypeAliases.mutually_recursive_types_restriction_not_ok_1 TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_swapsies_not_ok @@ -543,7 +523,6 @@ 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.cyclic_follow TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table @@ -559,20 +538,21 @@ TypeInfer.tc_if_else_expressions_expected_type_1 TypeInfer.tc_if_else_expressions_expected_type_2 TypeInfer.tc_if_else_expressions_expected_type_3 TypeInfer.tc_if_else_expressions_type_union +TypeInfer.tc_interpolated_string_basic +TypeInfer.tc_interpolated_string_constant_type +TypeInfer.tc_interpolated_string_with_invalid_expression TypeInfer.type_infer_recursion_limit_no_ice -TypeInfer.warn_on_lowercase_parent_property TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any TypeInferAnyError.can_get_length_of_any -TypeInferAnyError.for_in_loop_iterator_is_any TypeInferAnyError.for_in_loop_iterator_is_any2 -TypeInferAnyError.for_in_loop_iterator_is_error -TypeInferAnyError.for_in_loop_iterator_is_error2 TypeInferAnyError.for_in_loop_iterator_returns_any -TypeInferAnyError.for_in_loop_iterator_returns_any2 TypeInferAnyError.length_of_error_type_does_not_produce_an_error TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any +TypeInferAnyError.union_of_types_regression_test TypeInferClasses.call_base_method TypeInferClasses.call_instance_method +TypeInferClasses.can_assign_to_prop_of_base_class_using_string +TypeInferClasses.can_read_prop_of_base_class_using_string TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.classes_can_have_overloaded_operators TypeInferClasses.classes_without_overloaded_operators_cannot_be_added @@ -582,10 +562,13 @@ TypeInferClasses.higher_order_function_return_type_is_not_contravariant TypeInferClasses.higher_order_function_return_values_are_covariant TypeInferClasses.optional_class_field_access_error TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties +TypeInferClasses.table_indexers_are_invariant +TypeInferClasses.table_properties_are_invariant TypeInferClasses.warn_when_prop_almost_matches TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments TypeInferFunctions.another_recursive_local_function +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.complicated_return_types_require_an_explicit_annotation @@ -634,43 +617,23 @@ TypeInferFunctions.too_many_arguments TypeInferFunctions.too_many_return_values TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size -TypeInferLoops.for_in_loop TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values -TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given -TypeInferLoops.for_in_loop_on_error -TypeInferLoops.for_in_loop_on_non_function -TypeInferLoops.for_in_loop_should_fail_with_non_function_iterator -TypeInferLoops.for_in_loop_where_iteratee_is_free TypeInferLoops.for_in_loop_with_custom_iterator TypeInferLoops.for_in_loop_with_next -TypeInferLoops.for_in_with_a_custom_iterator_should_type_check -TypeInferLoops.for_in_with_an_iterator_of_type_any TypeInferLoops.for_in_with_just_one_iterator_is_ok -TypeInferLoops.fuzz_fail_missing_instantitation_follow -TypeInferLoops.ipairs_produces_integral_indices -TypeInferLoops.loop_iter_basic TypeInferLoops.loop_iter_iter_metamethod TypeInferLoops.loop_iter_no_indexer_nonstrict -TypeInferLoops.loop_iter_no_indexer_strict TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.loop_typecheck_crash_on_empty_optional -TypeInferLoops.properly_infer_iteratee_is_a_free_table TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free -TypeInferModules.do_not_modify_imported_types -TypeInferModules.do_not_modify_imported_types_2 -TypeInferModules.do_not_modify_imported_types_3 -TypeInferModules.general_require_call_expression +TypeInferModules.custom_require_global TypeInferModules.general_require_type_mismatch TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict_instantiated -TypeInferModules.require TypeInferModules.require_a_variadic_function -TypeInferModules.require_failed_module -TypeInferModules.require_module_that_does_not_export TypeInferModules.require_types TypeInferModules.type_error_of_unknown_qualified_type -TypeInferModules.warn_if_you_try_to_require_a_non_modulescript 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 @@ -725,7 +688,6 @@ TypeInferOperators.typecheck_unary_minus_error TypeInferOperators.unary_not_is_boolean TypeInferOperators.unknown_type_in_comparison TypeInferOperators.UnknownGlobalCompoundAssign -TypeInferPrimitives.cannot_call_primitives TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.string_function_other TypeInferPrimitives.string_index @@ -768,7 +730,6 @@ TypePackTests.type_alias_type_packs_nested TypePackTests.type_pack_hidden_free_tail_infinite_growth TypePackTests.type_pack_type_parameters TypePackTests.varargs_inference_through_multiple_scopes -TypePackTests.variadic_argument_tail TypePackTests.variadic_pack_syntax TypePackTests.variadic_packs TypeSingletons.bool_singleton_subtype @@ -793,7 +754,6 @@ TypeSingletons.string_singletons_escape_chars TypeSingletons.string_singletons_mismatch TypeSingletons.table_insert_with_a_singleton_argument TypeSingletons.table_properties_type_error_escapes -TypeSingletons.tagged_unions_immutable_tag TypeSingletons.tagged_unions_using_singletons TypeSingletons.taking_the_length_of_string_singleton TypeSingletons.taking_the_length_of_union_of_string_singleton diff --git a/tools/test_dcr.py b/tools/test_dcr.py index da33706c..db932253 100644 --- a/tools/test_dcr.py +++ b/tools/test_dcr.py @@ -84,12 +84,16 @@ def main(): failList = loadFailList() + commandLine = [ + args.path, + "--reporters=xml", + "--fflags=true,DebugLuauDeferredConstraintResolution=true", + ] + + print('>', ' '.join(commandLine), file=sys.stderr) + p = sp.Popen( - [ - args.path, - "--reporters=xml", - "--fflags=true,DebugLuauDeferredConstraintResolution=true", - ], + commandLine, stdout=sp.PIPE, ) @@ -122,7 +126,7 @@ def main(): with open(FAIL_LIST_PATH, "w", newline="\n") as f: for name in newFailList: print(name, file=f) - print("Updated faillist.txt") + print("Updated faillist.txt", file=sys.stderr) if handler.numSkippedTests > 0: print(