Sync to upstream/release/543 (#657)

- Improve ComparisonPrecedence lint suggestions for three-way comparisons (X < Y < Z)
- Improve type checking stability
- Improve location information for errors when parsing invalid type annotations
- Compiler now generates bytecode version 3 in all configurations
- Improve performance of comparisons against numeric constants on AArch64
This commit is contained in:
Arseny Kapoulkine 2022-09-01 16:14:03 -07:00 committed by GitHub
parent 42c24f98d9
commit ae35ada579
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1504 additions and 788 deletions

View file

@ -19,6 +19,7 @@ using ScopePtr = std::shared_ptr<Scope>;
// A substitution which replaces free types by any // A substitution which replaces free types by any
struct Anyification : Substitution struct Anyification : Substitution
{ {
Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack); Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
NotNull<Scope> scope; NotNull<Scope> scope;
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;
@ -35,4 +36,4 @@ struct Anyification : Substitution
bool ignoreChildren(TypePackId ty) override; bool ignoreChildren(TypePackId ty) override;
}; };
} } // namespace Luau

View file

@ -34,6 +34,7 @@ TypeId makeFunction( // Polymorphic
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes); std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
void attachMagicFunction(TypeId ty, MagicFunction fn); void attachMagicFunction(TypeId ty, MagicFunction fn);
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt); Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName); void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);

View file

@ -56,6 +56,10 @@ struct UnaryConstraint
TypeId resultType; TypeId resultType;
}; };
// let L : leftType
// let R : rightType
// in
// L op R : resultType
struct BinaryConstraint struct BinaryConstraint
{ {
AstExprBinary::Op op; AstExprBinary::Op op;
@ -64,6 +68,14 @@ struct BinaryConstraint
TypeId resultType; TypeId resultType;
}; };
// iteratee is iterable
// iterators is the iteration types.
struct IterableConstraint
{
TypePackId iterator;
TypePackId variables;
};
// name(namedType) = name // name(namedType) = name
struct NameConstraint struct NameConstraint
{ {
@ -78,20 +90,31 @@ struct TypeAliasExpansionConstraint
TypeId target; TypeId target;
}; };
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, NameConstraint, TypeAliasExpansionConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>; using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct FunctionCallConstraint
{
std::vector<NotNull<const Constraint>> innerConstraints;
TypeId fn;
TypePackId result;
class AstExprCall* astFragment;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint>;
struct Constraint struct Constraint
{ {
Constraint(ConstraintV&& c, NotNull<Scope> scope); Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c);
Constraint(const Constraint&) = delete; Constraint(const Constraint&) = delete;
Constraint& operator=(const Constraint&) = delete; Constraint& operator=(const Constraint&) = delete;
ConstraintV c;
std::vector<NotNull<Constraint>> dependencies;
NotNull<Scope> scope; NotNull<Scope> scope;
Location location;
ConstraintV c;
std::vector<NotNull<Constraint>> dependencies;
}; };
inline Constraint& asMutable(const Constraint& c) inline Constraint& asMutable(const Constraint& c)

View file

@ -9,6 +9,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypeVar.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. // It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors; std::vector<TypeError> errors;
// Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE. // Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope; ScopePtr globalScope;
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope); ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope);
/** /**
* Fabricates a new free type belonging to a given scope. * 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 scope the scope to add the constraint to.
* @param cv the constraint variant to add. * @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. * 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, AstStatBlock* block);
void visit(const ScopePtr& scope, AstStatLocal* local); void visit(const ScopePtr& scope, AstStatLocal* local);
void visit(const ScopePtr& scope, AstStatFor* for_); 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, AstStatWhile* while_);
void visit(const ScopePtr& scope, AstStatRepeat* repeat); void visit(const ScopePtr& scope, AstStatRepeat* repeat);
void visit(const ScopePtr& scope, AstStatLocalFunction* function); 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, AstStatDeclareClass* declareClass);
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
TypePackId checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs);
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs); TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr); TypePackId checkPack(const ScopePtr& scope, AstExpr* expr);

View file

@ -17,6 +17,8 @@ namespace Luau
// never dereference this pointer. // never dereference this pointer.
using BlockedConstraintId = const void*; using BlockedConstraintId = const void*;
struct ModuleResolver;
struct InstantiationSignature struct InstantiationSignature
{ {
TypeFun fn; TypeFun fn;
@ -42,6 +44,7 @@ struct ConstraintSolver
// The entire set of constraints that the solver is trying to resolve. // The entire set of constraints that the solver is trying to resolve.
std::vector<NotNull<Constraint>> constraints; std::vector<NotNull<Constraint>> constraints;
NotNull<Scope> rootScope; NotNull<Scope> rootScope;
ModuleName currentModuleName;
// Constraints that the solver has generated, rather than sourcing from the // Constraints that the solver has generated, rather than sourcing from the
// scope tree. // scope tree.
@ -63,9 +66,13 @@ struct ConstraintSolver
// Recorded errors that take place within the solver. // Recorded errors that take place within the solver.
ErrorVec errors; ErrorVec errors;
NotNull<ModuleResolver> moduleResolver;
std::vector<RequireCycle> requireCycles;
ConstraintSolverLogger logger; ConstraintSolverLogger logger;
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope); explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles);
/** /**
* Attempts to dispatch all pending constraints and reach a type solution * Attempts to dispatch all pending constraints and reach a type solution
@ -86,8 +93,17 @@ struct ConstraintSolver
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
// for a, ... in next_function, t, ... do
bool tryDispatchIterableFunction(
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint); void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/** /**
@ -108,6 +124,11 @@ struct ConstraintSolver
*/ */
bool isBlocked(TypeId ty); 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. * Returns whether the constraint is blocked on anything.
* @param constraint the constraint to check. * @param constraint the constraint to check.
@ -133,10 +154,22 @@ struct ConstraintSolver
/** Pushes a new solver constraint to the solver. /** Pushes a new solver constraint to the solver.
* @param cv the body of the constraint. * @param cv the body of the constraint.
**/ **/
void pushConstraint(ConstraintV cv, NotNull<Scope> scope); void pushConstraint(NotNull<Scope> 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(TypeErrorData&& data, const Location& location);
void reportError(TypeError e); void reportError(TypeError e);
private: private:
/** /**
* Marks a constraint as being blocked on a type or type pack. The constraint * 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. * @param progressed the type or type pack pointer that has progressed.
**/ **/
void unblock_(BlockedConstraintId progressed); void unblock_(BlockedConstraintId progressed);
ToStringOptions opts;
}; };
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts); void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);

View file

@ -16,7 +16,8 @@ struct ConstraintSolverLogger
{ {
std::string compileOutput(); std::string compileOutput();
void captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints); void captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force); void prepareStepSnapshot(
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force);
void commitPreparedStepSnapshot(); void commitPreparedStepSnapshot();
private: private:

View file

@ -157,7 +157,7 @@ struct Frontend
ScopePtr getGlobalScope(); ScopePtr getGlobalScope();
private: private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope); ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles);
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name); std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions); SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);

View file

@ -40,6 +40,9 @@ struct Scope
std::optional<TypePackId> varargPack; std::optional<TypePackId> varargPack;
// All constraints belonging to this scope. // All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// Constraints belonging to this scope that are queued manually by other
// constraints.
std::vector<ConstraintPtr> unqueuedConstraints;
TypeLevel level; TypeLevel level;

View file

@ -92,7 +92,6 @@ inline std::string toString(const Constraint& c)
return toString(c, ToStringOptions{}); return toString(c, ToStringOptions{});
} }
std::string toString(const TypeVar& tv, ToStringOptions& opts); std::string toString(const TypeVar& tv, ToStringOptions& opts);
std::string toString(const TypePackVar& tp, ToStringOptions& opts); std::string toString(const TypePackVar& tp, ToStringOptions& opts);

View file

@ -29,9 +29,10 @@ struct TypeArena
TypeId addTV(TypeVar&& tv); TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level); TypeId freshType(TypeLevel level);
TypeId freshType(Scope* scope);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types); TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
TypePackId addTypePack(TypePack pack); TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack); TypePackId addTypePack(TypePackVar pack);

View file

@ -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);
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options); 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. /** Attempt to unify the types.
* If this fails, and the subTy type can be instantiated, do so and try unification again. * If this fails, and the subTy type can be instantiated, do so and try unification again.

View file

@ -15,6 +15,7 @@ struct TypeArena;
struct TypePack; struct TypePack;
struct VariadicTypePack; struct VariadicTypePack;
struct BlockedTypePack;
struct TypePackVar; struct TypePackVar;
@ -24,7 +25,7 @@ using TypePackId = const TypePackVar*;
using FreeTypePack = Unifiable::Free; using FreeTypePack = Unifiable::Free;
using BoundTypePack = Unifiable::Bound<TypePackId>; using BoundTypePack = Unifiable::Bound<TypePackId>;
using GenericTypePack = Unifiable::Generic; using GenericTypePack = Unifiable::Generic;
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack>; using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack, BlockedTypePack>;
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode /* 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 * 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. 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 struct TypePackVar
{ {
explicit TypePackVar(const TypePackVariant& ty); explicit TypePackVar(const TypePackVariant& ty);

View file

@ -11,12 +11,16 @@
namespace Luau namespace Luau
{ {
struct TxnLog;
using ScopePtr = std::shared_ptr<struct Scope>; using ScopePtr = std::shared_ptr<struct Scope>;
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location); std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location);
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location); std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location);
std::optional<TypeId> getIndexTypeFromType( std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors, const Location& location, bool addErrors, InternalErrorReporter& handle);
InternalErrorReporter& handle);
// Returns the minimum and maximum number of types the argument list can accept.
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp);
} // namespace Luau } // namespace Luau

View file

@ -1,11 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Ast.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Predicate.h" #include "Luau/Predicate.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/NotNull.h"
#include <set> #include <set>
#include <string> #include <string>
@ -262,6 +264,8 @@ struct WithPredicate
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>( using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>; struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
using DcrMagicFunction = std::function<bool(NotNull<struct ConstraintSolver>, TypePackId, const class AstExprCall*)>;
struct FunctionTypeVar struct FunctionTypeVar
{ {
// Global monomorphic function // Global monomorphic function
@ -287,7 +291,8 @@ struct FunctionTypeVar
std::vector<std::optional<FunctionArgument>> argNames; std::vector<std::optional<FunctionArgument>> argNames;
TypePackId retTypes; TypePackId retTypes;
std::optional<FunctionDefinition> definition; std::optional<FunctionDefinition> 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; bool hasSelf;
Tags tags; Tags tags;
bool hasNoGenerics = false; bool hasNoGenerics = false;
@ -462,8 +467,9 @@ struct TypeFun
*/ */
struct PendingExpansionTypeVar struct PendingExpansionTypeVar
{ {
PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments); PendingExpansionTypeVar(std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments);
TypeFun fn; std::optional<AstName> prefix;
AstName name;
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
size_t index; size_t index;

View file

@ -59,7 +59,8 @@ struct Unifier
UnifierSharedState& sharedState; UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result. // Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy); ErrorVec canUnify(TypeId subTy, TypeId superTy);

View file

@ -188,6 +188,10 @@ struct GenericTypeVarVisitor
{ {
return visit(tp); return visit(tp);
} }
virtual bool visit(TypePackId tp, const BlockedTypePack& btp)
{
return visit(tp);
}
void traverse(TypeId ty) void traverse(TypeId ty)
{ {
@ -314,24 +318,6 @@ struct GenericTypeVarVisitor
{ {
if (visit(ty, *petv)) 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) for (TypeId a : petv->typeArguments)
traverse(a); traverse(a);
@ -388,6 +374,9 @@ struct GenericTypeVarVisitor
if (res) if (res)
traverse(pack->ty); traverse(pack->ty);
} }
else if (auto btp = get<BlockedTypePack>(tp))
visit(tp, *btp);
else else
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"); LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");

View file

@ -11,15 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau namespace Luau
{ {
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack) Anyification::Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
, scope(NotNull{scope.get()}) , scope(scope)
, iceHandler(iceHandler) , iceHandler(iceHandler)
, anyType(anyType) , anyType(anyType)
, anyTypePack(anyTypePack) , 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) bool Anyification::isDirty(TypeId ty)
{ {
if (ty->persistent) if (ty->persistent)
@ -93,4 +98,4 @@ bool Anyification::ignoreChildren(TypePackId ty)
return ty->persistent; return ty->persistent;
} }
} } // namespace Luau

View file

@ -1215,8 +1215,8 @@ static bool autocompleteIfElseExpression(
} }
} }
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena, static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result) TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{ {
LUAU_ASSERT(!ancestry.empty()); LUAU_ASSERT(!ancestry.empty());
@ -1422,8 +1422,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point; PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty)) if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
return { return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry,
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property}; AutocompleteContext::Property};
else else
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property}; 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<AstStatIf>(); statIf && !statIf->elseLocation.has_value()) else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{ {
return { return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; ancestry, AutocompleteContext::Keyword};
} }
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>()) else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
{ {

View file

@ -5,6 +5,7 @@
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include <algorithm> #include <algorithm>
@ -32,6 +33,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire( static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate); TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> solver, TypePackId result, const AstExprCall* expr);
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types) TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
{ {
return arena.addType(UnionTypeVar{std::move(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"); LUAU_ASSERT(!"Got a non functional type");
} }
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
{
if (auto ftv = getMutable<FunctionTypeVar>(ty))
ftv->dcrMagicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol) Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
{ {
return { return {
@ -263,6 +274,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
} }
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire); attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
} }
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
@ -509,4 +521,49 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
return std::nullopt; return std::nullopt;
} }
static bool checkRequirePathDcr(NotNull<ConstraintSolver> 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<AstExprIndexName>();
while (indexExpr)
{
if (indexExpr->index == "parent")
{
solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation);
good = false;
}
indexExpr = indexExpr->expr->as<AstExprIndexName>();
}
return good;
}
static bool dcrMagicFunctionRequire(NotNull<ConstraintSolver> 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<BoundTypePack>(moduleResult);
return true;
}
return false;
}
} // namespace Luau } // namespace Luau

View file

@ -102,6 +102,11 @@ struct TypePackCloner
defaultClone(t); 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. // 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. // 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<TypePackId>& t) void operator()(const Unifiable::Bound<TypePackId>& t)
@ -170,7 +175,7 @@ void TypeCloner::operator()(const BlockedTypeVar& t)
void TypeCloner::operator()(const PendingExpansionTypeVar& 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<PendingExpansionTypeVar>(res); PendingExpansionTypeVar* petv = getMutable<PendingExpansionTypeVar>(res);
LUAU_ASSERT(petv); LUAU_ASSERT(petv);
@ -184,32 +189,6 @@ void TypeCloner::operator()(const PendingExpansionTypeVar& t)
for (TypePackId arg : t.packArguments) for (TypePackId arg : t.packArguments)
packArguments.push_back(clone(arg, dest, cloneState)); 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<TypeId> 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<TypePackId> 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->typeArguments = std::move(typeArguments);
petv->packArguments = std::move(packArguments); 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.generics = ftv->generics;
clone.genericPacks = ftv->genericPacks; clone.genericPacks = ftv->genericPacks;
clone.magicFunction = ftv->magicFunction; clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;
result = dest.addType(std::move(clone)); 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<PendingExpansionTypeVar>(ty)) else if (const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(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)); result = dest.addType(std::move(clone));
} }
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone) else if (const ClassTypeVar* ctv = get<ClassTypeVar>(ty); FFlag::LuauClonePublicInterfaceLess && ctv && alwaysClone)

View file

@ -5,9 +5,10 @@
namespace Luau namespace Luau
{ {
Constraint::Constraint(ConstraintV&& c, NotNull<Scope> scope) Constraint::Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c)
: c(std::move(c)) : scope(scope)
, scope(scope) , location(location)
, c(std::move(c))
{ {
} }

View file

@ -4,6 +4,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ModuleResolver.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
@ -16,13 +17,31 @@ namespace Luau
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
ConstraintGraphBuilder::ConstraintGraphBuilder( static std::optional<AstExpr*> matchRequire(const AstExprCall& call)
const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope) {
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
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> moduleResolver, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope)
: moduleName(moduleName) : moduleName(moduleName)
, module(module) , module(module)
, singletonTypes(getSingletonTypes()) , singletonTypes(getSingletonTypes())
, arena(arena) , arena(arena)
, rootScope(nullptr) , rootScope(nullptr)
, moduleResolver(moduleResolver)
, ice(ice) , ice(ice)
, globalScope(globalScope) , globalScope(globalScope)
{ {
@ -54,9 +73,9 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
return scope; 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<Constraint> c) void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
@ -77,13 +96,6 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block)
prepopulateGlobalScope(scope, 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); visitBlockWithoutChildScope(scope, block);
} }
@ -158,6 +170,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatFor>()) else if (auto s = stat->as<AstStatFor>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatForIn>())
visit(scope, s);
else if (auto s = stat->as<AstStatWhile>()) else if (auto s = stat->as<AstStatWhile>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatRepeat>()) else if (auto s = stat->as<AstStatRepeat>())
@ -201,7 +215,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
{ {
location = local->annotation->location; location = local->annotation->location;
TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true); TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true);
addConstraint(scope, SubtypeConstraint{ty, annotation}); addConstraint(scope, location, SubtypeConstraint{ty, annotation});
} }
varTypes.push_back(ty); varTypes.push_back(ty);
@ -225,14 +239,38 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
{ {
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()}; std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
TypePackId tailPack = arena->addTypePack(std::move(tailValues)); TypePackId tailPack = arena->addTypePack(std::move(tailValues));
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack}); addConstraint(scope, local->location, PackSubtypeConstraint{exprPack, tailPack});
} }
} }
else else
{ {
TypeId exprType = check(scope, value); TypeId exprType = check(scope, value);
if (i < varTypes.size()) 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<AstExprCall>();
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; return;
TypeId t = check(scope, expr); TypeId t = check(scope, expr);
addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType}); addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes.numberType});
}; };
checkNumber(for_->from); checkNumber(for_->from);
@ -257,6 +295,29 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
visit(forScope, for_->body); 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<TypeId> 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_) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_)
{ {
check(scope, while_->condition); check(scope, while_->condition);
@ -284,6 +345,9 @@ void addConstraints(Constraint* constraint, NotNull<Scope> scope)
for (const auto& c : scope->constraints) for (const auto& c : scope->constraints)
constraint->dependencies.push_back(NotNull{c.get()}); constraint->dependencies.push_back(NotNull{c.get()});
for (const auto& c : scope->unqueuedConstraints)
constraint->dependencies.push_back(NotNull{c.get()});
for (NotNull<Scope> childScope : scope->children) for (NotNull<Scope> childScope : scope->children)
addConstraints(constraint, childScope); addConstraints(constraint, childScope);
} }
@ -308,7 +372,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope); std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraints(c.get(), NotNull(sig.bodyScope.get()));
addConstraint(scope, std::move(c)); addConstraint(scope, std::move(c));
@ -366,7 +431,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
prop.type = functionType; prop.type = functionType;
prop.location = function->name->location; prop.location = function->name->location;
addConstraint(scope, SubtypeConstraint{containingTableType, prospectiveTableType}); addConstraint(scope, indexName->location, SubtypeConstraint{containingTableType, prospectiveTableType});
} }
else if (AstExprError* err = function->name->as<AstExprError>()) else if (AstExprError* err = function->name->as<AstExprError>())
{ {
@ -378,7 +443,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(GeneralizationConstraint{functionType, sig.signature}, constraintScope); std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraints(c.get(), NotNull(sig.bodyScope.get()));
addConstraint(scope, std::move(c)); 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) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
{ {
TypePackId exprTypes = checkPack(scope, ret->list); 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) 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) 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); 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) 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) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
{ {
// TODO: Exported type aliases
auto bindingIt = scope->privateTypeBindings.find(alias->name.value); auto bindingIt = scope->privateTypeBindings.find(alias->name.value);
ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias); ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias);
// These will be undefined if the alias was a duplicate definition, in which // 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; ScopePtr resolvingScope = *defnIt;
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true); TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true);
if (alias->exported)
{
Name typeName(alias->name.value);
scope->exportedTypeBindings[typeName] = TypeFun{ty};
}
LUAU_ASSERT(get<FreeTypeVar>(bindingIt->second.type)); LUAU_ASSERT(get<FreeTypeVar>(bindingIt->second.type));
// Rather than using a subtype constraint, we instead directly bind // 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. // bind the free alias type to an unrelated type, causing havoc.
asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(ty); asMutable(bindingIt->second.type)->ty.emplace<BoundTypeVar>(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) 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<AstExpr*> exprs) TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs)
{ {
if (exprs.size == 0) std::vector<TypeId> head;
return arena->addTypePack({}); std::optional<TypePackId> tail;
std::vector<TypeId> 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<AstExpr*>& exprs)
{
TypePackId result = arena->addTypePack({});
TypePack* resultPack = getMutable<TypePack>(result);
LUAU_ASSERT(resultPack);
for (size_t i = 0; i < exprs.size; ++i) for (size_t i = 0; i < exprs.size; ++i)
{ {
AstExpr* expr = exprs.data[i]; AstExpr* expr = exprs.data[i];
if (i < exprs.size - 1) if (i < exprs.size - 1)
resultPack->head.push_back(check(scope, expr)); head.push_back(check(scope, expr));
else else
resultPack->tail = checkPack(scope, expr); tail = checkPack(scope, expr);
} }
if (resultPack->head.empty() && resultPack->tail) if (head.empty() && tail)
return *resultPack->tail; return *tail;
else else
return result; return arena->addTypePack(TypePack{std::move(head), tail});
} }
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr) TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
@ -683,13 +731,26 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
astOriginalCallTypes[call->func] = fnType; astOriginalCallTypes[call->func] = fnType;
TypeId instantiatedType = arena->addType(BlockedTypeVar{}); TypeId instantiatedType = arena->addType(BlockedTypeVar{});
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType}); TypePackId rets = arena->addTypePack(BlockedTypePack{});
TypePackId rets = freshTypePack(scope);
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets); FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
TypeId inferredFnType = arena->addType(ftv); TypeId inferredFnType = arena->addType(ftv);
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType}); scope->unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
scope->unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
NotNull<const Constraint> sc(scope->unqueuedConstraints.back().get());
addConstraint(scope, call->func->location,
FunctionCallConstraint{
{ic, sc},
fnType,
rets,
call,
});
result = rets; result = rets;
} }
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>()) else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
@ -805,7 +866,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
TypeId expectedTableType = arena->addType(std::move(ttv)); TypeId expectedTableType = arena->addType(std::move(ttv));
addConstraint(scope, SubtypeConstraint{obj, expectedTableType}); addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
return result; return result;
} }
@ -820,7 +881,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
TableIndexer indexer{indexType, result}; TableIndexer indexer{indexType, result};
TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free}); TypeId tableType = arena->addType(TableTypeVar{TableTypeVar::Props{}, TableIndexer{indexType, result}, TypeLevel{}, TableState::Free});
addConstraint(scope, SubtypeConstraint{obj, tableType}); addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
return result; return result;
} }
@ -834,7 +895,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
case AstExprUnary::Minus: case AstExprUnary::Minus:
{ {
TypeId resultType = arena->addType(BlockedTypeVar{}); TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, UnaryConstraint{AstExprUnary::Minus, operandType, resultType}); addConstraint(scope, unary->location, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
return resultType; return resultType;
} }
default: default:
@ -853,19 +914,19 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
{ {
case AstExprBinary::Or: case AstExprBinary::Or:
{ {
addConstraint(scope, SubtypeConstraint{leftType, rightType}); addConstraint(scope, binary->location, SubtypeConstraint{leftType, rightType});
return leftType; return leftType;
} }
case AstExprBinary::Add: case AstExprBinary::Add:
{ {
TypeId resultType = arena->addType(BlockedTypeVar{}); 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; return resultType;
} }
case AstExprBinary::Sub: case AstExprBinary::Sub:
{ {
TypeId resultType = arena->addType(BlockedTypeVar{}); 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; return resultType;
} }
default: default:
@ -886,8 +947,8 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifEls
if (ifElse->hasElse) if (ifElse->hasElse)
{ {
TypeId resultType = arena->addType(BlockedTypeVar{}); TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, SubtypeConstraint{thenType, resultType}); addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
addConstraint(scope, SubtypeConstraint{elseType, resultType}); addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
return resultType; return resultType;
} }
@ -906,7 +967,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
TableTypeVar* ttv = getMutable<TableTypeVar>(ty); TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(ttv); 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) if (!ttv->indexer)
{ {
TypeId indexType = this->freshType(scope); TypeId indexType = this->freshType(scope);
@ -914,8 +975,8 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
ttv->indexer = TableIndexer{indexType, resultType}; ttv->indexer = TableIndexer{indexType, resultType};
} }
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType}); addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType}); addConstraint(scope, location, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
}; };
for (const AstExprTable::Item& item : expr->items) for (const AstExprTable::Item& item : expr->items)
@ -937,13 +998,15 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
} }
else else
{ {
createIndexer(keyTy, itemTy); createIndexer(item.key->location, keyTy, itemTy);
} }
} }
else else
{ {
TypeId numberType = singletonTypes.numberType; 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) if (fn->returnAnnotation)
{ {
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType}); addConstraint(signatureScope, getLocation(*fn->returnAnnotation), PackSubtypeConstraint{returnType, annotatedRetType});
} }
std::vector<TypeId> argTypes; std::vector<TypeId> argTypes;
@ -1022,7 +1085,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (local->annotation) if (local->annotation)
{ {
TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true); 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)) if (nullptr != getFallthrough(fn->body))
{ {
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever 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<AstTypeReference>()) if (auto ref = ty->as<AstTypeReference>())
{ {
// TODO: Support imported types w/ require tracing.
LUAU_ASSERT(!ref->prefix);
std::optional<TypeFun> alias = scope->lookupType(ref->name.value); std::optional<TypeFun> 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 // If the alias is not generic, we don't need to set up a blocked
// type and an instantiation constraint. // 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; 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) 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, scope->level, TableState::Sealed});
result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed});
} }
else if (auto fn = ty->as<AstTypeFunction>()) else if (auto fn = ty->as<AstTypeFunction>())
{ {
@ -1363,7 +1422,7 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat
TypePack onePack{{typeResult}, freshTypePack(scope)}; TypePack onePack{{typeResult}, freshTypePack(scope)};
TypePackId oneTypePack = arena->addTypePack(std::move(onePack)); TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
addConstraint(scope, PackSubtypeConstraint{tp, oneTypePack}); addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
return typeResult; return typeResult;
} }

View file

@ -1,9 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Anyification.h"
#include "Luau/ApplyTypeFunction.h" #include "Luau/ApplyTypeFunction.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/Instantiation.h" #include "Luau/Instantiation.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Quantify.h" #include "Luau/Quantify.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
@ -240,11 +242,17 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
} }
} }
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope) ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles)
: arena(arena) : arena(arena)
, constraints(collectConstraints(rootScope)) , constraints(collectConstraints(rootScope))
, rootScope(rootScope) , rootScope(rootScope)
, currentModuleName(std::move(moduleName))
, moduleResolver(moduleResolver)
, requireCycles(requireCycles)
{ {
opts.exhaustive = true;
for (NotNull<Constraint> c : constraints) for (NotNull<Constraint> c : constraints)
{ {
unsolvedConstraints.push_back(c); unsolvedConstraints.push_back(c);
@ -261,9 +269,6 @@ void ConstraintSolver::run()
if (done()) if (done())
return; return;
ToStringOptions opts;
opts.exhaustive = true;
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
{ {
printf("Starting solver\n"); printf("Starting solver\n");
@ -371,10 +376,14 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*uc, constraint, force); success = tryDispatch(*uc, constraint, force);
else if (auto bc = get<BinaryConstraint>(*constraint)) else if (auto bc = get<BinaryConstraint>(*constraint))
success = tryDispatch(*bc, constraint, force); success = tryDispatch(*bc, constraint, force);
else if (auto ic = get<IterableConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint)) else if (auto nc = get<NameConstraint>(*constraint))
success = tryDispatch(*nc, constraint); success = tryDispatch(*nc, constraint);
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint)) else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
success = tryDispatch(*taec, constraint); success = tryDispatch(*taec, constraint);
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else else
LUAU_ASSERT(0); LUAU_ASSERT(0);
@ -400,6 +409,11 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> 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); unify(c.subPack, c.superPack, constraint->scope);
return true; return true;
} }
@ -512,6 +526,82 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
return true; return true;
} }
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> 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<TypePackId> anyified = anyify.substitute(c.variables);
LUAU_ASSERT(anyified);
unify(*anyified, c.variables, constraint->scope);
return true;
}
TypeId nextTy = follow(iteratorTypes[0]);
if (get<FreeTypeVar>(nextTy))
return block_(nextTy);
if (get<FunctionTypeVar>(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<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
{ {
if (isBlocked(c.namedType)) if (isBlocked(c.namedType))
@ -519,7 +609,7 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
TypeId target = follow(c.namedType); TypeId target = follow(c.namedType);
if (target->persistent) if (target->persistent || target->owningArena != arena)
return true; return true;
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target)) if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
@ -536,19 +626,27 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor
{ {
ConstraintSolver* solver; ConstraintSolver* solver;
const InstantiationSignature& signature; const InstantiationSignature& signature;
NotNull<Scope> scope;
bool foundInfiniteType = false; bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature) explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
: solver(solver) : solver(solver)
, signature(signature) , signature(signature)
, scope(scope)
{ {
} }
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
{ {
auto [typeArguments, packArguments] = saturateArguments(petv.fn, petv.typeArguments, petv.packArguments, solver->arena); std::optional<TypeFun> 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; foundInfiniteType = true;
return false; return false;
@ -563,17 +661,19 @@ struct InstantiationQueuer : TypeVarOnceVisitor
ConstraintSolver* solver; ConstraintSolver* solver;
const InstantiationSignature& signature; const InstantiationSignature& signature;
NotNull<Scope> scope; NotNull<Scope> scope;
Location location;
explicit InstantiationQueuer(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope) explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature)
: solver(solver) : solver(solver)
, signature(signature) , signature(signature)
, scope(scope) , scope(scope)
, location(location)
{ {
} }
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
{ {
solver->pushConstraint(TypeAliasExpansionConstraint{ty}, scope); solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
return false; return false;
} }
}; };
@ -592,23 +692,32 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
unblock(c.target); unblock(c.target);
}; };
// If there are no parameters to the type function we can just use the type std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
// directly. : constraint->scope->lookupType(petv->name.value);
if (petv->fn.typeParams.empty() && petv->fn.typePackParams.empty())
if (!tf.has_value())
{ {
bindResult(petv->fn.type); reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
bindResult(getSingletonTypes().errorRecoveryType());
return true; 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 = auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena);
std::equal(typeArguments.begin(), typeArguments.end(), petv->fn.typeParams.begin(), petv->fn.typeParams.end(), [](auto&& itp, auto&& p) {
return itp == p.ty;
});
bool samePacks = std::equal( bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
packArguments.begin(), packArguments.end(), petv->fn.typePackParams.begin(), petv->fn.typePackParams.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; return itp == p.tp;
}); });
@ -617,12 +726,12 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// to the TypeFun's type. // to the TypeFun's type.
if (sameTypes && samePacks) if (sameTypes && samePacks)
{ {
bindResult(petv->fn.type); bindResult(tf->type);
return true; return true;
} }
InstantiationSignature signature{ InstantiationSignature signature{
petv->fn, *tf,
typeArguments, typeArguments,
packArguments, 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. // 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 // This is a little nicer than using a recursion limit because we can catch
// the infinite expansion before actually trying to expand it. // the infinite expansion before actually trying to expand it.
InfiniteTypeFinder itf{this, signature}; InfiniteTypeFinder itf{this, signature, constraint->scope};
itf.traverse(petv->fn.type); itf.traverse(tf->type);
if (itf.foundInfiniteType) if (itf.foundInfiniteType)
{ {
@ -655,15 +764,15 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
ApplyTypeFunction applyTypeFunction{arena}; ApplyTypeFunction applyTypeFunction{arena};
for (size_t i = 0; i < typeArguments.size(); ++i) 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) 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<TypeId> maybeInstantiated = applyTypeFunction.substitute(petv->fn.type); std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf->type);
// Note that ApplyTypeFunction::encounteredForwardedType is never set in // Note that ApplyTypeFunction::encounteredForwardedType is never set in
// DCR, because we do not use free types for forward-declared generic // DCR, because we do not use free types for forward-declared generic
// aliases. // aliases.
@ -683,7 +792,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// Type function application will happily give us the exact same type if // Type function application will happily give us the exact same type if
// there are e.g. generic saturatedTypeArguments that go unused. // 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. // Only tables have the properties we're trying to set.
TableTypeVar* ttv = getMutableTableType(target); 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 // 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 // any child type function instantiations within the result in order for it
// to be complete. // to be complete.
InstantiationQueuer queuer{this, signature, constraint->scope}; InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature};
queuer.traverse(target); queuer.traverse(target);
instantiatedAliases[signature] = target; instantiatedAliases[signature] = target;
@ -730,6 +839,152 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true; return true;
} }
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
{
TypeId fn = follow(c.fn);
TypePackId result = follow(c.result);
if (isBlocked(c.fn))
{
return block(c.fn, constraint);
}
const FunctionTypeVar* ftv = get<FunctionTypeVar>(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<FreeTypePack>(constraint->scope);
}
unblock(c.result);
return true;
}
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> 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<FreeTypeVar>(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<AnyTypeVar>(iteratorTy))
{
anyify(c.variables);
return true;
}
if (get<ErrorTypeVar>(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<TableTypeVar>(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<MetatableTypeVar>(iteratorTy))
{
TypeId metaTy = follow(iteratorMetatable->metatable);
if (get<FreeTypeVar>(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<const Constraint> 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<FreeTypeVar>(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<const Constraint> constraint) void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
{ {
blocked[target].push_back(constraint); blocked[target].push_back(constraint);
@ -741,14 +996,14 @@ void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constrai
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint) void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
{ {
if (FFlag::DebugLuauLogSolver) 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); block_(target, constraint);
} }
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint) bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
{ {
if (FFlag::DebugLuauLogSolver) 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); block_(target, constraint);
return false; return false;
} }
@ -756,7 +1011,7 @@ bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint) bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
{ {
if (FFlag::DebugLuauLogSolver) 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); block_(target, constraint);
return false; return false;
} }
@ -772,7 +1027,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
{ {
auto& count = blockedConstraints[unblockedConstraint]; auto& count = blockedConstraints[unblockedConstraint];
if (FFlag::DebugLuauLogSolver) 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 // This assertion being hit indicates that `blocked` and
// `blockedConstraints` desynchronized at some point. This is problematic // `blockedConstraints` desynchronized at some point. This is problematic
@ -817,6 +1072,11 @@ bool ConstraintSolver::isBlocked(TypeId ty)
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty)); return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
} }
bool ConstraintSolver::isBlocked(TypePackId tp)
{
return nullptr != get<BlockedTypePack>(follow(tp));
}
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint) bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
{ {
auto blockedIt = blockedConstraints.find(constraint); auto blockedIt = blockedConstraints.find(constraint);
@ -830,6 +1090,13 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
u.tryUnify(subType, superType); 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(); const auto [changedTypes, changedPacks] = u.log.getChanges();
u.log.commit(); u.log.commit();
@ -853,22 +1120,69 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
unblock(changedPacks); unblock(changedPacks);
} }
void ConstraintSolver::pushConstraint(ConstraintV cv, NotNull<Scope> scope) void ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
{ {
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(std::move(cv), scope); std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
NotNull<Constraint> borrow = NotNull(c.get()); NotNull<Constraint> borrow = NotNull(c.get());
solverConstraints.push_back(std::move(c)); solverConstraints.push_back(std::move(c));
unsolvedConstraints.push_back(borrow); 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<Unifiable::Error>(modulePack))
return getSingletonTypes().errorRecoveryType();
std::optional<TypeId> 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) void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location)
{ {
errors.emplace_back(location, std::move(data)); errors.emplace_back(location, std::move(data));
errors.back().moduleName = currentModuleName;
} }
void ConstraintSolver::reportError(TypeError e) void ConstraintSolver::reportError(TypeError e)
{ {
errors.emplace_back(std::move(e)); errors.emplace_back(std::move(e));
errors.back().moduleName = currentModuleName;
} }
} // namespace Luau } // namespace Luau

View file

@ -3,6 +3,7 @@
#include "Luau/ConstraintSolverLogger.h" #include "Luau/ConstraintSolverLogger.h"
#include "Luau/JsonEmitter.h" #include "Luau/JsonEmitter.h"
#include "Luau/ToString.h"
LUAU_FASTFLAG(LuauFixNameMaps); LUAU_FASTFLAG(LuauFixNameMaps);

View file

@ -14,6 +14,7 @@
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/BuiltinDefinitions.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@ -99,7 +100,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
module.root = parseResult.root; module.root = parseResult.root;
module.mode = Mode::Definition; module.mode = Mode::Definition;
ModulePtr checkedModule = check(module, Mode::Definition, globalScope); ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {});
if (checkedModule->errors.size() > 0) if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, checkedModule}; return LoadDefinitionFileResult{false, parseResult, checkedModule};
@ -526,7 +527,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeChecker.requireCycles = requireCycles; typeChecker.requireCycles = requireCycles;
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope) ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope, requireCycles)
: typeChecker.check(sourceModule, mode, environmentScope); : typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp; stats.timeCheck += getTimestamp() - timestamp;
@ -832,15 +833,15 @@ ScopePtr Frontend::getGlobalScope()
return globalScope; return globalScope;
} }
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope) ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles)
{ {
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
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); cgb.visit(sourceModule.root);
result->errors = std::move(cgb.errors); 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(); cs.run();
for (TypeError& e : cs.errors) 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->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
result->astResolvedTypes = std::move(cgb.astResolvedTypes); result->astResolvedTypes = std::move(cgb.astResolvedTypes);
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks); result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
result->type = sourceModule.type;
result->clonePublicInterface(iceHandler);
Luau::check(sourceModule, result.get()); Luau::check(sourceModule, result.get());
result->clonePublicInterface(iceHandler);
return result; return result;
} }

View file

@ -46,6 +46,7 @@ TypeId Instantiation::clean(TypeId ty)
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction; clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone)); TypeId result = addType(std::move(clone));

View file

@ -11,7 +11,8 @@ namespace Luau::Json
static constexpr int CHUNK_SIZE = 1024; static constexpr int CHUNK_SIZE = 1024;
ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> emitter) ObjectEmitter::ObjectEmitter(NotNull<JsonEmitter> emitter)
: emitter(emitter), finished(false) : emitter(emitter)
, finished(false)
{ {
comma = emitter->pushComma(); comma = emitter->pushComma();
emitter->writeRaw('{'); emitter->writeRaw('{');
@ -33,7 +34,8 @@ void ObjectEmitter::finish()
} }
ArrayEmitter::ArrayEmitter(NotNull<JsonEmitter> emitter) ArrayEmitter::ArrayEmitter(NotNull<JsonEmitter> emitter)
: emitter(emitter), finished(false) : emitter(emitter)
, finished(false)
{ {
comma = emitter->pushComma(); comma = emitter->pushComma();
emitter->writeRaw('['); emitter->writeRaw('[');

View file

@ -216,7 +216,8 @@ static bool similar(AstExpr* lhs, AstExpr* rhs)
return false; return false;
for (size_t i = 0; i < le->strings.size; ++i) 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; return false;
for (size_t i = 0; i < le->expressions.size; ++i) for (size_t i = 0; i < le->expressions.size; ++i)
@ -2675,13 +2676,18 @@ public:
private: private:
LintContext* context; 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 || return op == AstExprBinary::CompareNe || op == AstExprBinary::CompareEq || op == AstExprBinary::CompareLt || op == AstExprBinary::CompareLe ||
op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe; op == AstExprBinary::CompareGt || op == AstExprBinary::CompareGe;
} }
bool isNot(AstExpr* node) static bool isNot(AstExpr* node)
{ {
AstExprUnary* expr = node->as<AstExprUnary>(); AstExprUnary* expr = node->as<AstExprUnary>();
@ -2698,22 +2704,26 @@ private:
{ {
std::string op = toString(node->op); 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, 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", "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(),
op.c_str(), op.c_str(), node->op == AstExprBinary::CompareEq ? "~=" : "=="); node->op == AstExprBinary::CompareEq ? "~=" : "==");
else else
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, 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<AstExprBinary>(); left && isComparison(left->op)) else if (AstExprBinary* left = node->left->as<AstExprBinary>(); left && isComparison(left->op))
{ {
std::string lop = toString(left->op); std::string lop = toString(left->op);
std::string rop = toString(node->op); std::string rop = toString(node->op);
emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location, if (isEquality(left->op) || isEquality(node->op))
"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(), emitWarning(*context, LintWarning::Code_ComparisonPrecedence, node->location,
lop.c_str(), rop.c_str()); "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; return true;

View file

@ -219,8 +219,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
TypePackId returnType = moduleScope->returnType; TypePackId returnType = moduleScope->returnType;
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack; std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
std::unordered_map<Name, TypeFun>* exportedTypeBindings = std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
TxnLog log; TxnLog log;
ClonePublicInterface clonePublicInterface{&log, this}; ClonePublicInterface clonePublicInterface{&log, this};

View file

@ -510,7 +510,7 @@ void Substitution::foundDirty(TypeId ty)
ty = log->follow(ty); ty = log->follow(ty);
if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty)) if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty))
return; return;
if (isDirty(ty)) if (isDirty(ty))
newTypes[ty] = follow(clean(ty)); newTypes[ty] = follow(clean(ty));
@ -523,7 +523,7 @@ void Substitution::foundDirty(TypePackId tp)
tp = log->follow(tp); tp = log->follow(tp);
if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp)) if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp))
return; return;
if (isDirty(tp)) if (isDirty(tp))
newPacks[tp] = follow(clean(tp)); newPacks[tp] = follow(clean(tp));

View file

@ -1036,6 +1036,13 @@ struct TypePackStringifier
{ {
stringify(btv.boundTo); stringify(btv.boundTo);
} }
void operator()(TypePackId, const BlockedTypePack& btp)
{
state.emit("*blocked-tp-");
state.emit(btp.index);
state.emit("*");
}
}; };
void TypeVarStringifier::stringify(TypePackId tp) void TypeVarStringifier::stringify(TypePackId tp)
@ -1099,9 +1106,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
ToStringResult result; ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps StringifierState state =
? StringifierState{opts, result, opts.nameMap} FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
std::set<TypeId> cycles; std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs; std::set<TypePackId> 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. * 4. Print out the root of the type using the same algorithm as step 3.
*/ */
ToStringResult result; ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps StringifierState state =
? StringifierState{opts, result, opts.nameMap} FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
std::set<TypeId> cycles; std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs; std::set<TypePackId> 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) std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts)
{ {
ToStringResult result; ToStringResult result;
StringifierState state = FFlag::LuauFixNameMaps StringifierState state =
? StringifierState{opts, result, opts.nameMap} FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
: StringifierState{opts, result, opts.DEPRECATED_nameMap};
TypeVarStringifier tvs{state}; TypeVarStringifier tvs{state};
state.emit(funcName); state.emit(funcName);
@ -1437,8 +1441,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
using T = std::decay_t<decltype(c)>; using T = std::decay_t<decltype(c)>;
// TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps // 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) if (FFlag::LuauFixNameMaps)
return toString(a, opts); return toString(a, opts);
else else
@ -1488,6 +1491,13 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">"; return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">";
} }
else if constexpr (std::is_same_v<T, IterableConstraint>)
{
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<T, NameConstraint>) else if constexpr (std::is_same_v<T, NameConstraint>)
{ {
std::string namedStr = tos(c.namedType, opts); 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); std::string targetStr = tos(c.target, opts);
return "expand " + targetStr; return "expand " + targetStr;
} }
else if constexpr (std::is_same_v<T, FunctionCallConstraint>)
{
return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }";
}
else else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch"); static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
}; };

View file

@ -31,6 +31,15 @@ TypeId TypeArena::freshType(TypeLevel level)
return allocated; 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<TypeId> types) TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
@ -40,9 +49,9 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
return allocated; return allocated;
} }
TypePackId TypeArena::addTypePack(std::vector<TypeId> types) TypePackId TypeArena::addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types), tail});
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;

View file

@ -373,6 +373,11 @@ public:
return Luau::visit(*this, btp.boundTo->ty); return Luau::visit(*this, btp.boundTo->ty);
} }
AstTypePack* operator()(const BlockedTypePack& btp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("*blocked*"));
}
AstTypePack* operator()(const TypePack& tp) const AstTypePack* operator()(const TypePack& tp) const
{ {
AstArray<AstType*> head; AstArray<AstType*> head;

View file

@ -166,7 +166,8 @@ struct TypeChecker2
auto pusher = pushStack(stat); auto pusher = pushStack(stat);
if (0) if (0)
{} {
}
else if (auto s = stat->as<AstStatBlock>()) else if (auto s = stat->as<AstStatBlock>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatIf>()) else if (auto s = stat->as<AstStatIf>())
@ -239,11 +240,9 @@ struct TypeChecker2
visit(repeatStatement->condition); visit(repeatStatement->condition);
} }
void visit(AstStatBreak*) void visit(AstStatBreak*) {}
{}
void visit(AstStatContinue*) void visit(AstStatContinue*) {}
{}
void visit(AstStatReturn* ret) void visit(AstStatReturn* ret)
{ {
@ -339,6 +338,50 @@ struct TypeChecker2
visit(forStatement->body); visit(forStatement->body);
} }
// "Render" a type pack out to an array of a given length. Expands
// variadics and various other things to get there.
static std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
{
std::vector<TypeId> result;
auto it = begin(pack);
auto endIt = end(pack);
while (it != endIt)
{
result.push_back(*it);
if (result.size() >= length)
return result;
++it;
}
if (!it.tail())
return result;
TypePackId tail = *it.tail();
if (get<TypePack>(tail))
LUAU_ASSERT(0);
else if (auto vtp = get<VariadicTypePack>(tail))
{
while (result.size() < length)
result.push_back(vtp->ty);
}
else if (get<FreeTypePack>(tail) || get<GenericTypePack>(tail))
{
while (result.size() < length)
result.push_back(arena.addType(FreeTypeVar{nullptr}));
}
else if (auto etp = get<Unifiable::Error>(tail))
{
while (result.size() < length)
result.push_back(getSingletonTypes().errorRecoveryType());
}
return result;
}
void visit(AstStatForIn* forInStatement) void visit(AstStatForIn* forInStatement)
{ {
for (AstLocal* local : forInStatement->vars) for (AstLocal* local : forInStatement->vars)
@ -351,6 +394,128 @@ struct TypeChecker2
visit(expr); visit(expr);
visit(forInStatement->body); 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> scope = stack.back();
TypeArena tempArena;
std::vector<TypeId> variableTypes;
for (AstLocal* var : forInStatement->vars)
{
std::optional<TypeId> 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<TypeId> 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<TypeId> 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<FunctionTypeVar>(iteratorTy))
{
if (iteratorTypes.size() < 1 || iteratorTypes.size() > 3)
reportError(GenericError{"for..in loops must be passed (next, [table[, state]])"}, getLocation(forInStatement->values));
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
std::vector<TypeId> expectedVariableTypes = flatten(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<TypeId> 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<TableTypeVar>(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<AnyTypeVar>(iteratorTy) || get<ErrorTypeVar>(iteratorTy))
{
// nothing
}
else
{
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
}
} }
void visit(AstStatAssign* assign) void visit(AstStatAssign* assign)
@ -456,7 +621,8 @@ struct TypeChecker2
auto StackPusher = pushStack(expr); auto StackPusher = pushStack(expr);
if (0) if (0)
{} {
}
else if (auto e = expr->as<AstExprGroup>()) else if (auto e = expr->as<AstExprGroup>())
return visit(e); return visit(e);
else if (auto e = expr->as<AstExprConstantNil>()) else if (auto e = expr->as<AstExprConstantNil>())
@ -561,9 +727,21 @@ struct TypeChecker2
TypePackId expectedRetType = lookupPack(call); TypePackId expectedRetType = lookupPack(call);
TypeId functionType = lookupType(call->func); TypeId functionType = lookupType(call->func);
TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr);
LUAU_ASSERT(functionType); LUAU_ASSERT(functionType);
if (get<AnyTypeVar>(functionType) || get<ErrorTypeVar>(functionType))
return;
// TODO: Lots of other types are callable: intersections of functions
// and things with the __call metamethod.
if (!get<FunctionTypeVar>(functionType))
{
reportError(CannotCallNonFunction{functionType}, call->func->location);
return;
}
TypeId instantiatedFunctionType = follow(instantiation.substitute(functionType).value_or(nullptr));
TypePack args; TypePack args;
for (AstExpr* arg : call->args) for (AstExpr* arg : call->args)
{ {
@ -575,12 +753,11 @@ struct TypeChecker2
TypePackId argsTp = arena.addTypePack(args); TypePackId argsTp = arena.addTypePack(args);
FunctionTypeVar ftv{argsTp, expectedRetType}; FunctionTypeVar ftv{argsTp, expectedRetType};
TypeId expectedType = arena.addType(ftv); TypeId expectedType = arena.addType(ftv);
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice)) if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice))
{ {
unfreeze(module->interfaceTypes);
CloneState cloneState; CloneState cloneState;
expectedType = clone(expectedType, module->interfaceTypes, cloneState); expectedType = clone(expectedType, module->internalTypes, cloneState);
freeze(module->interfaceTypes);
reportError(TypeMismatch{expectedType, functionType}, call->location); reportError(TypeMismatch{expectedType, functionType}, call->location);
} }
} }
@ -592,7 +769,8 @@ struct TypeChecker2
// leftType must have a property called indexName->index // leftType must have a property called indexName->index
std::optional<TypeId> ty = getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true); std::optional<TypeId> ty =
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
if (ty) if (ty)
{ {
if (!isSubtype(resultType, *ty, stack.back(), ice)) if (!isSubtype(resultType, *ty, stack.back(), ice))
@ -972,18 +1150,34 @@ struct TypeChecker2
} }
} }
void reportError(TypeErrorData&& data, const Location& location) template<typename TID>
ErrorVec tryUnify(NotNull<Scope> 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)); module->errors.emplace_back(location, sourceModule->name, std::move(data));
} }
void reportError(TypeError e) void reportError(TypeError e)
{ {
module->errors.emplace_back(std::move(e)); reportError(std::move(e.data), e.location);
} }
std::optional<TypeId> getIndexTypeFromType( void reportErrors(ErrorVec errors)
const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors) {
for (TypeError e : errors)
reportError(std::move(e));
}
std::optional<TypeId> 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); return Luau::getIndexTypeFromType(scope, module->errors, &module->internalTypes, type, prop, location, addErrors, ice);
} }

View file

@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false)
LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false) LUAU_FASTFLAGVARIABLE(LuauInplaceDemoteSkipAllBound, false)
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
@ -45,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false) LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false) LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
namespace Luau namespace Luau
{ {
@ -2343,7 +2343,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
{ {
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end()) if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
expectedResultTypes.push_back(prop->second.type); 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); 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) 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<FreeTypeVar>(a) || get<FreeTypeVar>(b))) if (unifyFreeTypes && (get<FreeTypeVar>(a) || get<FreeTypeVar>(b)))
{ {
if (unify(b, a, scope, location)) if (unify(b, a, scope, location))
@ -3659,33 +3665,6 @@ WithPredicate<TypePackId> 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( void TypeChecker::checkArgumentList(
const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations) const ScopePtr& scope, Unifier& state, TypePackId argPack, TypePackId paramPack, const std::vector<Location>& argLocations)
{ {
@ -3705,7 +3684,7 @@ void TypeChecker::checkArgumentList(
if (!argLocations.empty()) if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end}; 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))}}); state.reportError(TypeError{location, CountMismatch{minParams, std::distance(begin(argPack), end(argPack))}});
}; };
@ -3804,7 +3783,7 @@ void TypeChecker::checkArgumentList(
} // ok } // ok
else else
{ {
size_t minParams = getMinParameterCount(&state.log, paramPack); size_t minParams = getParameterExtents(&state.log, paramPack).first;
std::optional<TypePackId> tail = flatten(paramPack, state.log).second; std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
bool isVariadic = tail && Luau::isVariadic(*tail); bool isVariadic = tail && Luau::isVariadic(*tail);

View file

@ -8,6 +8,13 @@
namespace Luau namespace Luau
{ {
BlockedTypePack::BlockedTypePack()
: index(++nextIndex)
{
}
size_t BlockedTypePack::nextIndex = 0;
TypePackVar::TypePackVar(const TypePackVariant& tp) TypePackVar::TypePackVar(const TypePackVariant& tp)
: ty(tp) : ty(tp)
{ {

View file

@ -84,9 +84,8 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> getIndexTypeFromType( std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, const Location& location, bool addErrors, const Location& location, bool addErrors, InternalErrorReporter& handle)
InternalErrorReporter& handle)
{ {
type = follow(type); type = follow(type);
@ -190,4 +189,33 @@ std::optional<TypeId> getIndexTypeFromType(
return std::nullopt; return std::nullopt;
} }
std::pair<size_t, std::optional<size_t>> 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 } // namespace Luau

View file

@ -24,9 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDeduceFindMatchReturnTypes, false)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false) LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
namespace Luau namespace Luau
@ -446,8 +444,10 @@ BlockedTypeVar::BlockedTypeVar()
int BlockedTypeVar::nextIndex = 0; int BlockedTypeVar::nextIndex = 0;
PendingExpansionTypeVar::PendingExpansionTypeVar(TypeFun fn, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) PendingExpansionTypeVar::PendingExpansionTypeVar(
: fn(fn) std::optional<AstName> prefix, AstName name, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: prefix(prefix)
, name(name)
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
, index(++nextIndex) , index(++nextIndex)
@ -787,8 +787,8 @@ TypeId SingletonTypes::makeStringMetatable()
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})}); makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
attachMagicFunction(gmatchFunc, magicFunctionGmatch); attachMagicFunction(gmatchFunc, magicFunctionGmatch);
const TypeId matchFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), const TypeId matchFunc = arena->addType(
arena->addTypePack(TypePackVar{VariadicTypePack{FFlag::LuauDeduceFindMatchReturnTypes ? stringType : optionalString}})}); FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
attachMagicFunction(matchFunc, magicFunctionMatch); attachMagicFunction(matchFunc, magicFunctionMatch);
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
@ -1221,9 +1221,6 @@ static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const ch
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch( static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate) TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{ {
if (!FFlag::LuauDeduceGmatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate; auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack); const auto& [params, tail] = flatten(paramPack);
@ -1256,9 +1253,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch( static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate) TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{ {
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate; auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack); const auto& [params, tail] = flatten(paramPack);
@ -1295,9 +1289,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
static std::optional<WithPredicate<TypePackId>> magicFunctionFind( static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate) TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{ {
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate; auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack); const auto& [params, tail] = flatten(paramPack);

View file

@ -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 // By default we use operator new/delete instead of malloc/free so that they can be overridden externally
if (!FFlag::DebugLuauFreezeArena) if (!FFlag::DebugLuauFreezeArena)
{
return ::operator new(size, std::nothrow); 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 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. // On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit.

View file

@ -13,7 +13,8 @@ Free::Free(TypeLevel level)
} }
Free::Free(Scope* scope) Free::Free(Scope* scope)
: scope(scope) : index(++nextIndex)
, scope(scope)
{ {
} }

View file

@ -317,7 +317,8 @@ static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMat
return std::nullopt; return std::nullopt;
} }
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog)
: types(types) : types(types)
, mode(mode) , mode(mode)
, scope(scope) , scope(scope)
@ -492,9 +493,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (log.get<ConstrainedTypeVar>(subTy)) if (log.get<ConstrainedTypeVar>(subTy))
tryUnifyWithConstrainedSubTypeVar(subTy, superTy); tryUnifyWithConstrainedSubTypeVar(subTy, superTy);
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(subTy)) else if (const UnionTypeVar* subUnion = log.getMutable<UnionTypeVar>(subTy))
{ {
tryUnifyUnionWithType(subTy, uv, superTy); tryUnifyUnionWithType(subTy, subUnion, superTy);
} }
else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy)) else if (const UnionTypeVar* uv = log.getMutable<UnionTypeVar>(superTy))
{ {
@ -555,14 +556,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
log.popSeen(superTy, subTy); 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 // A | B <: T if A <: T and B <: T
bool failed = false; bool failed = false;
std::optional<TypeError> unificationTooComplex; std::optional<TypeError> unificationTooComplex;
std::optional<TypeError> firstFailedOption; std::optional<TypeError> firstFailedOption;
for (TypeId type : uv->options) for (TypeId type : subUnion->options)
{ {
Unifier innerState = makeChildUnifier(); Unifier innerState = makeChildUnifier();
innerState.tryUnify_(type, superTy); innerState.tryUnify_(type, superTy);
@ -608,9 +609,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
} }
}; };
if (auto utv = log.getMutable<UnionTypeVar>(superTy)) if (auto superUnion = log.getMutable<UnionTypeVar>(superTy))
{ {
for (TypeId ty : utv) for (TypeId ty : superUnion)
tryBind(ty); tryBind(ty);
} }
else else

View file

@ -1276,6 +1276,16 @@ public:
}; };
AstName getIdentifier(AstExpr*); AstName getIdentifier(AstExpr*);
Location getLocation(const AstTypeList& typeList);
template<typename T> // AstNode, AstExpr, AstLocal, etc
Location getLocation(AstArray<T*> array)
{
if (0 == array.size)
return {};
return Location{array.data[0]->location.begin, array.data[array.size - 1]->location.end};
}
#undef LUAU_RTTI #undef LUAU_RTTI

View file

@ -304,6 +304,12 @@ private:
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5); AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...) AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
LUAU_PRINTF_ATTR(5, 6); 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); AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
void reportAmbiguousCallError(); void reportAmbiguousCallError();

View file

@ -952,4 +952,17 @@ AstName getIdentifier(AstExpr* node)
return AstName(); 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 } // namespace Luau

View file

@ -91,18 +91,8 @@ Lexeme::Lexeme(const Location& location, Type type, const char* data, size_t siz
, length(unsigned(size)) , length(unsigned(size))
, data(data) , data(data)
{ {
LUAU_ASSERT( LUAU_ASSERT(type == RawString || type == QuotedString || type == InterpStringBegin || type == InterpStringMid || type == InterpStringEnd ||
type == RawString type == InterpStringSimple || type == BrokenInterpDoubleBrace || type == Number || type == Comment || type == BlockComment);
|| 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) 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) == '{') 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();
consume(); consume();
return brokenDoubleBrace; return brokenDoubleBrace;

View file

@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, 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_bin_integer = false;
bool lua_telemetry_parsed_out_of_range_hex_integer = false; bool lua_telemetry_parsed_out_of_range_hex_integer = false;
@ -1564,44 +1565,43 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
{ {
incrementRecursionCounter("type annotation"); incrementRecursionCounter("type annotation");
Location begin = lexer.current().location; Location start = lexer.current().location;
if (lexer.current().type == Lexeme::ReservedNil) if (lexer.current().type == Lexeme::ReservedNil)
{ {
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeReference>(begin, std::nullopt, nameNil), {}}; return {allocator.alloc<AstTypeReference>(start, std::nullopt, nameNil), {}};
} }
else if (lexer.current().type == Lexeme::ReservedTrue) else if (lexer.current().type == Lexeme::ReservedTrue)
{ {
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, true)}; return {allocator.alloc<AstTypeSingletonBool>(start, true)};
} }
else if (lexer.current().type == Lexeme::ReservedFalse) else if (lexer.current().type == Lexeme::ReservedFalse)
{ {
nextLexeme(); nextLexeme();
return {allocator.alloc<AstTypeSingletonBool>(begin, false)}; return {allocator.alloc<AstTypeSingletonBool>(start, false)};
} }
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString) else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString)
{ {
if (std::optional<AstArray<char>> value = parseCharArray()) if (std::optional<AstArray<char>> value = parseCharArray())
{ {
AstArray<char> svalue = *value; AstArray<char> svalue = *value;
return {allocator.alloc<AstTypeSingletonString>(begin, svalue)}; return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
} }
else 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) else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
{ {
parseInterpString(); 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) else if (lexer.current().type == Lexeme::BrokenString)
{ {
Location location = lexer.current().location;
nextLexeme(); nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false, "Malformed string")}; return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
} }
else if (lexer.current().type == Lexeme::Name) else if (lexer.current().type == Lexeme::Name)
{ {
@ -1632,7 +1632,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
expectMatchAndConsume(')', typeofBegin); expectMatchAndConsume(')', typeofBegin);
return {allocator.alloc<AstTypeTypeof>(Location(begin, end), expr), {}}; return {allocator.alloc<AstTypeTypeof>(Location(start, end), expr), {}};
} }
bool hasParameters = false; bool hasParameters = false;
@ -1646,7 +1646,7 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
Location end = lexer.previousLocation(); Location end = lexer.previousLocation();
return {allocator.alloc<AstTypeReference>(Location(begin, end), prefix, name.name, hasParameters, parameters), {}}; return {allocator.alloc<AstTypeReference>(Location(start, end), prefix, name.name, hasParameters, parameters), {}};
} }
else if (lexer.current().type == '{') else if (lexer.current().type == '{')
{ {
@ -1658,23 +1658,35 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
} }
else if (lexer.current().type == Lexeme::ReservedFunction) else if (lexer.current().type == Lexeme::ReservedFunction)
{ {
Location location = lexer.current().location;
nextLexeme(); 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) -> " "Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
"...any'"), "...any'"),
{}}; {}};
} }
else 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 // For a missing type annotation, capture 'space' between last token and the next one
location = Location(lexer.previousLocation().end, lexer.current().location.begin); 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(); 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(); return parseString();
} }
@ -2653,7 +2666,8 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
std::optional<AstArray<char>> Parser::parseCharArray() std::optional<AstArray<char>> 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); scratchData.assign(lexer.current().data, lexer.current().length);
@ -2691,14 +2705,11 @@ AstExpr* Parser::parseInterpString()
Location startLocation = lexer.current().location; Location startLocation = lexer.current().location;
do { do
{
Lexeme currentLexeme = lexer.current(); Lexeme currentLexeme = lexer.current();
LUAU_ASSERT( LUAU_ASSERT(currentLexeme.type == Lexeme::InterpStringBegin || currentLexeme.type == Lexeme::InterpStringMid ||
currentLexeme.type == Lexeme::InterpStringBegin currentLexeme.type == Lexeme::InterpStringEnd || currentLexeme.type == Lexeme::InterpStringSimple);
|| currentLexeme.type == Lexeme::InterpStringMid
|| currentLexeme.type == Lexeme::InterpStringEnd
|| currentLexeme.type == Lexeme::InterpStringSimple
);
Location location = currentLexeme.location; 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 // 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 // This can be used to pinpoint the problem location for a possible future *actual* mismatch
if (lexer.current().location.begin.line != begin.position.line && if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column &&
lexer.current().location.begin.column != begin.position.column &&
endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects
{ {
endMismatchSuspect = begin; endMismatchSuspect = begin;
@ -3108,6 +3118,13 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray<A
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...) AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& 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_list args;
va_start(args, format); va_start(args, format);
report(location, format, args); report(location, format, args);
@ -3116,6 +3133,18 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1)); return allocator.alloc<AstTypeError>(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<AstTypeError>(astErrorLocation, AstArray<AstType*>{}, true, unsigned(parseErrors.size() - 1));
}
void Parser::nextLexeme() void Parser::nextLexeme()
{ {
Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type;

View file

@ -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 // 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_MIN = 2,
LBC_VERSION_MAX = 3, LBC_VERSION_MAX = 3,
LBC_VERSION_TARGET = 2, LBC_VERSION_TARGET = 3,
// Types of constant table entries // Types of constant table entries
LBC_CONSTANT_NIL = 0, LBC_CONSTANT_NIL = 0,
LBC_CONSTANT_BOOLEAN, LBC_CONSTANT_BOOLEAN,

View file

@ -13,7 +13,8 @@ inline bool isFlagExperimental(const char* flag)
static const char* kList[] = { static const char* kList[] = {
"LuauLowerBoundsCalculation", "LuauLowerBoundsCalculation",
"LuauInterpolatedStringBaseSupport", "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) for (const char* item : kList)

View file

@ -6,8 +6,6 @@
#include <algorithm> #include <algorithm>
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauCompileBytecodeV3, false)
namespace Luau namespace Luau
{ {
@ -1079,9 +1077,6 @@ std::string BytecodeBuilder::getError(const std::string& message)
uint8_t BytecodeBuilder::getVersion() 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 // 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; return LBC_VERSION_TARGET;
} }

View file

@ -25,14 +25,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileXEQ, false)
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport) LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauCompileOptimalAssignment, false)
LUAU_FASTFLAGVARIABLE(LuauCompileExtractK, false)
namespace Luau 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->self);
LUAU_ASSERT(expr->args.size >= 1); LUAU_ASSERT(expr->args.size >= 1);
LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3)); LUAU_ASSERT(expr->args.size <= 2 || (bfid == LBF_BIT32_EXTRACTK && expr->args.size == 3));
LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0); LUAU_ASSERT(bfid == LBF_BIT32_EXTRACTK ? bfK >= 0 : bfK < 0);
LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : LOP_FASTCALL2; LuauOpcode opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
if (FFlag::LuauCompileExtractK)
{
opc = expr->args.size == 1 ? LOP_FASTCALL1 : (bfK >= 0 || isConstant(expr->args.data[1])) ? LOP_FASTCALL2K : LOP_FASTCALL2;
}
uint32_t args[3] = {}; uint32_t args[3] = {};
for (size_t i = 0; i < expr->args.size; ++i) 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)
int32_t cid = getConstantIndex(expr->args.data[i]); CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
args[i] = cid; args[i] = cid;
continue; // TODO: remove this and change if below to else if
}
} }
else if (i > 0) else if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 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)
{ {
args[i] = uint8_t(reg); args[i] = uint8_t(reg);
} }
@ -468,24 +444,10 @@ struct Compiler
// these FASTCALL variants. // these FASTCALL variants.
for (size_t i = 0; i < expr->args.size; ++i) 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]);
if (i > 0 && opc == LOP_FASTCALL2K) else if (args[i] != regs + 1 + i)
emitLoadK(uint8_t(regs + 1 + i), args[i]); bytecode.emitABC(LOP_MOVE, uint8_t(regs + 1 + i), uint8_t(args[i]), 0);
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);
}
} }
// note, these instructions are normally not executed and are used as a fallback for FASTCALL // 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 // 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 fc = getConstant(expr->args.data[1]);
Constant wc = getConstant(expr->args.data[2]); Constant wc = getConstant(expr->args.data[2]);
@ -1080,102 +1042,64 @@ struct Compiler
std::swap(left, right); 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); case Constant::Type_Nil:
LUAU_ASSERT(cv && cv->type != Constant::Type_Unknown); opc = LOP_JUMPXEQKNIL;
cid = 0;
break;
LuauOpcode opc = LOP_NOP; case Constant::Type_Boolean:
int32_t cid = -1; opc = LOP_JUMPXEQKB;
uint32_t flip = (expr->op == AstExprBinary::CompareEq) == not_ ? 0x80000000 : 0; cid = cv->valueBoolean;
break;
switch (cv->type) case Constant::Type_Number:
{ opc = LOP_JUMPXEQKN;
case Constant::Type_Nil: cid = getConstantIndex(right);
opc = LOP_JUMPXEQKNIL; break;
cid = 0;
break;
case Constant::Type_Boolean: case Constant::Type_String:
opc = LOP_JUMPXEQKB; opc = LOP_JUMPXEQKS;
cid = cv->valueBoolean; cid = getConstantIndex(right);
break; break;
case Constant::Type_Number: default:
opc = LOP_JUMPXEQKN; LUAU_ASSERT(!"Unexpected constant type");
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;
} }
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, rl, 0);
{ bytecode.emitAux(cid | flip);
bytecode.emitAD(opc, rr, 0);
bytecode.emitAux(rl);
}
else
{
bytecode.emitAD(opc, rl, 0);
bytecode.emitAux(rr);
}
return jumpLabel; return jumpLabel;
}
} }
else else
{ {
LuauOpcode opc = getJumpOpCompare(expr->op, not_); LuauOpcode opc = getJumpOpCompare(expr->op, not_);
uint8_t rl = compileExprAuto(left, rs); uint8_t rr = compileExprAuto(right, 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);
size_t jumpLabel = bytecode.emitLabel(); size_t jumpLabel = bytecode.emitLabel();
if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe) if (expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::CompareGe)
{ {
bytecode.emitAD(opc, uint8_t(rr), 0); bytecode.emitAD(opc, rr, 0);
bytecode.emitAux(rl); bytecode.emitAux(rl);
} }
else else
@ -2979,62 +2903,6 @@ struct Compiler
loops.pop_back(); loops.pop_back();
} }
void resolveAssignConflicts(AstStat* stat, std::vector<LValue>& 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 struct Assignment
{ {
LValue lvalue; LValue lvalue;
@ -3146,111 +3014,82 @@ struct Compiler
return; 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<Assignment> 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 AstExpr* value = stat->values.data[i];
// left hand side - for example, in "a[expr] = foo" expr will get evaluated here
std::vector<Assignment> vars(stat->vars.size);
for (size_t i = 0; i < stat->vars.size; ++i) if (i + 1 == stat->values.size && stat->vars.size > stat->values.size)
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)
{ {
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 var.valueReg = (var.conflictReg == kInvalidReg) ? var.lvalue.reg : var.conflictReg;
// note, this also handles trailing nils
uint8_t rest = uint8_t(stat->vars.size - stat->values.size + 1);
uint8_t temp = allocReg(stat, rest);
compileExprTempN(value, temp, rest, /* targetTop= */ true); compileExpr(stat->values.data[i], var.valueReg);
for (size_t j = i; j < stat->vars.size; ++j)
vars[j].valueReg = uint8_t(temp + (j - i));
} }
else else
{ {
Assignment& var = vars[i]; var.valueReg = compileExprAuto(stat->values.data[i], rs);
// 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);
}
} }
} }
// 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 RegScope rsi(this);
// left hand side for example, in "a[expr] = foo" expr will get evaluated here compileExprAuto(stat->values.data[i], rsi);
std::vector<LValue> vars(stat->vars.size); }
for (size_t i = 0; i < stat->vars.size; ++i) // almost done... let's assign everything left to right, noting that locals were either written-to directly, or will be written-to in a
vars[i] = compileLValue(stat->vars.data[i], rs); // 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 if (var.lvalue.kind != LValue::Kind_Local)
// 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)
{ {
setDebugLine(stat->vars.data[i]); setDebugLine(var.lvalue.location);
compileAssign(vars[i], uint8_t(regs + i)); 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) void compileStatCompoundAssign(AstStatCompoundAssign* stat)

View file

@ -15,8 +15,6 @@
#include <intrin.h> #include <intrin.h>
#endif #endif
LUAU_FASTFLAGVARIABLE(LuauFasterBit32NoWidth, false)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // 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. // 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 // 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) 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 a1 = nvalue(arg0);
double a2 = nvalue(args); 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) 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 a1 = nvalue(arg0);
double a2 = nvalue(args); double a2 = nvalue(args);

View file

@ -8,8 +8,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauTostringFormatSpecifier, false);
// macro to `unsign' a character // macro to `unsign' a character
#define uchar(c) ((unsigned char)(c)) #define uchar(c) ((unsigned char)(c))
@ -1036,9 +1034,6 @@ static int str_format(lua_State* L)
} }
case '*': case '*':
{ {
if (!FFlag::LuauTostringFormatSpecifier)
luaL_error(L, "invalid option '%%*' to 'format'");
if (formatItemSize != 1) if (formatItemSize != 1)
luaL_error(L, "'%%*' does not take a form"); luaL_error(L, "'%%*' does not take a form");

View file

@ -3031,7 +3031,16 @@ static void luau_execute(lua_State* L)
TValue* kv = VM_KV(aux & 0xffffff); TValue* kv = VM_KV(aux & 0xffffff);
LUAU_ASSERT(ttisnumber(kv)); 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; 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)); LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT(); VM_NEXT();
} }

View file

@ -4,7 +4,6 @@
#include "doctest.h" #include "doctest.h"
#include <functional>
#include <string.h> #include <string.h>
using namespace Luau::CodeGen; using namespace Luau::CodeGen;
@ -22,7 +21,7 @@ std::string bytecodeAsArray(const std::vector<uint8_t>& bytecode)
class AssemblyBuilderX64Fixture class AssemblyBuilderX64Fixture
{ {
public: public:
void check(std::function<void(AssemblyBuilderX64& build)> f, std::vector<uint8_t> code, std::vector<uint8_t> data = {}) void check(void (*f)(AssemblyBuilderX64& build), std::vector<uint8_t> code, std::vector<uint8_t> data = {})
{ {
AssemblyBuilderX64 build(/* logText= */ false); AssemblyBuilderX64 build(/* logText= */ false);

View file

@ -1241,8 +1241,7 @@ TEST_CASE("InterpStringZeroCost")
{ {
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ( CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
"\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
R"( R"(
LOADK R1 K0 LOADK R1 K0
LOADK R3 K1 LOADK R3 K1
@ -1250,16 +1249,14 @@ NAMECALL R1 R1 K2
CALL R1 2 1 CALL R1 2 1
MOVE R0 R1 MOVE R0 R1
RETURN R0 0 RETURN R0 0
)" )");
);
} }
TEST_CASE("InterpStringRegisterCleanup") TEST_CASE("InterpStringRegisterCleanup")
{ {
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ( CHECK_EQ("\n" + compileFunction0(R"(
"\n" + compileFunction0(R"(
local a, b, c = nil, "um", "uh oh" local a, b, c = nil, "um", "uh oh"
a = `foo{"bar"}` a = `foo{"bar"}`
print(a) print(a)
@ -1278,8 +1275,7 @@ GETIMPORT R3 6
MOVE R4 R0 MOVE R4 R0
CALL R3 1 0 CALL R3 1 0
RETURN R0 0 RETURN R0 0
)" )");
);
} }
TEST_CASE("ConstantFoldArith") TEST_CASE("ConstantFoldArith")
@ -2488,8 +2484,6 @@ end
TEST_CASE("DebugLineInfoRepeatUntil") TEST_CASE("DebugLineInfoRepeatUntil")
{ {
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction0Coverage(R"( CHECK_EQ("\n" + compileFunction0Coverage(R"(
local f = 0 local f = 0
repeat repeat
@ -2834,8 +2828,6 @@ RETURN R0 0
TEST_CASE("AssignmentConflict") TEST_CASE("AssignmentConflict")
{ {
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
// assignments are left to right // assignments are left to right
CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"( CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"(
LOADNIL R0 LOADNIL R0
@ -3610,8 +3602,6 @@ RETURN R0 1
TEST_CASE("ConstantJumpCompare") TEST_CASE("ConstantJumpCompare")
{ {
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction0(R"( CHECK_EQ("\n" + compileFunction0(R"(
local obj = ... local obj = ...
local b = obj == 1 local b = obj == 1
@ -6210,8 +6200,6 @@ L4: RETURN R0 -1
TEST_CASE("BuiltinFoldingMultret") TEST_CASE("BuiltinFoldingMultret")
{ {
ScopedFastFlag sff("LuauCompileXEQ", true);
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(
local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000 local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000
local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000 local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000
@ -6350,8 +6338,6 @@ RETURN R2 1
TEST_CASE("MultipleAssignments") TEST_CASE("MultipleAssignments")
{ {
ScopedFastFlag sff("LuauCompileOptimalAssignment", true);
// order of assignments is left to right // order of assignments is left to right
CHECK_EQ("\n" + compileFunction0(R"( CHECK_EQ("\n" + compileFunction0(R"(
local a, b local a, b
@ -6574,15 +6560,14 @@ RETURN R0 0
TEST_CASE("BuiltinExtractK") TEST_CASE("BuiltinExtractK")
{ {
ScopedFastFlag sff("LuauCompileExtractK", true);
// below, K0 refers to a packed f+w constant for bit32.extractk builtin // 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 // K1 and K2 refer to 1 and 3 and are only used during fallback path
CHECK_EQ("\n" + compileFunction0(R"( CHECK_EQ("\n" + compileFunction0(R"(
local v = ... local v = ...
return bit32.extract(v, 1, 3) return bit32.extract(v, 1, 3)
)"), R"( )"),
R"(
GETVARARGS R0 1 GETVARARGS R0 1
FASTCALL2K 59 R0 K0 L0 FASTCALL2K 59 R0 K0 L0
MOVE R2 R0 MOVE R2 R0

View file

@ -289,15 +289,12 @@ TEST_CASE("Clear")
TEST_CASE("Strings") TEST_CASE("Strings")
{ {
ScopedFastFlag sff{"LuauTostringFormatSpecifier", true};
runConformance("strings.lua"); runConformance("strings.lua");
} }
TEST_CASE("StringInterp") TEST_CASE("StringInterp")
{ {
ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true}; ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true};
ScopedFastFlag sffTostringFormat{"LuauTostringFormatSpecifier", true};
runConformance("stringinterp.lua"); runConformance("stringinterp.lua");
} }
@ -725,13 +722,16 @@ TEST_CASE("NewUserdataOverflow")
StateRef globalState(luaL_newstate(), lua_close); StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get(); lua_State* L = globalState.get();
lua_pushcfunction(L, [](lua_State* L1) { lua_pushcfunction(
// The following userdata request might cause an overflow. L,
lua_newuserdatadtor(L1, SIZE_MAX, [](void* d){}); [](lua_State* L1) {
// The overflow might segfault in the following call. // The following userdata request might cause an overflow.
lua_getmetatable(L1, -1); lua_newuserdatadtor(L1, SIZE_MAX, [](void* d) {});
return 0; // The overflow might segfault in the following call.
}, nullptr); lua_getmetatable(L1, -1);
return 0;
},
nullptr);
CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN); CHECK(lua_pcall(L, 0, 0, 0) == LUA_ERRRUN);
CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0); CHECK(strcmp(lua_tostring(L, -1), "memory allocation error: block too big") == 0);

View file

@ -57,13 +57,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "nil_primitive")
auto constraints = collectConstraints(NotNull(cgb.rootScope)); auto constraints = collectConstraints(NotNull(cgb.rootScope));
ToStringOptions opts; ToStringOptions opts;
REQUIRE(5 <= constraints.size()); REQUIRE(4 <= constraints.size());
CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts)); CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts));
CHECK("*blocked-2* ~ inst *blocked-1*" == toString(*constraints[1], opts)); CHECK("call *blocked-1* with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("() -> (b...) <: *blocked-2*" == toString(*constraints[2], opts)); CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
CHECK("b... <: c" == toString(*constraints[3], opts)); CHECK("nil <: a..." == toString(*constraints[3], opts));
CHECK("nil <: a..." == toString(*constraints[4], opts));
} }
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application") TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
@ -76,13 +75,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
cgb.visit(block); cgb.visit(block);
auto constraints = collectConstraints(NotNull(cgb.rootScope)); auto constraints = collectConstraints(NotNull(cgb.rootScope));
REQUIRE(4 == constraints.size()); REQUIRE(3 == constraints.size());
ToStringOptions opts; ToStringOptions opts;
CHECK("string <: a" == toString(*constraints[0], opts)); CHECK("string <: a" == toString(*constraints[0], opts));
CHECK("*blocked-1* ~ inst a" == toString(*constraints[1], opts)); CHECK("call a with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("(string) -> (b...) <: *blocked-1*" == toString(*constraints[2], opts)); CHECK("*blocked-tp-1* <: b" == toString(*constraints[2], opts));
CHECK("b... <: c" == toString(*constraints[3], opts));
} }
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition") TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
@ -114,13 +112,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
cgb.visit(block); cgb.visit(block);
auto constraints = collectConstraints(NotNull(cgb.rootScope)); auto constraints = collectConstraints(NotNull(cgb.rootScope));
REQUIRE(4 == constraints.size()); REQUIRE(3 == constraints.size());
ToStringOptions opts; ToStringOptions opts;
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts)); CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
CHECK("*blocked-2* ~ inst (a) -> (b...)" == toString(*constraints[1], opts)); CHECK("call (a) -> (b...) with { result = *blocked-tp-1* }" == toString(*constraints[1], opts));
CHECK("(a) -> (c...) <: *blocked-2*" == toString(*constraints[2], opts)); CHECK("*blocked-tp-1* <: b..." == toString(*constraints[2], opts));
CHECK("c... <: b..." == toString(*constraints[3], opts));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -28,7 +28,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
cgb.visit(block); cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope); NotNull<Scope> rootScope = NotNull(cgb.rootScope);
ConstraintSolver cs{&arena, rootScope}; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run(); cs.run();
@ -48,7 +49,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
cgb.visit(block); cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope); NotNull<Scope> rootScope = NotNull(cgb.rootScope);
ConstraintSolver cs{&arena, rootScope}; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run(); cs.run();
@ -57,7 +59,6 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
CHECK("<a>(a) -> a" == toString(idType)); CHECK("<a>(a) -> a" == toString(idType));
} }
#if 1
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
{ {
AstStatBlock* block = parse(R"( AstStatBlock* block = parse(R"(
@ -77,7 +78,8 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
ToStringOptions opts; ToStringOptions opts;
ConstraintSolver cs{&arena, rootScope}; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
cs.run(); cs.run();
@ -85,6 +87,5 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
CHECK("<a>(a) -> number" == toString(idType, opts)); CHECK("<a>(a) -> number" == toString(idType, opts));
} }
#endif
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -2,6 +2,8 @@
#include "Fixture.h" #include "Fixture.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/ModuleResolver.h"
#include "Luau/NotNull.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/TypeAttach.h" #include "Luau/TypeAttach.h"
@ -444,10 +446,11 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
: Fixture() : Fixture()
, mainModule(new Module) , mainModule(new Module)
, cgb(mainModuleName, mainModule, &arena, NotNull(&ice), frontend.getGlobalScope()) , cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), NotNull(&ice), frontend.getGlobalScope())
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true} , forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
{ {
BlockedTypeVar::nextIndex = 0; BlockedTypeVar::nextIndex = 0;
BlockedTypePack::nextIndex = 0;
} }
ModuleName fromString(std::string_view name) ModuleName fromString(std::string_view name)

View file

@ -132,6 +132,7 @@ struct Fixture
TestFileResolver fileResolver; TestFileResolver fileResolver;
TestConfigResolver configResolver; TestConfigResolver configResolver;
NullModuleResolver moduleResolver;
std::unique_ptr<SourceModule> sourceModule; std::unique_ptr<SourceModule> sourceModule;
Frontend frontend; Frontend frontend;
InternalErrorReporter ice; InternalErrorReporter ice;
@ -244,7 +245,7 @@ struct FindNthOccurenceOf : public AstVisitor
* 2. Luau::query<AstExprBinary>(Luau::query<AstStatFunction>(block)) * 2. Luau::query<AstExprBinary>(Luau::query<AstStatFunction>(block))
* 3. Luau::query<AstExprBinary>(block, {nth<AstExprBinary>(2)}) * 3. Luau::query<AstExprBinary>(block, {nth<AstExprBinary>(2)})
*/ */
template<typename T, size_t N = 1> template<typename T, int N = 1>
T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)}) T* query(AstNode* node, const std::vector<Nth>& nths = {nth<T>(N)})
{ {
static_assert(std::is_base_of_v<AstNode, T>, "T must be a derived class of AstNode"); static_assert(std::is_base_of_v<AstNode, T>, "T must be a derived class of AstNode");

View file

@ -1181,7 +1181,7 @@ s:match("[]")
nons: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].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[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"); 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 _ = not a <= b local _ = not a <= b
local _ = a <= b == 0 local _ = a <= b == 0
local _ = a <= b <= 0
local _ = not a == not b -- weird but ok local _ = not a == not b -- weird but ok
@ -1760,11 +1761,12 @@ local _ = (a <= b) == 0
local _ = a <= (b == 0) local _ = a <= (b == 0)
)"); )");
REQUIRE_EQ(result.warnings.size(), 4); 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 wrap one of the expressions in parentheses to silence"); 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 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 add 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[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; wrap one of the expressions in 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(); TEST_SUITE_END();

View file

@ -943,8 +943,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
{ {
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true}; ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
auto columnOfEndBraceError = [this](const char* code) auto columnOfEndBraceError = [this](const char* code) {
{
try try
{ {
parse(code); parse(code);
@ -1737,6 +1736,48 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation")
matchParseError("local a : 2 = 2", "Expected type, got '2'"); 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 <eof>");
}
{
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") TEST_CASE_FIXTURE(Fixture, "parse_declarations")
{ {
AstStatBlock* stat = parseEx(R"( AstStatBlock* stat = parseEx(R"(

View file

@ -346,4 +346,17 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_any_with_other_options")
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -1061,7 +1061,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")() local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
)END"); )END");
@ -1075,7 +1074,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")() local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
)END"); )END");
@ -1089,7 +1087,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his)() is a string", ".")() local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
)END"); )END");
@ -1107,7 +1104,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")() local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
)END"); )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") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")() local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
)END"); )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") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
{ {
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b = string.gmatch("[[[", "()([[])")() local a, b = string.gmatch("[[[", "()([[])")()
)END"); )END");
@ -1196,7 +1190,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallbac
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))") local a, b, c = string.match("This is a string", "(.()(%a+))")
)END"); )END");
@ -1210,7 +1203,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number") local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number")
)END"); )END");
@ -1229,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))") local d, e, a, b, c = string.find("This is a string", "(.()(%a+))")
)END"); )END");
@ -1245,7 +1236,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number") local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number")
)END"); )END");
@ -1266,7 +1256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool") local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool")
)END"); )END");
@ -1287,7 +1276,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{ {
ScopedFastFlag sffs{"LuauDeduceFindMatchReturnTypes", true};
CheckResult result = check(R"END( CheckResult result = check(R"END(
local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true) local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true)
)END"); )END");

View file

@ -133,6 +133,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
TableTypeVar* ttv = getMutable<TableTypeVar>(*r); TableTypeVar* ttv = getMutable<TableTypeVar>(*r);
REQUIRE(ttv); REQUIRE(ttv);
REQUIRE(ttv->props.count("f"));
TypeId k = ttv->props["f"].type; TypeId k = ttv->props["f"].type;
REQUIRE(k); REQUIRE(k);

View file

@ -14,6 +14,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSpecialTypesAsterisked) LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("TypeInferLoops"); 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); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -141,7 +154,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
end end
)"); )");
CHECK_EQ(2, result.errors.size()); LUAU_REQUIRE_ERROR_COUNT(2, result);
TypeId p = requireType("p"); TypeId p = requireType("p");
if (FFlag::LuauSpecialTypesAsterisked) if (FFlag::LuauSpecialTypesAsterisked)
@ -232,6 +245,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args
CHECK_EQ(0, acm->actual); 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<TypeMismatch>(result.errors[1]));
CHECK(Location{{9, 29}, {9, 37}} == result.errors[0].location);
CHECK(get<TypeMismatch>(result.errors[1]));
CHECK(Location{{9, 39}, {9, 50}} == result.errors[1].location);
}
TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator") TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -503,7 +540,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(0, result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.numberType, *requireType("key")); CHECK_EQ(*typeChecker.numberType, *requireType("key"));
} }

View file

@ -16,6 +16,35 @@ LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
TEST_SUITE_BEGIN("TypeInferModules"); 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<TypeId> bType = requireType(b, "b");
REQUIRE(bType);
CHECK(toString(*bType) == "number");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "require") TEST_CASE_FIXTURE(BuiltinsFixture, "require")
{ {
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(

View file

@ -11,7 +11,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes)
LUAU_FASTFLAG(LuauSpecialTypesAsterisked) LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
using namespace Luau; using namespace Luau;
@ -61,8 +60,8 @@ TEST_CASE_FIXTURE(Fixture, "string_method")
CheckResult result = check(R"( CheckResult result = check(R"(
local p = ("tacos"):len() local p = ("tacos"):len()
)"); )");
CHECK_EQ(0, result.errors.size());
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*requireType("p"), *typeChecker.numberType); CHECK_EQ(*requireType("p"), *typeChecker.numberType);
} }
@ -73,8 +72,8 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
local l = s.lower local l = s.lower
local p = l(s) local p = l(s)
)"); )");
CHECK_EQ(0, result.errors.size());
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*requireType("p"), *typeChecker.stringType); CHECK_EQ(*requireType("p"), *typeChecker.stringType);
} }
@ -84,12 +83,9 @@ TEST_CASE_FIXTURE(Fixture, "string_function_other")
local s:string local s:string
local p = s:match("foo") local p = s:match("foo")
)"); )");
CHECK_EQ(0, result.errors.size());
if (FFlag::LuauDeduceFindMatchReturnTypes) LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("p")), "string"); CHECK_EQ(toString(requireType("p")), "string");
else
CHECK_EQ(toString(requireType("p")), "string?");
} }
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber") TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")

View file

@ -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) 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: // 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. 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(); TEST_SUITE_END();

View file

@ -2994,8 +2994,6 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union") 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]: {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"}}})")); LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})"));

View file

@ -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 = 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 = 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) -- binary arithmetics coerces strings to numbers (sadly)
assert(1 + "2" == 3) assert(1 + "2" == 3)
assert(2 * "0xa" == 20) assert(2 * "0xa" == 20)

View file

@ -369,21 +369,31 @@ assert(not a and b:match('[^ ]+') == "short:1:")
local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10)) local a,b = loadstring("nope", "=" .. string.rep("thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilities", 10))
assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:") assert(not a and b:match('[^ ]+') == "thisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbufferprovidedtovariousdebugfacilitiesthisisaverylongstringitssolongthatitwontfitintotheinternalbuffe:1:")
-- arith errors
function ecall(fn, ...) function ecall(fn, ...)
local ok, err = pcall(fn, ...) local ok, err = pcall(fn, ...)
assert(not ok) assert(not ok)
return err:sub(err:find(": ") + 2, #err) return err:sub((err:find(": ") or -1) + 2, #err)
end end
-- arith errors
assert(ecall(function() return nil + 5 end) == "attempt to perform arithmetic (add) on nil and number") 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 "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 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") 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[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 = {} 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 -- 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='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)") assert(ecall(function() for i=1,'a' do end end) == "invalid 'for' limit (number expected, got string)")

View file

@ -1,10 +1,8 @@
AnnotationTests.builtin_types_are_not_exported AnnotationTests.builtin_types_are_not_exported
AnnotationTests.cannot_use_nonexported_type
AnnotationTests.cloned_interface_maintains_pointers_between_definitions
AnnotationTests.duplicate_type_param_name AnnotationTests.duplicate_type_param_name
AnnotationTests.for_loop_counter_annotation_is_checked AnnotationTests.for_loop_counter_annotation_is_checked
AnnotationTests.generic_aliases_are_cloned_properly AnnotationTests.generic_aliases_are_cloned_properly
AnnotationTests.interface_types_belong_to_interface_arena AnnotationTests.instantiation_clone_has_to_follow
AnnotationTests.luau_ice_triggers_an_ice AnnotationTests.luau_ice_triggers_an_ice
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag
AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler
@ -26,6 +24,7 @@ AutocompleteTest.autocomplete_first_function_arg_expected_type
AutocompleteTest.autocomplete_for_in_middle_keywords AutocompleteTest.autocomplete_for_in_middle_keywords
AutocompleteTest.autocomplete_for_middle_keywords AutocompleteTest.autocomplete_for_middle_keywords
AutocompleteTest.autocomplete_if_middle_keywords AutocompleteTest.autocomplete_if_middle_keywords
AutocompleteTest.autocomplete_interpolated_string
AutocompleteTest.autocomplete_on_string_singletons AutocompleteTest.autocomplete_on_string_singletons
AutocompleteTest.autocomplete_oop_implicit_self AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_repeat_middle_keyword AutocompleteTest.autocomplete_repeat_middle_keyword
@ -56,14 +55,12 @@ AutocompleteTest.global_functions_are_not_scoped_lexically
AutocompleteTest.globals_are_order_independent AutocompleteTest.globals_are_order_independent
AutocompleteTest.if_then_else_elseif_completions AutocompleteTest.if_then_else_elseif_completions
AutocompleteTest.keyword_methods AutocompleteTest.keyword_methods
AutocompleteTest.keyword_types
AutocompleteTest.library_non_self_calls_are_fine AutocompleteTest.library_non_self_calls_are_fine
AutocompleteTest.library_self_calls_are_invalid AutocompleteTest.library_self_calls_are_invalid
AutocompleteTest.local_function AutocompleteTest.local_function
AutocompleteTest.local_function_params AutocompleteTest.local_function_params
AutocompleteTest.local_functions_fall_out_of_scope AutocompleteTest.local_functions_fall_out_of_scope
AutocompleteTest.method_call_inside_function_body AutocompleteTest.method_call_inside_function_body
AutocompleteTest.module_type_members
AutocompleteTest.nested_member_completions AutocompleteTest.nested_member_completions
AutocompleteTest.nested_recursive_function AutocompleteTest.nested_recursive_function
AutocompleteTest.no_function_name_suggestions AutocompleteTest.no_function_name_suggestions
@ -78,7 +75,6 @@ AutocompleteTest.return_types
AutocompleteTest.sometimes_the_metatable_is_an_error AutocompleteTest.sometimes_the_metatable_is_an_error
AutocompleteTest.source_module_preservation_and_invalidation AutocompleteTest.source_module_preservation_and_invalidation
AutocompleteTest.statement_between_two_statements AutocompleteTest.statement_between_two_statements
AutocompleteTest.stop_at_first_stat_when_recommending_keywords
AutocompleteTest.string_prim_non_self_calls_are_avoided AutocompleteTest.string_prim_non_self_calls_are_avoided
AutocompleteTest.string_prim_self_calls_are_fine AutocompleteTest.string_prim_self_calls_are_fine
AutocompleteTest.suggest_external_module_type AutocompleteTest.suggest_external_module_type
@ -155,6 +151,7 @@ BuiltinTests.string_format_tostring_specifier
BuiltinTests.string_format_tostring_specifier_type_constraint BuiltinTests.string_format_tostring_specifier_type_constraint
BuiltinTests.string_format_use_correct_argument BuiltinTests.string_format_use_correct_argument
BuiltinTests.string_format_use_correct_argument2 BuiltinTests.string_format_use_correct_argument2
BuiltinTests.string_format_use_correct_argument3
BuiltinTests.string_lib_self_noself BuiltinTests.string_lib_self_noself
BuiltinTests.table_concat_returns_string BuiltinTests.table_concat_returns_string
BuiltinTests.table_dot_remove_optionally_returns_generic BuiltinTests.table_dot_remove_optionally_returns_generic
@ -168,31 +165,21 @@ BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2 BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.declaring_generic_functions DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes 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.ast_node_at_position
FrontendTest.automatically_check_dependent_scripts FrontendTest.automatically_check_dependent_scripts
FrontendTest.check_without_builtin_next
FrontendTest.dont_reparse_clean_file_when_linting FrontendTest.dont_reparse_clean_file_when_linting
FrontendTest.environments FrontendTest.environments
FrontendTest.imported_table_modification_2 FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.no_use_after_free_with_type_fun_instantiation
FrontendTest.nocheck_cycle_used_by_checked FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.nocheck_modules_are_typed
FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error
FrontendTest.recheck_if_dependent_script_is_dirty FrontendTest.recheck_if_dependent_script_is_dirty
FrontendTest.reexport_cyclic_type FrontendTest.reexport_cyclic_type
FrontendTest.reexport_type_alias
FrontendTest.report_require_to_nonexistent_file
FrontendTest.report_syntax_error_in_required_file FrontendTest.report_syntax_error_in_required_file
FrontendTest.trace_requires_in_nonstrict_mode FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1 GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2 GenericsTests.apply_type_function_nested_generics2
GenericsTests.better_mismatch_error_messages GenericsTests.better_mismatch_error_messages
GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.calling_self_generic_methods GenericsTests.calling_self_generic_methods
GenericsTests.check_generic_typepack_function GenericsTests.check_generic_typepack_function
GenericsTests.check_mutual_generic_functions GenericsTests.check_mutual_generic_functions
@ -208,7 +195,6 @@ GenericsTests.factories_of_generics
GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories GenericsTests.generic_factories
GenericsTests.generic_functions_dont_cache_type_parameters
GenericsTests.generic_functions_in_types GenericsTests.generic_functions_in_types
GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method GenericsTests.generic_table_method
@ -245,7 +231,6 @@ IntersectionTypes.no_stack_overflow_from_flattenintersection
IntersectionTypes.overload_is_not_a_function IntersectionTypes.overload_is_not_a_function
IntersectionTypes.select_correct_union_fn IntersectionTypes.select_correct_union_fn
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
IntersectionTypes.table_intersection_write
IntersectionTypes.table_intersection_write_sealed IntersectionTypes.table_intersection_write_sealed
IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect
@ -255,7 +240,6 @@ Linter.TableOperations
ModuleTests.clone_self_property ModuleTests.clone_self_property
ModuleTests.deepClone_cyclic_table ModuleTests.deepClone_cyclic_table
ModuleTests.do_not_clone_reexports ModuleTests.do_not_clone_reexports
ModuleTests.do_not_clone_types_of_reexported_values
NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything
NonstrictModeTests.for_in_iterator_variables_are_any NonstrictModeTests.for_in_iterator_variables_are_any
NonstrictModeTests.function_parameters_are_any NonstrictModeTests.function_parameters_are_any
@ -319,6 +303,7 @@ ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weird_fail_to_unify_type_pack ProvisionalTests.weird_fail_to_unify_type_pack
ProvisionalTests.weirditer_should_not_loop_forever ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined ProvisionalTests.while_body_are_also_refined
ProvisionalTests.xpcall_returns_what_f_returns
RefinementTest.and_constraint RefinementTest.and_constraint
RefinementTest.and_or_peephole_refinement RefinementTest.and_or_peephole_refinement
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string 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.not_t_or_some_prop_of_t
RefinementTest.or_predicate_with_truthy_predicates RefinementTest.or_predicate_with_truthy_predicates
RefinementTest.parenthesized_expressions_are_followed_through 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_the_correct_types_opposite_of_when_a_is_not_number_or_string
RefinementTest.refine_unknowns RefinementTest.refine_unknowns
RefinementTest.string_not_equal_to_string_or_nil RefinementTest.string_not_equal_to_string_or_nil
@ -394,7 +380,6 @@ TableTests.augment_table
TableTests.builtin_table_names TableTests.builtin_table_names
TableTests.call_method TableTests.call_method
TableTests.cannot_augment_sealed_table TableTests.cannot_augment_sealed_table
TableTests.cannot_call_tables
TableTests.cannot_change_type_of_unsealed_table_prop TableTests.cannot_change_type_of_unsealed_table_prop
TableTests.casting_sealed_tables_with_props_into_table_with_indexer TableTests.casting_sealed_tables_with_props_into_table_with_indexer
TableTests.casting_tables_with_props_into_table_with_indexer3 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.defining_a_self_method_for_a_local_sealed_table_must_fail
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back
TableTests.dont_leak_free_table_props TableTests.dont_leak_free_table_props
TableTests.dont_quantify_table_that_belongs_to_outer_scope TableTests.dont_quantify_table_that_belongs_to_outer_scope
TableTests.dont_suggest_exact_match_keys TableTests.dont_suggest_exact_match_keys
@ -448,6 +432,7 @@ TableTests.length_operator_intersection
TableTests.length_operator_non_table_union TableTests.length_operator_non_table_union
TableTests.length_operator_union TableTests.length_operator_union
TableTests.length_operator_union_errors TableTests.length_operator_union_errors
TableTests.less_exponential_blowup_please
TableTests.meta_add TableTests.meta_add
TableTests.meta_add_both_ways TableTests.meta_add_both_ways
TableTests.meta_add_inferred 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.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works TableTests.quantifying_a_bound_var_works
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
TableTests.recursive_metatable_type_call
TableTests.result_is_always_any_if_lhs_is_any TableTests.result_is_always_any_if_lhs_is_any
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
TableTests.right_table_missing_key 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
TableTests.shared_selfs_from_free_param TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables TableTests.shared_selfs_through_metatables
TableTests.table_function_check_use_after_free
TableTests.table_indexing_error_location TableTests.table_indexing_error_location
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
TableTests.table_insert_should_cope_with_optional_properties_in_strict 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.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cli_38393_recursive_intersection_oom TypeAliases.cli_38393_recursive_intersection_oom
TypeAliases.corecursive_types_generic
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.general_require_multi_assign
TypeAliases.generic_param_remap TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_pack_type_param TypeAliases.mismatched_generic_pack_type_param
TypeAliases.mismatched_generic_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_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok TypeAliases.mutually_recursive_types_swapsies_not_ok
@ -543,7 +523,6 @@ TypeAliases.type_alias_fwd_declaration_is_precise
TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeAliases.type_alias_of_an_imported_recursive_type
TypeInfer.checking_should_not_ice TypeInfer.checking_should_not_ice
TypeInfer.cyclic_follow TypeInfer.cyclic_follow
TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table 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_2
TypeInfer.tc_if_else_expressions_expected_type_3 TypeInfer.tc_if_else_expressions_expected_type_3
TypeInfer.tc_if_else_expressions_type_union 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.type_infer_recursion_limit_no_ice
TypeInfer.warn_on_lowercase_parent_property
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
TypeInferAnyError.can_get_length_of_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_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_any
TypeInferAnyError.for_in_loop_iterator_returns_any2
TypeInferAnyError.length_of_error_type_does_not_produce_an_error TypeInferAnyError.length_of_error_type_does_not_produce_an_error
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferAnyError.union_of_types_regression_test
TypeInferClasses.call_base_method TypeInferClasses.call_base_method
TypeInferClasses.call_instance_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.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_can_have_overloaded_operators TypeInferClasses.classes_can_have_overloaded_operators
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added 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.higher_order_function_return_values_are_covariant
TypeInferClasses.optional_class_field_access_error TypeInferClasses.optional_class_field_access_error
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties 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.warn_when_prop_almost_matches
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments
TypeInferFunctions.another_recursive_local_function 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_anytypepack_doesnt_leak_free_types
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
@ -634,43 +617,23 @@ TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_return_values TypeInferFunctions.too_many_return_values
TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_function_is_quantified
TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size 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_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_custom_iterator
TypeInferLoops.for_in_loop_with_next 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.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_iter_metamethod
TypeInferLoops.loop_iter_no_indexer_nonstrict TypeInferLoops.loop_iter_no_indexer_nonstrict
TypeInferLoops.loop_iter_no_indexer_strict
TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.loop_iter_trailing_nil
TypeInferLoops.loop_typecheck_crash_on_empty_optional TypeInferLoops.loop_typecheck_crash_on_empty_optional
TypeInferLoops.properly_infer_iteratee_is_a_free_table
TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.unreachable_code_after_infinite_loop
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
TypeInferModules.do_not_modify_imported_types TypeInferModules.custom_require_global
TypeInferModules.do_not_modify_imported_types_2
TypeInferModules.do_not_modify_imported_types_3
TypeInferModules.general_require_call_expression
TypeInferModules.general_require_type_mismatch TypeInferModules.general_require_type_mismatch
TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict
TypeInferModules.module_type_conflict_instantiated TypeInferModules.module_type_conflict_instantiated
TypeInferModules.require
TypeInferModules.require_a_variadic_function TypeInferModules.require_a_variadic_function
TypeInferModules.require_failed_module
TypeInferModules.require_module_that_does_not_export
TypeInferModules.require_types TypeInferModules.require_types
TypeInferModules.type_error_of_unknown_qualified_type TypeInferModules.type_error_of_unknown_qualified_type
TypeInferModules.warn_if_you_try_to_require_a_non_modulescript
TypeInferOOP.CheckMethodsOfSealed TypeInferOOP.CheckMethodsOfSealed
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
@ -725,7 +688,6 @@ TypeInferOperators.typecheck_unary_minus_error
TypeInferOperators.unary_not_is_boolean TypeInferOperators.unary_not_is_boolean
TypeInferOperators.unknown_type_in_comparison TypeInferOperators.unknown_type_in_comparison
TypeInferOperators.UnknownGlobalCompoundAssign TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.cannot_call_primitives
TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_function_other TypeInferPrimitives.string_function_other
TypeInferPrimitives.string_index TypeInferPrimitives.string_index
@ -768,7 +730,6 @@ TypePackTests.type_alias_type_packs_nested
TypePackTests.type_pack_hidden_free_tail_infinite_growth TypePackTests.type_pack_hidden_free_tail_infinite_growth
TypePackTests.type_pack_type_parameters TypePackTests.type_pack_type_parameters
TypePackTests.varargs_inference_through_multiple_scopes TypePackTests.varargs_inference_through_multiple_scopes
TypePackTests.variadic_argument_tail
TypePackTests.variadic_pack_syntax TypePackTests.variadic_pack_syntax
TypePackTests.variadic_packs TypePackTests.variadic_packs
TypeSingletons.bool_singleton_subtype TypeSingletons.bool_singleton_subtype
@ -793,7 +754,6 @@ TypeSingletons.string_singletons_escape_chars
TypeSingletons.string_singletons_mismatch TypeSingletons.string_singletons_mismatch
TypeSingletons.table_insert_with_a_singleton_argument TypeSingletons.table_insert_with_a_singleton_argument
TypeSingletons.table_properties_type_error_escapes TypeSingletons.table_properties_type_error_escapes
TypeSingletons.tagged_unions_immutable_tag
TypeSingletons.tagged_unions_using_singletons TypeSingletons.tagged_unions_using_singletons
TypeSingletons.taking_the_length_of_string_singleton TypeSingletons.taking_the_length_of_string_singleton
TypeSingletons.taking_the_length_of_union_of_string_singleton TypeSingletons.taking_the_length_of_union_of_string_singleton

View file

@ -84,12 +84,16 @@ def main():
failList = loadFailList() failList = loadFailList()
commandLine = [
args.path,
"--reporters=xml",
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
]
print('>', ' '.join(commandLine), file=sys.stderr)
p = sp.Popen( p = sp.Popen(
[ commandLine,
args.path,
"--reporters=xml",
"--fflags=true,DebugLuauDeferredConstraintResolution=true",
],
stdout=sp.PIPE, stdout=sp.PIPE,
) )
@ -122,7 +126,7 @@ def main():
with open(FAIL_LIST_PATH, "w", newline="\n") as f: with open(FAIL_LIST_PATH, "w", newline="\n") as f:
for name in newFailList: for name in newFailList:
print(name, file=f) print(name, file=f)
print("Updated faillist.txt") print("Updated faillist.txt", file=sys.stderr)
if handler.numSkippedTests > 0: if handler.numSkippedTests > 0:
print( print(