Sync to upstream/release/544 (#669)

- Remove type definitions of
`utf8.nfcnormalize`/`nfdnormalize`/`graphemes` that aren't supported by
standalone Luau library
- Add `lua_costatus` to retrieve extended thread status (similar to
`coroutine.status`)
- Improve GC sweeping performance (2-10% improvement on allocation-heavy
benchmarks)
This commit is contained in:
Arseny Kapoulkine 2022-09-08 15:14:25 -07:00 committed by GitHub
parent b2e357da30
commit ce2c3b3a4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 1987 additions and 793 deletions

View file

@ -19,9 +19,12 @@ 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, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler, TypeId anyType,
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack); TypePackId anyTypePack);
Anyification(TypeArena* arena, const ScopePtr& scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler, TypeId anyType,
TypePackId anyTypePack);
NotNull<Scope> scope; NotNull<Scope> scope;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;
TypeId anyType; TypeId anyType;

View file

@ -1,6 +1,7 @@
// 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/Frontend.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
@ -8,6 +9,7 @@ namespace Luau
{ {
void registerBuiltinTypes(TypeChecker& typeChecker); void registerBuiltinTypes(TypeChecker& typeChecker);
void registerBuiltinTypes(Frontend& frontend);
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types); TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types); TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
@ -15,6 +17,7 @@ TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
/** Build an optional 't' /** Build an optional 't'
*/ */
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t); TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t);
TypeId makeOption(Frontend& frontend, TypeArena& arena, TypeId t);
/** Small utility function for building up type definitions from C++. /** Small utility function for building up type definitions from C++.
*/ */
@ -41,12 +44,17 @@ void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::strin
std::string getBuiltinDefinitionSource(); std::string getBuiltinDefinitionSource();
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding); void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding);
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName); void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding); void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding);
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name); void addGlobalBinding(Frontend& frontend, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(Frontend& frontend, const std::string& name, Binding binding);
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, Binding binding);
std::optional<Binding> tryGetGlobalBinding(Frontend& frontend, const std::string& name);
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name); Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name);
TypeId getGlobalBinding(Frontend& frontend, const std::string& name);
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name); TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name);
} // namespace Luau } // namespace Luau

View file

@ -21,6 +21,8 @@ namespace Luau
struct Scope; struct Scope;
using ScopePtr = std::shared_ptr<Scope>; using ScopePtr = std::shared_ptr<Scope>;
struct DcrLogger;
struct ConstraintGraphBuilder struct ConstraintGraphBuilder
{ {
// A list of all the scopes in the module. This vector holds ownership of the // A list of all the scopes in the module. This vector holds ownership of the
@ -30,7 +32,7 @@ struct ConstraintGraphBuilder
ModuleName moduleName; ModuleName moduleName;
ModulePtr module; ModulePtr module;
SingletonTypes& singletonTypes; NotNull<SingletonTypes> singletonTypes;
const NotNull<TypeArena> arena; const NotNull<TypeArena> arena;
// The root scope of the module we're generating constraints for. // The root scope of the module we're generating constraints for.
// This is null when the CGB is initially constructed. // This is null when the CGB is initially constructed.
@ -58,9 +60,10 @@ struct ConstraintGraphBuilder
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope; ScopePtr globalScope;
DcrLogger* logger;
ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver, ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope); NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, DcrLogger* logger);
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.

View file

@ -5,14 +5,16 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSolverLogger.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/ToString.h"
#include <vector> #include <vector>
namespace Luau namespace Luau
{ {
struct DcrLogger;
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we // TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
// never dereference this pointer. // never dereference this pointer.
using BlockedConstraintId = const void*; using BlockedConstraintId = const void*;
@ -40,6 +42,7 @@ struct HashInstantiationSignature
struct ConstraintSolver struct ConstraintSolver
{ {
TypeArena* arena; TypeArena* arena;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
// 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;
@ -69,10 +72,10 @@ struct ConstraintSolver
NotNull<ModuleResolver> moduleResolver; NotNull<ModuleResolver> moduleResolver;
std::vector<RequireCycle> requireCycles; std::vector<RequireCycle> requireCycles;
ConstraintSolverLogger logger; DcrLogger* logger;
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, explicit ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<Scope> rootScope, ModuleName moduleName,
std::vector<RequireCycle> requireCycles); NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
/** /**
* Attempts to dispatch all pending constraints and reach a type solution * Attempts to dispatch all pending constraints and reach a type solution

View file

@ -1,29 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Constraint.h"
#include "Luau/NotNull.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include <optional>
#include <string>
#include <vector>
namespace Luau
{
struct ConstraintSolverLogger
{
std::string compileOutput();
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 commitPreparedStepSnapshot();
private:
std::vector<std::string> snapshots;
std::optional<std::string> preparedSnapshot;
ToStringOptions opts;
};
} // namespace Luau

View file

@ -0,0 +1,131 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Constraint.h"
#include "Luau/NotNull.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include "Luau/Error.h"
#include "Luau/Variant.h"
#include <optional>
#include <string>
#include <vector>
namespace Luau
{
struct ErrorSnapshot
{
std::string message;
Location location;
};
struct BindingSnapshot
{
std::string typeId;
std::string typeString;
Location location;
};
struct TypeBindingSnapshot
{
std::string typeId;
std::string typeString;
};
struct ConstraintGenerationLog
{
std::string source;
std::unordered_map<std::string, Location> constraintLocations;
std::vector<ErrorSnapshot> errors;
};
struct ScopeSnapshot
{
std::unordered_map<Name, BindingSnapshot> bindings;
std::unordered_map<Name, TypeBindingSnapshot> typeBindings;
std::unordered_map<Name, TypeBindingSnapshot> typePackBindings;
std::vector<ScopeSnapshot> children;
};
enum class ConstraintBlockKind
{
TypeId,
TypePackId,
ConstraintId,
};
struct ConstraintBlock
{
ConstraintBlockKind kind;
std::string stringification;
};
struct ConstraintSnapshot
{
std::string stringification;
std::vector<ConstraintBlock> blocks;
};
struct BoundarySnapshot
{
std::unordered_map<std::string, ConstraintSnapshot> constraints;
ScopeSnapshot rootScope;
};
struct StepSnapshot
{
std::string currentConstraint;
bool forced;
std::unordered_map<std::string, ConstraintSnapshot> unsolvedConstraints;
ScopeSnapshot rootScope;
};
struct TypeSolveLog
{
BoundarySnapshot initialState;
std::vector<StepSnapshot> stepStates;
BoundarySnapshot finalState;
};
struct TypeCheckLog
{
std::vector<ErrorSnapshot> errors;
};
using ConstraintBlockTarget = Variant<TypeId, TypePackId, NotNull<const Constraint>>;
struct DcrLogger
{
std::string compileOutput();
void captureSource(std::string source);
void captureGenerationError(const TypeError& error);
void captureConstraintLocation(NotNull<const Constraint> constraint, Location location);
void pushBlock(NotNull<const Constraint> constraint, TypeId block);
void pushBlock(NotNull<const Constraint> constraint, TypePackId block);
void pushBlock(NotNull<const Constraint> constraint, NotNull<const Constraint> block);
void popBlock(TypeId block);
void popBlock(TypePackId block);
void popBlock(NotNull<const Constraint> block);
void captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
StepSnapshot prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void commitStepSnapshot(StepSnapshot snapshot);
void captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void captureTypeCheckError(const TypeError& error);
private:
ConstraintGenerationLog generationLog;
std::unordered_map<NotNull<const Constraint>, std::vector<ConstraintBlockTarget>> constraintBlocks;
TypeSolveLog solveLog;
TypeCheckLog checkLog;
ToStringOptions opts;
std::vector<ConstraintBlock> snapshotBlocks(NotNull<const Constraint> constraint);
};
} // namespace Luau

View file

@ -1,3 +1,4 @@
// 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/DenseHash.h" #include "Luau/DenseHash.h"

View file

@ -174,6 +174,9 @@ private:
ScopePtr globalScope; ScopePtr globalScope;
public: public:
SingletonTypes singletonTypes_;
const NotNull<SingletonTypes> singletonTypes;
FileResolver* fileResolver; FileResolver* fileResolver;
FrontendModuleResolver moduleResolver; FrontendModuleResolver moduleResolver;
FrontendModuleResolver moduleResolverForAutocomplete; FrontendModuleResolver moduleResolverForAutocomplete;

View file

@ -4,6 +4,7 @@
#include <type_traits> #include <type_traits>
#include <string> #include <string>
#include <optional> #include <optional>
#include <unordered_map>
#include <vector> #include <vector>
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
@ -232,4 +233,15 @@ void write(JsonEmitter& emitter, const std::optional<T>& v)
emitter.writeRaw("null"); emitter.writeRaw("null");
} }
template<typename T>
void write(JsonEmitter& emitter, const std::unordered_map<std::string, T>& map)
{
ObjectEmitter o = emitter.writeObject();
for (const auto& [k, v] : map)
o.writePair(k, v);
o.finish();
}
} // namespace Luau::Json } // namespace Luau::Json

View file

@ -90,7 +90,7 @@ struct Module
// Once a module has been typechecked, we clone its public interface into a separate arena. // Once a module has been typechecked, we clone its public interface into a separate arena.
// This helps us to force TypeVar ownership into a DAG rather than a DCG. // This helps us to force TypeVar ownership into a DAG rather than a DCG.
void clonePublicInterface(InternalErrorReporter& ice); void clonePublicInterface(NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
}; };
} // namespace Luau } // namespace Luau

View file

@ -1,4 +1,5 @@
// 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
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
@ -12,17 +13,20 @@ namespace Luau
struct InternalErrorReporter; struct InternalErrorReporter;
struct Module; struct Module;
struct Scope; struct Scope;
struct SingletonTypes;
using ModulePtr = std::shared_ptr<Module>; using ModulePtr = std::shared_ptr<Module>;
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, InternalErrorReporter& ice); bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, InternalErrorReporter& ice); bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Scope> scope, TypeArena& arena, InternalErrorReporter& ice); std::pair<TypeId, bool> normalize(
std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Module> module, InternalErrorReporter& ice); TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice); std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Module> module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, NotNull<Scope> scope, TypeArena& arena, InternalErrorReporter& ice); std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, NotNull<Module> module, InternalErrorReporter& ice); std::pair<TypePackId, bool> normalize(
std::pair<TypePackId, bool> normalize(TypePackId ty, const ModulePtr& module, InternalErrorReporter& ice); TypePackId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, NotNull<Module> module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
} // namespace Luau } // namespace Luau

View file

@ -31,6 +31,8 @@ struct TypeArena
TypeId freshType(TypeLevel level); TypeId freshType(TypeLevel level);
TypeId freshType(Scope* scope); TypeId freshType(Scope* scope);
TypePackId freshTypePack(Scope* scope);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {}); TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
TypePackId addTypePack(TypePack pack); TypePackId addTypePack(TypePack pack);

View file

@ -4,10 +4,14 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h"
namespace Luau namespace Luau
{ {
void check(const SourceModule& sourceModule, Module* module); struct DcrLogger;
struct SingletonTypes;
void check(NotNull<SingletonTypes> singletonTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module);
} // namespace Luau } // namespace Luau

View file

@ -58,7 +58,7 @@ public:
// within a program are borrowed pointers into this set. // within a program are borrowed pointers into this set.
struct TypeChecker struct TypeChecker
{ {
explicit TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler); explicit TypeChecker(ModuleResolver* resolver, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler);
TypeChecker(const TypeChecker&) = delete; TypeChecker(const TypeChecker&) = delete;
TypeChecker& operator=(const TypeChecker&) = delete; TypeChecker& operator=(const TypeChecker&) = delete;
@ -353,6 +353,7 @@ public:
ModuleName currentModuleName; ModuleName currentModuleName;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope; std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;
UnifierSharedState unifierState; UnifierSharedState unifierState;

View file

@ -15,10 +15,12 @@ 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(
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location); NotNull<SingletonTypes> singletonTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, std::optional<TypeId> findTablePropertyRespectingMeta(
const Location& location, bool addErrors, InternalErrorReporter& handle); NotNull<SingletonTypes> singletonTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, NotNull<SingletonTypes> singletonTypes,
TypeId type, const std::string& prop, const Location& location, bool addErrors, InternalErrorReporter& handle);
// Returns the minimum and maximum number of types the argument list can accept. // 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); std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp);

View file

@ -586,7 +586,7 @@ bool isOverloadedFunction(TypeId ty);
// True when string is a subtype of ty // True when string is a subtype of ty
bool maybeString(TypeId ty); bool maybeString(TypeId ty);
std::optional<TypeId> getMetatable(TypeId type); std::optional<TypeId> getMetatable(TypeId type, NotNull<struct SingletonTypes> singletonTypes);
TableTypeVar* getMutableTableType(TypeId type); TableTypeVar* getMutableTableType(TypeId type);
const TableTypeVar* getTableType(TypeId type); const TableTypeVar* getTableType(TypeId type);
@ -614,21 +614,6 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount);
struct SingletonTypes struct SingletonTypes
{ {
const TypeId nilType;
const TypeId numberType;
const TypeId stringType;
const TypeId booleanType;
const TypeId threadType;
const TypeId trueType;
const TypeId falseType;
const TypeId anyType;
const TypeId unknownType;
const TypeId neverType;
const TypePackId anyTypePack;
const TypePackId neverTypePack;
const TypePackId uninhabitableTypePack;
SingletonTypes(); SingletonTypes();
~SingletonTypes(); ~SingletonTypes();
SingletonTypes(const SingletonTypes&) = delete; SingletonTypes(const SingletonTypes&) = delete;
@ -644,9 +629,28 @@ private:
bool debugFreezeArena = false; bool debugFreezeArena = false;
TypeId makeStringMetatable(); TypeId makeStringMetatable();
public:
const TypeId nilType;
const TypeId numberType;
const TypeId stringType;
const TypeId booleanType;
const TypeId threadType;
const TypeId trueType;
const TypeId falseType;
const TypeId anyType;
const TypeId unknownType;
const TypeId neverType;
const TypeId errorType;
const TypePackId anyTypePack;
const TypePackId neverTypePack;
const TypePackId uninhabitableTypePack;
const TypePackId errorTypePack;
}; };
SingletonTypes& getSingletonTypes(); // Clip with FFlagLuauNoMoreGlobalSingletonTypes
SingletonTypes& DEPRECATED_getSingletonTypes();
void persist(TypeId ty); void persist(TypeId ty);
void persist(TypePackId tp); void persist(TypePackId tp);

View file

@ -3,10 +3,11 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/ParseOptions.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Substitution.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeInfer.h"
#include "Luau/UnifierSharedState.h" #include "Luau/UnifierSharedState.h"
#include <unordered_set> #include <unordered_set>
@ -23,11 +24,14 @@ enum Variance
// A substitution which replaces singleton types by their wider types // A substitution which replaces singleton types by their wider types
struct Widen : Substitution struct Widen : Substitution
{ {
Widen(TypeArena* arena) Widen(TypeArena* arena, NotNull<SingletonTypes> singletonTypes)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
, singletonTypes(singletonTypes)
{ {
} }
NotNull<SingletonTypes> singletonTypes;
bool isDirty(TypeId ty) override; bool isDirty(TypeId ty) override;
bool isDirty(TypePackId ty) override; bool isDirty(TypePackId ty) override;
TypeId clean(TypeId ty) override; TypeId clean(TypeId ty) override;
@ -47,6 +51,7 @@ struct UnifierOptions
struct Unifier struct Unifier
{ {
TypeArena* const types; TypeArena* const types;
NotNull<SingletonTypes> singletonTypes;
Mode mode; Mode mode;
NotNull<Scope> scope; // const Scope maybe NotNull<Scope> scope; // const Scope maybe
@ -59,8 +64,8 @@ struct Unifier
UnifierSharedState& sharedState; UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState, Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance,
TxnLog* parentLog = nullptr); 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

@ -11,17 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau namespace Luau
{ {
Anyification::Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack) Anyification::Anyification(TypeArena* arena, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler,
TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
, scope(scope) , scope(scope)
, singletonTypes(singletonTypes)
, 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::Anyification(TypeArena* arena, const ScopePtr& scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler,
: Anyification(arena, NotNull{scope.get()}, iceHandler, anyType, anyTypePack) TypeId anyType, TypePackId anyTypePack)
: Anyification(arena, NotNull{scope.get()}, singletonTypes, iceHandler, anyType, anyTypePack)
{ {
} }
@ -71,7 +74,7 @@ TypeId Anyification::clean(TypeId ty)
for (TypeId& ty : copy) for (TypeId& ty : copy)
ty = replace(ty); ty = replace(ty);
TypeId res = copy.size() == 1 ? copy[0] : addType(UnionTypeVar{std::move(copy)}); TypeId res = copy.size() == 1 ? copy[0] : addType(UnionTypeVar{std::move(copy)});
auto [t, ok] = normalize(res, scope, *arena, *iceHandler); auto [t, ok] = normalize(res, scope, *arena, singletonTypes, *iceHandler);
if (!ok) if (!ok)
normalizationTooComplex = true; normalizationTooComplex = true;
return t; return t;

View file

@ -14,8 +14,6 @@
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3) LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFixGlobalOrder, false)
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -137,27 +135,28 @@ static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* n
return *it; return *it;
} }
static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, TypeArena* typeArena) static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes)
{ {
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
Unifier unifier(typeArena, Mode::Strict, scope, Location(), Variance::Covariant, unifierState); Unifier unifier(typeArena, singletonTypes, Mode::Strict, scope, Location(), Variance::Covariant, unifierState);
return unifier.canUnify(subTy, superTy).empty(); return unifier.canUnify(subTy, superTy).empty();
} }
static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typeArena, AstNode* node, Position position, TypeId ty) static TypeCorrectKind checkTypeCorrectKind(
const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, AstNode* node, Position position, TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
NotNull<Scope> moduleScope{module.getModuleScope().get()}; NotNull<Scope> moduleScope{module.getModuleScope().get()};
auto canUnify = [&typeArena, moduleScope](TypeId subTy, TypeId superTy) { auto canUnify = [&typeArena, singletonTypes, moduleScope](TypeId subTy, TypeId superTy) {
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3);
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
Unifier unifier(typeArena, Mode::Strict, moduleScope, Location(), Variance::Covariant, unifierState); Unifier unifier(typeArena, singletonTypes, Mode::Strict, moduleScope, Location(), Variance::Covariant, unifierState);
unifier.tryUnify(subTy, superTy); unifier.tryUnify(subTy, superTy);
bool ok = unifier.errors.empty(); bool ok = unifier.errors.empty();
@ -171,11 +170,11 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
TypeId expectedType = follow(*typeAtPosition); TypeId expectedType = follow(*typeAtPosition);
auto checkFunctionType = [typeArena, moduleScope, &canUnify, &expectedType](const FunctionTypeVar* ftv) { auto checkFunctionType = [typeArena, singletonTypes, moduleScope, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LuauSelfCallAutocompleteFix3)
{ {
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes)) if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena); return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, singletonTypes);
return false; return false;
} }
@ -214,7 +213,8 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
} }
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LuauSelfCallAutocompleteFix3)
return checkTypeMatch(ty, expectedType, NotNull{module.getModuleScope().get()}, typeArena) ? TypeCorrectKind::Correct : TypeCorrectKind::None; return checkTypeMatch(ty, expectedType, NotNull{module.getModuleScope().get()}, typeArena, singletonTypes) ? TypeCorrectKind::Correct
: TypeCorrectKind::None;
else else
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
} }
@ -226,8 +226,8 @@ enum class PropIndexType
Key, Key,
}; };
static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId rootTy, TypeId ty, PropIndexType indexType, static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId rootTy, TypeId ty,
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen, PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> containingClass = std::nullopt) std::optional<const ClassTypeVar*> containingClass = std::nullopt)
{ {
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LuauSelfCallAutocompleteFix3)
@ -272,7 +272,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
return colonIndex; return colonIndex;
} }
}; };
auto isWrongIndexer = [typeArena, &module, rootTy, indexType](Luau::TypeId type) { auto isWrongIndexer = [typeArena, singletonTypes, &module, rootTy, indexType](Luau::TypeId type) {
LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3); LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3);
if (indexType == PropIndexType::Key) if (indexType == PropIndexType::Key)
@ -280,7 +280,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
bool calledWithSelf = indexType == PropIndexType::Colon; bool calledWithSelf = indexType == PropIndexType::Colon;
auto isCompatibleCall = [typeArena, &module, rootTy, calledWithSelf](const FunctionTypeVar* ftv) { auto isCompatibleCall = [typeArena, singletonTypes, &module, rootTy, calledWithSelf](const FunctionTypeVar* ftv) {
// Strong match with definition is a success // Strong match with definition is a success
if (calledWithSelf == ftv->hasSelf) if (calledWithSelf == ftv->hasSelf)
return true; return true;
@ -293,7 +293,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
// When called with '.', but declared with 'self', it is considered invalid if first argument is compatible // When called with '.', but declared with 'self', it is considered invalid if first argument is compatible
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes)) if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
{ {
if (checkTypeMatch(rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena)) if (checkTypeMatch(rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, singletonTypes))
return calledWithSelf; return calledWithSelf;
} }
@ -327,8 +327,9 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (result.count(name) == 0 && name != kParseNameError) if (result.count(name) == 0 && name != kParseNameError)
{ {
Luau::TypeId type = Luau::follow(prop.type); Luau::TypeId type = Luau::follow(prop.type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct TypeCorrectKind typeCorrect = indexType == PropIndexType::Key
: checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type); ? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, singletonTypes, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens = ParenthesesRecommendation parens =
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
@ -355,13 +356,13 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
TypeId followed = follow(indexIt->second.type); TypeId followed = follow(indexIt->second.type);
if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed)) if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed))
{ {
autocompleteProps(module, typeArena, rootTy, followed, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, followed, indexType, nodes, result, seen);
} }
else if (auto indexFunction = get<FunctionTypeVar>(followed)) else if (auto indexFunction = get<FunctionTypeVar>(followed))
{ {
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes); std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
if (indexFunctionResult) if (indexFunctionResult)
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
} }
} }
}; };
@ -371,13 +372,13 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
containingClass = containingClass.value_or(cls); containingClass = containingClass.value_or(cls);
fillProps(cls->props); fillProps(cls->props);
if (cls->parent) if (cls->parent)
autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass); autocompleteProps(module, typeArena, singletonTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
} }
else if (auto tbl = get<TableTypeVar>(ty)) else if (auto tbl = get<TableTypeVar>(ty))
fillProps(tbl->props); fillProps(tbl->props);
else if (auto mt = get<MetatableTypeVar>(ty)) else if (auto mt = get<MetatableTypeVar>(ty))
{ {
autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, mt->table, indexType, nodes, result, seen);
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LuauSelfCallAutocompleteFix3)
{ {
@ -395,12 +396,12 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{ {
TypeId followed = follow(indexIt->second.type); TypeId followed = follow(indexIt->second.type);
if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed)) if (get<TableTypeVar>(followed) || get<MetatableTypeVar>(followed))
autocompleteProps(module, typeArena, rootTy, followed, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, followed, indexType, nodes, result, seen);
else if (auto indexFunction = get<FunctionTypeVar>(followed)) else if (auto indexFunction = get<FunctionTypeVar>(followed))
{ {
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes); std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
if (indexFunctionResult) if (indexFunctionResult)
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
} }
} }
} }
@ -413,7 +414,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryMap inner; AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen = seen; std::unordered_set<TypeId> innerSeen = seen;
autocompleteProps(module, typeArena, rootTy, ty, indexType, nodes, inner, innerSeen); autocompleteProps(module, typeArena, singletonTypes, rootTy, ty, indexType, nodes, inner, innerSeen);
for (auto& pair : inner) for (auto& pair : inner)
result.insert(pair); result.insert(pair);
@ -436,7 +437,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (iter == endIter) if (iter == endIter)
return; return;
autocompleteProps(module, typeArena, rootTy, *iter, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, *iter, indexType, nodes, result, seen);
++iter; ++iter;
@ -454,7 +455,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
continue; continue;
} }
autocompleteProps(module, typeArena, rootTy, *iter, indexType, nodes, inner, innerSeen); autocompleteProps(module, typeArena, singletonTypes, rootTy, *iter, indexType, nodes, inner, innerSeen);
std::unordered_set<std::string> toRemove; std::unordered_set<std::string> toRemove;
@ -481,7 +482,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
} }
else if (FFlag::LuauSelfCallAutocompleteFix3 && get<StringSingleton>(get<SingletonTypeVar>(ty))) else if (FFlag::LuauSelfCallAutocompleteFix3 && get<StringSingleton>(get<SingletonTypeVar>(ty)))
{ {
autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, rootTy, singletonTypes->stringType, indexType, nodes, result, seen);
} }
} }
@ -506,18 +507,18 @@ static void autocompleteKeywords(
} }
} }
static void autocompleteProps( static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId ty, PropIndexType indexType,
const Module& module, TypeArena* typeArena, TypeId ty, PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result) const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result)
{ {
std::unordered_set<TypeId> seen; std::unordered_set<TypeId> seen;
autocompleteProps(module, typeArena, ty, ty, indexType, nodes, result, seen); autocompleteProps(module, typeArena, singletonTypes, ty, ty, indexType, nodes, result, seen);
} }
AutocompleteEntryMap autocompleteProps( AutocompleteEntryMap autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId ty,
const Module& module, TypeArena* typeArena, TypeId ty, PropIndexType indexType, const std::vector<AstNode*>& nodes) PropIndexType indexType, const std::vector<AstNode*>& nodes)
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
autocompleteProps(module, typeArena, ty, indexType, nodes, result); autocompleteProps(module, typeArena, singletonTypes, ty, indexType, nodes, result);
return result; return result;
} }
@ -1079,19 +1080,11 @@ T* extractStat(const std::vector<AstNode*>& ancestry)
static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& binding, Position pos) static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& binding, Position pos)
{ {
if (FFlag::LuauAutocompleteFixGlobalOrder) if (symbol.local)
{ return binding.location.end < pos;
if (symbol.local)
return binding.location.end < pos;
// Builtin globals have an empty location; for defined globals, we want pos to be outside of the definition range to suggest it // Builtin globals have an empty location; for defined globals, we want pos to be outside of the definition range to suggest it
return binding.location == Location() || !binding.location.containsClosed(pos); return binding.location == Location() || !binding.location.containsClosed(pos);
}
else
{
// Default Location used for global bindings, which are always legal.
return binding.location == Location() || binding.location.end < pos;
}
} }
static AutocompleteEntryMap autocompleteStatement( static AutocompleteEntryMap autocompleteStatement(
@ -1220,12 +1213,14 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
{ {
LUAU_ASSERT(!ancestry.empty()); LUAU_ASSERT(!ancestry.empty());
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
AstNode* node = ancestry.rbegin()[0]; AstNode* node = ancestry.rbegin()[0];
if (node->is<AstExprIndexName>()) if (node->is<AstExprIndexName>())
{ {
if (auto it = module.astTypes.find(node->asExpr())) if (auto it = module.astTypes.find(node->asExpr()))
autocompleteProps(module, typeArena, *it, PropIndexType::Point, ancestry, result); autocompleteProps(module, typeArena, singletonTypes, *it, PropIndexType::Point, ancestry, result);
} }
else if (autocompleteIfElseExpression(node, ancestry, position, result)) else if (autocompleteIfElseExpression(node, ancestry, position, result))
return AutocompleteContext::Keyword; return AutocompleteContext::Keyword;
@ -1249,7 +1244,7 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
std::string n = toString(name); std::string n = toString(name);
if (!result.count(n)) if (!result.count(n))
{ {
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, node, position, binding.typeId); TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, binding.typeId);
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt, result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)}; binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
@ -1259,9 +1254,9 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
scope = scope->parent; scope = scope->parent;
} }
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.nilType); TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, typeChecker.nilType);
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, node, position, getSingletonTypes().trueType); TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->trueType);
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, node, position, getSingletonTypes().falseType); TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->falseType);
TypeCorrectKind correctForFunction = TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None; functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
@ -1396,6 +1391,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (isWithinComment(sourceModule, position)) if (isWithinComment(sourceModule, position))
return {}; return {};
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position); std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
LUAU_ASSERT(!ancestry.empty()); LUAU_ASSERT(!ancestry.empty());
AstNode* node = ancestry.back(); AstNode* node = ancestry.back();
@ -1422,10 +1419,11 @@ 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 {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, return {autocompleteProps(
AutocompleteContext::Property}; *module, typeArena, singletonTypes, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
ancestry, AutocompleteContext::Property};
else else
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property}; return {autocompleteProps(*module, typeArena, singletonTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
} }
else if (auto typeReference = node->as<AstTypeReference>()) else if (auto typeReference = node->as<AstTypeReference>())
{ {
@ -1548,7 +1546,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
{ {
if (auto it = module->astExpectedTypes.find(exprTable)) if (auto it = module->astExpectedTypes.find(exprTable))
{ {
auto result = autocompleteProps(*module, typeArena, *it, PropIndexType::Key, ancestry); auto result = autocompleteProps(*module, typeArena, singletonTypes, *it, PropIndexType::Key, ancestry);
// Remove keys that are already completed // Remove keys that are already completed
for (const auto& item : exprTable->items) for (const auto& item : exprTable->items)
@ -1590,7 +1588,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>()) if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
{ {
if (auto it = module->astTypes.find(idxExpr->expr)) if (auto it = module->astTypes.find(idxExpr->expr))
autocompleteProps(*module, typeArena, follow(*it), PropIndexType::Point, ancestry, result); autocompleteProps(*module, typeArena, singletonTypes, follow(*it), PropIndexType::Point, ancestry, result);
} }
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>()) else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
{ {

View file

@ -6,6 +6,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/TypeInfer.h"
#include <algorithm> #include <algorithm>
@ -45,6 +46,11 @@ TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
return arena.addType(IntersectionTypeVar{std::move(types)}); return arena.addType(IntersectionTypeVar{std::move(types)});
} }
TypeId makeOption(Frontend& frontend, TypeArena& arena, TypeId t)
{
return makeUnion(arena, {frontend.typeChecker.nilType, t});
}
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t) TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t)
{ {
return makeUnion(arena, {typeChecker.nilType, t}); return makeUnion(arena, {typeChecker.nilType, t});
@ -128,34 +134,50 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
}; };
} }
void addGlobalBinding(Frontend& frontend, const std::string& name, TypeId ty, const std::string& packageName)
{
addGlobalBinding(frontend, frontend.getGlobalScope(), name, ty, packageName);
}
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName) void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName)
{ {
addGlobalBinding(typeChecker, typeChecker.globalScope, name, ty, packageName); addGlobalBinding(typeChecker, typeChecker.globalScope, name, ty, packageName);
} }
void addGlobalBinding(Frontend& frontend, const std::string& name, Binding binding)
{
addGlobalBinding(frontend, frontend.getGlobalScope(), name, binding);
}
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding) void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding)
{ {
addGlobalBinding(typeChecker, typeChecker.globalScope, name, binding); addGlobalBinding(typeChecker, typeChecker.globalScope, name, binding);
} }
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
{
std::string documentationSymbol = packageName + "/global/" + name;
addGlobalBinding(frontend, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
}
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName) void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
{ {
std::string documentationSymbol = packageName + "/global/" + name; std::string documentationSymbol = packageName + "/global/" + name;
addGlobalBinding(typeChecker, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol}); addGlobalBinding(typeChecker, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
} }
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, Binding binding)
{
addGlobalBinding(frontend.typeChecker, scope, name, binding);
}
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding) void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding)
{ {
scope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = binding; scope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = binding;
} }
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
{
auto t = tryGetGlobalBinding(typeChecker, name);
LUAU_ASSERT(t.has_value());
return t->typeId;
}
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name) std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name)
{ {
AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str()); AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str());
@ -166,6 +188,23 @@ std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::
return std::nullopt; return std::nullopt;
} }
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
{
auto t = tryGetGlobalBinding(typeChecker, name);
LUAU_ASSERT(t.has_value());
return t->typeId;
}
TypeId getGlobalBinding(Frontend& frontend, const std::string& name)
{
return getGlobalBinding(frontend.typeChecker, name);
}
std::optional<Binding> tryGetGlobalBinding(Frontend& frontend, const std::string& name)
{
return tryGetGlobalBinding(frontend.typeChecker, name);
}
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name) Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name)
{ {
AstName astName = typeChecker.globalNames.names->get(name.c_str()); AstName astName = typeChecker.globalNames.names->get(name.c_str());
@ -195,6 +234,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId nilType = typeChecker.nilType; TypeId nilType = typeChecker.nilType;
TypeArena& arena = typeChecker.globalTypes; TypeArena& arena = typeChecker.globalTypes;
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau"); LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
LUAU_ASSERT(loadResult.success); LUAU_ASSERT(loadResult.success);
@ -203,7 +243,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId genericV = arena.addType(GenericTypeVar{"V"}); TypeId genericV = arena.addType(GenericTypeVar{"V"});
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic}); TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(getSingletonTypes().stringType); std::optional<TypeId> stringMetatableTy = getMetatable(singletonTypes->stringType, singletonTypes);
LUAU_ASSERT(stringMetatableTy); LUAU_ASSERT(stringMetatableTy);
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy)); const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable); LUAU_ASSERT(stringMetatableTable);
@ -277,6 +317,98 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire); attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
} }
void registerBuiltinTypes(Frontend& frontend)
{
LUAU_ASSERT(!frontend.globalTypes.typeVars.isFrozen());
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
TypeId nilType = frontend.typeChecker.nilType;
TypeArena& arena = frontend.globalTypes;
NotNull<SingletonTypes> singletonTypes = frontend.singletonTypes;
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(getBuiltinDefinitionSource(), "@luau");
LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericTypeVar{"K"});
TypeId genericV = arena.addType(GenericTypeVar{"V"});
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), frontend.getGlobalScope()->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(singletonTypes->stringType, singletonTypes);
LUAU_ASSERT(stringMetatableTy);
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable);
auto it = stringMetatableTable->props.find("__index");
LUAU_ASSERT(it != stringMetatableTable->props.end());
addGlobalBinding(frontend, "string", it->second.type, "@luau");
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
addGlobalBinding(frontend, "next",
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
TableTypeVar tab{TableState::Generic, frontend.getGlobalScope()->level};
TypeId tabTy = arena.addType(tab);
TypeId tableMetaMT = arena.addType(MetatableTypeVar{tabTy, genericMT});
addGlobalBinding(frontend, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
// clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
addGlobalBinding(frontend, "setmetatable",
arena.addType(
FunctionTypeVar{
{genericMT},
{},
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
);
// clang-format on
for (const auto& pair : frontend.getGlobalScope()->bindings)
{
persist(pair.second.typeId);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = toString(pair.first);
}
}
attachMagicFunction(getGlobalBinding(frontend, "assert"), magicFunctionAssert);
attachMagicFunction(getGlobalBinding(frontend, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(frontend, "select"), magicFunctionSelect);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(frontend, "table")))
{
// tabTy is a generic table type which we can't express via declaration syntax yet
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
attachMagicFunction(ttv->props["pack"].type, magicFunctionPack);
}
attachMagicFunction(getGlobalBinding(frontend, "require"), magicFunctionRequire);
attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire);
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate) TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{ {

View file

@ -7,8 +7,10 @@
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/DcrLogger.h"
LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
#include "Luau/Scope.h" #include "Luau/Scope.h"
@ -35,17 +37,20 @@ static std::optional<AstExpr*> matchRequire(const AstExprCall& call)
} }
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena, ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
NotNull<ModuleResolver> moduleResolver, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope) NotNull<ModuleResolver> moduleResolver, NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, DcrLogger* logger)
: moduleName(moduleName) : moduleName(moduleName)
, module(module) , module(module)
, singletonTypes(getSingletonTypes()) , singletonTypes(singletonTypes)
, arena(arena) , arena(arena)
, rootScope(nullptr) , rootScope(nullptr)
, moduleResolver(moduleResolver) , moduleResolver(moduleResolver)
, ice(ice) , ice(ice)
, globalScope(globalScope) , globalScope(globalScope)
, logger(logger)
{ {
LUAU_ASSERT(arena); if (FFlag::DebugLuauLogSolverToJson)
LUAU_ASSERT(logger);
LUAU_ASSERT(module); LUAU_ASSERT(module);
} }
@ -66,6 +71,7 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
scopes.emplace_back(node->location, scope); scopes.emplace_back(node->location, scope);
scope->returnType = parent->returnType; scope->returnType = parent->returnType;
scope->varargPack = parent->varargPack;
parent->children.push_back(NotNull{scope.get()}); parent->children.push_back(NotNull{scope.get()});
module->astScopes[node] = scope.get(); module->astScopes[node] = scope.get();
@ -282,7 +288,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
return; return;
TypeId t = check(scope, expr); TypeId t = check(scope, expr);
addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes.numberType}); addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes->numberType});
}; };
checkNumber(for_->from); checkNumber(for_->from);
@ -290,7 +296,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
checkNumber(for_->step); checkNumber(for_->step);
ScopePtr forScope = childScope(for_, scope); ScopePtr forScope = childScope(for_, scope);
forScope->bindings[for_->var] = Binding{singletonTypes.numberType, for_->var->location}; forScope->bindings[for_->var] = Binding{singletonTypes->numberType, for_->var->location};
visit(forScope, for_->body); visit(forScope, for_->body);
} }
@ -435,7 +441,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
} }
else if (AstExprError* err = function->name->as<AstExprError>()) else if (AstExprError* err = function->name->as<AstExprError>())
{ {
functionType = singletonTypes.errorRecoveryType(); functionType = singletonTypes->errorRecoveryType();
} }
LUAU_ASSERT(functionType != nullptr); LUAU_ASSERT(functionType != nullptr);
@ -657,12 +663,18 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
std::vector<TypeId> genericTys; std::vector<TypeId> genericTys;
genericTys.reserve(generics.size()); genericTys.reserve(generics.size());
for (auto& [name, generic] : generics) for (auto& [name, generic] : generics)
{
genericTys.push_back(generic.ty); genericTys.push_back(generic.ty);
scope->privateTypeBindings[name] = TypeFun{generic.ty};
}
std::vector<TypePackId> genericTps; std::vector<TypePackId> genericTps;
genericTps.reserve(genericPacks.size()); genericTps.reserve(genericPacks.size());
for (auto& [name, generic] : genericPacks) for (auto& [name, generic] : genericPacks)
{
genericTps.push_back(generic.tp); genericTps.push_back(generic.tp);
scope->privateTypePackBindings[name] = generic.tp;
}
ScopePtr funScope = scope; ScopePtr funScope = scope;
if (!generics.empty() || !genericPacks.empty()) if (!generics.empty() || !genericPacks.empty())
@ -710,7 +722,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LuauCheckRecursionLimit)
{ {
reportCodeTooComplex(expr->location); reportCodeTooComplex(expr->location);
return singletonTypes.errorRecoveryTypePack(); return singletonTypes->errorRecoveryTypePack();
} }
TypePackId result = nullptr; TypePackId result = nullptr;
@ -758,7 +770,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
if (scope->varargPack) if (scope->varargPack)
result = *scope->varargPack; result = *scope->varargPack;
else else
result = singletonTypes.errorRecoveryTypePack(); result = singletonTypes->errorRecoveryTypePack();
} }
else else
{ {
@ -778,7 +790,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LuauCheckRecursionLimit)
{ {
reportCodeTooComplex(expr->location); reportCodeTooComplex(expr->location);
return singletonTypes.errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
TypeId result = nullptr; TypeId result = nullptr;
@ -786,20 +798,20 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
if (auto group = expr->as<AstExprGroup>()) if (auto group = expr->as<AstExprGroup>())
result = check(scope, group->expr); result = check(scope, group->expr);
else if (expr->is<AstExprConstantString>()) else if (expr->is<AstExprConstantString>())
result = singletonTypes.stringType; result = singletonTypes->stringType;
else if (expr->is<AstExprConstantNumber>()) else if (expr->is<AstExprConstantNumber>())
result = singletonTypes.numberType; result = singletonTypes->numberType;
else if (expr->is<AstExprConstantBool>()) else if (expr->is<AstExprConstantBool>())
result = singletonTypes.booleanType; result = singletonTypes->booleanType;
else if (expr->is<AstExprConstantNil>()) else if (expr->is<AstExprConstantNil>())
result = singletonTypes.nilType; result = singletonTypes->nilType;
else if (auto a = expr->as<AstExprLocal>()) else if (auto a = expr->as<AstExprLocal>())
{ {
std::optional<TypeId> ty = scope->lookup(a->local); std::optional<TypeId> ty = scope->lookup(a->local);
if (ty) if (ty)
result = *ty; result = *ty;
else else
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point? result = singletonTypes->errorRecoveryType(); // FIXME? Record an error at this point?
} }
else if (auto g = expr->as<AstExprGlobal>()) else if (auto g = expr->as<AstExprGlobal>())
{ {
@ -812,7 +824,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
* global that is not already in-scope is definitely an unknown symbol. * global that is not already in-scope is definitely an unknown symbol.
*/ */
reportError(g->location, UnknownSymbol{g->name.value}); reportError(g->location, UnknownSymbol{g->name.value});
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point? result = singletonTypes->errorRecoveryType(); // FIXME? Record an error at this point?
} }
} }
else if (expr->is<AstExprVarargs>()) else if (expr->is<AstExprVarargs>())
@ -842,7 +854,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
else if (auto err = expr->as<AstExprError>()) else if (auto err = expr->as<AstExprError>())
{ {
// Open question: Should we traverse into this? // Open question: Should we traverse into this?
result = singletonTypes.errorRecoveryType(); result = singletonTypes->errorRecoveryType();
} }
else else
{ {
@ -903,7 +915,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
} }
LUAU_UNREACHABLE(); LUAU_UNREACHABLE();
return singletonTypes.errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary) TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary)
@ -1003,7 +1015,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
} }
else else
{ {
TypeId numberType = singletonTypes.numberType; TypeId numberType = singletonTypes->numberType;
// FIXME? The location isn't quite right here. Not sure what is // FIXME? The location isn't quite right here. Not sure what is
// right. // right.
createIndexer(item.value->location, numberType, itemTy); createIndexer(item.value->location, numberType, itemTy);
@ -1068,6 +1080,23 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
signatureScope = bodyScope; signatureScope = bodyScope;
} }
std::optional<TypePackId> varargPack;
if (fn->vararg)
{
if (fn->varargAnnotation)
{
TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation);
varargPack = annotationType;
}
else
{
varargPack = arena->freshTypePack(signatureScope.get());
}
signatureScope->varargPack = varargPack;
}
if (fn->returnAnnotation) if (fn->returnAnnotation)
{ {
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
@ -1092,7 +1121,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// TODO: Vararg annotation. // TODO: Vararg annotation.
// TODO: Preserve argument names in the function's type. // TODO: Preserve argument names in the function's type.
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType}; FunctionTypeVar actualFunction{arena->addTypePack(argTypes, varargPack), returnType};
actualFunction.hasNoGenerics = !hasGenerics; actualFunction.hasNoGenerics = !hasGenerics;
actualFunction.generics = std::move(genericTypes); actualFunction.generics = std::move(genericTypes);
actualFunction.genericPacks = std::move(genericTypePacks); actualFunction.genericPacks = std::move(genericTypePacks);
@ -1175,7 +1204,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
else else
{ {
reportError(ty->location, UnknownSymbol{ref->name.value, UnknownSymbol::Context::Type}); reportError(ty->location, UnknownSymbol{ref->name.value, UnknownSymbol::Context::Type});
result = singletonTypes.errorRecoveryType(); result = singletonTypes->errorRecoveryType();
} }
} }
else if (auto tab = ty->as<AstTypeTable>()) else if (auto tab = ty->as<AstTypeTable>())
@ -1308,12 +1337,12 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
} }
else if (ty->is<AstTypeError>()) else if (ty->is<AstTypeError>())
{ {
result = singletonTypes.errorRecoveryType(); result = singletonTypes->errorRecoveryType();
} }
else else
{ {
LUAU_ASSERT(0); LUAU_ASSERT(0);
result = singletonTypes.errorRecoveryType(); result = singletonTypes->errorRecoveryType();
} }
astResolvedTypes[ty] = result; astResolvedTypes[ty] = result;
@ -1341,13 +1370,13 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
else else
{ {
reportError(tp->location, UnknownSymbol{gen->genericName.value, UnknownSymbol::Context::Type}); reportError(tp->location, UnknownSymbol{gen->genericName.value, UnknownSymbol::Context::Type});
result = singletonTypes.errorRecoveryTypePack(); result = singletonTypes->errorRecoveryTypePack();
} }
} }
else else
{ {
LUAU_ASSERT(0); LUAU_ASSERT(0);
result = singletonTypes.errorRecoveryTypePack(); result = singletonTypes->errorRecoveryTypePack();
} }
astResolvedTypePacks[tp] = result; astResolvedTypePacks[tp] = result;
@ -1430,11 +1459,17 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat
void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err) void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)
{ {
errors.push_back(TypeError{location, moduleName, std::move(err)}); errors.push_back(TypeError{location, moduleName, std::move(err)});
if (FFlag::DebugLuauLogSolverToJson)
logger->captureGenerationError(errors.back());
} }
void ConstraintGraphBuilder::reportCodeTooComplex(Location location) void ConstraintGraphBuilder::reportCodeTooComplex(Location location)
{ {
errors.push_back(TypeError{location, moduleName, CodeTooComplex{}}); errors.push_back(TypeError{location, moduleName, CodeTooComplex{}});
if (FFlag::DebugLuauLogSolverToJson)
logger->captureGenerationError(errors.back());
} }
struct GlobalPrepopulator : AstVisitor struct GlobalPrepopulator : AstVisitor

View file

@ -9,6 +9,7 @@
#include "Luau/Quantify.h" #include "Luau/Quantify.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
#include "Luau/DcrLogger.h"
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
@ -50,8 +51,8 @@ static void dumpConstraints(NotNull<Scope> scope, ToStringOptions& opts)
dumpConstraints(child, opts); dumpConstraints(child, opts);
} }
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments( static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(TypeArena* arena, NotNull<SingletonTypes> singletonTypes,
const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments, TypeArena* arena) const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments)
{ {
std::vector<TypeId> saturatedTypeArguments; std::vector<TypeId> saturatedTypeArguments;
std::vector<TypeId> extraTypes; std::vector<TypeId> extraTypes;
@ -131,7 +132,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
if (!defaultTy) if (!defaultTy)
break; break;
TypeId instantiatedDefault = atf.substitute(defaultTy).value_or(getSingletonTypes().errorRecoveryType()); TypeId instantiatedDefault = atf.substitute(defaultTy).value_or(singletonTypes->errorRecoveryType());
atf.typeArguments[fn.typeParams[i].ty] = instantiatedDefault; atf.typeArguments[fn.typeParams[i].ty] = instantiatedDefault;
saturatedTypeArguments.push_back(instantiatedDefault); saturatedTypeArguments.push_back(instantiatedDefault);
} }
@ -149,7 +150,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
if (!defaultTp) if (!defaultTp)
break; break;
TypePackId instantiatedDefault = atf.substitute(defaultTp).value_or(getSingletonTypes().errorRecoveryTypePack()); TypePackId instantiatedDefault = atf.substitute(defaultTp).value_or(singletonTypes->errorRecoveryTypePack());
atf.typePackArguments[fn.typePackParams[i].tp] = instantiatedDefault; atf.typePackArguments[fn.typePackParams[i].tp] = instantiatedDefault;
saturatedPackArguments.push_back(instantiatedDefault); saturatedPackArguments.push_back(instantiatedDefault);
} }
@ -167,12 +168,12 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
// even if they're missing, so we use the error type as a filler. // even if they're missing, so we use the error type as a filler.
for (size_t i = saturatedTypeArguments.size(); i < typesRequired; ++i) for (size_t i = saturatedTypeArguments.size(); i < typesRequired; ++i)
{ {
saturatedTypeArguments.push_back(getSingletonTypes().errorRecoveryType()); saturatedTypeArguments.push_back(singletonTypes->errorRecoveryType());
} }
for (size_t i = saturatedPackArguments.size(); i < packsRequired; ++i) for (size_t i = saturatedPackArguments.size(); i < packsRequired; ++i)
{ {
saturatedPackArguments.push_back(getSingletonTypes().errorRecoveryTypePack()); saturatedPackArguments.push_back(singletonTypes->errorRecoveryTypePack());
} }
// At this point, these two conditions should be true. If they aren't we // At this point, these two conditions should be true. If they aren't we
@ -242,14 +243,16 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
} }
} }
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<Scope> rootScope, ModuleName moduleName,
std::vector<RequireCycle> requireCycles) NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
: arena(arena) : arena(arena)
, singletonTypes(singletonTypes)
, constraints(collectConstraints(rootScope)) , constraints(collectConstraints(rootScope))
, rootScope(rootScope) , rootScope(rootScope)
, currentModuleName(std::move(moduleName)) , currentModuleName(std::move(moduleName))
, moduleResolver(moduleResolver) , moduleResolver(moduleResolver)
, requireCycles(requireCycles) , requireCycles(requireCycles)
, logger(logger)
{ {
opts.exhaustive = true; opts.exhaustive = true;
@ -262,6 +265,9 @@ ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, M
block(dep, c); block(dep, c);
} }
} }
if (FFlag::DebugLuauLogSolverToJson)
LUAU_ASSERT(logger);
} }
void ConstraintSolver::run() void ConstraintSolver::run()
@ -277,7 +283,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLuauLogSolverToJson)
{ {
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints); logger->captureInitialSolverState(rootScope, unsolvedConstraints);
} }
auto runSolverPass = [&](bool force) { auto runSolverPass = [&](bool force) {
@ -294,10 +300,11 @@ void ConstraintSolver::run()
} }
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{}; std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{};
StepSnapshot snapshot;
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLuauLogSolverToJson)
{ {
logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints, force); snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
} }
bool success = tryDispatch(c, force); bool success = tryDispatch(c, force);
@ -311,7 +318,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLuauLogSolverToJson)
{ {
logger.commitPreparedStepSnapshot(); logger->commitStepSnapshot(snapshot);
} }
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
@ -347,8 +354,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLuauLogSolverToJson)
{ {
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints); logger->captureFinalSolverState(rootScope, unsolvedConstraints);
printf("Logger output:\n%s\n", logger.compileOutput().c_str());
} }
} }
@ -516,7 +522,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
if (isBlocked(leftType)) if (isBlocked(leftType))
{ {
asMutable(resultType)->ty.emplace<BoundTypeVar>(getSingletonTypes().errorRecoveryType()); asMutable(resultType)->ty.emplace<BoundTypeVar>(singletonTypes->errorRecoveryType());
// reportError(constraint->location, CannotInferBinaryOperation{c.op, std::nullopt, CannotInferBinaryOperation::Operation}); // reportError(constraint->location, CannotInferBinaryOperation{c.op, std::nullopt, CannotInferBinaryOperation::Operation});
return true; return true;
} }
@ -571,7 +577,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
if (0 == iteratorTypes.size()) if (0 == iteratorTypes.size())
{ {
Anyification anyify{ Anyification anyify{
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()}; arena, constraint->scope, singletonTypes, &iceReporter, singletonTypes->errorRecoveryType(), singletonTypes->errorRecoveryTypePack()};
std::optional<TypePackId> anyified = anyify.substitute(c.variables); std::optional<TypePackId> anyified = anyify.substitute(c.variables);
LUAU_ASSERT(anyified); LUAU_ASSERT(anyified);
unify(*anyified, c.variables, constraint->scope); unify(*anyified, c.variables, constraint->scope);
@ -585,11 +591,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
if (get<FunctionTypeVar>(nextTy)) if (get<FunctionTypeVar>(nextTy))
{ {
TypeId tableTy = getSingletonTypes().nilType; TypeId tableTy = singletonTypes->nilType;
if (iteratorTypes.size() >= 2) if (iteratorTypes.size() >= 2)
tableTy = iteratorTypes[1]; tableTy = iteratorTypes[1];
TypeId firstIndexTy = getSingletonTypes().nilType; TypeId firstIndexTy = singletonTypes->nilType;
if (iteratorTypes.size() >= 3) if (iteratorTypes.size() >= 3)
firstIndexTy = iteratorTypes[2]; firstIndexTy = iteratorTypes[2];
@ -644,7 +650,7 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor
if (!tf.has_value()) if (!tf.has_value())
return true; return true;
auto [typeArguments, packArguments] = saturateArguments(*tf, petv.typeArguments, petv.packArguments, solver->arena); auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->singletonTypes, *tf, petv.typeArguments, petv.packArguments);
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
{ {
@ -698,7 +704,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (!tf.has_value()) if (!tf.has_value())
{ {
reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location); reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
bindResult(getSingletonTypes().errorRecoveryType()); bindResult(singletonTypes->errorRecoveryType());
return true; return true;
} }
@ -710,7 +716,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true; return true;
} }
auto [typeArguments, packArguments] = saturateArguments(*tf, petv->typeArguments, petv->packArguments, arena); auto [typeArguments, packArguments] = saturateArguments(arena, singletonTypes, *tf, petv->typeArguments, petv->packArguments);
bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) { bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
return itp == p.ty; return itp == p.ty;
@ -757,7 +763,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (itf.foundInfiniteType) if (itf.foundInfiniteType)
{ {
// TODO (CLI-56761): Report an error. // TODO (CLI-56761): Report an error.
bindResult(getSingletonTypes().errorRecoveryType()); bindResult(singletonTypes->errorRecoveryType());
return true; return true;
} }
@ -780,7 +786,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (!maybeInstantiated.has_value()) if (!maybeInstantiated.has_value())
{ {
// TODO (CLI-56761): Report an error. // TODO (CLI-56761): Report an error.
bindResult(getSingletonTypes().errorRecoveryType()); bindResult(singletonTypes->errorRecoveryType());
return true; return true;
} }
@ -894,7 +900,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
return block_(iteratorTy); return block_(iteratorTy);
auto anyify = [&](auto ty) { auto anyify = [&](auto ty) {
Anyification anyify{arena, constraint->scope, &iceReporter, getSingletonTypes().anyType, getSingletonTypes().anyTypePack}; Anyification anyify{arena, constraint->scope, singletonTypes, &iceReporter, singletonTypes->anyType, singletonTypes->anyTypePack};
std::optional anyified = anyify.substitute(ty); std::optional anyified = anyify.substitute(ty);
if (!anyified) if (!anyified)
reportError(CodeTooComplex{}, constraint->location); reportError(CodeTooComplex{}, constraint->location);
@ -904,7 +910,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
auto errorify = [&](auto ty) { auto errorify = [&](auto ty) {
Anyification anyify{ Anyification anyify{
arena, constraint->scope, &iceReporter, getSingletonTypes().errorRecoveryType(), getSingletonTypes().errorRecoveryTypePack()}; arena, constraint->scope, singletonTypes, &iceReporter, singletonTypes->errorRecoveryType(), singletonTypes->errorRecoveryTypePack()};
std::optional errorified = anyify.substitute(ty); std::optional errorified = anyify.substitute(ty);
if (!errorified) if (!errorified)
reportError(CodeTooComplex{}, constraint->location); reportError(CodeTooComplex{}, constraint->location);
@ -973,7 +979,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
: firstIndexTy; : firstIndexTy;
// nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...) // nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...)
const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, getSingletonTypes().nilType}})}); const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, singletonTypes->nilType}})});
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope}); const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy}); const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
@ -995,23 +1001,35 @@ 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::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).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::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).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;
} }
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint) bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
{ {
if (FFlag::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).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;
} }
@ -1042,16 +1060,25 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
void ConstraintSolver::unblock(NotNull<const Constraint> progressed) void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
{ {
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(progressed);
return unblock_(progressed); return unblock_(progressed);
} }
void ConstraintSolver::unblock(TypeId progressed) void ConstraintSolver::unblock(TypeId progressed)
{ {
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(progressed);
return unblock_(progressed); return unblock_(progressed);
} }
void ConstraintSolver::unblock(TypePackId progressed) void ConstraintSolver::unblock(TypePackId progressed)
{ {
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(progressed);
return unblock_(progressed); return unblock_(progressed);
} }
@ -1086,13 +1113,13 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope) void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
{ {
UnifierSharedState sharedState{&iceReporter}; UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, scope, Location{}, Covariant, sharedState}; Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.tryUnify(subType, superType); u.tryUnify(subType, superType);
if (!u.errors.empty()) if (!u.errors.empty())
{ {
TypeId errorType = getSingletonTypes().errorRecoveryType(); TypeId errorType = singletonTypes->errorRecoveryType();
u.tryUnify(subType, errorType); u.tryUnify(subType, errorType);
u.tryUnify(superType, errorType); u.tryUnify(superType, errorType);
} }
@ -1108,7 +1135,7 @@ void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> sc
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope) void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope)
{ {
UnifierSharedState sharedState{&iceReporter}; UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, scope, Location{}, Covariant, sharedState}; Unifier u{arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.tryUnify(subPack, superPack); u.tryUnify(subPack, superPack);
@ -1133,7 +1160,7 @@ TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& l
if (info.name.empty()) if (info.name.empty())
{ {
reportError(UnknownRequire{}, location); reportError(UnknownRequire{}, location);
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name); std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name);
@ -1141,7 +1168,7 @@ TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& l
for (const auto& [location, path] : requireCycles) for (const auto& [location, path] : requireCycles)
{ {
if (!path.empty() && path.front() == humanReadableName) if (!path.empty() && path.front() == humanReadableName)
return getSingletonTypes().anyType; return singletonTypes->anyType;
} }
ModulePtr module = moduleResolver->getModule(info.name); ModulePtr module = moduleResolver->getModule(info.name);
@ -1150,24 +1177,24 @@ TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& l
if (!moduleResolver->moduleExists(info.name) && !info.optional) if (!moduleResolver->moduleExists(info.name) && !info.optional)
reportError(UnknownRequire{humanReadableName}, location); reportError(UnknownRequire{humanReadableName}, location);
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
if (module->type != SourceCode::Type::Module) if (module->type != SourceCode::Type::Module)
{ {
reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location); reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
TypePackId modulePack = module->getModuleScope()->returnType; TypePackId modulePack = module->getModuleScope()->returnType;
if (get<Unifiable::Error>(modulePack)) if (get<Unifiable::Error>(modulePack))
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
std::optional<TypeId> moduleType = first(modulePack); std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType) if (!moduleType)
{ {
reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location); reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
return *moduleType; return *moduleType;

View file

@ -1,150 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintSolverLogger.h"
#include "Luau/JsonEmitter.h"
#include "Luau/ToString.h"
LUAU_FASTFLAG(LuauFixNameMaps);
namespace Luau
{
static void dumpScopeAndChildren(const Scope* scope, Json::JsonEmitter& emitter, ToStringOptions& opts)
{
emitter.writeRaw("{");
Json::write(emitter, "bindings");
emitter.writeRaw(":");
Json::ObjectEmitter o = emitter.writeObject();
for (const auto& [name, binding] : scope->bindings)
{
if (FFlag::LuauFixNameMaps)
o.writePair(name.c_str(), toString(binding.typeId, opts));
else
{
ToStringResult result = toStringDetailed(binding.typeId, opts);
opts.DEPRECATED_nameMap = std::move(result.DEPRECATED_nameMap);
o.writePair(name.c_str(), result.name);
}
}
o.finish();
emitter.writeRaw(",");
Json::write(emitter, "children");
emitter.writeRaw(":");
Json::ArrayEmitter a = emitter.writeArray();
for (const Scope* child : scope->children)
{
emitter.writeComma();
dumpScopeAndChildren(child, emitter, opts);
}
a.finish();
emitter.writeRaw("}");
}
static std::string dumpConstraintsToDot(std::vector<NotNull<const Constraint>>& constraints, ToStringOptions& opts)
{
std::string result = "digraph Constraints {\n";
result += "rankdir=LR\n";
std::unordered_set<NotNull<const Constraint>> contained;
for (NotNull<const Constraint> c : constraints)
{
contained.insert(c);
}
for (NotNull<const Constraint> c : constraints)
{
std::string shape;
if (get<SubtypeConstraint>(*c))
shape = "box";
else if (get<PackSubtypeConstraint>(*c))
shape = "box3d";
else
shape = "oval";
std::string id = std::to_string(reinterpret_cast<size_t>(c.get()));
result += id;
result += " [label=\"";
result += toString(*c, opts);
result += "\" shape=" + shape + "];\n";
for (NotNull<const Constraint> dep : c->dependencies)
{
if (contained.count(dep) == 0)
continue;
result += std::to_string(reinterpret_cast<size_t>(dep.get()));
result += " -> ";
result += id;
result += ";\n";
}
}
result += "}";
return result;
}
std::string ConstraintSolverLogger::compileOutput()
{
Json::JsonEmitter emitter;
emitter.writeRaw("[");
for (const std::string& snapshot : snapshots)
{
emitter.writeComma();
emitter.writeRaw(snapshot);
}
emitter.writeRaw("]");
return emitter.str();
}
void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
Json::JsonEmitter emitter;
Json::ObjectEmitter o = emitter.writeObject();
o.writePair("type", "boundary");
o.writePair("constraintGraph", dumpConstraintsToDot(unsolvedConstraints, opts));
emitter.writeComma();
Json::write(emitter, "rootScope");
emitter.writeRaw(":");
dumpScopeAndChildren(rootScope, emitter, opts);
o.finish();
snapshots.push_back(emitter.str());
}
void ConstraintSolverLogger::prepareStepSnapshot(
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints, bool force)
{
Json::JsonEmitter emitter;
Json::ObjectEmitter o = emitter.writeObject();
o.writePair("type", "step");
o.writePair("constraintGraph", dumpConstraintsToDot(unsolvedConstraints, opts));
o.writePair("currentId", std::to_string(reinterpret_cast<size_t>(current.get())));
o.writePair("current", toString(*current, opts));
o.writePair("force", force);
emitter.writeComma();
Json::write(emitter, "rootScope");
emitter.writeRaw(":");
dumpScopeAndChildren(rootScope, emitter, opts);
o.finish();
preparedSnapshot = emitter.str();
}
void ConstraintSolverLogger::commitPreparedStepSnapshot()
{
if (preparedSnapshot)
{
snapshots.push_back(std::move(*preparedSnapshot));
preparedSnapshot = std::nullopt;
}
}
} // namespace Luau

395
Analysis/src/DcrLogger.cpp Normal file
View file

@ -0,0 +1,395 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DcrLogger.h"
#include <algorithm>
#include "Luau/JsonEmitter.h"
namespace Luau
{
namespace Json
{
void write(JsonEmitter& emitter, const Location& location)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("beginLine", location.begin.line);
o.writePair("beginColumn", location.begin.column);
o.writePair("endLine", location.end.line);
o.writePair("endColumn", location.end.column);
o.finish();
}
void write(JsonEmitter& emitter, const ErrorSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("message", snapshot.message);
o.writePair("location", snapshot.location);
o.finish();
}
void write(JsonEmitter& emitter, const BindingSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("typeId", snapshot.typeId);
o.writePair("typeString", snapshot.typeString);
o.writePair("location", snapshot.location);
o.finish();
}
void write(JsonEmitter& emitter, const TypeBindingSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("typeId", snapshot.typeId);
o.writePair("typeString", snapshot.typeString);
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintGenerationLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("source", log.source);
emitter.writeComma();
write(emitter, "constraintLocations");
emitter.writeRaw(":");
ObjectEmitter locationEmitter = emitter.writeObject();
for (const auto& [id, location] : log.constraintLocations)
{
locationEmitter.writePair(id, location);
}
locationEmitter.finish();
o.writePair("errors", log.errors);
o.finish();
}
void write(JsonEmitter& emitter, const ScopeSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("bindings", snapshot.bindings);
o.writePair("typeBindings", snapshot.typeBindings);
o.writePair("typePackBindings", snapshot.typePackBindings);
o.writePair("children", snapshot.children);
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintBlockKind& kind)
{
switch (kind)
{
case ConstraintBlockKind::TypeId:
return write(emitter, "type");
case ConstraintBlockKind::TypePackId:
return write(emitter, "typePack");
case ConstraintBlockKind::ConstraintId:
return write(emitter, "constraint");
default:
LUAU_ASSERT(0);
}
}
void write(JsonEmitter& emitter, const ConstraintBlock& block)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("kind", block.kind);
o.writePair("stringification", block.stringification);
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("stringification", snapshot.stringification);
o.writePair("blocks", snapshot.blocks);
o.finish();
}
void write(JsonEmitter& emitter, const BoundarySnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("rootScope", snapshot.rootScope);
o.writePair("constraints", snapshot.constraints);
o.finish();
}
void write(JsonEmitter& emitter, const StepSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("currentConstraint", snapshot.currentConstraint);
o.writePair("forced", snapshot.forced);
o.writePair("unsolvedConstraints", snapshot.unsolvedConstraints);
o.writePair("rootScope", snapshot.rootScope);
o.finish();
}
void write(JsonEmitter& emitter, const TypeSolveLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("initialState", log.initialState);
o.writePair("stepStates", log.stepStates);
o.writePair("finalState", log.finalState);
o.finish();
}
void write(JsonEmitter& emitter, const TypeCheckLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("errors", log.errors);
o.finish();
}
} // namespace Json
static std::string toPointerId(NotNull<const Constraint> ptr)
{
return std::to_string(reinterpret_cast<size_t>(ptr.get()));
}
static ScopeSnapshot snapshotScope(const Scope* scope, ToStringOptions& opts)
{
std::unordered_map<Name, BindingSnapshot> bindings;
std::unordered_map<Name, TypeBindingSnapshot> typeBindings;
std::unordered_map<Name, TypeBindingSnapshot> typePackBindings;
std::vector<ScopeSnapshot> children;
for (const auto& [name, binding] : scope->bindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(binding.typeId));
ToStringResult result = toStringDetailed(binding.typeId, opts);
bindings[name.c_str()] = BindingSnapshot{
id,
result.name,
binding.location,
};
}
for (const auto& [name, tf] : scope->exportedTypeBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tf.type));
typeBindings[name] = TypeBindingSnapshot{
id,
toString(tf.type, opts),
};
}
for (const auto& [name, tf] : scope->privateTypeBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tf.type));
typeBindings[name] = TypeBindingSnapshot{
id,
toString(tf.type, opts),
};
}
for (const auto& [name, tp] : scope->privateTypePackBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tp));
typePackBindings[name] = TypeBindingSnapshot{
id,
toString(tp, opts),
};
}
for (const auto& child : scope->children)
{
children.push_back(snapshotScope(child.get(), opts));
}
return ScopeSnapshot{
bindings,
typeBindings,
typePackBindings,
children,
};
}
std::string DcrLogger::compileOutput()
{
Json::JsonEmitter emitter;
Json::ObjectEmitter o = emitter.writeObject();
o.writePair("generation", generationLog);
o.writePair("solve", solveLog);
o.writePair("check", checkLog);
o.finish();
return emitter.str();
}
void DcrLogger::captureSource(std::string source)
{
generationLog.source = std::move(source);
}
void DcrLogger::captureGenerationError(const TypeError& error)
{
std::string stringifiedError = toString(error);
generationLog.errors.push_back(ErrorSnapshot {
/* message */ stringifiedError,
/* location */ error.location,
});
}
void DcrLogger::captureConstraintLocation(NotNull<const Constraint> constraint, Location location)
{
std::string id = toPointerId(constraint);
generationLog.constraintLocations[id] = location;
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, TypeId block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, TypePackId block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, NotNull<const Constraint> block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::popBlock(TypeId block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
void DcrLogger::popBlock(TypePackId block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
void DcrLogger::popBlock(NotNull<const Constraint> block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
void DcrLogger::captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
solveLog.initialState.rootScope = snapshotScope(rootScope, opts);
solveLog.initialState.constraints.clear();
for (NotNull<const Constraint> c : unsolvedConstraints)
{
std::string id = toPointerId(c);
solveLog.initialState.constraints[id] = {
toString(*c.get(), opts),
snapshotBlocks(c),
};
}
}
StepSnapshot DcrLogger::prepareStepSnapshot(const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
ScopeSnapshot scopeSnapshot = snapshotScope(rootScope, opts);
std::string currentId = toPointerId(current);
std::unordered_map<std::string, ConstraintSnapshot> constraints;
for (NotNull<const Constraint> c : unsolvedConstraints)
{
std::string id = toPointerId(c);
constraints[id] = {
toString(*c.get(), opts),
snapshotBlocks(c),
};
}
return StepSnapshot{
currentId,
force,
constraints,
scopeSnapshot,
};
}
void DcrLogger::commitStepSnapshot(StepSnapshot snapshot)
{
solveLog.stepStates.push_back(std::move(snapshot));
}
void DcrLogger::captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
solveLog.finalState.rootScope = snapshotScope(rootScope, opts);
solveLog.finalState.constraints.clear();
for (NotNull<const Constraint> c : unsolvedConstraints)
{
std::string id = toPointerId(c);
solveLog.finalState.constraints[id] = {
toString(*c.get(), opts),
snapshotBlocks(c),
};
}
}
void DcrLogger::captureTypeCheckError(const TypeError& error)
{
std::string stringifiedError = toString(error);
checkLog.errors.push_back(ErrorSnapshot {
/* message */ stringifiedError,
/* location */ error.location,
});
}
std::vector<ConstraintBlock> DcrLogger::snapshotBlocks(NotNull<const Constraint> c)
{
auto it = constraintBlocks.find(c);
if (it == constraintBlocks.end())
{
return {};
}
std::vector<ConstraintBlock> snapshot;
for (const ConstraintBlockTarget& target : it->second)
{
if (const TypeId* ty = get_if<TypeId>(&target))
{
snapshot.push_back({
ConstraintBlockKind::TypeId,
toString(*ty, opts),
});
}
else if (const TypePackId* tp = get_if<TypePackId>(&target))
{
snapshot.push_back({
ConstraintBlockKind::TypePackId,
toString(*tp, opts),
});
}
else if (const NotNull<const Constraint>* c = get_if<NotNull<const Constraint>>(&target))
{
snapshot.push_back({
ConstraintBlockKind::ConstraintId,
toString(*(c->get()), opts),
});
}
else
{
LUAU_ASSERT(0);
}
}
return snapshot;
}
} // namespace Luau

View file

@ -187,13 +187,9 @@ declare utf8: {
char: (...number) -> string, char: (...number) -> string,
charpattern: string, charpattern: string,
codes: (string) -> ((string, number) -> (number, number), string, number), codes: (string) -> ((string, number) -> (number, number), string, number),
-- FIXME codepoint: (string, number?, number?) -> ...number,
codepoint: (string, number?, number?) -> (number, ...number),
len: (string, number?, number?) -> (number?, number?), len: (string, number?, number?) -> (number?, number?),
offset: (string, number?, number?) -> number, offset: (string, number?, number?) -> number,
nfdnormalize: (string) -> string,
nfcnormalize: (string) -> string,
graphemes: (string, number?, number?) -> (() -> (number, number)),
} }
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.

View file

@ -6,6 +6,7 @@
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/ConstraintGraphBuilder.h" #include "Luau/ConstraintGraphBuilder.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/DcrLogger.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
@ -23,10 +24,12 @@
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false) LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
namespace Luau namespace Luau
{ {
@ -389,11 +392,12 @@ double getTimestamp()
} // namespace } // namespace
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options) Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
: fileResolver(fileResolver) : singletonTypes(NotNull{FFlag::LuauNoMoreGlobalSingletonTypes ? &singletonTypes_ : &DEPRECATED_getSingletonTypes()})
, fileResolver(fileResolver)
, moduleResolver(this) , moduleResolver(this)
, moduleResolverForAutocomplete(this) , moduleResolverForAutocomplete(this)
, typeChecker(&moduleResolver, &iceHandler) , typeChecker(&moduleResolver, singletonTypes, &iceHandler)
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, &iceHandler) , typeCheckerForAutocomplete(&moduleResolverForAutocomplete, singletonTypes, &iceHandler)
, configResolver(configResolver) , configResolver(configResolver)
, options(options) , options(options)
{ {
@ -837,11 +841,22 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
{ {
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), NotNull(&iceHandler), getGlobalScope()}; std::unique_ptr<DcrLogger> logger;
if (FFlag::DebugLuauLogSolverToJson)
{
logger = std::make_unique<DcrLogger>();
std::optional<SourceCode> source = fileResolver->readSource(sourceModule.name);
if (source)
{
logger->captureSource(source->source);
}
}
ConstraintGraphBuilder cgb{sourceModule.name, result, &result->internalTypes, NotNull(&moduleResolver), singletonTypes, NotNull(&iceHandler), getGlobalScope(), logger.get()};
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), sourceModule.name, NotNull(&moduleResolver), requireCycles}; ConstraintSolver cs{&result->internalTypes, singletonTypes, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles, logger.get()};
cs.run(); cs.run();
for (TypeError& e : cs.errors) for (TypeError& e : cs.errors)
@ -855,9 +870,15 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks); result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
result->type = sourceModule.type; result->type = sourceModule.type;
Luau::check(sourceModule, result.get()); Luau::check(singletonTypes, logger.get(), sourceModule, result.get());
result->clonePublicInterface(iceHandler); if (FFlag::DebugLuauLogSolverToJson)
{
std::string output = logger->compileOutput();
printf("%s\n", output.c_str());
}
result->clonePublicInterface(singletonTypes, iceHandler);
return result; return result;
} }

View file

@ -14,7 +14,6 @@
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false) LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
LUAU_FASTFLAGVARIABLE(LuauLintComparisonPrecedence, false)
LUAU_FASTFLAGVARIABLE(LuauLintFixDeprecationMessage, false) LUAU_FASTFLAGVARIABLE(LuauLintFixDeprecationMessage, false)
namespace Luau namespace Luau
@ -2954,7 +2953,7 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_IntegerParsing)) if (context.warningEnabled(LintWarning::Code_IntegerParsing))
LintIntegerParsing::process(context); LintIntegerParsing::process(context);
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence) && FFlag::LuauLintComparisonPrecedence) if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
LintComparisonPrecedence::process(context); LintComparisonPrecedence::process(context);
std::sort(context.result.begin(), context.result.end(), WarningComparator()); std::sort(context.result.begin(), context.result.end(), WarningComparator());

View file

@ -92,10 +92,12 @@ struct ForceNormal : TypeVarOnceVisitor
struct ClonePublicInterface : Substitution struct ClonePublicInterface : Substitution
{ {
NotNull<SingletonTypes> singletonTypes;
NotNull<Module> module; NotNull<Module> module;
ClonePublicInterface(const TxnLog* log, Module* module) ClonePublicInterface(const TxnLog* log, NotNull<SingletonTypes> singletonTypes, Module* module)
: Substitution(log, &module->interfaceTypes) : Substitution(log, &module->interfaceTypes)
, singletonTypes(singletonTypes)
, module(module) , module(module)
{ {
LUAU_ASSERT(module); LUAU_ASSERT(module);
@ -147,7 +149,7 @@ struct ClonePublicInterface : Substitution
else else
{ {
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
} }
@ -163,7 +165,7 @@ struct ClonePublicInterface : Substitution
else else
{ {
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
return getSingletonTypes().errorRecoveryTypePack(); return singletonTypes->errorRecoveryTypePack();
} }
} }
@ -208,7 +210,7 @@ Module::~Module()
unfreeze(internalTypes); unfreeze(internalTypes);
} }
void Module::clonePublicInterface(InternalErrorReporter& ice) void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
LUAU_ASSERT(interfaceTypes.typeVars.empty()); LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty()); LUAU_ASSERT(interfaceTypes.typePacks.empty());
@ -222,7 +224,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings; std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
TxnLog log; TxnLog log;
ClonePublicInterface clonePublicInterface{&log, this}; ClonePublicInterface clonePublicInterface{&log, singletonTypes, this};
if (FFlag::LuauClonePublicInterfaceLess) if (FFlag::LuauClonePublicInterfaceLess)
returnType = clonePublicInterface.cloneTypePack(returnType); returnType = clonePublicInterface.cloneTypePack(returnType);
@ -243,12 +245,12 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
normalize(returnType, NotNull{this}, ice); normalize(returnType, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal) if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(returnType); forceNormal.traverse(returnType);
if (varargPack) if (varargPack)
{ {
normalize(*varargPack, NotNull{this}, ice); normalize(*varargPack, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal) if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(*varargPack); forceNormal.traverse(*varargPack);
} }
@ -264,7 +266,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
tf = clone(tf, interfaceTypes, cloneState); tf = clone(tf, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
normalize(tf.type, NotNull{this}, ice); normalize(tf.type, NotNull{this}, singletonTypes, ice);
// We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables
// won't be marked normal. If the types aren't normal by now, they never will be. // won't be marked normal. If the types aren't normal by now, they never will be.
@ -275,7 +277,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
if (param.defaultValue) if (param.defaultValue)
{ {
normalize(*param.defaultValue, NotNull{this}, ice); normalize(*param.defaultValue, NotNull{this}, singletonTypes, ice);
forceNormal.traverse(*param.defaultValue); forceNormal.traverse(*param.defaultValue);
} }
} }
@ -301,7 +303,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
ty = clone(ty, interfaceTypes, cloneState); ty = clone(ty, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
normalize(ty, NotNull{this}, ice); normalize(ty, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal) if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(ty); forceNormal.traverse(ty);

View file

@ -54,11 +54,11 @@ struct Replacer
} // anonymous namespace } // anonymous namespace
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, InternalErrorReporter& ice) bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Unifier u{&arena, Mode::Strict, scope, Location{}, Covariant, sharedState}; Unifier u{&arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.anyIsTop = true; u.anyIsTop = true;
u.tryUnify(subTy, superTy); u.tryUnify(subTy, superTy);
@ -66,11 +66,11 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, InternalError
return ok; return ok;
} }
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, InternalErrorReporter& ice) bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Unifier u{&arena, Mode::Strict, scope, Location{}, Covariant, sharedState}; Unifier u{&arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.anyIsTop = true; u.anyIsTop = true;
u.tryUnify(subPack, superPack); u.tryUnify(subPack, superPack);
@ -133,15 +133,17 @@ struct Normalize final : TypeVarVisitor
{ {
using TypeVarVisitor::Set; using TypeVarVisitor::Set;
Normalize(TypeArena& arena, NotNull<Scope> scope, InternalErrorReporter& ice) Normalize(TypeArena& arena, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
: arena(arena) : arena(arena)
, scope(scope) , scope(scope)
, singletonTypes(singletonTypes)
, ice(ice) , ice(ice)
{ {
} }
TypeArena& arena; TypeArena& arena;
NotNull<Scope> scope; NotNull<Scope> scope;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter& ice; InternalErrorReporter& ice;
int iterationLimit = 0; int iterationLimit = 0;
@ -499,9 +501,9 @@ struct Normalize final : TypeVarVisitor
for (TypeId& part : result) for (TypeId& part : result)
{ {
if (isSubtype(ty, part, scope, ice)) if (isSubtype(ty, part, scope, singletonTypes, ice))
return; // no need to do anything return; // no need to do anything
else if (isSubtype(part, ty, scope, ice)) else if (isSubtype(part, ty, scope, singletonTypes, ice))
{ {
part = ty; // replace the less general type by the more general one part = ty; // replace the less general type by the more general one
return; return;
@ -553,12 +555,12 @@ struct Normalize final : TypeVarVisitor
bool merged = false; bool merged = false;
for (TypeId& part : result->parts) for (TypeId& part : result->parts)
{ {
if (isSubtype(part, ty, scope, ice)) if (isSubtype(part, ty, scope, singletonTypes, ice))
{ {
merged = true; merged = true;
break; // no need to do anything break; // no need to do anything
} }
else if (isSubtype(ty, part, scope, ice)) else if (isSubtype(ty, part, scope, singletonTypes, ice))
{ {
merged = true; merged = true;
part = ty; // replace the less general type by the more general one part = ty; // replace the less general type by the more general one
@ -691,13 +693,14 @@ struct Normalize final : TypeVarVisitor
/** /**
* @returns A tuple of TypeId and a success indicator. (true indicates that the normalization completed successfully) * @returns A tuple of TypeId and a success indicator. (true indicates that the normalization completed successfully)
*/ */
std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Scope> scope, TypeArena& arena, InternalErrorReporter& ice) std::pair<TypeId, bool> normalize(
TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
CloneState state; CloneState state;
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLuauCopyBeforeNormalizing)
(void)clone(ty, arena, state); (void)clone(ty, arena, state);
Normalize n{arena, scope, ice}; Normalize n{arena, scope, singletonTypes, ice};
n.traverse(ty); n.traverse(ty);
return {ty, !n.limitExceeded}; return {ty, !n.limitExceeded};
@ -707,39 +710,40 @@ std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Scope> scope, TypeArena& ar
// reclaim memory used by wantonly allocated intermediate types here. // reclaim memory used by wantonly allocated intermediate types here.
// The main wrinkle here is that we don't want clone() to copy a type if the source and dest // The main wrinkle here is that we don't want clone() to copy a type if the source and dest
// arena are the same. // arena are the same.
std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Module> module, InternalErrorReporter& ice) std::pair<TypeId, bool> normalize(TypeId ty, NotNull<Module> module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
return normalize(ty, NotNull{module->getModuleScope().get()}, module->internalTypes, ice); return normalize(ty, NotNull{module->getModuleScope().get()}, module->internalTypes, singletonTypes, ice);
} }
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice) std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
return normalize(ty, NotNull{module.get()}, ice); return normalize(ty, NotNull{module.get()}, singletonTypes, ice);
} }
/** /**
* @returns A tuple of TypeId and a success indicator. (true indicates that the normalization completed successfully) * @returns A tuple of TypeId and a success indicator. (true indicates that the normalization completed successfully)
*/ */
std::pair<TypePackId, bool> normalize(TypePackId tp, NotNull<Scope> scope, TypeArena& arena, InternalErrorReporter& ice) std::pair<TypePackId, bool> normalize(
TypePackId tp, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
CloneState state; CloneState state;
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLuauCopyBeforeNormalizing)
(void)clone(tp, arena, state); (void)clone(tp, arena, state);
Normalize n{arena, scope, ice}; Normalize n{arena, scope, singletonTypes, ice};
n.traverse(tp); n.traverse(tp);
return {tp, !n.limitExceeded}; return {tp, !n.limitExceeded};
} }
std::pair<TypePackId, bool> normalize(TypePackId tp, NotNull<Module> module, InternalErrorReporter& ice) std::pair<TypePackId, bool> normalize(TypePackId tp, NotNull<Module> module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
return normalize(tp, NotNull{module->getModuleScope().get()}, module->internalTypes, ice); return normalize(tp, NotNull{module->getModuleScope().get()}, module->internalTypes, singletonTypes, ice);
} }
std::pair<TypePackId, bool> normalize(TypePackId tp, const ModulePtr& module, InternalErrorReporter& ice) std::pair<TypePackId, bool> normalize(TypePackId tp, const ModulePtr& module, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{ {
return normalize(tp, NotNull{module.get()}, ice); return normalize(tp, NotNull{module.get()}, singletonTypes, ice);
} }
} // namespace Luau } // namespace Luau

View file

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

View file

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include <algorithm>
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
@ -13,6 +11,12 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
#include "Luau/ToString.h"
#include "Luau/DcrLogger.h"
#include <algorithm>
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
namespace Luau namespace Luau
{ {
@ -54,18 +58,22 @@ struct StackPusher
struct TypeChecker2 struct TypeChecker2
{ {
NotNull<SingletonTypes> singletonTypes;
DcrLogger* logger;
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
const SourceModule* sourceModule; const SourceModule* sourceModule;
Module* module; Module* module;
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
SingletonTypes& singletonTypes;
std::vector<NotNull<Scope>> stack; std::vector<NotNull<Scope>> stack;
TypeChecker2(const SourceModule* sourceModule, Module* module) TypeChecker2(NotNull<SingletonTypes> singletonTypes, DcrLogger* logger, const SourceModule* sourceModule, Module* module)
: sourceModule(sourceModule) : singletonTypes(singletonTypes)
, logger(logger)
, sourceModule(sourceModule)
, module(module) , module(module)
, singletonTypes(getSingletonTypes())
{ {
if (FFlag::DebugLuauLogSolverToJson)
LUAU_ASSERT(logger);
} }
std::optional<StackPusher> pushStack(AstNode* node) std::optional<StackPusher> pushStack(AstNode* node)
@ -85,7 +93,7 @@ struct TypeChecker2
if (tp) if (tp)
return follow(*tp); return follow(*tp);
else else
return singletonTypes.anyTypePack; return singletonTypes->anyTypePack;
} }
TypeId lookupType(AstExpr* expr) TypeId lookupType(AstExpr* expr)
@ -101,7 +109,7 @@ struct TypeChecker2
if (tp) if (tp)
return flattenPack(*tp); return flattenPack(*tp);
return singletonTypes.anyType; return singletonTypes->anyType;
} }
TypeId lookupAnnotation(AstType* annotation) TypeId lookupAnnotation(AstType* annotation)
@ -253,7 +261,7 @@ struct TypeChecker2
TypePackId actualRetType = reconstructPack(ret->list, arena); TypePackId actualRetType = reconstructPack(ret->list, arena);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Unifier u{&arena, Mode::Strict, stack.back(), ret->location, Covariant, sharedState}; Unifier u{&arena, singletonTypes, Mode::Strict, stack.back(), ret->location, Covariant, sharedState};
u.anyIsTop = true; u.anyIsTop = true;
u.tryUnify(actualRetType, expectedRetType); u.tryUnify(actualRetType, expectedRetType);
@ -299,7 +307,7 @@ struct TypeChecker2
if (var->annotation) if (var->annotation)
{ {
TypeId varType = lookupAnnotation(var->annotation); TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(*it, varType, stack.back(), ice)) if (!isSubtype(*it, varType, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{varType, *it}, value->location); reportError(TypeMismatch{varType, *it}, value->location);
} }
@ -317,7 +325,7 @@ struct TypeChecker2
if (var->annotation) if (var->annotation)
{ {
TypeId varType = lookupAnnotation(var->annotation); TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(varType, valueType, stack.back(), ice)) if (!isSubtype(varType, valueType, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{varType, valueType}, value->location); reportError(TypeMismatch{varType, valueType}, value->location);
} }
@ -340,7 +348,7 @@ struct TypeChecker2
// "Render" a type pack out to an array of a given length. Expands // "Render" a type pack out to an array of a given length. Expands
// variadics and various other things to get there. // variadics and various other things to get there.
static std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length) std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
{ {
std::vector<TypeId> result; std::vector<TypeId> result;
@ -376,7 +384,7 @@ struct TypeChecker2
else if (auto etp = get<Unifiable::Error>(tail)) else if (auto etp = get<Unifiable::Error>(tail))
{ {
while (result.size() < length) while (result.size() < length)
result.push_back(getSingletonTypes().errorRecoveryType()); result.push_back(singletonTypes->errorRecoveryType());
} }
return result; return result;
@ -532,7 +540,7 @@ struct TypeChecker2
visit(rhs); visit(rhs);
TypeId rhsType = lookupType(rhs); TypeId rhsType = lookupType(rhs);
if (!isSubtype(rhsType, lhsType, stack.back(), ice)) if (!isSubtype(rhsType, lhsType, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{lhsType, rhsType}, rhs->location); reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
} }
@ -681,9 +689,9 @@ struct TypeChecker2
void visit(AstExprConstantNumber* number) void visit(AstExprConstantNumber* number)
{ {
TypeId actualType = lookupType(number); TypeId actualType = lookupType(number);
TypeId numberType = getSingletonTypes().numberType; TypeId numberType = singletonTypes->numberType;
if (!isSubtype(numberType, actualType, stack.back(), ice)) if (!isSubtype(numberType, actualType, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{actualType, numberType}, number->location); reportError(TypeMismatch{actualType, numberType}, number->location);
} }
@ -692,9 +700,9 @@ struct TypeChecker2
void visit(AstExprConstantString* string) void visit(AstExprConstantString* string)
{ {
TypeId actualType = lookupType(string); TypeId actualType = lookupType(string);
TypeId stringType = getSingletonTypes().stringType; TypeId stringType = singletonTypes->stringType;
if (!isSubtype(stringType, actualType, stack.back(), ice)) if (!isSubtype(stringType, actualType, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{actualType, stringType}, string->location); reportError(TypeMismatch{actualType, stringType}, string->location);
} }
@ -754,7 +762,7 @@ struct TypeChecker2
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(), singletonTypes, ice))
{ {
CloneState cloneState; CloneState cloneState;
expectedType = clone(expectedType, module->internalTypes, cloneState); expectedType = clone(expectedType, module->internalTypes, cloneState);
@ -773,7 +781,7 @@ struct TypeChecker2
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true); 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(), singletonTypes, ice))
{ {
reportError(TypeMismatch{resultType, *ty}, indexName->location); reportError(TypeMismatch{resultType, *ty}, indexName->location);
} }
@ -806,7 +814,7 @@ struct TypeChecker2
TypeId inferredArgTy = *argIt; TypeId inferredArgTy = *argIt;
TypeId annotatedArgTy = lookupAnnotation(arg->annotation); TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back(), ice)) if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back(), singletonTypes, ice))
{ {
reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location); reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location);
} }
@ -851,10 +859,10 @@ struct TypeChecker2
TypeId computedType = lookupType(expr->expr); TypeId computedType = lookupType(expr->expr);
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case. // Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
if (isSubtype(annotationType, computedType, stack.back(), ice)) if (isSubtype(annotationType, computedType, stack.back(), singletonTypes, ice))
return; return;
if (isSubtype(computedType, annotationType, stack.back(), ice)) if (isSubtype(computedType, annotationType, stack.back(), singletonTypes, ice))
return; return;
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location); reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
@ -908,7 +916,7 @@ struct TypeChecker2
return result; return result;
} }
else if (get<Unifiable::Error>(pack)) else if (get<Unifiable::Error>(pack))
return singletonTypes.errorRecoveryType(); return singletonTypes->errorRecoveryType();
else else
ice.ice("flattenPack got a weird pack!"); ice.ice("flattenPack got a weird pack!");
} }
@ -1154,7 +1162,7 @@ struct TypeChecker2
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy) ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Unifier u{&module->internalTypes, Mode::Strict, scope, location, Covariant, sharedState}; Unifier u{&module->internalTypes, singletonTypes, Mode::Strict, scope, location, Covariant, sharedState};
u.anyIsTop = true; u.anyIsTop = true;
u.tryUnify(subTy, superTy); u.tryUnify(subTy, superTy);
@ -1164,6 +1172,9 @@ struct TypeChecker2
void reportError(TypeErrorData data, const Location& location) 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));
if (FFlag::DebugLuauLogSolverToJson)
logger->captureTypeCheckError(module->errors.back());
} }
void reportError(TypeError e) void reportError(TypeError e)
@ -1179,13 +1190,13 @@ struct TypeChecker2
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const std::string& prop, const Location& location, bool addErrors) 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, singletonTypes, type, prop, location, addErrors, ice);
} }
}; };
void check(const SourceModule& sourceModule, Module* module) void check(NotNull<SingletonTypes> singletonTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
{ {
TypeChecker2 typeChecker{&sourceModule, module}; TypeChecker2 typeChecker{singletonTypes, logger, &sourceModule, module};
typeChecker.visit(sourceModule.root); typeChecker.visit(sourceModule.root);
} }

View file

@ -248,21 +248,22 @@ size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second); return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
} }
TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler) TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler)
: resolver(resolver) : resolver(resolver)
, singletonTypes(singletonTypes)
, iceHandler(iceHandler) , iceHandler(iceHandler)
, unifierState(iceHandler) , unifierState(iceHandler)
, nilType(getSingletonTypes().nilType) , nilType(singletonTypes->nilType)
, numberType(getSingletonTypes().numberType) , numberType(singletonTypes->numberType)
, stringType(getSingletonTypes().stringType) , stringType(singletonTypes->stringType)
, booleanType(getSingletonTypes().booleanType) , booleanType(singletonTypes->booleanType)
, threadType(getSingletonTypes().threadType) , threadType(singletonTypes->threadType)
, anyType(getSingletonTypes().anyType) , anyType(singletonTypes->anyType)
, unknownType(getSingletonTypes().unknownType) , unknownType(singletonTypes->unknownType)
, neverType(getSingletonTypes().neverType) , neverType(singletonTypes->neverType)
, anyTypePack(getSingletonTypes().anyTypePack) , anyTypePack(singletonTypes->anyTypePack)
, neverTypePack(getSingletonTypes().neverTypePack) , neverTypePack(singletonTypes->neverTypePack)
, uninhabitableTypePack(getSingletonTypes().uninhabitableTypePack) , uninhabitableTypePack(singletonTypes->uninhabitableTypePack)
, duplicateTypeAliases{{false, {}}} , duplicateTypeAliases{{false, {}}}
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
@ -357,7 +358,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
prepareErrorsForDisplay(currentModule->errors); prepareErrorsForDisplay(currentModule->errors);
currentModule->clonePublicInterface(*iceHandler); currentModule->clonePublicInterface(singletonTypes, *iceHandler);
// Clear unifier cache since it's keyed off internal types that get deallocated // Clear unifier cache since it's keyed off internal types that get deallocated
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
@ -1606,7 +1607,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
auto [t, ok] = normalize(bindingType, currentModule, *iceHandler); auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
bindingType = t; bindingType = t;
if (!ok) if (!ok)
reportError(typealias.location, NormalizationTooComplex{}); reportError(typealias.location, NormalizationTooComplex{});
@ -1923,7 +1924,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors) std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors)
{ {
ErrorVec errors; ErrorVec errors;
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location); auto result = Luau::findTablePropertyRespectingMeta(singletonTypes, errors, lhsType, name, location);
if (addErrors) if (addErrors)
reportErrors(errors); reportErrors(errors);
return result; return result;
@ -1932,7 +1933,7 @@ std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsTyp
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors) std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors)
{ {
ErrorVec errors; ErrorVec errors;
auto result = Luau::findMetatableEntry(errors, type, entry, location); auto result = Luau::findMetatableEntry(singletonTypes, errors, type, entry, location);
if (addErrors) if (addErrors)
reportErrors(errors); reportErrors(errors);
return result; return result;
@ -2034,8 +2035,8 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
auto [t, ok] = normalize(addType(UnionTypeVar{std::move(goodOptions)}), currentModule, // FIXME Inefficient. We craft a UnionTypeVar and immediately throw it away.
*iceHandler); // FIXME Inefficient. We craft a UnionTypeVar and immediately throw it away. auto [t, ok] = normalize(addType(UnionTypeVar{std::move(goodOptions)}), currentModule, singletonTypes, *iceHandler);
if (!ok) if (!ok)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
@ -2642,8 +2643,8 @@ TypeId TypeChecker::checkRelationalOperation(
std::string metamethodName = opToMetaTableEntry(expr.op); std::string metamethodName = opToMetaTableEntry(expr.op);
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType)); std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType), singletonTypes);
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType)); std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType), singletonTypes);
if (leftMetatable != rightMetatable) if (leftMetatable != rightMetatable)
{ {
@ -2654,7 +2655,7 @@ TypeId TypeChecker::checkRelationalOperation(
{ {
for (TypeId leftOption : utv) for (TypeId leftOption : utv)
{ {
if (getMetatable(follow(leftOption)) == rightMetatable) if (getMetatable(follow(leftOption), singletonTypes) == rightMetatable)
{ {
matches = true; matches = true;
break; break;
@ -2668,7 +2669,7 @@ TypeId TypeChecker::checkRelationalOperation(
{ {
for (TypeId rightOption : utv) for (TypeId rightOption : utv)
{ {
if (getMetatable(follow(rightOption)) == leftMetatable) if (getMetatable(follow(rightOption), singletonTypes) == leftMetatable)
{ {
matches = true; matches = true;
break; break;
@ -4113,7 +4114,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
std::vector<TypeId> adjustedArgTypes; std::vector<TypeId> adjustedArgTypes;
auto it = begin(argPack); auto it = begin(argPack);
auto endIt = end(argPack); auto endIt = end(argPack);
Widen widen{&currentModule->internalTypes}; Widen widen{&currentModule->internalTypes, singletonTypes};
for (; it != endIt; ++it) for (; it != endIt; ++it)
{ {
adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}})); adjustedArgTypes.push_back(addType(ConstrainedTypeVar{level, {widen(*it)}}));
@ -4649,7 +4650,7 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
auto [t, ok] = Luau::normalize(ty, currentModule, *iceHandler); auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok) if (!ok)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
return t; return t;
@ -4664,7 +4665,7 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (FFlag::LuauLowerBoundsCalculation && ftv) if (FFlag::LuauLowerBoundsCalculation && ftv)
{ {
auto [t, ok] = Luau::normalize(ty, currentModule, *iceHandler); auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok) if (!ok)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
return t; return t;
@ -4701,13 +4702,13 @@ TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
{ {
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
auto [t, ok] = normalize(ty, currentModule, *iceHandler); auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok) if (!ok)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
ty = t; ty = t;
} }
Anyification anyification{&currentModule->internalTypes, scope, iceHandler, anyType, anyTypePack}; Anyification anyification{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
std::optional<TypeId> any = anyification.substitute(ty); std::optional<TypeId> any = anyification.substitute(ty);
if (anyification.normalizationTooComplex) if (anyification.normalizationTooComplex)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
@ -4724,13 +4725,13 @@ TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location lo
{ {
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LuauLowerBoundsCalculation)
{ {
auto [t, ok] = normalize(ty, currentModule, *iceHandler); auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok) if (!ok)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
ty = t; ty = t;
} }
Anyification anyification{&currentModule->internalTypes, scope, iceHandler, anyType, anyTypePack}; Anyification anyification{&currentModule->internalTypes, scope, singletonTypes, iceHandler, anyType, anyTypePack};
std::optional<TypePackId> any = anyification.substitute(ty); std::optional<TypePackId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
return *any; return *any;
@ -4868,7 +4869,8 @@ void TypeChecker::merge(RefinementMap& l, const RefinementMap& r)
Unifier TypeChecker::mkUnifier(const ScopePtr& scope, const Location& location) Unifier TypeChecker::mkUnifier(const ScopePtr& scope, const Location& location)
{ {
return Unifier{&currentModule->internalTypes, currentModule->mode, NotNull{scope.get()}, location, Variance::Covariant, unifierState}; return Unifier{
&currentModule->internalTypes, singletonTypes, currentModule->mode, NotNull{scope.get()}, location, Variance::Covariant, unifierState};
} }
TypeId TypeChecker::freshType(const ScopePtr& scope) TypeId TypeChecker::freshType(const ScopePtr& scope)
@ -4883,7 +4885,7 @@ TypeId TypeChecker::freshType(TypeLevel level)
TypeId TypeChecker::singletonType(bool value) TypeId TypeChecker::singletonType(bool value)
{ {
return value ? getSingletonTypes().trueType : getSingletonTypes().falseType; return value ? singletonTypes->trueType : singletonTypes->falseType;
} }
TypeId TypeChecker::singletonType(std::string value) TypeId TypeChecker::singletonType(std::string value)
@ -4894,22 +4896,22 @@ TypeId TypeChecker::singletonType(std::string value)
TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope) TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope)
{ {
return getSingletonTypes().errorRecoveryType(); return singletonTypes->errorRecoveryType();
} }
TypeId TypeChecker::errorRecoveryType(TypeId guess) TypeId TypeChecker::errorRecoveryType(TypeId guess)
{ {
return getSingletonTypes().errorRecoveryType(guess); return singletonTypes->errorRecoveryType(guess);
} }
TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope) TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope)
{ {
return getSingletonTypes().errorRecoveryTypePack(); return singletonTypes->errorRecoveryTypePack();
} }
TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess) TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
{ {
return getSingletonTypes().errorRecoveryTypePack(guess); return singletonTypes->errorRecoveryTypePack(guess);
} }
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
@ -5836,48 +5838,52 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return; return;
} }
using ConditionFunc = bool(TypeId); auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional<TypeId> mapsTo = std::nullopt) {
using SenseToTypeIdPredicate = std::function<TypeIdPredicate(bool)>; TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional<TypeId> {
auto mkFilter = [](ConditionFunc f, std::optional<TypeId> other = std::nullopt) -> SenseToTypeIdPredicate { if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownTypeVar>(ty))
return [f, other](bool sense) -> TypeIdPredicate { return mapsTo.value_or(ty);
return [f, other, sense](TypeId ty) -> std::optional<TypeId> {
if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownTypeVar>(ty))
return other.value_or(ty);
if (f(ty) == sense) if (f(ty) == sense)
return ty; return ty;
if (isUndecidable(ty)) if (isUndecidable(ty))
return other.value_or(ty); return mapsTo.value_or(ty);
return std::nullopt; return std::nullopt;
};
}; };
refineLValue(lvalue, refis, scope, predicate);
}; };
// Note: "vector" never happens here at this point, so we don't have to write something for it. // Note: "vector" never happens here at this point, so we don't have to write something for it.
// clang-format off if (typeguardP.kind == "nil")
static const std::unordered_map<std::string, SenseToTypeIdPredicate> primitives{ return refine(isNil, nilType); // This can still happen when sense is false!
// Trivial primitives. else if (typeguardP.kind == "string")
{"nil", mkFilter(isNil, nilType)}, // This can still happen when sense is false! return refine(isString, stringType);
{"string", mkFilter(isString, stringType)}, else if (typeguardP.kind == "number")
{"number", mkFilter(isNumber, numberType)}, return refine(isNumber, numberType);
{"boolean", mkFilter(isBoolean, booleanType)}, else if (typeguardP.kind == "boolean")
{"thread", mkFilter(isThread, threadType)}, return refine(isBoolean, booleanType);
else if (typeguardP.kind == "thread")
// Non-trivial primitives. return refine(isThread, threadType);
{"table", mkFilter([](TypeId ty) -> bool { return isTableIntersection(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty); })}, else if (typeguardP.kind == "table")
{"function", mkFilter([](TypeId ty) -> bool { return isOverloadedFunction(ty) || get<FunctionTypeVar>(ty); })},
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
{"userdata", mkFilter([](TypeId ty) -> bool { return get<ClassTypeVar>(ty); })},
};
// clang-format on
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
{ {
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense)); return refine([](TypeId ty) -> bool {
return; return isTableIntersection(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty);
});
}
else if (typeguardP.kind == "function")
{
return refine([](TypeId ty) -> bool {
return isOverloadedFunction(ty) || get<FunctionTypeVar>(ty);
});
}
else if (typeguardP.kind == "userdata")
{
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
return refine([](TypeId ty) -> bool {
return get<ClassTypeVar>(ty);
});
} }
if (!typeguardP.isTypeof) if (!typeguardP.isTypeof)

View file

@ -9,18 +9,19 @@
namespace Luau namespace Luau
{ {
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location) std::optional<TypeId> findMetatableEntry(
NotNull<SingletonTypes> singletonTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location)
{ {
type = follow(type); type = follow(type);
std::optional<TypeId> metatable = getMetatable(type); std::optional<TypeId> metatable = getMetatable(type, singletonTypes);
if (!metatable) if (!metatable)
return std::nullopt; return std::nullopt;
TypeId unwrapped = follow(*metatable); TypeId unwrapped = follow(*metatable);
if (get<AnyTypeVar>(unwrapped)) if (get<AnyTypeVar>(unwrapped))
return getSingletonTypes().anyType; return singletonTypes->anyType;
const TableTypeVar* mtt = getTableType(unwrapped); const TableTypeVar* mtt = getTableType(unwrapped);
if (!mtt) if (!mtt)
@ -36,7 +37,8 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const st
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location) std::optional<TypeId> findTablePropertyRespectingMeta(
NotNull<SingletonTypes> singletonTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location)
{ {
if (get<AnyTypeVar>(ty)) if (get<AnyTypeVar>(ty))
return ty; return ty;
@ -48,7 +50,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
return it->second.type; return it->second.type;
} }
std::optional<TypeId> mtIndex = findMetatableEntry(errors, ty, "__index", location); std::optional<TypeId> mtIndex = findMetatableEntry(singletonTypes, errors, ty, "__index", location);
int count = 0; int count = 0;
while (mtIndex) while (mtIndex)
{ {
@ -69,23 +71,23 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
{ {
std::optional<TypeId> r = first(follow(itf->retTypes)); std::optional<TypeId> r = first(follow(itf->retTypes));
if (!r) if (!r)
return getSingletonTypes().nilType; return singletonTypes->nilType;
else else
return *r; return *r;
} }
else if (get<AnyTypeVar>(index)) else if (get<AnyTypeVar>(index))
return getSingletonTypes().anyType; return singletonTypes->anyType;
else else
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
mtIndex = findMetatableEntry(errors, *mtIndex, "__index", location); mtIndex = findMetatableEntry(singletonTypes, errors, *mtIndex, "__index", location);
} }
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop, std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, NotNull<SingletonTypes> singletonTypes,
const Location& location, bool addErrors, InternalErrorReporter& handle) TypeId type, const std::string& prop, const Location& location, bool addErrors, InternalErrorReporter& handle)
{ {
type = follow(type); type = follow(type);
@ -97,14 +99,14 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
if (isString(type)) if (isString(type))
{ {
std::optional<TypeId> mtIndex = Luau::findMetatableEntry(errors, getSingletonTypes().stringType, "__index", location); std::optional<TypeId> mtIndex = Luau::findMetatableEntry(singletonTypes, errors, singletonTypes->stringType, "__index", location);
LUAU_ASSERT(mtIndex); LUAU_ASSERT(mtIndex);
type = *mtIndex; type = *mtIndex;
} }
if (getTableType(type)) if (getTableType(type))
{ {
return findTablePropertyRespectingMeta(errors, type, prop, location); return findTablePropertyRespectingMeta(singletonTypes, errors, type, prop, location);
} }
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type)) else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
{ {
@ -125,7 +127,8 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
if (get<AnyTypeVar>(follow(t))) if (get<AnyTypeVar>(follow(t)))
return t; return t;
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, errors, arena, t, prop, location, /* addErrors= */ false, handle)) if (std::optional<TypeId> ty =
getIndexTypeFromType(scope, errors, arena, singletonTypes, t, prop, location, /* addErrors= */ false, handle))
goodOptions.push_back(*ty); goodOptions.push_back(*ty);
else else
badOptions.push_back(t); badOptions.push_back(t);
@ -144,17 +147,17 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
} }
if (goodOptions.empty()) if (goodOptions.empty())
return getSingletonTypes().neverType; return singletonTypes->neverType;
if (goodOptions.size() == 1) if (goodOptions.size() == 1)
return goodOptions[0]; return goodOptions[0];
// TODO: inefficient. // TODO: inefficient.
TypeId result = arena->addType(UnionTypeVar{std::move(goodOptions)}); TypeId result = arena->addType(UnionTypeVar{std::move(goodOptions)});
auto [ty, ok] = normalize(result, NotNull{scope.get()}, *arena, handle); auto [ty, ok] = normalize(result, NotNull{scope.get()}, *arena, singletonTypes, handle);
if (!ok && addErrors) if (!ok && addErrors)
errors.push_back(TypeError{location, NormalizationTooComplex{}}); errors.push_back(TypeError{location, NormalizationTooComplex{}});
return ok ? ty : getSingletonTypes().anyType; return ok ? ty : singletonTypes->anyType;
} }
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type)) else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(type))
{ {
@ -165,7 +168,8 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
// TODO: we should probably limit recursion here? // TODO: we should probably limit recursion here?
// RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit); // RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, errors, arena, t, prop, location, /* addErrors= */ false, handle)) if (std::optional<TypeId> ty =
getIndexTypeFromType(scope, errors, arena, singletonTypes, t, prop, location, /* addErrors= */ false, handle))
parts.push_back(*ty); parts.push_back(*ty);
} }

View file

@ -26,6 +26,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false) LUAU_FASTFLAGVARIABLE(LuauStringFormatArgumentErrorFix, false)
LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false)
namespace Luau namespace Luau
{ {
@ -239,7 +240,7 @@ bool isOverloadedFunction(TypeId ty)
return std::all_of(parts.begin(), parts.end(), isFunction); return std::all_of(parts.begin(), parts.end(), isFunction);
} }
std::optional<TypeId> getMetatable(TypeId type) std::optional<TypeId> getMetatable(TypeId type, NotNull<SingletonTypes> singletonTypes)
{ {
type = follow(type); type = follow(type);
@ -249,7 +250,7 @@ std::optional<TypeId> getMetatable(TypeId type)
return classType->metatable; return classType->metatable;
else if (isString(type)) else if (isString(type))
{ {
auto ptv = get<PrimitiveTypeVar>(getSingletonTypes().stringType); auto ptv = get<PrimitiveTypeVar>(singletonTypes->stringType);
LUAU_ASSERT(ptv && ptv->metatable); LUAU_ASSERT(ptv && ptv->metatable);
return ptv->metatable; return ptv->metatable;
} }
@ -707,44 +708,30 @@ TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initi
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
std::initializer_list<TypeId> retTypes); std::initializer_list<TypeId> retTypes);
static TypeVar nilType_{PrimitiveTypeVar{PrimitiveTypeVar::NilType}, /*persistent*/ true};
static TypeVar numberType_{PrimitiveTypeVar{PrimitiveTypeVar::Number}, /*persistent*/ true};
static TypeVar stringType_{PrimitiveTypeVar{PrimitiveTypeVar::String}, /*persistent*/ true};
static TypeVar booleanType_{PrimitiveTypeVar{PrimitiveTypeVar::Boolean}, /*persistent*/ true};
static TypeVar threadType_{PrimitiveTypeVar{PrimitiveTypeVar::Thread}, /*persistent*/ true};
static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true};
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true};
static TypeVar unknownType_{UnknownTypeVar{}, /*persistent*/ true};
static TypeVar neverType_{NeverTypeVar{}, /*persistent*/ true};
static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, /*persistent*/ true};
static TypePackVar errorTypePack_{Unifiable::Error{}, /*persistent*/ true};
static TypePackVar neverTypePack_{VariadicTypePack{&neverType_}, /*persistent*/ true};
static TypePackVar uninhabitableTypePack_{TypePack{{&neverType_}, &neverTypePack_}, /*persistent*/ true};
SingletonTypes::SingletonTypes() SingletonTypes::SingletonTypes()
: nilType(&nilType_) : arena(new TypeArena)
, numberType(&numberType_) , debugFreezeArena(FFlag::DebugLuauFreezeArena)
, stringType(&stringType_) , nilType(arena->addType(TypeVar{PrimitiveTypeVar{PrimitiveTypeVar::NilType}, /*persistent*/ true}))
, booleanType(&booleanType_) , numberType(arena->addType(TypeVar{PrimitiveTypeVar{PrimitiveTypeVar::Number}, /*persistent*/ true}))
, threadType(&threadType_) , stringType(arena->addType(TypeVar{PrimitiveTypeVar{PrimitiveTypeVar::String}, /*persistent*/ true}))
, trueType(&trueType_) , booleanType(arena->addType(TypeVar{PrimitiveTypeVar{PrimitiveTypeVar::Boolean}, /*persistent*/ true}))
, falseType(&falseType_) , threadType(arena->addType(TypeVar{PrimitiveTypeVar{PrimitiveTypeVar::Thread}, /*persistent*/ true}))
, anyType(&anyType_) , trueType(arena->addType(TypeVar{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true}))
, unknownType(&unknownType_) , falseType(arena->addType(TypeVar{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true}))
, neverType(&neverType_) , anyType(arena->addType(TypeVar{AnyTypeVar{}, /*persistent*/ true}))
, anyTypePack(&anyTypePack_) , unknownType(arena->addType(TypeVar{UnknownTypeVar{}, /*persistent*/ true}))
, neverTypePack(&neverTypePack_) , neverType(arena->addType(TypeVar{NeverTypeVar{}, /*persistent*/ true}))
, uninhabitableTypePack(&uninhabitableTypePack_) , errorType(arena->addType(TypeVar{ErrorTypeVar{}, /*persistent*/ true}))
, arena(new TypeArena) , anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true}))
, neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true}))
, uninhabitableTypePack(arena->addTypePack({neverType}, neverTypePack))
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
{ {
TypeId stringMetatable = makeStringMetatable(); TypeId stringMetatable = makeStringMetatable();
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable}; asMutable(stringType)->ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable};
persist(stringMetatable); persist(stringMetatable);
persist(uninhabitableTypePack);
debugFreezeArena = FFlag::DebugLuauFreezeArena;
freeze(*arena); freeze(*arena);
} }
@ -834,12 +821,12 @@ TypeId SingletonTypes::makeStringMetatable()
TypeId SingletonTypes::errorRecoveryType() TypeId SingletonTypes::errorRecoveryType()
{ {
return &errorType_; return errorType;
} }
TypePackId SingletonTypes::errorRecoveryTypePack() TypePackId SingletonTypes::errorRecoveryTypePack()
{ {
return &errorTypePack_; return errorTypePack;
} }
TypeId SingletonTypes::errorRecoveryType(TypeId guess) TypeId SingletonTypes::errorRecoveryType(TypeId guess)
@ -852,7 +839,7 @@ TypePackId SingletonTypes::errorRecoveryTypePack(TypePackId guess)
return guess; return guess;
} }
SingletonTypes& getSingletonTypes() SingletonTypes& DEPRECATED_getSingletonTypes()
{ {
static SingletonTypes singletonTypes; static SingletonTypes singletonTypes;
return singletonTypes; return singletonTypes;

View file

@ -257,12 +257,12 @@ TypeId Widen::clean(TypeId ty)
LUAU_ASSERT(stv); LUAU_ASSERT(stv);
if (get<StringSingleton>(stv)) if (get<StringSingleton>(stv))
return getSingletonTypes().stringType; return singletonTypes->stringType;
else else
{ {
// If this assert trips, it's likely we now have number singletons. // If this assert trips, it's likely we now have number singletons.
LUAU_ASSERT(get<BooleanSingleton>(stv)); LUAU_ASSERT(get<BooleanSingleton>(stv));
return getSingletonTypes().booleanType; return singletonTypes->booleanType;
} }
} }
@ -317,9 +317,10 @@ 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, Unifier::Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location,
TxnLog* parentLog) Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
: types(types) : types(types)
, singletonTypes(singletonTypes)
, mode(mode) , mode(mode)
, scope(scope) , scope(scope)
, log(parentLog) , log(parentLog)
@ -409,7 +410,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
promoteTypeLevels(log, types, superFree->level, subTy); promoteTypeLevels(log, types, superFree->level, subTy);
Widen widen{types}; Widen widen{types, singletonTypes};
log.replace(superTy, BoundTypeVar(widen(subTy))); log.replace(superTy, BoundTypeVar(widen(subTy)));
} }
@ -1018,7 +1019,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
{ {
if (!occursCheck(superTp, subTp)) if (!occursCheck(superTp, subTp))
{ {
Widen widen{types}; Widen widen{types, singletonTypes};
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp))); log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
} }
} }
@ -1162,13 +1163,13 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
while (superIter.good()) while (superIter.good())
{ {
tryUnify_(*superIter, getSingletonTypes().errorRecoveryType()); tryUnify_(*superIter, singletonTypes->errorRecoveryType());
superIter.advance(); superIter.advance();
} }
while (subIter.good()) while (subIter.good())
{ {
tryUnify_(*subIter, getSingletonTypes().errorRecoveryType()); tryUnify_(*subIter, singletonTypes->errorRecoveryType());
subIter.advance(); subIter.advance();
} }
@ -1613,7 +1614,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
// Given t1 where t1 = { lower: (t1) -> (a, b...) } // Given t1 where t1 = { lower: (t1) -> (a, b...) }
// It should be the case that `string <: t1` iff `(subtype's metatable).__index <: t1` // It should be the case that `string <: t1` iff `(subtype's metatable).__index <: t1`
if (auto metatable = getMetatable(subTy)) if (auto metatable = getMetatable(subTy, singletonTypes))
{ {
auto mttv = log.get<TableTypeVar>(*metatable); auto mttv = log.get<TableTypeVar>(*metatable);
if (!mttv) if (!mttv)
@ -1658,10 +1659,10 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
TableTypeVar* resultTtv = getMutable<TableTypeVar>(result); TableTypeVar* resultTtv = getMutable<TableTypeVar>(result);
for (auto& [name, prop] : resultTtv->props) for (auto& [name, prop] : resultTtv->props)
prop.type = deeplyOptional(prop.type, seen); prop.type = deeplyOptional(prop.type, seen);
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, result}}); return types->addType(UnionTypeVar{{singletonTypes->nilType, result}});
} }
else else
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, ty}}); return types->addType(UnionTypeVar{{singletonTypes->nilType, ty}});
} }
void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
@ -1951,7 +1952,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}}); anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});
else else
{ {
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}}); const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{singletonTypes->anyType}});
anyTp = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}}); anyTp = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
} }
@ -1960,15 +1961,15 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
sharedState.tempSeenTy.clear(); sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear(); sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, Luau::tryUnifyWithAny(
FFlag::LuauUnknownAndNeverType ? anyTy : getSingletonTypes().anyType, anyTp); queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, FFlag::LuauUnknownAndNeverType ? anyTy : singletonTypes->anyType, anyTp);
} }
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp) void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
{ {
LUAU_ASSERT(get<Unifiable::Error>(anyTp)); LUAU_ASSERT(get<Unifiable::Error>(anyTp));
const TypeId anyTy = getSingletonTypes().errorRecoveryType(); const TypeId anyTy = singletonTypes->errorRecoveryType();
std::vector<TypeId> queue; std::vector<TypeId> queue;
@ -1982,7 +1983,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
{ {
return Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location); return Luau::findTablePropertyRespectingMeta(singletonTypes, errors, lhsType, name, location);
} }
void Unifier::tryUnifyWithConstrainedSubTypeVar(TypeId subTy, TypeId superTy) void Unifier::tryUnifyWithConstrainedSubTypeVar(TypeId subTy, TypeId superTy)
@ -2193,7 +2194,7 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
if (needle == haystack) if (needle == haystack)
{ {
reportError(TypeError{location, OccursCheckFailed{}}); reportError(TypeError{location, OccursCheckFailed{}});
log.replace(needle, *getSingletonTypes().errorRecoveryType()); log.replace(needle, *singletonTypes->errorRecoveryType());
return true; return true;
} }
@ -2250,7 +2251,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
if (needle == haystack) if (needle == haystack)
{ {
reportError(TypeError{location, OccursCheckFailed{}}); reportError(TypeError{location, OccursCheckFailed{}});
log.replace(needle, *getSingletonTypes().errorRecoveryTypePack()); log.replace(needle, *singletonTypes->errorRecoveryTypePack());
return true; return true;
} }
@ -2269,7 +2270,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
Unifier Unifier::makeChildUnifier() Unifier Unifier::makeChildUnifier()
{ {
Unifier u = Unifier{types, mode, scope, location, variance, sharedState, &log}; Unifier u = Unifier{types, singletonTypes, mode, scope, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop; u.anyIsTop = anyIsTop;
return u; return u;
} }

View file

@ -0,0 +1,50 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <vector>
#include <stdint.h>
#include <stddef.h>
namespace Luau
{
namespace CodeGen
{
struct CodeAllocator
{
CodeAllocator(size_t blockSize, size_t maxTotalSize);
~CodeAllocator();
// Places data and code into the executable page area
// To allow allocation while previously allocated code is already running, allocation has page granularity
// It's important to group functions together so that page alignment won't result in a lot of wasted space
bool allocate(uint8_t* data, size_t dataSize, uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart);
// Provided to callbacks
void* context = nullptr;
// Called when new block is created to create and setup the unwinding information for all the code in the block
// Some platforms require this data to be placed inside the block itself, so we also return 'unwindDataSizeInBlock'
void* (*createBlockUnwindInfo)(void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock) = nullptr;
// Called to destroy unwinding information returned by 'createBlockUnwindInfo'
void (*destroyBlockUnwindInfo)(void* context, void* unwindData) = nullptr;
static const size_t kMaxUnwindDataSize = 128;
bool allocateNewBlock(size_t& unwindInfoSize);
// Current block we use for allocations
uint8_t* blockPos = nullptr;
uint8_t* blockEnd = nullptr;
// All allocated blocks
std::vector<uint8_t*> blocks;
std::vector<void*> unwindInfos;
size_t blockSize = 0;
size_t maxTotalSize = 0;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -1,3 +1,4 @@
// 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/Common.h" #include "Luau/Common.h"

View file

@ -1,3 +1,4 @@
// 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/Common.h" #include "Luau/Common.h"

View file

@ -0,0 +1,188 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/CodeAllocator.h"
#include "Luau/Common.h"
#include <string.h>
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
const size_t kPageSize = 4096;
#else
#include <sys/mman.h>
#include <unistd.h>
const size_t kPageSize = sysconf(_SC_PAGESIZE);
#endif
static size_t alignToPageSize(size_t size)
{
return (size + kPageSize - 1) & ~(kPageSize - 1);
}
#if defined(_WIN32)
static uint8_t* allocatePages(size_t size)
{
return (uint8_t*)VirtualAlloc(nullptr, alignToPageSize(size), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
}
static void freePages(uint8_t* mem, size_t size)
{
if (VirtualFree(mem, 0, MEM_RELEASE) == 0)
LUAU_ASSERT(!"failed to deallocate block memory");
}
static void makePagesExecutable(uint8_t* mem, size_t size)
{
LUAU_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
LUAU_ASSERT(size == alignToPageSize(size));
DWORD oldProtect;
if (VirtualProtect(mem, size, PAGE_EXECUTE_READ, &oldProtect) == 0)
LUAU_ASSERT(!"failed to change page protection");
}
static void flushInstructionCache(uint8_t* mem, size_t size)
{
if (FlushInstructionCache(GetCurrentProcess(), mem, size) == 0)
LUAU_ASSERT(!"failed to flush instruction cache");
}
#else
static uint8_t* allocatePages(size_t size)
{
return (uint8_t*)mmap(nullptr, alignToPageSize(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
}
static void freePages(uint8_t* mem, size_t size)
{
if (munmap(mem, alignToPageSize(size)) != 0)
LUAU_ASSERT(!"failed to deallocate block memory");
}
static void makePagesExecutable(uint8_t* mem, size_t size)
{
LUAU_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
LUAU_ASSERT(size == alignToPageSize(size));
if (mprotect(mem, size, PROT_READ | PROT_EXEC) != 0)
LUAU_ASSERT(!"failed to change page protection");
}
static void flushInstructionCache(uint8_t* mem, size_t size)
{
__builtin___clear_cache((char*)mem, (char*)mem + size);
}
#endif
namespace Luau
{
namespace CodeGen
{
CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize)
: blockSize(blockSize)
, maxTotalSize(maxTotalSize)
{
LUAU_ASSERT(blockSize > kMaxUnwindDataSize);
LUAU_ASSERT(maxTotalSize >= blockSize);
}
CodeAllocator::~CodeAllocator()
{
if (destroyBlockUnwindInfo)
{
for (void* unwindInfo : unwindInfos)
destroyBlockUnwindInfo(context, unwindInfo);
}
for (uint8_t* block : blocks)
freePages(block, blockSize);
}
bool CodeAllocator::allocate(
uint8_t* data, size_t dataSize, uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart)
{
// 'Round up' to preserve 16 byte alignment
size_t alignedDataSize = (dataSize + 15) & ~15;
size_t totalSize = alignedDataSize + codeSize;
// Function has to fit into a single block with unwinding information
if (totalSize > blockSize - kMaxUnwindDataSize)
return false;
size_t unwindInfoSize = 0;
// We might need a new block
if (totalSize > size_t(blockEnd - blockPos))
{
if (!allocateNewBlock(unwindInfoSize))
return false;
LUAU_ASSERT(totalSize <= size_t(blockEnd - blockPos));
}
LUAU_ASSERT((uintptr_t(blockPos) & (kPageSize - 1)) == 0); // Allocation starts on page boundary
size_t dataOffset = unwindInfoSize + alignedDataSize - dataSize;
size_t codeOffset = unwindInfoSize + alignedDataSize;
if (dataSize)
memcpy(blockPos + dataOffset, data, dataSize);
if (codeSize)
memcpy(blockPos + codeOffset, code, codeSize);
size_t pageSize = alignToPageSize(unwindInfoSize + totalSize);
makePagesExecutable(blockPos, pageSize);
flushInstructionCache(blockPos + codeOffset, codeSize);
result = blockPos + unwindInfoSize;
resultSize = totalSize;
resultCodeStart = blockPos + codeOffset;
blockPos += pageSize;
LUAU_ASSERT((uintptr_t(blockPos) & (kPageSize - 1)) == 0); // Allocation ends on page boundary
return true;
}
bool CodeAllocator::allocateNewBlock(size_t& unwindInfoSize)
{
// Stop allocating once we reach a global limit
if ((blocks.size() + 1) * blockSize > maxTotalSize)
return false;
uint8_t* block = allocatePages(blockSize);
if (!block)
return false;
blockPos = block;
blockEnd = block + blockSize;
blocks.push_back(block);
if (createBlockUnwindInfo)
{
void* unwindInfo = createBlockUnwindInfo(context, block, blockSize, unwindInfoSize);
// 'Round up' to preserve 16 byte alignment of the following data and code
unwindInfoSize = (unwindInfoSize + 15) & ~15;
LUAU_ASSERT(unwindInfoSize <= kMaxUnwindDataSize);
if (!unwindInfo)
return false;
unwindInfos.push_back(unwindInfo);
}
return true;
}
} // namespace CodeGen
} // namespace Luau

View file

@ -289,17 +289,19 @@ enum LuauOpcode
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code // the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
LOP_FORGLOOP, LOP_FORGLOOP,
// FORGPREP_INEXT/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext // FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT // A: target register (see FORGLOOP for register layout)
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
LOP_FORGPREP_INEXT, LOP_FORGPREP_INEXT,
LOP_FORGLOOP_INEXT,
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next // removed in v3
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT LOP_DEP_FORGLOOP_INEXT,
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
LOP_FORGPREP_NEXT, LOP_FORGPREP_NEXT,
LOP_FORGLOOP_NEXT,
// removed in v3
LOP_DEP_FORGLOOP_NEXT,
// GETVARARGS: copy variables into the target register from vararg storage for current function // GETVARARGS: copy variables into the target register from vararg storage for current function
// A: target register // A: target register
@ -343,12 +345,9 @@ enum LuauOpcode
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF) // B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
LOP_CAPTURE, LOP_CAPTURE,
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants) // removed in v3
// A: source register 1 LOP_DEP_JUMPIFEQK,
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump") LOP_DEP_JUMPIFNOTEQK,
// AUX: constant table index
LOP_JUMPIFEQK,
LOP_JUMPIFNOTEQK,
// FASTCALL1: perform a fast call of a built-in function using 1 register argument // FASTCALL1: perform a fast call of a built-in function using 1 register argument
// A: builtin function id (see LuauBuiltinFunction) // A: builtin function id (see LuauBuiltinFunction)

View file

@ -73,8 +73,6 @@ static int getOpLength(LuauOpcode op)
case LOP_SETLIST: case LOP_SETLIST:
case LOP_FORGLOOP: case LOP_FORGLOOP:
case LOP_LOADKX: case LOP_LOADKX:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_FASTCALL2: case LOP_FASTCALL2:
case LOP_FASTCALL2K: case LOP_FASTCALL2K:
case LOP_JUMPXEQKNIL: case LOP_JUMPXEQKNIL:
@ -106,12 +104,8 @@ inline bool isJumpD(LuauOpcode op)
case LOP_FORGPREP: case LOP_FORGPREP:
case LOP_FORGLOOP: case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT: case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT: case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
case LOP_JUMPBACK: case LOP_JUMPBACK:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_JUMPXEQKNIL: case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB: case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN: case LOP_JUMPXEQKN:
@ -1247,13 +1241,6 @@ void BytecodeBuilder::validate() const
VJUMP(LUAU_INSN_D(insn)); VJUMP(LUAU_INSN_D(insn));
break; break;
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
VREG(LUAU_INSN_A(insn));
VCONSTANY(insns[i + 1]);
VJUMP(LUAU_INSN_D(insn));
break;
case LOP_JUMPXEQKNIL: case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB: case LOP_JUMPXEQKB:
VREG(LUAU_INSN_A(insn)); VREG(LUAU_INSN_A(insn));
@ -1360,9 +1347,7 @@ void BytecodeBuilder::validate() const
break; break;
case LOP_FORGPREP_INEXT: case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT: case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
VREG(LUAU_INSN_A(insn) + 4); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, A+4 are loop variables VREG(LUAU_INSN_A(insn) + 4); // forg loop protocol: A, A+1, A+2 are used for iteration protocol; A+3, A+4 are loop variables
VJUMP(LUAU_INSN_D(insn)); VJUMP(LUAU_INSN_D(insn));
break; break;
@ -1728,18 +1713,10 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel); formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGPREP_NEXT: case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel); formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break; break;
case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_GETVARARGS: case LOP_GETVARARGS:
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1); formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
break; break;
@ -1797,14 +1774,6 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn)); LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
break; break;
case LOP_JUMPIFEQK:
formatAppend(result, "JUMPIFEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFNOTEQK:
formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPXEQKNIL: case LOP_JUMPXEQKNIL:
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : ""); formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
code++; code++;

View file

@ -3457,14 +3457,6 @@ struct Compiler
return uint8_t(top); return uint8_t(top);
} }
void reserveReg(AstNode* node, unsigned int count)
{
if (regTop + count > kMaxRegisterCount)
CompileError::raise(node->location, "Out of registers when trying to allocate %d registers: exceeded limit %d", count, kMaxRegisterCount);
stackSize = std::max(stackSize, regTop + count);
}
void setDebugLine(AstNode* node) void setDebugLine(AstNode* node)
{ {
if (options.debugLevel >= 1) if (options.debugLevel >= 1)

View file

@ -142,12 +142,16 @@ coverage: $(TESTS_TARGET)
llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info
format: format:
find . -name '*.h' -or -name '*.cpp' | xargs clang-format-11 -i git ls-files '*.h' '*.cpp' | xargs clang-format-11 -i
luau-size: luau luau-size: luau
nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }' nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }'
nm --print-size --demangle luau | grep ' t luauF_' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " builtins" }' nm --print-size --demangle luau | grep ' t luauF_' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " builtins" }'
check-source:
git ls-files '*.h' '*.cpp' | xargs -I+ sh -c 'grep -L LICENSE +'
git ls-files '*.h' ':!:extern' | xargs -I+ sh -c 'grep -L "#pragma once" +'
# executable target aliases # executable target aliases
luau: $(REPL_CLI_TARGET) luau: $(REPL_CLI_TARGET)
ln -fs $^ $@ ln -fs $^ $@

View file

@ -56,12 +56,14 @@ target_sources(Luau.Compiler PRIVATE
# Luau.CodeGen Sources # Luau.CodeGen Sources
target_sources(Luau.CodeGen PRIVATE target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/AssemblyBuilderX64.h CodeGen/include/Luau/AssemblyBuilderX64.h
CodeGen/include/Luau/CodeAllocator.h
CodeGen/include/Luau/Condition.h CodeGen/include/Luau/Condition.h
CodeGen/include/Luau/Label.h CodeGen/include/Luau/Label.h
CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OperandX64.h
CodeGen/include/Luau/RegisterX64.h CodeGen/include/Luau/RegisterX64.h
CodeGen/src/AssemblyBuilderX64.cpp CodeGen/src/AssemblyBuilderX64.cpp
CodeGen/src/CodeAllocator.cpp
) )
# Luau.Analysis Sources # Luau.Analysis Sources
@ -77,7 +79,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Constraint.h Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGraphBuilder.h Analysis/include/Luau/ConstraintGraphBuilder.h
Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ConstraintSolver.h
Analysis/include/Luau/ConstraintSolverLogger.h Analysis/include/Luau/DcrLogger.h
Analysis/include/Luau/Documentation.h Analysis/include/Luau/Documentation.h
Analysis/include/Luau/Error.h Analysis/include/Luau/Error.h
Analysis/include/Luau/FileResolver.h Analysis/include/Luau/FileResolver.h
@ -127,7 +129,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/Constraint.cpp Analysis/src/Constraint.cpp
Analysis/src/ConstraintGraphBuilder.cpp Analysis/src/ConstraintGraphBuilder.cpp
Analysis/src/ConstraintSolver.cpp Analysis/src/ConstraintSolver.cpp
Analysis/src/ConstraintSolverLogger.cpp Analysis/src/DcrLogger.cpp
Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/EmbeddedBuiltinDefinitions.cpp
Analysis/src/Error.cpp Analysis/src/Error.cpp
Analysis/src/Frontend.cpp Analysis/src/Frontend.cpp
@ -266,6 +268,7 @@ if(TARGET Luau.UnitTest)
tests/AstVisitor.test.cpp tests/AstVisitor.test.cpp
tests/Autocomplete.test.cpp tests/Autocomplete.test.cpp
tests/BuiltinDefinitions.test.cpp tests/BuiltinDefinitions.test.cpp
tests/CodeAllocator.test.cpp
tests/Compiler.test.cpp tests/Compiler.test.cpp
tests/Config.test.cpp tests/Config.test.cpp
tests/ConstraintGraphBuilder.test.cpp tests/ConstraintGraphBuilder.test.cpp

View file

@ -35,6 +35,15 @@ enum lua_Status
LUA_BREAK, // yielded for a debug breakpoint LUA_BREAK, // yielded for a debug breakpoint
}; };
enum lua_CoStatus
{
LUA_CORUN = 0, // running
LUA_COSUS, // suspended
LUA_CONOR, // 'normal' (it resumed another coroutine)
LUA_COFIN, // finished
LUA_COERR, // finished with error
};
typedef struct lua_State lua_State; typedef struct lua_State lua_State;
typedef int (*lua_CFunction)(lua_State* L); typedef int (*lua_CFunction)(lua_State* L);
@ -224,6 +233,7 @@ LUA_API int lua_status(lua_State* L);
LUA_API int lua_isyieldable(lua_State* L); LUA_API int lua_isyieldable(lua_State* L);
LUA_API void* lua_getthreaddata(lua_State* L); LUA_API void* lua_getthreaddata(lua_State* L);
LUA_API void lua_setthreaddata(lua_State* L, void* data); LUA_API void lua_setthreaddata(lua_State* L, void* data);
LUA_API int lua_costatus(lua_State* L, lua_State* co);
/* /*
** garbage-collection function and options ** garbage-collection function and options

View file

@ -1008,6 +1008,23 @@ int lua_status(lua_State* L)
return L->status; return L->status;
} }
int lua_costatus(lua_State* L, lua_State* co)
{
if (co == L)
return LUA_CORUN;
if (co->status == LUA_YIELD)
return LUA_COSUS;
if (co->status == LUA_BREAK)
return LUA_CONOR;
if (co->status != 0) // some error occurred
return LUA_COERR;
if (co->ci != co->base_ci) // does it have frames?
return LUA_CONOR;
if (co->top == co->base)
return LUA_COFIN;
return LUA_COSUS; // initial state
}
void* lua_getthreaddata(lua_State* L) void* lua_getthreaddata(lua_State* L)
{ {
return L->userdata; return L->userdata;

View file

@ -5,38 +5,16 @@
#include "lstate.h" #include "lstate.h"
#include "lvm.h" #include "lvm.h"
#define CO_RUN 0 // running
#define CO_SUS 1 // suspended
#define CO_NOR 2 // 'normal' (it resumed another coroutine)
#define CO_DEAD 3
#define CO_STATUS_ERROR -1 #define CO_STATUS_ERROR -1
#define CO_STATUS_BREAK -2 #define CO_STATUS_BREAK -2
static const char* const statnames[] = {"running", "suspended", "normal", "dead"}; static const char* const statnames[] = {"running", "suspended", "normal", "dead", "dead"}; // dead appears twice for LUA_COERR and LUA_COFIN
static int auxstatus(lua_State* L, lua_State* co)
{
if (co == L)
return CO_RUN;
if (co->status == LUA_YIELD)
return CO_SUS;
if (co->status == LUA_BREAK)
return CO_NOR;
if (co->status != 0) // some error occurred
return CO_DEAD;
if (co->ci != co->base_ci) // does it have frames?
return CO_NOR;
if (co->top == co->base)
return CO_DEAD;
return CO_SUS; // initial state
}
static int costatus(lua_State* L) static int costatus(lua_State* L)
{ {
lua_State* co = lua_tothread(L, 1); lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread"); luaL_argexpected(L, co, 1, "thread");
lua_pushstring(L, statnames[auxstatus(L, co)]); lua_pushstring(L, statnames[lua_costatus(L, co)]);
return 1; return 1;
} }
@ -45,8 +23,8 @@ static int auxresume(lua_State* L, lua_State* co, int narg)
// error handling for edge cases // error handling for edge cases
if (co->status != LUA_YIELD) if (co->status != LUA_YIELD)
{ {
int status = auxstatus(L, co); int status = lua_costatus(L, co);
if (status != CO_SUS) if (status != LUA_COSUS)
{ {
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]); lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
return CO_STATUS_ERROR; return CO_STATUS_ERROR;
@ -236,8 +214,8 @@ static int coclose(lua_State* L)
lua_State* co = lua_tothread(L, 1); lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread"); luaL_argexpected(L, co, 1, "thread");
int status = auxstatus(L, co); int status = lua_costatus(L, co);
if (status != CO_DEAD && status != CO_SUS) if (status != LUA_COFIN && status != LUA_COERR && status != LUA_COSUS)
luaL_error(L, "cannot close %s coroutine", statnames[status]); luaL_error(L, "cannot close %s coroutine", statnames[status]);
if (co->status == LUA_OK || co->status == LUA_YIELD) if (co->status == LUA_OK || co->status == LUA_YIELD)

View file

@ -123,6 +123,7 @@
LUAU_FASTFLAGVARIABLE(LuauSimplerUpval, false) LUAU_FASTFLAGVARIABLE(LuauSimplerUpval, false)
LUAU_FASTFLAGVARIABLE(LuauNoSleepBit, false) LUAU_FASTFLAGVARIABLE(LuauNoSleepBit, false)
LUAU_FASTFLAGVARIABLE(LuauEagerShrink, false) LUAU_FASTFLAGVARIABLE(LuauEagerShrink, false)
LUAU_FASTFLAGVARIABLE(LuauFasterSweep, false)
#define GC_SWEEPPAGESTEPCOST 16 #define GC_SWEEPPAGESTEPCOST 16
@ -848,6 +849,7 @@ static size_t atomic(lua_State* L)
static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco) static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco)
{ {
LUAU_ASSERT(!FFlag::LuauFasterSweep);
global_State* g = L->global; global_State* g = L->global;
int deadmask = otherwhite(g); int deadmask = otherwhite(g);
@ -890,22 +892,62 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
int blockSize; int blockSize;
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize); luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
for (char* pos = start; pos != end; pos += blockSize) LUAU_ASSERT(busyBlocks > 0);
if (FFlag::LuauFasterSweep)
{ {
GCObject* gco = (GCObject*)pos; LUAU_ASSERT(FFlag::LuauNoSleepBit && FFlag::LuauEagerShrink);
// skip memory blocks that are already freed global_State* g = L->global;
if (gco->gch.tt == LUA_TNIL)
continue;
// when true is returned it means that the element was deleted int deadmask = otherwhite(g);
if (sweepgco(L, page, gco)) LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
int newwhite = luaC_white(g);
for (char* pos = start; pos != end; pos += blockSize)
{ {
LUAU_ASSERT(busyBlocks > 0); GCObject* gco = (GCObject*)pos;
// if the last block was removed, page would be removed as well // skip memory blocks that are already freed
if (--busyBlocks == 0) if (gco->gch.tt == LUA_TNIL)
return int(pos - start) / blockSize + 1; continue;
// is the object alive?
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
{
LUAU_ASSERT(!isdead(g, gco));
// make it white (for next cycle)
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
}
else
{
LUAU_ASSERT(isdead(g, gco));
freeobj(L, gco, page);
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
}
}
}
else
{
for (char* pos = start; pos != end; pos += blockSize)
{
GCObject* gco = (GCObject*)pos;
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
// when true is returned it means that the element was deleted
if (sweepgco(L, page, gco))
{
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
}
} }
} }
@ -993,10 +1035,19 @@ static size_t gcstep(lua_State* L, size_t limit)
// nothing more to sweep? // nothing more to sweep?
if (g->sweepgcopage == NULL) if (g->sweepgcopage == NULL)
{ {
// don't forget to visit main thread // don't forget to visit main thread, it's the only object not allocated in GCO pages
sweepgco(L, NULL, obj2gco(g->mainthread)); if (FFlag::LuauFasterSweep)
{
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
}
else
{
sweepgco(L, NULL, obj2gco(g->mainthread));
}
shrinkbuffers(L); shrinkbuffers(L);
g->gcstate = GCSpause; // end collection g->gcstate = GCSpause; // end collection
} }
break; break;

View file

@ -107,10 +107,10 @@ LUAU_FASTFLAG(LuauNoSleepBit)
VM_DISPATCH_OP(LOP_POWK), VM_DISPATCH_OP(LOP_AND), VM_DISPATCH_OP(LOP_OR), VM_DISPATCH_OP(LOP_ANDK), VM_DISPATCH_OP(LOP_ORK), \ VM_DISPATCH_OP(LOP_POWK), VM_DISPATCH_OP(LOP_AND), VM_DISPATCH_OP(LOP_OR), VM_DISPATCH_OP(LOP_ANDK), VM_DISPATCH_OP(LOP_ORK), \
VM_DISPATCH_OP(LOP_CONCAT), VM_DISPATCH_OP(LOP_NOT), VM_DISPATCH_OP(LOP_MINUS), VM_DISPATCH_OP(LOP_LENGTH), VM_DISPATCH_OP(LOP_NEWTABLE), \ VM_DISPATCH_OP(LOP_CONCAT), VM_DISPATCH_OP(LOP_NOT), VM_DISPATCH_OP(LOP_MINUS), VM_DISPATCH_OP(LOP_LENGTH), VM_DISPATCH_OP(LOP_NEWTABLE), \
VM_DISPATCH_OP(LOP_DUPTABLE), VM_DISPATCH_OP(LOP_SETLIST), VM_DISPATCH_OP(LOP_FORNPREP), VM_DISPATCH_OP(LOP_FORNLOOP), \ VM_DISPATCH_OP(LOP_DUPTABLE), VM_DISPATCH_OP(LOP_SETLIST), VM_DISPATCH_OP(LOP_FORNPREP), VM_DISPATCH_OP(LOP_FORNLOOP), \
VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \ VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
VM_DISPATCH_OP(LOP_FORGLOOP_NEXT), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \ VM_DISPATCH_OP(LOP_DEP_FORGLOOP_NEXT), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \
VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \ VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_JUMPIFEQK), VM_DISPATCH_OP(LOP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \ VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \
VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \ VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \
VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS),
@ -2401,7 +2401,7 @@ static void luau_execute(lua_State* L)
VM_NEXT(); VM_NEXT();
} }
VM_CASE(LOP_FORGLOOP_INEXT) VM_CASE(LOP_DEP_FORGLOOP_INEXT)
{ {
VM_INTERRUPT(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
@ -2473,7 +2473,7 @@ static void luau_execute(lua_State* L)
VM_NEXT(); VM_NEXT();
} }
VM_CASE(LOP_FORGLOOP_NEXT) VM_CASE(LOP_DEP_FORGLOOP_NEXT)
{ {
VM_INTERRUPT(); VM_INTERRUPT();
Instruction insn = *pc++; Instruction insn = *pc++;
@ -2748,7 +2748,7 @@ static void luau_execute(lua_State* L)
LUAU_UNREACHABLE(); LUAU_UNREACHABLE();
} }
VM_CASE(LOP_JUMPIFEQK) VM_CASE(LOP_DEP_JUMPIFEQK)
{ {
Instruction insn = *pc++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;
@ -2793,7 +2793,7 @@ static void luau_execute(lua_State* L)
} }
} }
VM_CASE(LOP_JUMPIFNOTEQK) VM_CASE(LOP_DEP_JUMPIFNOTEQK)
{ {
Instruction insn = *pc++; Instruction insn = *pc++;
uint32_t aux = *pc; uint32_t aux = *pc;

View file

@ -107,8 +107,8 @@ struct ACFixture : ACFixtureImpl<Fixture>
ACFixture() ACFixture()
: ACFixtureImpl<Fixture>() : ACFixtureImpl<Fixture>()
{ {
addGlobalBinding(frontend.typeChecker, "table", Binding{typeChecker.anyType}); addGlobalBinding(frontend, "table", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeChecker, "math", Binding{typeChecker.anyType}); addGlobalBinding(frontend, "math", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeCheckerForAutocomplete, "table", Binding{typeChecker.anyType}); addGlobalBinding(frontend.typeCheckerForAutocomplete, "table", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeCheckerForAutocomplete, "math", Binding{typeChecker.anyType}); addGlobalBinding(frontend.typeCheckerForAutocomplete, "math", Binding{typeChecker.anyType});
} }
@ -3200,8 +3200,6 @@ a.@1
TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent") TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent")
{ {
ScopedFastFlag sff("LuauAutocompleteFixGlobalOrder", true);
check(R"( check(R"(
local myLocal = 4 local myLocal = 4
function abc0() function abc0()

View file

@ -0,0 +1,162 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AssemblyBuilderX64.h"
#include "Luau/CodeAllocator.h"
#include "doctest.h"
#include <string.h>
using namespace Luau::CodeGen;
TEST_SUITE_BEGIN("CodeAllocation");
TEST_CASE("CodeAllocation")
{
size_t blockSize = 1024 * 1024;
size_t maxTotalSize = 1024 * 1024;
CodeAllocator allocator(blockSize, maxTotalSize);
uint8_t* nativeData = nullptr;
size_t sizeNativeData = 0;
uint8_t* nativeEntry = nullptr;
std::vector<uint8_t> code;
code.resize(128);
REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
CHECK(nativeData != nullptr);
CHECK(sizeNativeData == 128);
CHECK(nativeEntry != nullptr);
CHECK(nativeEntry == nativeData);
std::vector<uint8_t> data;
data.resize(8);
REQUIRE(allocator.allocate(data.data(), data.size(), code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
CHECK(nativeData != nullptr);
CHECK(sizeNativeData == 16 + 128);
CHECK(nativeEntry != nullptr);
CHECK(nativeEntry == nativeData + 16);
}
TEST_CASE("CodeAllocationFailure")
{
size_t blockSize = 16384;
size_t maxTotalSize = 32768;
CodeAllocator allocator(blockSize, maxTotalSize);
uint8_t* nativeData;
size_t sizeNativeData;
uint8_t* nativeEntry;
std::vector<uint8_t> code;
code.resize(18000);
// allocation has to fit in a block
REQUIRE(!allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
// each allocation exhausts a block, so third allocation fails
code.resize(10000);
REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
REQUIRE(!allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
}
TEST_CASE("CodeAllocationWithUnwindCallbacks")
{
struct Info
{
std::vector<uint8_t> unwind;
uint8_t* block = nullptr;
bool destroyCalled = false;
};
Info info;
info.unwind.resize(8);
{
size_t blockSize = 1024 * 1024;
size_t maxTotalSize = 1024 * 1024;
CodeAllocator allocator(blockSize, maxTotalSize);
uint8_t* nativeData = nullptr;
size_t sizeNativeData = 0;
uint8_t* nativeEntry = nullptr;
std::vector<uint8_t> code;
code.resize(128);
std::vector<uint8_t> data;
data.resize(8);
allocator.context = &info;
allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& unwindDataSizeInBlock) -> void* {
Info& info = *(Info*)context;
CHECK(info.unwind.size() == 8);
memcpy(block, info.unwind.data(), info.unwind.size());
unwindDataSizeInBlock = 8;
info.block = block;
return new int(7);
};
allocator.destroyBlockUnwindInfo = [](void* context, void* unwindData) {
Info& info = *(Info*)context;
info.destroyCalled = true;
CHECK(*(int*)unwindData == 7);
delete (int*)unwindData;
};
REQUIRE(allocator.allocate(data.data(), data.size(), code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
CHECK(nativeData != nullptr);
CHECK(sizeNativeData == 16 + 128);
CHECK(nativeEntry != nullptr);
CHECK(nativeEntry == nativeData + 16);
CHECK(nativeData == info.block + 16);
}
CHECK(info.destroyCalled);
}
#if defined(__x86_64__) || defined(_M_X64)
TEST_CASE("GeneratedCodeExecution")
{
#if defined(_WIN32)
// Windows x64 ABI
constexpr RegisterX64 rArg1 = rcx;
constexpr RegisterX64 rArg2 = rdx;
#else
// System V AMD64 ABI
constexpr RegisterX64 rArg1 = rdi;
constexpr RegisterX64 rArg2 = rsi;
#endif
AssemblyBuilderX64 build(/* logText= */ false);
build.mov(rax, rArg1);
build.add(rax, rArg2);
build.imul(rax, rax, 7);
build.ret();
build.finalize();
size_t blockSize = 1024 * 1024;
size_t maxTotalSize = 1024 * 1024;
CodeAllocator allocator(blockSize, maxTotalSize);
uint8_t* nativeData;
size_t sizeNativeData;
uint8_t* nativeEntry;
REQUIRE(allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData, sizeNativeData, nativeEntry));
REQUIRE(nativeEntry);
using FunctionType = int64_t(int64_t, int64_t);
FunctionType* f = (FunctionType*)nativeEntry;
int64_t result = f(10, 20);
CHECK(result == 210);
}
#endif
TEST_SUITE_END();

View file

@ -6100,6 +6100,7 @@ return
math.round(7.6), math.round(7.6),
bit32.extract(-1, 31), bit32.extract(-1, 31),
bit32.replace(100, 1, 0), bit32.replace(100, 1, 0),
math.log(100, 10),
(type("fin")) (type("fin"))
)", )",
0, 2), 0, 2),
@ -6153,8 +6154,9 @@ LOADN R45 1
LOADN R46 8 LOADN R46 8
LOADN R47 1 LOADN R47 1
LOADN R48 101 LOADN R48 101
LOADK R49 K3 LOADN R49 2
RETURN R0 50 LOADK R50 K3
RETURN R0 51
)"); )");
} }
@ -6166,7 +6168,12 @@ return
math.max(1, true), math.max(1, true),
string.byte("abc", 42), string.byte("abc", 42),
bit32.rshift(10, 42), bit32.rshift(10, 42),
bit32.extract(1, 2, "3") bit32.extract(1, 2, "3"),
bit32.bor(1, true),
bit32.band(1, true),
bit32.bxor(1, true),
bit32.btest(1, true),
math.min(1, true)
)", )",
0, 2), 0, 2),
R"( R"(
@ -6193,11 +6200,96 @@ LOADN R6 2
LOADK R7 K14 LOADK R7 K14
FASTCALL 34 L4 FASTCALL 34 L4
GETIMPORT R4 16 GETIMPORT R4 16
CALL R4 3 -1 CALL R4 3 1
L4: RETURN R0 -1 L4: LOADN R6 1
FASTCALL2K 31 R6 K3 L5
LOADK R7 K3
GETIMPORT R5 18
CALL R5 2 1
L5: LOADN R7 1
FASTCALL2K 29 R7 K3 L6
LOADK R8 K3
GETIMPORT R6 20
CALL R6 2 1
L6: LOADN R8 1
FASTCALL2K 32 R8 K3 L7
LOADK R9 K3
GETIMPORT R7 22
CALL R7 2 1
L7: LOADN R9 1
FASTCALL2K 33 R9 K3 L8
LOADK R10 K3
GETIMPORT R8 24
CALL R8 2 1
L8: LOADN R10 1
FASTCALL2K 19 R10 K3 L9
LOADK R11 K3
GETIMPORT R9 26
CALL R9 2 -1
L9: RETURN R0 -1
)"); )");
} }
TEST_CASE("BuiltinFoldingProhibitedCoverage")
{
const char* builtins[] = {
"math.abs",
"math.acos",
"math.asin",
"math.atan2",
"math.atan",
"math.ceil",
"math.cosh",
"math.cos",
"math.deg",
"math.exp",
"math.floor",
"math.fmod",
"math.ldexp",
"math.log10",
"math.log",
"math.max",
"math.min",
"math.pow",
"math.rad",
"math.sinh",
"math.sin",
"math.sqrt",
"math.tanh",
"math.tan",
"bit32.arshift",
"bit32.band",
"bit32.bnot",
"bit32.bor",
"bit32.bxor",
"bit32.btest",
"bit32.extract",
"bit32.lrotate",
"bit32.lshift",
"bit32.replace",
"bit32.rrotate",
"bit32.rshift",
"type",
"string.byte",
"string.len",
"typeof",
"math.clamp",
"math.sign",
"math.round",
};
for (const char* func : builtins)
{
std::string source = "return ";
source += func;
source += "()";
std::string bc = compileFunction(source.c_str(), 0, 2);
CHECK(bc.find("FASTCALL") != std::string::npos);
}
}
TEST_CASE("BuiltinFoldingMultret") TEST_CASE("BuiltinFoldingMultret")
{ {
CHECK_EQ("\n" + compileFunction(R"( CHECK_EQ("\n" + compileFunction(R"(

View file

@ -496,7 +496,8 @@ TEST_CASE("Types")
runConformance("types.lua", [](lua_State* L) { runConformance("types.lua", [](lua_State* L) {
Luau::NullModuleResolver moduleResolver; Luau::NullModuleResolver moduleResolver;
Luau::InternalErrorReporter iceHandler; Luau::InternalErrorReporter iceHandler;
Luau::TypeChecker env(&moduleResolver, &iceHandler); Luau::SingletonTypes singletonTypes;
Luau::TypeChecker env(&moduleResolver, Luau::NotNull{&singletonTypes}, &iceHandler);
Luau::registerBuiltinTypes(env); Luau::registerBuiltinTypes(env);
Luau::freeze(env.globalTypes); Luau::freeze(env.globalTypes);

View file

@ -26,10 +26,10 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
)"); )");
cgb.visit(block); cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope); NotNull<Scope> rootScope{cgb.rootScope};
NullModuleResolver resolver; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
cs.run(); cs.run();
@ -47,10 +47,10 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
)"); )");
cgb.visit(block); cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope); NotNull<Scope> rootScope{cgb.rootScope};
NullModuleResolver resolver; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
cs.run(); cs.run();
@ -74,12 +74,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
)"); )");
cgb.visit(block); cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope); NotNull<Scope> rootScope{cgb.rootScope};
ToStringOptions opts; ToStringOptions opts;
NullModuleResolver resolver; NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}}; ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
cs.run(); cs.run();

View file

@ -1,6 +1,8 @@
// 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/Parser.h" #include "Luau/Parser.h"
#include "ScopedFlags.h"
#include "doctest.h" #include "doctest.h"
using namespace Luau; using namespace Luau;
@ -223,4 +225,21 @@ end
CHECK_EQ(6, Luau::Compile::computeCost(model, args2, 1)); CHECK_EQ(6, Luau::Compile::computeCost(model, args2, 1));
} }
TEST_CASE("InterpString")
{
ScopedFastFlag sff("LuauInterpolatedStringBaseSupport", true);
uint64_t model = modelFunction(R"(
function test(a)
return `hello, {a}!`
end
)");
const bool args1[] = {false};
const bool args2[] = {true};
CHECK_EQ(3, Luau::Compile::computeCost(model, args1, 1));
CHECK_EQ(3, Luau::Compile::computeCost(model, args2, 1));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -91,6 +91,7 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete)
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze) : sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
, frontend(&fileResolver, &configResolver, {/* retainFullTypeGraphs= */ true}) , frontend(&fileResolver, &configResolver, {/* retainFullTypeGraphs= */ true})
, typeChecker(frontend.typeChecker) , typeChecker(frontend.typeChecker)
, singletonTypes(frontend.singletonTypes)
{ {
configResolver.defaultConfig.mode = Mode::Strict; configResolver.defaultConfig.mode = Mode::Strict;
configResolver.defaultConfig.enabledLint.warningMask = ~0ull; configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
@ -367,9 +368,9 @@ void Fixture::dumpErrors(std::ostream& os, const std::vector<TypeError>& errors)
void Fixture::registerTestTypes() void Fixture::registerTestTypes()
{ {
addGlobalBinding(typeChecker, "game", typeChecker.anyType, "@luau"); addGlobalBinding(frontend, "game", typeChecker.anyType, "@luau");
addGlobalBinding(typeChecker, "workspace", typeChecker.anyType, "@luau"); addGlobalBinding(frontend, "workspace", typeChecker.anyType, "@luau");
addGlobalBinding(typeChecker, "script", typeChecker.anyType, "@luau"); addGlobalBinding(frontend, "script", typeChecker.anyType, "@luau");
} }
void Fixture::dumpErrors(const CheckResult& cr) void Fixture::dumpErrors(const CheckResult& cr)
@ -434,7 +435,7 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
Luau::unfreeze(frontend.typeChecker.globalTypes); Luau::unfreeze(frontend.typeChecker.globalTypes);
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes); Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
registerBuiltinTypes(frontend.typeChecker); registerBuiltinTypes(frontend);
if (prepareAutocomplete) if (prepareAutocomplete)
registerBuiltinTypes(frontend.typeCheckerForAutocomplete); registerBuiltinTypes(frontend.typeCheckerForAutocomplete);
registerTestTypes(); registerTestTypes();
@ -446,7 +447,7 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
: Fixture() : Fixture()
, mainModule(new Module) , mainModule(new Module)
, cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), NotNull(&ice), frontend.getGlobalScope()) , cgb(mainModuleName, mainModule, &arena, NotNull(&moduleResolver), singletonTypes, NotNull(&ice), frontend.getGlobalScope(), &logger)
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true} , forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
{ {
BlockedTypeVar::nextIndex = 0; BlockedTypeVar::nextIndex = 0;

View file

@ -12,6 +12,7 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/DcrLogger.h"
#include "IostreamOptional.h" #include "IostreamOptional.h"
#include "ScopedFlags.h" #include "ScopedFlags.h"
@ -137,6 +138,7 @@ struct Fixture
Frontend frontend; Frontend frontend;
InternalErrorReporter ice; InternalErrorReporter ice;
TypeChecker& typeChecker; TypeChecker& typeChecker;
NotNull<SingletonTypes> singletonTypes;
std::string decorateWithTypes(const std::string& code); std::string decorateWithTypes(const std::string& code);
@ -165,6 +167,7 @@ struct ConstraintGraphBuilderFixture : Fixture
TypeArena arena; TypeArena arena;
ModulePtr mainModule; ModulePtr mainModule;
ConstraintGraphBuilder cgb; ConstraintGraphBuilder cgb;
DcrLogger logger;
ScopedFastFlag forceTheFlag; ScopedFastFlag forceTheFlag;

View file

@ -81,8 +81,8 @@ struct FrontendFixture : BuiltinsFixture
{ {
FrontendFixture() FrontendFixture()
{ {
addGlobalBinding(typeChecker, "game", frontend.typeChecker.anyType, "@test"); addGlobalBinding(frontend, "game", frontend.typeChecker.anyType, "@test");
addGlobalBinding(typeChecker, "script", frontend.typeChecker.anyType, "@test"); addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
} }
}; };

View file

@ -34,23 +34,28 @@ static LValue mkSymbol(const std::string& s)
return Symbol{AstName{s.data()}}; return Symbol{AstName{s.data()}};
} }
struct LValueFixture
{
SingletonTypes singletonTypes;
};
TEST_SUITE_BEGIN("LValue"); TEST_SUITE_BEGIN("LValue");
TEST_CASE("Luau_merge_hashmap_order") TEST_CASE_FIXTURE(LValueFixture, "Luau_merge_hashmap_order")
{ {
std::string a = "a"; std::string a = "a";
std::string b = "b"; std::string b = "b";
std::string c = "c"; std::string c = "c";
RefinementMap m{{ RefinementMap m{{
{mkSymbol(b), getSingletonTypes().stringType}, {mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), getSingletonTypes().numberType}, {mkSymbol(c), singletonTypes.numberType},
}}; }};
RefinementMap other{{ RefinementMap other{{
{mkSymbol(a), getSingletonTypes().stringType}, {mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), getSingletonTypes().stringType}, {mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), getSingletonTypes().booleanType}, {mkSymbol(c), singletonTypes.booleanType},
}}; }};
TypeArena arena; TypeArena arena;
@ -66,21 +71,21 @@ TEST_CASE("Luau_merge_hashmap_order")
CHECK_EQ("boolean | number", toString(m[mkSymbol(c)])); CHECK_EQ("boolean | number", toString(m[mkSymbol(c)]));
} }
TEST_CASE("Luau_merge_hashmap_order2") TEST_CASE_FIXTURE(LValueFixture, "Luau_merge_hashmap_order2")
{ {
std::string a = "a"; std::string a = "a";
std::string b = "b"; std::string b = "b";
std::string c = "c"; std::string c = "c";
RefinementMap m{{ RefinementMap m{{
{mkSymbol(a), getSingletonTypes().stringType}, {mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), getSingletonTypes().stringType}, {mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), getSingletonTypes().numberType}, {mkSymbol(c), singletonTypes.numberType},
}}; }};
RefinementMap other{{ RefinementMap other{{
{mkSymbol(b), getSingletonTypes().stringType}, {mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), getSingletonTypes().booleanType}, {mkSymbol(c), singletonTypes.booleanType},
}}; }};
TypeArena arena; TypeArena arena;
@ -96,7 +101,7 @@ TEST_CASE("Luau_merge_hashmap_order2")
CHECK_EQ("boolean | number", toString(m[mkSymbol(c)])); CHECK_EQ("boolean | number", toString(m[mkSymbol(c)]));
} }
TEST_CASE("one_map_has_overlap_at_end_whereas_other_has_it_in_start") TEST_CASE_FIXTURE(LValueFixture, "one_map_has_overlap_at_end_whereas_other_has_it_in_start")
{ {
std::string a = "a"; std::string a = "a";
std::string b = "b"; std::string b = "b";
@ -105,15 +110,15 @@ TEST_CASE("one_map_has_overlap_at_end_whereas_other_has_it_in_start")
std::string e = "e"; std::string e = "e";
RefinementMap m{{ RefinementMap m{{
{mkSymbol(a), getSingletonTypes().stringType}, {mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), getSingletonTypes().numberType}, {mkSymbol(b), singletonTypes.numberType},
{mkSymbol(c), getSingletonTypes().booleanType}, {mkSymbol(c), singletonTypes.booleanType},
}}; }};
RefinementMap other{{ RefinementMap other{{
{mkSymbol(c), getSingletonTypes().stringType}, {mkSymbol(c), singletonTypes.stringType},
{mkSymbol(d), getSingletonTypes().numberType}, {mkSymbol(d), singletonTypes.numberType},
{mkSymbol(e), getSingletonTypes().booleanType}, {mkSymbol(e), singletonTypes.booleanType},
}}; }};
TypeArena arena; TypeArena arena;
@ -133,7 +138,7 @@ TEST_CASE("one_map_has_overlap_at_end_whereas_other_has_it_in_start")
CHECK_EQ("boolean", toString(m[mkSymbol(e)])); CHECK_EQ("boolean", toString(m[mkSymbol(e)]));
} }
TEST_CASE("hashing_lvalue_global_prop_access") TEST_CASE_FIXTURE(LValueFixture, "hashing_lvalue_global_prop_access")
{ {
std::string t1 = "t"; std::string t1 = "t";
std::string x1 = "x"; std::string x1 = "x";
@ -154,13 +159,13 @@ TEST_CASE("hashing_lvalue_global_prop_access")
CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2)); CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2));
RefinementMap m; RefinementMap m;
m[t_x1] = getSingletonTypes().stringType; m[t_x1] = singletonTypes.stringType;
m[t_x2] = getSingletonTypes().numberType; m[t_x2] = singletonTypes.numberType;
CHECK_EQ(1, m.size()); CHECK_EQ(1, m.size());
} }
TEST_CASE("hashing_lvalue_local_prop_access") TEST_CASE_FIXTURE(LValueFixture, "hashing_lvalue_local_prop_access")
{ {
std::string t1 = "t"; std::string t1 = "t";
std::string x1 = "x"; std::string x1 = "x";
@ -183,8 +188,8 @@ TEST_CASE("hashing_lvalue_local_prop_access")
CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2)); CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2));
RefinementMap m; RefinementMap m;
m[t_x1] = getSingletonTypes().stringType; m[t_x1] = singletonTypes.stringType;
m[t_x2] = getSingletonTypes().numberType; m[t_x2] = singletonTypes.numberType;
CHECK_EQ(2, m.size()); CHECK_EQ(2, m.size());
} }

View file

@ -35,7 +35,7 @@ TEST_CASE_FIXTURE(Fixture, "UnknownGlobal")
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal") TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
{ {
// Normally this would be defined externally, so hack it in for testing // Normally this would be defined externally, so hack it in for testing
addGlobalBinding(typeChecker, "Wait", Binding{typeChecker.anyType, {}, true, "wait", "@test/global/Wait"}); addGlobalBinding(frontend, "Wait", Binding{typeChecker.anyType, {}, true, "wait", "@test/global/Wait"});
LintResult result = lintTyped("Wait(5)"); LintResult result = lintTyped("Wait(5)");
@ -49,7 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
// Normally this would be defined externally, so hack it in for testing // Normally this would be defined externally, so hack it in for testing
const char* deprecationReplacementString = ""; const char* deprecationReplacementString = "";
addGlobalBinding(typeChecker, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString}); addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});
LintResult result = lintTyped("Version()"); LintResult result = lintTyped("Version()");
@ -380,7 +380,7 @@ return bar()
TEST_CASE_FIXTURE(Fixture, "ImportUnused") TEST_CASE_FIXTURE(Fixture, "ImportUnused")
{ {
// Normally this would be defined externally, so hack it in for testing // Normally this would be defined externally, so hack it in for testing
addGlobalBinding(typeChecker, "game", typeChecker.anyType, "@test"); addGlobalBinding(frontend, "game", typeChecker.anyType, "@test");
LintResult result = lint(R"( LintResult result = lint(R"(
local Roact = require(game.Packages.Roact) local Roact = require(game.Packages.Roact)
@ -1464,7 +1464,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
getMutable<TableTypeVar>(colorType)->props = {{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"}}}; getMutable<TableTypeVar>(colorType)->props = {{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"}}};
addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}}); addGlobalBinding(frontend, "Color3", Binding{colorType, {}});
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
@ -1737,8 +1737,6 @@ local _ = 0x0xffffffffffffffffffffffffffffffffff
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence") TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
{ {
ScopedFastFlag sff("LuauLintComparisonPrecedence", true);
LintResult result = lint(R"( LintResult result = lint(R"(
local a, b = ... local a, b = ...

View file

@ -10,6 +10,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauLowerBoundsCalculation);
TEST_SUITE_BEGIN("ModuleTests"); TEST_SUITE_BEGIN("ModuleTests");
@ -134,7 +135,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
REQUIRE(signType != nullptr); REQUIRE(signType != nullptr);
CHECK(!isInArena(signType, module->interfaceTypes)); CHECK(!isInArena(signType, module->interfaceTypes));
CHECK(isInArena(signType, typeChecker.globalTypes)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(isInArena(signType, frontend.globalTypes));
else
CHECK(isInArena(signType, typeChecker.globalTypes));
} }
TEST_CASE_FIXTURE(Fixture, "deepClone_union") TEST_CASE_FIXTURE(Fixture, "deepClone_union")
@ -230,7 +234,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
{ {
TypeArena src; TypeArena src;
TypeId constrained = src.addType(ConstrainedTypeVar{TypeLevel{}, {getSingletonTypes().numberType, getSingletonTypes().stringType}}); TypeId constrained = src.addType(ConstrainedTypeVar{TypeLevel{}, {singletonTypes->numberType, singletonTypes->stringType}});
TypeArena dest; TypeArena dest;
CloneState cloneState; CloneState cloneState;
@ -240,8 +244,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(cloned); const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(cloned);
REQUIRE_EQ(2, ctv->parts.size()); REQUIRE_EQ(2, ctv->parts.size());
CHECK_EQ(getSingletonTypes().numberType, ctv->parts[0]); CHECK_EQ(singletonTypes->numberType, ctv->parts[0]);
CHECK_EQ(getSingletonTypes().stringType, ctv->parts[1]); CHECK_EQ(singletonTypes->stringType, ctv->parts[1]);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property") TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")

View file

@ -15,13 +15,13 @@ struct NormalizeFixture : Fixture
bool isSubtype(TypeId a, TypeId b) bool isSubtype(TypeId a, TypeId b)
{ {
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, ice); return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice);
} }
}; };
void createSomeClasses(TypeChecker& typeChecker) void createSomeClasses(Frontend& frontend)
{ {
auto& arena = typeChecker.globalTypes; auto& arena = frontend.globalTypes;
unfreeze(arena); unfreeze(arena);
@ -32,23 +32,23 @@ void createSomeClasses(TypeChecker& typeChecker)
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})}; parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
addGlobalBinding(typeChecker, "Parent", {parentType}); addGlobalBinding(frontend, "Parent", {parentType});
typeChecker.globalScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType}; frontend.getGlobalScope()->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
TypeId childType = arena.addType(ClassTypeVar{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"}); TypeId childType = arena.addType(ClassTypeVar{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
ClassTypeVar* childClass = getMutable<ClassTypeVar>(childType); ClassTypeVar* childClass = getMutable<ClassTypeVar>(childType);
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})}; childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
addGlobalBinding(typeChecker, "Child", {childType}); addGlobalBinding(frontend, "Child", {childType});
typeChecker.globalScope->exportedTypeBindings["Child"] = TypeFun{{}, childType}; frontend.getGlobalScope()->exportedTypeBindings["Child"] = TypeFun{{}, childType};
TypeId unrelatedType = arena.addType(ClassTypeVar{"Unrelated", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}); TypeId unrelatedType = arena.addType(ClassTypeVar{"Unrelated", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType}); addGlobalBinding(frontend, "Unrelated", {unrelatedType});
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType}; frontend.getGlobalScope()->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings) for (const auto& [name, ty] : frontend.getGlobalScope()->exportedTypeBindings)
persist(ty.type); persist(ty.type);
freeze(arena); freeze(arena);
@ -508,7 +508,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_table")
TEST_CASE_FIXTURE(NormalizeFixture, "classes") TEST_CASE_FIXTURE(NormalizeFixture, "classes")
{ {
createSomeClasses(typeChecker); createSomeClasses(frontend);
check(""); // Ensure that we have a main Module. check(""); // Ensure that we have a main Module.
@ -596,7 +596,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_with_overlapping_field_that_has_a_sub
)"); )");
ModulePtr tempModule{new Module}; ModulePtr tempModule{new Module};
tempModule->scopes.emplace_back(Location(), std::make_shared<Scope>(getSingletonTypes().anyTypePack)); tempModule->scopes.emplace_back(Location(), std::make_shared<Scope>(singletonTypes->anyTypePack));
// HACK: Normalization is an in-place operation. We need to cheat a little here and unfreeze // HACK: Normalization is an in-place operation. We need to cheat a little here and unfreeze
// the arena that the type lives in. // the arena that the type lives in.
@ -604,7 +604,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_with_overlapping_field_that_has_a_sub
unfreeze(mainModule->internalTypes); unfreeze(mainModule->internalTypes);
TypeId tType = requireType("t"); TypeId tType = requireType("t");
normalize(tType, tempModule, *typeChecker.iceHandler); normalize(tType, tempModule, singletonTypes, *typeChecker.iceHandler);
CHECK_EQ("{| x: number? |}", toString(tType, {true})); CHECK_EQ("{| x: number? |}", toString(tType, {true}));
} }
@ -1085,7 +1085,7 @@ TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types") TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
{ {
createSomeClasses(typeChecker); createSomeClasses(frontend);
CheckResult result = check(R"( CheckResult result = check(R"(
export type t0 = { a: Child } export type t0 = { a: Child }

View file

@ -1,3 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "doctest.h" #include "doctest.h"

View file

@ -76,8 +76,8 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
Location{{1, 21}, {1, 26}}, Location{{1, 21}, {1, 26}},
getMainSourceModule()->name, getMainSourceModule()->name,
TypeMismatch{ TypeMismatch{
getSingletonTypes().numberType, singletonTypes->numberType,
getSingletonTypes().stringType, singletonTypes->stringType,
}, },
}); });
} }
@ -87,8 +87,8 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
Location{{1, 8}, {1, 26}}, Location{{1, 8}, {1, 26}},
getMainSourceModule()->name, getMainSourceModule()->name,
TypeMismatch{ TypeMismatch{
getSingletonTypes().numberType, singletonTypes->numberType,
getSingletonTypes().stringType, singletonTypes->stringType,
}, },
}); });
} }
@ -501,7 +501,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
CheckResult result = check("type t10<x> = typeof(table)"); CheckResult result = check("type t10<x> = typeof(table)");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
TypeId ty = getGlobalBinding(frontend.typeChecker, "table"); TypeId ty = getGlobalBinding(frontend, "table");
CHECK_EQ(toString(ty), "table"); CHECK_EQ(toString(ty), "table");
const TableTypeVar* ttv = get<TableTypeVar>(ty); const TableTypeVar* ttv = get<TableTypeVar>(ty);

View file

@ -557,7 +557,7 @@ TEST_CASE_FIXTURE(Fixture, "cloned_interface_maintains_pointers_between_definiti
TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file") TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file")
{ {
addGlobalBinding(frontend.typeChecker, "script", frontend.typeChecker.anyType, "@test"); addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
fileResolver.source["Modules/Main"] = R"( fileResolver.source["Modules/Main"] = R"(
--!strict --!strict
@ -583,7 +583,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file")
TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type") TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type")
{ {
addGlobalBinding(frontend.typeChecker, "script", frontend.typeChecker.anyType, "@test"); addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
fileResolver.source["Modules/Main"] = R"( fileResolver.source["Modules/Main"] = R"(
--!strict --!strict
@ -609,7 +609,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type")
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_are_not_exported") TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_are_not_exported")
{ {
addGlobalBinding(frontend.typeChecker, "script", frontend.typeChecker.anyType, "@test"); addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
fileResolver.source["Modules/Main"] = R"( fileResolver.source["Modules/Main"] = R"(
--!strict --!strict

View file

@ -32,7 +32,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
}; };
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
addGlobalBinding(typeChecker, "BaseClass", baseClassType, "@test"); addGlobalBinding(frontend, "BaseClass", baseClassType, "@test");
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"}); TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
@ -45,7 +45,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
}; };
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
addGlobalBinding(typeChecker, "ChildClass", childClassType, "@test"); addGlobalBinding(frontend, "ChildClass", childClassType, "@test");
TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"}); TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"});
@ -58,7 +58,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
}; };
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType}; typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
addGlobalBinding(typeChecker, "GrandChild", childClassType, "@test"); addGlobalBinding(frontend, "GrandChild", childClassType, "@test");
TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"}); TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
@ -71,7 +71,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
}; };
typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType}; typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
addGlobalBinding(typeChecker, "AnotherChild", childClassType, "@test"); addGlobalBinding(frontend, "AnotherChild", childClassType, "@test");
TypeId vector2MetaType = arena.addType(TableTypeVar{}); TypeId vector2MetaType = arena.addType(TableTypeVar{});
@ -89,7 +89,7 @@ struct ClassFixture : BuiltinsFixture
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}}, {"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
}; };
typeChecker.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; typeChecker.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
addGlobalBinding(typeChecker, "Vector2", vector2Type, "@test"); addGlobalBinding(frontend, "Vector2", vector2Type, "@test");
for (const auto& [name, tf] : typeChecker.globalScope->exportedTypeBindings) for (const auto& [name, tf] : typeChecker.globalScope->exportedTypeBindings)
persist(tf.type); persist(tf.type);

View file

@ -19,13 +19,13 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
declare foo2: typeof(foo) declare foo2: typeof(foo)
)"); )");
TypeId globalFooTy = getGlobalBinding(frontend.typeChecker, "foo"); TypeId globalFooTy = getGlobalBinding(frontend, "foo");
CHECK_EQ(toString(globalFooTy), "number"); CHECK_EQ(toString(globalFooTy), "number");
TypeId globalBarTy = getGlobalBinding(frontend.typeChecker, "bar"); TypeId globalBarTy = getGlobalBinding(frontend, "bar");
CHECK_EQ(toString(globalBarTy), "(number) -> string"); CHECK_EQ(toString(globalBarTy), "(number) -> string");
TypeId globalFoo2Ty = getGlobalBinding(frontend.typeChecker, "foo2"); TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
CHECK_EQ(toString(globalFoo2Ty), "number"); CHECK_EQ(toString(globalFoo2Ty), "number");
CheckResult result = check(R"( CheckResult result = check(R"(
@ -48,20 +48,20 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
declare function var(...: any): string declare function var(...: any): string
)"); )");
TypeId globalFooTy = getGlobalBinding(frontend.typeChecker, "foo"); TypeId globalFooTy = getGlobalBinding(frontend, "foo");
CHECK_EQ(toString(globalFooTy), "number"); CHECK_EQ(toString(globalFooTy), "number");
std::optional<TypeFun> globalAsdfTy = frontend.typeChecker.globalScope->lookupType("Asdf"); std::optional<TypeFun> globalAsdfTy = frontend.getGlobalScope()->lookupType("Asdf");
REQUIRE(bool(globalAsdfTy)); REQUIRE(bool(globalAsdfTy));
CHECK_EQ(toString(globalAsdfTy->type), "number | string"); CHECK_EQ(toString(globalAsdfTy->type), "number | string");
TypeId globalBarTy = getGlobalBinding(frontend.typeChecker, "bar"); TypeId globalBarTy = getGlobalBinding(frontend, "bar");
CHECK_EQ(toString(globalBarTy), "(number) -> string"); CHECK_EQ(toString(globalBarTy), "(number) -> string");
TypeId globalFoo2Ty = getGlobalBinding(frontend.typeChecker, "foo2"); TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
CHECK_EQ(toString(globalFoo2Ty), "number"); CHECK_EQ(toString(globalFoo2Ty), "number");
TypeId globalVarTy = getGlobalBinding(frontend.typeChecker, "var"); TypeId globalVarTy = getGlobalBinding(frontend, "var");
CHECK_EQ(toString(globalVarTy), "(...any) -> string"); CHECK_EQ(toString(globalVarTy), "(...any) -> string");
@ -85,7 +85,7 @@ TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_sc
freeze(typeChecker.globalTypes); freeze(typeChecker.globalTypes);
REQUIRE(!parseFailResult.success); REQUIRE(!parseFailResult.success);
std::optional<Binding> fooTy = tryGetGlobalBinding(typeChecker, "foo"); std::optional<Binding> fooTy = tryGetGlobalBinding(frontend, "foo");
CHECK(!fooTy.has_value()); CHECK(!fooTy.has_value());
LoadDefinitionFileResult checkFailResult = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"( LoadDefinitionFileResult checkFailResult = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
@ -95,7 +95,7 @@ TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_sc
"@test"); "@test");
REQUIRE(!checkFailResult.success); REQUIRE(!checkFailResult.success);
std::optional<Binding> barTy = tryGetGlobalBinding(typeChecker, "bar"); std::optional<Binding> barTy = tryGetGlobalBinding(frontend, "bar");
CHECK(!barTy.has_value()); CHECK(!barTy.has_value());
} }

View file

@ -127,6 +127,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
return T return T
)"); )");
LUAU_REQUIRE_NO_ERRORS(result);
auto r = first(getMainModule()->getModuleScope()->returnType); auto r = first(getMainModule()->getModuleScope()->returnType);
REQUIRE(r); REQUIRE(r);
@ -136,8 +138,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
REQUIRE(ttv->props.count("f")); REQUIRE(ttv->props.count("f"));
TypeId k = ttv->props["f"].type; TypeId k = ttv->props["f"].type;
REQUIRE(k); REQUIRE(k);
LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count")

View file

@ -102,4 +102,18 @@ end
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'string'"); CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'string'");
} }
TEST_CASE("singleton_types")
{
BuiltinsFixture a;
{
BuiltinsFixture b;
}
// Check that Frontend 'a' environment wasn't modified by 'b'
CheckResult result = a.check("local s: string = 'hello' local t = s:lower()");
CHECK(result.errors.empty());
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -353,6 +353,9 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", false}, {"LuauLowerBoundsCalculation", false},
// I'm not sure why this is broken without DCR, but it seems to be fixed
// when DCR is enabled.
{"DebugLuauDeferredConstraintResolution", false},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -367,6 +370,9 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", false}, {"LuauLowerBoundsCalculation", false},
// I'm not sure why this is broken without DCR, but it seems to be fixed
// when DCR is enabled.
{"DebugLuauDeferredConstraintResolution", false},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -588,9 +594,9 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
}; };
TypeArena arena; TypeArena arena;
TypeId nilType = getSingletonTypes().nilType; TypeId nilType = singletonTypes->nilType;
std::unique_ptr scope = std::make_unique<Scope>(getSingletonTypes().anyTypePack); std::unique_ptr scope = std::make_unique<Scope>(singletonTypes->anyTypePack);
TypeId free1 = arena.addType(FreeTypePack{scope.get()}); TypeId free1 = arena.addType(FreeTypePack{scope.get()});
TypeId option1 = arena.addType(UnionTypeVar{{nilType, free1}}); TypeId option1 = arena.addType(UnionTypeVar{{nilType, free1}});
@ -600,7 +606,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState sharedState{&iceHandler}; UnifierSharedState sharedState{&iceHandler};
Unifier u{&arena, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant, sharedState}; Unifier u{&arena, singletonTypes, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant, sharedState};
u.tryUnify(option1, option2); u.tryUnify(option1, option2);

View file

@ -50,7 +50,7 @@ struct RefinementClassFixture : Fixture
{"Y", Property{typeChecker.numberType}}, {"Y", Property{typeChecker.numberType}},
{"Z", Property{typeChecker.numberType}}, {"Z", Property{typeChecker.numberType}},
}; };
normalize(vec3, scope, arena, *typeChecker.iceHandler); normalize(vec3, scope, arena, singletonTypes, *typeChecker.iceHandler);
TypeId inst = arena.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}); TypeId inst = arena.addType(ClassTypeVar{"Instance", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
@ -58,21 +58,21 @@ struct RefinementClassFixture : Fixture
TypePackId isARets = arena.addTypePack({typeChecker.booleanType}); TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
TypeId isA = arena.addType(FunctionTypeVar{isAParams, isARets}); TypeId isA = arena.addType(FunctionTypeVar{isAParams, isARets});
getMutable<FunctionTypeVar>(isA)->magicFunction = magicFunctionInstanceIsA; getMutable<FunctionTypeVar>(isA)->magicFunction = magicFunctionInstanceIsA;
normalize(isA, scope, arena, *typeChecker.iceHandler); normalize(isA, scope, arena, singletonTypes, *typeChecker.iceHandler);
getMutable<ClassTypeVar>(inst)->props = { getMutable<ClassTypeVar>(inst)->props = {
{"Name", Property{typeChecker.stringType}}, {"Name", Property{typeChecker.stringType}},
{"IsA", Property{isA}}, {"IsA", Property{isA}},
}; };
normalize(inst, scope, arena, *typeChecker.iceHandler); normalize(inst, scope, arena, singletonTypes, *typeChecker.iceHandler);
TypeId folder = typeChecker.globalTypes.addType(ClassTypeVar{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"}); TypeId folder = typeChecker.globalTypes.addType(ClassTypeVar{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"});
normalize(folder, scope, arena, *typeChecker.iceHandler); normalize(folder, scope, arena, singletonTypes, *typeChecker.iceHandler);
TypeId part = typeChecker.globalTypes.addType(ClassTypeVar{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"}); TypeId part = typeChecker.globalTypes.addType(ClassTypeVar{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"});
getMutable<ClassTypeVar>(part)->props = { getMutable<ClassTypeVar>(part)->props = {
{"Position", Property{vec3}}, {"Position", Property{vec3}},
}; };
normalize(part, scope, arena, *typeChecker.iceHandler); normalize(part, scope, arena, singletonTypes, *typeChecker.iceHandler);
typeChecker.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3}; typeChecker.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst}; typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};

View file

@ -18,7 +18,7 @@ struct TryUnifyFixture : Fixture
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState unifierState{&iceHandler}; UnifierSharedState unifierState{&iceHandler};
Unifier state{&arena, Mode::Strict, NotNull{globalScope.get()}, Location{}, Variance::Covariant, unifierState}; Unifier state{&arena, singletonTypes, Mode::Strict, NotNull{globalScope.get()}, Location{}, Variance::Covariant, unifierState};
}; };
TEST_SUITE_BEGIN("TryUnifyTests"); TEST_SUITE_BEGIN("TryUnifyTests");

View file

@ -194,7 +194,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs")
TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.stringType}}); TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.stringType}});
// clang-format off // clang-format off
addGlobalBinding(typeChecker, "foo", addGlobalBinding(frontend, "foo",
arena.addType( arena.addType(
FunctionTypeVar{ FunctionTypeVar{
listOfNumbers, listOfNumbers,
@ -203,7 +203,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs")
), ),
"@test" "@test"
); );
addGlobalBinding(typeChecker, "bar", addGlobalBinding(frontend, "bar",
arena.addType( arena.addType(
FunctionTypeVar{ FunctionTypeVar{
arena.addTypePack({{typeChecker.numberType}, listOfStrings}), arena.addTypePack({{typeChecker.numberType}, listOfStrings}),

View file

@ -273,7 +273,7 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
TypeId root = &ttvTweenResult; TypeId root = &ttvTweenResult;
typeChecker.currentModule = std::make_shared<Module>(); typeChecker.currentModule = std::make_shared<Module>();
typeChecker.currentModule->scopes.emplace_back(Location{}, std::make_shared<Scope>(getSingletonTypes().anyTypePack)); typeChecker.currentModule->scopes.emplace_back(Location{}, std::make_shared<Scope>(singletonTypes->anyTypePack));
TypeId result = typeChecker.anyify(typeChecker.globalScope, root, Location{}); TypeId result = typeChecker.anyify(typeChecker.globalScope, root, Location{});

View file

@ -10,9 +10,6 @@ local ignore =
-- what follows is a set of mismatches that hopefully eventually will go down to 0 -- what follows is a set of mismatches that hopefully eventually will go down to 0
"_G.require", -- need to move to Roblox type defs "_G.require", -- need to move to Roblox type defs
"_G.utf8.nfcnormalize", -- need to move to Roblox type defs
"_G.utf8.nfdnormalize", -- need to move to Roblox type defs
"_G.utf8.graphemes", -- need to move to Roblox type defs
} }
function verify(real, rtti, path) function verify(real, rtti, path)

View file

@ -163,6 +163,7 @@ BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2 BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.class_definition_overload_metamethods
DefinitionTests.declaring_generic_functions DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes DefinitionTests.definition_file_classes
FrontendTest.ast_node_at_position FrontendTest.ast_node_at_position
@ -199,7 +200,6 @@ 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
GenericsTests.generic_type_pack_parentheses GenericsTests.generic_type_pack_parentheses
GenericsTests.generic_type_pack_syntax
GenericsTests.generic_type_pack_unification1 GenericsTests.generic_type_pack_unification1
GenericsTests.generic_type_pack_unification2 GenericsTests.generic_type_pack_unification2
GenericsTests.generic_type_pack_unification3 GenericsTests.generic_type_pack_unification3
@ -300,10 +300,8 @@ ProvisionalTests.operator_eq_completely_incompatible
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
ProvisionalTests.setmetatable_constrains_free_type_into_free_table ProvisionalTests.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.typeguard_inference_incomplete
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
@ -494,13 +492,10 @@ ToString.function_type_with_argument_names_generic
ToString.no_parentheses_around_cyclic_function_type_in_union ToString.no_parentheses_around_cyclic_function_type_in_union
ToString.toStringDetailed2 ToString.toStringDetailed2
ToString.toStringErrorPack ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack
ToString.toStringNamedFunction_hide_type_params ToString.toStringNamedFunction_hide_type_params
ToString.toStringNamedFunction_id ToString.toStringNamedFunction_id
ToString.toStringNamedFunction_map ToString.toStringNamedFunction_map
ToString.toStringNamedFunction_overrides_param_names
ToString.toStringNamedFunction_variadics ToString.toStringNamedFunction_variadics
TranspilerTests.type_lists_should_be_emitted_correctly
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
@ -616,7 +611,6 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments 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
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_with_custom_iterator TypeInferLoops.for_in_loop_with_custom_iterator
TypeInferLoops.for_in_loop_with_next TypeInferLoops.for_in_loop_with_next
@ -641,7 +635,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.methods_are_topologically_sorted TypeInferOOP.methods_are_topologically_sorted
TypeInferOOP.nonstrict_self_mismatch_tail
TypeInferOperators.and_adds_boolean TypeInferOperators.and_adds_boolean
TypeInferOperators.and_adds_boolean_no_superfluous_union TypeInferOperators.and_adds_boolean_no_superfluous_union
TypeInferOperators.and_binexps_dont_unify TypeInferOperators.and_binexps_dont_unify
@ -689,6 +682,7 @@ TypeInferOperators.unary_not_is_boolean
TypeInferOperators.unknown_type_in_comparison TypeInferOperators.unknown_type_in_comparison
TypeInferOperators.UnknownGlobalCompoundAssign TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.singleton_types
TypeInferPrimitives.string_function_other TypeInferPrimitives.string_function_other
TypeInferPrimitives.string_index TypeInferPrimitives.string_index
TypeInferPrimitives.string_length TypeInferPrimitives.string_length
@ -730,7 +724,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_pack_syntax
TypePackTests.variadic_packs TypePackTests.variadic_packs
TypeSingletons.bool_singleton_subtype TypeSingletons.bool_singleton_subtype
TypeSingletons.bool_singletons TypeSingletons.bool_singletons