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
struct Anyification : Substitution
{
Anyification(TypeArena* arena, NotNull<Scope> scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack);
Anyification(TypeArena* arena, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler, TypeId anyType,
TypePackId anyTypePack);
Anyification(TypeArena* arena, const ScopePtr& scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler, TypeId anyType,
TypePackId anyTypePack);
NotNull<Scope> scope;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter* iceHandler;
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
#pragma once
#include "Luau/Frontend.h"
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
@ -8,6 +9,7 @@ namespace Luau
{
void registerBuiltinTypes(TypeChecker& typeChecker);
void registerBuiltinTypes(Frontend& frontend);
TypeId makeUnion(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'
*/
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++.
*/
@ -41,12 +44,17 @@ void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::strin
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, 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);
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);
TypeId getGlobalBinding(Frontend& frontend, const std::string& name);
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name);
} // namespace Luau

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
#include <type_traits>
#include <string>
#include <optional>
#include <unordered_map>
#include <vector>
#include "Luau/NotNull.h"
@ -232,4 +233,15 @@ void write(JsonEmitter& emitter, const std::optional<T>& v)
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

View File

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

View File

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

View File

@ -4,10 +4,14 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
#include "Luau/NotNull.h"
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

View File

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

View File

@ -15,10 +15,12 @@ struct TxnLog;
using ScopePtr = std::shared_ptr<struct Scope>;
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const std::string& entry, Location location);
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, const std::string& name, Location location);
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const Location& location, bool addErrors, InternalErrorReporter& handle);
std::optional<TypeId> findMetatableEntry(
NotNull<SingletonTypes> singletonTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
std::optional<TypeId> findTablePropertyRespectingMeta(
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.
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
bool maybeString(TypeId ty);
std::optional<TypeId> getMetatable(TypeId type);
std::optional<TypeId> getMetatable(TypeId type, NotNull<struct SingletonTypes> singletonTypes);
TableTypeVar* getMutableTableType(TypeId type);
const TableTypeVar* getTableType(TypeId type);
@ -614,21 +614,6 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount);
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(const SingletonTypes&) = delete;
@ -644,9 +629,28 @@ private:
bool debugFreezeArena = false;
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(TypePackId tp);

View File

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

View File

@ -11,17 +11,20 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
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)
, scope(scope)
, singletonTypes(singletonTypes)
, iceHandler(iceHandler)
, anyType(anyType)
, anyTypePack(anyTypePack)
{
}
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
: Anyification(arena, NotNull{scope.get()}, iceHandler, anyType, anyTypePack)
Anyification::Anyification(TypeArena* arena, const ScopePtr& scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler,
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)
ty = replace(ty);
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)
normalizationTooComplex = true;
return t;

View File

@ -14,8 +14,6 @@
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFixGlobalOrder, false)
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"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;
}
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;
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();
}
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);
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);
InternalErrorReporter 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);
bool ok = unifier.errors.empty();
@ -171,11 +170,11 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
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 (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena);
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, singletonTypes);
return false;
}
@ -214,7 +213,8 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
}
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
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
}
@ -226,8 +226,8 @@ enum class PropIndexType
Key,
};
static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId rootTy, TypeId ty, PropIndexType indexType,
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId rootTy, TypeId ty,
PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> containingClass = std::nullopt)
{
if (FFlag::LuauSelfCallAutocompleteFix3)
@ -272,7 +272,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
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);
if (indexType == PropIndexType::Key)
@ -280,7 +280,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
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
if (calledWithSelf == ftv->hasSelf)
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
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;
}
@ -327,8 +327,9 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (result.count(name) == 0 && name != kParseNameError)
{
Luau::TypeId type = Luau::follow(prop.type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key
? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, singletonTypes, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens =
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);
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))
{
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
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);
fillProps(cls->props);
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))
fillProps(tbl->props);
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)
{
@ -395,12 +396,12 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{
TypeId followed = follow(indexIt->second.type);
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))
{
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
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;
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)
result.insert(pair);
@ -436,7 +437,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (iter == endIter)
return;
autocompleteProps(module, typeArena, rootTy, *iter, indexType, nodes, result, seen);
autocompleteProps(module, typeArena, singletonTypes, rootTy, *iter, indexType, nodes, result, seen);
++iter;
@ -454,7 +455,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
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;
@ -481,7 +482,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
}
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(
const Module& module, TypeArena* typeArena, TypeId ty, PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result)
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId ty, PropIndexType indexType,
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result)
{
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(
const Module& module, TypeArena* typeArena, TypeId ty, PropIndexType indexType, const std::vector<AstNode*>& nodes)
AutocompleteEntryMap autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<SingletonTypes> singletonTypes, TypeId ty,
PropIndexType indexType, const std::vector<AstNode*>& nodes)
{
AutocompleteEntryMap result;
autocompleteProps(module, typeArena, ty, indexType, nodes, result);
autocompleteProps(module, typeArena, singletonTypes, ty, indexType, nodes, 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)
{
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
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;
}
// 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);
}
static AutocompleteEntryMap autocompleteStatement(
@ -1220,12 +1213,14 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
{
LUAU_ASSERT(!ancestry.empty());
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
AstNode* node = ancestry.rbegin()[0];
if (node->is<AstExprIndexName>())
{
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))
return AutocompleteContext::Keyword;
@ -1249,7 +1244,7 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
std::string n = toString(name);
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,
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
@ -1259,9 +1254,9 @@ static AutocompleteContext autocompleteExpression(const SourceModule& sourceModu
scope = scope->parent;
}
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, node, position, typeChecker.nilType);
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, node, position, getSingletonTypes().trueType);
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, node, position, getSingletonTypes().falseType);
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, typeChecker.nilType);
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->trueType);
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, singletonTypes, node, position, singletonTypes->falseType);
TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
@ -1396,6 +1391,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (isWithinComment(sourceModule, position))
return {};
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
LUAU_ASSERT(!ancestry.empty());
AstNode* node = ancestry.back();
@ -1422,10 +1419,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry,
AutocompleteContext::Property};
return {autocompleteProps(
*module, typeArena, singletonTypes, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
ancestry, AutocompleteContext::Property};
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>())
{
@ -1548,7 +1546,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
{
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
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 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>())
{

View File

@ -6,6 +6,7 @@
#include "Luau/Common.h"
#include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/TypeInfer.h"
#include <algorithm>
@ -45,6 +46,11 @@ TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& 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)
{
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)
{
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)
{
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)
{
std::string documentationSymbol = packageName + "/global/" + name;
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)
{
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)
{
AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str());
@ -166,6 +188,23 @@ std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::
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)
{
AstName astName = typeChecker.globalNames.names->get(name.c_str());
@ -195,6 +234,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId nilType = typeChecker.nilType;
TypeArena& arena = typeChecker.globalTypes;
NotNull<SingletonTypes> singletonTypes = typeChecker.singletonTypes;
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
LUAU_ASSERT(loadResult.success);
@ -203,7 +243,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId genericV = arena.addType(GenericTypeVar{"V"});
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);
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable);
@ -277,6 +317,98 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
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(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{

View File

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

View File

@ -9,6 +9,7 @@
#include "Luau/Quantify.h"
#include "Luau/ToString.h"
#include "Luau/Unifier.h"
#include "Luau/DcrLogger.h"
#include "Luau/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
@ -50,8 +51,8 @@ static void dumpConstraints(NotNull<Scope> scope, ToStringOptions& opts)
dumpConstraints(child, opts);
}
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(
const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments, TypeArena* arena)
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)
{
std::vector<TypeId> saturatedTypeArguments;
std::vector<TypeId> extraTypes;
@ -131,7 +132,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
if (!defaultTy)
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;
saturatedTypeArguments.push_back(instantiatedDefault);
}
@ -149,7 +150,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
if (!defaultTp)
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;
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.
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)
{
saturatedPackArguments.push_back(getSingletonTypes().errorRecoveryTypePack());
saturatedPackArguments.push_back(singletonTypes->errorRecoveryTypePack());
}
// 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,
std::vector<RequireCycle> requireCycles)
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<SingletonTypes> singletonTypes, NotNull<Scope> rootScope, ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
: arena(arena)
, singletonTypes(singletonTypes)
, constraints(collectConstraints(rootScope))
, rootScope(rootScope)
, currentModuleName(std::move(moduleName))
, moduleResolver(moduleResolver)
, requireCycles(requireCycles)
, logger(logger)
{
opts.exhaustive = true;
@ -262,6 +265,9 @@ ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope, M
block(dep, c);
}
}
if (FFlag::DebugLuauLogSolverToJson)
LUAU_ASSERT(logger);
}
void ConstraintSolver::run()
@ -277,7 +283,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson)
{
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
logger->captureInitialSolverState(rootScope, unsolvedConstraints);
}
auto runSolverPass = [&](bool force) {
@ -294,10 +300,11 @@ void ConstraintSolver::run()
}
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{};
StepSnapshot snapshot;
if (FFlag::DebugLuauLogSolverToJson)
{
logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints, force);
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
}
bool success = tryDispatch(c, force);
@ -311,7 +318,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson)
{
logger.commitPreparedStepSnapshot();
logger->commitStepSnapshot(snapshot);
}
if (FFlag::DebugLuauLogSolver)
@ -347,8 +354,7 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolverToJson)
{
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
printf("Logger output:\n%s\n", logger.compileOutput().c_str());
logger->captureFinalSolverState(rootScope, unsolvedConstraints);
}
}
@ -516,7 +522,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
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});
return true;
}
@ -571,7 +577,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
if (0 == iteratorTypes.size())
{
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);
LUAU_ASSERT(anyified);
unify(*anyified, c.variables, constraint->scope);
@ -585,11 +591,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
if (get<FunctionTypeVar>(nextTy))
{
TypeId tableTy = getSingletonTypes().nilType;
TypeId tableTy = singletonTypes->nilType;
if (iteratorTypes.size() >= 2)
tableTy = iteratorTypes[1];
TypeId firstIndexTy = getSingletonTypes().nilType;
TypeId firstIndexTy = singletonTypes->nilType;
if (iteratorTypes.size() >= 3)
firstIndexTy = iteratorTypes[2];
@ -644,7 +650,7 @@ struct InfiniteTypeFinder : TypeVarOnceVisitor
if (!tf.has_value())
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))
{
@ -698,7 +704,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (!tf.has_value())
{
reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
bindResult(getSingletonTypes().errorRecoveryType());
bindResult(singletonTypes->errorRecoveryType());
return true;
}
@ -710,7 +716,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
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) {
return itp == p.ty;
@ -757,7 +763,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (itf.foundInfiniteType)
{
// TODO (CLI-56761): Report an error.
bindResult(getSingletonTypes().errorRecoveryType());
bindResult(singletonTypes->errorRecoveryType());
return true;
}
@ -780,7 +786,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (!maybeInstantiated.has_value())
{
// TODO (CLI-56761): Report an error.
bindResult(getSingletonTypes().errorRecoveryType());
bindResult(singletonTypes->errorRecoveryType());
return true;
}
@ -894,7 +900,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
return block_(iteratorTy);
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);
if (!anyified)
reportError(CodeTooComplex{}, constraint->location);
@ -904,7 +910,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
auto errorify = [&](auto ty) {
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);
if (!errorified)
reportError(CodeTooComplex{}, constraint->location);
@ -973,7 +979,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
: firstIndexTy;
// 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 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)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver)
printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
}
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
return false;
}
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->pushBlock(constraint, target);
if (FFlag::DebugLuauLogSolver)
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
block_(target, constraint);
return false;
}
@ -1042,16 +1060,25 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(progressed);
return unblock_(progressed);
}
void ConstraintSolver::unblock(TypeId progressed)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(progressed);
return unblock_(progressed);
}
void ConstraintSolver::unblock(TypePackId progressed)
{
if (FFlag::DebugLuauLogSolverToJson)
logger->popBlock(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)
{
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);
if (!u.errors.empty())
{
TypeId errorType = getSingletonTypes().errorRecoveryType();
TypeId errorType = singletonTypes->errorRecoveryType();
u.tryUnify(subType, 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)
{
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);
@ -1133,7 +1160,7 @@ TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& l
if (info.name.empty())
{
reportError(UnknownRequire{}, location);
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
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)
{
if (!path.empty() && path.front() == humanReadableName)
return getSingletonTypes().anyType;
return singletonTypes->anyType;
}
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)
reportError(UnknownRequire{humanReadableName}, location);
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
if (module->type != SourceCode::Type::Module)
{
reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
TypePackId modulePack = module->getModuleScope()->returnType;
if (get<Unifiable::Error>(modulePack))
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType)
{
reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location);
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
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,
charpattern: string,
codes: (string) -> ((string, number) -> (number, number), string, number),
-- FIXME
codepoint: (string, number?, number?) -> (number, ...number),
codepoint: (string, number?, number?) -> ...number,
len: (string, number?, 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.

View File

@ -6,6 +6,7 @@
#include "Luau/Config.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DcrLogger.h"
#include "Luau/FileResolver.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
@ -23,10 +24,12 @@
LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
namespace Luau
{
@ -389,11 +392,12 @@ double getTimestamp()
} // namespace
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
: fileResolver(fileResolver)
: singletonTypes(NotNull{FFlag::LuauNoMoreGlobalSingletonTypes ? &singletonTypes_ : &DEPRECATED_getSingletonTypes()})
, fileResolver(fileResolver)
, moduleResolver(this)
, moduleResolverForAutocomplete(this)
, typeChecker(&moduleResolver, &iceHandler)
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, &iceHandler)
, typeChecker(&moduleResolver, singletonTypes, &iceHandler)
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, singletonTypes, &iceHandler)
, configResolver(configResolver)
, options(options)
{
@ -837,11 +841,22 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
{
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);
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();
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->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;
}

View File

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

View File

@ -92,10 +92,12 @@ struct ForceNormal : TypeVarOnceVisitor
struct ClonePublicInterface : Substitution
{
NotNull<SingletonTypes> singletonTypes;
NotNull<Module> module;
ClonePublicInterface(const TxnLog* log, Module* module)
ClonePublicInterface(const TxnLog* log, NotNull<SingletonTypes> singletonTypes, Module* module)
: Substitution(log, &module->interfaceTypes)
, singletonTypes(singletonTypes)
, module(module)
{
LUAU_ASSERT(module);
@ -147,7 +149,7 @@ struct ClonePublicInterface : Substitution
else
{
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
}
@ -163,7 +165,7 @@ struct ClonePublicInterface : Substitution
else
{
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
return getSingletonTypes().errorRecoveryTypePack();
return singletonTypes->errorRecoveryTypePack();
}
}
@ -208,7 +210,7 @@ Module::~Module()
unfreeze(internalTypes);
}
void Module::clonePublicInterface(InternalErrorReporter& ice)
void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
{
LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty());
@ -222,7 +224,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
TxnLog log;
ClonePublicInterface clonePublicInterface{&log, this};
ClonePublicInterface clonePublicInterface{&log, singletonTypes, this};
if (FFlag::LuauClonePublicInterfaceLess)
returnType = clonePublicInterface.cloneTypePack(returnType);
@ -243,12 +245,12 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(returnType, NotNull{this}, ice);
normalize(returnType, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(returnType);
if (varargPack)
{
normalize(*varargPack, NotNull{this}, ice);
normalize(*varargPack, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(*varargPack);
}
@ -264,7 +266,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
tf = clone(tf, interfaceTypes, cloneState);
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
// 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)
{
normalize(*param.defaultValue, NotNull{this}, ice);
normalize(*param.defaultValue, NotNull{this}, singletonTypes, ice);
forceNormal.traverse(*param.defaultValue);
}
}
@ -301,7 +303,7 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
ty = clone(ty, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(ty, NotNull{this}, ice);
normalize(ty, NotNull{this}, singletonTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(ty);

View File

@ -54,11 +54,11 @@ struct Replacer
} // 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};
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.tryUnify(subTy, superTy);
@ -66,11 +66,11 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, InternalError
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};
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.tryUnify(subPack, superPack);
@ -133,15 +133,17 @@ struct Normalize final : TypeVarVisitor
{
using TypeVarVisitor::Set;
Normalize(TypeArena& arena, NotNull<Scope> scope, InternalErrorReporter& ice)
Normalize(TypeArena& arena, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
: arena(arena)
, scope(scope)
, singletonTypes(singletonTypes)
, ice(ice)
{
}
TypeArena& arena;
NotNull<Scope> scope;
NotNull<SingletonTypes> singletonTypes;
InternalErrorReporter& ice;
int iterationLimit = 0;
@ -499,9 +501,9 @@ struct Normalize final : TypeVarVisitor
for (TypeId& part : result)
{
if (isSubtype(ty, part, scope, ice))
if (isSubtype(ty, part, scope, singletonTypes, ice))
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
return;
@ -553,12 +555,12 @@ struct Normalize final : TypeVarVisitor
bool merged = false;
for (TypeId& part : result->parts)
{
if (isSubtype(part, ty, scope, ice))
if (isSubtype(part, ty, scope, singletonTypes, ice))
{
merged = true;
break; // no need to do anything
}
else if (isSubtype(ty, part, scope, ice))
else if (isSubtype(ty, part, scope, singletonTypes, ice))
{
merged = true;
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)
*/
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;
if (FFlag::DebugLuauCopyBeforeNormalizing)
(void)clone(ty, arena, state);
Normalize n{arena, scope, ice};
Normalize n{arena, scope, singletonTypes, ice};
n.traverse(ty);
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.
// The main wrinkle here is that we don't want clone() to copy a type if the source and dest
// 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)
*/
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;
if (FFlag::DebugLuauCopyBeforeNormalizing)
(void)clone(tp, arena, state);
Normalize n{arena, scope, ice};
Normalize n{arena, scope, singletonTypes, ice};
n.traverse(tp);
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

View File

@ -40,6 +40,15 @@ TypeId TypeArena::freshType(Scope* scope)
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 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 <algorithm>
#include "Luau/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Clone.h"
@ -13,6 +11,12 @@
#include "Luau/TypeUtils.h"
#include "Luau/TypeVar.h"
#include "Luau/Unifier.h"
#include "Luau/ToString.h"
#include "Luau/DcrLogger.h"
#include <algorithm>
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
namespace Luau
{
@ -54,18 +58,22 @@ struct StackPusher
struct TypeChecker2
{
NotNull<SingletonTypes> singletonTypes;
DcrLogger* logger;
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
const SourceModule* sourceModule;
Module* module;
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
SingletonTypes& singletonTypes;
std::vector<NotNull<Scope>> stack;
TypeChecker2(const SourceModule* sourceModule, Module* module)
: sourceModule(sourceModule)
TypeChecker2(NotNull<SingletonTypes> singletonTypes, DcrLogger* logger, const SourceModule* sourceModule, Module* module)
: singletonTypes(singletonTypes)
, logger(logger)
, sourceModule(sourceModule)
, module(module)
, singletonTypes(getSingletonTypes())
{
if (FFlag::DebugLuauLogSolverToJson)
LUAU_ASSERT(logger);
}
std::optional<StackPusher> pushStack(AstNode* node)
@ -85,7 +93,7 @@ struct TypeChecker2
if (tp)
return follow(*tp);
else
return singletonTypes.anyTypePack;
return singletonTypes->anyTypePack;
}
TypeId lookupType(AstExpr* expr)
@ -101,7 +109,7 @@ struct TypeChecker2
if (tp)
return flattenPack(*tp);
return singletonTypes.anyType;
return singletonTypes->anyType;
}
TypeId lookupAnnotation(AstType* annotation)
@ -253,7 +261,7 @@ struct TypeChecker2
TypePackId actualRetType = reconstructPack(ret->list, arena);
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.tryUnify(actualRetType, expectedRetType);
@ -299,7 +307,7 @@ struct TypeChecker2
if (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);
}
@ -317,7 +325,7 @@ struct TypeChecker2
if (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);
}
@ -340,7 +348,7 @@ struct TypeChecker2
// "Render" a type pack out to an array of a given length. Expands
// variadics and various other things to get there.
static std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
std::vector<TypeId> flatten(TypeArena& arena, TypePackId pack, size_t length)
{
std::vector<TypeId> result;
@ -376,7 +384,7 @@ struct TypeChecker2
else if (auto etp = get<Unifiable::Error>(tail))
{
while (result.size() < length)
result.push_back(getSingletonTypes().errorRecoveryType());
result.push_back(singletonTypes->errorRecoveryType());
}
return result;
@ -532,7 +540,7 @@ struct TypeChecker2
visit(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);
}
@ -681,9 +689,9 @@ struct TypeChecker2
void visit(AstExprConstantNumber* 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);
}
@ -692,9 +700,9 @@ struct TypeChecker2
void visit(AstExprConstantString* 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);
}
@ -754,7 +762,7 @@ struct TypeChecker2
FunctionTypeVar ftv{argsTp, expectedRetType};
TypeId expectedType = arena.addType(ftv);
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), ice))
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), singletonTypes, ice))
{
CloneState cloneState;
expectedType = clone(expectedType, module->internalTypes, cloneState);
@ -773,7 +781,7 @@ struct TypeChecker2
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
if (ty)
{
if (!isSubtype(resultType, *ty, stack.back(), ice))
if (!isSubtype(resultType, *ty, stack.back(), singletonTypes, ice))
{
reportError(TypeMismatch{resultType, *ty}, indexName->location);
}
@ -806,7 +814,7 @@ struct TypeChecker2
TypeId inferredArgTy = *argIt;
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);
}
@ -851,10 +859,10 @@ struct TypeChecker2
TypeId computedType = lookupType(expr->expr);
// 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;
if (isSubtype(computedType, annotationType, stack.back(), ice))
if (isSubtype(computedType, annotationType, stack.back(), singletonTypes, ice))
return;
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
@ -908,7 +916,7 @@ struct TypeChecker2
return result;
}
else if (get<Unifiable::Error>(pack))
return singletonTypes.errorRecoveryType();
return singletonTypes->errorRecoveryType();
else
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)
{
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.tryUnify(subTy, superTy);
@ -1164,6 +1172,9 @@ struct TypeChecker2
void reportError(TypeErrorData data, const Location& location)
{
module->errors.emplace_back(location, sourceModule->name, std::move(data));
if (FFlag::DebugLuauLogSolverToJson)
logger->captureTypeCheckError(module->errors.back());
}
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)
{
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);
}

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);
}
TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler)
TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter* iceHandler)
: resolver(resolver)
, singletonTypes(singletonTypes)
, iceHandler(iceHandler)
, unifierState(iceHandler)
, nilType(getSingletonTypes().nilType)
, numberType(getSingletonTypes().numberType)
, stringType(getSingletonTypes().stringType)
, booleanType(getSingletonTypes().booleanType)
, threadType(getSingletonTypes().threadType)
, anyType(getSingletonTypes().anyType)
, unknownType(getSingletonTypes().unknownType)
, neverType(getSingletonTypes().neverType)
, anyTypePack(getSingletonTypes().anyTypePack)
, neverTypePack(getSingletonTypes().neverTypePack)
, uninhabitableTypePack(getSingletonTypes().uninhabitableTypePack)
, nilType(singletonTypes->nilType)
, numberType(singletonTypes->numberType)
, stringType(singletonTypes->stringType)
, booleanType(singletonTypes->booleanType)
, threadType(singletonTypes->threadType)
, anyType(singletonTypes->anyType)
, unknownType(singletonTypes->unknownType)
, neverType(singletonTypes->neverType)
, anyTypePack(singletonTypes->anyTypePack)
, neverTypePack(singletonTypes->neverTypePack)
, uninhabitableTypePack(singletonTypes->uninhabitableTypePack)
, duplicateTypeAliases{{false, {}}}
{
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
@ -357,7 +358,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
prepareErrorsForDisplay(currentModule->errors);
currentModule->clonePublicInterface(*iceHandler);
currentModule->clonePublicInterface(singletonTypes, *iceHandler);
// 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.
@ -1606,7 +1607,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (FFlag::LuauLowerBoundsCalculation)
{
auto [t, ok] = normalize(bindingType, currentModule, *iceHandler);
auto [t, ok] = normalize(bindingType, currentModule, singletonTypes, *iceHandler);
bindingType = t;
if (!ok)
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)
{
ErrorVec errors;
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
auto result = Luau::findTablePropertyRespectingMeta(singletonTypes, errors, lhsType, name, location);
if (addErrors)
reportErrors(errors);
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)
{
ErrorVec errors;
auto result = Luau::findMetatableEntry(errors, type, entry, location);
auto result = Luau::findMetatableEntry(singletonTypes, errors, type, entry, location);
if (addErrors)
reportErrors(errors);
return result;
@ -2034,8 +2035,8 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (FFlag::LuauLowerBoundsCalculation)
{
auto [t, ok] = normalize(addType(UnionTypeVar{std::move(goodOptions)}), currentModule,
*iceHandler); // FIXME Inefficient. We craft a UnionTypeVar and immediately throw it away.
// 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)
reportError(location, NormalizationTooComplex{});
@ -2642,8 +2643,8 @@ TypeId TypeChecker::checkRelationalOperation(
std::string metamethodName = opToMetaTableEntry(expr.op);
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType));
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType));
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType), singletonTypes);
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType), singletonTypes);
if (leftMetatable != rightMetatable)
{
@ -2654,7 +2655,7 @@ TypeId TypeChecker::checkRelationalOperation(
{
for (TypeId leftOption : utv)
{
if (getMetatable(follow(leftOption)) == rightMetatable)
if (getMetatable(follow(leftOption), singletonTypes) == rightMetatable)
{
matches = true;
break;
@ -2668,7 +2669,7 @@ TypeId TypeChecker::checkRelationalOperation(
{
for (TypeId rightOption : utv)
{
if (getMetatable(follow(rightOption)) == leftMetatable)
if (getMetatable(follow(rightOption), singletonTypes) == leftMetatable)
{
matches = true;
break;
@ -4113,7 +4114,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
std::vector<TypeId> adjustedArgTypes;
auto it = begin(argPack);
auto endIt = end(argPack);
Widen widen{&currentModule->internalTypes};
Widen widen{&currentModule->internalTypes, singletonTypes};
for (; it != endIt; ++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)
{
auto [t, ok] = Luau::normalize(ty, currentModule, *iceHandler);
auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok)
reportError(location, NormalizationTooComplex{});
return t;
@ -4664,7 +4665,7 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (FFlag::LuauLowerBoundsCalculation && ftv)
{
auto [t, ok] = Luau::normalize(ty, currentModule, *iceHandler);
auto [t, ok] = Luau::normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok)
reportError(location, NormalizationTooComplex{});
return t;
@ -4701,13 +4702,13 @@ TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
{
if (FFlag::LuauLowerBoundsCalculation)
{
auto [t, ok] = normalize(ty, currentModule, *iceHandler);
auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok)
reportError(location, NormalizationTooComplex{});
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);
if (anyification.normalizationTooComplex)
reportError(location, NormalizationTooComplex{});
@ -4724,13 +4725,13 @@ TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location lo
{
if (FFlag::LuauLowerBoundsCalculation)
{
auto [t, ok] = normalize(ty, currentModule, *iceHandler);
auto [t, ok] = normalize(ty, currentModule, singletonTypes, *iceHandler);
if (!ok)
reportError(location, NormalizationTooComplex{});
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);
if (any.has_value())
return *any;
@ -4868,7 +4869,8 @@ void TypeChecker::merge(RefinementMap& l, const RefinementMap& r)
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)
@ -4883,7 +4885,7 @@ TypeId TypeChecker::freshType(TypeLevel level)
TypeId TypeChecker::singletonType(bool value)
{
return value ? getSingletonTypes().trueType : getSingletonTypes().falseType;
return value ? singletonTypes->trueType : singletonTypes->falseType;
}
TypeId TypeChecker::singletonType(std::string value)
@ -4894,22 +4896,22 @@ TypeId TypeChecker::singletonType(std::string value)
TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope)
{
return getSingletonTypes().errorRecoveryType();
return singletonTypes->errorRecoveryType();
}
TypeId TypeChecker::errorRecoveryType(TypeId guess)
{
return getSingletonTypes().errorRecoveryType(guess);
return singletonTypes->errorRecoveryType(guess);
}
TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope)
{
return getSingletonTypes().errorRecoveryTypePack();
return singletonTypes->errorRecoveryTypePack();
}
TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
{
return getSingletonTypes().errorRecoveryTypePack(guess);
return singletonTypes->errorRecoveryTypePack(guess);
}
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
@ -5836,48 +5838,52 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return;
}
using ConditionFunc = bool(TypeId);
using SenseToTypeIdPredicate = std::function<TypeIdPredicate(bool)>;
auto mkFilter = [](ConditionFunc f, std::optional<TypeId> other = std::nullopt) -> SenseToTypeIdPredicate {
return [f, other](bool sense) -> TypeIdPredicate {
return [f, other, sense](TypeId ty) -> std::optional<TypeId> {
if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownTypeVar>(ty))
return other.value_or(ty);
auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional<TypeId> mapsTo = std::nullopt) {
TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional<TypeId> {
if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownTypeVar>(ty))
return mapsTo.value_or(ty);
if (f(ty) == sense)
return ty;
if (f(ty) == sense)
return ty;
if (isUndecidable(ty))
return other.value_or(ty);
if (isUndecidable(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.
// clang-format off
static const std::unordered_map<std::string, SenseToTypeIdPredicate> primitives{
// Trivial primitives.
{"nil", mkFilter(isNil, nilType)}, // This can still happen when sense is false!
{"string", mkFilter(isString, stringType)},
{"number", mkFilter(isNumber, numberType)},
{"boolean", mkFilter(isBoolean, booleanType)},
{"thread", mkFilter(isThread, threadType)},
// Non-trivial primitives.
{"table", mkFilter([](TypeId ty) -> bool { return isTableIntersection(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty); })},
{"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())
if (typeguardP.kind == "nil")
return refine(isNil, nilType); // This can still happen when sense is false!
else if (typeguardP.kind == "string")
return refine(isString, stringType);
else if (typeguardP.kind == "number")
return refine(isNumber, numberType);
else if (typeguardP.kind == "boolean")
return refine(isBoolean, booleanType);
else if (typeguardP.kind == "thread")
return refine(isThread, threadType);
else if (typeguardP.kind == "table")
{
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return;
return refine([](TypeId ty) -> bool {
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)

View File

@ -9,18 +9,19 @@
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);
std::optional<TypeId> metatable = getMetatable(type);
std::optional<TypeId> metatable = getMetatable(type, singletonTypes);
if (!metatable)
return std::nullopt;
TypeId unwrapped = follow(*metatable);
if (get<AnyTypeVar>(unwrapped))
return getSingletonTypes().anyType;
return singletonTypes->anyType;
const TableTypeVar* mtt = getTableType(unwrapped);
if (!mtt)
@ -36,7 +37,8 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, const st
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))
return ty;
@ -48,7 +50,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
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;
while (mtIndex)
{
@ -69,23 +71,23 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
{
std::optional<TypeId> r = first(follow(itf->retTypes));
if (!r)
return getSingletonTypes().nilType;
return singletonTypes->nilType;
else
return *r;
}
else if (get<AnyTypeVar>(index))
return getSingletonTypes().anyType;
return singletonTypes->anyType;
else
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;
}
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, TypeId type, const std::string& prop,
const Location& location, bool addErrors, InternalErrorReporter& handle)
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)
{
type = follow(type);
@ -97,14 +99,14 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
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);
type = *mtIndex;
}
if (getTableType(type))
{
return findTablePropertyRespectingMeta(errors, type, prop, location);
return findTablePropertyRespectingMeta(singletonTypes, errors, type, prop, location);
}
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)))
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);
else
badOptions.push_back(t);
@ -144,17 +147,17 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
}
if (goodOptions.empty())
return getSingletonTypes().neverType;
return singletonTypes->neverType;
if (goodOptions.size() == 1)
return goodOptions[0];
// TODO: inefficient.
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)
errors.push_back(TypeError{location, NormalizationTooComplex{}});
return ok ? ty : getSingletonTypes().anyType;
return ok ? ty : singletonTypes->anyType;
}
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?
// 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);
}

View File

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

View File

@ -257,12 +257,12 @@ TypeId Widen::clean(TypeId ty)
LUAU_ASSERT(stv);
if (get<StringSingleton>(stv))
return getSingletonTypes().stringType;
return singletonTypes->stringType;
else
{
// If this assert trips, it's likely we now have number singletons.
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;
}
Unifier::Unifier(TypeArena* types, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog)
Unifier::Unifier(TypeArena* types, NotNull<SingletonTypes> singletonTypes, Mode mode, NotNull<Scope> scope, const Location& location,
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
: types(types)
, singletonTypes(singletonTypes)
, mode(mode)
, scope(scope)
, log(parentLog)
@ -409,7 +410,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
promoteTypeLevels(log, types, superFree->level, subTy);
Widen widen{types};
Widen widen{types, singletonTypes};
log.replace(superTy, BoundTypeVar(widen(subTy)));
}
@ -1018,7 +1019,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
{
if (!occursCheck(superTp, subTp))
{
Widen widen{types};
Widen widen{types, singletonTypes};
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
}
}
@ -1162,13 +1163,13 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
while (superIter.good())
{
tryUnify_(*superIter, getSingletonTypes().errorRecoveryType());
tryUnify_(*superIter, singletonTypes->errorRecoveryType());
superIter.advance();
}
while (subIter.good())
{
tryUnify_(*subIter, getSingletonTypes().errorRecoveryType());
tryUnify_(*subIter, singletonTypes->errorRecoveryType());
subIter.advance();
}
@ -1613,7 +1614,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
// Given t1 where t1 = { lower: (t1) -> (a, b...) }
// 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);
if (!mttv)
@ -1658,10 +1659,10 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
TableTypeVar* resultTtv = getMutable<TableTypeVar>(result);
for (auto& [name, prop] : resultTtv->props)
prop.type = deeplyOptional(prop.type, seen);
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, result}});
return types->addType(UnionTypeVar{{singletonTypes->nilType, result}});
}
else
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, ty}});
return types->addType(UnionTypeVar{{singletonTypes->nilType, ty}});
}
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}});
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{}});
}
@ -1960,15 +1961,15 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types,
FFlag::LuauUnknownAndNeverType ? anyTy : getSingletonTypes().anyType, anyTp);
Luau::tryUnifyWithAny(
queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, FFlag::LuauUnknownAndNeverType ? anyTy : singletonTypes->anyType, anyTp);
}
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
{
LUAU_ASSERT(get<Unifiable::Error>(anyTp));
const TypeId anyTy = getSingletonTypes().errorRecoveryType();
const TypeId anyTy = singletonTypes->errorRecoveryType();
std::vector<TypeId> queue;
@ -1982,7 +1983,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
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)
@ -2193,7 +2194,7 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
if (needle == haystack)
{
reportError(TypeError{location, OccursCheckFailed{}});
log.replace(needle, *getSingletonTypes().errorRecoveryType());
log.replace(needle, *singletonTypes->errorRecoveryType());
return true;
}
@ -2250,7 +2251,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
if (needle == haystack)
{
reportError(TypeError{location, OccursCheckFailed{}});
log.replace(needle, *getSingletonTypes().errorRecoveryTypePack());
log.replace(needle, *singletonTypes->errorRecoveryTypePack());
return true;
}
@ -2269,7 +2270,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
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;
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
#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
#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
LOP_FORGLOOP,
// FORGPREP_INEXT/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
// A: target register (see FORGLOOP for register layout)
LOP_FORGPREP_INEXT,
LOP_FORGLOOP_INEXT,
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
// removed in v3
LOP_DEP_FORGLOOP_INEXT,
// 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_FORGLOOP_NEXT,
// removed in v3
LOP_DEP_FORGLOOP_NEXT,
// GETVARARGS: copy variables into the target register from vararg storage for current function
// A: target register
@ -343,12 +345,9 @@ enum LuauOpcode
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
LOP_CAPTURE,
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants)
// A: source register 1
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
// AUX: constant table index
LOP_JUMPIFEQK,
LOP_JUMPIFNOTEQK,
// removed in v3
LOP_DEP_JUMPIFEQK,
LOP_DEP_JUMPIFNOTEQK,
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
// A: builtin function id (see LuauBuiltinFunction)

View File

@ -73,8 +73,6 @@ static int getOpLength(LuauOpcode op)
case LOP_SETLIST:
case LOP_FORGLOOP:
case LOP_LOADKX:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_JUMPXEQKNIL:
@ -106,12 +104,8 @@ inline bool isJumpD(LuauOpcode op)
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
@ -1247,13 +1241,6 @@ void BytecodeBuilder::validate() const
VJUMP(LUAU_INSN_D(insn));
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_JUMPXEQKB:
VREG(LUAU_INSN_A(insn));
@ -1360,9 +1347,7 @@ void BytecodeBuilder::validate() const
break;
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
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
VJUMP(LUAU_INSN_D(insn));
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);
break;
case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_GETVARARGS:
formatAppend(result, "GETVARARGS R%d %d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn) - 1);
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));
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:
formatAppend(result, "JUMPXEQKNIL R%d L%d%s\n", LUAU_INSN_A(insn), targetLabel, *code >> 31 ? " NOT" : "");
code++;

View File

@ -3457,14 +3457,6 @@ struct Compiler
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)
{
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
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
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" }'
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
luau: $(REPL_CLI_TARGET)
ln -fs $^ $@

View File

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

View File

@ -35,6 +35,15 @@ enum lua_Status
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 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 void* lua_getthreaddata(lua_State* L);
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

View File

@ -1008,6 +1008,23 @@ int lua_status(lua_State* L)
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)
{
return L->userdata;

View File

@ -5,38 +5,16 @@
#include "lstate.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_BREAK -2
static const char* const statnames[] = {"running", "suspended", "normal", "dead"};
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 const char* const statnames[] = {"running", "suspended", "normal", "dead", "dead"}; // dead appears twice for LUA_COERR and LUA_COFIN
static int costatus(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
lua_pushstring(L, statnames[auxstatus(L, co)]);
lua_pushstring(L, statnames[lua_costatus(L, co)]);
return 1;
}
@ -45,8 +23,8 @@ static int auxresume(lua_State* L, lua_State* co, int narg)
// error handling for edge cases
if (co->status != LUA_YIELD)
{
int status = auxstatus(L, co);
if (status != CO_SUS)
int status = lua_costatus(L, co);
if (status != LUA_COSUS)
{
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
return CO_STATUS_ERROR;
@ -236,8 +214,8 @@ static int coclose(lua_State* L)
lua_State* co = lua_tothread(L, 1);
luaL_argexpected(L, co, 1, "thread");
int status = auxstatus(L, co);
if (status != CO_DEAD && status != CO_SUS)
int status = lua_costatus(L, co);
if (status != LUA_COFIN && status != LUA_COERR && status != LUA_COSUS)
luaL_error(L, "cannot close %s coroutine", statnames[status]);
if (co->status == LUA_OK || co->status == LUA_YIELD)

View File

@ -123,6 +123,7 @@
LUAU_FASTFLAGVARIABLE(LuauSimplerUpval, false)
LUAU_FASTFLAGVARIABLE(LuauNoSleepBit, false)
LUAU_FASTFLAGVARIABLE(LuauEagerShrink, false)
LUAU_FASTFLAGVARIABLE(LuauFasterSweep, false)
#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)
{
LUAU_ASSERT(!FFlag::LuauFasterSweep);
global_State* g = L->global;
int deadmask = otherwhite(g);
@ -890,22 +892,62 @@ static int sweepgcopage(lua_State* L, lua_Page* page)
int 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
if (gco->gch.tt == LUA_TNIL)
continue;
global_State* g = L->global;
// when true is returned it means that the element was deleted
if (sweepgco(L, page, gco))
int deadmask = otherwhite(g);
LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); // make sure we never sweep fixed objects
int newwhite = luaC_white(g);
for (char* pos = start; pos != end; pos += blockSize)
{
LUAU_ASSERT(busyBlocks > 0);
GCObject* gco = (GCObject*)pos;
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
// skip memory blocks that are already freed
if (gco->gch.tt == LUA_TNIL)
continue;
// is the object alive?
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
{
LUAU_ASSERT(!isdead(g, gco));
// make it white (for next cycle)
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
}
else
{
LUAU_ASSERT(isdead(g, gco));
freeobj(L, gco, page);
// if the last block was removed, page would be removed as well
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
}
}
}
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?
if (g->sweepgcopage == NULL)
{
// don't forget to visit main thread
sweepgco(L, NULL, obj2gco(g->mainthread));
// don't forget to visit main thread, it's the only object not allocated in GCO pages
if (FFlag::LuauFasterSweep)
{
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
makewhite(g, obj2gco(g->mainthread)); // make it white (for next cycle)
}
else
{
sweepgco(L, NULL, obj2gco(g->mainthread));
}
shrinkbuffers(L);
g->gcstate = GCSpause; // end collection
}
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_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_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_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_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
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_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_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_CASE(LOP_FORGLOOP_INEXT)
VM_CASE(LOP_DEP_FORGLOOP_INEXT)
{
VM_INTERRUPT();
Instruction insn = *pc++;
@ -2473,7 +2473,7 @@ static void luau_execute(lua_State* L)
VM_NEXT();
}
VM_CASE(LOP_FORGLOOP_NEXT)
VM_CASE(LOP_DEP_FORGLOOP_NEXT)
{
VM_INTERRUPT();
Instruction insn = *pc++;
@ -2748,7 +2748,7 @@ static void luau_execute(lua_State* L)
LUAU_UNREACHABLE();
}
VM_CASE(LOP_JUMPIFEQK)
VM_CASE(LOP_DEP_JUMPIFEQK)
{
Instruction insn = *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++;
uint32_t aux = *pc;

View File

@ -107,8 +107,8 @@ struct ACFixture : ACFixtureImpl<Fixture>
ACFixture()
: ACFixtureImpl<Fixture>()
{
addGlobalBinding(frontend.typeChecker, "table", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeChecker, "math", Binding{typeChecker.anyType});
addGlobalBinding(frontend, "table", Binding{typeChecker.anyType});
addGlobalBinding(frontend, "math", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeCheckerForAutocomplete, "table", Binding{typeChecker.anyType});
addGlobalBinding(frontend.typeCheckerForAutocomplete, "math", Binding{typeChecker.anyType});
}
@ -3200,8 +3200,6 @@ a.@1
TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent")
{
ScopedFastFlag sff("LuauAutocompleteFixGlobalOrder", true);
check(R"(
local myLocal = 4
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),
bit32.extract(-1, 31),
bit32.replace(100, 1, 0),
math.log(100, 10),
(type("fin"))
)",
0, 2),
@ -6153,8 +6154,9 @@ LOADN R45 1
LOADN R46 8
LOADN R47 1
LOADN R48 101
LOADK R49 K3
RETURN R0 50
LOADN R49 2
LOADK R50 K3
RETURN R0 51
)");
}
@ -6166,7 +6168,12 @@ return
math.max(1, true),
string.byte("abc", 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),
R"(
@ -6193,11 +6200,96 @@ LOADN R6 2
LOADK R7 K14
FASTCALL 34 L4
GETIMPORT R4 16
CALL R4 3 -1
L4: RETURN R0 -1
CALL R4 3 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")
{
CHECK_EQ("\n" + compileFunction(R"(

View File

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

View File

@ -26,10 +26,10 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
)");
cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
NotNull<Scope> rootScope{cgb.rootScope};
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
cs.run();
@ -47,10 +47,10 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
)");
cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
NotNull<Scope> rootScope{cgb.rootScope};
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
cs.run();
@ -74,12 +74,12 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
)");
cgb.visit(block);
NotNull<Scope> rootScope = NotNull(cgb.rootScope);
NotNull<Scope> rootScope{cgb.rootScope};
ToStringOptions opts;
NullModuleResolver resolver;
ConstraintSolver cs{&arena, rootScope, "MainModule", NotNull(&resolver), {}};
ConstraintSolver cs{&arena, singletonTypes, rootScope, "MainModule", NotNull(&resolver), {}, &logger};
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
#include "Luau/Parser.h"
#include "ScopedFlags.h"
#include "doctest.h"
using namespace Luau;
@ -223,4 +225,21 @@ end
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();

View File

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

View File

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

View File

@ -81,8 +81,8 @@ struct FrontendFixture : BuiltinsFixture
{
FrontendFixture()
{
addGlobalBinding(typeChecker, "game", frontend.typeChecker.anyType, "@test");
addGlobalBinding(typeChecker, "script", frontend.typeChecker.anyType, "@test");
addGlobalBinding(frontend, "game", 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()}};
}
struct LValueFixture
{
SingletonTypes singletonTypes;
};
TEST_SUITE_BEGIN("LValue");
TEST_CASE("Luau_merge_hashmap_order")
TEST_CASE_FIXTURE(LValueFixture, "Luau_merge_hashmap_order")
{
std::string a = "a";
std::string b = "b";
std::string c = "c";
RefinementMap m{{
{mkSymbol(b), getSingletonTypes().stringType},
{mkSymbol(c), getSingletonTypes().numberType},
{mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), singletonTypes.numberType},
}};
RefinementMap other{{
{mkSymbol(a), getSingletonTypes().stringType},
{mkSymbol(b), getSingletonTypes().stringType},
{mkSymbol(c), getSingletonTypes().booleanType},
{mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), singletonTypes.booleanType},
}};
TypeArena arena;
@ -66,21 +71,21 @@ TEST_CASE("Luau_merge_hashmap_order")
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 b = "b";
std::string c = "c";
RefinementMap m{{
{mkSymbol(a), getSingletonTypes().stringType},
{mkSymbol(b), getSingletonTypes().stringType},
{mkSymbol(c), getSingletonTypes().numberType},
{mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), singletonTypes.numberType},
}};
RefinementMap other{{
{mkSymbol(b), getSingletonTypes().stringType},
{mkSymbol(c), getSingletonTypes().booleanType},
{mkSymbol(b), singletonTypes.stringType},
{mkSymbol(c), singletonTypes.booleanType},
}};
TypeArena arena;
@ -96,7 +101,7 @@ TEST_CASE("Luau_merge_hashmap_order2")
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 b = "b";
@ -105,15 +110,15 @@ TEST_CASE("one_map_has_overlap_at_end_whereas_other_has_it_in_start")
std::string e = "e";
RefinementMap m{{
{mkSymbol(a), getSingletonTypes().stringType},
{mkSymbol(b), getSingletonTypes().numberType},
{mkSymbol(c), getSingletonTypes().booleanType},
{mkSymbol(a), singletonTypes.stringType},
{mkSymbol(b), singletonTypes.numberType},
{mkSymbol(c), singletonTypes.booleanType},
}};
RefinementMap other{{
{mkSymbol(c), getSingletonTypes().stringType},
{mkSymbol(d), getSingletonTypes().numberType},
{mkSymbol(e), getSingletonTypes().booleanType},
{mkSymbol(c), singletonTypes.stringType},
{mkSymbol(d), singletonTypes.numberType},
{mkSymbol(e), singletonTypes.booleanType},
}};
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)]));
}
TEST_CASE("hashing_lvalue_global_prop_access")
TEST_CASE_FIXTURE(LValueFixture, "hashing_lvalue_global_prop_access")
{
std::string t1 = "t";
std::string x1 = "x";
@ -154,13 +159,13 @@ TEST_CASE("hashing_lvalue_global_prop_access")
CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2));
RefinementMap m;
m[t_x1] = getSingletonTypes().stringType;
m[t_x2] = getSingletonTypes().numberType;
m[t_x1] = singletonTypes.stringType;
m[t_x2] = singletonTypes.numberType;
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 x1 = "x";
@ -183,8 +188,8 @@ TEST_CASE("hashing_lvalue_local_prop_access")
CHECK_EQ(LValueHasher{}(t_x2), LValueHasher{}(t_x2));
RefinementMap m;
m[t_x1] = getSingletonTypes().stringType;
m[t_x2] = getSingletonTypes().numberType;
m[t_x1] = singletonTypes.stringType;
m[t_x2] = singletonTypes.numberType;
CHECK_EQ(2, m.size());
}

View File

@ -35,7 +35,7 @@ TEST_CASE_FIXTURE(Fixture, "UnknownGlobal")
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
{
// 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)");
@ -49,7 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
// Normally this would be defined externally, so hack it in for testing
const char* deprecationReplacementString = "";
addGlobalBinding(typeChecker, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});
addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});
LintResult result = lintTyped("Version()");
@ -380,7 +380,7 @@ return bar()
TEST_CASE_FIXTURE(Fixture, "ImportUnused")
{
// 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"(
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"}}};
addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}});
addGlobalBinding(frontend, "Color3", Binding{colorType, {}});
freeze(typeChecker.globalTypes);
@ -1737,8 +1737,6 @@ local _ = 0x0xffffffffffffffffffffffffffffffffff
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
{
ScopedFastFlag sff("LuauLintComparisonPrecedence", true);
LintResult result = lint(R"(
local a, b = ...

View File

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

View File

@ -15,13 +15,13 @@ struct NormalizeFixture : Fixture
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);
@ -32,23 +32,23 @@ void createSomeClasses(TypeChecker& typeChecker)
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
addGlobalBinding(typeChecker, "Parent", {parentType});
typeChecker.globalScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
addGlobalBinding(frontend, "Parent", {parentType});
frontend.getGlobalScope()->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
TypeId childType = arena.addType(ClassTypeVar{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
ClassTypeVar* childClass = getMutable<ClassTypeVar>(childType);
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
addGlobalBinding(typeChecker, "Child", {childType});
typeChecker.globalScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
addGlobalBinding(frontend, "Child", {childType});
frontend.getGlobalScope()->exportedTypeBindings["Child"] = TypeFun{{}, childType};
TypeId unrelatedType = arena.addType(ClassTypeVar{"Unrelated", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"});
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
addGlobalBinding(frontend, "Unrelated", {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);
freeze(arena);
@ -508,7 +508,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_table")
TEST_CASE_FIXTURE(NormalizeFixture, "classes")
{
createSomeClasses(typeChecker);
createSomeClasses(frontend);
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};
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
// 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);
TypeId tType = requireType("t");
normalize(tType, tempModule, *typeChecker.iceHandler);
normalize(tType, tempModule, singletonTypes, *typeChecker.iceHandler);
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")
{
createSomeClasses(typeChecker);
createSomeClasses(frontend);
CheckResult result = check(R"(
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 "doctest.h"

View File

@ -76,8 +76,8 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
Location{{1, 21}, {1, 26}},
getMainSourceModule()->name,
TypeMismatch{
getSingletonTypes().numberType,
getSingletonTypes().stringType,
singletonTypes->numberType,
singletonTypes->stringType,
},
});
}
@ -87,8 +87,8 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
Location{{1, 8}, {1, 26}},
getMainSourceModule()->name,
TypeMismatch{
getSingletonTypes().numberType,
getSingletonTypes().stringType,
singletonTypes->numberType,
singletonTypes->stringType,
},
});
}
@ -501,7 +501,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
CheckResult result = check("type t10<x> = typeof(table)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeId ty = getGlobalBinding(frontend.typeChecker, "table");
TypeId ty = getGlobalBinding(frontend, "table");
CHECK_EQ(toString(ty), "table");
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")
{
addGlobalBinding(frontend.typeChecker, "script", frontend.typeChecker.anyType, "@test");
addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
fileResolver.source["Modules/Main"] = R"(
--!strict
@ -583,7 +583,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file")
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"(
--!strict
@ -609,7 +609,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type")
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"(
--!strict

View File

@ -32,7 +32,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {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"});
@ -45,7 +45,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {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"});
@ -58,7 +58,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {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"});
@ -71,7 +71,7 @@ struct ClassFixture : BuiltinsFixture
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
};
typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
addGlobalBinding(typeChecker, "AnotherChild", childClassType, "@test");
addGlobalBinding(frontend, "AnotherChild", childClassType, "@test");
TypeId vector2MetaType = arena.addType(TableTypeVar{});
@ -89,7 +89,7 @@ struct ClassFixture : BuiltinsFixture
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {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)
persist(tf.type);

View File

@ -19,13 +19,13 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
declare foo2: typeof(foo)
)");
TypeId globalFooTy = getGlobalBinding(frontend.typeChecker, "foo");
TypeId globalFooTy = getGlobalBinding(frontend, "foo");
CHECK_EQ(toString(globalFooTy), "number");
TypeId globalBarTy = getGlobalBinding(frontend.typeChecker, "bar");
TypeId globalBarTy = getGlobalBinding(frontend, "bar");
CHECK_EQ(toString(globalBarTy), "(number) -> string");
TypeId globalFoo2Ty = getGlobalBinding(frontend.typeChecker, "foo2");
TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
CHECK_EQ(toString(globalFoo2Ty), "number");
CheckResult result = check(R"(
@ -48,20 +48,20 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
declare function var(...: any): string
)");
TypeId globalFooTy = getGlobalBinding(frontend.typeChecker, "foo");
TypeId globalFooTy = getGlobalBinding(frontend, "foo");
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));
CHECK_EQ(toString(globalAsdfTy->type), "number | string");
TypeId globalBarTy = getGlobalBinding(frontend.typeChecker, "bar");
TypeId globalBarTy = getGlobalBinding(frontend, "bar");
CHECK_EQ(toString(globalBarTy), "(number) -> string");
TypeId globalFoo2Ty = getGlobalBinding(frontend.typeChecker, "foo2");
TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
CHECK_EQ(toString(globalFoo2Ty), "number");
TypeId globalVarTy = getGlobalBinding(frontend.typeChecker, "var");
TypeId globalVarTy = getGlobalBinding(frontend, "var");
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);
REQUIRE(!parseFailResult.success);
std::optional<Binding> fooTy = tryGetGlobalBinding(typeChecker, "foo");
std::optional<Binding> fooTy = tryGetGlobalBinding(frontend, "foo");
CHECK(!fooTy.has_value());
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");
REQUIRE(!checkFailResult.success);
std::optional<Binding> barTy = tryGetGlobalBinding(typeChecker, "bar");
std::optional<Binding> barTy = tryGetGlobalBinding(frontend, "bar");
CHECK(!barTy.has_value());
}

View File

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

View File

@ -353,6 +353,9 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
{
ScopedFastFlag sff[] = {
{"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"(
@ -367,6 +370,9 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
{
ScopedFastFlag sff[] = {
{"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"(
@ -588,9 +594,9 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
};
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 option1 = arena.addType(UnionTypeVar{{nilType, free1}});
@ -600,7 +606,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
InternalErrorReporter 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);

View File

@ -50,7 +50,7 @@ struct RefinementClassFixture : Fixture
{"Y", 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"});
@ -58,21 +58,21 @@ struct RefinementClassFixture : Fixture
TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
TypeId isA = arena.addType(FunctionTypeVar{isAParams, isARets});
getMutable<FunctionTypeVar>(isA)->magicFunction = magicFunctionInstanceIsA;
normalize(isA, scope, arena, *typeChecker.iceHandler);
normalize(isA, scope, arena, singletonTypes, *typeChecker.iceHandler);
getMutable<ClassTypeVar>(inst)->props = {
{"Name", Property{typeChecker.stringType}},
{"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"});
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"});
getMutable<ClassTypeVar>(part)->props = {
{"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["Instance"] = TypeFun{{}, inst};

View File

@ -18,7 +18,7 @@ struct TryUnifyFixture : Fixture
InternalErrorReporter 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");

View File

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

View File

@ -273,7 +273,7 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
TypeId root = &ttvTweenResult;
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{});

View File

@ -10,9 +10,6 @@ local ignore =
-- 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.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)

View File

@ -163,6 +163,7 @@ BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.class_definition_overload_metamethods
DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
FrontendTest.ast_node_at_position
@ -199,7 +200,6 @@ GenericsTests.generic_functions_in_types
GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method
GenericsTests.generic_type_pack_parentheses
GenericsTests.generic_type_pack_syntax
GenericsTests.generic_type_pack_unification1
GenericsTests.generic_type_pack_unification2
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.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weird_fail_to_unify_type_pack
ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined
ProvisionalTests.xpcall_returns_what_f_returns
RefinementTest.and_constraint
RefinementTest.and_or_peephole_refinement
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
@ -494,13 +492,10 @@ ToString.function_type_with_argument_names_generic
ToString.no_parentheses_around_cyclic_function_type_in_union
ToString.toStringDetailed2
ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack
ToString.toStringNamedFunction_hide_type_params
ToString.toStringNamedFunction_id
ToString.toStringNamedFunction_map
ToString.toStringNamedFunction_overrides_param_names
ToString.toStringNamedFunction_variadics
TranspilerTests.type_lists_should_be_emitted_correctly
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification
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_return_values
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_with_custom_iterator
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.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.methods_are_topologically_sorted
TypeInferOOP.nonstrict_self_mismatch_tail
TypeInferOperators.and_adds_boolean
TypeInferOperators.and_adds_boolean_no_superfluous_union
TypeInferOperators.and_binexps_dont_unify
@ -689,6 +682,7 @@ TypeInferOperators.unary_not_is_boolean
TypeInferOperators.unknown_type_in_comparison
TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.singleton_types
TypeInferPrimitives.string_function_other
TypeInferPrimitives.string_index
TypeInferPrimitives.string_length
@ -730,7 +724,6 @@ TypePackTests.type_alias_type_packs_nested
TypePackTests.type_pack_hidden_free_tail_infinite_growth
TypePackTests.type_pack_type_parameters
TypePackTests.varargs_inference_through_multiple_scopes
TypePackTests.variadic_pack_syntax
TypePackTests.variadic_packs
TypeSingletons.bool_singleton_subtype
TypeSingletons.bool_singletons